> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.dealsignal.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.dealsignal.com/_mcp/server.

# 2026-09-17 — Contact Profile Refresh Requests: change-detection fields (`ds_*`)

> `response_format=changes` now carries the `ds_*` change-detection fields; the beta `changes[]` key is gone; `changed_company` / `changed_title` compare the primary position with the previous snapshot.

## Added

### `ds_*` fields in `response_format=changes`

`GET /api/v1/contact_profile_refresh_requests/request_status?response_format=changes` now returns, per profile, a compact change-detection block instead of the full `contact_data`:

* **Position** — `ds_position_state`, `ds_current_position_count`, `ds_primary_company`, `ds_primary_title`, `ds_primary_start_date`, `ds_position_primacy_confidence`.
* **Baseline** — `ds_baseline_primary_company`, `ds_baseline_primary_title` (what the verdict was compared against; `null` on the first refresh and when the previous refresh found no current position), `ds_first_seen`.
* **Verdict** — `ds_company_change`, `ds_title_change`, `ds_job_changes`, `ds_company_change_type`, `ds_title_change_type`, `ds_change_events`, `ds_change_description`, `ds_change_status`.

The `ds_*` keys are **omitted** (not `null`) while no verdict is available for the profile — the refresh hasn't concluded for the profile yet, no contact was matched, or the profile was processed before this release. `first_name` / `last_name` / `linkedin_url` in this format are the contact's identity at the time the verdict was computed, not the values you submitted.

See **[Contact Profile Refresh Requests → response_format=changes](/documentation/contact-profile-refresh-requests/overview#response_formatchanges-and-the-ds_-fields)** for the field-by-field reference.

### Webhook documented

The webhook now has its own entry in the API Reference, **[Contact Profile Refresh Request completed](/api-reference/contact-profile-refresh-requests/contact-profile-refresh-request-completed)**: a bare JSON array of `{ custom_columns, contact_data }` for refreshed profiles, followed by a second POST with the not-found profiles. No envelope and no signature; a delivery that fails is sent again the next time you call `request_status`, and further calls can trigger additional deliveries. See **[Webhook payload](/documentation/contact-profile-refresh-requests/overview#webhook-payload)** for delivery and idempotency guidance.

---

## Changed

### `changed_company` / `changed_title` now compare the primary position with the previous snapshot

In the full format and the webhook payload, `changed_company` and `changed_title` used to mean "the set of employers changed inside a look-back window". They now answer a single question: **did the contact's primary employer / title change compared with your account's previous refresh of this contact?** They are the same values as `ds_company_change` / `ds_title_change`, computed once, when the refresh concludes for that profile, and never recomputed.

Consequences for clients:

* **The first refresh of a contact for your account reports `false` / `false`.** If the contact already had positions, `ds_change_status` is `undetermined` with reason `no prior snapshot`; if this refresh delivered the contact's first positions, both change types are `newly_added` (`no_change` when those positions have all already ended). It establishes the baseline; the second refresh is the first that can report a change.
* There is **no look-back window** any more — `contact_refresh_time` doesn't limit how far back the comparison reaches; it decides which contacts get refreshed.
* A move inside a corporate family, a renamed employer, or a role that ends and resumes at the same employer never sets `changed_company`.

### Documentation corrections

* `synchronous` defaults to **`true`** and never blocks on enrichment: the call returns every profile inline, and stale / unmatched contacts are refreshed in the background either way.
* `contact_refresh_time` is omitted for the 30-day default (the request then echoes `null`).
* `request_status` accepts `response_format` (`summary` / `changes`); `page_no` / `page_size` default to **1 / 50** and start at 1. The paginated wrapper echoes both values verbatim (as strings when supplied in the query).
* The full-format response is the Contact Profile Refresh Request (`id`, `unique_identifier`, `webhook`, `status`, `contact_refresh_time`, `user`) plus its profiles in `contact_profile_refreshes` — a flat array for synchronous requests, `{ total, page_no, page_size, profiles }` for asynchronous ones.
* `contact_data` always carries the same 33 keys; `enrichment_status` and `logo_url` live inside it. Emails and phones are never part of it. `profile_completeness` is a label (`complete` … `poor`), `contact_education` an array of objects, `current_company_and_title` an object (`{}` when none) and `previous_companies_and_titles` an array (`[]` when none) — both snapshots that may predate the refresh, not the refreshed state. `search_status` can be `null` on a profile closed without a final status.
* New request fields documented: `api_key` in the body, `find_match_and_refresh`, `requested_attributes`, and the profile field `metadata`.
* Pricing for API requests is **tiered** (0.25 / 1.00 / 1.25 / 1.50 per completed profile with a matched contact, up to the ceiling set by `requested_attributes`).
* `401` responses (invalid key, inactive account, expired subscription) and the full `422` surface are now in the spec.
* Credit metrics: `daily_profile_request_metrics` counts **profile rows** on the **server** date, not requests at midnight in the user's timezone.

---

## Removed

### Beta `changes[]` key

The beta `changes[]` array in `response_format=changes` is gone. Use `ds_change_events` and the `ds_*` booleans instead.

---

## Notes for clients

* If you consume `changed_company` / `changed_title` for job-change alerts, expect `false` / `false` on every contact's first refresh after this release and treat the second refresh as the first meaningful signal.
* In the `changes` format, key off the **presence** of `ds_*` keys and `ds_change_status` before reading the booleans — absent keys mean no verdict is available (not yet, or not at all, for example for an unmatched profile), and `undetermined` means "no verdict"; neither means "no change".
* Correlate webhook payloads through `custom_columns` (put your own ID in `metadata`); the payload carries no request identifier.