/contacts
Endpoint pra criar, atualizar ou ler contatos da sua organização.
POST /contacts— cria ou atualiza (upsert).GET /contacts— lista paginada, com filtros.GET /contacts?email=...— busca um contato individual porid,email,phoneoucpf.GET /contacts/{id}— um contato.GET /contacts/{id}/conversations— conversas daquele contato.GET /contacts/{id}/history— mudanças de etapa, de dono e notas, numa linha do tempo.PATCH /contacts/{id}— altera nome, email, telefone, notas e campos.DELETE /contacts/{id}— anonimiza (padrão) ou apaga de vez.POST /contacts/{id}/merge— funde outro contato neste.POST /contacts/{id}/opt-oute/opt-in.GETePOST /contacts/{id}/notes— notas do contato.GET /contacts/{id}/export— tudo que existe sobre a pessoa.POST /contacts/batch— até 100 upserts numa chamada.
Auth: Authorization: Bearer wevi_SUA_CHAVE (crie em Conta → Desenvolvedores → API Keys).
Escopos: contacts:read para ler, contacts:write para gravar. Escrever exige plano com o recurso de API; ler funciona em qualquer plano.
POST /contacts
Cria ou atualiza um contato. Se já existe (matched por email ou phone), atualiza. Caso contrário, cria.
| Campo | Valor |
|---|---|
| URL | https://api.wevi.chat/functions/v1/contacts |
| Método | POST |
| Auth | Authorization: Bearer wevi_SUA_CHAVE |
| Content-Type | application/json |
| Rate limit | 300 req/min por token |
Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
email |
string | sim, se phone não vier |
Email do contato. Lowercase, normalizado. |
phone |
string | sim, se email não vier |
Telefone no formato internacional (ex: +5511999999999). |
name |
string | não | Nome completo. Vai pra display_name do contato. Sobrescreve sempre que enviado. |
fields |
object | não | Campos personalizados (chave → valor string). Veja regras abaixo. |
tags |
array de strings | não | Tags por nome. Se não existir na org, é criada com cor padrão (verde Wevi). |
assigned_user_email |
string | não | Email do atendente que vira dono da carteira desse contato. Precisa ser membro da org com role admin, editor ou atendente. |
Matching: como sabemos se já existe
- Procuramos primeiro por
emailemcontact_identifiers. - Se não achar, procuramos por
phone. - Se não achar nenhum, criamos contato novo.
Quer matching por CPF ou outro identificador? Use a UI pra cadastrar identificadores e os contatos passam a ser localizáveis também por eles em buscas internas (não via API ainda).
Sobre fields (campos personalizados)
- Cada chave precisa estar previamente cadastrada na sua org em Conta → Campos personalizados.
- Valores são salvos como string (mesmo que você mande número ou boolean).
- Merge: campos existentes do contato não são apagados. Você sobrescreve apenas o que mandar.
- Se mandar uma chave não cadastrada, a resposta é 400 com
unknown_fields: [...].
Pra descobrir quais chaves existem na sua org, use `GET /contacts-schema`.
Sobre tags
- Array de strings. Vazio ou ausente = não mexe nas tags do contato.
- Case-insensitive:
"VIP"e"vip"são consideradas a mesma tag. - Se a tag não existe na org, é criada automaticamente com cor verde.
- Atribuição não sobrescreve: tags antigas continuam. A API hoje só adiciona.
Exemplos
Mínimo: só email
curl -X POST https://api.wevi.chat/functions/v1/contacts \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"email":"joao@exemplo.com"}'
await fetch("https://api.wevi.chat/functions/v1/contacts", {
method: "POST",
headers: {
"Authorization": "Bearer wevi_SUA_CHAVE",
"Content-Type": "application/json",
},
body: JSON.stringify({ email: "joao@exemplo.com" }),
});
Completo: nome, telefone, campos, tags e dono
curl -X POST https://api.wevi.chat/functions/v1/contacts \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"email": "joao@exemplo.com",
"phone": "+5511999999999",
"name": "João Silva",
"fields": {
"cpf": "12345678900",
"plano_atual": "Pro"
},
"tags": ["VIP", "Trial expirando"],
"assigned_user_email": "maria@suaempresa.com.br"
}'
await fetch("https://api.wevi.chat/functions/v1/contacts", {
method: "POST",
headers: {
"Authorization": "Bearer wevi_SUA_CHAVE",
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "joao@exemplo.com",
phone: "+5511999999999",
name: "João Silva",
fields: { cpf: "12345678900", plano_atual: "Pro" },
tags: ["VIP", "Trial expirando"],
assigned_user_email: "maria@suaempresa.com.br",
}),
});
Resposta
Sucesso (200)
{
"ok": true,
"contact_id": "a1b2c3d4-...",
"tags_assigned": [
{ "name": "VIP", "id": "uuid-1", "created": false },
{ "name": "Trial expirando", "id": "uuid-2", "created": true }
],
"assigned_user_id": "user-uuid"
}
tags_assignedindica quais tags foram aplicadas e se foram criadas agora (created: true) ou já existiam.assigned_user_idaparece se oassigned_user_emailfoi resolvido com sucesso.
Sucesso com falha parcial de atribuição (200)
Se o assigned_user_email não corresponder a um membro válido, o contato é criado/atualizado normalmente mas vem assignment_error:
{
"ok": true,
"contact_id": "a1b2c3d4-...",
"tags_assigned": [],
"assignment_error": "assigned_user_email_not_found"
}
Possíveis valores de assignment_error:
assigned_user_email_not_found— email não bate com nenhum usuário cadastrado.assigned_user_not_member— usuário existe mas não é membro da sua org.assigned_user_role_not_eligible— usuário é membro mas tem rolefinanceiro(não elegível pra carteira).
Erros (4xx)
Ver lista completa em códigos de erro. Os mais frequentes aqui:
// 400 — falta identificador
{ "error": "At least one of 'email' or 'phone' is required" }
// 400 — chave não cadastrada
{
"error": "unknown_fields",
"message": "Algumas chaves em 'fields' não estão cadastradas em Campos personalizados. Cadastre as chaves antes de enviar.",
"unknown_fields": ["plano_atual"]
}
// 401
{ "error": "Invalid token" }
// 402 — trial expirado
{ "error": "plan_limit_exceeded", "feature": "subscription" }
// 429
{ "error": "Too many requests", "retry_after": 47 }
Padrões de uso
Sincronizar lead novo do CRM
Toda vez que um lead é criado/atualizado no seu CRM, dispare este webhook. O contato vai aparecer na Wevi com tudo já preenchido.
Atribuir vendedor automaticamente
Use assigned_user_email pra que o atendente "dono da carteira" receba transbordos desse contato com prioridade. Veja carteirização pra entender o conceito.
Atualizar status sem duplicar
Como o matching é por email ou phone, mandar o mesmo payload duas vezes só atualiza. Não duplica contato.
GET /contacts
Busca um contato individual com todos os dados (campos nativos, custom, tags, identificadores, dono da carteira e estatísticas de conversa).
| Campo | Valor |
|---|---|
| URL | https://api.wevi.chat/functions/v1/contacts |
| Método | GET |
| Auth | Authorization: Bearer wevi_SUA_CHAVE |
| Rate limit | 120 req/min por token |
Query params
Passe um dos quatro identificadores (ordem de prioridade: id → email → phone → cpf):
| Param | Descrição |
|---|---|
id |
UUID do contato. Match exato. |
email |
Email (case-insensitive). Procura primeiro em identifiers, fallback na coluna nativa. |
phone |
Telefone (preferir formato +5511...). Mesmo fallback. |
cpf |
CPF cadastrado como identifier. |
Exemplos
# por email
curl "https://api.wevi.chat/functions/v1/contacts?email=cliente@exemplo.com" \
-H "Authorization: Bearer wevi_SUA_CHAVE"
# por telefone
curl "https://api.wevi.chat/functions/v1/contacts?phone=%2B5511999999999" \
-H "Authorization: Bearer wevi_SUA_CHAVE"
# por UUID
curl "https://api.wevi.chat/functions/v1/contacts?id=a1b2c3d4-..." \
-H "Authorization: Bearer wevi_SUA_CHAVE"
const params = new URLSearchParams({ email: "cliente@exemplo.com" });
const res = await fetch(`https://api.wevi.chat/functions/v1/contacts?${params}`, {
headers: { "Authorization": "Bearer wevi_SUA_CHAVE" },
});
const contact = await res.json();
Resposta
Sucesso (200)
{
"id": "a1b2c3d4-...",
"name": "João Silva",
"email": "joao@exemplo.com",
"phone": "+5511999999999",
"notes": "Cliente VIP, prefere atendimento por WhatsApp",
"custom_fields": { "cpf": "12345678900", "plano": "Pro" },
"tags": [
{ "id": "tag-1", "name": "VIP", "color": "#22D650" }
],
"assigned_user": {
"email": "maria@empresa.com",
"name": "Maria Silva",
"role": "atendente"
},
"pipelines": [
{
"pipeline_id": "pipe-1",
"pipeline_name": "Funil de vendas",
"stage_id": "stage-3",
"stage_name": "Negociação",
"entered_stage_at": "2026-05-18T09:12:00Z",
"lost_at": null,
"lost_reason": null
}
],
"identifiers": [
{ "type": "email", "value": "joao@exemplo.com", "label": null },
{ "type": "phone", "value": "+5511999999999", "label": null },
{ "type": "cpf", "value": "12345678900", "label": null }
],
"stats": {
"conversation_count": 12,
"first_seen_at": "2026-01-15T10:23:00Z",
"last_seen_at": "2026-05-22T14:30:00Z",
"channels": ["whatsapp", "web"]
},
"assigned_at": "2026-03-10T...",
"assignment_source": "manual",
"created_at": "2026-01-15T...",
"updated_at": "2026-05-22T..."
}
Erros
| Status | error |
|---|---|
| 400 | Provide one of: id, email, phone, cpf |
| 401 | Missing Authorization header ou Invalid token |
| 404 | Contact not found |
| 429 | Too many requests |
pipelines traz uma linha por funil em que o contato está, com a etapa atual e desde quando. Funil em que ele não entrou não aparece na lista. lost_at preenchido significa que ele foi marcado como perdido e continua parado naquela etapa.
Padrão de uso
Use após criar/atualizar via POST pra confirmar o estado final, ou pra puxar dados frescos quando seu CRM precisa exibir o que está na Wevi (last_seen, número de conversas, dono da carteira, etapa no funil).
Operações pontuais
Pra modificar um aspecto específico de um contato sem mandar um upsert completo, use os sub-endpoints abaixo. Todos aceitam um identificador no body (id, email, phone ou cpf) e respondem 404 se o contato não existir.
Auth: Authorization: Bearer wevi_SUA_CHAVE em todos.
GET /contacts — lista
Sem nenhum identificador na query, GET /contacts devolve a lista paginada da organização. Com id, email, phone ou cpf, continua devolvendo um contato só, como sempre fez.
| Campo | Valor |
|---|---|
| URL | https://api.wevi.chat/functions/v1/contacts |
| Método | GET |
| Rate limit | 120 req/min por chave |
Filtros
| Parâmetro | Exemplo | Descrição |
|---|---|---|
q |
maria |
Busca por nome, email ou telefone |
tag |
Lead |
Etiqueta, por nome ou id |
pipeline |
Funil de vendas |
Funil, por nome ou id |
stage |
Proposta |
Etapa, por nome ou id |
lost |
true |
Só perdidos, ou só ativos com false |
assigned_to |
uuid ou none |
Dono da carteira; none traz os sem dono |
opted_out |
true |
Só quem pediu para não receber mais |
Mais limit, cursor e updated_since, descritos em paginação.
curl "https://api.wevi.chat/functions/v1/contacts?tag=Lead&limit=100" \
-H "Authorization: Bearer wevi_SUA_CHAVE"
const url = new URL("https://api.wevi.chat/functions/v1/contacts");
url.searchParams.set("tag", "Lead");
url.searchParams.set("limit", "100");
const res = await fetch(url, { headers: { Authorization: "Bearer wevi_SUA_CHAVE" } });
const { data, next_cursor, has_more } = await res.json();
Cada item da lista é o mesmo objeto que o GET individual devolve, com tags, funis, identificadores, dono e estatísticas. Não existe uma versão resumida na lista e uma completa no detalhe: você aprende a forma do contato uma vez.
GET /contacts/{id}/conversations
Devolve as conversas do contato, da mais recente para a mais antiga, no mesmo formato de /conversations.
curl "https://api.wevi.chat/functions/v1/contacts/{id}/conversations" \
-H "Authorization: Bearer wevi_SUA_CHAVE"
GET /contacts/{id}/history
Junta numa linha do tempo só: mudanças de etapa no funil, trocas de dono da carteira e notas registradas.
{
"data": [
{
"type": "stage_changed",
"at": "2026-09-05T14:22:00Z",
"data": {
"pipeline_id": "c24cde19-...",
"from_stage_id": "00741eec-...",
"to_stage_id": "3af8c4a2-...",
"event": "moved",
"source": "api",
"reason": null,
"changed_by": null
}
},
{
"type": "assignment_changed",
"at": "2026-09-04T09:10:00Z",
"data": { "from_user_id": null, "to_user_id": "ca5dd03e-...", "source": "webhook", "reason": null, "changed_by": null }
},
{ "type": "note_added", "at": "2026-09-03T16:40:00Z", "data": { "id": "...", "body": "Pediu retorno na semana que vem.", "created_by": "..." } }
],
"next_cursor": null,
"has_more": false
}
type vale stage_changed, assignment_changed ou note_added. Os últimos 100 de cada tipo, ordenados do mais recente para o mais antigo.
POST /contacts/tag — adicionar tag
Anexa uma tag ao contato. Se a tag não existir na org, é criada com cor verde padrão.
Body:
{ "email": "joao@x.com", "tag": "VIP" }
curl -X POST https://api.wevi.chat/functions/v1/contacts/tag \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"email":"joao@x.com","tag":"VIP"}'
await fetch("https://api.wevi.chat/functions/v1/contacts/tag", {
method: "POST",
headers: { "Authorization": "Bearer wevi_SUA_CHAVE", "Content-Type": "application/json" },
body: JSON.stringify({ email: "joao@x.com", tag: "VIP" }),
});
Resposta 200:
{
"ok": true,
"contact_id": "uuid",
"tag": { "name": "VIP", "id": "tag-uuid", "created": false }
}
created: true se a tag foi criada agora; false se já existia.
DELETE /contacts/tag — remover tag
Remove a atribuição de uma tag. Não apaga a tag da org, só desassocia do contato. Idempotente: se o contato não tinha a tag, responde removed: false sem erro.
Body:
{ "email": "joao@x.com", "tag": "VIP" }
curl -X DELETE https://api.wevi.chat/functions/v1/contacts/tag \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"email":"joao@x.com","tag":"VIP"}'
await fetch("https://api.wevi.chat/functions/v1/contacts/tag", {
method: "DELETE",
headers: { "Authorization": "Bearer wevi_SUA_CHAVE", "Content-Type": "application/json" },
body: JSON.stringify({ email: "joao@x.com", tag: "VIP" }),
});
Resposta 200:
{ "ok": true, "contact_id": "uuid", "removed": true, "tag": { "id": "tag-uuid", "name": "VIP" } }
Ou se a tag não existia na org:
{ "ok": true, "contact_id": "uuid", "removed": false, "reason": "tag_not_found" }
POST /contacts/assign — atribuir dono
Define o atendente "dono da carteira" do contato. O email precisa pertencer a um membro da org com role admin, editor ou atendente.
Body:
{ "email": "joao@x.com", "assigned_user_email": "maria@empresa.com" }
curl -X POST https://api.wevi.chat/functions/v1/contacts/assign \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"email":"joao@x.com","assigned_user_email":"maria@empresa.com"}'
await fetch("https://api.wevi.chat/functions/v1/contacts/assign", {
method: "POST",
headers: { "Authorization": "Bearer wevi_SUA_CHAVE", "Content-Type": "application/json" },
body: JSON.stringify({ email: "joao@x.com", assigned_user_email: "maria@empresa.com" }),
});
Resposta 200:
{ "ok": true, "contact_id": "uuid", "assigned_user_id": "user-uuid" }
422 (atendente inválido): assigned_user_email_not_found, assigned_user_not_member ou assigned_user_role_not_eligible.
DELETE /contacts/assign — desatribuir dono
Limpa o dono da carteira. Não exige nenhum campo além do identificador do contato.
curl -X DELETE https://api.wevi.chat/functions/v1/contacts/assign \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"email":"joao@x.com"}'
Resposta 200:
{ "ok": true, "contact_id": "uuid", "unassigned": true }
POST /contacts/note — setar notas
Sobrescreve as notas internas do contato. Mande null ou string vazia pra limpar.
Body:
{ "email": "joao@x.com", "note": "Cliente VIP, prefere atendimento por WhatsApp" }
curl -X POST https://api.wevi.chat/functions/v1/contacts/note \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"email":"joao@x.com","note":"Cliente VIP"}'
Resposta 200:
{ "ok": true, "contact_id": "uuid", "note": "Cliente VIP" }
POST /contacts/field — setar/limpar custom field
Atualiza um único campo personalizado. A key precisa estar cadastrada em Conta → Campos personalizados (descubra via `GET /contacts-schema`). Mande value: null pra apagar a chave.
Body:
{ "email": "joao@x.com", "key": "plano", "value": "Pro" }
# setar
curl -X POST https://api.wevi.chat/functions/v1/contacts/field \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"email":"joao@x.com","key":"plano","value":"Pro"}'
# limpar
curl -X POST https://api.wevi.chat/functions/v1/contacts/field \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"email":"joao@x.com","key":"plano","value":null}'
Resposta 200:
{ "ok": true, "contact_id": "uuid", "key": "plano", "value": "Pro" }
Erros:
400unknown_fieldse a chave não está cadastrada.400'value' is required (use null to clear)se o body não tem campovalue.
POST /contacts/stage — mover de etapa no funil
Move o contato para outra etapa de um funil, ou marca e desmarca como perdido. As etapas ficam em Configurações → Funis; veja Funis e etapas de contato.
| Campo | Obrigatório | Valor |
|---|---|---|
| identificador | sim | id, email, phone ou cpf |
pipeline |
não | Id ou nome do funil. Omitido, usa o funil padrão da organização. |
stage |
sim, se não mandar lost |
Id ou nome da etapa de destino. O nome não diferencia maiúsculas. |
lost |
sim, se não mandar stage |
true marca como perdido na etapa atual, false reabre. |
reason |
não | Motivo da perda, até 200 caracteres. Só vale com lost: true. |
Mandar stage em um contato que ainda não está no funil coloca ele lá. Mover um contato que estava perdido reabre ele automaticamente.
Body:
{ "email": "joao@x.com", "stage": "Negociação" }
# mover de etapa (funil padrão)
curl -X POST https://api.wevi.chat/functions/v1/contacts/stage \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"email":"joao@x.com","stage":"Negociação"}'
# mover em um funil específico
curl -X POST https://api.wevi.chat/functions/v1/contacts/stage \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"phone":"+5511999999999","pipeline":"Pós-venda","stage":"Onboarding"}'
# marcar como perdido
curl -X POST https://api.wevi.chat/functions/v1/contacts/stage \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"email":"joao@x.com","lost":true,"reason":"Fechou com concorrente"}'
# reabrir
curl -X POST https://api.wevi.chat/functions/v1/contacts/stage \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"email":"joao@x.com","lost":false}'
Resposta 200:
{
"ok": true,
"contact_id": "uuid",
"pipeline": { "id": "pipe-1", "name": "Funil de vendas" },
"stage": { "id": "stage-3", "name": "Negociação" },
"lost": false
}
Erros:
| Status | error |
Quando |
|---|---|---|
| 400 | Provide 'stage' or 'lost' |
body sem stage e sem lost |
| 400 | unknown_pipeline |
o funil informado não existe, ou a organização não tem funil padrão |
| 400 | unknown_stage |
a etapa não existe naquele funil |
| 400 | not_in_pipeline |
só com lost: o contato ainda não está no funil. Mande stage para colocar ele lá. |
| 404 | Contact not found |
nenhum contato bate com o identificador |
Mudar a etapa por aqui vale como qualquer outra mudança: entra no histórico do contato (marcada como feita pela API) e pode disparar automações com o gatilho Entrou em uma etapa do funil.
Diferença pro POST /contacts em batch
O POST /contacts (upsert) faz tudo de uma vez: cria contato, anexa tags, atribui dono, mescla custom_fields. Use quando você tem o pacote completo de dados.
Os sub-endpoints (/contacts/tag, /contacts/assign, etc) são pra operações individuais sobre contato existente:
- Webhook do seu CRM disparou "lead foi qualificado" →
POST /contacts/tagcom"qualificado". - Vendedor saiu de férias →
POST /contacts/assigncom o substituto. - Cliente cancelou plano →
POST /contacts/fieldcomvalue: nullemplano. - Proposta enviada no seu ERP →
POST /contacts/stagecom"Negociação".
Mais granular, sem precisar buscar o contato primeiro pra montar um payload completo.
PATCH /contacts/{id}
Altera só o que você mandar. O que não vier no corpo fica como está.
curl -X PATCH "https://api.wevi.chat/functions/v1/contacts/{id}" \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"name":"Maria Souza","fields":{"plano":"premium"}}'
| Campo | Efeito |
|---|---|
name |
Troca o nome |
email, phone |
Trocam o valor; mande null para limpar. Telefone é normalizado e vira identificador |
notes |
Troca as notas do cadastro |
fields |
Mescla com os campos que já existem |
replace_fields |
Com true, fields substitui tudo em vez de mesclar |
Devolve o contato completo, no mesmo formato do GET.
DELETE /contacts/{id}
Por padrão anonimiza: nome, email, telefone, notas e campos personalizados são apagados, o contato é marcado como opt-out e as conversas continuam existindo como histórico de atendimento, sem o que identifica a pessoa. É o que a LGPD pede sem destruir o registro do que foi conversado.
curl -X DELETE "https://api.wevi.chat/functions/v1/contacts/{id}" \
-H "Authorization: Bearer wevi_SUA_CHAVE"
Para apagar mesmo, incluindo as conversas:
curl -X DELETE "https://api.wevi.chat/functions/v1/contacts/{id}?mode=erase" \
-H "Authorization: Bearer wevi_SUA_CHAVE"
mode=erase não tem volta.
GET /contacts/{id}/export
Reúne numa resposta só tudo que a plataforma guarda sobre a pessoa: cadastro, conversas, mensagens, notas e disparos. Serve para responder pedido de portabilidade sem ninguém precisar montar consulta no banco.
{
"contact": { "id": "...", "name": "Maria Souza", "tags": [], "pipelines": [] },
"conversations": [{ "id": "...", "channel": "whatsapp", "status": "open" }],
"messages": [{ "role": "user", "content": "...", "created_at": "..." }],
"notes": [{ "body": "Pediu retorno em janeiro", "created_at": "..." }],
"sends": [{ "kind": "template", "template_name": "boas_vindas", "status": "delivered" }],
"exported_at": "2026-09-08T04:47:00Z"
}
POST /contacts/{id}/merge
Funde outro contato neste. Identificadores e conversas passam para cá, campos vazios são preenchidos com os do outro, e o antigo vira uma marca apontando para este.
curl -X POST "https://api.wevi.chat/functions/v1/contacts/{id}/merge" \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"contact_id":"id-do-contato-duplicado"}'
POST /contacts/{id}/opt-out e /opt-in
curl -X POST "https://api.wevi.chat/functions/v1/contacts/{id}/opt-out" \
-H "Authorization: Bearer wevi_SUA_CHAVE"
Quem está em opt-out não recebe template nem abordagem. Responder continua valendo enquanto a janela de 24 horas estiver aberta.
Notas
curl -X POST "https://api.wevi.chat/functions/v1/contacts/{id}/notes" \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"body":"Ligou pedindo retorno na semana que vem."}'
GET na mesma rota devolve as notas, da mais recente para a mais antiga. São as mesmas que aparecem na ficha do contato no painel.
POST /contacts/batch
Até 100 contatos por chamada, cada um com o mesmo corpo do POST /contacts. Limite de 30 chamadas por minuto.
curl -X POST "https://api.wevi.chat/functions/v1/contacts/batch" \
-H "Authorization: Bearer wevi_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{ "email": "maria@exemplo.com", "name": "Maria", "tags": ["Lead"] },
{ "phone": "11955554444", "name": "João", "fields": { "origem": "feira" } }
]
}'
O lote nunca falha inteiro por causa de um item. A resposta diz o que aconteceu com cada linha:
{
"processed": 3,
"succeeded": 2,
"failed": 1,
"results": [
{ "index": 0, "ok": true, "contact_id": "...", "tags_assigned": [] },
{ "index": 1, "ok": true, "contact_id": "...", "tags_assigned": [] },
{ "index": 2, "ok": false, "error": "missing_identifier", "message": "Informe email ou phone." }
]
}
Próximo
/contacts-schema