pik.li pik.li
Versão 1 · estável

API do pik.li

Crie, edite e meça seus links curtos a partir do seu próprio código. Uma API REST com JSON na entrada e na saída, uma chave por integração e cada endpoint documentado aqui com uma requisição e uma resposta real.

Obter uma chave de API OpenAPI 3.1
URL basehttps://pik.li/api/v1

Introdução

A API do pik.li permite que o seu software faça o que o painel faz: criar e editar links curtos, ler as estatísticas deles, listar suas tags e seus domínios e gerenciar webhooks. Ela funciona via HTTPS, e todo caminho começa com a URL base mostrada acima.

  • Envie os corpos das requisições em JSON com Content-Type: application/json; os parâmetros das requisições GET vão na query string. As respostas são sempre JSON em UTF-8, exceto o QR code, que é uma imagem.
  • A versão faz parte do caminho (/api/v1) e toda resposta traz o cabeçalho X-Api-Version: 1. Novos campos podem aparecer nas respostas da v1 com o tempo: faça o seu código ignorar os que ele não conhece.
  • Datas e horários são strings ISO 8601 com o offset de fuso horário (2026-09-23T10:15:42.118+02:00); datas sem horário têm o formato 2026-09-23.
  • Os identificadores são números inteiros. Um link também pode ser encontrado pela URL curta com GET /links/lookup.

Início rápido

  1. Crie uma chave no painel (veja Criar uma chave) e guarde-a em uma variável de ambiente.
  2. Confira se ela funciona: GET /me responde com a sua conta e o seu plano.
  3. Crie seu primeiro link curto com POST /links.
Terminal
export PIKLI_KEY="pk_live_…"

curl https://pik.li/api/v1/me \
  -H "Authorization: Bearer $PIKLI_KEY"

curl -X POST https://pik.li/api/v1/links \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/a/very/long/page"}'

Autenticação

Toda chamada, exceto GET /ping, precisa de uma chave de API, enviada no cabeçalho Authorization como bearer token.

Cabeçalho
Authorization: Bearer pk_live_3kZ9vQeX1mT0bR7yNw2aLp4sUc8dHf6J

O cabeçalho X-API-Key também funciona. O parâmetro de query api_key ainda é aceito para clientes antigos, mas evite-o: os endereços acabam em logs e no histórico dos navegadores.

As chaves começam com pk_live_ seguido de 32 caracteres. Uma chave enxerga exatamente o que a conta dela enxerga — links, domínios, tags, estatísticas e webhooks — e todas as chaves de uma conta podem fazer as mesmas coisas: não existem chaves somente leitura.

Trate uma chave como uma senha. Mantenha-a no seu servidor, nunca em código que roda no navegador ou em um app que você distribui. Se uma chave vazar, revogue-a e crie outra: leva dez segundos.

Criar uma chave

  1. Entre na sua conta e abra API e chaves no painel.
  2. Dê à chave um nome que diga onde ela é usada (por exemplo CRM produção) e clique em Gerar chave.
  3. Copie a chave na hora: o painel a mostra uma única vez.
  4. Você pode ter até 10 chaves ativas. Para cada uma, a página mostra quando foi usada pela última vez e quantas requisições fez, e permite revogá-la: a partir da chamada seguinte, uma chave revogada recebe 401 unauthorized.

Quem pode usar a API

A API vem com os planos pagos (Premium e Business). O plano Base não a inclui, a menos que a equipe do pik.li a habilite para o plano gratuito ou para a sua conta. Em todos os casos, a conta precisa ter um endereço de e-mail confirmado e não pode estar bloqueada nem suspensa.

Quando falta uma dessas condições, a API responde api_disabled, email_unconfirmed ou account_blocked.

Base

Só se a equipe habilitar
Requisições por minuto
60
Novos links
3 por dia
Links por chamada em massa
10
Estatísticas guardadas por
90 dias
Webhooks
Não

Premium

API incluída
Requisições por minuto
600
Novos links
2.500 por mês
Links por chamada em massa
100
Estatísticas guardadas por
730 dias
Webhooks
Sim

Business

API incluída
Requisições por minuto
3.000
Novos links
10.000 por mês
Links por chamada em massa
100
Estatísticas guardadas por
1.095 dias
Webhooks
Sim

Estes são os números padrão de cada plano, lidos em tempo real das configurações de preços. Os seus números, com qualquer ajuste que a equipe tenha feito na sua conta, vêm de GET /limits e GET /me.

Limites de requisições

Cada chave pode fazer um certo número de requisições por minuto, que depende do plano (veja a tabela acima). A contagem recomeça no início de cada minuto.

X-RateLimit-Limit
requisições permitidas por minuto para esta chave
X-RateLimit-Remaining
requisições restantes no minuto atual
X-RateLimit-Reset
quando a contagem recomeça, como horário Unix em segundos
Retry-After
só em uma resposta 429: quantos segundos esperar

Acima do limite, a API responde 429 com o erro rate_limited e um cabeçalho Retry-After. Espere esse tempo e continue: tentar de novo na hora só consome o minuto seguinte.

Resposta
HTTP/1.1 429 Too Many Requests
Content-Type: application/json; charset=utf-8
Retry-After: 18
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1790151360

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Check the X-RateLimit-* headers and retry after the window resets.",
    "details": { "limit": 600, "period_seconds": 60, "resets_at": "2026-09-23T08:16:00Z" },
    "docs_url": "https://pik.li/help#error-rate_limited"
  },
  "request_id": "2b8e51d0-7a4c-4f1e-9d3b-5c6a0e8f7d21"
}
  • Chamadas sem chave são limitadas a 30 por minuto para cada endereço IP (GET /ping não conta).
  • Além disso, todo endereço IP tem um teto de 600 requisições por minuto para o pik.li como um todo, qualquer que seja o plano.

Erros

Uma requisição que falha sempre recebe uma resposta com o mesmo formato, qualquer que seja o endpoint, com um status HTTP que corresponde ao problema.

Resposta
{
  "error": {
    "code": "quota_exceeded",
    "message": "Link quota reached: 2500 links per month on your plan.",
    "details": { "limit": 2500, "used": 2500, "period": "month", "resets_at": "2026-09-30T22:00:00Z" },
    "docs_url": "https://pik.li/help#error-quota_exceeded"
  },
  "request_id": "6f1c2a9e-4b0d-4c55-9a51-8f2f0d7c1e3b"
}
code
Identificador estável, feito para o seu código. Nunca muda e nunca é traduzido.
message
Uma frase para pessoas, no idioma da requisição (veja Idiomas).
details
Dados estruturados, quando ajudam: os campos inválidos, os valores permitidos, os números de uma cota. Nem sempre está presente.
docs_url
O link para a explicação deste código, nesta página.
request_id
O identificador da requisição, também enviado no cabeçalho X-Request-Id. Informe-o quando escrever para o suporte.

Todo endpoint que precisa de chave também pode responder unauthorized account_blocked email_unconfirmed api_disabled rate_limited: eles não são repetidos em cada endpoint.

unauthorized401
A chave está ausente, errada ou foi revogada. Verifique o cabeçalho Authorization e se a chave ainda aparece como ativa no painel.
account_blocked403
A conta está bloqueada, suspensa ou desativada. A API fica fechada até a conta voltar a ficar ativa.
email_unconfirmed403
O endereço de e-mail da conta ainda não foi confirmado. Siga o link do e-mail de confirmação e tente de novo.
api_disabled403
A API não está incluída no plano desta conta ou foi desligada para ela. Veja Quem pode usar a API.
forbidden403
A chave não pode executar esta ação. reservado
feature_required402
O plano não inclui a funcionalidade de que este endpoint precisa. details.feature indica qual é, e details.plan o primeiro plano que a oferece.
quota_exceeded402
A cota de links do dia ou do mês se esgotou. details traz limit, used, period e resets_at, o momento em que será possível criar links de novo.
not_found404
O recurso não existe, foi excluído ou pertence a outra conta.
conflict409
A requisição entra em conflito com o estado atual do recurso. reservado
invalid422
Alguns dados não passaram na validação: details.fields lista os problemas campo a campo, e details.messages traz as mesmas informações como frases prontas.
domain_not_allowed422
Sua conta não pode usar este domínio. GET /domains lista os que ela pode usar.
bad_request400
Falta um parâmetro obrigatório; details.parameter indica qual.
invalid_json400
O corpo não é um JSON válido. Verifique as aspas e o cabeçalho Content-Type.
invalid_parameter400
Um parâmetro tem um valor que o endpoint não reconhece. A mensagem indica o parâmetro, e details.allowed, quando presente, lista os valores aceitos.
bulk_empty400
O array links de uma chamada em massa está vazio ou ausente.
bulk_too_many400
Links demais em uma única chamada em massa. details.max é o limite do seu plano: divida a lista em várias chamadas.
idempotency_key_invalid400
O cabeçalho Idempotency-Key tem mais de 128 caracteres.
rate_limited429
Requisições demais neste minuto. Espere os segundos indicados em Retry-After e continue.

Uma resposta 500 significa que algo quebrou do nosso lado e não há nada a corrigir na sua requisição. Tente de novo um pouco mais tarde; se continuar acontecendo, escreva para o suporte informando o valor do cabeçalho X-Request-Id.

Paginação

Listas que podem ficar longas são divididas em páginas: GET /links, GET /links/active e GET /links/:id/clicks.

  • page: a página desejada, a partir de 1.
  • per_page: quantos itens por página, de 1 a 100 (padrão 50). limit é aceito como sinônimo.

A resposta traz um objeto pagination, e o total também vem no cabeçalho X-Total-Count. Continue pedindo next_page até que ele seja null.

Resposta
{
  "links": [],
  "pagination": { "page": 2, "per_page": 50, "total": 137, "total_pages": 3, "next_page": 3, "prev_page": 1 }
}

Idempotência

POST /links, POST /links/bulk e POST /links/generate aceitam um cabeçalho Idempotency-Key: qualquer texto de até 128 caracteres escolhido por você, um por operação — um número de pedido, um UUID.

Se o mesmo valor chegar de novo, com a mesma chave de API, em até 24 horas, o pik.li não cria nada: devolve a primeira resposta, com o cabeçalho Idempotent-Replayed: true. Uma requisição repetida depois de um timeout nunca gera duplicatas.

Mesma requisição, mesma chave, em até 24 horas
HTTP/1.1 201 Created
Idempotent-Replayed: true
Content-Type: application/json; charset=utf-8

Respostas com status 5xx não são memorizadas; por isso, repetir a requisição com o mesmo valor a executa de novo. Um valor longo demais recebe idempotency_key_invalid.

Idiomas

As mensagens de erro são escritas em nove idiomas: en it zh ar ru fr de es pt-BR. A API escolhe o idioma pelo parâmetro locale, depois pelo cabeçalho Accept-Language, depois pelo idioma da sua conta e, em último caso, usa o inglês. Só a mensagem muda: códigos e nomes de campos continuam os mesmos, assim como docs_url, que abre esta página no mesmo idioma.

Terminal
curl "https://pik.li/api/v1/links?status=nope" \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Accept-Language: it"

{
  "error": {
    "code": "invalid_parameter",
    "message": "Valore non valido per il parametro status.",
    "details": { "allowed": ["all", "active", "disabled", "review", "expired"] },
    "docs_url": "https://pik.li/help?locale=it#error-invalid_parameter"
  },
  "request_id": "0d5c9b7e-1f2a-4e3b-8c6d-7a9e0f1b2c3d"
}

Janelas de estatísticas

Os endpoints de estatísticas leem uma janela de tempo e a comparam com a janela imediatamente anterior, de mesma duração: é daí que vêm previous e delta_pct.

period
24h, 7d, 30d, 90d, 1y ou all (todo o histórico que o seu plano guarda). Um número de dias também funciona: period=45, ou o antigo days=45. O padrão é 7d para a conta, 30d para um link e all para a lista de cliques.
from, to
Em vez de period: uma data (2026-09-01) ou um horário ISO 8601. Por padrão, to é agora e from é 30 dias antes de to; uma data em to conta até o fim daquele dia.
interval
hour ou day, o passo da série temporal. Por padrão, janelas de até 48 horas são por hora e as mais longas, por dia; uma série por hora cobre no máximo 7 dias.
window.clamped
Uma janela nunca volta mais longe do que o histórico que o seu plano guarda (retention_days). Quando ela é cortada, window.clamped é true.
bots
Cliques de bots ficam de fora. Adicione bots=1 para contá-los também; as estatísticas de um link também os informam à parte, em period.bots.

Endpoints

Cada endpoint com seus parâmetros, uma requisição que você pode colar no terminal e uma resposta real, encurtada quando uma lista ficaria longa. Os exemplos leem a chave de $PIKLI_KEY, como no início rápido.

Serviço

Se a API está no ar e quanto resta do seu plano.

GET /ping Sem chave

Verificar se a API está no ar

Não precisa de chave: pensado para health checks. Devolve o horário do servidor e os endereços desta documentação e do arquivo OpenAPI.

Requisição
curl https://pik.li/api/v1/ping
Resposta · 200
{
  "ok": true,
  "service": "pik.li",
  "version": "v1",
  "time": "2026-09-23T08:15:42Z",
  "docs": "https://pik.li/help",
  "openapi": "https://pik.li/openapi.json"
}
Erros Nenhum: este endpoint não precisa de chave.
GET /limits

O que resta das suas cotas

Links ainda disponíveis hoje ou neste mês, cliques rastreados, a janela do limite de requisições em andamento, chaves e domínios personalizados em uso e o tamanho máximo de uma chamada em massa. Todos os números incluem os ajustes que a equipe possa ter feito na sua conta.

Requisição
curl https://pik.li/api/v1/limits \
  -H "Authorization: Bearer $PIKLI_KEY"
Resposta · 200
{
  "plan": "premium",
  "plan_name": "Premium",
  "links": {
    "period": "month", "limit": 2500, "used": 412, "remaining": 2088,
    "resets_at": "2026-09-30T22:00:00Z",
    "today": 9, "this_month": 412, "per_day": 0, "per_month": 2500
  },
  "clicks": { "limit": 150000, "used": 38120, "remaining": 111880, "resets_at": "2026-09-30T22:00:00Z" },
  "rate_limit": { "limit": 600, "remaining": 597, "period_seconds": 60, "resets_at": "2026-09-23T08:16:00Z" },
  "api_keys": { "used": 2, "limit": 10 },
  "custom_domains": { "used": 1, "limit": 3 },
  "bulk": { "max_items": 100 },
  "retention_days": 730,
  "link_ttl_days": 0,
  "features": ["qr", "analytics_full", "tags", "export", "custom_slug", "password", "expiry", "utm", "bulk", "api", "webhooks"]
}
Erros Só os que qualquer endpoint pode devolver.

Conta

Quem está chamando: a conta dona da chave.

GET /me

Sua conta e a chave em uso

Perfil, plano e sua expiração, funcionalidades, limites e consumo, e a chave que fez a chamada, com o seu contador de requisições.

Requisição
curl https://pik.li/api/v1/me \
  -H "Authorization: Bearer $PIKLI_KEY"
Resposta · 200
{
  "id": 1042,
  "username": "anna",
  "email": "[email protected]",
  "name": "Anna Rossi",
  "status": "active",
  "confirmed": true,
  "locale": "it",
  "plan": "premium",
  "plan_name": "Premium",
  "interval": "monthly",
  "plan_expires_at": "2026-10-14T23:59:59.000+02:00",
  "api_enabled": true,
  "features": ["qr", "analytics_full", "tags", "export", "custom_slug", "password", "expiry", "utm", "bulk", "api", "webhooks"],
  "limits": {
    "links_per_day": 0, "links_per_month": 2500, "clicks_per_month": 150000,
    "retention_days": 730, "link_ttl_days": 0, "custom_domains": 3, "api_rpm": 600
  },
  "usage": {
    "links_today": 9, "links_this_month": 412, "clicks_this_month": 38120,
    "links_quota_used": 412, "links_quota_limit": 2500, "links_total": 3187
  },
  "api_key": {
    "id": 55, "name": "CRM production", "prefix": "pk_live_Xq3v",
    "created_at": "2026-09-02T16:40:10.311+02:00", "last_used_at": "2026-09-23T10:15:42.020+02:00", "requests_count": 18230
  },
  "created_at": "2026-03-11T09:12:47.664+01:00"
}
Erros Só os que qualquer endpoint pode devolver.

Estatísticas da conta

Os números de todos os seus links juntos, com os filtros do painel: janela, domínio, tag, bots. Veja Janelas de estatísticas para os parâmetros comuns.

GET /stats

Estatísticas da conta em uma chamada

Totais da conta, a janela comparada com a anterior, a série temporal, as 10 principais origens e países, os 6 principais dispositivos, navegadores e sistemas operacionais, e os 10 links mais clicados. Os outros endpoints /stats devolvem cada parte separadamente, com listas mais longas.

Parâmetros
  • period string query string

    A janela: 24h, 7d (padrão), 30d, 90d, 1y, all. Veja Janelas de estatísticas.

  • from string query string

    Início da janela, em vez de period: uma data ou um horário ISO 8601.

  • to string query string

    Fim da janela (agora, por padrão).

  • interval string query string

    hour ou day, o passo da série temporal.

  • domain string query string

    Só este domínio, pelo hostname (por exemplo lnkz.li). Precisa ser um dos seus: veja GET /domains.

  • tag string query string

    Só os links com esta tag.

  • bots string query string

    1 para contar também os cliques de bots.

Requisição
curl "https://pik.li/api/v1/stats?period=7d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Resposta · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "totals": {
    "links": 3187, "active_links": 2954, "clicks": 402118, "unique_clicks": 288930,
    "clicks_today": 1204, "links_active_24h": 214, "links_created_in_window": 61
  },
  "period": { "clicks": 21480, "uniques": 15522, "links_clicked": 690, "clicks_per_link": 31.1 },
  "previous": { "clicks": 19870, "uniques": 14410, "links_clicked": 655, "clicks_per_link": 30.3, "delta_pct": 8.1, "uniques_delta_pct": 7.7 },
  "timeseries": {
    "interval": "day",
    "points": [
      { "at": "2026-09-17", "clicks": 2890, "uniques": 2104 },
      { "at": "2026-09-18", "clicks": 3120, "uniques": 2240 }
    ]
  },
  "referrers": [{ "referrer": "instagram.com", "clicks": 5210, "share": 24.3 }],
  "direct": 8740,
  "countries": [{ "country": "IT", "clicks": 12877, "share": 59.9 }],
  "devices": [{ "device": "mobile", "clicks": 14002, "share": 65.2 }],
  "browsers": [{ "browser": "Chrome", "clicks": 9406, "share": 43.8 }],
  "os": [{ "os": "iOS", "clicks": 7340, "share": 34.2 }],
  "top_links": [{
    "id": 4821,
    "short_url": "https://lnkz.li/spring-sale",
    "slug": "spring-sale",
    "domain": "lnkz.li",
    "target_url": "https://example.com/shop/spring-sale",
    "target_host": "example.com",
    "title": "Spring sale",
    "description": null,
    "tags": ["promo", "newsletter"],
    "status": "active",
    "moderation": "allowed",
    "active": true,
    "expired": false,
    "password_protected": false,
    "clicks": 1284,
    "unique_clicks": 902,
    "last_clicked_at": "2026-09-23T09:58:12.431+02:00",
    "expires_at": null,
    "max_clicks": null,
    "disabled_reason": null,
    "utm": { "source": "newsletter", "medium": "email", "campaign": "spring" },
    "preview_url": "https://lnkz.li/spring-sale+",
    "qr_url": "https://pik.li/api/v1/links/4821/qr",
    "created_at": "2026-09-01T08:30:05.117+02:00",
    "updated_at": "2026-09-01T08:30:05.117+02:00",
    "clicks_in_window": 311,
    "uniques_in_window": 240
  }]
}
GET /stats/timeseries

Cliques ao longo do tempo

Cliques e visitantes únicos por hora ou por dia, com o total da janela anterior e a variação percentual.

Parâmetros
  • period string query string

    A janela: 24h, 7d (padrão), 30d, 90d, 1y, all. Veja Janelas de estatísticas.

  • from string query string

    Início da janela, em vez de period: uma data ou um horário ISO 8601.

  • to string query string

    Fim da janela (agora, por padrão).

  • interval string query string

    hour ou day, o passo da série temporal.

  • domain string query string

    Só este domínio, pelo hostname (por exemplo lnkz.li). Precisa ser um dos seus: veja GET /domains.

  • tag string query string

    Só os links com esta tag.

  • bots string query string

    1 para contar também os cliques de bots.

Requisição
curl "https://pik.li/api/v1/stats/timeseries?period=24h" \
  -H "Authorization: Bearer $PIKLI_KEY"
Resposta · 200
{
  "window": {
    "from": "2026-09-22T10:15:42+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 1,
    "interval": "hour",
    "period": "24h",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-21T10:15:42+02:00", "to": "2026-09-22T10:15:42+02:00" }
  },
  "filters": { "bots": false },
  "interval": "hour",
  "clicks": 3308,
  "previous_clicks": 2977,
  "delta_pct": 11.1,
  "points": [
    { "at": "2026-09-22T08:00:00Z", "clicks": 131, "uniques": 97 },
    { "at": "2026-09-22T09:00:00Z", "clicks": 158, "uniques": 120 }
  ]
}
GET /stats/referrers

De onde vêm os cliques

Os sites que enviaram mais cliques, cada um com a sua fatia do total, e os cliques sem origem (direct).

Parâmetros
  • period string query string

    A janela: 24h, 7d (padrão), 30d, 90d, 1y, all. Veja Janelas de estatísticas.

  • from string query string

    Início da janela, em vez de period: uma data ou um horário ISO 8601.

  • to string query string

    Fim da janela (agora, por padrão).

  • domain string query string

    Só este domínio, pelo hostname (por exemplo lnkz.li). Precisa ser um dos seus: veja GET /domains.

  • tag string query string

    Só os links com esta tag.

  • bots string query string

    1 para contar também os cliques de bots.

  • limit integer query string

    Quantas linhas por lista, de 1 a 50 (padrão 10).

Requisição
curl "https://pik.li/api/v1/stats/referrers?period=30d&limit=5" \
  -H "Authorization: Bearer $PIKLI_KEY"
Resposta · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "clicks": 21480,
  "direct": 8740,
  "referrers": [
    { "referrer": "instagram.com", "clicks": 5210, "share": 24.3 },
    { "referrer": "google.com", "clicks": 3011, "share": 14.0 }
  ]
}
GET /stats/countries

Países e cidades

Países como códigos ISO, com a sua fatia; cities vem nos planos que incluem estatísticas completas.

Parâmetros
  • period string query string

    A janela: 24h, 7d (padrão), 30d, 90d, 1y, all. Veja Janelas de estatísticas.

  • from string query string

    Início da janela, em vez de period: uma data ou um horário ISO 8601.

  • to string query string

    Fim da janela (agora, por padrão).

  • domain string query string

    Só este domínio, pelo hostname (por exemplo lnkz.li). Precisa ser um dos seus: veja GET /domains.

  • tag string query string

    Só os links com esta tag.

  • bots string query string

    1 para contar também os cliques de bots.

  • limit integer query string

    Quantas linhas por lista, de 1 a 50 (padrão 10).

Requisição
curl "https://pik.li/api/v1/stats/countries?period=30d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Resposta · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "clicks": 21480,
  "countries": [
    { "country": "IT", "clicks": 12877, "share": 59.9 },
    { "country": "DE", "clicks": 2630, "share": 12.2 }
  ],
  "cities": [
    { "city": "Milano", "clicks": 3120, "share": 14.5 },
    { "city": "Roma", "clicks": 2210, "share": 10.3 }
  ]
}
GET /stats/devices

Dispositivos, navegadores, sistemas e idiomas

Quatro listas em uma chamada, cada uma com os cliques e a fatia do total.

Parâmetros
  • period string query string

    A janela: 24h, 7d (padrão), 30d, 90d, 1y, all. Veja Janelas de estatísticas.

  • from string query string

    Início da janela, em vez de period: uma data ou um horário ISO 8601.

  • to string query string

    Fim da janela (agora, por padrão).

  • domain string query string

    Só este domínio, pelo hostname (por exemplo lnkz.li). Precisa ser um dos seus: veja GET /domains.

  • tag string query string

    Só os links com esta tag.

  • bots string query string

    1 para contar também os cliques de bots.

  • limit integer query string

    Quantas linhas por lista, de 1 a 50 (padrão 10).

Requisição
curl "https://pik.li/api/v1/stats/devices?period=30d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Resposta · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "clicks": 21480,
  "devices": [{ "device": "mobile", "clicks": 14002, "share": 65.2 }, { "device": "desktop", "clicks": 7100, "share": 33.1 }],
  "browsers": [{ "browser": "Chrome", "clicks": 9406, "share": 43.8 }],
  "os": [{ "os": "iOS", "clicks": 7340, "share": 34.2 }],
  "languages": [{ "language": "it", "clicks": 12690, "share": 59.1 }]
}
GET /stats/top

Os links mais clicados

Os links com mais cliques na janela, cada um com clicks_in_window e uniques_in_window.

Parâmetros
  • period string query string

    A janela: 24h, 7d (padrão), 30d, 90d, 1y, all. Veja Janelas de estatísticas.

  • from string query string

    Início da janela, em vez de period: uma data ou um horário ISO 8601.

  • to string query string

    Fim da janela (agora, por padrão).

  • domain string query string

    Só este domínio, pelo hostname (por exemplo lnkz.li). Precisa ser um dos seus: veja GET /domains.

  • tag string query string

    Só os links com esta tag.

  • bots string query string

    1 para contar também os cliques de bots.

  • limit integer query string

    Quantas linhas por lista, de 1 a 50 (padrão 10).

Requisição
curl "https://pik.li/api/v1/stats/top?period=7d&limit=10" \
  -H "Authorization: Bearer $PIKLI_KEY"
Resposta · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "clicks": 21480,
  "links": [{
    "id": 4821,
    "short_url": "https://lnkz.li/spring-sale",
    "slug": "spring-sale",
    "domain": "lnkz.li",
    "target_url": "https://example.com/shop/spring-sale",
    "target_host": "example.com",
    "title": "Spring sale",
    "description": null,
    "tags": ["promo", "newsletter"],
    "status": "active",
    "moderation": "allowed",
    "active": true,
    "expired": false,
    "password_protected": false,
    "clicks": 1284,
    "unique_clicks": 902,
    "last_clicked_at": "2026-09-23T09:58:12.431+02:00",
    "expires_at": null,
    "max_clicks": null,
    "disabled_reason": null,
    "utm": { "source": "newsletter", "medium": "email", "campaign": "spring" },
    "preview_url": "https://lnkz.li/spring-sale+",
    "qr_url": "https://pik.li/api/v1/links/4821/qr",
    "created_at": "2026-09-01T08:30:05.117+02:00",
    "updated_at": "2026-09-01T08:30:05.117+02:00",
    "clicks_in_window": 311,
    "uniques_in_window": 240
  }]
}

Tags

Os rótulos que você coloca nos seus links.

GET /tags

Suas tags

Todas as tags usadas nos seus links, com quantos links têm cada uma e quantos cliques eles receberam, as mais usadas primeiro.

Parâmetros
  • q string query string

    Só as tags que contêm este texto.

Requisição
curl https://pik.li/api/v1/tags \
  -H "Authorization: Bearer $PIKLI_KEY"
Resposta · 200
{
  "tags": [
    { "name": "promo", "links": 214, "clicks": 90211 },
    { "name": "newsletter", "links": 58, "clicks": 20473 }
  ],
  "total": 2
}
Erros Só os que qualquer endpoint pode devolver.

Domínios

Onde os seus links curtos podem morar.

GET /domains

Os domínios que você pode usar

Os domínios do pik.li que o seu plano permite e os seus domínios personalizados verificados, qual deles é o padrão e quantos domínios personalizados o seu plano permite. Passe o hostname como domain ao criar um link.

Requisição
curl https://pik.li/api/v1/domains \
  -H "Authorization: Bearer $PIKLI_KEY"
Resposta · 200
{
  "domains": [
    { "id": 1, "hostname": "lnkz.li", "base_url": "https://lnkz.li", "kind": "system", "min_plan": "free", "status": "active", "verified": true, "default": true },
    { "id": 318, "hostname": "go.example.com", "base_url": "https://go.example.com", "kind": "custom", "min_plan": "free", "status": "active", "verified": true, "default": false }
  ],
  "default": "lnkz.li",
  "custom_domains": { "used": 1, "limit": 3 }
}
Erros Só os que qualquer endpoint pode devolver.

WebhooksPlanos: Premium · Business

Gerencie pelo seu código os webhooks que você configuraria na página Webhooks do painel. O funcionamento das entregas está descrito na seção Webhooks, mais abaixo.

GET /webhooks

Listar seus webhooks

Seus webhooks e os eventos que você pode assinar. O segredo nunca mais é mostrado depois da criação.

Requisição
curl https://pik.li/api/v1/webhooks \
  -H "Authorization: Bearer $PIKLI_KEY"
Resposta · 200
{
  "webhooks": [{
    "id": 17,
    "url": "https://example.com/hooks/pikli",
    "events": ["link.created", "link.disabled"],
    "active": true,
    "failures_count": 0,
    "last_delivered_at": "2026-09-22T18:04:11.520+02:00",
    "created_at": "2026-09-10T11:20:33.004+02:00",
    "updated_at": "2026-09-10T11:20:33.004+02:00",
    "signature_header": "X-Pikli-Signature"
  }],
  "events": ["link.created", "link.clicked", "link.disabled", "link.deleted"]
}
GET /webhooks/:id

Ler um webhook

O webhook com as suas últimas 20 entregas e o status que o seu servidor respondeu a cada uma.

Parâmetros
  • id integer no caminho obrigatório

    O id do webhook.

Requisição
curl https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY"
Resposta · 200
{
  "id": 17,
  "url": "https://example.com/hooks/pikli",
  "events": ["link.created", "link.disabled"],
  "active": true,
  "failures_count": 0,
  "last_delivered_at": "2026-09-22T18:04:11.520+02:00",
  "created_at": "2026-09-10T11:20:33.004+02:00",
  "updated_at": "2026-09-10T11:20:33.004+02:00",
  "signature_header": "X-Pikli-Signature",
  "deliveries": [
    { "id": 903, "event": "ping", "response_code": 200, "attempts": 1, "delivered_at": "2026-09-22T18:04:11.520+02:00", "created_at": "2026-09-22T18:04:11.520+02:00" }
  ]
}
POST /webhooks

Criar um webhook

A resposta traz o secret usado para assinar as entregas, só desta vez: guarde-o agora.

Parâmetros
  • url string corpo JSON obrigatório

    O endereço que recebe as entregas, http:// ou https://, acessível pela internet.

  • events array | string corpo JSON

    Os eventos a receber, como array ou separados por vírgulas. Sem ele: todos. Veja a lista de eventos abaixo.

  • active boolean corpo JSON

    false para criá-lo pausado. Padrão true.

Requisição
curl -X POST https://pik.li/api/v1/webhooks \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/pikli","events":["link.created","link.disabled"]}'
Resposta · 201
{
  "id": 17,
  "url": "https://example.com/hooks/pikli",
  "events": ["link.created", "link.disabled"],
  "active": true,
  "failures_count": 0,
  "last_delivered_at": null,
  "created_at": "2026-09-10T11:20:33.004+02:00",
  "updated_at": "2026-09-10T11:20:33.004+02:00",
  "signature_header": "X-Pikli-Signature",
  "secret": "ec307f56cfac71856a10d641eee7dd58e0eb8286"
}
PATCH /webhooks/:id

Alterar um webhook

Envie só o que muda: endereço, eventos ou active, para pausar e retomar as entregas.

PUT também é aceito, com os mesmos parâmetros.

Parâmetros
  • id integer no caminho obrigatório

    O id do webhook.

  • url string corpo JSON

    Um novo endereço.

  • events array | string corpo JSON

    A nova lista de eventos; não pode ficar vazia.

  • active boolean corpo JSON

    false pausa as entregas, true as retoma.

Requisição
curl -X PATCH https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"active":false}'
Resposta · 200
{
  "id": 17,
  "url": "https://example.com/hooks/pikli",
  "events": ["link.created", "link.disabled"],
  "active": false,
  "failures_count": 0,
  "last_delivered_at": "2026-09-22T18:04:11.520+02:00",
  "created_at": "2026-09-10T11:20:33.004+02:00",
  "updated_at": "2026-09-23T10:15:42.118+02:00",
  "signature_header": "X-Pikli-Signature"
}
DELETE /webhooks/:id

Excluir um webhook

As entregas param, e o histórico delas é excluído junto.

Parâmetros
  • id integer no caminho obrigatório

    O id do webhook.

Requisição
curl -X DELETE https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY"
Resposta · 204
Sem corpo: o status já diz tudo.
POST /webhooks/:id/test

Enviar uma entrega de teste

Coloca na fila um ping para o endereço do webhook, assinado como qualquer entrega: o jeito mais rápido de testar o seu código de verificação da assinatura. Ele sai logo depois e aparece em GET /webhooks/:id.

Parâmetros
  • id integer no caminho obrigatório

    O id do webhook.

Requisição
curl -X POST https://pik.li/api/v1/webhooks/17/test \
  -H "Authorization: Bearer $PIKLI_KEY"
Resposta · 202
{
  "queued": true,
  "event": "ping",
  "webhook_id": 17
}

WebhooksPlanos: Premium · Business

Um webhook é um endereço no seu servidor que o pik.li chama com um POST quando algo acontece na sua conta. Você cria um na página Webhooks do painel ou pelos endpoints de webhooks.

Eventos

ping
A entrega de teste, enviada quando você clica em Testar no painel ou chama POST /webhooks/:id/test.
link.created
Um link foi criado, pelo painel ou pela API. em breve
link.clicked
Alguém clicou em um dos seus links. em breve
link.disabled
Um link foi desativado, por você ou pela moderação. em breve
link.deleted
Um link foi excluído. em breve
Os eventos de link já podem ser escolhidos em um webhook, mas as entregas deles ainda não começaram: por enquanto, o pik.li envia só o ping de teste.

Como é uma entrega

Um POST com um corpo JSON de três campos: event, o nome do evento; created_at, quando foi enviado; data, os detalhes do evento. Junto vêm estes cabeçalhos:

O que o seu servidor recebe
POST /hooks/pikli HTTP/1.1
Content-Type: application/json
X-Pikli-Event: ping
X-Pikli-Signature: sha256=bdaf35d42413498dab7fea8be1f292c36b690152e5ec7d66e6dd2c2cd0b2d61c

{"event":"ping","created_at":"2026-09-23T10:15:42+02:00","data":{"message":"pik.li webhook test","at":"2026-09-23T10:15:42+02:00","via":"api"}}
Content-Type
sempre application/json.
X-Pikli-Event
o nome do evento, o mesmo do corpo: encaminhe a requisição antes de lê-la.
X-Pikli-Signature
sha256= seguido da assinatura do corpo, como descrito abaixo.

Verificar a assinatura

Cada entrega é assinada com o segredo do webhook, que você recebe uma única vez, ao criá-lo. O cabeçalho X-Pikli-Signature contém sha256= seguido do HMAC-SHA256 do corpo bruto, em hexadecimal. Calcule o mesmo HMAC sobre os bytes recebidos — antes de convertê-los em JSON, o que os alteraria — e compare as duas strings em tempo constante. Se forem diferentes, responda 401 e ignore a requisição.

Para testar o seu código: com o segredo ec307f56cfac71856a10d641eee7dd58e0eb8286 do exemplo acima, o corpo da entrega mostrada aqui gera exatamente a assinatura que aparece no cabeçalho dela.

Node.js
import crypto from "node:crypto";
import express from "express";

const app = express();

app.post("/hooks/pikli", express.raw({ type: "application/json" }), (req, res) => {
  const expected = "sha256=" + crypto
    .createHmac("sha256", process.env.PIKLI_WEBHOOK_SECRET)
    .update(req.body)
    .digest("hex");
  const received = req.get("X-Pikli-Signature") || "";
  const valid = received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!valid) return res.sendStatus(401);

  const event = JSON.parse(req.body);
  res.sendStatus(204);
});
Python
import hashlib, hmac, os
from flask import Flask, abort, request

app = Flask(__name__)
SECRET = os.environ["PIKLI_WEBHOOK_SECRET"].encode()

@app.post("/hooks/pikli")
def pikli_hook():
    body = request.get_data()
    expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("X-Pikli-Signature", "")):
        abort(401)
    event = request.get_json()
    return "", 204
Ruby
class PikliHooksController < ActionController::API
  def create
    body = request.raw_post
    expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", ENV.fetch("PIKLI_WEBHOOK_SECRET"), body)
    received = request.headers["X-Pikli-Signature"].to_s
    return head(:unauthorized) unless ActiveSupport::SecurityUtils.secure_compare(expected, received)

    event = JSON.parse(body)
    head :no_content
  end
end
PHP
<?php
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $body, getenv('PIKLI_WEBHOOK_SECRET'));
$received = $_SERVER['HTTP_X_PIKLI_SIGNATURE'] ?? '';
if (!hash_equals($expected, $received)) {
    http_response_code(401);
    exit;
}
$event = json_decode($body, true);
http_response_code(204);

Respostas, novas tentativas e falhas

  • Responda com qualquer status 2xx o quanto antes e deixe o trabalho demorado para depois.
  • Se o pik.li não conseguir alcançar o seu endereço de jeito nenhum — erro de rede, conexão recusada, um endereço que não é público —, a entrega é tentada até 5 vezes, com pausas cada vez mais longas: a última tentativa acontece cerca de seis minutos depois da primeira.
  • Uma resposta com status 400 ou superior é registrada e não é repetida. Ela soma um ao failures_count do webhook, que volta a zero na primeira entrega bem-sucedida.
  • As últimas 20 entregas, com o status que o seu servidor respondeu, estão em GET /webhooks/:id e na página Webhooks do painel.
  • O endereço precisa ser público: o pik.li não chama redes privadas nem locais. Use https:// para que as entregas trafeguem criptografadas.

OpenAPI

A mesma referência, em formato legível por máquina, está publicada em /openapi.json (OpenAPI 3.1). Importe-a no Postman, Insomnia ou Bruno, ou gere um cliente com o openapi-generator.

Falta alguma coisa, ou algo não funciona como esta página diz? Escreva para [email protected].