Authorization: Bearer cliqo_… — veja Visão geral e autenticação.
GET /v1/me
Devolve a identidade e as capacidades da chave que chamou — e o relógio do servidor. Use como teste de conectividade. Funciona com qualquer escopo, inclusivesend_only, que não pode ler mais nada. É de propósito: uma chave precisa conseguir se apresentar mesmo sem permissão de leitura.
server_time é ISO-8601 em UTC. Ele existe para você notar uma diferença de relógio ou de fuso antes de concluir que um relatório está errado — o GET /v1/reports/campaigns recebe datas, e um dia de diferença é muito mais fácil de explicar quando dá para comparar os relógios.
POST /v1/contacts
Cria ou atualiza um contato (upsert por e-mail, dentro da marca da chave). Exige escopo que escreve contatos (full ou send_only).
email— obrigatório; normalizado (espaços removidos, minúsculas).opt_in/whatsapp_opt_in— booleanos que registram consentimento com origem e data. Só mandetruequando o seu sistema de fato coletou aquele consentimento; a chamada de API vira o registro de proveniência dele.whatsapp_opt_in: truesem telefone →400 WHATSAPP_OPTIN_WITHOUT_PHONE.source— texto livre de origem (até 50 caracteres), exibido no perfil do contato.mode— o que fazer quando o contato já existe; padrãoupdate. Veja a tabela abaixo.custom_fields— gravado exatamente como veio (sem normalização neste caminho). Mande as chaves já emsnake_caseminúsculo e datas comoAAAA-MM-DD, iguais às que o import e os formulários criam — senão você cria um campo paralelo. Exceção: chave que é tag do sistema (nome,primeiro_nome,email,link_descadastro,link_ver_online,company_name,endereco,data_do_envio,cupom_codigo,cupom_validade) grava em<chave>_1(endereco→endereco_1), porque{{endereco}}no e-mail é o endereço da loja, e um valor emendereconunca apareceria. Se você mandar as duas, a<chave>_1explícita vence.
409 CONTACT_SUPPRESSED — o endereço descadastrou ou foi suprimido. Não contorne mudando o e-mail: a supressão protege o destinatário e a sua reputação de remetente, tenha vindo de um descadastro, de uma reclamação de spam, de um bounce forte ou da blocklist.
mode — as mesmas regras do import de CSV
São os quatro modos que a tela de importação já oferece, agora também aqui. Todos decidem uma coisa só: o que fazer quando o e-mail já existe naquela marca.
Dois detalhes do
replace, para não surpreender: tags só é gravado quando você o envia (nunca é apagado por omissão), e o source de um contato existente nunca é reescrito — ele registra por onde o contato entrou, não a última chamada.
⚠️ mode desconhecido é recusado com 400 MODE_INVALID, nunca aceito como update. Um mode: "only_new" virando silenciosamente “atualizar tudo” faria a plataforma executar o oposto do pedido, na base inteira.
A resposta diz o que de fato aconteceu: created, updated e o mode aplicado.
🔴 Três coisas que nenhum modo muda — e as três têm peso legal
① Suprimido continua suprimido. Um contato descadastrado existe. Seignore (“só adicionar novos”) o tratasse como inexistente, re-inscreveríamos quem pediu para sair. A guarda de supressão roda antes do modo, então os quatro devolvem o mesmo 409.
② Contato existente nunca é re-inscrito. status e subscribed são definidos apenas quando o contato nasce. Em contato existente eles ficam intactos: um POST não faz voltar a receber e-mail quem tinha parado.
③ opt_in: true não ressuscita consentimento recusado. Se o contato já havia optado por não receber marketing, o consentimento não é regravado — e a resposta diz isso em email_consent: "skipped_previous_opt_out", para você não descobrir só na auditoria.
Reativar é ato deliberado, feito no app por uma pessoa.
POST /v1/contacts/batch
A mesma escrita doPOST /v1/contacts, para até 1.000 contatos numa chamada. Subir 5.000 contatos um a um eram 5.000 chamadas e, no limite de 600 por minuto da chave, no mínimo 8,5 minutos; aqui são 5 chamadas.
✅ Medido: 1.000 contatos em 4,6 s de ponta a ponta, contra um teto de duração de 30 s. A folga importa porque a checagem de domínio faz uma consulta de DNS por domínio distinto — uma base com muitos domínios diferentes custa mais que esta medição.
contacts— de 1 a 1.000 itens, cada um no formato doPOST /v1/contacts(email,name,phone_whatsapp,tags,custom_fields,opt_in,whatsapp_opt_in). Mais de 1.000 →413 BATCH_TOO_LARGE, com o teto emmax_items.mode— vale para o lote inteiro; são os mesmos quatro modos; um desconhecido é400 MODE_INVALID, nunca silenciosamenteupdate.list— opcional; o número humanizado da lista ou o UUID dela, como noGET /v1/contacts. Todo mundo que entrou é vinculado a ela.source— opcional, padrãoapi.
received, created, updated, unchanged, linked, email_consents_granted, whatsapp_consents_granted, invalid_phones, e uma lista skipped com index (a posição no seu array), email e reason — e-mail ausente ou inválido, e-mail repetido dentro do lote (vale a última ocorrência), domínio que não recebe e-mail, contato suprimido, whatsapp_opt_in sem telefone, ou “não existe” sob updateonly. created + updated + unchanged + skipped.length é igual a received, com uma exceção: um contato criado por outra chamada no mesmo instante não aparece em contador nenhum. unchanged só conta sob ignore (os outros modos sempre reescrevem um contato existente). Item pulado não derruba o lote — os outros entram.
Onde ele difere do POST /v1/contacts, de propósito:
opt_in: falsenão faz nada, exatamente como no endpoint de um só. Ele não é uma recusa; recusa é o contato se descadastrar. Um sistema que mandafalsepara todo mundo que nunca respondeu não pode descadastrar a sua base inteira por acidente.- Contato suprimido não devolve
409— ele aparece emskippedcom o motivo e o resto do lote segue. Mesma regra (a guarda de supressão roda primeiro), contabilidade diferente. - Os valores de
custom_fieldssão gravados como texto e as chaves são normalizadas como no import de CSV (snake_caseminúsculo;Data de Nascimentoviradata_de_nascimento). Mande as chaves já emsnake_caseminúsculo, iguais às que você usa noPOST /v1/contacts, ou você cria um campo paralelo. Chave que é tag do sistema grava em<chave>_1, como noPOST /v1/contacts. - O consentimento é por item e nunca é inventado: sem
opt_in: trueno item, nenhum consentimento de e-mail é registrado.whatsapp_opt_in: trueexige umphone_whatsappválido em E.164; um número inválido conta eminvalid_phonese o contato é gravado sem o canal. - Uma falha do nosso lado no meio do lote devolve
503 BATCH_WRITE_FAILED, e o que já foi gravado permanece gravado. Repetir a chamada inteira é seguro — a escrita é idempotente por e-mail.
full ou send_only).
GET /v1/contacts
Lê os contatos de uma lista — é por aqui que você tira os seus dados:listé obrigatório — o número humanizado da lista ou o UUID dela. Não existe “exportar tudo” por omissão: devolver a base inteira porque alguém esqueceu um parâmetro não é um padrão seguro.limit— 1 a 1000; padrão 100.offset— padrão 0.
id, email, name, phone_whatsapp, status, subscribed, tags, custom_fields, source, created_at e updated_at.
Exige chave que lê — escopo “Só ler” ou “Tudo”. Uma chave “Só enviar” recebe 403 API_KEY_SCOPE_INSUFICIENTE: ela escreve contatos e, de propósito, não os lê de volta.
Um número que surpreende: meta.total aqui conta destinatários elegíveis, enquanto contact_count em /v1/lists conta vínculos. A mesma lista legitimamente mostra dois números diferentes — descadastrados e suprimidos continuam na lista, mas não receberiam a campanha. Nenhum está errado; eles respondem perguntas diferentes.
GET /v1/lists
Todas as listas da marca a que a chave pertence:limit— 1 a 1000; padrão 100.offset— padrão 0.
number humanizado da lista (o mesmo que aparece no app), o id, name, description, contact_count, created_at e updated_at.
O objeto meta sempre traz count, total, limit, offset e has_more. has_more vem sempre, inclusive quando é false — assim você nunca precisa distinguir “ausente” de “false”, que é como uma integração para calada na primeira página.
GET /v1/lists/
Uma lista, por qualquer um dos dois identificadores:- O objeto vem cru, sem envelope
data— igual aoGET /v1/me, o outro endpoint de recurso único, e a convenção que se espera de um retrieve. - Ele é idêntico, byte a byte, a um item do
data[]do plural, porque os dois saem do mesmo serializador. Código escrito contra um funciona contra o outro.
404 com código LIST_NOT_FOUND — a mesma resposta quer o id seja desconhecido, quer pertença a outra marca, então o endpoint nunca confirma a existência do dado de terceiro.
GET /v1/reports/campaigns
Métricas por campanha, filtráveis por período e canal:from/to— datas em ISO-8601. A janela tem teto de 366 dias.channel—emailouwhatsapp; omita para os dois.
A janela, e o que acontece quando você a omite
Janela mais larga custa mais para calcular, e o custo cresce com a idade da conta — por isso a faixa é limitada:
🔑 A resposta sempre declara a janela que usou, em
meta.from e meta.to, com o teto em meta.max_window_days. Leia de lá em vez de supor: faixa omitida é respondida com um padrão, não com “tudo”, e sem esses campos você não teria como saber a diferença.
O formato da resposta
É JSON — este endpoint não devolve arquivo, e não há variante CSV nem XLSX.data é um array com uma entrada por campanha; meta descreve a consulta que a produziu.
31.43 é 31,43%), não em fração. meta.truncated diz se a fonte cortou a lista.
Como ler os números, por desenho:
- Métrica que não existe naquele canal vem
null, nunca0— por exemploopens,open_rateeamp_rateno WhatsApp. - Taxa sem denominador vem
null(0/0não é0%). - Campanha cujo disparo falhou pode vir com
sent_at: nulle ainda assim aparecer — disparo que falhou dentro da sua janela é informação, não ruído.