Documentation / Contacts

Contacts

The project's customer base — the Contact object, custom contact fields, tags, the activity journal, duplicate search, merging and CSV export.

A contact is a customer, lead or partner of the project. Besides its built-in fields, a contact can hold values for the project's custom fields (meta) and carry any number of tags. It can be assigned to a project member, and that assignment decides who sees it. Orders link to contacts through contact_uuid.

Deleting a contact is permanent. Its orders stay and lose their contact_uuid.

Field types follow the field notation.

Access

Every endpoint on this page requires the projects OAuth scope. All endpoints except custom fields also require the contacts scope. The user must be a member of the project, and the owner's subscription must be active. Otherwise the endpoint returns 404 RES_NOT_FOUND. The project owner bypasses permission checks.

Permission Grants
can_view_own_contacts Read contacts assigned to the user (user_uuid)
can_view_role_contacts Read contacts assigned to members with the same role as the user
can_view_all_contacts Read every contact in the project
can_edit_contacts Create, update, delete, merge and export contacts; manage tags; add journal entries
can_edit_project Manage custom contact fields

Every endpoint that addresses a contact, including updating, deleting, merging and adding journal entries, sees only the contacts within the user's widest view permission. A contact outside that range behaves as if it does not exist (404 RES_NOT_FOUND).

user_uuid must be a member of the project and tag_uuids must be tags of the project; otherwise the request fails with 400 RES_NOT_FOUND. On update, user_uuid is checked only when it changes.

Contact Object

Contact Structure

Field Type Description
uuid uuid Contact ID
name string Name, up to 255 characters. May be empty
email string Email, up to 64 characters. May be empty. Not validated as an email address
phone string Phone, up to 32 characters. May be empty. Stored as sent
address string Address, up to 512 characters
note string Free-text note, up to 1024 characters
type string lead, client or partner
status string active or archive
user_uuid ?uuid Project member the contact is assigned to
meta object Custom field values keyed by field name. Every value is a string
tags array of tag objects Tags on the contact: uuid, name and color only
total_spent decimal Sum of paid_amount over this contact's orders in a done-type status. Only in List Contacts
orders_count integer Number of this contact's orders that are not deleted. Only in List Contacts
last_order_date ?ISO8601 datetime When the contact's latest order was created. Only in List Contacts
created_at ISO8601 datetime When the contact was created
updated_at ISO8601 datetime When the contact was last changed

total_spent and orders_count are computed by Postgres and are returned as strings, e.g. "1470.00" and "3".

Example Contact

{
  "uuid": "0199a4d0-11aa-7b0c-9e8f-7a6b5c4d3e2f",
  "name": "Acme Windows LLC",
  "email": "[email protected]",
  "phone": "+1 555 010 2030",
  "address": "12 Industrial Rd, Springfield",
  "note": "",
  "type": "client",
  "status": "active",
  "user_uuid": "0198e7aa-91f0-7c22-a4d1-0f9e8d7c6b5a",
  "meta": {
    "inn": "7701234567",
    "delivery_address": "Warehouse 4, gate B"
  },
  "tags": [
    {
      "uuid": "0199a1aa-0001-7000-8000-000000000001",
      "name": "Wholesale",
      "color": "emerald"
    }
  ],
  "total_spent": "1470.00",
  "orders_count": "3",
  "last_order_date": "2026-10-01T09:30:00.000Z",
  "created_at": "2026-08-14T07:12:00.000Z",
  "updated_at": "2026-09-30T16:45:10.000Z"
}

Contact Input

The body of Create Contact, Modify Contact and Replace Contact.

Field Type Description
name string Up to 255 characters
email string Up to 64 characters
phone string Up to 32 characters
address string Up to 512 characters
note string Up to 1024 characters
type string lead, client or partner
status string active or archive
user_uuid ?uuid Assigned member; null unassigns
meta object Custom field values, {name: value}; values must be strings. See Custom field values
tag_uuids array of uuid Tags to put on the contact. Duplicates are ignored. Replaces the current tags

Custom field values

meta keys are custom field names. Values are stored as strings of up to 512 characters, whatever the field's type, so send numbers and dates as strings, e.g. "2026-10-01". On update, only the keys you send change. An empty string removes that value; keys you leave out are kept.

Values are not checked against the field definitions: an unknown key is stored, and is_required is not enforced by the API. Required fields are enforced in one place only — when an order moves into a status that requires them, see Modify Order.

List Contacts

GET /api/v1/projects/{project.uuid}/contacts

Returns a list of contact objects visible to the user, newest first. Unless a status filter is given, only active contacts are returned.

Requires one of can_view_own_contacts, can_view_role_contacts, can_view_all_contacts.

Query String Params

Field Type Description
page? integer Page number, from 1. Default 1
limit? integer Page size, 1–100. Default 50
filters? string A URL-encoded JSON array of filter objects. Invalid JSON is ignored

Filters combine with AND. The response is a plain array with no total count.

Contact Filter Structure

Field Type Description
field string What to filter on, see below
operator string equal (default), include, starts_with, greater, less, is_empty
value any Value to compare with
field Matches
name, email, phone, address, type, status The contact's own column. include and starts_with are case-sensitive
total_spent, orders_count, last_order_date The computed values above; is_empty is ignored
tags value is a tag UUID or an array of them; matches contacts with any of the tags. operator is ignored
any other string A custom field name. is_empty matches contacts with no value for it

Example Request

GET /api/v1/projects/0198e7a0-0000-7000-8000-000000000001/contacts?filters=%5B%7B%22field%22%3A%22type%22%2C%22value%22%3A%22client%22%7D%5D
Authorization: Bearer <access_token>

where filters is the encoded form of:

[
  {
    "field": "type",
    "operator": "equal",
    "value": "client"
  },
  {
    "field": "tags",
    "value": [
      "0199a1aa-0001-7000-8000-000000000001"
    ]
  }
]

Response

200 OK — an array of contact objects.

Errors

Status Code When
403 AUTH_INSUFFICIENT_SCOPE The token lacks projects or contacts
403 AUTH_NO_PROJECT_ACCESS The user's role has no contact view permission
404 RES_NOT_FOUND The project does not exist or is not accessible

Get Contact

GET /api/v1/projects/{project.uuid}/contacts/{contact.uuid}

Returns a single contact, without total_spent, orders_count and last_order_date.

Requires one of can_view_own_contacts, can_view_role_contacts, can_view_all_contacts.

Response

200 OK — a contact object.

Errors

Status Code When
403 AUTH_INSUFFICIENT_SCOPE The token lacks projects or contacts
403 AUTH_NO_PROJECT_ACCESS The user's role has no contact view permission
404 RES_NOT_FOUND The project or contact does not exist, or the contact is outside the user's visibility

Create Contact

POST /api/v1/projects/{project.uuid}/contacts

Creates a contact and writes a created entry to its journal.

Requires can_edit_contacts.

JSON Params

All fields of Contact Input are optional. Defaults: empty strings for the text fields, type: "lead", status: "active", no member, no custom values, no tags.

The API does not check for duplicates. To find existing contacts with the same phone or email, use Find Duplicate Contacts.

Example Request

{
  "name": "Acme Windows LLC",
  "email": "[email protected]",
  "phone": "+1 555 010 2030",
  "type": "client",
  "meta": {
    "inn": "7701234567"
  },
  "tag_uuids": [
    "0199a1aa-0001-7000-8000-000000000001"
  ]
}

Response

201 Created

{
  "uuid": "0199a4d0-11aa-7b0c-9e8f-7a6b5c4d3e2f"
}

Errors

Status Code When
400 REQ_VALIDATION_FAILED The body does not match the schema
400 RES_NOT_FOUND user_uuid is not a project member, or a tag is not in this project
403 AUTH_INSUFFICIENT_SCOPE The token lacks projects or contacts
403 AUTH_NO_PROJECT_ACCESS The user's role lacks can_edit_contacts
404 RES_NOT_FOUND The project does not exist or is not accessible

Modify Contact

PATCH /api/v1/projects/{project.uuid}/contacts/{contact.uuid}

Updates only the fields you send. tag_uuids, if sent, replaces all tags; [] removes them. meta changes only the keys it contains. Changes are recorded in the journal as an updated entry.

Requires can_edit_contacts.

JSON Params

Any fields of Contact Input. At least one must be present.

Example Request

{
  "status": "archive",
  "meta": {
    "delivery_address": ""
  }
}

Response

204 No Content

Errors

Status Code When
400 REQ_VALIDATION_FAILED The body does not match the schema
400 REQ_NO_DATA_PROVIDED The body has no fields
400 RES_NOT_FOUND A new user_uuid is not a project member, or a tag is not in this project
403 AUTH_INSUFFICIENT_SCOPE The token lacks projects or contacts
403 AUTH_NO_PROJECT_ACCESS The user's role lacks can_edit_contacts
404 RES_NOT_FOUND The project or contact does not exist, or the contact is outside the user's visibility

Replace Contact

PUT /api/v1/projects/{project.uuid}/contacts/{contact.uuid}

Same as Modify Contact, but name, email, phone, address, note, type and status are required. user_uuid, meta and tag_uuids stay optional and are left untouched when omitted.

Requires can_edit_contacts.

Response

204 No Content

Errors

Same as Modify Contact, except REQ_NO_DATA_PROVIDED.

Delete Contact

DELETE /api/v1/projects/{project.uuid}/contacts/{contact.uuid}

Permanently deletes the contact with its custom values and tags. Its orders remain with contact_uuid set to null.

Requires can_edit_contacts.

Response

204 No Content

Errors

Status Code When
403 AUTH_INSUFFICIENT_SCOPE The token lacks projects or contacts
403 AUTH_NO_PROJECT_ACCESS The user's role lacks can_edit_contacts
404 RES_NOT_FOUND The project or contact does not exist, or the contact is outside the user's visibility

Contact Journal

Contact Journal Entry Structure

Field Type Description
project_uuid uuid Project ID
contact_uuid ?uuid The contact
user_uuid ?uuid Who made the entry; null for system and automation entries
user_name string Author's full name; empty string if none
entity_type ?string What the entry is attached to besides the contact, e.g. order
entity_uuid ?uuid ID of that entity
action string What happened, see below
message ?string Note text, or the merged contact's name for merged
payload ?object Action details. For updated, a field-level diff of the contact, its meta and tags
created_at ISO8601 datetime When the entry was recorded
Action Meaning
created The contact was created
updated Fields, custom values or tags changed
merged Another contact was merged into this one; message holds its name
note A note, written by a user or through Create Contact Journal Entry
email An email was sent to the contact by an automation
task_created, task_completed A task linked to the contact was created or completed; see Tasks

Other actions may be added over time. Clients should show unknown actions as generic entries rather than fail.

Get Contact Journal

GET /api/v1/projects/{project.uuid}/contacts/{contact.uuid}/logs

Returns the contact's whole journal, oldest first, as an array of journal entries. This includes notes made on the contact's orders.

Requires one of can_view_own_contacts, can_view_role_contacts, can_view_all_contacts.

Errors

Status Code When
403 AUTH_INSUFFICIENT_SCOPE The token lacks projects or contacts
403 AUTH_NO_PROJECT_ACCESS The user's role has no contact view permission
404 RES_NOT_FOUND The project or contact does not exist, or the contact is outside the user's visibility

Create Contact Journal Entry

POST /api/v1/projects/{project.uuid}/contacts/{contact.uuid}/logs

Adds an entry to the contact's journal. The author is the authenticated user.

Requires can_edit_contacts.

JSON Params

Field Type Description
message? ?string Entry text
action? string Entry action. Default note
entity_type? string Attached entity type. Default order
entity_uuid? ?uuid Attached entity ID
payload? object Extra data. Default {}

For an ordinary note, send only message. To note an order, prefer Create Order Note, which also works before the order has a contact.

Example Request

{
  "message": "Called back, waiting for the signed quote."
}

Response

201 Created with an empty body.

Errors

Status Code When
400 REQ_VALIDATION_FAILED The body does not match the schema
403 AUTH_INSUFFICIENT_SCOPE The token lacks projects or contacts
403 AUTH_NO_PROJECT_ACCESS The user's role lacks can_edit_contacts
404 RES_NOT_FOUND The project or contact does not exist, or the contact is outside the user's visibility

Find Duplicate Contacts

GET /api/v1/projects/{project.uuid}/contacts/duplicates

Finds groups of active contacts that share a phone number or an email. Phones are compared by digits only, so +1 (555) 010-2030 matches 15550102030. Emails are compared trimmed and lower-cased. Names are never compared. Only contacts visible to the user are considered.

Requires can_edit_contacts.

Response

200 OK

Field Type Description
groups array Groups of duplicates; phone groups first, then email groups

Duplicate Group Structure

Field Type Description
match_type string phone or email
match_value string The normalized value the contacts share
contacts array of contact objects The matching contacts, oldest first. No updated_at or computed fields

A contact can appear in both a phone group and an email group.

Example Response

{
  "groups": [
    {
      "match_type": "phone",
      "match_value": "15550102030",
      "contacts": [
        {
          "uuid": "0199a4d0-11aa-7b0c-9e8f-7a6b5c4d3e2f",
          "name": "Acme Windows LLC",
          "phone": "+1 555 010 2030",
          "...": "..."
        },
        {
          "uuid": "0199a6f2-2b3c-7d4e-8f50-617283940a1b",
          "name": "Acme",
          "phone": "15550102030",
          "...": "..."
        }
      ]
    }
  ]
}

Errors

Status Code When
403 AUTH_INSUFFICIENT_SCOPE The token lacks projects or contacts
403 AUTH_NO_PROJECT_ACCESS The user's role lacks can_edit_contacts
404 RES_NOT_FOUND The project does not exist or is not accessible

Merge Contacts

POST /api/v1/projects/{project.uuid}/contacts/merge

Merges loser_uuid into winner_uuid in a single transaction:

  • fields are written to the winner. Send the final value for each field you resolved; fields you omit keep the winner's value.
  • The loser's orders, journal, warehouse receipts and tasks are moved to the winner.
  • Tags are combined.
  • The loser's custom values that the winner does not have are copied over. Values in fields.meta take precedence.
  • The loser is deleted, and a merged entry is added to the winner's journal.

Requires can_edit_contacts.

JSON Params

Field Type Description
winner_uuid uuid Contact that remains
loser_uuid uuid Contact that is merged and deleted; must differ from winner_uuid
fields? object Final values for the winner: any of name, email, phone, address, note, type, status, user_uuid, meta, with the same rules as Contact Input

Example Request

{
  "winner_uuid": "0199a4d0-11aa-7b0c-9e8f-7a6b5c4d3e2f",
  "loser_uuid": "0199a6f2-2b3c-7d4e-8f50-617283940a1b",
  "fields": {
    "email": "[email protected]",
    "meta": {
      "inn": "7701234567"
    }
  }
}

Response

204 No Content

Errors

Status Code When
400 REQ_VALIDATION_FAILED The body does not match the schema, or the two UUIDs are equal
403 AUTH_INSUFFICIENT_SCOPE The token lacks projects or contacts
403 AUTH_NO_PROJECT_ACCESS The user's role lacks can_edit_contacts
404 RES_NOT_FOUND The project or either contact does not exist, or one is outside the user's visibility

Export Contacts

GET /api/v1/projects/{project.uuid}/contacts/export

Exports every contact matching filters, without pagination, as a CSV file. Only contacts visible to the user are included. filters works as in List Contacts.

Requires can_edit_contacts.

Query String Params

Field Type Description
filters? string A URL-encoded JSON array of filter objects

Response

200 OK with Content-Type: text/csv; charset=utf-8 and Content-Disposition: attachment; filename="contacts.csv". The file is UTF-8 with a byte-order mark, comma-separated, with CRLF line endings.

Columns: Name, Email, Phone, Address, Note, Type, Status, Owner (assigned member's name), Tags (comma-separated names), Created at (ISO8601), then one column per custom field, titled with its label, in field order.

Name,Email,Phone,Address,Note,Type,Status,Owner,Tags,Created at,INN,Delivery address
Acme Windows LLC,[email protected],+1 555 010 2030,"12 Industrial Rd, Springfield",,client,active,Anna Petrova,Wholesale,2026-08-14T07:12:00.000Z,7701234567,"Warehouse 4, gate B"

Errors

Status Code When
403 AUTH_INSUFFICIENT_SCOPE The token lacks projects or contacts
403 AUTH_NO_PROJECT_ACCESS The user's role lacks can_edit_contacts
404 RES_NOT_FOUND The project does not exist or is not accessible

Contact Field Object

A custom field defined for all contacts of the project. Its values live in each contact's meta.

Contact Field Structure

Field Type Description
name string Key in meta, unique within the project: lowercase letters, digits and _, up to 64 characters. Cannot be changed
label string Display name, up to 128 characters
type string text, number, date, datetime or select. A hint for input UIs; values are always strings
options array Choices for select fields
is_required boolean Marks the field as required in the app. Not enforced by the API
order integer Sort position
created_at ISO8601 datetime When the field was created
updated_at ISO8601 datetime When the field was last changed

Example Contact Field

{
  "name": "delivery_address",
  "label": "Delivery address",
  "type": "text",
  "options": [],
  "is_required": false,
  "order": 2,
  "created_at": "2026-08-14T07:00:00.000Z",
  "updated_at": "2026-08-14T07:00:00.000Z"
}

List Contact Fields

GET /api/v1/projects/{project.uuid}/contacts/fields

Returns the project's contact fields, sorted by order. Any project member can list them.

Create Contact Field

POST /api/v1/projects/{project.uuid}/contacts/fields

Requires can_edit_project.

JSON Params

Field Type Description
name string Key, ^[a-z0-9_]+$, 1–64 characters
label string 1–128 characters
type? string One of the types above. Default text
options? array Choices for select. Default []
is_required? boolean Default false
order? integer Sort position. Default: after the last field

Example Request

{
  "name": "source",
  "label": "Lead source",
  "type": "select",
  "options": [
    "Website",
    "Exhibition",
    "Referral"
  ]
}

Response

201 Created

{
  "name": "source"
}

Modify Contact Field

PATCH /api/v1/projects/{project.uuid}/contacts/fields/{field.name}

Updates any of label, type, options, is_required, order. At least one must be present.

Requires can_edit_project.

Response

204 No Content

Replace Contact Field

PUT /api/v1/projects/{project.uuid}/contacts/fields/{field.name}

Same as Modify Contact Field, but label, type, options, is_required and order are all required.

Requires can_edit_project.

Response

204 No Content

Delete Contact Field

DELETE /api/v1/projects/{project.uuid}/contacts/fields/{field.name}

Deletes the field, all contacts' values for it, and the requirement for it on any order status.

Requires can_edit_project.

Response

204 No Content

Contact Field Errors

Status Code When
400 REQ_VALIDATION_FAILED The body does not match the schema
400 REQ_NO_DATA_PROVIDED Modify only: the body has no fields
403 AUTH_INSUFFICIENT_SCOPE The token lacks projects
403 AUTH_NO_PROJECT_ACCESS Create, modify, replace, delete: the user's role lacks can_edit_project
404 RES_NOT_FOUND The project or field does not exist
409 RES_ALREADY_EXISTS Create only: a field with this name already exists

Tag Object

A label that can be put on contacts. Tag names are unique within the project.

Tag Structure

Field Type Description
uuid uuid Tag ID
name string Name, 1–64 characters, unique within the project
color string Colour name, see below
created_at ISO8601 datetime When the tag was created

The app's palette is gray, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, fuchsia, purple, pink, rose. The API accepts any string of up to 16 characters, but the app may not display other values correctly.

Example Tag

{
  "uuid": "0199a1aa-0001-7000-8000-000000000001",
  "name": "Wholesale",
  "color": "emerald",
  "created_at": "2026-08-11T09:04:00.000Z"
}

List Tags

GET /api/v1/projects/{project.uuid}/tags

Returns the project's tags, sorted by name. Any project member can list tags.

Create Tag

POST /api/v1/projects/{project.uuid}/tags

Requires can_edit_contacts.

JSON Params

Field Type Description
name string 1–64 characters, trimmed
color? string Up to 16 characters. Default blue

Response

201 Created

{
  "uuid": "0199a1aa-0001-7000-8000-000000000001"
}

Modify Tag

PATCH /api/v1/projects/{project.uuid}/tags/{tag.uuid}

Updates name and/or color. At least one must be present.

Requires can_edit_contacts.

Response

204 No Content

Delete Tag

DELETE /api/v1/projects/{project.uuid}/tags/{tag.uuid}

Deletes the tag and removes it from every contact.

Requires can_edit_contacts.

Response

204 No Content

Tag Errors

Status Code When
400 REQ_VALIDATION_FAILED The body does not match the schema
400 REQ_NO_DATA_PROVIDED Modify only: the body has no fields
400 RES_ALREADY_EXISTS Create, modify: a tag with this name already exists
403 AUTH_INSUFFICIENT_SCOPE The token lacks projects or contacts
403 AUTH_NO_PROJECT_ACCESS Create, modify, delete: the user's role lacks can_edit_contacts
404 RES_NOT_FOUND The project or tag does not exist