# Creating a Signal

### What is a Signal?

A **Signal** is an AI-powered research request that answers a specific question about a company or contact. Think of it as asking an AI research assistant to investigate and provide verified answers with sources.

When you create a signal, Saber's AI:

1. 🔍 **Researches** the target (company website, LinkedIn, public data sources)
2. 🤖 **Analyzes** the information using advanced AI models
3. ✅ **Verifies** findings against multiple sources
4. 📊 **Structures** the answer in your requested format
5. 🔗 **Provides** confidence scores and source citations

Signals support multiple answer types: text, numbers, booleans, lists, percentages, currency, URLs, and contacts.

***

### Creating Signals

**Company Signals** analyze company domains to answer business intelligence questions like:

* Technology stack and tools used
* Company size, funding, and growth metrics
* Market positioning and competitors
* Products and services offered

**Contact Signals** analyze LinkedIn profiles to answer questions about individuals:

* Professional interests and expertise
* Career history and experience
* Engagement and activity patterns
* Communication preferences

**Batch Creation** allows you to efficiently create multiple signals at once using templates.

## Create a company signal synchronously

> \*\*⭐ This is the recommended way to create signals and get results\*\* instead of async + webhooks.\
> \
> Creates a signal and waits synchronously for it to complete, returning the result in the same request.\
> This endpoint blocks until the signal processing finishes or a timeout is reached.\
> \
> \---\
> \
> \## Why Use the Sync Endpoint?\
> \
> \*\*Advantages over async + webhooks:\*\*\
> \- ✅ Get results immediately in one request\
> \- ✅ No webhook hosting required\
> \- ✅ No polling implementation needed\
> \- ✅ Simpler integration\
> \- ✅ Better for synchronous workflows\
> \
> \---\
> \
> \## How It Works\
> \
> 1\. Send your signal creation request (same payload as regular POST /v1/companies/signals)\
> 2\. The endpoint creates the signal and polls for completion\
> 3\. Returns the completed signal with results when processing finishes\
> 4\. If timeout is reached, returns the latest status (processing)\
> \
> \---\
> \
> \## Timeout Configuration\
> \
> \- \*\*Default timeout:\*\* Configured per API key\
> \- \*\*Max timeout:\*\* 900 seconds (15 minutes)\
> \- \*\*Custom timeout:\*\* Use \`X-Sbr-Timeout-Sec\` header to set custom timeout (up to max)\
> \- \*\*Error on timeout:\*\* Use \`X-Sbr-Timeout-Error: true\` header to return 408 on timeout instead of latest status\
> \
> \---\
> \
> \## Response Codes\
> \
> \- \`200 OK\` - Signal completed successfully\
> \- \`202 Accepted\` - Timeout reached, signal still processing\
> \- \`408 Request Timeout\` - Timeout reached (when X-Sbr-Timeout-Error: true)\
> \
> \---\
> \
> \## Using with Templates\
> \
> Use \`signalTemplateId\` instead of defining questions inline for standardized research:\
> \
> \`\`\`json\
> {\
> &#x20; "domain": "acme.com",\
> &#x20; "signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"\
> }\
> \`\`\`\
> \
> Templates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via \`POST /v1/companies/signals/templates\`.<br>

````json
{"openapi":"3.2.0","info":{"title":"Saber Platform API","version":"1.0.0"},"tags":[{"name":"Creating a Signal","summary":"Creating a Signal","kind":"nav","description":"## What is a Signal?\n\nA **Signal** is an AI-powered research request that answers a specific question about a company or contact.\nThink of it as asking an AI research assistant to investigate and provide verified answers with sources.\n\nWhen you create a signal, Saber's AI:\n1. 🔍 **Researches** the target (company website, LinkedIn, public data sources)\n2. 🤖 **Analyzes** the information using advanced AI models\n3. ✅ **Verifies** findings against multiple sources\n4. 📊 **Structures** the answer in your requested format\n5. 🔗 **Provides** confidence scores and source citations\n\nSignals support multiple answer types: text, numbers, booleans, lists, percentages, currency, URLs, and contacts.\n\n---\n\n## Creating Signals\n\n**Company Signals** analyze company domains to answer business intelligence questions like:\n- Technology stack and tools used\n- Company size, funding, and growth metrics\n- Market positioning and competitors\n- Products and services offered\n\n**Contact Signals** analyze LinkedIn profiles to answer questions about individuals:\n- Professional interests and expertise\n- Career history and experience\n- Engagement and activity patterns\n- Communication preferences\n\n**Batch Creation** allows you to efficiently create multiple signals at once using templates.\n"}],"servers":[{"url":"https://api.saber.app","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"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."}},"schemas":{"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},"question":{"type":"string","description":"The research question to ask about the company","minLength":1,"maxLength":500},"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"},"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},"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},"weight":{"type":"string","description":"The importance/weight of the signal","enum":["important","nice_to_have","not_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"},"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},"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"}}}}},"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"}},"additionalProperties":false},"SignalDetailResponse":{"allOf":[{"$ref":"#/components/schemas/SignalResponse"},{"type":"object","properties":{"completedAt":{"type":"string","format":"date-time","description":"When the signal processing completed (only present when status is completed)"},"answer":{"oneOf":[{"$ref":"#/components/schemas/Answer"},{"type":"null"}],"description":"The AI-generated answer (null when status is not completed)"},"reasoning":{"type":"string","description":"Detailed reasoning for the answer (only present when status is completed)"},"confidence":{"type":"number","format":"float","minimum":0,"maximum":1,"description":"Confidence score for the answer (0-1, only present when status is completed)"},"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"},"title":{"type":"string"},"snippet":{"type":"string"}}}},"metadata":{"type":"object","description":"Additional metadata about the processing (only present when status is completed)","additionalProperties":true,"properties":{"companyName":{"type":"string","description":"Company name (for company signals)"},"processingTimeMs":{"type":"number","description":"Processing time in milliseconds"},"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"},"reason":{"type":"string","enum":["rate_limited","disconnected"],"description":"Reason for degraded status (only present when status is degraded)"}}}}},"linkedInContext":{"$ref":"#/components/schemas/LinkedInContext"}}},"error":{"type":"string","description":"Error message (only present when status is failed)"},"errorCode":{"type":"string","description":"Machine-readable error code (only present when status is failed)","enum":["LINKEDIN_RATE_LIMIT_EXCEEDED","LINKEDIN_CONNECTOR_REQUIRED","CONNECTOR_UNAVAILABLE","VALIDATION_ERROR"]},"errorAction":{"type":"string","description":"Suggested action to resolve the error (only present when status is failed)"},"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"]},"company":{"$ref":"#/components/schemas/CompanyFirmographics","description":"Company firmographic data (only present in webhook payloads for company signals)"}}}]},"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"}}},"CompanySignalResponse":{"description":"Response for company signals","allOf":[{"$ref":"#/components/schemas/BaseSignalResponse"},{"type":"object","properties":{"signalType":{"type":"string","enum":["COMPANY"]},"domain":{"type":"string","description":"The domain that was researched"}},"required":["domain"]}]},"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}$"},"signalType":{"type":"string","description":"Type of signal (company or contact)","enum":["COMPANY","CONTACT"]},"status":{"type":"string","description":"Current processing status of the signal","enum":["processing","completed","failed"]},"question":{"type":"string","description":"The research question"},"answerType":{"type":"string","description":"Answer type for this signal","enum":["boolean","number","percentage","currency","open_text","url","list","json_schema","contacts","contact_posts"]},"createdAt":{"type":"string","format":"date-time","description":"When the signal was created"}},"required":["id","signalType","status","question","createdAt"]},"ContactSignalResponse":{"description":"Response for contact signals","allOf":[{"$ref":"#/components/schemas/BaseSignalResponse"},{"type":"object","properties":{"signalType":{"type":"string","enum":["CONTACT"]},"contactProfileUrl":{"type":"string","description":"LinkedIn profile URL"}},"required":["contactProfileUrl"]}]},"Answer":{"description":"Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n","oneOf":[{"type":"object","required":["type","open_text"],"properties":{"type":{"type":"string","enum":["open_text"],"description":"The answer type discriminator"},"open_text":{"$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"}}},"NumberAnswer":{"type":"object","required":["value"],"properties":{"value":{"type":"number","description":"Numeric value"}}},"BooleanAnswer":{"type":"object","required":["value"],"properties":{"value":{"type":"boolean","description":"True or false value"}}},"ListAnswer":{"type":"object","required":["value"],"properties":{"value":{"type":"array","description":"Array of string items (maximum 500 items)","maxItems":500,"items":{"type":"string"}}}},"PercentageAnswer":{"type":"object","required":["value","unit"],"properties":{"value":{"type":"number","description":"Percentage value"},"unit":{"type":"string","description":"Unit symbol (typically \"%\")"}}},"CurrencyAnswer":{"type":"object","required":["value","currency"],"properties":{"value":{"type":"number","description":"Monetary amount"},"currency":{"type":"string","description":"ISO 4217 currency code"}}},"URLAnswer":{"type":"object","required":["value"],"properties":{"value":{"type":"string","format":"uri","description":"Valid URL string"}}},"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"},"education":{"type":"array","description":"Array of education entries","items":{"$ref":"#/components/schemas/Education"}}}},"ContactPosition":{"type":"object","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"},"tenureInMonths":{"type":"integer","description":"Total tenure in months (computed from tenure)"},"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"},"month":{"type":"integer","description":"Month (1-12)"}}},"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"}}}},"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"}}},"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\")"}}},"JSONSchemaAnswer":{"type":"object","description":"The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)","additionalProperties":true},"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"},"mode":{"type":"string","enum":["required","preferred","off"],"description":"LinkedIn connector mode used"},"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"}}},"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"},"used":{"type":"boolean","description":"Whether this capability was used during signal generation"},"rateLimited":{"type":"boolean","description":"Whether this capability was rate-limited"}}},"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}$"},"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"},"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"},"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"},"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"}}},"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"]},"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},"CompanyFirmographics":{"type":"object","description":"Company firmographic and LinkedIn enrichment data included in webhook payloads","properties":{"id":{"type":"string","description":"Company ID"},"name":{"type":"string","description":"Company name"},"domain":{"type":"string","description":"Company domain"},"industry":{"type":"string","description":"Industry classification"},"website":{"type":"string","description":"Company website URL"},"size":{"type":"string","description":"Company size range"},"type":{"type":"string","description":"Company type (e.g. private, public)"},"founded":{"type":"integer","description":"Year the company was founded"},"city":{"type":"string","description":"City of headquarters"},"state":{"type":"string","description":"State or region of headquarters"},"countryCode":{"type":"string","description":"ISO country code"},"handle":{"type":"string","description":"Company handle/slug"},"linkedInId":{"type":"integer","description":"LinkedIn numeric company ID"},"linkedInUrl":{"type":"string","format":"uri","description":"LinkedIn company page URL"},"linkedInFollowers":{"type":"integer","description":"Number of LinkedIn followers"},"employeeCount":{"type":"integer","description":"Approximate employee count from LinkedIn"},"description":{"type":"string","description":"Company description"},"logoUrl":{"type":"string","format":"uri","description":"Company logo URL"}}},"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","INTERNAL","EXTERNAL","TIMEOUT"]},"code":{"type":"string","description":"Stable machine-readable error code. Safe to switch on in client code."},"message":{"type":"string","description":"Human-readable error message. Wording may change; do not match against this string."},"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."},"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"},"message":{"type":"string"}}}},"debug":{"type":"object","description":"Debug information. Only present in development environments.","properties":{"cause":{"type":"string"}}}}}}}}},"paths":{"/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:** Configured per API key\n- **Max timeout:** 900 seconds (15 minutes)\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\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed successfully\n- `202 Accepted` - Timeout reached, signal still processing\n- `408 Request Timeout` - Timeout reached (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","operationId":"createCompanySignalSync","tags":["Creating a Signal"],"parameters":[{"name":"X-Sbr-Timeout-Sec","in":"header","required":false,"description":"Custom timeout in seconds (max 900). Defaults to your API key's configured timeout.","schema":{"type":"integer","minimum":1,"maximum":900}},{"name":"X-Sbr-Timeout-Error","in":"header","required":false,"description":"If \"true\", returns 408 Request Timeout on timeout instead of 202 with latest status","schema":{"type":"string","enum":["true","false"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSignalRequest"}}}},"responses":{"200":{"description":"Signal completed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignalDetailResponse"}}}},"202":{"description":"Timeout reached - signal still processing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignalResponse"}}}},"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 - Signal processing exceeded timeout (only when X-Sbr-Timeout-Error header is set to true)","content":{"application/json":{}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
````

## Create a company signal asynchronously

> Submit a domain and question to create a new research signal. The AI will analyze the domain\
> and provide a structured answer based on the specified answer type.\
> \
> \---\
> \
> \## When to Use This Endpoint\
> \
> Use this endpoint for \*\*event-driven architectures\*\* where you need webhook notifications.\
> For simpler integrations, use the \`/v1/companies/signals/sync\` endpoint (recommended) which returns results immediately.\
> \
> \---\
> \
> \## Webhooks\
> \
> Configure webhooks by including a \`webhookUrl\` parameter in your request.\
> \
> \*\*Webhook events:\*\*\
> \- \`signal.completed\` - Signal processing finished successfully\
> \- \`signal.failed\` - Signal processing failed\
> \
> \*\*Webhook security:\*\*\
> All webhooks include HMAC-SHA256 signatures in the \`X-Webhook-Signature\` header for verification.\
> \
> \*\*Webhook reliability:\*\*\
> \- Failed deliveries are retried up to 25 times with exponential backoff\
> \- Webhook requests timeout after 30 seconds\
> \- Your endpoint should respond with 2xx status code\
> \
> \---\
> \
> \## Caching Behavior\
> \
> Your response will be cached for up to 12 hours by default, unless you set the \`forceRefresh\` flag to true.\
> \
> Cache 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.\
> We 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.\
> \
> Instead you will receive the latest status of the signal immediately in the response.\
> \
> \---\
> \
> \## Using with Templates\
> \
> Use \`signalTemplateId\` instead of defining questions inline for standardized research:\
> \
> \`\`\`json\
> {\
> &#x20; "domain": "acme.com",\
> &#x20; "signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",\
> &#x20; "webhookUrl": "<https://myapp.com/webhooks/signals"\\>
> }\
> \`\`\`\
> \
> Templates let you reuse research questions, qualification criteria, and answer types across multiple companies. Create templates via \`POST /v1/companies/signals/templates\`.<br>

````json
{"openapi":"3.2.0","info":{"title":"Saber Platform API","version":"1.0.0"},"tags":[{"name":"Creating a Signal","summary":"Creating a Signal","kind":"nav","description":"## What is a Signal?\n\nA **Signal** is an AI-powered research request that answers a specific question about a company or contact.\nThink of it as asking an AI research assistant to investigate and provide verified answers with sources.\n\nWhen you create a signal, Saber's AI:\n1. 🔍 **Researches** the target (company website, LinkedIn, public data sources)\n2. 🤖 **Analyzes** the information using advanced AI models\n3. ✅ **Verifies** findings against multiple sources\n4. 📊 **Structures** the answer in your requested format\n5. 🔗 **Provides** confidence scores and source citations\n\nSignals support multiple answer types: text, numbers, booleans, lists, percentages, currency, URLs, and contacts.\n\n---\n\n## Creating Signals\n\n**Company Signals** analyze company domains to answer business intelligence questions like:\n- Technology stack and tools used\n- Company size, funding, and growth metrics\n- Market positioning and competitors\n- Products and services offered\n\n**Contact Signals** analyze LinkedIn profiles to answer questions about individuals:\n- Professional interests and expertise\n- Career history and experience\n- Engagement and activity patterns\n- Communication preferences\n\n**Batch Creation** allows you to efficiently create multiple signals at once using templates.\n"}],"servers":[{"url":"https://api.saber.app","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"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."}},"schemas":{"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},"question":{"type":"string","description":"The research question to ask about the company","minLength":1,"maxLength":500},"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"},"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},"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},"weight":{"type":"string","description":"The importance/weight of the signal","enum":["important","nice_to_have","not_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"},"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},"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"}}}}},"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"}},"additionalProperties":false},"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"}}},"CompanySignalResponse":{"description":"Response for company signals","allOf":[{"$ref":"#/components/schemas/BaseSignalResponse"},{"type":"object","properties":{"signalType":{"type":"string","enum":["COMPANY"]},"domain":{"type":"string","description":"The domain that was researched"}},"required":["domain"]}]},"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}$"},"signalType":{"type":"string","description":"Type of signal (company or contact)","enum":["COMPANY","CONTACT"]},"status":{"type":"string","description":"Current processing status of the signal","enum":["processing","completed","failed"]},"question":{"type":"string","description":"The research question"},"answerType":{"type":"string","description":"Answer type for this signal","enum":["boolean","number","percentage","currency","open_text","url","list","json_schema","contacts","contact_posts"]},"createdAt":{"type":"string","format":"date-time","description":"When the signal was created"}},"required":["id","signalType","status","question","createdAt"]},"ContactSignalResponse":{"description":"Response for contact signals","allOf":[{"$ref":"#/components/schemas/BaseSignalResponse"},{"type":"object","properties":{"signalType":{"type":"string","enum":["CONTACT"]},"contactProfileUrl":{"type":"string","description":"LinkedIn profile URL"}},"required":["contactProfileUrl"]}]},"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","INTERNAL","EXTERNAL","TIMEOUT"]},"code":{"type":"string","description":"Stable machine-readable error code. Safe to switch on in client code."},"message":{"type":"string","description":"Human-readable error message. Wording may change; do not match against this string."},"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."},"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"},"message":{"type":"string"}}}},"debug":{"type":"object","description":"Debug information. Only present in development environments.","properties":{"cause":{"type":"string"}}}}}}}}},"paths":{"/v1/companies/signals":{"post":{"summary":"Create a company signal asynchronously","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**Webhook events:**\n- `signal.completed` - Signal processing finished successfully\n- `signal.failed` - Signal processing failed\n\n**Webhook security:**\nAll webhooks include HMAC-SHA256 signatures in the `X-Webhook-Signature` header for verification.\n\n**Webhook reliability:**\n- Failed deliveries are retried up to 25 times with exponential backoff\n- Webhook requests timeout after 30 seconds\n- Your endpoint should respond with 2xx status code\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","operationId":"createSignal","tags":["Creating a Signal"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSignalRequest"}}}},"responses":{"201":{"description":"Signal created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignalResponse"}}}},"400":{"description":"Bad Request - Malformed request body (invalid JSON)","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","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","headers":{"X-Ratelimit-Limit":{"schema":{"type":"integer"},"description":"Maximum requests allowed per minute"},"X-Ratelimit-Remaining":{"schema":{"type":"integer"},"description":"Requests remaining in current window"},"X-Ratelimit-Reset":{"schema":{"type":"integer","format":"int64"},"description":"Unix timestamp when the rate limit resets"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
````

## Create company signals in batch

> Submit multiple signals and domains to create signals using a Cartesian product pattern.\
> Each signal is combined with each domain to create individual signals.\
> \
> \*\*Two ways to create batch signals:\*\*\
> 1\. \*\*Inline Questions\*\*: Define questions directly in the request for ad-hoc research without creating templates\
> 2\. \*\*Using Templates\*\* (recommended for reusability): Reference pre-created templates by ID for consistent, reusable questions\
> \
> \*\*Cartesian Product Logic:\*\*\
> \- If you submit 2 templates and 3 domains, the system creates 6 signals (2 × 3)\
> \- \*\*Sync mode\*\* (default): Maximum 100 resulting signals (templates × domains ≤ 100)\
> \- \*\*Async mode\*\* (\`async: true\`): Maximum 20,000 resulting signals (templates × domains ≤ 20,000)\
> \
> \*\*Sync Mode (default):\*\*\
> \- Returns inline results with individual signal statuses\
> \- Maximum 100 signals per batch\
> \- Returns 202 with \`results\[]\` array\
> \
> \*\*Async Mode (\`async: true\`):\*\*\
> \- For large batches up to 20,000 signals\
> \- Returns immediately with a \`batchId\` for tracking\
> \- Signals are processed in background chunks\
> \- Returns 202 with \`batchId\`, \`totalSignals\`, \`status: "accepted"\`\
> \- Poll \`/v1/companies/signals/batches/status?domain=...\` for progress\
> \
> \*\*Partial Failure Handling (sync mode):\*\*\
> \- Individual signal failures don't block other signals from processing\
> \- Each signal in the response includes its own status (accepted or failed)\
> \- Returns 202 if at least one signal is scheduled successfully\
> \- Returns 422 if all signals failed to be scheduled\
> \
> \*\*Processing:\*\*\
> \- All signals are processed asynchronously\
> \- No guarantee of processing order\
> \- Use the \`/v1/companies/signals/sync\` endpoint (recommended) to retrieve results as they complete\
> \
> \*\*Webhooks:\*\*\
> \- Each signal in the batch can include a \`webhookUrl\` to receive notifications on completion\
> \- Webhooks fire individually per signal as each one completes processing\
> \- Webhook events: \`signal.completed\` (success) or \`signal.failed\` (failure)\
> \- All webhooks include HMAC-SHA256 signatures in the \`X-Webhook-Signature\` header\
> \
> \*\*Caching Behavior:\*\*\
> \- Signals are cached based on a hash of domain, question, answer type, verification mode, and output schema (if provided)\
> \- If a matching cached result exists, the cached response is returned immediately and \*\*no webhook is sent\*\*\
> \- To force fresh processing (and webhook delivery), set \`forceRefresh: true\` on individual signals\
> \
> \*\*Automatic Summary Generation:\*\*\
> \- Set \`generateSummaryOnComplete\` to \`true\` to automatically generate summaries when all signals in the batch complete\
> \- Summaries are generated per domain (one summary per unique domain in the batch)\
> \- Summary generation is triggered automatically once all signals for a domain have completed\
> \- Use the \`/v1/companies/signals/summaries\` endpoint to retrieve generated summaries<br>

```json
{"openapi":"3.2.0","info":{"title":"Saber Platform API","version":"1.0.0"},"tags":[{"name":"Creating a Signal","summary":"Creating a Signal","kind":"nav","description":"## What is a Signal?\n\nA **Signal** is an AI-powered research request that answers a specific question about a company or contact.\nThink of it as asking an AI research assistant to investigate and provide verified answers with sources.\n\nWhen you create a signal, Saber's AI:\n1. 🔍 **Researches** the target (company website, LinkedIn, public data sources)\n2. 🤖 **Analyzes** the information using advanced AI models\n3. ✅ **Verifies** findings against multiple sources\n4. 📊 **Structures** the answer in your requested format\n5. 🔗 **Provides** confidence scores and source citations\n\nSignals support multiple answer types: text, numbers, booleans, lists, percentages, currency, URLs, and contacts.\n\n---\n\n## Creating Signals\n\n**Company Signals** analyze company domains to answer business intelligence questions like:\n- Technology stack and tools used\n- Company size, funding, and growth metrics\n- Market positioning and competitors\n- Products and services offered\n\n**Contact Signals** analyze LinkedIn profiles to answer questions about individuals:\n- Professional interests and expertise\n- Career history and experience\n- Engagement and activity patterns\n- Communication preferences\n\n**Batch Creation** allows you to efficiently create multiple signals at once using templates.\n"}],"servers":[{"url":"https://api.saber.app","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"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."}},"schemas":{"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":500},"templateId":{"type":"string","format":"uuid","description":"ID of the signal template to use. If provided, other fields (except question) use template settings."},"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"]},"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"]},"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},"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}}}},"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}},"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},"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}},"additionalProperties":false},"SignalBatchResponse":{"type":"object","properties":{"submittedAt":{"type":"string","format":"date-time","description":"Timestamp when the batch was submitted"},"totalSignals":{"type":"integer","description":"Total number of signals created from Cartesian product (templates × domains)","minimum":0,"maximum":100},"accepted":{"type":"integer","description":"Number of signals successfully scheduled for processing","minimum":0},"rejected":{"type":"integer","description":"Number of signals that failed to be scheduled","minimum":0},"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"]},"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"}}},"CompanySignalResponse":{"description":"Response for company signals","allOf":[{"$ref":"#/components/schemas/BaseSignalResponse"},{"type":"object","properties":{"signalType":{"type":"string","enum":["COMPANY"]},"domain":{"type":"string","description":"The domain that was researched"}},"required":["domain"]}]},"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}$"},"signalType":{"type":"string","description":"Type of signal (company or contact)","enum":["COMPANY","CONTACT"]},"status":{"type":"string","description":"Current processing status of the signal","enum":["processing","completed","failed"]},"question":{"type":"string","description":"The research question"},"answerType":{"type":"string","description":"Answer type for this signal","enum":["boolean","number","percentage","currency","open_text","url","list","json_schema","contacts","contact_posts"]},"createdAt":{"type":"string","format":"date-time","description":"When the signal was created"}},"required":["id","signalType","status","question","createdAt"]},"ContactSignalResponse":{"description":"Response for contact signals","allOf":[{"$ref":"#/components/schemas/BaseSignalResponse"},{"type":"object","properties":{"signalType":{"type":"string","enum":["CONTACT"]},"contactProfileUrl":{"type":"string","description":"LinkedIn profile URL"}},"required":["contactProfileUrl"]}]},"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."},"submittedAt":{"type":"string","format":"date-time","description":"Timestamp when the batch was accepted"},"totalSignals":{"type":"integer","description":"Total number of signals that will be processed","minimum":1,"maximum":20000},"status":{"type":"string","description":"Status of the batch submission","enum":["accepted"]},"async":{"type":"boolean","description":"Always `true` for async batch responses"}},"required":["batchId","submittedAt","totalSignals","status","async"]},"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","INTERNAL","EXTERNAL","TIMEOUT"]},"code":{"type":"string","description":"Stable machine-readable error code. Safe to switch on in client code."},"message":{"type":"string","description":"Human-readable error message. Wording may change; do not match against this string."},"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."},"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"},"message":{"type":"string"}}}},"debug":{"type":"object","description":"Debug information. Only present in development environments.","properties":{"cause":{"type":"string"}}}}}}}}},"paths":{"/v1/companies/signals/batch":{"post":{"summary":"Create company signals in batch","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- Webhook events: `signal.completed` (success) or `signal.failed` (failure)\n- All webhooks include HMAC-SHA256 signatures in the `X-Webhook-Signature` header\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","operationId":"createSignalBatch","tags":["Creating a Signal"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSignalBatchRequest"}}}},"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"}]}}}},"400":{"description":"Bad Request - Malformed request body (invalid JSON)","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 or all signals in batch failed","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"$ref":"#/components/schemas/SignalBatchResponse"}]}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Create a contact signal synchronously

> \*\*⭐ This is the recommended way to create contact signals and get results\*\* instead of async + webhooks.\
> \
> Creates a contact signal and waits synchronously for it to complete, returning the result in the same request.\
> Works identically to the company signals sync endpoint but for contact-based signals.\
> \
> \---\
> \
> \## Why Use the Sync Endpoint?\
> \
> \*\*Advantages over async + webhooks:\*\*\
> \- ✅ Get results immediately in one request\
> \- ✅ No webhook hosting required\
> \- ✅ No polling implementation needed\
> \- ✅ Simpler integration\
> \- ✅ Better for synchronous workflows\
> \
> \---\
> \
> \## How It Works\
> \
> 1\. Send your signal creation request (same payload as regular POST /v1/contacts/signals)\
> 2\. The endpoint creates the signal and polls for completion\
> 3\. Returns the completed signal with results when processing finishes\
> 4\. If timeout is reached, returns the latest status (processing)\
> \
> \---\
> \
> \## Timeout Configuration\
> \
> \- \*\*Default timeout:\*\* Configured per API key\
> \- \*\*Max timeout:\*\* 900 seconds (15 minutes)\
> \- \*\*Custom timeout:\*\* Use \`X-Sbr-Timeout-Sec\` header to set custom timeout (up to max)\
> \- \*\*Error on timeout:\*\* Use \`X-Sbr-Timeout-Error: true\` header to return 408 on timeout instead of latest status\
> \
> \---\
> \
> \## Response Codes\
> \
> \- \`200 OK\` - Signal completed successfully\
> \- \`202 Accepted\` - Timeout reached, signal still processing\
> \- \`408 Request Timeout\` - Timeout reached (when X-Sbr-Timeout-Error: true)\
> \
> \---\
> \
> \## Using with Templates\
> \
> Use \`signalTemplateId\` instead of defining questions inline for standardized research:\
> \
> \`\`\`json\
> {\
> &#x20; "contactProfileUrl": "<https://linkedin.com/in/johndoe",\\>
> &#x20; "signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"\
> }\
> \`\`\`\
> \
> Templates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via \`POST /v1/companies/signals/templates\`.<br>

````json
{"openapi":"3.2.0","info":{"title":"Saber Platform API","version":"1.0.0"},"tags":[{"name":"Creating a Signal","summary":"Creating a Signal","kind":"nav","description":"## What is a Signal?\n\nA **Signal** is an AI-powered research request that answers a specific question about a company or contact.\nThink of it as asking an AI research assistant to investigate and provide verified answers with sources.\n\nWhen you create a signal, Saber's AI:\n1. 🔍 **Researches** the target (company website, LinkedIn, public data sources)\n2. 🤖 **Analyzes** the information using advanced AI models\n3. ✅ **Verifies** findings against multiple sources\n4. 📊 **Structures** the answer in your requested format\n5. 🔗 **Provides** confidence scores and source citations\n\nSignals support multiple answer types: text, numbers, booleans, lists, percentages, currency, URLs, and contacts.\n\n---\n\n## Creating Signals\n\n**Company Signals** analyze company domains to answer business intelligence questions like:\n- Technology stack and tools used\n- Company size, funding, and growth metrics\n- Market positioning and competitors\n- Products and services offered\n\n**Contact Signals** analyze LinkedIn profiles to answer questions about individuals:\n- Professional interests and expertise\n- Career history and experience\n- Engagement and activity patterns\n- Communication preferences\n\n**Batch Creation** allows you to efficiently create multiple signals at once using templates.\n"}],"servers":[{"url":"https://api.saber.app","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"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."}},"schemas":{"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},"question":{"type":"string","description":"The research question to ask about the contact","minLength":1,"maxLength":500},"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"},"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},"weight":{"type":"string","description":"The importance/weight of the signal","enum":["important","nice_to_have","not_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"},"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"}}}}},"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"},"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}},"additionalProperties":false},"SignalDetailResponse":{"allOf":[{"$ref":"#/components/schemas/SignalResponse"},{"type":"object","properties":{"completedAt":{"type":"string","format":"date-time","description":"When the signal processing completed (only present when status is completed)"},"answer":{"oneOf":[{"$ref":"#/components/schemas/Answer"},{"type":"null"}],"description":"The AI-generated answer (null when status is not completed)"},"reasoning":{"type":"string","description":"Detailed reasoning for the answer (only present when status is completed)"},"confidence":{"type":"number","format":"float","minimum":0,"maximum":1,"description":"Confidence score for the answer (0-1, only present when status is completed)"},"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"},"title":{"type":"string"},"snippet":{"type":"string"}}}},"metadata":{"type":"object","description":"Additional metadata about the processing (only present when status is completed)","additionalProperties":true,"properties":{"companyName":{"type":"string","description":"Company name (for company signals)"},"processingTimeMs":{"type":"number","description":"Processing time in milliseconds"},"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"},"reason":{"type":"string","enum":["rate_limited","disconnected"],"description":"Reason for degraded status (only present when status is degraded)"}}}}},"linkedInContext":{"$ref":"#/components/schemas/LinkedInContext"}}},"error":{"type":"string","description":"Error message (only present when status is failed)"},"errorCode":{"type":"string","description":"Machine-readable error code (only present when status is failed)","enum":["LINKEDIN_RATE_LIMIT_EXCEEDED","LINKEDIN_CONNECTOR_REQUIRED","CONNECTOR_UNAVAILABLE","VALIDATION_ERROR"]},"errorAction":{"type":"string","description":"Suggested action to resolve the error (only present when status is failed)"},"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"]},"company":{"$ref":"#/components/schemas/CompanyFirmographics","description":"Company firmographic data (only present in webhook payloads for company signals)"}}}]},"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"}}},"CompanySignalResponse":{"description":"Response for company signals","allOf":[{"$ref":"#/components/schemas/BaseSignalResponse"},{"type":"object","properties":{"signalType":{"type":"string","enum":["COMPANY"]},"domain":{"type":"string","description":"The domain that was researched"}},"required":["domain"]}]},"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}$"},"signalType":{"type":"string","description":"Type of signal (company or contact)","enum":["COMPANY","CONTACT"]},"status":{"type":"string","description":"Current processing status of the signal","enum":["processing","completed","failed"]},"question":{"type":"string","description":"The research question"},"answerType":{"type":"string","description":"Answer type for this signal","enum":["boolean","number","percentage","currency","open_text","url","list","json_schema","contacts","contact_posts"]},"createdAt":{"type":"string","format":"date-time","description":"When the signal was created"}},"required":["id","signalType","status","question","createdAt"]},"ContactSignalResponse":{"description":"Response for contact signals","allOf":[{"$ref":"#/components/schemas/BaseSignalResponse"},{"type":"object","properties":{"signalType":{"type":"string","enum":["CONTACT"]},"contactProfileUrl":{"type":"string","description":"LinkedIn profile URL"}},"required":["contactProfileUrl"]}]},"Answer":{"description":"Discriminated union representing different answer types.\nThe 'type' field indicates which specific answer format is used.\n","oneOf":[{"type":"object","required":["type","open_text"],"properties":{"type":{"type":"string","enum":["open_text"],"description":"The answer type discriminator"},"open_text":{"$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"}}},"NumberAnswer":{"type":"object","required":["value"],"properties":{"value":{"type":"number","description":"Numeric value"}}},"BooleanAnswer":{"type":"object","required":["value"],"properties":{"value":{"type":"boolean","description":"True or false value"}}},"ListAnswer":{"type":"object","required":["value"],"properties":{"value":{"type":"array","description":"Array of string items (maximum 500 items)","maxItems":500,"items":{"type":"string"}}}},"PercentageAnswer":{"type":"object","required":["value","unit"],"properties":{"value":{"type":"number","description":"Percentage value"},"unit":{"type":"string","description":"Unit symbol (typically \"%\")"}}},"CurrencyAnswer":{"type":"object","required":["value","currency"],"properties":{"value":{"type":"number","description":"Monetary amount"},"currency":{"type":"string","description":"ISO 4217 currency code"}}},"URLAnswer":{"type":"object","required":["value"],"properties":{"value":{"type":"string","format":"uri","description":"Valid URL string"}}},"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"},"education":{"type":"array","description":"Array of education entries","items":{"$ref":"#/components/schemas/Education"}}}},"ContactPosition":{"type":"object","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"},"tenureInMonths":{"type":"integer","description":"Total tenure in months (computed from tenure)"},"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"},"month":{"type":"integer","description":"Month (1-12)"}}},"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"}}}},"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"}}},"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\")"}}},"JSONSchemaAnswer":{"type":"object","description":"The generated data conforming to the user-provided JSON Schema (payload at answer.jsonSchema directly)","additionalProperties":true},"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"},"mode":{"type":"string","enum":["required","preferred","off"],"description":"LinkedIn connector mode used"},"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"}}},"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"},"used":{"type":"boolean","description":"Whether this capability was used during signal generation"},"rateLimited":{"type":"boolean","description":"Whether this capability was rate-limited"}}},"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}$"},"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"},"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"},"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"},"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"}}},"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"]},"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},"CompanyFirmographics":{"type":"object","description":"Company firmographic and LinkedIn enrichment data included in webhook payloads","properties":{"id":{"type":"string","description":"Company ID"},"name":{"type":"string","description":"Company name"},"domain":{"type":"string","description":"Company domain"},"industry":{"type":"string","description":"Industry classification"},"website":{"type":"string","description":"Company website URL"},"size":{"type":"string","description":"Company size range"},"type":{"type":"string","description":"Company type (e.g. private, public)"},"founded":{"type":"integer","description":"Year the company was founded"},"city":{"type":"string","description":"City of headquarters"},"state":{"type":"string","description":"State or region of headquarters"},"countryCode":{"type":"string","description":"ISO country code"},"handle":{"type":"string","description":"Company handle/slug"},"linkedInId":{"type":"integer","description":"LinkedIn numeric company ID"},"linkedInUrl":{"type":"string","format":"uri","description":"LinkedIn company page URL"},"linkedInFollowers":{"type":"integer","description":"Number of LinkedIn followers"},"employeeCount":{"type":"integer","description":"Approximate employee count from LinkedIn"},"description":{"type":"string","description":"Company description"},"logoUrl":{"type":"string","format":"uri","description":"Company logo URL"}}},"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","INTERNAL","EXTERNAL","TIMEOUT"]},"code":{"type":"string","description":"Stable machine-readable error code. Safe to switch on in client code."},"message":{"type":"string","description":"Human-readable error message. Wording may change; do not match against this string."},"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."},"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"},"message":{"type":"string"}}}},"debug":{"type":"object","description":"Debug information. Only present in development environments.","properties":{"cause":{"type":"string"}}}}}}}}},"paths":{"/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:** Configured per API key\n- **Max timeout:** 900 seconds (15 minutes)\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\n\n---\n\n## Response Codes\n\n- `200 OK` - Signal completed successfully\n- `202 Accepted` - Timeout reached, signal still processing\n- `408 Request Timeout` - Timeout reached (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","operationId":"createContactSignalSync","tags":["Creating a Signal"],"parameters":[{"name":"X-Sbr-Timeout-Sec","in":"header","required":false,"description":"Custom timeout in seconds (max 900). Defaults to your API key's configured timeout.","schema":{"type":"integer","minimum":1,"maximum":900}},{"name":"X-Sbr-Timeout-Error","in":"header","required":false,"description":"If \"true\", returns 408 Request Timeout on timeout instead of 202 with latest status","schema":{"type":"string","enum":["true","false"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContactSignalRequest"}}}},"responses":{"200":{"description":"Signal completed successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SignalDetailResponse"},{"type":"object","properties":{"contactProfileUrl":{"type":"string","format":"uri","description":"Contact profile URL"}}}]}}}},"202":{"description":"Timeout reached - signal still processing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignalResponse"}}}},"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 - Signal processing exceeded timeout (only when X-Sbr-Timeout-Error header is set to true)","content":{"application/json":{}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
````

## Create a contact signal asynchronously

> Submit a LinkedIn URL and question to create a new research signal for an individual contact.\
> The AI will analyze the contact's profile and provide a structured answer based on the specified answer type.\
> \
> Contact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.\
> \
> \---\
> \
> \## When to Use This Endpoint\
> \
> Use this endpoint for \*\*event-driven architectures\*\* where you need webhook notifications.\
> For simpler integrations, use the \`/v1/contacts/signals/sync\` endpoint (recommended) which returns results immediately.\
> \
> \---\
> \
> \## Webhooks\
> \
> Configure webhooks by including a \`webhookUrl\` parameter in your request.\
> \
> \*\*Webhook events:\*\*\
> \- \`signal.completed\` - Signal processing finished successfully\
> \- \`signal.failed\` - Signal processing failed\
> \
> \*\*Webhook security:\*\*\
> All webhooks include HMAC-SHA256 signatures in the \`X-Webhook-Signature\` header for verification.\
> \
> \*\*Webhook reliability:\*\*\
> \- Failed deliveries are retried up to 25 times with exponential backoff\
> \- Webhook requests timeout after 30 seconds\
> \- Your endpoint should respond with 2xx status code\
> \
> \---\
> \
> \## Using with Templates\
> \
> Use \`signalTemplateId\` instead of defining questions inline for standardized research:\
> \
> \`\`\`json\
> {\
> &#x20; "contactProfileUrl": "<https://linkedin.com/in/johndoe",\\>
> &#x20; "signalTemplateId": "a12b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",\
> &#x20; "webhookUrl": "<https://myapp.com/webhooks/signals"\\>
> }\
> \`\`\`\
> \
> Templates let you reuse research questions, qualification criteria, and answer types across multiple contacts. Create templates via \`POST /v1/companies/signals/templates\`.\
> \
> \---\
> \
> Contact signals work similarly to company signals but focus on individual contacts using their LinkedIn profile URLs.<br>

````json
{"openapi":"3.2.0","info":{"title":"Saber Platform API","version":"1.0.0"},"tags":[{"name":"Creating a Signal","summary":"Creating a Signal","kind":"nav","description":"## What is a Signal?\n\nA **Signal** is an AI-powered research request that answers a specific question about a company or contact.\nThink of it as asking an AI research assistant to investigate and provide verified answers with sources.\n\nWhen you create a signal, Saber's AI:\n1. 🔍 **Researches** the target (company website, LinkedIn, public data sources)\n2. 🤖 **Analyzes** the information using advanced AI models\n3. ✅ **Verifies** findings against multiple sources\n4. 📊 **Structures** the answer in your requested format\n5. 🔗 **Provides** confidence scores and source citations\n\nSignals support multiple answer types: text, numbers, booleans, lists, percentages, currency, URLs, and contacts.\n\n---\n\n## Creating Signals\n\n**Company Signals** analyze company domains to answer business intelligence questions like:\n- Technology stack and tools used\n- Company size, funding, and growth metrics\n- Market positioning and competitors\n- Products and services offered\n\n**Contact Signals** analyze LinkedIn profiles to answer questions about individuals:\n- Professional interests and expertise\n- Career history and experience\n- Engagement and activity patterns\n- Communication preferences\n\n**Batch Creation** allows you to efficiently create multiple signals at once using templates.\n"}],"servers":[{"url":"https://api.saber.app","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"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."}},"schemas":{"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},"question":{"type":"string","description":"The research question to ask about the contact","minLength":1,"maxLength":500},"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"},"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},"weight":{"type":"string","description":"The importance/weight of the signal","enum":["important","nice_to_have","not_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"},"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"}}}}},"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"},"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}},"additionalProperties":false},"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"}}},"CompanySignalResponse":{"description":"Response for company signals","allOf":[{"$ref":"#/components/schemas/BaseSignalResponse"},{"type":"object","properties":{"signalType":{"type":"string","enum":["COMPANY"]},"domain":{"type":"string","description":"The domain that was researched"}},"required":["domain"]}]},"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}$"},"signalType":{"type":"string","description":"Type of signal (company or contact)","enum":["COMPANY","CONTACT"]},"status":{"type":"string","description":"Current processing status of the signal","enum":["processing","completed","failed"]},"question":{"type":"string","description":"The research question"},"answerType":{"type":"string","description":"Answer type for this signal","enum":["boolean","number","percentage","currency","open_text","url","list","json_schema","contacts","contact_posts"]},"createdAt":{"type":"string","format":"date-time","description":"When the signal was created"}},"required":["id","signalType","status","question","createdAt"]},"ContactSignalResponse":{"description":"Response for contact signals","allOf":[{"$ref":"#/components/schemas/BaseSignalResponse"},{"type":"object","properties":{"signalType":{"type":"string","enum":["CONTACT"]},"contactProfileUrl":{"type":"string","description":"LinkedIn profile URL"}},"required":["contactProfileUrl"]}]}}},"paths":{"/v1/contacts/signals":{"post":{"summary":"Create a contact signal asynchronously","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**Webhook events:**\n- `signal.completed` - Signal processing finished successfully\n- `signal.failed` - Signal processing failed\n\n**Webhook security:**\nAll webhooks include HMAC-SHA256 signatures in the `X-Webhook-Signature` header for verification.\n\n**Webhook reliability:**\n- Failed deliveries are retried up to 25 times with exponential backoff\n- Webhook requests timeout after 30 seconds\n- Your endpoint should respond with 2xx status code\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","operationId":"createContactSignal","tags":["Creating a Signal"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContactSignalRequest"}}}},"responses":{"201":{"description":"Contact signal created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignalResponse"}}}},"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"}}}}}}
````


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developers.saber.app/creating-a-signal.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
