Criar ou atualizar contato
POST /contacts — cria ou atualiza um contato (upsert) por email ou telefone, com UTMs, click ids e campos personalizados.
/contactsBetaEscopo necessário: contacts:write
Cria um contato ou atualiza um existente (upsert). A organização é resolvida a partir da própria API key — não envie organization_id.
Resolução de duplicata: o contato é localizado primeiro por email e, se não houver, por telefone. O match de telefone considera variantes do 9º dígito (Brasil) e formas equivalentes de México/Argentina, então um número salvo sem o 9 (+556792275634) casa com a forma canônica (+5567992275634). Se nenhum casar, um novo contato é criado. É obrigatório informar pelo menos email ou phone.
Endpoint
POST https://crm.oficinamartech.com/api/v1/contactsCorpo da requisição
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
email | string (email) | △ | Email do contato. Usado como primeira chave de match (case-insensitive). |
phone | string | △ | Telefone em qualquer formato; normalizado para E.164. Segunda chave de match. |
name | string (até 200) | ❌ | Nome do contato. |
source | string (até 120) | ❌ | Origem do contato. Gravada apenas na criação. Default: api. |
utm | object | ❌ | { source, medium, campaign, term, content } — todos string opcionais. |
fbclid | string | ❌ | Facebook click id. |
gclid | string | ❌ | Google click id. |
src | string | ❌ | Parâmetro de origem (src). |
sck | string | ❌ | Parâmetro de subtracking (sck). |
landing_page_url | string (até 2048) | ❌ | URL da landing page do toque. |
referrer | string (até 2048) | ❌ | Referrer do toque. |
custom_fields | object (mapa chave→valor) | ❌ | Campos personalizados. Cada chave deve existir como campo de contato ativo da organização. |
△ = pelo menos um de email ou phone é obrigatório.
Semântica de atualização
- Os campos enviados sobrescrevem os valores atuais do contato.
custom_fieldsé mesclado por chave: só as chaves enviadas mudam; as demais permanecem intactas.sourcenão é sobrescrito em updates (só é gravado na criação, salvo se você enviá-lo explicitamente).
Campos personalizados
As chaves de custom_fields precisam corresponder a campos personalizados de contato cadastrados em Configurações → Campos personalizados (ativos). Chaves desconhecidas retornam 400. Para campos do tipo select/multiselect, os valores devem estar entre as opções cadastradas.
Rastreamento (atribuição)
Quando você envia UTMs, fbclid, gclid, src, sck, landing_page_url ou referrer, a API grava um toque de atribuição (contact_touch_event, append-only) associado ao contato. Cada chamada com sinal de rastreamento gera um novo toque.
Resposta
201 Created (novo contato) ou 200 OK (contato atualizado):
{
"data": {
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"organization_id": "...",
"name": "Maria Silva",
"email": "maria@example.com",
"phone": "+5511999999999",
"phone_e164": "+5511999999999",
"source": "api",
"custom_fields": { "plano": "Pro" },
"created_at": "2026-06-13T12:00:00.000Z",
"updated_at": "2026-06-13T12:00:00.000Z"
},
"created": true,
"touch_event_recorded": true
}| Campo | Tipo | Descrição |
|---|---|---|
data | object | O contato criado/atualizado. |
created | boolean | true se criou; false se atualizou um contato existente. |
touch_event_recorded | boolean | true se um toque de atribuição foi gravado nesta chamada. |
Exemplos
curl -X POST "https://crm.oficinamartech.com/api/v1/contacts" \
-H "Authorization: Bearer $OFM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Maria Silva",
"email": "maria@example.com",
"phone": "+55 11 99999-9999",
"utm": { "source": "google", "medium": "cpc", "campaign": "black-friday" },
"fbclid": "IwAR123",
"custom_fields": { "plano": "Pro" }
}'Códigos de erro específicos
| Status | code | Causa |
|---|---|---|
| 400 | — | JSON inválido, ou nem email nem phone informados |
| 400 | invalid_phone | phone não pôde ser normalizado para E.164 |
| 400 | unknown_custom_fields | Chave de custom_fields não cadastrada na organização |
| 400 | invalid_custom_field_values | Valor fora das opções de um campo select/multiselect |
| 401 | missing_api_key | Header Authorization ausente |
| 401 | invalid_api_key | Chave malformada, inexistente ou hash não confere |
| 401 | revoked_api_key | Chave revogada |
| 401 | expired_api_key | Chave expirada |
| 403 | missing_scope | Chave sem o escopo contacts:write |
| 409 | duplicate_contact | Email ou telefone já pertence a outro contato da organização |
| 500 | — | Erro interno |
Veja Códigos de erro para o formato completo.
Notas
- O match por email é case-insensitive.
- O telefone é normalizado para E.164 (default Brasil
+55); o valor normalizado é gravado emphoneephone_e164. - A organização vem da API key — uma chave só consegue gravar contatos na própria organização.
- Cada chamada é registrada na aba Atividades do contato, com o payload enviado visível em JSON. Esse histórico de envios via API é permanente (não expira).