Skip to main content
PATCH
Update contact
PATCH /candidates/{id} is a permanent alias of this endpoint and keeps working unchanged, with the same scopes and the same response. See Resource names.
Updates an existing contact. Only the fields you send are changed; everything else is left alone.

Overview

A partial update, so you send the fields you want to change and nothing more. Use it to correct a phone number, move someone’s status, change their job assignments, or attach metadata.

Use Cases

  • Status updates: move a contact through your own process
  • Reachability: change email, phone number or name
  • Gender override: set or clear it for accurate addressing on the call
  • Job assignments: attach, replace or clear them
  • Metadata: add or change your own opaque key-value pairs — not the same thing as contact fields
  • Refinement: fill the record in as you learn more

Partial Updates

You can update any combination of fields:
metadata is not readable by an agent. It is your own scratch space — free-form, unvalidated, and never placed in a prompt. If you want a value an agent can say, it has to be a contact field: catalogued, typed, and addressable as {{contact.<key>}}. See Update Contact.
Sending metadata never disturbs the contact’s fields. The two are stored separately, so replacing metadata wholesale cannot reach the catalogued values: they survive the write untouched, and the response shows them back to you under fields. A routine sync of your own payload cannot erase a value an agent depends on.It does not work as a back door either. Nothing on the metadata path checks a value against its field’s type, enum or reserved-key rules, and nothing reads a contact field out of it — whatever you put there stays your own scratch space. fields below is the validated door, on this endpoint and on POST /contacts, and it answers 422 naming the key when a value does not fit.

Updating Field Values

Send fields to change a contact’s catalogued values in the same request as anything else:
fields merges; metadata replaces. A key you omit keeps its value, and an explicit null clears one. The two behave differently in the same request body on purpose: metadata is one opaque document you own, fields are individually catalogued values.
"fields": null is not a wipe — it reads as “no values in this request” and is a no-op. Clearing is per key, which is what stops a partial update from blanking a contact by accident.

Values are validated against the catalog

Every value is checked against its field’s declared type, and an unknown key is refused. Both answer 422 naming the key — never a silent drop, because a caller who believes it wrote a value it did not is the worse outcome. 422 rather than 400: the request is well formed, it is the catalog that rejects its contents. Validation runs in the same transaction as the contact update, so a rejection applies nothing — not the status, not the metadata, not the other values. Reserved and read-only keys are refused too: first_name, email and the rest are set as the contact’s own properties above, and company_description and agent_name resolve from your company and your agent, so a value stored under them would be shadowed on every call.

tags takes two shapes

A tags field stores a string[] and accepts either the array or one comma-separated string — so a single CSV cell holding several tags maps onto one field without your side splitting it first. The two are equivalent:
Entries are trimmed, blanks are dropped ("vip,, renewal," is two tags) and duplicates are removed case-insensitively, keeping the spelling that came first — ["VIP","vip"] stores ["VIP"].
A comma is always a separator, so a tag cannot contain one. That holds inside an array element too: ["north, east"] stores two tags, exactly as "north, east" does. The two shapes would otherwise mean different things, and a value edited in the dashboard — which renders the list as one comma-joined line — would split on the next save with no warning.
Bounded, because these are stored on the contact and read on every call: at most 50 tags, 60 characters per tag, and 2000 characters of raw tag text per request. Each is a 422 naming the limit.
An empty list is the erase. [], [""] and "," all say the contact has no tags, so they clear the field exactly as null and "" do — you never get a key present with an empty array behind it.
What it will not do is guess: a number, a boolean or an object is a 422 rather than a one-element list, because a caller sending 42 for a tags field has a mapping bug and a silent ["42"] would be spoken on a call.

?createMissingFields=true

Off by default. When passed, a key in fields that your catalog does not know yet is defined for you as a string field, instead of the request being rejected.
It works the same way on POST /contacts.
A typo becomes a permanent field. oder_number creates oder_number, visible in every agent’s variable palette until somebody deletes it. That is why the option is opt-in per request rather than a mode you can leave on.
Two things to know:
  • It needs no extra scope. The same write:contacts that lets you define a field explicitly lets you define one implicitly. What it will still never do is quietly ignore an unknown key you did not ask it to create.
  • The field is always string. The value is the only evidence available and it is a poor one: "1" could be a number, a boolean or an order reference, and a wrong guess becomes validation that rejects your next row. PATCH /contact-fields/{id} retypes it afterwards. Reserved keys, malformed keys and a company already at its field ceiling are all still refused.
For anything you control, define the fields explicitly with POST /contact-fields — you get the right type and label, and a typo fails loudly.

Managing Job Assignments

jobIds sets which jobs a contact is associated with. It replaces the array rather than adding to it:
Job validation: every id in jobIds must name an existing job belonging to your API key’s company. An unknown id is a validation error, not a silent omission.
Concurrent jobIds updates lose assignments. Because the field replaces the array, adding one job is a read-modify-write, and the endpoint offers no precondition to make that safe — no If-Match, no version field. Two requests that both read ["job-1"] and each append their own job leave the contact with whichever wrote last, and the other assignment is gone. It is not reported: both calls answer 200.Serialise jobIds writes for a given contact — a queue or a lock of your own, keyed on the contact id — rather than issuing them in parallel from several workers. Fields that are not read-modify-write (email, status, metadata) do not have this problem.

Status Values

status is one of:
The names are hiring-flavoured because they predate the neutral vocabulary, and they are the values the API accepts, so they are unchanged. Sending anything else is a 400.

Company Isolation

You can only update contacts belonging to your API key’s company. Ownership is validated before anything is written.

Error Scenarios

  • 404 Not Found: the contact does not exist, or has been deleted
  • 403 Forbidden: the contact belongs to a different company, or a jobIds entry names another company’s job
  • 400 Bad Request: invalid field values, or a jobIds entry naming no job at all

Contacts Resource Guide

Learn about managing contacts and their status workflow

Get Contact

Read the current state first

Create Contact

Create a new contact

Authorizations

Authorization
string
header
required

API key for authentication using Bearer scheme

Path Parameters

id
string<uuid>
required

Query Parameters

companyId
string

Required for ATS API keys to specify which company to access. Ignored for standard company API keys.

createMissingFields
boolean
default:false

Define any key in fields that your catalog does not know yet, as a STRING field, instead of rejecting the request. Off by default, because a typo would otherwise become a permanent field in your catalog — visible in every agent's variable palette until somebody deletes it. A key the platform reserves, a malformed key, and a company already at its field ceiling are all still refused.

Body

application/json
firstName
string

The contact's first name

Required string length: 1 - 100
Example:

"John"

lastName
string

The contact's last name

Required string length: 1 - 100
Example:

"Doe"

email
string

The contact's email address

Example:

"john.doe@example.com"

phoneNumber
string

The contact's phone number in E.164 format

Pattern: ^\+[1-9]\d{1,14}$
Example:

"+421901234567"

gdprExpiryDate
string<date>

GDPR expiry date in ISO 8601 format (must be in the future)

Example:

"2026-11-16"

status
enum<string>

Contact status

Available options:
UNDEFINED,
APPLIED,
IN_PROCESS,
REJECTED,
ACCEPTED
Example:

"IN_PROCESS"

jobIds
string<uuid>[]

Array of job IDs to assign the contact to (replaces existing assignments)

Example:
metadata
object

Custom metadata (replaces existing metadata, max 10KB, 5 levels deep, 50 keys)

Example:
fields
object

Catalogued field values, keyed by catalog key. MERGED key by key, unlike metadata which replaces: a key you omit keeps its value, and an explicit null clears one. Validated against each field's declared type, so an unknown key or a wrong-typed value is a 422 naming it, and the whole request is rolled back.

Example:
gender
enum<string> | null

The contact's gender. Set to 'male' or 'female' to override auto-detection, or null to clear and revert to auto-detection.

Available options:
male,
female
Example:

"female"

cvText
string

The contact's CV in plain text. This will be automatically anonymized.

Maximum string length: 50000
Example:

"Jane Doe\nSoftware Engineer\nExperience: ..."

workHistory
object[]

Optional work history items for the contact (replaces the existing ones).

Maximum array length: 20

Response

200 - application/json
id
string
required

Contact ID

Example:

"123e4567-e89b-12d3-a456-426614174000"

firstName
string
required

The contact's first name

Example:

"John"

lastName
string
required

The contact's last name

Example:

"Doe"

status
enum<string>
required

Contact status

Available options:
UNDEFINED,
APPLIED,
IN_PROCESS,
REJECTED,
ACCEPTED
Example:

"APPLIED"

fields
object
required

The contact's catalogued field values, keyed by field key — the counterpart of metadata, and the only half an agent can speak. Each key is defined in the company catalog (GET /contact-fields) and addressable in a flow as {{contact.<key>}}. Written through fields on POST /contacts and PATCH /contacts/{id}, never through metadata. A field with no value is absent rather than null; {} means none are set.

Example:
createdAt
string<date-time>
required

Created timestamp (UTC)

Example:

"2025-11-20T10:30:00Z"

updatedAt
string<date-time>
required

Updated timestamp (UTC)

Example:

"2025-11-20T10:30:00Z"

jobId
string
deprecated

[Deprecated] Single job ID; use jobIds instead.

Example:

"987e6543-e21b-12d3-a456-426614174000"

jobIds
string<uuid>[]

Jobs the contact is assigned to.

Maximum array length: 50
email
string

The contact's email address

Example:

"john.doe@example.com"

phoneNumber
string

The contact's phone number

Example:

"+421915123456"

gdprExpiryDate
string<date>

GDPR expiry date. Currently returned as a date (no time). NOTE: We plan to migrate to a timestamp with timezone (timestamptz) for global correctness.

Example:

"2026-11-16"

overallRating
number

Overall rating/match score (0-100)

Required range: 0 <= x <= 100
Example:

85

metadata
object

Your own scratch space on the contact: free-form, unvalidated, and never read by an agent. Replaced wholesale on update. Not the place for values you want an agent to say — those are fields.

Example:
analysisCount
number

Number of analyses for this contact. Not currently populated by any endpoint — treat as absent.

Example:

2

interviewCount
number

Number of conversations for this contact. Keeps its original field name, and is not currently populated by any endpoint — treat as absent.

Example:

3

Convenience links to related collections. Endpoints may be added incrementally.

Example:
gender
enum<string> | null

The contact's gender. Used for gender-aware addressing on the call. Null if not explicitly set (auto-detected from the name).

Available options:
male,
female
Example:

"female"

anonymizedCvText
string

The contact's anonymized CV in plain text.

Example:

"[NAME]\nSoftware Engineer\nExperience: ..."

workHistory
object[]

Work history items for the contact.

Maximum array length: 20