Referencia de la API

API REST
Construye sobre tus enlaces.

Autentícate con un token de API, lista y crea enlaces, lee visitas y suscríbete a webhooks.

URL base https://letl.ink/api/v1 Solo HTTPS

Planes Growth Ultimate
En esta página

Primeros pasos

La API REST está disponible en Growth y Ultimate. Las solicitudes van a https://letl.ink/api/v1 por HTTPS. Un espacio de trabajo en Free o Solo recibe 403 plan_required de /api/v1; el endpoint MCP se restringe por separado a Ultimate.

  1. Abre Integraciones → API REST (solo propietarios y administradores del espacio de trabajo).
  2. Genera un token. No caduca.
  3. Guárdalo como una contraseña. Un solo token sirve para la API REST, el servidor MCP y todas las plataformas de automatización conectadas a este espacio de trabajo, así que regenerarlo los invalida todos a la vez.

Los miembros pueden copiar un token existente si ya se generó; solo los propietarios y administradores pueden rotarlo.

Autenticación

Envía el token como X-API-KEY o como Authorization: Bearer. Ambos se aceptan en /api/v1 y en /mcp. Una solicitud sin ningún token devuelve 401 unauthenticated.

GET ¿Quién soy?
curl -H "X-API-KEY: YOUR_TOKEN" \
  https://letl.ink/api/v1/auth/me

Encabezado equivalente: Authorization: Bearer YOUR_TOKEN

Convenciones

Se aplican a todos los endpoints de abajo.

Estructura de la respuesta

Cada respuesta correcta es un objeto JSON con una clave data, y una clave meta cuando hay algo que decir sobre el resultado: paginación, el periodo de fechas aplicado o si se incluyó la geografía. Cada error es un objeto con una clave error que contiene un code estable, un message legible y, a veces, details. Toma decisiones según code, nunca según el texto del mensaje.

Formas
// success
{ "data": { … }, "meta": { … } }

// failure
{ "error": { "code": "invalid_parameter",
             "message": "sort must be one of: created_at, clicks.",
             "details": { "parameter": "sort" } } }

Paginación

Los endpoints de listas aceptan limit (1–200, 50 por defecto) y offset. El bloque meta indica total, has_more y next_offset, para que puedas paginar sin hacer cálculos: sigue llamando con offset = meta.next_offset hasta que has_more sea false.

Recorrer todos los enlaces página a página
offset=0
while [ "$offset" != "null" ]; do
  page=$(curl -s -H "X-API-KEY: $TOKEN" \
    "https://letl.ink/api/v1/links?limit=200&offset=$offset")
  echo "$page" | jq -c '.data[]'
  offset=$(echo "$page" | jq -r '.meta.next_offset')
done

Ordenación, filtrado y búsqueda

GET /links acepta sort (created_at, clicks, short_code, title, updated_at), direction (asc/desc), search (busca en el código corto, el título y el destino), type, archived, favorite y domain. Los valores desconocidos se rechazan con invalid_parameter y la lista de valores permitidos, en lugar de ignorarse en silencio.

Periodos de fechas

start_date y end_date usan YYYY-MM-DD e incluyen ambos días. El periodo se ajusta al límite de retención de tu plan: si pides un año de historial en un plan con 30 días de retención, obtienes los 30 días disponibles, y meta.date_range te indica el periodo realmente usado.

Límites de uso

600 solicitudes por minuto por cuenta, en una ventana deslizante. Cada respuesta incluye X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset (una marca de tiempo Unix). Si superas el límite, se devuelve 429 rate_limited con un encabezado Retry-After en segundos. Espera lo que indique Retry-After en lugar de reintentar de inmediato.

Encabezados en cada respuesta
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 586
X-RateLimit-Reset: 1789000816

Control de versiones

La ruta incluye la versión. Agregar un campo a una respuesta o un nuevo parámetro opcional no es un cambio incompatible y puede ocurrir en cualquier momento, así que analiza las respuestas con cuidado e ignora las claves que no reconozcas. Todo lo que cambie o elimine un campo existente se publica con un nuevo prefijo de versión.

Endpoints

Cuenta

GET /me Prueba de conexión

Identidad, espacio de trabajo, plan y todos los derechos que incluye ese plan. La forma más económica de verificar un token y descubrir qué puede hacer la cuenta antes de intentarlo.

GET cURL
curl -H "X-API-KEY: YOUR_TOKEN" \
  https://letl.ink/api/v1/me
Respuesta 200
{
  "data": {
    "user": { "id": 1, "email": "[email protected]", "name": "You" },
    "workspace": { "id": 12, "name": "My Workspace", "block_bots": true },
    "plan": {
      "id": "growth",
      "name": "Growth",
      "limits": {
        "links": 200,
        "monthly_clicks": 100000,
        "custom_domains": 3,
        "qr_codes": 30,
        "retention_days": 365
      },
      "features": {
        "api": true,
        "webhooks": true,
        "integrations": true,
        "ai_access": false,
        "geo_analytics": false,
        "smart_link_types": true
      }
    },
    "base_domain": "letl.ink"
  }
}
GET /usage

Cuánto se ha consumido de cada cuota en este mes de facturación. Úsalo para avisar a un cliente antes de que alcance el límite de clics registrados: al superarlo, la redirección sigue funcionando, pero los clics dejan de registrarse.

Respuesta 200
{
  "data": {
    "links": { "used": 42, "limit": 200, "remaining": 158, "percent_used": 21.0 },
    "clicks_this_month": { "used": 8140, "limit": 100000, "remaining": 91860, "percent_used": 8.1 },
    "custom_domains": { "used": 1, "limit": 3, "remaining": 2, "percent_used": 33.3 },
    "qr_codes": { "used": 4, "limit": 30, "remaining": 26, "percent_used": 13.3 }
  },
  "meta": { "plan": "growth", "period": "2026-09" }
}
GET /domains

Los dominios personalizados del espacio de trabajo y si cada uno terminó la verificación DNS. Solo los dominios verificados se pueden usar en el campo domain al crear un enlace.

GET /integrations

Todas las integraciones que ofrece el producto, si tu plan la incluye, si este espacio de trabajo la conectó y cómo van las entregas: último estado, último error y número de fallos consecutivos. Útil para supervisar muchos espacios de trabajo.

Enlaces

Analíticas

GET /analytics/summary

Las cifras principales de todo el espacio de trabajo: clics totales y en el periodo, visitantes únicos, escaneos QR, día pico y distribuciones por hora del día y día de la semana. Acepta days (30 por defecto, ajustado a tu retención).

Respuesta 200 (resumido)
{
  "data": {
    "window_days": 30,
    "clicks": {
      "total": 24592, "window": 8140, "last_7_days": 2103,
      "daily_average": 271,
      "peak_day": { "date": "Sep 02", "clicks": 612 }
    },
    "visitors": { "unique_total": 18204, "unique_window": 6021 },
    "qr": { "scans_total": 940, "scans_window": 310 },
    "timing": {
      "by_hour": [12, 4, 2, …],
      "by_weekday": [1420, 1610, 1585, 1490, 1310, 480, 245],
      "peak_hour": { "peakHour": 14, "peakValue": 812, "label": "14:00–14:59" },
      "peak_weekday": { "peakDay": 1, "peakValue": 1610, "label": "Tuesday" }
    },
    "links": { "active": 42, "limit": 200 }
  },
  "meta": { "plan": "growth", "geo_included": true, "retention_days": 365 }
}
GET /analytics/timeseries

Número de clics diarios, listos para graficar. Pasa link_id para limitarlo a un enlace y days para definir el periodo. Los días sin tráfico se devuelven como cero en lugar de omitirse, así que la serie siempre es continua.

Respuesta 200
{
  "data": [
    { "date": "Sep 04", "clicks": 22 },
    { "date": "Sep 05", "clicks": 11 }
  ],
  "meta": { "window_days": 7, "total": 33, "link_id": null }
}
GET /analytics/breakdown

Clics agrupados por una dimensión, con la proporción del total de cada fila.

Parámetros de consulta

  • dimension string

    channel, referrer, device, browser, os, brand, variation o country. browser, os, brand y variation requieren un link_id.

    Obligatorio
  • link_id integer

    Limita a un enlace. Omítelo para todo el espacio de trabajo.

    Opcional
  • limit integer

    1–100. Por defecto, 10.

    Opcional
Respuesta 200
{
  "data": [
    { "label": "Search", "value": 215, "share": 33.1 },
    { "label": "Social", "value": 201, "share": 30.9 },
    { "label": "Direct / none", "value": 142, "share": 21.8 }
  ],
  "meta": { "dimension": "channel", "link_id": 49, "total": 650 }
}
GET /events Paginado

El flujo de visitas en bruto de todos los enlaces del espacio de trabajo, de la más reciente a la más antigua. Es el reemplazo moderno de GET /visits: los mismos datos, el envoltorio estándar y compatibilidad con limit y offset.

Respuesta 200
{
  "data": [
    {
      "id": 2691,
      "link_id": 49,
      "short_code": "spring",
      "occurred_at": "2026-09-10T22:45:33+00:00",
      "ip_address": "203.0.113.7",
      "referrer": "https://twitter.com/",
      "browser": "Chrome",
      "os": "iOS",
      "device_type": "mobile",
      "device_brand": "Apple",
      "country": "US",
      "variation": "control"
    }
  ],
  "meta": { "limit": 50, "returned": 50, "geo_included": true }
}

Webhooks

GET /hooks

Endpoints de webhook registrados en esta cuenta.

POST /hooks

Registra un endpoint HTTPS para recibir un payload JSON con cada clic registrado. Hasta 25 por cuenta. Las entregas fallidas se reintentan automáticamente.

Cuerpo JSON

  • target_url

    Debe ser https://.

    Obligatorio
  • event

    Por defecto, link_visited, que actualmente es el único evento.

    Opcional
POST cURL
curl -X POST https://letl.ink/api/v1/hooks \
  -H "X-API-KEY: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"target_url": "https://example.com/hooks/clicks"}'
DELETE /hooks/{id}

Elimina un endpoint de webhook. POST /hooks/subscribe y DELETE /hooks/unsubscribe/{id} siguen disponibles para el flujo de REST Hooks de Zapier.

Heredado

GET /auth/me Heredado

La prueba de conexión original: devuelve tu id de usuario, correo y el id/nombre del espacio de trabajo como un objeto plano. GET /me devuelve la misma identidad más los límites de tu plan y los indicadores de funciones, y es la mejor opción para código nuevo.

GET cURL
curl -H "X-API-KEY: YOUR_TOKEN" \
  https://letl.ink/api/v1/auth/me
Respuesta 200
{
  "id": 1,
  "email": "[email protected]",
  "workspace_id": 12,
  "workspace_name": "My Workspace"
}
GET /visits Heredado

Las 50 visitas más recientes de todo el espacio de trabajo como un array JSON sin envoltorio, con los nombres de campo visit_id, visit_time, link_shortcode y visitor_*. Es lo que lee el paso "perform list" de los REST Hooks de Zapier, así que su estructura está congelada. Usa GET /events para desarrollos nuevos.

POST /hooks/subscribe Zapier

Suscripción de REST Hook. Recibe target_url y un event opcional (por defecto link_visited) y devuelve la suscripción creada tal cual, sin envoltorio. POST /hooks es la misma operación con la estructura estándar.

DELETE /hooks/unsubscribe/{id} Zapier

Cancelación de la suscripción de REST Hook, que devuelve {"success": true}. DELETE /hooks/{id} es el equivalente con la estructura estándar.

Errores

  • 400 invalid_request

    El cuerpo no era un objeto JSON o faltaba un campo obligatorio.

  • 400 invalid_parameter

    Un parámetro de consulta tenía un formato incorrecto o estaba fuera de los valores permitidos. details.parameter indica cuál.

  • 401 unauthenticated

    No se envió ningún token. Agrega X-API-KEY o Authorization: Bearer.

  • 401 invalid_token

    El token no corresponde a ninguna cuenta. Puede que se haya regenerado.

  • 403 plan_required

    El plan de la cuenta no incluye este endpoint o función. details.upgrade_url apunta a la facturación.

  • 403 forbidden

    Autenticado, pero sin permiso; por ejemplo, un miembro que intenta una acción exclusiva del propietario.

  • 404 not_found

    No existe ese recurso en este espacio de trabajo. Los id de otro espacio de trabajo se indican como no encontrados en lugar de prohibidos.

  • 409 conflict

    La solicitud entra en conflicto con el estado actual: se alcanzó el límite de webhooks o la cuenta todavía no tiene espacio de trabajo.

  • 422

    Lo devuelve POST /links/bulk cuando fallan todas las filas del lote. Revisa meta.errors.

  • 429 rate_limited

    Más de 600 solicitudes en el último minuto. Espera los segundos indicados en Retry-After.

Los endpoints heredados GET /visits y GET /links/{id}/visits devuelven un array JSON sin envoltorio con sus propios nombres de campo y están congelados para las integraciones de Zapier que ya los usan. El código nuevo debe usar GET /events y GET /links/{id}/events, que usan el envoltorio estándar y admiten paginación.

¿Todavía tienes preguntas?

Estamos aquí para ayudarte. Escríbenos y te responderemos lo antes posible.