---
title: "Upsert a contact"
method: POST
path: "/public/v1/contacts"
tags: ["Contact"]
---

# Upsert a contact

`POST /public/v1/contacts`

Upsert a single contact — one POST handles both create and update. Include an existing contact `id` to update that contact, or omit `id` to create a new one. The create path returns 201 and the update path returns 200; either way the response body is the full contact.

Updates are sparse: only the fields you send are changed, and any field you omit keeps its current value. `custom_data` is the exception in one direction — sending it replaces the contact's entire custom-field map (it is not merged key-by-key), so include every key you want to keep; omitting `custom_data` leaves the existing values untouched.

A contact is a person record in your CRM. It is linked to a company relationship (its employer) via `company_id`, to an owning Distru user via `owner_id`, and carries a name/email/phone profile plus optional driver-license details that surface on an order's shipping manifest when this contact is named as the driver. Writing a contact does not move inventory and does not sync to Metrc or BioTrack; it does record an activity-log entry on the contact and notify the relevant users. New contacts appear in `GET /public/v1/contacts` within about a second (the list is eventually consistent).

Required permission: `contacts_permissions_create` to create a new contact, `contacts_permissions_edit` (plus access to the contact under your team restrictions) to update an existing one.

## Request body

- object
  - `id` string — ID of the contact. Provide it to update that contact — it must belong to your company, otherwise the request returns 404. Omit it to create a new contact.
  - `first_name` string, required — First name of the contact. Required on create; on update, omitting it leaves the current value unchanged. Must be 1-100 characters and cannot be the literal `you`/`You`. Combined with `last_name` to form the response `full_name`.
  - `last_name` string — Last name of the contact. When provided, must be 1-100 characters. Combined with `first_name` to form the response `full_name`.
  - `title` string — Job title of the contact.
  - `email` string — Email address of the contact. When provided, must contain an `@`.
  - `phone_number` string — Primary phone number of the contact.
  - `work_phone_number` string — Work phone number of the contact, returned as `work_phone_number`.
  - `description` string — Free-text note describing the contact.
  - `company_id` string — ID of the company relationship (the contact's employer) in your network that this contact belongs to. Must reference an existing company relationship you own. Returned as `company.id` in the response. Omit to create the contact without an employer; on update, omitting leaves the current employer unchanged.
  - `driver_license_number` string — Driver license number recorded on shipping manifests when this contact is assigned as the driver on an order shipment.
  - `driver_license_issuing_state` string — Two-letter US state or territory abbreviation (e.g. `CA`, `PR`) of the license's issuing state, recorded on shipping manifests when this contact is the driver. Must be one of the recognized US state/territory codes; any other value is rejected.
  - `owner_id` string — ID of the Distru user who owns this contact. Must be an existing user in your company that is assignable to you under your team restrictions. Omit on create to leave the contact without an owner; on update, omitting leaves the current owner unchanged.
  - `custom_data` object — A map of custom field IDs to their values. Use GET /public/v1/custom-fields?parent_object=contact to retrieve the available custom fields, their IDs, and their types; every key you send must be one of those IDs or the request is rejected. The value format depends on the field's type: a text field takes a string, a date field takes a full ISO8601 datetime, and a checkbox field takes an array of its selected options. Sending this field replaces the contact's entire custom-field map, so include every key you want to keep; omit it to leave existing custom values unchanged.

## Response `200`

An updated contact

## Other responses

- `201` — A new contact
- `400` — Invalid parameters
- `401` — Missing or invalid API token
- `403` — The API token lacks the required permission
- `404` — Not Found

---

[API](https://skmtc.dev/distru/apis/distru-api.md) · [All operations](https://skmtc.dev/distru/apis/distru-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/distru/distru-api/revisions/eb775b7b6ee0/schema)
