---
title: "Read or run Dex Research on a contact (v2)"
method: POST
path: "/v2/contacts/{contactId}/research"
tags: ["contacts"]
---

# Read or run Dex Research on a contact (v2)

`POST /v2/contacts/{contactId}/research`

Dex Research: a cited web research note about one contact (who they are, current focus, background, interests) plus structured findings (LinkedIn, website, email, phone) with per-finding confidence and evidence. The default body (`{}`) only READS the stored research and never generates — `research` is null when nothing has been researched yet — and reports where the contact's current or latest run is (`in_progress`, `stage`, `run_id`, `failure`). `{ run: true }` starts the pipeline (web search + page extraction + LLM summary); a run whose stored result is under 30 days old returns the cached note at once unless `force: true`. Otherwise the run executes in the background and the call answers immediately with `in_progress: true` and the run's `run_id` — poll the default body every few seconds until `in_progress` is false: the run finished when `research.run_id` equals that `run_id`, and failed when `failure` is set (`unavailable`: a research provider is down, retry later). Runs usually take 20–60 seconds and occasionally over two minutes. `in_progress: true` on a run request can also mean another request's run is already in flight — poll it the same way instead of starting another. A run auto-fills the contact's EMPTY `linkedin` / `website` fields when a high-confidence finding exists (those findings come back `auto_applied`); email and phone findings stay `pending` for the user to apply. A run request that would start a paid run is refused with 429 `research_limit_reached` once the user has used up a fair-use window — 100 runs per UTC calendar month and 50 per UTC day, counting forced re-runs and runs that later fail but never cache hits or a run already in flight — with `error.details` `{ window: "month" | "day", limit, resets_at }` and `Retry-After` set to the seconds until `resets_at`. Unlike the per-minute `rate_limited` 429 it will not clear on a retry: do not retry before `resets_at`. The default-body read is never limited this way. 404 when the contact does not belong to the authenticated user.

## Path parameters

- `contactId` string, uuid, required

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `run` boolean
  - `force` boolean

## Response `200`

Successful response

- object
  - `data` object, required
    - `research` object, nullable, required
      - `run_id` string, nullable, required
      - `status` 'success' | 'no_data_found', required
      - `reason` 'missing_name' | 'fetch_failed' | 'insufficient_info' | 'wrong_person_suspected', nullable, required
      - `researched_at` string, required
      - `one_line_summary` string, required
      - `sections` object[], required
        - `key` 'current_focus' | 'background' | 'interests', required
        - `title` string, required
        - `bullets` string[], required
      - `sources` object, required
      - `identity_confidence` 'high' | 'medium' | 'low', nullable, required
      - `fields` object[], required
        - `field` 'linkedin' | 'website' | 'email' | 'phone', required
        - `value` string, required
        - `display` string, required
        - `citations` number[], required
        - `confidence` 'high' | 'medium' | 'low', required
        - `evidence` string, required
        - `status` 'pending' | 'auto_applied' | 'applied' | 'already_present', required
    - `applied` object, nullable, required
      - `linkedin` boolean, required
      - `website` boolean, required
    - `in_progress` boolean, required
    - `stage` 'queued' | 'searching' | 'selecting' | 'reading' | 'writing' | 'done' | 'failed', nullable, required
    - `run_id` string, nullable, required
    - `failure` 'unavailable' | 'error', nullable, required

## Other responses

- `400` — Request body failed validation.
- `401` — Missing or invalid API key.
- `403` — Valid key, insufficient permission.
- `404` — Resource does not exist.
- `409` — Request conflicts with the current state of the resource.
- `429` — Rate limit exceeded.
- `500` — Unexpected server error.

## Changes

- **2026-09-23** `c12b47d614d8` — 3 info
  - added the required property `data/failure` to the response with the `200` status
  - added the required property `data/run_id` to the response with the `200` status
  - added the required property `data/stage` to the response with the `200` status
- **2026-09-03** `aac20b882b49` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/getdex/apis/dex-public-api/changes/v2/contacts/:contactId/research/post.md)

---

[API](https://skmtc.dev/getdex/apis/dex-public-api.md) · [All operations](https://skmtc.dev/getdex/apis/dex-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/getdex/apis/dex-public-api/revisions/4797af6dcfb7?raw)
