Skip to content

Create a contact

POST
/v1/contacts

Creates one contact in the active workspace. At least one of name, username, phone, email or externalId must be provided. Defaults platform to telegram if omitted.

Create is strict about externalId: if that id already exists in the workspace the call returns 409 with the existing contact id in details. Use POST /v1/contacts/batch when you want upsert semantics instead.

Send Idempotency-Key to make a retry safe. Emits a contact.created webhook. Requires scope contacts:write.

X-Workspace-Id
integer

Act on this workspace’s shared CRM pool. Membership is verified on every request, so an id you do not belong to falls back to personal scope rather than widening access. Omit it to use your default workspace. Applies to every CRM endpoint, not only the ones that list it here.

Idempotency-Key
string
<= 255 characters

A unique string of your choosing (a UUID works). The first request with this key runs normally and its response is stored for 24 hours; a retry with the same key returns that stored response verbatim, with Idempotency-Replayed: true, instead of writing again. Reusing a key with a different body returns 409.

At least one of name, username, phone, email or externalId is required. The same shape is used for a batch row.

object
platform

Platform for the new contact. Defaults to telegram.

string
default: telegram
Allowed values: telegram twitter
name
string
nullable <= 100 characters
username

Handle with or without a leading @ - the @ is stripped server-side.

string
nullable <= 100 characters
phone
string
nullable <= 20 characters
email
string
nullable <= 200 characters
company
string
nullable <= 200 characters
externalId

Your own customer id. Must be unique inside the workspace; the batch endpoint upserts on it.

string
nullable <= 100 characters
country
string
nullable <= 100 characters
city
string
nullable <= 100 characters
notes
string
nullable <= 500 characters
stage
string
nullable
Allowed values: novalue lead conversation proposal negotiation won lost
pipelineStageId

Column on a custom pipeline board. Must belong to a board in the active workspace.

integer
nullable
leadScore
integer
nullable <= 100
source

Where the record came from (Manual, CsvImport, RevenueImport, …). Defaults to the historical v1 value.

string
nullable
tags

Tag names. Missing tags are created in the workspace dictionary.

Array<string>
nullable <= 50 items
customFields

Custom field values keyed by field key. A null value clears that key.

object
Example
{
"platform": "telegram",
"name": "Ada Lovelace",
"username": "adalovelace",
"email": "ada@example.com",
"company": "Analytical Engines",
"externalId": "shopify-cust-88213",
"country": "United Kingdom",
"city": "London",
"stage": "lead",
"tags": [
"vip",
"newsletter"
],
"customFields": {
"lifetime_value": 4820,
"preferred_channel": "email"
},
"notes": "Met at conference"
}

Contact created successfully.

A CRM contact record.

object
id
integer
platform

Social platform the contact belongs to.

string
Allowed values: telegram twitter
name
string
nullable
username

Handle without the leading @.

string
nullable
phone
string
nullable
email
string
nullable
company
string
nullable
externalId

The contact’s id in your own system. The stable key the batch endpoint upserts on.

string
nullable <= 100 characters
country
string
nullable
city
string
nullable
notes
string
nullable
stage

CRM pipeline stage.

string
leadScore

Lead score 0-100 (higher = hotter). Null when not scored.

integer
nullable
leadScoreIsAi

True when the score was last set by AI, false for a manual override.

boolean
assignedToUserId

Team member the contact is assigned to.

integer
nullable
ownerUserId

Workspace member the row is filed under. Differs from the caller in a shared pool.

integer
nullable
pipelineId

Custom pipeline board the contact sits on.

integer
nullable
pipelineStageId

Column within pipelineId.

integer
nullable
source

How the contact entered the CRM (OutboundMessage, InboundMessage, Manual, CsvImport, Extension, …).

string
isArchived

True once the contact has been soft-deleted or archived.

boolean
createdAt
string format: date-time
updatedAt
string format: date-time
nullable
lastMessageAt
string format: date-time
nullable
hasUnreadMessages
boolean
customFields

Custom field values keyed by field key. Null when the contact has none.

object
tags

Attached tags (populated on GET by id, omitted in list responses).

Array<object>
nullable
object
id
integer
name
string
color

Hex color

string
Idempotency-Replayed
string
Allowed values: true

Present and “true” when this body was replayed from a previous identical request.

The request body or parameters failed validation.

Standard error envelope for all v1 error responses.

object
error

Machine-readable error code.

string
Allowed values: bad_request not_found conflict unauthorized forbidden payment_required provider_rejected rate_limited internal_error
message

Human-readable explanation of the error.

string
details

Optional structured context (field-level validation errors, etc.).

nullable
Example
{
"error": "bad_request",
"message": "at least one of name, username, phone is required"
}

Missing or invalid bearer token.

Standard error envelope for all v1 error responses.

object
error

Machine-readable error code.

string
Allowed values: bad_request not_found conflict unauthorized forbidden payment_required provider_rejected rate_limited internal_error
message

Human-readable explanation of the error.

string
details

Optional structured context (field-level validation errors, etc.).

nullable
Example
{
"error": "unauthorized",
"message": "Invalid bearer principal"
}

The API key does not have the required scope for this operation.

Standard error envelope for all v1 error responses.

object
error

Machine-readable error code.

string
Allowed values: bad_request not_found conflict unauthorized forbidden payment_required provider_rejected rate_limited internal_error
message

Human-readable explanation of the error.

string
details

Optional structured context (field-level validation errors, etc.).

nullable
Example
{
"error": "forbidden",
"message": "scope contacts:write is required"
}

The request collides with existing state: a duplicate externalId, an Idempotency-Key replayed with a different body or still in flight, or a permanent delete blocked by records that still reference the contact.

Standard error envelope for all v1 error responses.

object
error

Machine-readable error code.

string
Allowed values: bad_request not_found conflict unauthorized forbidden payment_required provider_rejected rate_limited internal_error
message

Human-readable explanation of the error.

string
details

Optional structured context (field-level validation errors, etc.).

nullable
Example
{
"error": "conflict",
"message": "a contact with externalId 'shopify-cust-88213' already exists",
"details": {
"existingContactId": 4821
}
}

Per-key rate limit exceeded. Retry after the time indicated by X-RateLimit-Reset.

Standard error envelope for all v1 error responses.

object
error

Machine-readable error code.

string
Allowed values: bad_request not_found conflict unauthorized forbidden payment_required provider_rejected rate_limited internal_error
message

Human-readable explanation of the error.

string
details

Optional structured context (field-level validation errors, etc.).

nullable
Example
{
"error": "rate_limited",
"message": "rate limit exceeded"
}
X-RateLimit-Limit
integer

Maximum requests allowed per minute for this key.

X-RateLimit-Remaining
integer

Requests remaining in the current window.

X-RateLimit-Reset
integer

Unix timestamp (seconds) when the rate limit window resets.