Listing and reading contacts requires
contacts:read. Creating or updating a contact (POST /v1/contacts), reassigning owners, and managing variables require contacts:write.Upsert a contact
POST /v1/contacts creates a contact or updates the matching one if it already exists. Topo matches inside your organization on email first, then on linkedin_url (both normalized), so re-sending the same person is safe and always returns the same id. Fields you send overwrite stored values; fields you omit are left untouched. Both create and update return 201.
At least one of email or linkedin_url is required.
company_domain links the contact to an account — the account (and its company) is created when it does not exist yet, and company_name is only used for that creation. Sending company_name alone does not link an account.
List and filter contacts
GET /v1/contacts returns every contact attached to your workspace with the standard pagination envelope.
GET /v1/contacts/{contact_id} fetches a single contact.
Custom variables
Each scope (contact or account) holds a maximum of 200 variables. Setting a key’s value to null clears the value but does not free its slot toward the limit — use DELETE /v1/contacts/{contact_id}/variables/{key} to fully remove a key.
GET /v1/contacts/{contact_id}/variables— read contact and account variablesPATCH /v1/contacts/{contact_id}/variables— mergecontact_variablesandaccount_variablesDELETE /v1/contacts/{contact_id}/variables/{key}?scope=contact|account— remove one key
account_variables live on the contact’s linked account, so every contact at the same company shares them. The same data is also manageable directly through the Account variables endpoints when you hold an account_id — the contact response exposes it as account_id.
Reassign owner
PATCH /v1/contacts/{contact_id}/owner with { "owner_user_id": "<uuid>" } reassigns the contact’s owner to a workspace user.