{"openapi":"3.1.0","info":{"title":"API Reference","version":"1.0.0"},"paths":{"/api/v1/lead_enrichments":{"get":{"operationId":"leadEnrichment_listLeadEnrichmentJobs","summary":"List LE jobs","description":"Returns the authenticated account's Lead Enrichment jobs, newest first,\nwith their current status and stats.\n\nA job's results are ready when `is_completed` is `true`.\n","tags":["leadEnrichment"],"parameters":[{"name":"api_key","in":"query","description":"Your Dealsignal API key. Obtain it from the Dealsignal dashboard. Send it here **or**\nin the `X-Api-Key` header — one of the two is required. If a request carries both,\nthis parameter is used, even an empty `api_key=`: remove `api_key` from the URL when\nyou switch to the header.\n","required":false,"schema":{"type":"string"}},{"name":"page_no","in":"query","description":"Zero-based page number.","required":false,"schema":{"type":"integer","default":0}},{"name":"size","in":"query","description":"Number of jobs per page.","required":false,"schema":{"type":"integer","default":20}},{"name":"X-Api-Key","in":"header","description":"Your Dealsignal API key, as an alternative to the `api_key` parameter — send the key\nin one place. Preferred: it keeps the key out of the URL.\n","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A page of LE jobs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadEnrichmentJobListResponse"}}}},"401":{"description":"Invalid key, inactive account, or expired subscription.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiMessageError"}}}}}},"post":{"operationId":"leadEnrichment_createLeadEnrichmentJob","summary":"Create and launch an LE job","description":"Creates a new Lead Enrichment job and immediately launches it.\nThe job runs asynchronously — poll `GET /api/v1/lead_enrichments/{id}` for status\nor provide a `webhook` to be notified on completion.\n","tags":["leadEnrichment"],"parameters":[{"name":"api_key","in":"query","description":"Your Dealsignal API key. Obtain it from the Dealsignal dashboard. Send it here **or**\nin the `X-Api-Key` header — one of the two is required. If a request carries both,\nthis parameter is used, even an empty `api_key=`: remove `api_key` from the URL when\nyou switch to the header.\n","required":false,"schema":{"type":"string"}},{"name":"X-Api-Key","in":"header","description":"Your Dealsignal API key, as an alternative to the `api_key` parameter — send the key\nin one place. Preferred: it keeps the key out of the URL.\n","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Job created and launched.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadEnrichmentCreateResponse"}}}},"401":{"description":"Invalid key, inactive account, or expired subscription.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiMessageError"}}}},"422":{"description":"Invalid input, job limit reached, or not enough credits for the job.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiMessageError"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadEnrichmentCreateRequest"}}}}}},"/api/v1/lead_enrichments/{id}":{"get":{"operationId":"leadEnrichment_getLeadEnrichmentJob","summary":"Get LE job status","description":"Returns the current status and stats for a single Lead Enrichment job,\nwrapped in `lead_enrichment`.\n\n**Polling tip:** the job is finished when `is_completed` is `true`\n(`completed_at` is then set). `Completion Running` still means in progress.\n","tags":["leadEnrichment"],"parameters":[{"name":"id","in":"path","description":"ID of the Lead Enrichment job.","required":true,"schema":{"type":"integer"}},{"name":"api_key","in":"query","description":"Your Dealsignal API key. Obtain it from the Dealsignal dashboard. Send it here **or**\nin the `X-Api-Key` header — one of the two is required. If a request carries both,\nthis parameter is used, even an empty `api_key=`: remove `api_key` from the URL when\nyou switch to the header.\n","required":false,"schema":{"type":"string"}},{"name":"X-Api-Key","in":"header","description":"Your Dealsignal API key, as an alternative to the `api_key` parameter — send the key\nin one place. Preferred: it keeps the key out of the URL.\n","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Job details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadEnrichmentJobResponse"}}}},"401":{"description":"Invalid key, inactive account, or expired subscription.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiMessageError"}}}},"422":{"description":"No Lead Enrichment job with this `id` exists on your account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiMessageError"}}}}}}},"/api/v1/lead_enrichments/{id}/final_result":{"get":{"operationId":"leadEnrichment_getLeadEnrichmentFinalResult","summary":"Get final results","description":"Returns paginated lead data for a **completed** LE job.\n\n- Pages are zero-based (`page=0` is the first page); the default `page_size` is 100.\n- `enriched_leads.total` is the number of leads in the job — use it to calculate the\n  number of pages.\n- Call it only once `is_completed` is `true`. Before that, the response is HTTP `200`\n  with `{ message, info }` instead of results. If the job reads `is_completed: true`\n  and you still get a `message`, stop retrying and check `page` / `page_size`.\n","tags":["leadEnrichment"],"parameters":[{"name":"id","in":"path","description":"ID of the completed Lead Enrichment job.","required":true,"schema":{"type":"integer"}},{"name":"api_key","in":"query","description":"Your Dealsignal API key. Obtain it from the Dealsignal dashboard. Send it here **or**\nin the `X-Api-Key` header — one of the two is required. If a request carries both,\nthis parameter is used, even an empty `api_key=`: remove `api_key` from the URL when\nyou switch to the header.\n","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","description":"Zero-based page number.","required":false,"schema":{"type":"integer","default":0}},{"name":"page_size","in":"query","description":"Number of leads per page.","required":false,"schema":{"type":"integer","default":100}},{"name":"X-Api-Key","in":"header","description":"Your Dealsignal API key, as an alternative to the `api_key` parameter — send the key\nin one place. Preferred: it keeps the key out of the URL.\n","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A page of results — or, while the job is still running, a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lead_Enrichment_getLeadEnrichmentFinalResult_Response_200"}}}},"401":{"description":"Invalid key, inactive account, or expired subscription.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiMessageError"}}}},"422":{"description":"No Lead Enrichment job with this `id` exists on your account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiMessageError"}}}}}}},"/api/v1/contact_profile_refresh_requests":{"post":{"operationId":"contactProfileRefreshRequests_createContactProfileRefreshRequest","summary":"Create a Contact Profile Refresh Request","description":"Creates a Contact Profile Refresh Request for the supplied `profiles`. Every profile is\nreturned right away with the contact matched so far. `contact_data.id`\nis `null` until a contact is matched. A **fresh** matched contact is\nusually `completed` right away; when `requested_attributes` includes\ncontact details such as `email`, it can stay in progress until they are\nfetched. A **stale** one reads `refreshing`. Stale and unmatched contacts\nare refreshed in the background; poll `request_status` until the\nrequest's `status` is `completed` to get them. A `webhook` can\noptionally notify you when that happens.\n\nA contact is **stale** when its LinkedIn profile was last verified more\nthan `contact_refresh_time` days before the request was created (30 days\nwhen the parameter is omitted).\n\n### Authentication\n\nSend the API key in the `X-Api-Key` header, as the `api_key` query-string\nparameter, **or** inside the JSON body. The account must be active with a subscription that\nhas not expired, and must hold enough credits for the request.\n\n### What the response contains\n\nBy default (`synchronous: true`) the response carries the request\nenvelope **plus** `contact_profile_refreshes` — every profile, each as\n`{ custom_columns, contact_data }` with the contact matched so far.\n`status` is `running` while background refresh continues, or `completed`\nstraight away when every profile was resolved during the call. A\n`completed` create response is the final result for every profile; any\nwebhook POSTs for such a request are duplicates. With\n`synchronous: false` only the envelope is returned.\n\nThe call **never blocks** on the background refresh in either mode.\n\n### Verification tiers\n\nSend `verification_tier` (`quick`, `standard` or `premium`) to choose how\nthoroughly the profile, work email and phones are verified. Each profile\nthen adds the tier fields, and each requested contact detail comes back\nwith its verification status. The profile is finished when `delivered` is\nnot `null`, and you are charged only for what is delivered. See\n**Verification Tiers** in the documentation for what each tier does and\ncosts.\n\nThe rest of this description applies to a request with no tier.\n\n### Pricing (credits per profile)\n\nEach completed profile with a matched contact is charged in credits.\n`requested_attributes` sets the most a profile can cost:\n\n| `requested_attributes` (all names within) | Tier | Maximum credits per profile |\n|---|---|---|\n| `contact_profile` only, or omitted | Contact Profile | 0.25 |\n| … plus `email` | Lead Profile | 1.00 |\n| … plus `corporate_phone` | Full Prospect Profile | 1.25 |\n| … plus any of `direct_phone`, `mobile_number` | Full Prospect Profile with Direct Dial | 1.50 |\n\nA profile is charged at the highest tier its data actually supports, up\nto that maximum. For example, if you request `email` but no verified\nwork email is available, the profile is charged as a Contact Profile.\n\n- **Balance check.** The call is rejected with `422` when your balance\n  is too low for the submitted profiles. Keep a margin: a profile can\n  cost up to the maximum for the attributes you request.\n- **Every refresh is charged.** A contact you have refreshed before is\n  charged again when you refresh it through this endpoint.\n- **Not charged:** profiles that end without a matched contact\n  (`contact_data.id` `null`) or `not_found`, profiles whose contact has\n  no current position, and every profile of a `test` request.\n","tags":["contactProfileRefreshRequests"],"parameters":[{"name":"api_key","in":"query","description":"Your Dealsignal API key. On this endpoint it may be supplied here, as the\n`api_key` property of the JSON body, **or** in the `X-Api-Key` header — one of\nthese is required. If a request carries both, the `api_key` value (query or body)\nis used, even an empty one: drop it when you switch to the header.\n","required":false,"schema":{"type":"string"}},{"name":"X-Api-Key","in":"header","description":"Your Dealsignal API key, as an alternative to the `api_key` parameter — send the key\nin one place. Preferred: it keeps the key out of the URL.\n","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Request created. `contact_profile_refreshes` is present only for\n`synchronous: true` (the default).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactProfileRefreshRequestCreateResponse"}}}},"401":{"description":"Invalid key, inactive account, or expired subscription.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiMessageError"}}}},"422":{"description":"No profiles supplied, `requested_attributes` is not an array of objects,\nor insufficient credits for the request.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiMessageError"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactProfileRefreshRequestInput"}}}}}},"/api/v1/contact_profile_refresh_requests/request_status":{"get":{"operationId":"contactProfileRefreshRequests_getContactProfileRefreshRequest","summary":"Get a Contact Profile Refresh Request","description":"Looks up a refresh request by the `unique_identifier` returned at create\ntime and returns the request envelope with every profile of the request,\nin its current state.\nThis is the recommended way to get the results: it always returns every\nprofile of the request. A `webhook` is only an optional completion signal.\n\n### Response formats\n\n- **Full** (default) — the request envelope plus `contact_profile_refreshes`:\n  a flat array of `{ custom_columns, contact_data }` for a synchronous\n  request, or `{ total, page_no, page_size, profiles }` for an\n  asynchronous one.\n- **`response_format=summary`** — `{ request_id, status, unique_identifier }`\n  only. The smallest response for checking whether the request is done.\n- **`response_format=changes`** — the status fields plus a compact\n  `profiles` collection carrying the `ds_*` change-detection fields\n  (primary position, baseline, verdict booleans, change types and\n  events) instead of the full `contact_data`. Use it for job-change\n  monitoring.\n\nAny other value of `response_format` returns the full format.\n\nOn a request with a `verification_tier`, the full format adds the tier\nfields to each profile; a profile is finished when `delivered` is not\n`null`. `summary` and `changes` have no tier fields. See **Verification\nTiers** in the documentation.\n\n### Pagination\n\n`page_no` and `page_size` apply **only to asynchronous requests**\n(`synchronous: false` at creation); a synchronous request always returns\nevery profile as a flat array. Defaults are `1` and `50`; both must be\n`1` or greater. The wrapper echoes the values you sent (a query-string\nvalue comes back as a string).\n\n### Change verdicts\n\n`changed_company` / `changed_title` (full format, webhook) and the `ds_*`\nfields (`changes` format) answer one question: *did the contact's\n**primary** position change compared with your account's previous\nrefresh of this contact?* The verdict has no look-back window:\n`contact_refresh_time` doesn't limit how far back the comparison reaches;\nit controls which contacts get refreshed. The\n**first refresh of a contact for your account** has no baseline and\nreports `false` / `false`. If the contact already had positions,\n`ds_change_status` is `undetermined` with reason `no prior snapshot`; if\nthis refresh delivered the contact's first positions, both change types\nare `newly_added` (`no_change` when those positions have all already\nended). Either way it establishes the baseline, and the second\nrefresh is the first that can report a change. The verdict is computed\nonce, when the refresh concludes for that profile — the profile can\nstill be in progress while requested contact details are fetched — and\ndoes not change afterwards.\n\n### Repeated polling\n\nPolling has no fee of its own. Once `status` is `completed`, stop\npolling: if you set a `webhook`, further calls can trigger additional\ndeliveries to it, so make your webhook receiver idempotent. A valid\n`unique_identifier` keeps working after the request has completed.\n","tags":["contactProfileRefreshRequests"],"parameters":[{"name":"api_key","in":"query","description":"Your Dealsignal API key. Obtain it from the Dealsignal dashboard. Send it here **or**\nin the `X-Api-Key` header — one of the two is required. If a request carries both,\nthis parameter is used, even an empty `api_key=`: remove `api_key` from the URL when\nyou switch to the header.\n","required":false,"schema":{"type":"string"}},{"name":"unique_identifier","in":"query","description":"The `unique_identifier` returned when the request was created.","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"response_format","in":"query","description":"`summary` returns only `request_id`, `status` and `unique_identifier`;\n`changes` returns the compact change-detection view. Omit it (or send\nany other value) for the full envelope.\n","required":false,"schema":{"$ref":"#/components/schemas/ApiV1ContactProfileRefreshRequestsRequestStatusGetParametersResponseFormat"}},{"name":"page_no","in":"query","description":"Page number for the profile list. Asynchronous requests only. Must be `1` or greater.","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"page_size","in":"query","description":"Profiles per page. Asynchronous requests only. Must be `1` or greater.","required":false,"schema":{"type":"integer","minimum":1,"default":50}},{"name":"X-Api-Key","in":"header","description":"Your Dealsignal API key, as an alternative to the `api_key` parameter — send the key\nin one place. Preferred: it keeps the key out of the URL.\n","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The request's status and every profile of the request, in its current\nstate and in the shape selected by `response_format`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact_Profile_Refresh_Requests_getContactProfileRefreshRequest_Response_200"}}}},"401":{"description":"Invalid key, inactive account, or expired subscription.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiMessageError"}}}},"422":{"description":"No request matches the supplied `unique_identifier`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiMessageError"}}}}}}},"/api/v1/users/credit_metrics":{"get":{"operationId":"credits_getCreditMetrics","summary":"Get credit metrics","description":"Returns the authenticated account's identity, remaining subscription\ncredits, and today's profile-request metrics in a single JSON object.\nRead-only: it creates nothing and does not consume credits.\n\n`daily_profile_request_metrics` counts Contact Profile Refresh\n**profiles** created on the current **server** date. Profiles of a\nsynchronous request that were matched and already `completed` in the\ncreate response are usually left out; `test` requests are never\ncounted, and the counts start again at the next server-day boundary.\n\n<Note>\nSend `Accept: application/json` from API clients so that authentication\nerrors come back as JSON. The same applies to every endpoint.\n</Note>\n","tags":["credits"],"parameters":[{"name":"api_key","in":"query","description":"Your Dealsignal API key. Obtain it from the Dealsignal dashboard. Send it here **or**\nin the `X-Api-Key` header — one of the two is required. If a request carries both,\nthis parameter is used, even an empty `api_key=`: remove `api_key` from the URL when\nyou switch to the header.\n","required":false,"schema":{"type":"string"}},{"name":"X-Api-Key","in":"header","description":"Your Dealsignal API key, as an alternative to the `api_key` parameter — send the key\nin one place. Preferred: it keeps the key out of the URL.\n","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Credit metrics for the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditMetricsResponse"}}}},"401":{"description":"Invalid key, inactive account, or expired subscription.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiMessageError"}}}}}}},"/api/v1/auth/api_keys":{"get":{"operationId":"authentication_listApiKeys","summary":"List API keys","description":"Returns the authenticated user's current API key metadata along with any\nrolled-away previous keys still inside their individual grace windows.\n\nPlaintext keys are never returned — use this endpoint to track which keys\nyou have minted and when each one expires.\n","tags":["authentication"],"parameters":[{"name":"api_key","in":"query","description":"Your Dealsignal API key. Obtain it from the Dealsignal dashboard. Send it here **or**\nin the `X-Api-Key` header — one of the two is required. If a request carries both,\nthis parameter is used, even an empty `api_key=`: remove `api_key` from the URL when\nyou switch to the header.\n","required":false,"schema":{"type":"string"}},{"name":"X-Api-Key","in":"header","description":"Your Dealsignal API key, as an alternative to the `api_key` parameter — send the key\nin one place. Preferred: it keeps the key out of the URL.\n","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Current and previous API keys.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyListResponse"}}}},"401":{"description":"Invalid key, inactive account, or expired subscription.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiMessageError"}}}}}}},"/api/v1/auth/api_keys/roll":{"post":{"operationId":"authentication_rollApiKey","summary":"Roll API key","description":"Mints a new API key and parks the current one as a previous key with its\nown grace window (default 48 hours). The previous key remains valid until\nits `expires_at`, so deployments can rotate credentials without downtime.\n\n### Security\n\nThe `api_key` **must be supplied in the request body**. Passing it as a\nquery-string parameter returns `400` — query-string credentials leak into\naccess logs, CDN logs, browser history, and `Referer` headers, which is\nunacceptable on a credential-minting endpoint.\n\n### Rate limiting\n\nRolls are rate-limited per user. If you roll too frequently the response\nis `429` with a `Retry-After` header and a `retry_after_seconds` field in\nthe JSON body.\n","tags":["authentication"],"responses":{"200":{"description":"New API key minted; previous key parked with its grace window.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyRollResponse"}}}},"400":{"description":"The `api_key` was supplied via query string. Resend it in the request body.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyRollError"}}}},"401":{"description":"Invalid key, inactive account, or expired subscription.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiMessageError"}}}},"429":{"description":"Rolled too recently. Honor `Retry-After` and try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyRollRateLimitedError"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyRollRequest"}}}}}}},"tags":[{"name":"leadEnrichment"},{"name":"contactProfileRefreshRequests"},{"name":"credits"},{"name":"authentication"}],"servers":[{"url":"https://app.dealsignal.com","description":"Production"}],"webhooks":{"contactProfileRefreshRequests_contactProfileRefreshRequestCompleted":{"post":{"operationId":"contactProfileRefreshRequests_contactProfileRefreshRequestCompleted","summary":"Contact Profile Refresh Request completed (webhook)","description":"When a refresh request **completes**, Dealsignal `POST`s the results to\nthe `webhook` URL you supplied at creation as JSON\n(`Content-Type: application/json`, 60-second timeout). The payload is a\n**bare JSON array** of profile objects, each\n`{ \"custom_columns\": {...}, \"contact_data\": {...} }` — exactly the\nstructure returned by `request_status`.\n\nA create response that reads `status: completed` is the final result for\nevery profile. POSTs for such a request can still arrive, even before the\ncreate response (the request completed during the create call); treat\nthem as duplicates.\n\nUp to two POSTs are sent, one after the other. Profiles that were already\n`completed` in a synchronous create response, with or without a matched\ncontact, are normally not sent again: read them from the create response.\n\n1. **Refreshed profiles** — profiles that completed with a matched\n   contact. Skipped when there is nothing to report.\n2. **Not-found profiles** — profiles that ended `not_found` without a\n   matched contact (`contact_data` carries only the status fields).\n   Skipped when there is nothing to report.\n\nThe webhook does not carry every profile of every request;\n`request_status` always returns all of them.\n\nOn a request with a `verification_tier`, each profile also carries the\ntier fields, and the second POST carries every profile that ended\n`not_found`, matched or not.\n\n### Correlation\n\nThe payload has **no envelope**: it carries neither the request's `id`\nnor its `unique_identifier`. Correlate each profile through\n`custom_columns`, which echoes the profile object you submitted — put a\nkey of your own in `metadata` if you need one.\n\n### Delivery\n\nBuild an idempotent receiver:\n\n- A delivery that fails (anything but a 2xx within 60 seconds) is\n  retried eight times over about 27 hours, at intervals from 1 minute\n  to 12 hours. If your endpoint was down for longer, read the results\n  with `request_status`.\n- Deliveries can repeat: a retried one your endpoint had received but\n  answered too late arrives again. Deduplicate on your own key in\n  `custom_columns.metadata`.\n- Acknowledge each POST with a 2xx status right away, and process it\n  after responding.\n- Webhook requests are not signed. Use a hard-to-guess URL (for example\n  with a secret token in it) and act only on profiles you can match to\n  your own records.\n","responses":{"200":{"description":"Webhook received successfully"}},"requestBody":{"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ContactProfileRefreshProfile"}}}}}}}},"components":{"schemas":{"LeadEnrichmentJobStatus":{"type":"string","enum":["Created","Launched","Running","Completion Running","Completed"],"description":"`Completion Running` means the results are being finalized — treat it\nas in progress. The job is finished when `is_completed` is `true`.\n","title":"LeadEnrichmentJobStatus"},"LeadEnrichmentJobType":{"type":"string","enum":["name","email","linkedin_url"],"description":"The job's `match_field` (`name`, `email` or `linkedin_url`).","title":"LeadEnrichmentJobType"},"LeadEnrichmentJobProfileType":{"type":"string","enum":["Contact Profile","Lead Profile","Full Prospect Profile","Full Prospect Profile With Direct Dial"],"description":"Profile level the job enriches, derived from the request flags.","title":"LeadEnrichmentJobProfileType"},"LeadEnrichmentJob":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"status":{"$ref":"#/components/schemas/LeadEnrichmentJobStatus","description":"`Completion Running` means the results are being finalized — treat it\nas in progress. The job is finished when `is_completed` is `true`.\n"},"is_completed":{"type":"boolean","description":"`true` once results are available (`completed_at` is set)."},"completed_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"type":{"$ref":"#/components/schemas/LeadEnrichmentJobType","description":"The job's `match_field` (`name`, `email` or `linkedin_url`)."},"source_type":{"type":"string"},"profile_type":{"$ref":"#/components/schemas/LeadEnrichmentJobProfileType","description":"Profile level the job enriches, derived from the request flags."},"is_full_prospect_profile":{"type":["boolean","null"]},"total_leads_to_enrich":{"type":"integer","description":"Number of leads submitted in the job."},"total_enriched_leads":{"type":"integer","description":"Number of leads matched to a Dealsignal contact."}},"description":"A Lead Enrichment job. Responses can include further keys that are not\npart of this contract; ignore them.\n","title":"LeadEnrichmentJob"},"LeadEnrichmentJobListResponse":{"type":"object","properties":{"lead_enrichment_jobs":{"type":"array","items":{"$ref":"#/components/schemas/LeadEnrichmentJob"},"description":"The page of jobs, newest first."},"total":{"type":"integer","description":"Total number of jobs on the account."}},"title":"LeadEnrichmentJobListResponse"},"ApiMessageError":{"type":"object","properties":{"message":{"type":"string"},"authorization_failure":{"type":"boolean"},"inactive":{"type":"boolean"}},"required":["message"],"description":"Error body of the `401` responses documented here (invalid key, inactive\naccount, expired subscription) and of the Lead Enrichment and Contact\nProfile Refresh `422` responses. `authorization_failure` and `inactive`\nare present only on the inactive-account rejection.\n","title":"ApiMessageError"},"LeadEnrichmentCreateRequestLeadEnrichmentsSourceType":{"type":"string","enum":["from_json"],"description":"Always `from_json` for API-submitted jobs.","title":"LeadEnrichmentCreateRequestLeadEnrichmentsSourceType"},"LeadEnrichmentCreateRequestLeadEnrichmentsMatchField":{"type":"string","enum":["name","email","linkedin_url"],"description":"Field used to match leads against Dealsignal's database.\n- `name` — matches on `first_name`, `last_name`, and `company_name`\n- `email` — matches on `email`\n- `linkedin_url` — matches on `linkedin_url`\n","title":"LeadEnrichmentCreateRequestLeadEnrichmentsMatchField"},"LeadDataKeysItems":{"type":"string","enum":["first_name","last_name","company_name","email","linkedin_url","title","city","state","country","unique_identifier"],"title":"LeadDataKeysItems"},"LeadData":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/LeadDataKeysItems"}},"values":{"type":"array","items":{"type":"array","items":{"description":"Any type"}}}},"required":["keys","values"],"description":"Columnar format for lead input data. `keys` defines the field names;\neach array in `values` is one lead's data in the same order.\n","title":"LeadData"},"LeadEnrichmentCreateRequestLeadEnrichments":{"type":"object","properties":{"name":{"type":"string","description":"Name for the job. Must be unique: a name that's already taken returns\n`422` — include something distinctive, such as a date or your own ID.\n"},"source_type":{"$ref":"#/components/schemas/LeadEnrichmentCreateRequestLeadEnrichmentsSourceType","description":"Always `from_json` for API-submitted jobs."},"match_field":{"$ref":"#/components/schemas/LeadEnrichmentCreateRequestLeadEnrichmentsMatchField","description":"Field used to match leads against Dealsignal's database.\n- `name` — matches on `first_name`, `last_name`, and `company_name`\n- `email` — matches on `email`\n- `linkedin_url` — matches on `linkedin_url`\n"},"lead_data":{"$ref":"#/components/schemas/LeadData"},"append_company_data":{"type":"boolean","default":false,"description":"Include enriched company data alongside contact data."},"contact_profile_only":{"type":"boolean","default":false,"description":"Enrich the contact profile only — no email or phone enrichment\n(`profile_type` `Contact Profile`). Don't combine it with\n`is_full_prospect_profile` or `direct_dial`.\n"},"is_full_prospect_profile":{"type":"boolean","default":false,"description":"Also enrich phone numbers (`profile_type` `Full Prospect Profile`).\nWithout this flag, `direct_dial` and `contact_profile_only`, the job\nenriches the contact profile and work email (`Lead Profile`).\n"},"direct_dial":{"type":"boolean","default":false,"description":"Also enrich phone numbers, including direct dials\n(`profile_type` `Full Prospect Profile With Direct Dial`). Turns on\n`is_full_prospect_profile`.\n"},"contact_refresh_time":{"type":"integer","minimum":1,"default":30,"description":"Freshness threshold in days. Contacts whose LinkedIn profile was verified\nwithin this many days before the job are returned without a refresh;\nolder ones are refreshed first. Defaults to 30.\n"},"flag_if_lead_changed_company":{"type":"boolean","default":false,"description":"Compute `changed_company` for each lead: whether the contact's primary\nemployer changed within about the last month before the job (many\nposition dates are only month-precise). It is not a comparison with the\nvalues you submitted. `false` also covers leads that couldn't be\nassessed, so it isn't proof the contact stayed.\n"},"flag_if_lead_changed_title":{"type":"boolean","default":false,"description":"Compute `changed_title` for each lead: whether the contact's primary\ntitle changed within about the last month before the job. It is not a\ncomparison with the values you submitted. `false` also covers leads\nthat couldn't be assessed.\n"},"webhook":{"type":"string","format":"uri","description":"URL to receive a POST when the job completes. The body is the job\nobject — the same fields as `lead_enrichment` in the\n`GET /api/v1/lead_enrichments/{id}` response, without the wrapper;\nit does not carry the leads. It is sent once, while the job is being\nfinalized, so the payload shows `status: \"Completion Running\"` and\n`is_completed: false` — treat the webhook's arrival as the\ncompletion signal. If `final_result` then\nreturns a `message` instead of results, retry after a few seconds.\nIf the job reads `is_completed: true` and `final_result` still\nreturns a `message`, stop retrying and check `page` / `page_size`.\n"}},"required":["name","source_type","match_field","lead_data"],"title":"LeadEnrichmentCreateRequestLeadEnrichments"},"LeadEnrichmentCreateRequest":{"type":"object","properties":{"lead_enrichments":{"$ref":"#/components/schemas/LeadEnrichmentCreateRequestLeadEnrichments"}},"required":["lead_enrichments"],"title":"LeadEnrichmentCreateRequest"},"LeadEnrichmentCreateResponse":{"type":"object","properties":{"message":{"type":"string"},"id":{"type":"integer","description":"ID of the new job. Use it to poll status and fetch results."}},"description":"Returned when the job is created and launched. Responses can include\nfurther keys that are not part of this contract; ignore them.\n","title":"LeadEnrichmentCreateResponse"},"LeadEnrichmentJobResponse":{"type":"object","properties":{"lead_enrichment":{"$ref":"#/components/schemas/LeadEnrichmentJob"}},"description":"Single-job status. The job is wrapped in `lead_enrichment`.","title":"LeadEnrichmentJobResponse"},"EnrichedLeadProfileCompleteness":{"type":"string","enum":["complete","rich","basic","partial","poor"],"title":"EnrichedLeadProfileCompleteness"},"LeadCompanyAndTitle":{"type":"object","properties":{"company":{"type":["string","null"]},"title":{"type":["string","null"]}},"title":"LeadCompanyAndTitle"},"EnrichedLead":{"type":"object","properties":{"custom_columns":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The row you submitted in `lead_data`, keyed by your `keys` (including\n`unique_identifier`). Use it to correlate results with your records.\n"},"id":{"type":["integer","null"],"description":"The matched Dealsignal contact ID; `null` when not matched."},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"company_name":{"type":["string","null"]},"title":{"type":["string","null"]},"changed_company":{"type":"boolean","description":"`true` when the contact's primary employer changed within about the\nlast month before the job (many position dates are only month-precise) — not a\ncomparison with the row you submitted. `false` means no change was\ndetected; it also covers leads that couldn't be assessed, so it isn't\nproof the contact stayed. Computed when `flag_if_lead_changed_company`\nis set; otherwise `false`.\n"},"changed_title":{"type":["boolean","null"],"description":"`true` when the contact's primary title changed within about the last\nmonth before the job — not a comparison with the row you submitted.\n`false` means no change was detected and also covers leads that\ncouldn't be assessed. Computed when `flag_if_lead_changed_title` is\nset; `null` when the flag isn't set or the lead wasn't matched to a\ncontact.\n"},"linkedin_url":{"type":["string","null"]},"company_website":{"type":["string","null"]},"seniorities":{"type":["string","null"],"description":"Seniority names, joined with ` || ` when there are several."},"departments":{"type":["string","null"],"description":"Department names, joined with ` || ` when there are several."},"contact_industry":{"type":["string","null"]},"contact_location":{"type":["string","null"]},"contact_country":{"type":["string","null"]},"contact_state":{"type":["string","null"]},"contact_city":{"type":["string","null"]},"skills":{"type":["string","null"]},"profile_completeness":{"oneOf":[{"$ref":"#/components/schemas/EnrichedLeadProfileCompleteness"},{"type":"null"}]},"enriched_by":{"type":"string","description":"Always `dealsignal`."},"contact_updated_at":{"type":["string","null"],"format":"date-time"},"email":{"type":["string","null"],"description":"Work email for the lead's position."},"email_status":{"type":["string","null"],"description":"Verification status of `email`, e.g. `Verified` or `Guaranteed`."},"phone":{"type":["string","null"]},"mobile_number":{"type":["string","null"]},"direct_phone":{"type":["string","null"]},"direct_phone_verified":{"type":["boolean","null"]},"corporate_phone":{"type":["string","null"]},"corporate_phone_verified":{"type":["boolean","null"]},"corporate_phone_directory":{"type":["boolean","null"]},"current_company_and_title":{"$ref":"#/components/schemas/LeadCompanyAndTitle","description":"Company and title of the lead's current position; `{}` when none."},"previous_companies_and_titles":{"type":"array","items":{"$ref":"#/components/schemas/LeadCompanyAndTitle"},"description":"The contact's ended positions (no guaranteed order); `[]` when none."},"company_data":{"type":["object","null"],"additionalProperties":{"description":"Any type"},"description":"Company attributes, present only when the job was created with\n`append_company_data: true` and the lead has a company; otherwise\n`null`. Keys: `company_id`, `company_name`, `company_social_name`,\n`company_homepage_url`, `company_domains`, `company_linkedin_url`,\n`company_employee_low`, `company_employee_high`,\n`company_employee_range`, `employee_count`, `company_industry`,\n`company_location`, `company_street_address`, `company_city`,\n`company_state`, `country_abbr`, `company_country`,\n`company_zip_code`, `company_date_founded`, `company_updated_at`,\n`company_linkedin_followers`, `company_type`, `company_specialties`.\n"}},"required":["custom_columns","id","first_name","last_name","company_name","title","changed_company","changed_title","linkedin_url","company_website","seniorities","departments","contact_industry","contact_location","contact_country","contact_state","contact_city","skills","profile_completeness","enriched_by","contact_updated_at","email","email_status","phone","mobile_number","direct_phone","direct_phone_verified","corporate_phone","corporate_phone_verified","corporate_phone_directory","current_company_and_title","previous_companies_and_titles","company_data"],"description":"One lead of the job. Responses can include further keys that are not\npart of this contract; ignore them. Every key below is always present;\ncontact-derived fields are `null` when the lead was not matched to a\ncontact or the value is not available.\n","title":"EnrichedLead"},"FinalResultResponseEnrichedLeads":{"type":"object","properties":{"total":{"type":"integer","description":"Total number of leads in the job (matched or not). Use it to calculate the number of pages."},"leads":{"type":"array","items":{"$ref":"#/components/schemas/EnrichedLead"}}},"title":"FinalResultResponseEnrichedLeads"},"FinalResultResponse":{"type":"object","properties":{"lead_enrichment":{"$ref":"#/components/schemas/LeadEnrichmentJob"},"enriched_leads":{"$ref":"#/components/schemas/FinalResultResponseEnrichedLeads"}},"title":"FinalResultResponse"},"FinalResultNotReady":{"type":"object","properties":{"message":{"type":"string"},"info":{"type":"string"}},"description":"Returned with HTTP `200` instead of results when the job has not\ncompleted yet. Check `is_completed` before fetching results.\n","title":"FinalResultNotReady"},"Lead_Enrichment_getLeadEnrichmentFinalResult_Response_200":{"oneOf":[{"$ref":"#/components/schemas/FinalResultResponse"},{"$ref":"#/components/schemas/FinalResultNotReady"}],"title":"Lead Enrichment_getLeadEnrichmentFinalResult_Response_200"},"ContactProfileInput":{"type":"object","properties":{"ds_contact_id":{"type":"integer","description":"Existing Dealsignal contact ID, if known — the most reliable identifier.\nSend it without other identifiers (adding `metadata` is fine). Profiles\nthat carry it are counted as `requested_with_ds_contact_id` in credit\nmetrics.\n"},"first_name":{"type":"string"},"last_name":{"type":"string"},"company_name":{"type":"string"},"email":{"type":"string","format":"email"},"linkedin_url":{"type":"string","format":"uri"},"metadata":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Arbitrary metadata, echoed back verbatim inside `custom_columns`.\nPut your own record ID here to correlate webhook results. A key named\n`clay_row_id` on every profile makes a repeat within 15 minutes return\nthe first request (see **Idempotent requests from Clay** in the documentation).\n"}},"description":"One contact to look up and refresh. Supply at least one identifier —\n`ds_contact_id`, `linkedin_url`, `email`, or the complete\n`first_name` + `last_name` + `company_name` triple — so Dealsignal can\nmatch the contact. A profile with none of them is closed at creation,\nwith `contact_data.id` `null` and the remark `Invalid input data!`. So\nis a profile that carries only a `linkedin_url` (plus `metadata`) that\nisn't a linkedin.com URL, such as a shortened link.\n\n**The whole object is echoed back.** Every profile in every response and\nwebhook payload carries a `custom_columns` object that is this profile\nexactly as you submitted it. It is the only correlation key the webhook\npayload carries. Put your own fields in `metadata`; don't add other keys\nto the profile object.\n","title":"ContactProfileInput"},"ContactProfileRefreshRequestInputVerificationTier":{"type":"string","enum":["quick","standard","premium"],"description":"How thoroughly to verify the profile, work email and phones. The\nresponse then reports each requested field with its verification\nstatus, and each profile adds the tier fields. Send one of these\nnames: any other value is rejected with `422`, and nothing is created.\nSee **Verification Tiers** in the documentation.\n","title":"ContactProfileRefreshRequestInputVerificationTier"},"RequestedAttributeName":{"type":"string","enum":["contact_profile","email","corporate_phone","direct_phone","mobile_number"],"description":"Data to request for each profile: `contact_profile`, `email`,\n`corporate_phone`, `direct_phone`, `mobile_number`. Send only these\nfive names — other names are not supported and can be charged at the\nhighest tier; `cma` and `personal_email` are no longer offered and are\nignored.\n","title":"RequestedAttributeName"},"RequestedAttribute":{"type":"object","properties":{"name":{"$ref":"#/components/schemas/RequestedAttributeName","description":"Data to request for each profile: `contact_profile`, `email`,\n`corporate_phone`, `direct_phone`, `mobile_number`. Send only these\nfive names — other names are not supported and can be charged at the\nhighest tier; `cma` and `personal_email` are no longer offered and are\nignored.\n"},"priority_order":{"type":"integer","description":"Relative priority of the attribute."}},"required":["name"],"title":"RequestedAttribute"},"ContactProfileRefreshRequestInput":{"type":"object","properties":{"api_key":{"type":"string","description":"Account API key. Alternative to the `api_key` query-string parameter and\nthe `X-Api-Key` header — supply it in one of these places.\n"},"profiles":{"type":"array","items":{"$ref":"#/components/schemas/ContactProfileInput"},"description":"One or more contact profiles to look up and refresh."},"verification_tier":{"$ref":"#/components/schemas/ContactProfileRefreshRequestInputVerificationTier","description":"How thoroughly to verify the profile, work email and phones. The\nresponse then reports each requested field with its verification\nstatus, and each profile adds the tier fields. Send one of these\nnames: any other value is rejected with `422`, and nothing is created.\nSee **Verification Tiers** in the documentation.\n"},"contact_refresh_time":{"type":["integer","null"],"minimum":0,"description":"Freshness threshold in days. A matched contact whose LinkedIn profile\nwas last verified more than this many days before the request was\ncreated is treated as **stale** and refreshed in the background.\n\n**Omit it for the 30-day default** — the request then stores and\nechoes `null`. `0` compares against the day the request was created:\na contact verified on an earlier day is refreshed, one verified the\nsame day is returned as fresh.\n\nWith a `verification_tier` the default is the tier's window (60, 30 or\n7 days), and `0` refreshes every profile.\n"},"webhook":{"type":["string","null"],"format":"uri","description":"Optional. URL to notify when the request completes — see the\n*Contact Profile Refresh Request completed* webhook. The POSTs can\nrepeat and don't carry every profile, so treat them as a signal and\nread the results with `request_status`. Omit it (the default) or send\n`\"\"` to rely on polling alone.\n"},"synchronous":{"type":"boolean","default":true,"description":"`true` (default): the response includes **every** profile inline in\n`contact_profile_refreshes`, with the contact matched so far. A fresh\nmatched contact is usually `completed` right away — when\n`requested_attributes` includes contact details such as `email`, it\ncan stay in progress until they are fetched; a stale one reads\n`refreshing`; an unmatched profile has `contact_data.id` `null` and is\neither still in progress or already closed.\n\n`false`: the response carries only the request envelope; poll\n`request_status` for the profiles.\n\nBackground refresh of stale and unmatched contacts runs either way —\nthe call **never blocks** on it.\n"},"find_match_and_refresh":{"type":"boolean","default":true,"description":"When `false`, profiles that do not match a Dealsignal contact with a\nvalid LinkedIn profile are closed at creation instead of being\nsearched for in the background: with `contact_data.id` `null` when no\ncontact matched, as `completed` when the matched contact has no valid\nLinkedIn profile.\nMatched contacts are refreshed as usual.\n"},"requested_attributes":{"type":"array","items":{"$ref":"#/components/schemas/RequestedAttribute"},"default":[{"name":"contact_profile","priority_order":0}],"description":"Data to request for each profile, as an array of objects; any other\nshape returns `422`. Sets the maximum credits a profile can cost — see\n**Pricing** in the operation description. Defaults to `contact_profile`\nonly.\n\nWithout a `verification_tier`, `contact_data` in responses always\ncarries the same fixed set of keys regardless of what you request here;\ncontact details (email, phones) are not part of it. With a tier, the\nrequested contact details are added next to those keys.\n"},"test":{"type":"boolean","default":false,"description":"`true` forces `synchronous: true` and flags the request as test data:\nprofiles are matched against test contacts only and closed\n`completed` immediately, no refresh runs, **no credits are consumed**,\nand the request is excluded from credit metrics. Your balance must\nstill cover the request. A profile with no matching test contact is\nalso closed `completed`, with `contact_data.id` `null`. In test mode,\nread the results from the create response.\n"}},"required":["profiles"],"title":"ContactProfileRefreshRequestInput"},"ContactProfileRefreshRequestCreateResponseStatus":{"type":"string","enum":["running","completed"],"description":"`running` while background processing continues; `completed` once every\nprofile is resolved (possibly already in the create response).\n","title":"ContactProfileRefreshRequestCreateResponseStatus"},"ContactProfileRefreshRequestUser":{"type":"object","properties":{"id":{"type":"integer"},"email":{"type":"string","format":"email"},"name":{"type":"string"}},"description":"The authenticated account.","title":"ContactProfileRefreshRequestUser"},"ContactProfileRefreshContactDataProfileCompleteness":{"type":"string","enum":["complete","rich","basic","partial","poor"],"description":"How complete the contact's profile is; `null` until a contact is matched.","title":"ContactProfileRefreshContactDataProfileCompleteness"},"ContactProfileRefreshContactDataPrimaryPosition":{"type":"object","properties":{"company":{"type":["string","null"]},"title":{"type":["string","null"]},"company_linkedin_url":{"type":["string","null"]},"company_domain":{"type":["string","null"]}},"description":"The contact's primary position; `null` until a contact is matched.\nSelected independently of `ds_primary_company` / `ds_primary_title`\nin the `changes` format and can differ from it on some contacts — the\nchange verdict is always about the `ds_primary_*` position.\n","title":"ContactProfileRefreshContactDataPrimaryPosition"},"ContactProfileRefreshEducation":{"type":"object","properties":{"university":{"type":"string"},"major":{"type":"string"},"degree":{"type":"string"},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"logo_url":{"type":"string"}},"description":"One education entry. Keys without a value are omitted.","title":"ContactProfileRefreshEducation"},"ContactProfileRefreshCompanyAndTitle":{"type":"object","properties":{"company":{"type":["string","null"]},"title":{"type":["string","null"]},"company_linkedin_url":{"type":["string","null"]},"company_domain":{"type":["string","null"]}},"title":"ContactProfileRefreshCompanyAndTitle"},"ContactProfileRefreshPositionDetail":{"type":"object","properties":{"title":{"type":["string","null"]},"company_name":{"type":["string","null"]},"company_linkedin_url":{"type":["string","null"]},"start_date":{"type":["string","null"],"format":"date"},"end_date":{"type":["string","null"],"format":"date"},"country":{"type":["string","null"]},"company_logo_url":{"type":["string","null"]}},"title":"ContactProfileRefreshPositionDetail"},"ContactProfileRefreshContactData":{"type":"object","properties":{"id":{"type":["integer","null"],"description":"The matched Dealsignal contact ID; `null` until a contact is matched.\nA finished profile (`completed` or `not_found`) with `id` `null` has no\nmatched contact — use this field, not `search_status`, to tell matched\nfrom unmatched profiles.\n"},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"logo_url":{"type":["string","null"]},"company_name":{"type":["string","null"]},"title":{"type":["string","null"]},"changed_company":{"type":"boolean","description":"Always `true` or `false`, never `null`. Whether the contact's **primary\nemployer** differs from your account's previous refresh of this\ncontact. Identical to `ds_company_change` in the `changes` format. A\nprofile without a verdict yet (refresh not concluded, no contact\nmatched) reads `false`. The first refresh of a contact for your\naccount has no previous refresh to compare with and reads `false` —\nit establishes the baseline for the next one. `false` therefore does\nnot distinguish \"no change\" from \"no verdict\"; use\n`response_format=changes` and `ds_change_status` when that matters.\n"},"changed_title":{"type":"boolean","description":"Always `true` or `false`, never `null`. Whether the contact's **primary\ntitle** differs from your account's previous refresh of this\ncontact — same rules as\n`changed_company`. Identical to `ds_title_change` in the `changes`\nformat.\n"},"linkedin_url":{"type":["string","null"]},"company_website":{"type":["string","null"]},"seniorities":{"type":["string","null"],"description":"Seniority names, joined with ` || ` when there are several."},"departments":{"type":["string","null"],"description":"Department names, joined with ` || ` when there are several."},"contact_industry":{"type":["string","null"]},"contact_location":{"type":["string","null"]},"contact_country":{"type":["string","null"]},"contact_state":{"type":["string","null"]},"contact_city":{"type":["string","null"]},"skills":{"type":["string","null"]},"profile_completeness":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshContactDataProfileCompleteness"},{"type":"null"}],"description":"How complete the contact's profile is; `null` until a contact is matched."},"enriched_by":{"type":"string","description":"Always `dealsignal`."},"contact_updated_at":{"type":["string","null"],"format":"date-time"},"primary_position":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshContactDataPrimaryPosition"},{"type":"null"}],"description":"The contact's primary position; `null` until a contact is matched.\nSelected independently of `ds_primary_company` / `ds_primary_title`\nin the `changes` format and can differ from it on some contacts — the\nchange verdict is always about the `ds_primary_*` position.\n"},"contact_headline":{"type":["string","null"]},"contact_about_me":{"type":["string","null"]},"contact_education":{"type":["array","null"],"items":{"$ref":"#/components/schemas/ContactProfileRefreshEducation"},"description":"The contact's education, one object per entry; keys without a value\nare omitted. `[]` when the contact has none, `null` until a contact\nis matched.\n"},"current_company_and_title":{"$ref":"#/components/schemas/ContactProfileRefreshCompanyAndTitle","description":"A snapshot of the contact's current position that may predate the\nrefresh. Read `primary_position` /\n`current_position_details` for the refreshed state. `{}` when there was\nno current position or no contact is matched; never `null`.\n"},"previous_companies_and_titles":{"type":"array","items":{"$ref":"#/components/schemas/ContactProfileRefreshCompanyAndTitle"},"description":"The contact's ended positions, in no guaranteed order. A snapshot taken\ntogether with `current_company_and_title`. `[]` when there were none\nor no contact is matched; never `null`.\n"},"search_status":{"type":["string","null"],"description":"Processing state of this profile. `completed` and `not_found` are\nfinal; `refreshing` means the profile is being refreshed. Any other\nvalue means the profile is still in progress — rely on the request's\n`status` rather than on specific in-progress values. `null` for a\nprofile that was closed without a final status when the request\ncompleted. Whether a contact was matched is told by `contact_data.id`\n(see that field), not by `search_status`.\n"},"match_status":{"type":"string","description":"`found_and_verified` (matched and `search_status: completed`),\n`found_and_unverified` (matched, not verified by this refresh),\n`invalid_li_profile` (matched, but the LinkedIn profile is flagged\ninvalid), `not_found`, or a verification note (`Verified`,\n`Contact Not Found`, `Invalid Source Data - Contact`,\n`Invalid Source Data - Linkedin Url`, `Invalid Source Data - Email`).\n\nDon't use `match_status` to decide whether a profile is finished — use\n`search_status` and the request's `status`.\n"},"remark":{"type":["string","null"]},"enrichment_status":{"type":["string","null"],"description":"Display label of `search_status`: `Finding A Match`,\n`Found And Refreshing`, `Found And Enriching`, `Completed`,\n`Not Found`; `null` for states without a label. A profile that\nfinished (`completed` or `not_found`) without a match reports\n`Not Found`.\n"},"current_position_details":{"type":["array","null"],"items":{"$ref":"#/components/schemas/ContactProfileRefreshPositionDetail"},"description":"The contact's current positions; `null` until a contact is matched."},"old_position_details":{"type":["array","null"],"items":{"$ref":"#/components/schemas/ContactProfileRefreshPositionDetail"},"description":"The contact's ended positions; `null` until a contact is matched."}},"required":["id","first_name","last_name","logo_url","company_name","title","changed_company","changed_title","linkedin_url","company_website","seniorities","departments","contact_industry","contact_location","contact_country","contact_state","contact_city","skills","profile_completeness","enriched_by","contact_updated_at","primary_position","contact_headline","contact_about_me","contact_education","current_company_and_title","previous_companies_and_titles","search_status","match_status","remark","enrichment_status","current_position_details","old_position_details"],"description":"The enriched contact. Always carries the same **fixed set of 33 keys**,\nwhatever you sent in `requested_attributes`. Contact details — `email`,\n`phone`, `mobile_number`, `direct_phone`, `corporate_phone` — are **not\npart of these keys**: without a `verification_tier` they are never\nreturned, even when requested.\n\nEvery contact-derived field is `null` until a contact is matched, except\n`current_company_and_title` (`{}`) and `previous_companies_and_titles`\n(`[]`). `search_status`, `match_status`, `remark`, `enrichment_status`,\n`enriched_by` and the two booleans are always present.\n","title":"ContactProfileRefreshContactData"},"ContactProfileRefreshProfileWithoutTier":{"type":"object","properties":{"custom_columns":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The profile object you submitted, echoed back verbatim."},"contact_data":{"$ref":"#/components/schemas/ContactProfileRefreshContactData"}},"description":"One profile of a request — the profile you submitted, echoed back, plus\nthe enriched contact.\n","title":"ContactProfileRefreshProfileWithoutTier"},"ContactProfileRefreshProfileWithTierContactDataOneOf0ProfileCompleteness":{"type":"string","enum":["complete","rich","basic","partial","poor"],"description":"How complete the contact's profile is; `null` until a contact is matched.","title":"ContactProfileRefreshProfileWithTierContactDataOneOf0ProfileCompleteness"},"ContactProfileRefreshProfileWithTierContactDataOneOf0PrimaryPosition":{"type":"object","properties":{"company":{"type":["string","null"]},"title":{"type":["string","null"]},"company_linkedin_url":{"type":["string","null"]},"company_domain":{"type":["string","null"]}},"description":"The contact's primary position; `null` until a contact is matched.\nSelected independently of `ds_primary_company` / `ds_primary_title`\nin the `changes` format and can differ from it on some contacts — the\nchange verdict is always about the `ds_primary_*` position.\n","title":"ContactProfileRefreshProfileWithTierContactDataOneOf0PrimaryPosition"},"ContactProfileRefreshTierEmailStatus":{"type":"string","enum":["Verified","Valid","Unverified","Not Found"],"description":"`Verified`: passed all the tier's checks. `Valid`: found, but not fully\nverified; treat it as risky, for example on a catch-all domain.\n`Unverified`: found, but failed the check, for example the address\nbounced. `Not Found`: nothing found at this tier's depth, or only a value\nnot verified within the tier's window.\n","title":"ContactProfileRefreshTierEmailStatus"},"ContactProfileRefreshTierEmailVerificationMethod":{"type":"string","enum":["smtp","inbox_single","inbox_triple"],"description":"`smtp`: mail server check. `inbox_single`: inbox check, one pass.\n`inbox_triple`: inbox check, several passes. `null` when the value was not\nchecked by one of these methods, and on a `Not Found` field.\n","title":"ContactProfileRefreshTierEmailVerificationMethod"},"ContactProfileRefreshTierEmailTarget":{"type":"string","enum":["Verified","Valid"],"description":"`in_progress` only: the status the tier needs.","title":"ContactProfileRefreshTierEmailTarget"},"ContactProfileRefreshTierEmail":{"type":"object","properties":{"value":{"type":["string","null"],"description":"The email. `null` when it is not delivered."},"status":{"$ref":"#/components/schemas/ContactProfileRefreshTierEmailStatus","description":"`Verified`: passed all the tier's checks. `Valid`: found, but not fully\nverified; treat it as risky, for example on a catch-all domain.\n`Unverified`: found, but failed the check, for example the address\nbounced. `Not Found`: nothing found at this tier's depth, or only a value\nnot verified within the tier's window.\n"},"verified_at":{"type":["string","null"],"format":"date-time","description":"When this value was last verified."},"verification_method":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshTierEmailVerificationMethod"},{"type":"null"}],"description":"`smtp`: mail server check. `inbox_single`: inbox check, one pass.\n`inbox_triple`: inbox check, several passes. `null` when the value was not\nchecked by one of these methods, and on a `Not Found` field.\n"},"guaranteed":{"type":"boolean","description":"`premium` only: `true` when the contact replied to the email."},"target":{"$ref":"#/components/schemas/ContactProfileRefreshTierEmailTarget","description":"`in_progress` only: the status the tier needs."}},"title":"ContactProfileRefreshTierEmail"},"ContactProfileRefreshTierPhoneStatus":{"type":"string","enum":["Verified","Valid","Unverified","Not Found"],"description":"`Verified`: the call reached the contact or their own voicemail.\n`Valid`: the number is valid, but no call confirmed that it is the\ncontact's. `Unverified`: the number is wrong. `Not Found`: nothing found\nat this tier's depth, or only a value not verified within the tier's\nwindow.\n","title":"ContactProfileRefreshTierPhoneStatus"},"ContactProfileRefreshTierPhoneVerificationMethod":{"type":"string","enum":["validation_only","dial_ai"],"description":"`validation_only`: number format and carrier check, no call. `dial_ai`:\nautomated call. `null` when the value was not checked by one of these\nmethods, on a `Not Found` field, and on `corporate_phone`.\n","title":"ContactProfileRefreshTierPhoneVerificationMethod"},"ContactProfileRefreshTierPhoneDialResult":{"type":"string","enum":["Connected to Contact","Voicemail of Contact","Generic Voicemail","Connected, identity not confirmed","Connected, declined to confirm","No Answer","Wrong Number"],"description":"The outcome of the call; `null` when no call was logged.","title":"ContactProfileRefreshTierPhoneDialResult"},"ContactProfileRefreshTierPhoneTarget":{"type":"string","enum":["Verified","Valid"],"description":"`in_progress` only: the status the tier needs.","title":"ContactProfileRefreshTierPhoneTarget"},"ContactProfileRefreshTierPhone":{"type":"object","properties":{"value":{"type":["string","null"],"description":"The phone number. `null` when it is not delivered."},"status":{"$ref":"#/components/schemas/ContactProfileRefreshTierPhoneStatus","description":"`Verified`: the call reached the contact or their own voicemail.\n`Valid`: the number is valid, but no call confirmed that it is the\ncontact's. `Unverified`: the number is wrong. `Not Found`: nothing found\nat this tier's depth, or only a value not verified within the tier's\nwindow.\n"},"verified_at":{"type":["string","null"],"format":"date-time","description":"When this value was last verified."},"verification_method":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshTierPhoneVerificationMethod"},{"type":"null"}],"description":"`validation_only`: number format and carrier check, no call. `dial_ai`:\nautomated call. `null` when the value was not checked by one of these\nmethods, on a `Not Found` field, and on `corporate_phone`.\n"},"dial_result":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshTierPhoneDialResult"},{"type":"null"}],"description":"The outcome of the call; `null` when no call was logged."},"guaranteed":{"type":"boolean","description":"`premium` only: `true` when the contact answered the call in person."},"target":{"$ref":"#/components/schemas/ContactProfileRefreshTierPhoneTarget","description":"`in_progress` only: the status the tier needs."}},"title":"ContactProfileRefreshTierPhone"},"ContactProfileRefreshProfileWithTierContactData0":{"type":"object","properties":{"id":{"type":["integer","null"],"description":"The matched Dealsignal contact ID; `null` until a contact is matched.\nA finished profile (`completed` or `not_found`) with `id` `null` has no\nmatched contact — use this field, not `search_status`, to tell matched\nfrom unmatched profiles.\n"},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"logo_url":{"type":["string","null"]},"company_name":{"type":["string","null"]},"title":{"type":["string","null"]},"changed_company":{"type":"boolean","description":"Always `true` or `false`, never `null`. Whether the contact's **primary\nemployer** differs from your account's previous refresh of this\ncontact. Identical to `ds_company_change` in the `changes` format. A\nprofile without a verdict yet (refresh not concluded, no contact\nmatched) reads `false`. The first refresh of a contact for your\naccount has no previous refresh to compare with and reads `false` —\nit establishes the baseline for the next one. `false` therefore does\nnot distinguish \"no change\" from \"no verdict\"; use\n`response_format=changes` and `ds_change_status` when that matters.\n"},"changed_title":{"type":"boolean","description":"Always `true` or `false`, never `null`. Whether the contact's **primary\ntitle** differs from your account's previous refresh of this\ncontact — same rules as\n`changed_company`. Identical to `ds_title_change` in the `changes`\nformat.\n"},"linkedin_url":{"type":["string","null"]},"company_website":{"type":["string","null"]},"seniorities":{"type":["string","null"],"description":"Seniority names, joined with ` || ` when there are several."},"departments":{"type":["string","null"],"description":"Department names, joined with ` || ` when there are several."},"contact_industry":{"type":["string","null"]},"contact_location":{"type":["string","null"]},"contact_country":{"type":["string","null"]},"contact_state":{"type":["string","null"]},"contact_city":{"type":["string","null"]},"skills":{"type":["string","null"]},"profile_completeness":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshProfileWithTierContactDataOneOf0ProfileCompleteness"},{"type":"null"}],"description":"How complete the contact's profile is; `null` until a contact is matched."},"enriched_by":{"type":"string","description":"Always `dealsignal`."},"contact_updated_at":{"type":["string","null"],"format":"date-time"},"primary_position":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshProfileWithTierContactDataOneOf0PrimaryPosition"},{"type":"null"}],"description":"The contact's primary position; `null` until a contact is matched.\nSelected independently of `ds_primary_company` / `ds_primary_title`\nin the `changes` format and can differ from it on some contacts — the\nchange verdict is always about the `ds_primary_*` position.\n"},"contact_headline":{"type":["string","null"]},"contact_about_me":{"type":["string","null"]},"contact_education":{"type":["array","null"],"items":{"$ref":"#/components/schemas/ContactProfileRefreshEducation"},"description":"The contact's education, one object per entry; keys without a value\nare omitted. `[]` when the contact has none, `null` until a contact\nis matched.\n"},"current_company_and_title":{"$ref":"#/components/schemas/ContactProfileRefreshCompanyAndTitle","description":"A snapshot of the contact's current position that may predate the\nrefresh. Read `primary_position` /\n`current_position_details` for the refreshed state. `{}` when there was\nno current position or no contact is matched; never `null`.\n"},"previous_companies_and_titles":{"type":"array","items":{"$ref":"#/components/schemas/ContactProfileRefreshCompanyAndTitle"},"description":"The contact's ended positions, in no guaranteed order. A snapshot taken\ntogether with `current_company_and_title`. `[]` when there were none\nor no contact is matched; never `null`.\n"},"search_status":{"type":["string","null"],"description":"Processing state of this profile. `completed` and `not_found` are\nfinal; `refreshing` means the profile is being refreshed. Any other\nvalue means the profile is still in progress — rely on the request's\n`status` rather than on specific in-progress values. `null` for a\nprofile that was closed without a final status when the request\ncompleted. Whether a contact was matched is told by `contact_data.id`\n(see that field), not by `search_status`.\n"},"match_status":{"type":"string","description":"`found_and_verified` (matched and `search_status: completed`),\n`found_and_unverified` (matched, not verified by this refresh),\n`invalid_li_profile` (matched, but the LinkedIn profile is flagged\ninvalid), `not_found`, or a verification note (`Verified`,\n`Contact Not Found`, `Invalid Source Data - Contact`,\n`Invalid Source Data - Linkedin Url`, `Invalid Source Data - Email`).\n\nDon't use `match_status` to decide whether a profile is finished — use\n`search_status` and the request's `status`.\n"},"remark":{"type":["string","null"]},"enrichment_status":{"type":["string","null"],"description":"Display label of `search_status`: `Finding A Match`,\n`Found And Refreshing`, `Found And Enriching`, `Completed`,\n`Not Found`; `null` for states without a label. A profile that\nfinished (`completed` or `not_found`) without a match reports\n`Not Found`.\n"},"current_position_details":{"type":["array","null"],"items":{"$ref":"#/components/schemas/ContactProfileRefreshPositionDetail"},"description":"The contact's current positions; `null` until a contact is matched."},"old_position_details":{"type":["array","null"],"items":{"$ref":"#/components/schemas/ContactProfileRefreshPositionDetail"},"description":"The contact's ended positions; `null` until a contact is matched."},"email":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshTierEmail"},{"type":"null"}]},"mobile_number":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshTierPhone"},{"type":"null"}]},"direct_phone":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshTierPhone"},{"type":"null"}]},"corporate_phone":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshTierPhone"},{"type":"null"}],"description":"Company data, returned as stored with its stored status, or as\n`Not Found`. Tier statuses and windows don't apply to it, and it is not\ncharged. On `premium` it can stay in `pending_fields` while it is being\nworked on.\n"}},"required":["id","first_name","last_name","logo_url","company_name","title","changed_company","changed_title","linkedin_url","company_website","seniorities","departments","contact_industry","contact_location","contact_country","contact_state","contact_city","skills","profile_completeness","enriched_by","contact_updated_at","primary_position","contact_headline","contact_about_me","contact_education","current_company_and_title","previous_companies_and_titles","search_status","match_status","remark","enrichment_status","current_position_details","old_position_details"],"description":"The contact details a request with a tier asked for, next to the profile\nkeys. Each is present only when requested, and `null` until it is delivered.\n","title":"ContactProfileRefreshProfileWithTierContactData0"},"ContactProfileRefreshProfileWithTierContactData":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshProfileWithTierContactData0"},{"type":"object","additionalProperties":{"description":"Any type"}}],"description":"The enriched contact and the requested contact details. `{}` when\n`delivered` is `false`, except with the reason `no_current_employer`,\nwhich keeps the profile keys.\n","title":"ContactProfileRefreshProfileWithTierContactData"},"ContactProfileRefreshProfileWithTierTier":{"type":"string","enum":["quick","standard","premium"],"description":"The tier that ran.","title":"ContactProfileRefreshProfileWithTierTier"},"ContactProfileRefreshProfileWithTierDeliveryReason":{"type":"string","enum":["email_not_found","email_below_tier","profile_not_found","profile_outdated","profile_failed","profile_not_refreshed","no_current_employer"],"description":"Why `delivered` is `false`; `null` otherwise.\n\n- `email_not_found` — no email verified within the tier's window was found.\n- `email_below_tier` — an email was found, but it did not reach the\n  status the tier delivers.\n- `profile_not_found` — the contact's profile could not be found.\n- `profile_outdated` — no copy of the profile checked recently enough\n  was available (`quick`, `standard`).\n- `profile_failed` — the profile could not be refreshed (`quick`,\n  `standard`).\n- `profile_not_refreshed` — the refresh did not succeed, and the stored\n  profile is older than the tier's window (`premium`).\n- `no_current_employer` — the profile shows no current employer.\n","title":"ContactProfileRefreshProfileWithTierDeliveryReason"},"ContactProfileRefreshProfileWithTierPendingFieldsItems":{"type":"string","enum":["email","mobile_number","direct_phone","corporate_phone"],"title":"ContactProfileRefreshProfileWithTierPendingFieldsItems"},"ContactProfileRefreshProfileWithTierInProgress":{"type":"object","properties":{"email":{"$ref":"#/components/schemas/ContactProfileRefreshTierEmail"},"mobile_number":{"$ref":"#/components/schemas/ContactProfileRefreshTierPhone"},"direct_phone":{"$ref":"#/components/schemas/ContactProfileRefreshTierPhone"},"corporate_phone":{"$ref":"#/components/schemas/ContactProfileRefreshTierPhone"}},"description":"Interim values of fields that are not delivered yet: those in\n`pending_fields`, and a phone that is ready but waits for the email. Each\nhas the `target` status the tier needs (none on `corporate_phone`). Not\ncharged, and they can change or disappear.\n","title":"ContactProfileRefreshProfileWithTierInProgress"},"ContactProfileRefreshProfileWithTier":{"type":"object","properties":{"custom_columns":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The profile object you submitted, echoed back verbatim."},"contact_data":{"$ref":"#/components/schemas/ContactProfileRefreshProfileWithTierContactData","description":"The enriched contact and the requested contact details. `{}` when\n`delivered` is `false`, except with the reason `no_current_employer`,\nwhich keeps the profile keys.\n"},"tier":{"$ref":"#/components/schemas/ContactProfileRefreshProfileWithTierTier","description":"The tier that ran."},"delivered":{"type":["boolean","null"],"description":"`null` while the profile is in progress. `true`: the result is final.\n`false`: the contact is not delivered and not charged; this is also\nfinal. If you requested `email`, the contact is delivered only with it.\n"},"delivery_reason":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshProfileWithTierDeliveryReason"},{"type":"null"}],"description":"Why `delivered` is `false`; `null` otherwise.\n\n- `email_not_found` — no email verified within the tier's window was found.\n- `email_below_tier` — an email was found, but it did not reach the\n  status the tier delivers.\n- `profile_not_found` — the contact's profile could not be found.\n- `profile_outdated` — no copy of the profile checked recently enough\n  was available (`quick`, `standard`).\n- `profile_failed` — the profile could not be refreshed (`quick`,\n  `standard`).\n- `profile_not_refreshed` — the refresh did not succeed, and the stored\n  profile is older than the tier's window (`premium`).\n- `no_current_employer` — the profile shows no current employer.\n"},"tier_satisfied":{"type":"boolean","description":"`true` when the profile is finished and every requested field reached\nthe tier's status or ended `Not Found`. Use `delivered` to decide\nwhether the contact is delivered.\n"},"credits_charged":{"type":"number","format":"double","description":"Credits charged for this profile so far. Final once the request's\n`status` is `completed`.\n"},"pending_fields":{"type":"array","items":{"$ref":"#/components/schemas/ContactProfileRefreshProfileWithTierPendingFieldsItems"},"description":"Requested fields still being worked on. It can also be empty while the\nprofile itself is being refreshed, for example on a request for\n`contact_profile` only, so use `delivered` to tell when it is finished.\n"},"in_progress":{"$ref":"#/components/schemas/ContactProfileRefreshProfileWithTierInProgress","description":"Interim values of fields that are not delivered yet: those in\n`pending_fields`, and a phone that is ready but waits for the email. Each\nhas the `target` status the tier needs (none on `corporate_phone`). Not\ncharged, and they can change or disappear.\n"}},"description":"One profile of a request that named a `verification_tier`. It is finished\nwhen `delivered` is not `null`.\n\n`contact_data` contains only fields that have reached the requested tier's\nverification status. `in_progress` is interim and is not suitable for\naccuracy comparisons or production sends.\n","title":"ContactProfileRefreshProfileWithTier"},"ContactProfileRefreshProfile":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshProfileWithoutTier"},{"$ref":"#/components/schemas/ContactProfileRefreshProfileWithTier"}],"description":"One profile of a request. Its shape depends on whether the request named\na `verification_tier`.\n","title":"ContactProfileRefreshProfile"},"ContactProfileRefreshRequestCreateResponse":{"type":"object","properties":{"id":{"type":"integer","description":"Request ID."},"unique_identifier":{"type":"string","format":"uuid","description":"Use this to poll `request_status`."},"webhook":{"type":["string","null"],"description":"The `webhook` URL you sent, if any; `null` when omitted (`\"\"` when sent empty)."},"status":{"$ref":"#/components/schemas/ContactProfileRefreshRequestCreateResponseStatus","description":"`running` while background processing continues; `completed` once every\nprofile is resolved (possibly already in the create response).\n"},"contact_refresh_time":{"type":["integer","null"],"description":"The freshness threshold (days) sent at creation, echoed back; `null`\nwhen it was omitted (the 30-day default, or the tier's window, then\napplies).\n"},"user":{"$ref":"#/components/schemas/ContactProfileRefreshRequestUser"},"contact_profile_refreshes":{"type":"array","items":{"$ref":"#/components/schemas/ContactProfileRefreshProfile"},"description":"Present only when `synchronous` is `true` (the default): the\nprofiles created inside the call, each with the contact matched\nso far. Contacts that still have to be refreshed arrive later via\nthe webhook or `request_status`.\n"}},"description":"The request envelope. Returned by the create call and by `request_status`\n(full format).\n","title":"ContactProfileRefreshRequestCreateResponse"},"ApiV1ContactProfileRefreshRequestsRequestStatusGetParametersResponseFormat":{"type":"string","enum":["summary","changes"],"title":"ApiV1ContactProfileRefreshRequestsRequestStatusGetParametersResponseFormat"},"ContactProfileRefreshRequestFullStatus":{"type":"string","enum":["running","completed"],"description":"`running` while background processing continues; `completed` once every\nprofile is resolved (possibly already in the create response).\n","title":"ContactProfileRefreshRequestFullStatus"},"ContactProfileRefreshRequestFullContactProfileRefreshes0":{"type":"array","items":{"$ref":"#/components/schemas/ContactProfileRefreshProfile"},"title":"ContactProfileRefreshRequestFullContactProfileRefreshes0"},"ContactProfileRefreshPaginatedProfilesPageNo":{"oneOf":[{"type":"integer"},{"type":"string"}],"description":"The value you sent, echoed **verbatim** — a query-string value comes\nback as a string (`\"2\"`), an omitted one as the integer default `1`.\nTyped clients must accept both.\n","title":"ContactProfileRefreshPaginatedProfilesPageNo"},"ContactProfileRefreshPaginatedProfilesPageSize":{"oneOf":[{"type":"integer"},{"type":"string"}],"description":"Echoed verbatim like `page_no`: a string when you supplied it, the\ninteger `50` when defaulted.\n","title":"ContactProfileRefreshPaginatedProfilesPageSize"},"ContactProfileRefreshPaginatedProfiles":{"type":"object","properties":{"total":{"type":"integer","description":"Total number of profiles in the request."},"page_no":{"$ref":"#/components/schemas/ContactProfileRefreshPaginatedProfilesPageNo","description":"The value you sent, echoed **verbatim** — a query-string value comes\nback as a string (`\"2\"`), an omitted one as the integer default `1`.\nTyped clients must accept both.\n"},"page_size":{"$ref":"#/components/schemas/ContactProfileRefreshPaginatedProfilesPageSize","description":"Echoed verbatim like `page_no`: a string when you supplied it, the\ninteger `50` when defaulted.\n"},"profiles":{"type":"array","items":{"$ref":"#/components/schemas/ContactProfileRefreshProfile"},"description":"Profile objects for the current page."}},"description":"Paginated wrapper around the profile list, used for **asynchronous**\nrequests (`synchronous: false`).\n","title":"ContactProfileRefreshPaginatedProfiles"},"ContactProfileRefreshRequestFullContactProfileRefreshes":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshRequestFullContactProfileRefreshes0"},{"$ref":"#/components/schemas/ContactProfileRefreshPaginatedProfiles"}],"description":"Every profile of the request, in its current state. The shape\ndepends on how the request was created: a flat **array** of\nprofile objects for a synchronous\nrequest (all profiles, never paginated) or a paginated **object**\nfor an asynchronous request.\n","title":"ContactProfileRefreshRequestFullContactProfileRefreshes"},"ContactProfileRefreshRequestFull":{"type":"object","properties":{"id":{"type":"integer","description":"Request ID."},"unique_identifier":{"type":"string","format":"uuid","description":"Use this to poll `request_status`."},"webhook":{"type":["string","null"],"description":"The `webhook` URL you sent, if any; `null` when omitted (`\"\"` when sent empty)."},"status":{"$ref":"#/components/schemas/ContactProfileRefreshRequestFullStatus","description":"`running` while background processing continues; `completed` once every\nprofile is resolved (possibly already in the create response).\n"},"contact_refresh_time":{"type":["integer","null"],"description":"The freshness threshold (days) sent at creation, echoed back; `null`\nwhen it was omitted (the 30-day default, or the tier's window, then\napplies).\n"},"user":{"$ref":"#/components/schemas/ContactProfileRefreshRequestUser"},"contact_profile_refreshes":{"$ref":"#/components/schemas/ContactProfileRefreshRequestFullContactProfileRefreshes","description":"Every profile of the request, in its current state. The shape\ndepends on how the request was created: a flat **array** of\nprofile objects for a synchronous\nrequest (all profiles, never paginated) or a paginated **object**\nfor an asynchronous request.\n"}},"description":"Returned when `response_format` is omitted (or any other value) — the\nrequest envelope plus every profile of the request.\n","title":"ContactProfileRefreshRequestFull"},"ContactProfileRefreshRequestSummaryStatus":{"type":"string","enum":["running","completed"],"title":"ContactProfileRefreshRequestSummaryStatus"},"ContactProfileRefreshRequestSummary":{"type":"object","properties":{"request_id":{"type":"integer","description":"Request ID."},"status":{"$ref":"#/components/schemas/ContactProfileRefreshRequestSummaryStatus"},"unique_identifier":{"type":"string","format":"uuid"}},"description":"Returned when `response_format=summary` — status fields only, no profile data.","title":"ContactProfileRefreshRequestSummary"},"ContactProfileRefreshRequestChangesStatus":{"type":"string","enum":["running","completed"],"title":"ContactProfileRefreshRequestChangesStatus"},"ContactProfileRefreshChangeProfileDsPositionState":{"type":"string","enum":["single_current","multiple_current","no_current","undetermined"],"description":"How the contact's current positions look after the refresh.\n`undetermined`: the contact has current positions but none with a\nstart date, so no primary can be selected.\n","title":"ContactProfileRefreshChangeProfileDsPositionState"},"ContactProfileRefreshChangeProfileDsPositionPrimacyConfidence":{"type":"string","enum":["high","medium","low"],"description":"How confident Dealsignal is that the selected position is the\ncontact's primary one. Omitted together with `ds_primary_company`.\n","title":"ContactProfileRefreshChangeProfileDsPositionPrimacyConfidence"},"ContactProfileRefreshChangeProfileDsCompanyChangeType":{"type":"string","enum":["job_change","left_employer","returned_to_work","newly_added","dropped_role","consolidated","added_role","moved_within_corporate_family","no_change","undetermined"],"description":"The highest-precedence company event. `newly_added` = first positions\never captured for this contact; `consolidated` = a role dropped, one\nemployer left; `moved_within_corporate_family` = parent/subsidiary or\npost-acquisition rename (`ds_company_change` stays `false`).\n","title":"ContactProfileRefreshChangeProfileDsCompanyChangeType"},"ContactProfileRefreshChangeProfileDsTitleChangeType":{"type":"string","enum":["title_change_promotion","title_change_lateral","title_change_demotion","title_gained","title_lost","newly_added","no_change","undetermined"],"title":"ContactProfileRefreshChangeProfileDsTitleChangeType"},"ContactProfileRefreshChangeEventName":{"type":"string","enum":["job_change","left_employer","returned_to_work","added_role","dropped_role","title_change","newly_added","moved_within_corporate_family"],"title":"ContactProfileRefreshChangeEventName"},"ContactProfileRefreshChangeEventConfidence":{"type":"string","enum":["100","85","75"],"description":"Confidence in the event — `100`, `85` or `75`.","title":"ContactProfileRefreshChangeEventConfidence"},"ContactProfileRefreshChangeEvent":{"type":"object","properties":{"name":{"$ref":"#/components/schemas/ContactProfileRefreshChangeEventName"},"confidence":{"$ref":"#/components/schemas/ContactProfileRefreshChangeEventConfidence","description":"Confidence in the event — `100`, `85` or `75`."},"previous_company":{"type":["string","null"]},"current_company":{"type":["string","null"]},"previous_title":{"type":["string","null"]},"current_title":{"type":["string","null"]},"company":{"type":["string","null"]},"title":{"type":["string","null"]},"remaining_title":{"type":["string","null"]},"remaining_company":{"type":["string","null"]},"previous_primary_title":{"type":["string","null"]},"last_held_title":{"type":["string","null"]},"relation":{"type":"string","description":"Corporate-family relation, on `moved_within_corporate_family` only."},"start_date":{"type":"string","format":"date","description":"On `added_role` only — start date of the added role."},"new_seat":{"type":"boolean","description":"On `left_employer` only — the remaining role is a new advisory or board seat."},"after_employment_gap":{"type":"boolean","description":"On `job_change` only — the move followed a period with no role."},"previous_role_continues":{"type":"boolean"},"transient":{"type":"boolean"},"interim":{"type":"boolean"},"recent_start":{"type":"boolean"}},"required":["name","confidence"],"description":"One detected change event. `name` and `confidence` are always present;\nthe remaining keys are the event's payload and are **omitted** when they\ndo not apply:\n\n- `job_change` — `previous_company`, `current_company`, `after_employment_gap`, `previous_role_continues`, `remaining_title`, `previous_primary_title`, `transient`\n- `left_employer` — `previous_company`, `previous_title`, `remaining_title`, `remaining_company`, `previous_primary_title`, `interim`, `new_seat`\n- `returned_to_work` — `company`, `current_title`, `last_held_title`\n- `added_role` — `title`, `company`, `start_date`\n- `dropped_role` — `company`\n- `title_change` — `previous_title`, `current_title`\n- `newly_added` — `company`, `current_title`, `recent_start`\n- `moved_within_corporate_family` — `previous_company`, `current_company`, `relation`\n","title":"ContactProfileRefreshChangeEvent"},"ContactProfileRefreshChangeStatusCompany":{"type":"string","enum":["determined","undetermined"],"title":"ContactProfileRefreshChangeStatusCompany"},"ContactProfileRefreshChangeStatusTitle":{"type":"string","enum":["determined","undetermined"],"title":"ContactProfileRefreshChangeStatusTitle"},"ContactProfileRefreshChangeStatusReasonsItems":{"type":"string","enum":["no prior snapshot","current employer could not be matched","profile not captured","position state undetermined"],"title":"ContactProfileRefreshChangeStatusReasonsItems"},"ContactProfileRefreshChangeStatus":{"type":"object","properties":{"company":{"$ref":"#/components/schemas/ContactProfileRefreshChangeStatusCompany"},"title":{"$ref":"#/components/schemas/ContactProfileRefreshChangeStatusTitle"},"reasons":{"type":"array","items":{"$ref":"#/components/schemas/ContactProfileRefreshChangeStatusReasonsItems"}}},"description":"Whether the company and title verdicts could be determined, and why not.\n`undetermined` means **no verdict**, not \"no change\": the matching\nboolean (`ds_company_change` / `ds_title_change`) reads `false` but must\nnot be treated as \"nothing changed\".\n","title":"ContactProfileRefreshChangeStatus"},"ContactProfileRefreshChangeProfile":{"type":"object","properties":{"custom_columns":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The profile object you submitted, echoed back verbatim."},"id":{"type":["integer","null"],"description":"The matched Dealsignal contact's ID; `null` until a contact has been\nmatched. Same rule as `contact_data.id` in the full format: a finished\nprofile with `id` `null` has no matched contact.\n"},"first_name":{"type":["string","null"],"description":"The matched contact's identity **at the time the verdict was\ncomputed** — not the value you submitted. `null` while no verdict\nexists.\n"},"last_name":{"type":["string","null"],"description":"Identity at verdict time, like `first_name`."},"linkedin_url":{"type":["string","null"],"description":"Identity at verdict time, like `first_name`."},"search_status":{"type":["string","null"],"description":"Processing state of the profile (same values as in the full format).\n`null` for a profile that was closed without a final status when the\nrequest completed. Whether a contact was matched is told by `id`, not\nby `search_status`.\n"},"match_status":{"type":"string","description":"Same values as in the full format. Reflects the contact's current\nstate and can change after completion (for example when the LinkedIn\nprofile is later flagged invalid).\n"},"ds_position_state":{"$ref":"#/components/schemas/ContactProfileRefreshChangeProfileDsPositionState","description":"How the contact's current positions look after the refresh.\n`undetermined`: the contact has current positions but none with a\nstart date, so no primary can be selected.\n"},"ds_current_position_count":{"type":"integer","description":"Number of dated current positions; `0` when `ds_position_state` is\n`no_current` or `undetermined`.\n"},"ds_primary_company":{"type":"string","description":"The primary position selected after the refresh — the one the verdict\nis about. Can differ from `primary_position` in the full format.\n**Omitted** when `ds_position_state` is `no_current` or\n`undetermined`.\n"},"ds_primary_title":{"type":"string","description":"Omitted together with `ds_primary_company`."},"ds_primary_start_date":{"type":"string","format":"date","description":"Start date of the selected primary position. Omitted together with `ds_primary_company`."},"ds_position_primacy_confidence":{"$ref":"#/components/schemas/ContactProfileRefreshChangeProfileDsPositionPrimacyConfidence","description":"How confident Dealsignal is that the selected position is the\ncontact's primary one. Omitted together with `ds_primary_company`.\n"},"ds_baseline_primary_company":{"type":["string","null"],"description":"The primary company captured at your account's **previous** refresh of\nthis contact — what the verdict was compared against. Unlike the\n`ds_primary_*` keys it is never omitted: whenever the `ds_*` fields\nare present this key is too, `null` on the first refresh and when the\nprevious refresh found no current position.\n"},"ds_baseline_primary_title":{"type":["string","null"],"description":"The primary title captured at the previous refresh. Never omitted,\nlike `ds_baseline_primary_company`; `null` on the first refresh and\nwhen the previous refresh found no current position.\n"},"ds_first_seen":{"type":"boolean","description":"`true` when Dealsignal had no record of this contact before this request."},"ds_company_change":{"type":"boolean","description":"The primary employer differs from the baseline. Equal to `changed_company` in the full format."},"ds_title_change":{"type":"boolean","description":"The primary title differs from the baseline. Equal to `changed_title` in the full format."},"ds_job_changes":{"type":"boolean","description":"The contact's **set** of current employers changed (a role was added\nor dropped), independently of whether the primary moved.\n"},"ds_company_change_type":{"$ref":"#/components/schemas/ContactProfileRefreshChangeProfileDsCompanyChangeType","description":"The highest-precedence company event. `newly_added` = first positions\never captured for this contact; `consolidated` = a role dropped, one\nemployer left; `moved_within_corporate_family` = parent/subsidiary or\npost-acquisition rename (`ds_company_change` stays `false`).\n"},"ds_title_change_type":{"$ref":"#/components/schemas/ContactProfileRefreshChangeProfileDsTitleChangeType"},"ds_change_events":{"type":"array","items":{"$ref":"#/components/schemas/ContactProfileRefreshChangeEvent"},"description":"Every detected event."},"ds_change_description":{"type":"string","description":"One human-readable sentence summarising the verdict, e.g.\n`Moved from Engineer of Acme to Senior Engineer of Globex`,\n`Promoted from Engineer to Senior Engineer at Globex`, `Left Acme`.\n"},"ds_change_status":{"$ref":"#/components/schemas/ContactProfileRefreshChangeStatus"}},"description":"Compact per-profile view returned by `response_format=changes`: the\nprofile's identity, its processing status and — **only once its change\nverdict is available** — the `ds_*` family of change-detection fields.\n\nThe `ds_*` keys are **omitted** (not `null`) while no verdict is\navailable: the refresh hasn't concluded for the profile yet, no contact\nwas matched, or\nthe profile was processed before change detection became available.\n\nTreat the employer as \"assessed, nothing changed\" only when the `ds_*`\nkeys are present, `ds_change_status.company` is `determined`,\n`ds_company_change` is `false` and `ds_baseline_primary_company` is not\n`null` (likewise `ds_change_status.title`, `ds_title_change` and\n`ds_baseline_primary_title` for the title). A `null` baseline means there\nwas no previous primary position to compare against; on a first refresh\nthe verdict only establishes the baseline. Never infer \"no change\" from\nabsent keys or from an `undetermined` verdict — its booleans also read\n`false`.\n","title":"ContactProfileRefreshChangeProfile"},"ContactProfileRefreshRequestChangesProfiles0":{"type":"array","items":{"$ref":"#/components/schemas/ContactProfileRefreshChangeProfile"},"title":"ContactProfileRefreshRequestChangesProfiles0"},"ContactProfileRefreshPaginatedChangeProfilesPageNo":{"oneOf":[{"type":"integer"},{"type":"string"}],"title":"ContactProfileRefreshPaginatedChangeProfilesPageNo"},"ContactProfileRefreshPaginatedChangeProfilesPageSize":{"oneOf":[{"type":"integer"},{"type":"string"}],"title":"ContactProfileRefreshPaginatedChangeProfilesPageSize"},"ContactProfileRefreshPaginatedChangeProfiles":{"type":"object","properties":{"total":{"type":"integer"},"page_no":{"$ref":"#/components/schemas/ContactProfileRefreshPaginatedChangeProfilesPageNo"},"page_size":{"$ref":"#/components/schemas/ContactProfileRefreshPaginatedChangeProfilesPageSize"},"profiles":{"type":"array","items":{"$ref":"#/components/schemas/ContactProfileRefreshChangeProfile"}}},"description":"Paginated wrapper used for **asynchronous** requests in the `changes`\nformat. Same paging rules as the full format.\n","title":"ContactProfileRefreshPaginatedChangeProfiles"},"ContactProfileRefreshRequestChangesProfiles":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshRequestChangesProfiles0"},{"$ref":"#/components/schemas/ContactProfileRefreshPaginatedChangeProfiles"}],"description":"A flat **array** for a synchronous request (always returned in full),\nor the paginated wrapper for an asynchronous request.\n","title":"ContactProfileRefreshRequestChangesProfiles"},"ContactProfileRefreshRequestChanges":{"type":"object","properties":{"request_id":{"type":"integer"},"status":{"$ref":"#/components/schemas/ContactProfileRefreshRequestChangesStatus"},"unique_identifier":{"type":"string","format":"uuid"},"profiles":{"$ref":"#/components/schemas/ContactProfileRefreshRequestChangesProfiles","description":"A flat **array** for a synchronous request (always returned in full),\nor the paginated wrapper for an asynchronous request.\n"}},"description":"Returned when `response_format=changes` — the status fields plus a\ncompact `profiles` collection carrying the `ds_*` change-detection fields\ninstead of the full `contact_data` payload.\n","title":"ContactProfileRefreshRequestChanges"},"Contact_Profile_Refresh_Requests_getContactProfileRefreshRequest_Response_200":{"oneOf":[{"$ref":"#/components/schemas/ContactProfileRefreshRequestFull"},{"$ref":"#/components/schemas/ContactProfileRefreshRequestSummary"},{"$ref":"#/components/schemas/ContactProfileRefreshRequestChanges"}],"title":"Contact Profile Refresh Requests_getContactProfileRefreshRequest_Response_200"},"CreditMetricsResponseCurrentSubscriptionData":{"type":"object","properties":{"remaining_credits":{"type":"number","format":"double","description":"Contact / Company credits still available — the plan's allowance\nplus additional credits and adjustments, minus consumption. Never negative.\n"}},"title":"CreditMetricsResponseCurrentSubscriptionData"},"CreditMetricsResponseDailyProfileRequestMetrics":{"type":"object","properties":{"requested":{"type":"integer","description":"Profiles submitted today. Profiles of a synchronous request that\nwere matched and already `completed` in the create response are\nusually left out.\n"},"requested_with_ds_contact_id":{"type":"integer","description":"Subset submitted **with** a `ds_contact_id` (refresh path)."},"requested_without_ds_contact_id":{"type":"integer","description":"Subset submitted **without** a `ds_contact_id` (search-and-refresh\npath), whether or not a contact was matched later.\n"}},"description":"Counts of Contact Profile Refresh **profiles** (rows, not requests)\nsubmitted today — the current **server** date. Profiles of a\nsynchronous request that were matched and already `completed` in the\ncreate response are usually not counted; every other profile is,\nincluding profiles closed at creation without a match\n(`contact_data.id` `null`) and every profile of an asynchronous\nrequest. Requests submitted with `test: true` are excluded.\n`requested` = `requested_with_ds_contact_id` + `requested_without_ds_contact_id`.\n","title":"CreditMetricsResponseDailyProfileRequestMetrics"},"CreditMetricsResponse":{"type":"object","properties":{"email":{"type":"string","format":"email"},"name":{"type":"string"},"username":{"type":"string"},"current_subscription_data":{"$ref":"#/components/schemas/CreditMetricsResponseCurrentSubscriptionData"},"daily_profile_request_metrics":{"$ref":"#/components/schemas/CreditMetricsResponseDailyProfileRequestMetrics","description":"Counts of Contact Profile Refresh **profiles** (rows, not requests)\nsubmitted today — the current **server** date. Profiles of a\nsynchronous request that were matched and already `completed` in the\ncreate response are usually not counted; every other profile is,\nincluding profiles closed at creation without a match\n(`contact_data.id` `null`) and every profile of an asynchronous\nrequest. Requests submitted with `test: true` are excluded.\n`requested` = `requested_with_ds_contact_id` + `requested_without_ds_contact_id`.\n"}},"description":"The metrics object is returned directly as the JSON body — there is no envelope.","title":"CreditMetricsResponse"},"ApiKeyListResponseCurrent":{"type":"object","properties":{"expires_at":{"type":["string","null"],"format":"date-time","description":"Recommended rotation date for the current key — two months after\nit was minted or last rolled. It is a reminder, not an expiry: the\nkey keeps working after this date until you roll it. `null` when no\ndate is recorded.\n"}},"title":"ApiKeyListResponseCurrent"},"PreviousApiKey":{"type":"object","properties":{"id":{"type":"integer"},"expires_at":{"type":"string","format":"date-time","description":"When this previous key stops being accepted."},"created_at":{"type":"string","format":"date-time","description":"When this key was rolled away from current."}},"description":"A previously-current API key that is still inside its grace window.\nEach rolled-away key has its own independent `expires_at`; multiple\nprevious keys can be active simultaneously. At most five stay active: a\nroll beyond that revokes the one closest to its expiry right away.\n","title":"PreviousApiKey"},"ApiKeyListResponse":{"type":"object","properties":{"current":{"$ref":"#/components/schemas/ApiKeyListResponseCurrent"},"previous":{"type":"array","items":{"$ref":"#/components/schemas/PreviousApiKey"},"description":"Rolled-away keys still inside their grace windows, ordered by `expires_at` ascending."}},"title":"ApiKeyListResponse"},"ApiKeyRollRequest":{"type":"object","properties":{"api_key":{"type":"string","description":"Your current API key. **Must be supplied in the request body.**\nPassing it via query string returns `400` — query-string credentials\nleak into access logs, CDN logs, browser history, and `Referer` headers.\n"},"expires_in_hours":{"type":"integer","minimum":1,"default":48,"description":"Grace period (in hours) for the rolled-away key before it expires.\nDefaults to 48 hours and is clamped to a maximum of 48 hours.\n"}},"required":["api_key"],"description":"Mints a new API key and parks the current one as a previous key with its\nown grace window. The previous key remains valid until its `expires_at`.\n","title":"ApiKeyRollRequest"},"ApiKeyRollResponse":{"type":"object","properties":{"api_key":{"type":"string","description":"The newly-minted API key. Store this — plaintext is not returned again."},"recommended_rotation_date":{"type":"string","format":"date-time","description":"Recommended rotation date for the new key — two months after the\nroll. It is a reminder, not an expiry: the key keeps working after\nthis date until you roll it.\n"},"previous_api_key_expires_at":{"type":"string","format":"date-time","description":"When the just-parked previous key will stop being accepted."}},"title":"ApiKeyRollResponse"},"ApiKeyRollError":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"],"description":"Error body of the API key roll's `400` and `429` responses.","title":"ApiKeyRollError"},"ApiKeyRollRateLimitedError":{"type":"object","properties":{"error":{"type":"string"},"retry_after_seconds":{"type":"integer","description":"Seconds until the next roll is permitted (at most 300 — the window is\n5 minutes). Mirrors the `Retry-After` response header.\n"}},"required":["error"],"description":"Error body of the API key roll's `400` and `429` responses.","title":"ApiKeyRollRateLimitedError"}}}}