Documentação da API
Última atualização: Agosto de 2026
403.A base é https://klyk.app.br. Toda resposta é JSON, e salvo onde indicado ela vem no envelope { "data": …, "error": … } — em caso de erro, data é null e error traz a mensagem.
Autenticação
Toda requisição leva a chave no cabeçalho:
Authorization: Bearer klyk_live_sua_chave_aquiA chave se gera no painel, em Configurações → API. Ela aparece uma única vez: guarde em variável de ambiente, nunca no código versionado, e gere outra se desconfiar que vazou.
Testar a conexão
/api/v1/me
Devolve a identidade da conta. É o endpoint para verificar se a chave está válida. Resposta achatada, sem envelope — integrações como Zapier montam o rótulo da conexão a partir dos campos de primeiro nível.
{
"id": "0f8c…",
"email": "voce@exemplo.com.br",
"plan": "pro"
}Limite de requisições
100 requisições por hora, por chave, em janela deslizante. Vale para todos os planos que têm API.
Toda resposta traz o estado do limite:
| Cabeçalho | O que é |
|---|---|
X-RateLimit-Limit | O teto da janela — hoje, 100 |
X-RateLimit-Remaining | Quantas ainda cabem |
X-RateLimit-Reset | Quando a janela zera, em milissegundos desde 1970 |
Estourado o limite, a resposta é 429 com os mesmos cabeçalhos — espere até o Reset antes de tentar de novo.
Links
/api/v1/links
Cria um link curto.
Corpo
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
url | texto | Sim | O destino. Sem esquema, assume https:// |
custom_code | texto | Não | O apelido do link. Sem ele, sai um código aleatório |
title | texto | Não | Nome interno, só para você achar depois |
{
"url": "https://loja.exemplo.com.br/promocao",
"custom_code": "promo",
"title": "Black Friday — Instagram"
}Resposta 201
{
"data": {
"id": "9a1f…",
"short_code": "promo",
"short_url": "https://klyk.app.br/promo",
"destination_url": "https://loja.exemplo.com.br/promocao",
"title": "Black Friday — Instagram",
"is_active": true,
"created_at": "2026-08-01T12:00:00.000Z"
},
"error": null
}Erros
400—urlausente ou inválida, oucustom_codefora do formato aceito403— limite de links do mês atingido, ou plano sem API409— já existe um link com essecustom_code
O limite de links é por mês corrido, não vitalício: ele reabre no dia 1º. Links já criados nunca deixam de funcionar por causa do limite.
/api/v1/links
Lista os seus links, do mais recente para o mais antigo.
Parâmetros de consulta
| Parâmetro | Padrão | Descrição |
|---|---|---|
limit | 20 | Entre 1 e 100 |
offset | 0 | Quantos pular |
page | 1 | Alternativa ao offset; se os dois vierem, vale o offset |
status | todos | active ou inactive |
Resposta 200
{
"data": [ { "id": "9a1f…", "short_code": "promo", "short_url": "…",
"destination_url": "…", "title": "…", "is_active": true,
"total_clicks": 1204, "created_at": "…" } ],
"total": 47,
"limit": 20,
"offset": 0,
"error": null
}A resposta repete a lista em links e a paginação em pagination. São campos de compatibilidade com integrações antigas — em código novo, use data, total, limit e offset.
/api/v1/links/:id
Lê um link só, pelo id. É o endpoint para saber o estado atual de algo que você criou antes, sem precisar listar a conta inteira.
{
"data": {
"id": "9a1f…",
"short_code": "promo",
"short_url": "https://klyk.app.br/promo",
"destination_url": "https://loja.exemplo.com.br/promocao",
"title": "Black Friday — Instagram",
"is_active": true,
"total_clicks": 1204,
"created_at": "2026-08-01T12:00:00.000Z"
},
"error": null
}Link que não existe e link que não é seu respondem a mesma coisa: 404. Distinguir os dois contaria a um estranho que aquele id existe.
/api/v1/links/:id
Altera o destino, o nome interno ou o estado do link. Só os campos enviados mudam.
{
"destination_url": "https://loja.exemplo.com.br/nova-pagina",
"is_active": false
}Responde 200 com o link atualizado no envelope data, ou 404 se o link não for seu.
destination também é aceito como nome do campo de destino, por compatibilidade.
/api/v1/links/:id
Apaga o link. Responde 204, sem corpo.
PATCH com "is_active": false, que é reversível./api/v1/links/:id/analytics
Os cliques de um link, agregados. O parâmetro period aceita 7d, 30d, 90d ou all — o padrão é 30d.
{
"data": {
"total_clicks": 1204,
"unique_clicks": 843,
"by_country": { "BR": 1102, "PT": 61, "unknown": 41 },
"by_device": { "mobile": 890, "desktop": 300, "tablet": 14 },
"by_source": { "instagram.com": 700, "direct": 504 }
},
"error": null,
"meta": { "linkId": "9a1f…", "period": "30d" }
}As chaves de by_source são o domínio de origem, sem www.; quem chegou sem origem conhecida cai em direct. O período é limitado pela retenção do seu plano.
Webhooks
O Klyk chama uma URL sua a cada evento. Dá para cadastrar pelo painel, em Configurações → Webhooks, ou pelos endpoints abaixo — que é como Zapier, Make e n8n se inscrevem sozinhos quando alguém liga a automação.
Eventos
link.created— um link foi criadolink.clicked— alguém acessou um linklink.updated— destino, título ou estado mudoulink.deactivated— o link foi pausadolink.deleted— o link foi apagado
O que chega
POST https://sua-url/callback
Content-Type: application/json
X-Klyk-Event: link.clicked
X-Klyk-Timestamp: 2026-08-01T12:00:00.000Z
X-Klyk-Signature: sha256=4f1c…
{
"event": "link.clicked",
"timestamp": "2026-08-01T12:00:00.000Z",
"data": {
"link_id": "9a1f…",
"short_code": "promo",
"country": "BR",
"device": "mobile",
"source": "https://www.instagram.com/"
}
}Conferir a assinatura
O HMAC-SHA256 é calculado sobre o timestamp, um ponto, e o corpo cru — não sobre o corpo sozinho. O timestamp usado é o mesmo que vai em X-Klyk-Timestamp, e o cabeçalho traz o prefixo sha256=.
import { createHmac, timingSafeEqual } from "node:crypto";
function confere(corpoCru, cabecalhos, segredo) {
const timestamp = cabecalhos["x-klyk-timestamp"];
const recebida = cabecalhos["x-klyk-signature"].replace(/^sha256=/, "");
const esperada = createHmac("sha256", segredo)
.update(`${timestamp}.${corpoCru}`)
.digest("hex");
// Comparação de tempo constante: usar === vaza, pelo tempo de resposta,
// quantos caracteres iniciais bateram.
const a = Buffer.from(recebida, "hex");
const b = Buffer.from(esperada, "hex");
return a.length === b.length && timingSafeEqual(a, b);
}Compare sobre o corpo cru, antes de qualquer JSON.parse: reserializar muda espaços e ordem de chaves, e a assinatura deixa de bater. Rejeite também timestamps muito antigos — é o que impede que uma entrega capturada seja reenviada depois.
Entrega e reenvio
Cada chamada espera até 10 segundos pela sua resposta. Falhas são reenviadas, com espera crescente entre as tentativas. Responda rápido: processe depois, em fila, e devolva 200 logo.
/api/v1/webhooks
Inscreve uma URL. Sem o campo de eventos, ela recebe todos.
{
"url": "https://sua-url/callback",
"events": ["link.created", "link.clicked"]
}Responde 201 com a inscrição em data, incluindo o id usado para cancelar depois. URLs que apontam para a rede interna são recusadas com 400.
event no singular, com um texto só, também é aceito — é a convenção de REST Hook que o Zapier usa.
/api/v1/webhooks
Lista as inscrições da conta em data: id, url, events, active e datas. O segredo de assinatura não é devolvido.
/api/v1/webhooks/:id
Cancela a inscrição. Responde 204, sem corpo.
Códigos de resposta
| Código | O que significa |
|---|---|
| 200 / 201 / 204 | Deu certo |
| 400 | O corpo ou um parâmetro está inválido |
| 401 | Chave ausente, malformada ou revogada |
| 403 | Plano sem API, ou limite do plano atingido |
| 404 | O recurso não existe ou não é da sua conta |
| 409 | Conflito — o apelido já está em uso |
| 429 | Limite de requisições estourado |
| 500 | Erro nosso. Se persistir, escreva para o suporte |
Suporte
suporte@klyk.app.br. Mande a chamada que falhou (sem a chave) e o que você recebeu de volta — chega direto em quem escreveu o endpoint.