Contatos

Gerencie os contatos da empresa com dados de CRM: listagem com filtros, detalhe completo (etiquetas, etapa de pipeline, campos personalizados, conexões), criação, atualização parcial e remoção. Todos os endpoints exigem plano Enterprise.

escopo: contacts:readEnterprise

Lista contatos com dados de CRM. Filtros: ?search=&source_platform=&inbox_status=open&pipeline_stage_id=&crm_status=&is_ai_enabled=true&page=1&limit=50

Resposta (200)

Corpo da resposta
{
  "success": true,
  "data": [
    {
      "id": "<uuid>",
      "identifier": "5548999990000",
      "phone": "5548999990000",
      "email": "joao@exemplo.com.br",
      "name": "João Silva",
      "display_name": "João Silva",
      "source_platform": "whatsapp",
      "inbox_status": "open",
      "priority": "normal",
      "crm_status": "open",
      "is_ai_enabled": true,
      "is_archived": false,
      "is_blocked": false,
      "first_message_at": "2026-07-01T14:03:00.000Z",
      "last_message_at": "2026-07-15T09:22:00.000Z",
      "last_inbound_at": "2026-07-15T09:20:00.000Z",
      "pipeline_stage_id": "<uuid>",
      "assigned_to_member_id": "<uuid>",
      "last_connection_id": "<uuid>",
      "created_at": "2026-07-01T14:03:00.000Z",
      "updated_at": "2026-07-15T09:22:00.000Z",
      "pipeline_stage": {
        "id": "<uuid>",
        "name": "Novo",
        "color": "#6366f1",
        "display_order": 1
      },
      "tags": [
        {
          "id": "<uuid>",
          "name": "VIP",
          "color": "#6366f1"
        }
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 320,
    "total_pages": 7
  }
}

Exemplo

curl
curl -s -H "Authorization: Bearer <sua-key>" https://www.prosalab.com/api/v1/contacts
escopo: contacts:read (lead_memory:read adiciona o campo lead_memory)Enterprise

Detalhe do contato com etiquetas, etapa de pipeline, responsável, campos personalizados, conexões e — para chaves com o escopo lead_memory:read — a memória do lead inferida pela IA.

  • custom_fields tem a MESMA forma na leitura e na escrita: chave = ID do campo (UUID), valor = STRING. O bloco desta resposta pode ser reenviado como está em POST e PATCH. Só aparecem campos com definição ATIVA e valor preenchido; contato sem nenhum devolve {} (nunca null).
  • O rótulo e o tipo de cada campo NÃO viajam aqui — eles pertencem à definição e vivem em GET /api/v1/custom-fields, que é a fonte única. Para montar uma tela, cruze pelo id.
  • lead_memory é INFERIDO pela IA a partir da conversa, não declarado pelo contato. Trate como hipótese de trabalho: summary, sentiment, intent, consciousness_level e bant são julgamento do modelo e podem estar errados. key_facts, products_interested e objections são listas cumulativas, com teto de 20, 10 e 10 itens.
  • O campo só aparece quando a chave tem o escopo lead_memory:read. Sem o escopo a resposta é idêntica à anterior e a chave lead_memory NÃO vem no JSON — não é 403. Contato que ainda não tem memória devolve lead_memory: null. Ausência da chave significa "falta escopo"; null significa "sem memória".
  • bant.authority tem DOIS formatos por herança: em registros novos é um código estável (ex.: decisor-com-consulta) e o texto original vem em bant.authority_raw; em registros antigos authority é o próprio texto livre e authority_raw não existe. Para exibir ao usuário leia authority_raw ?? authority; para agrupar/contar use authority apenas quando authority_raw estiver presente.

Resposta (200)

Corpo da resposta
{
  "success": true,
  "data": {
    "id": "<uuid>",
    "identifier": "5548999990000",
    "phone": "5548999990000",
    "email": "joao@exemplo.com.br",
    "name": "João Silva",
    "display_name": "João Silva",
    "wa_profile_name": "João",
    "wa_push_name": "João S.",
    "source_platform": "whatsapp",
    "inbox_status": "open",
    "priority": "normal",
    "crm_status": "open",
    "is_ai_enabled": true,
    "is_archived": false,
    "is_pinned": false,
    "is_muted": false,
    "is_blocked": false,
    "first_message_at": "2026-07-01T14:03:00.000Z",
    "last_message_at": "2026-07-15T09:22:00.000Z",
    "last_inbound_at": "2026-07-15T09:20:00.000Z",
    "last_read_at": "2026-07-15T09:25:00.000Z",
    "pipeline_stage_id": "<uuid>",
    "assigned_to_member_id": "<uuid>",
    "last_connection_id": "<uuid>",
    "created_at": "2026-07-01T14:03:00.000Z",
    "updated_at": "2026-07-15T09:22:00.000Z",
    "pipeline_stage": {
      "id": "<uuid>",
      "name": "Novo",
      "color": "#6366f1",
      "display_order": 1
    },
    "tags": [
      {
        "id": "<uuid>",
        "name": "VIP",
        "color": "#6366f1"
      }
    ],
    "assigned_to": {
      "id": "<uuid>",
      "display_name": "Ana Souza",
      "role": "agent"
    },
    "custom_fields": {
      "<id-do-campo-uuid>": "campanha"
    },
    "connections": [
      {
        "id": "<uuid>",
        "type": "whatsapp",
        "name": "Comercial",
        "provider": "uazapi",
        "identifier": "5548999990000",
        "last_message_at": "2026-07-15T09:22:00.000Z",
        "unread_count": 0,
        "inbox_status": "open"
      }
    ],
    "lead_memory": {
      "version": 1,
      "summary": "Renovação de seguro auto; comparou preço com a concorrência.",
      "sentiment": "neutral",
      "intent": "purchase",
      "consciousness_level": "solution_aware",
      "bant": {
        "budget": "Até R$ 3.000 por ano",
        "authority": "decisor-com-consulta",
        "authority_raw": "Precisa falar com o marido",
        "need": "Renovar antes do vencimento",
        "timeline": "Este mês"
      },
      "key_facts": [
        "Tem dois veículos",
        "Cliente há 4 anos"
      ],
      "products_interested": [
        "Seguro auto"
      ],
      "objections": [
        "Achou o prêmio alto"
      ],
      "last_topic": "valor da parcela",
      "interaction_count": 12,
      "first_interaction_at": "2026-07-01T14:03:00.000Z",
      "last_memory_update_at": "2026-07-15T09:22:00.000Z"
    }
  }
}

Erros

StatusCódigoQuando ocorre
404NOT_FOUNDContato não existe ou pertence a outra empresa

Exemplo

curl
curl -s -H "Authorization: Bearer <sua-key>" https://www.prosalab.com/api/v1/contacts/{id}
escopo: contacts:writeEnterprise

Cria um contato. Telefone duplicado retorna 409 com o contact_id do existente.

  • custom_fields é keyed pelo ID do campo (UUID retornado por GET /api/v1/custom-fields), com valores sempre STRING (máx. 500 caracteres) e sem limite de quantidade de chaves. ID inexistente/inativo/de outra empresa é rejeitado com 422, com a lista dos inválidos em details.custom_fields e o caminho de conserto em details.hint.
  • Round-trip suportado: o custom_fields devolvido por GET /api/v1/contacts/{id} tem exatamente esta forma e pode ser reenviado aqui sem conversão.
  • Campos nativos de identidade e endereço vão no top-level (cpf, rg, genero, estado_civil, profissao, city, state, postal_code) — não em custom_fields. cpf e postal_code são normalizados para dígitos pelo servidor.
  • cnpj (opcional, normalizado para dígitos) cria ou associa uma empresa-cliente (organization) e busca a razão social automaticamente em segundo plano.
  • list_ids é validado ANTES da criação: uma list_id de outra empresa ou inativa retorna 404 e o contato NÃO é criado. Só a falha de infraestrutura na associação PÓS-criação vira o campo warnings na resposta (o contato existe).

Modelo de envio

Corpo da requisição
{
  "phone": "5548999990000",
  "name": "João Silva",
  "email": "joao@exemplo.com.br",
  "birthday": "1990-05-12",
  "observation": "Veio da feira de negócios",
  "cpf": "99001122334",
  "rg": "1234567",
  "genero": "Masculino",
  "estado_civil": "Casado",
  "profissao": "Engenheiro",
  "city": "Florianópolis",
  "state": "SC",
  "postal_code": "88010000",
  "cnpj": "12345678000190",
  "custom_fields": {
    "<id-do-campo-uuid>": "campanha"
  },
  "list_ids": [
    "<uuid>"
  ]
}

Resposta (201)

Corpo da resposta
{
  "success": true,
  "data": {
    "contact": {
      "id": "<uuid>",
      "identifier": "5548999990000",
      "phone": "5548999990000",
      "email": "joao@exemplo.com.br",
      "name": "João Silva",
      "display_name": "João Silva",
      "source_platform": "whatsapp",
      "inbox_status": "open",
      "priority": "normal",
      "crm_status": "open",
      "is_ai_enabled": false,
      "is_archived": false,
      "is_blocked": false,
      "first_message_at": null,
      "last_message_at": null,
      "last_inbound_at": null,
      "pipeline_stage_id": null,
      "assigned_to_member_id": null,
      "last_connection_id": null,
      "created_at": "2026-07-16T18:00:00.000Z",
      "updated_at": "2026-07-16T18:00:00.000Z"
    }
  }
}

Erros

StatusCódigoQuando ocorre
409CONTACT_EXISTSTelefone já cadastrado nesta empresa — retorna "contact_id" do existente
422VALIDATION_ERRORTelefone não pôde ser normalizado, valor de custom_fields não é string, ou a chave não é o ID de um campo ativo da empresa (lista dos inválidos em details)
404NOT_FOUNDUma das list_ids não pertence à empresa ou está inativa — o contato não é criado (details.list_ids)

Exemplo

curl
curl -s -X POST -H "Authorization: Bearer <sua-key>" -H "Content-Type: application/json" \
  -d '{"phone":"5548999990000","name":"João Silva","email":"joao@exemplo.com.br","birthday":"1990-05-12","observation":"Veio da feira de negócios","cpf":"99001122334","rg":"1234567","genero":"Masculino","estado_civil":"Casado","profissao":"Engenheiro","city":"Florianópolis","state":"SC","postal_code":"88010000","cnpj":"12345678000190","custom_fields":{"<id-do-campo-uuid>":"campanha"},"list_ids":["<uuid>"]}' \
  https://www.prosalab.com/api/v1/contacts
escopo: contacts:writeEnterprise

Atualiza um contato (merge parcial — só os campos enviados; telefone é imutável).

  • custom_fields (keyed pelo ID do campo, valores string) faz merge campo a campo sobre o valor atual — não substitui o objeto inteiro.
  • Campos nativos (cpf, rg, genero, estado_civil, profissao, city, state, postal_code) são atualizados individualmente; enviar "" limpa o campo.
  • cnpj só associa uma organization se o contato ainda NÃO tiver uma — não sobrescreve o vínculo existente.
  • O corpo GRAVÁVEL é: name, email, birthday, observation, city, state, postal_code, cpf, rg, genero, estado_civil, profissao, cnpj, custom_fields, list_ids.
  • Round-trip suportado: pegue o corpo de GET /api/v1/contacts/{id}, altere o que quiser e reenvie inteiro. Os campos só-leitura da resposta (id, display_name, crm_status, inbox_status, tags, connections, assigned_to, lead_memory, created_at, updated_at) são IGNORADOS em silêncio — são eco da leitura, não intenção de escrita.
  • phone e identifier continuam imutáveis, mas com a comparação certa: reenviar o valor atual (inclusive reformatado, como "+55 (11) 99999-9999") é aceito; enviar um número DIFERENTE retorna 422 com details.fields. Para trocar o número, crie um novo contato.
  • Chave desconhecida (um "nome" no lugar de "name") continua retornando 422 — o descarte vale só para a lista de só-leitura acima, para que erro de digitação não passe despercebido.

Modelo de envio

Corpo da requisição
{
  "name": "João da Silva",
  "email": "joao.silva@exemplo.com.br",
  "profissao": "Arquiteto",
  "custom_fields": {
    "<id-do-campo-uuid>": "indicação"
  }
}

Resposta (200)

Corpo da resposta
{
  "success": true,
  "data": {
    "contact": {
      "id": "<uuid>",
      "identifier": "5548999990000",
      "phone": "5548999990000",
      "email": "joao.silva@exemplo.com.br",
      "name": "João da Silva",
      "display_name": "João da Silva",
      "source_platform": "whatsapp",
      "inbox_status": "open",
      "priority": "normal",
      "crm_status": "open",
      "is_ai_enabled": true,
      "is_archived": false,
      "is_blocked": false,
      "first_message_at": "2026-07-01T14:03:00.000Z",
      "last_message_at": "2026-07-15T09:22:00.000Z",
      "last_inbound_at": "2026-07-15T09:20:00.000Z",
      "pipeline_stage_id": "<uuid>",
      "assigned_to_member_id": "<uuid>",
      "last_connection_id": "<uuid>",
      "created_at": "2026-07-01T14:03:00.000Z",
      "updated_at": "2026-07-16T18:05:00.000Z"
    }
  }
}

Erros

StatusCódigoQuando ocorre
404NOT_FOUNDContato não existe, ou uma das list_ids enviadas não pertence à empresa
422VALIDATION_ERRORCorpo envia chave desconhecida, tenta MUDAR phone/identifier (details.fields), ou custom_fields usa um ID de campo inexistente/inativo

Exemplo

curl
curl -s -X PATCH -H "Authorization: Bearer <sua-key>" -H "Content-Type: application/json" \
  -d '{"name":"João da Silva","email":"joao.silva@exemplo.com.br","profissao":"Arquiteto","custom_fields":{"<id-do-campo-uuid>":"indicação"}}' \
  https://www.prosalab.com/api/v1/contacts/{id}
escopo: contacts:writeEnterprise

Remove o contato e purga seus documentos anexados (LGPD).

Resposta (200)

Corpo da resposta
{
  "success": true
}

Erros

StatusCódigoQuando ocorre
404NOT_FOUNDContato não existe ou pertence a outra empresa
500PURGE_FAILEDFalha ao apagar os documentos do contato — o contato é preservado, nada é deletado

Exemplo

curl
curl -s -X DELETE -H "Authorization: Bearer <sua-key>" https://www.prosalab.com/api/v1/contacts/{id}
Contatos — Prosalab Developers