Voltar ao início

Documentação da API

Última atualização: Agosto de 2026

A API está disponível nos planos Pro e Business. Numa conta Free toda chamada responde 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_aqui

A 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

GET

/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çalhoO que é
X-RateLimit-LimitO teto da janela — hoje, 100
X-RateLimit-RemainingQuantas ainda cabem
X-RateLimit-ResetQuando 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

POST

/api/v1/links

Cria um link curto.

Corpo

CampoTipoObrigatórioDescrição
urltextoSimO destino. Sem esquema, assume https://
custom_codetextoNãoO apelido do link. Sem ele, sai um código aleatório
titletextoNãoNome 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

  • 400url ausente ou inválida, ou custom_code fora do formato aceito
  • 403 — limite de links do mês atingido, ou plano sem API
  • 409 — já existe um link com esse custom_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.

GET

/api/v1/links

Lista os seus links, do mais recente para o mais antigo.

Parâmetros de consulta

ParâmetroPadrãoDescrição
limit20Entre 1 e 100
offset0Quantos pular
page1Alternativa ao offset; se os dois vierem, vale o offset
statustodosactive 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.

GET

/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.

PATCH

/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.

DELETE

/api/v1/links/:id

Apaga o link. Responde 204, sem corpo.

Isto apaga de verdade, e o histórico de cliques vai junto — não há como desfazer. Para só interromper o link mantendo os dados, use PATCH com "is_active": false, que é reversível.
GET

/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

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.

POST

/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.

GET

/api/v1/webhooks

Lista as inscrições da conta em data: id, url, events, active e datas. O segredo de assinatura não é devolvido.

DELETE

/api/v1/webhooks/:id

Cancela a inscrição. Responde 204, sem corpo.

Códigos de resposta

CódigoO que significa
200 / 201 / 204Deu certo
400O corpo ou um parâmetro está inválido
401Chave ausente, malformada ou revogada
403Plano sem API, ou limite do plano atingido
404O recurso não existe ou não é da sua conta
409Conflito — o apelido já está em uso
429Limite de requisições estourado
500Erro 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.