---
title: "Update one lead’s identity fields, status, notes, custom_fields, or tags"
method: PATCH
path: "/api/v1/leads/{id}"
tags: ["Leads"]
---

# Update one lead’s identity fields, status, notes, custom_fields, or tags

`PATCH /api/v1/leads/{id}`

## Request

- Base URL: `https://app.reachium.io`
- URL: `https://app.reachium.io/api/v1/leads/{id}`
- Auth: HTTP bearer

Same underlying core as the MCP updateLead tool, reused verbatim so REST and MCP callers get identical semantics. An allowlist, not a blocklist: any key not in the request body schema below (including workspace_id/lead_id/id) is rejected as an unknown field. Pass at least one field. status/notes/custom_fields live on workspace_leads (this workspace only); first_name/last_name/email/position/headline/company/linkedin_url edit the single row shared by every workspace holding this lead. custom_fields REPLACES the whole object for this workspace, it is not merged key by key. company is freetext, resolved to a company record the same way CSV import resolves it; an empty string clears the link. linkedin_url is globally unique, so setting it to a URL already used by a different lead fails (code duplicate_linkedin_url) instead of overwriting. add_tags/remove_tags go through the apply_lead_tag RPC: every tag except follow_up_later permanently ends this lead’s active campaign sequences when added, and removing it never restarts them.

## Path parameters

- `id` string, required

## Request body

- object: At least one of the fields above is required.
  - `status` 'new' | 'active' | 'contacted' | 'qualified' | 'lost' | 'unsubscribed'
  - `notes` string: Replaces the lead notes. Pass an empty string to clear.
  - `custom_fields` object: Replaces the whole object for this workspace (not merged key by key).
  - `first_name` string
  - `last_name` string
  - `email` string
  - `position` string
  - `headline` string
  - `company` string: Freetext; resolved to a company record. Empty string clears the link.
  - `linkedin_url` string: Globally unique. Fails with duplicate_linkedin_url if another lead already has it.
  - `add_tags` string[]
  - `remove_tags` string[]

## Response `200`

Updated.

- object
  - `data` object, required
    - `ok` boolean, required
    - `lead_id` string, uuid, required
    - `updated` string[], required: Field names (and tag:+name / tag:-name entries) actually written.
    - `tags` string[], nullable, required: The lead’s full tag list after this update, or null if the read-back failed (see warnings).
    - `warnings` string[], required: Non-fatal issues, e.g. a company name that could not be resolved, or one tag in a batch that failed to apply.
  - `error` unknown, required

## Other responses

- `400`: Unknown field(s) or an empty patch (code invalid_request), or the update core’s own invalid_args (e.g. an unknown tag name) or duplicate_linkedin_url (this linkedin_url is already used by a different lead).
- `401`: Missing or invalid API key. Send `Authorization: Bearer rmcp_...` (code unauthorized).
- `402`: Workspace billing is blocked; mutating requests are refused (code billing_blocked).
- `403`: The key’s scopes are below write (code insufficient_scope).
- `404`: No such lead in this workspace (code not_found).
- `429`: Rate limited: shared `mcp:ip:{ip}` (600/60s) or `mcp:key:{keyId}` (120/60s) bucket, the SAME buckets the MCP endpoint uses (code rate_limited).
- `500`: A DB error on the membership check (code query_failed) or the update itself (code update_failed).
- `503`: The billing-status check itself failed (DB error): fails closed rather than letting a blocked workspace through; safe to retry (code billing_check_failed).

---

[API](https://skmtc.dev/reachium/apis/reachium-api.md) · [All operations](https://skmtc.dev/reachium/apis/reachium-api/llms.txt) · [OpenAPI document](https://skmtc.dev/reachium/apis/reachium-api/revisions/11682826692b?raw)
