pik.li pik.li
Versión 1 · estable

API de pik.li

Crea, edita y mide tus enlaces cortos desde tu propio código. Una API REST que recibe y devuelve JSON, una clave por integración y cada endpoint documentado aquí con una solicitud y una respuesta real.

Obtén una clave API OpenAPI 3.1
URL basehttps://pik.li/api/v1

Introducción

La API de pik.li permite que tu propio software haga lo mismo que el panel: crear y editar enlaces cortos, consultar sus estadísticas, listar tus etiquetas y dominios y gestionar webhooks. Funciona por HTTPS y todas las rutas empiezan por la URL base que ves arriba.

  • Envía el cuerpo de las solicitudes en JSON con Content-Type: application/json; los parámetros de las solicitudes GET van en la cadena de consulta. Las respuestas son siempre JSON en UTF-8, salvo el código QR, que es una imagen.
  • La versión forma parte de la ruta (/api/v1) y cada respuesta lleva la cabecera X-Api-Version: 1. Con el tiempo pueden aparecer campos nuevos en las respuestas de v1: haz que tu código ignore los que no conozca.
  • Las fechas y horas son cadenas ISO 8601 con su desfase horario (2026-09-23T10:15:42.118+02:00); las fechas solas tienen la forma 2026-09-23.
  • Los identificadores son números enteros. Un enlace también se puede encontrar a partir de su URL corta con GET /links/lookup.

Inicio rápido

  1. Crea una clave en el panel (consulta Crear una clave) y guárdala en una variable de entorno.
  2. Comprueba que funciona: GET /me responde con tu cuenta y tu plan.
  3. Crea tu primer enlace corto con 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"}'

Autenticación

Todas las llamadas, salvo GET /ping, necesitan una clave API, enviada en la cabecera Authorization como token bearer.

Cabecera
Authorization: Bearer pk_live_3kZ9vQeX1mT0bR7yNw2aLp4sUc8dHf6J

También sirve la cabecera X-API-Key. El parámetro de consulta api_key se sigue aceptando para clientes antiguos, pero evítalo: las direcciones acaban en los logs y en el historial del navegador.

Las claves empiezan por pk_live_, seguido de 32 caracteres. Una clave ve exactamente lo que ve su cuenta —sus enlaces, dominios, etiquetas, estadísticas y webhooks— y todas las claves de una cuenta pueden hacer lo mismo: no hay claves de solo lectura.

Trata una clave como una contraseña. Guárdala en tu servidor, nunca en código que se ejecute en un navegador ni en una app que distribuyas. Si se filtra, revócala y crea otra: se tarda diez segundos.

Crear una clave

  1. Inicia sesión y abre API y claves en el panel.
  2. Ponle a la clave un nombre que te diga dónde se usa (por ejemplo CRM producción) y pulsa Generar clave.
  3. Copia la clave enseguida: el panel solo la muestra una vez.
  4. Puedes tener hasta 10 claves activas. Para cada una, la página muestra cuándo se usó por última vez y cuántas solicitudes ha hecho, y te permite revocarla: a partir de la siguiente llamada, una clave revocada recibe 401 unauthorized.

Quién puede usar la API

La API viene con los planes de pago (Premium y Business). El plan Base no la incluye, salvo que el equipo de pik.li la active para el plan gratuito o para tu cuenta. En cualquier caso, la cuenta necesita una dirección de correo confirmada y no puede estar bloqueada ni suspendida.

Si falta alguna de estas condiciones, la API responde api_disabled, email_unconfirmed o account_blocked.

Base

Solo si el equipo la activa
Solicitudes por minuto
60
Enlaces nuevos
3 al día
Enlaces por llamada masiva
10
Historial de estadísticas
90 días
Webhooks
No

Premium

API incluida
Solicitudes por minuto
600
Enlaces nuevos
2.500 al mes
Enlaces por llamada masiva
100
Historial de estadísticas
730 días
Webhooks

Business

API incluida
Solicitudes por minuto
3.000
Enlaces nuevos
10.000 al mes
Enlaces por llamada masiva
100
Historial de estadísticas
1.095 días
Webhooks

Son las cifras estándar de cada plan, leídas en tiempo real de la configuración de precios. Tus propias cifras, con cualquier cambio que el equipo haya hecho en tu cuenta, las devuelven GET /limits y GET /me.

Límites de solicitudes

Cada clave puede hacer un número de solicitudes por minuto que depende del plan (consulta la tabla de arriba). El recuento vuelve a empezar al comienzo de cada minuto.

X-RateLimit-Limit
solicitudes permitidas por minuto a esta clave
X-RateLimit-Remaining
solicitudes que quedan en el minuto en curso
X-RateLimit-Reset
cuándo vuelve a empezar el recuento, como tiempo Unix en segundos
Retry-After
solo en una respuesta 429: cuántos segundos hay que esperar

Si superas el límite, la API responde 429 con el error rate_limited y una cabecera Retry-After. Espera ese tiempo y continúa: reintentar enseguida solo le resta solicitudes al minuto siguiente.

Respuesta
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"
}
  • Las llamadas sin clave están limitadas a 30 por minuto por cada dirección IP (GET /ping no cuenta).
  • Además, cada dirección IP tiene un techo de 600 solicitudes por minuto a pik.li en su conjunto, sea cual sea el plan.

Errores

Una solicitud que falla recibe siempre una respuesta con la misma forma, sea cual sea el endpoint, y un código de estado HTTP que corresponde al problema.

Respuesta
{
  "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 estable, pensado para tu código. Nunca cambia y nunca se traduce.
message
Una frase para personas, en el idioma de la solicitud (consulta «Idiomas»).
details
Datos estructurados cuando son útiles: los campos no válidos, los valores permitidos, las cifras de una cuota. No siempre está presente.
docs_url
El enlace a la explicación de este código, en esta página.
request_id
El identificador de la solicitud, enviado también en la cabecera X-Request-Id. Indícalo cuando escribas al soporte.

Todos los endpoints que necesitan clave pueden responder además unauthorized account_blocked email_unconfirmed api_disabled rate_limited: no se repiten en cada endpoint.

unauthorized401
La clave falta, es incorrecta o ha sido revocada. Revisa la cabecera Authorization y comprueba que la clave sigue apareciendo como activa en el panel.
account_blocked403
La cuenta está bloqueada, suspendida o desactivada. La API permanece cerrada hasta que la cuenta vuelva a estar activa.
email_unconfirmed403
La dirección de correo de la cuenta aún no se ha confirmado. Sigue el enlace del correo de confirmación y vuelve a intentarlo.
api_disabled403
La API no está incluida en el plan de esta cuenta, o se ha desactivado para ella. Consulta «Quién puede usar la API».
forbidden403
La clave no puede realizar esta acción. reservado
feature_required402
El plan no incluye la función que necesita este endpoint. details.feature indica cuál es y details.plan, el primer plan que la tiene.
quota_exceeded402
Se ha agotado la cuota de enlaces del día o del mes. details contiene limit, used, period y resets_at, el momento en que se podrán volver a crear enlaces.
not_found404
El recurso no existe, se ha eliminado o pertenece a otra cuenta.
conflict409
La solicitud entra en conflicto con el estado actual del recurso. reservado
invalid422
Algunos datos no han superado la validación: details.fields enumera los problemas campo por campo y details.messages los da como frases ya redactadas.
domain_not_allowed422
Tu cuenta no puede usar este dominio. GET /domains lista los que sí puede usar.
bad_request400
Falta un parámetro obligatorio; details.parameter indica cuál.
invalid_json400
El cuerpo no es JSON válido. Revisa las comillas y la cabecera Content-Type.
invalid_parameter400
Un parámetro tiene un valor que el endpoint no reconoce. El mensaje indica el parámetro y details.allowed, cuando está presente, enumera los valores aceptados.
bulk_empty400
El array links de una llamada masiva está vacío o falta.
bulk_too_many400
Demasiados enlaces en una sola llamada masiva. details.max es el límite de tu plan: divide la lista en varias llamadas.
idempotency_key_invalid400
La cabecera Idempotency-Key tiene más de 128 caracteres.
rate_limited429
Demasiadas solicitudes en este minuto. Espera los segundos que indica Retry-After y continúa.

Una respuesta 500 significa que algo ha fallado por nuestra parte y no hay nada que corregir en tu solicitud. Vuelve a intentarlo un poco más tarde; si sigue pasando, escribe al soporte con el valor de la cabecera X-Request-Id.

Paginación

Las listas que pueden crecer mucho se dividen en páginas: GET /links, GET /links/active y GET /links/:id/clicks.

  • page: la página que quieres, empezando por 1.
  • per_page: cuántos elementos por página, de 1 a 100 (por defecto 50). limit se acepta como sinónimo.

La respuesta incluye un objeto pagination, y el total también está en la cabecera X-Total-Count. Sigue pidiendo next_page hasta que sea null.

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

Idempotencia

POST /links, POST /links/bulk y POST /links/generate aceptan una cabecera Idempotency-Key: cualquier texto de hasta 128 caracteres que elijas tú, uno por operación, como un número de pedido o un UUID.

Cuando la misma clave de idempotencia vuelve a llegar con la misma clave API en menos de 24 horas, pik.li no crea nada: devuelve la primera respuesta, con la cabecera Idempotent-Replayed: true. Una solicitud repetida tras un timeout nunca genera un duplicado.

Misma solicitud, misma clave, en menos de 24 horas
HTTP/1.1 201 Created
Idempotent-Replayed: true
Content-Type: application/json; charset=utf-8

Las respuestas con un estado 5xx no se guardan, así que repetir la solicitud con la misma clave de idempotencia la ejecuta de nuevo. Una clave demasiado larga recibe idempotency_key_invalid.

Idiomas

Los mensajes de error están escritos en nueve idiomas: en it zh ar ru fr de es pt-BR. La API elige a partir del parámetro locale, luego de la cabecera Accept-Language, luego del idioma de tu cuenta y, si no, usa el inglés. Solo cambia el mensaje: los códigos y los nombres de los campos no cambian, y tampoco docs_url, que abre esta página en el mismo 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"
}

Ventanas de estadísticas

Los endpoints de estadísticas leen una ventana de tiempo y la comparan con la ventana inmediatamente anterior, de la misma duración: de ahí salen previous y delta_pct.

period
24h, 7d, 30d, 90d, 1y o all (todo el historial que conserva tu plan). También sirve un número de días: period=45, o el antiguo days=45. El valor por defecto es 7d para la cuenta, 30d para un enlace y all para la lista de clics.
from, to
En lugar de period: una fecha (2026-09-01) o una hora ISO 8601. Por defecto, to es el momento actual y from, 30 días antes de to; una fecha en to cuenta hasta el final de ese día.
interval
hour o day, el paso de la serie temporal. Por defecto, las ventanas de hasta 48 horas van por horas y las más largas, por días; una serie por horas abarca como máximo 7 días.
window.clamped
Una ventana nunca llega más atrás que el historial que conserva tu plan (retention_days). Cuando se ha recortado, window.clamped es true.
bots
Los clics de bots quedan excluidos. Añade bots=1 para contarlos también; las estadísticas de un enlace los indican además por separado en period.bots.

Endpoints

Cada endpoint con sus parámetros, una solicitud que puedes pegar en un terminal y una respuesta real, abreviada cuando la lista sería larga. Los ejemplos leen la clave de $PIKLI_KEY, como en el inicio rápido.

Servicio

Si la API está activa y cuánto te queda del plan.

GET /ping Sin clave

Comprobar que la API está activa

No necesita clave: está pensado para comprobaciones de estado. Devuelve la hora del servidor y las direcciones de esta documentación y del archivo OpenAPI.

Solicitud
curl https://pik.li/api/v1/ping
Respuesta · 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"
}
Errores Ninguno: este endpoint no necesita clave.
GET /limits

Lo que queda de tus cuotas

Enlaces aún disponibles hoy o este mes, clics registrados, la ventana del límite de solicitudes en curso, claves y dominios personalizados en uso, y el tamaño máximo de una llamada masiva. Cada cifra incluye los cambios que el equipo haya podido hacer en tu cuenta.

Solicitud
curl https://pik.li/api/v1/limits \
  -H "Authorization: Bearer $PIKLI_KEY"
Respuesta · 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"]
}
Errores Solo los que puede devolver cualquier endpoint.

Cuenta

Quién llama: la cuenta a la que pertenece la clave.

GET /me

Tu cuenta y la clave en uso

Perfil, plan y su vencimiento, funciones, límites y uso, y la clave que ha hecho la llamada, con su contador de solicitudes.

Solicitud
curl https://pik.li/api/v1/me \
  -H "Authorization: Bearer $PIKLI_KEY"
Respuesta · 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"
}
Errores Solo los que puede devolver cualquier endpoint.

Estadísticas de la cuenta

Las cifras de todos tus enlaces juntos, con los filtros del panel: ventana, dominio, etiqueta, bots. Consulta «Ventanas de estadísticas» para los parámetros comunes.

GET /stats

Estadísticas de la cuenta en una sola llamada

Totales de la cuenta, la ventana comparada con la anterior, la serie temporal, los 10 principales referentes y países, los 6 principales dispositivos, navegadores y sistemas operativos, y los 10 enlaces más clicados. Los demás endpoints /stats devuelven cada parte por separado, con listas más largas.

Parámetros
  • period string cadena de consulta

    La ventana: 24h, 7d (por defecto), 30d, 90d, 1y, all. Consulta «Ventanas de estadísticas».

  • from string cadena de consulta

    Inicio de la ventana, en lugar de period: una fecha o una hora ISO 8601.

  • to string cadena de consulta

    Fin de la ventana (por defecto, el momento actual).

  • interval string cadena de consulta

    hour o day, el paso de la serie temporal.

  • domain string cadena de consulta

    Solo este dominio, por nombre de host (por ejemplo lnkz.li). Tiene que ser uno de los tuyos: consulta GET /domains.

  • tag string cadena de consulta

    Solo los enlaces con esta etiqueta.

  • bots string cadena de consulta

    1 para contar también los clics de bots.

Solicitud
curl "https://pik.li/api/v1/stats?period=7d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Respuesta · 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

Clics a lo largo del tiempo

Clics y visitantes únicos por hora o por día, con el total de la ventana anterior y la variación en porcentaje.

Parámetros
  • period string cadena de consulta

    La ventana: 24h, 7d (por defecto), 30d, 90d, 1y, all. Consulta «Ventanas de estadísticas».

  • from string cadena de consulta

    Inicio de la ventana, en lugar de period: una fecha o una hora ISO 8601.

  • to string cadena de consulta

    Fin de la ventana (por defecto, el momento actual).

  • interval string cadena de consulta

    hour o day, el paso de la serie temporal.

  • domain string cadena de consulta

    Solo este dominio, por nombre de host (por ejemplo lnkz.li). Tiene que ser uno de los tuyos: consulta GET /domains.

  • tag string cadena de consulta

    Solo los enlaces con esta etiqueta.

  • bots string cadena de consulta

    1 para contar también los clics de bots.

Solicitud
curl "https://pik.li/api/v1/stats/timeseries?period=24h" \
  -H "Authorization: Bearer $PIKLI_KEY"
Respuesta · 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 dónde vienen los clics

Los sitios que han enviado más clics, cada uno con su porcentaje del total, y los clics sin referente (direct).

Parámetros
  • period string cadena de consulta

    La ventana: 24h, 7d (por defecto), 30d, 90d, 1y, all. Consulta «Ventanas de estadísticas».

  • from string cadena de consulta

    Inicio de la ventana, en lugar de period: una fecha o una hora ISO 8601.

  • to string cadena de consulta

    Fin de la ventana (por defecto, el momento actual).

  • domain string cadena de consulta

    Solo este dominio, por nombre de host (por ejemplo lnkz.li). Tiene que ser uno de los tuyos: consulta GET /domains.

  • tag string cadena de consulta

    Solo los enlaces con esta etiqueta.

  • bots string cadena de consulta

    1 para contar también los clics de bots.

  • limit integer cadena de consulta

    Cuántas filas por lista, de 1 a 50 (por defecto 10).

Solicitud
curl "https://pik.li/api/v1/stats/referrers?period=30d&limit=5" \
  -H "Authorization: Bearer $PIKLI_KEY"
Respuesta · 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 y ciudades

Los países como códigos ISO, con su porcentaje; cities se incluye con los planes que tienen estadísticas completas.

Parámetros
  • period string cadena de consulta

    La ventana: 24h, 7d (por defecto), 30d, 90d, 1y, all. Consulta «Ventanas de estadísticas».

  • from string cadena de consulta

    Inicio de la ventana, en lugar de period: una fecha o una hora ISO 8601.

  • to string cadena de consulta

    Fin de la ventana (por defecto, el momento actual).

  • domain string cadena de consulta

    Solo este dominio, por nombre de host (por ejemplo lnkz.li). Tiene que ser uno de los tuyos: consulta GET /domains.

  • tag string cadena de consulta

    Solo los enlaces con esta etiqueta.

  • bots string cadena de consulta

    1 para contar también los clics de bots.

  • limit integer cadena de consulta

    Cuántas filas por lista, de 1 a 50 (por defecto 10).

Solicitud
curl "https://pik.li/api/v1/stats/countries?period=30d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Respuesta · 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

Cuatro listas en una sola llamada, cada una con los clics y el porcentaje del total.

Parámetros
  • period string cadena de consulta

    La ventana: 24h, 7d (por defecto), 30d, 90d, 1y, all. Consulta «Ventanas de estadísticas».

  • from string cadena de consulta

    Inicio de la ventana, en lugar de period: una fecha o una hora ISO 8601.

  • to string cadena de consulta

    Fin de la ventana (por defecto, el momento actual).

  • domain string cadena de consulta

    Solo este dominio, por nombre de host (por ejemplo lnkz.li). Tiene que ser uno de los tuyos: consulta GET /domains.

  • tag string cadena de consulta

    Solo los enlaces con esta etiqueta.

  • bots string cadena de consulta

    1 para contar también los clics de bots.

  • limit integer cadena de consulta

    Cuántas filas por lista, de 1 a 50 (por defecto 10).

Solicitud
curl "https://pik.li/api/v1/stats/devices?period=30d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Respuesta · 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

Los enlaces más clicados

Los enlaces con más clics en la ventana, cada uno con clicks_in_window y uniques_in_window.

Parámetros
  • period string cadena de consulta

    La ventana: 24h, 7d (por defecto), 30d, 90d, 1y, all. Consulta «Ventanas de estadísticas».

  • from string cadena de consulta

    Inicio de la ventana, en lugar de period: una fecha o una hora ISO 8601.

  • to string cadena de consulta

    Fin de la ventana (por defecto, el momento actual).

  • domain string cadena de consulta

    Solo este dominio, por nombre de host (por ejemplo lnkz.li). Tiene que ser uno de los tuyos: consulta GET /domains.

  • tag string cadena de consulta

    Solo los enlaces con esta etiqueta.

  • bots string cadena de consulta

    1 para contar también los clics de bots.

  • limit integer cadena de consulta

    Cuántas filas por lista, de 1 a 50 (por defecto 10).

Solicitud
curl "https://pik.li/api/v1/stats/top?period=7d&limit=10" \
  -H "Authorization: Bearer $PIKLI_KEY"
Respuesta · 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
  }]
}

Etiquetas

Las etiquetas que pones a tus enlaces.

GET /tags

Tus etiquetas

Todas las etiquetas usadas en tus enlaces, con cuántos enlaces las llevan y cuántos clics han acumulado, primero las más usadas.

Parámetros
  • q string cadena de consulta

    Solo las etiquetas que contienen este texto.

Solicitud
curl https://pik.li/api/v1/tags \
  -H "Authorization: Bearer $PIKLI_KEY"
Respuesta · 200
{
  "tags": [
    { "name": "promo", "links": 214, "clicks": 90211 },
    { "name": "newsletter", "links": 58, "clicks": 20473 }
  ],
  "total": 2
}
Errores Solo los que puede devolver cualquier endpoint.

Dominios

Dónde pueden vivir tus enlaces cortos.

GET /domains

Los dominios que puedes usar

Los dominios de pik.li que permite tu plan y tus dominios personalizados verificados, cuál es el predeterminado y cuántos dominios personalizados permite tu plan. Pasa hostname como domain al crear un enlace.

Solicitud
curl https://pik.li/api/v1/domains \
  -H "Authorization: Bearer $PIKLI_KEY"
Respuesta · 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 }
}
Errores Solo los que puede devolver cualquier endpoint.

WebhooksPlanes: Premium · Business

Gestiona desde tu código los webhooks que, si no, configurarías en la página Webhooks del panel. Cómo funcionan las entregas se explica más abajo, en la sección «Webhooks».

GET /webhooks

Listar tus webhooks

Tus webhooks y los eventos a los que te puedes suscribir. El secreto no se vuelve a mostrar después de la creación.

Solicitud
curl https://pik.li/api/v1/webhooks \
  -H "Authorization: Bearer $PIKLI_KEY"
Respuesta · 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

Consultar un webhook

El webhook con sus 20 últimas entregas y el código de estado con el que respondió tu servidor a cada una.

Parámetros
  • id integer en la ruta obligatorio

    El id del webhook.

Solicitud
curl https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY"
Respuesta · 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

Crear un webhook

La respuesta incluye el secret con el que se firman las entregas, solo esta vez: guárdalo ahora.

Parámetros
  • url string cuerpo JSON obligatorio

    La dirección que recibe las entregas, http:// o https://, accesible desde internet.

  • events array | string cuerpo JSON

    Los eventos que quieres recibir, como array o separados por comas. Si no lo indicas: todos. Consulta la lista de eventos más abajo.

  • active boolean cuerpo JSON

    false para crearlo en pausa. Por defecto, true.

Solicitud
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"]}'
Respuesta · 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

Modificar un webhook

Envía solo lo que cambia: la dirección, los eventos o active para pausar y reanudar las entregas.

También se acepta PUT, con los mismos parámetros.

Parámetros
  • id integer en la ruta obligatorio

    El id del webhook.

  • url string cuerpo JSON

    Una dirección nueva.

  • events array | string cuerpo JSON

    La nueva lista de eventos; no puede estar vacía.

  • active boolean cuerpo JSON

    false pausa las entregas, true las reanuda.

Solicitud
curl -X PATCH https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"active":false}'
Respuesta · 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

Eliminar un webhook

Las entregas se detienen y su historial se elimina con él.

Parámetros
  • id integer en la ruta obligatorio

    El id del webhook.

Solicitud
curl -X DELETE https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY"
Respuesta · 204
Sin cuerpo: el código de estado lo dice todo.
POST /webhooks/:id/test

Enviar una entrega de prueba

Pone en cola un ping a la dirección del webhook, firmado como cualquier entrega: la forma más rápida de comprobar tu código de verificación de la firma. Sale poco después y aparece en GET /webhooks/:id.

Parámetros
  • id integer en la ruta obligatorio

    El id del webhook.

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

WebhooksPlanes: Premium · Business

Un webhook es una dirección de tu servidor a la que pik.li llama con un POST cuando pasa algo en tu cuenta. Se crea en la página Webhooks del panel o mediante los endpoints de webhooks.

Eventos

ping
La entrega de prueba, que se envía cuando pulsas «Probar» en el panel o llamas a POST /webhooks/:id/test.
link.created
Se ha creado un enlace, desde el panel o desde la API. próximamente
link.clicked
Alguien ha hecho clic en uno de tus enlaces. próximamente
link.disabled
Se ha desactivado un enlace, por ti o por la moderación. próximamente
link.deleted
Se ha eliminado un enlace. próximamente
Los eventos de enlaces ya se pueden elegir en un webhook, pero sus entregas todavía no han empezado: por ahora pik.li solo envía el ping de prueba.

Cómo es una entrega

Un POST con un cuerpo JSON de tres campos: event, el nombre del evento; created_at, cuándo se envió; data, los detalles del evento. Lo acompañan estas cabeceras:

Lo que recibe tu servidor
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
siempre application/json.
X-Pikli-Event
el nombre del evento, el mismo que en el cuerpo: te permite enrutar la solicitud antes de leerla.
X-Pikli-Signature
sha256= seguido de la firma del cuerpo, como se explica más abajo.

Verificar la firma

Cada entrega va firmada con el secreto de su webhook, que recibes una sola vez, al crearlo. La cabecera X-Pikli-Signature contiene sha256= seguido del HMAC-SHA256 del cuerpo sin procesar, en hexadecimal. Calcula el mismo HMAC sobre los bytes que has recibido —antes de convertirlos en JSON, porque eso los modificaría— y compara las dos cadenas en tiempo constante. Si no coinciden, responde 401 e ignora la solicitud.

Para probar tu código: con el secreto ec307f56cfac71856a10d641eee7dd58e0eb8286 del ejemplo de arriba, el cuerpo de la entrega que se muestra aquí produce exactamente la firma de su cabecera.

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);

Respuestas, reintentos y fallos

  • Responde con cualquier estado 2xx lo antes posible y deja el trabajo lento para después.
  • Si pik.li no consigue llegar a tu dirección —error de red, conexión rechazada, una dirección que no es pública—, la entrega se intenta hasta 5 veces, con pausas cada vez más largas: el último intento llega unos seis minutos después del primero.
  • Una respuesta con un estado 400 o superior se registra y no se repite. Suma uno al failures_count del webhook, que vuelve a cero con la primera entrega correcta.
  • Las 20 últimas entregas, con el estado con el que respondió tu servidor, están en GET /webhooks/:id y en la página Webhooks del panel.
  • La dirección debe ser pública: pik.li no llama a redes privadas ni locales. Usa https:// para que las entregas viajen cifradas.

OpenAPI

La misma referencia, en un formato legible por máquinas, está publicada en /openapi.json (OpenAPI 3.1). Impórtala en Postman, Insomnia o Bruno, o genera un cliente con openapi-generator.

¿Falta algo, o algo no funciona como dice esta página? Escribe a [email protected].