Convenciones de la API
Paginación, filtros, expansión, límites, errores, idempotencia y versionado.
Todas las respuestas son JSON. Las listas comparten la misma forma y los objetos siempre traen un campo object para que puedas despachar por tipo sin adivinar.
Los nombres de campo van en snake_case, en la respuesta y en el cuerpo que mandás. Las fechas son ISO 8601 en UTC y los importes vienen en pesos argentinos, sin decimales de centavos escondidos.
Nunca mandes el id del workspace: lo deducimos de la API key. Un campo que no aplica llega como null, no ausente, así podés tipar la respuesta sin campos opcionales por todos lados.
{ "object": "list", "data": [ { "object": "contact", "id": "ct_juan", "name": "Juan Pérez" } ], "next_cursor": "ct_camila", "has_more": true}Paginación por cursor
Las listas devuelven hasta 25 elementos por defecto y 100 como máximo. Cuando has_more es true, pasá next_cursor en el parámetro cursor para pedir la página siguiente.
No uses offsets: el cursor es estable aunque se creen registros mientras recorrés.
| Campo | Tipo | Descripción |
|---|---|---|
limit | number | Elementos por página. Entre 1 y 100. Por defecto 25. |
cursor | string | next_cursor de la respuesta anterior. |
async function* allContacts(key) { let cursor; do { const url = new URL("https://api.tinkay.app/v1/contacts"); url.searchParams.set("limit", "100"); if (cursor) url.searchParams.set("cursor", cursor); const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } }); const page = await res.json(); yield* page.data; cursor = page.has_more ? page.next_cursor : null; } while (cursor);}Filtros y búsqueda
Cada recurso documenta sus filtros. Los valores múltiples se separan con coma y se combinan con o; filtros distintos se combinan con y.
Para buscar texto libre en varios recursos a la vez usá GET /v1/search.
# Conversaciones abiertas o esperando, del equipo de soportecurl "https://api.tinkay.app/v1/conversations?status=open,waiting&team_id=team_soporte" \ -H "Authorization: Bearer dk_live_xxx"Expandir relaciones
Evitá pedidos en cascada con expand. Cada recurso lista qué relaciones acepta.
curl "https://api.tinkay.app/v1/conversations/cv_1832?expand=contact,assignee,messages" \ -H "Authorization: Bearer dk_live_xxx"Cada relación expandida cuenta como un pedido para el límite de tasa. Expandí solo lo que vas a usar.
Límites de tasa
600 pedidos por minuto por API key. Cada respuesta trae el estado del límite en los headers.
Al llegar al límite devolvemos 429 con Retry-After en segundos. Esperá ese tiempo y reintentá con espera exponencial y jitter.
| Campo | Tipo | Descripción |
|---|---|---|
X-RateLimit-Limit | number | Pedidos permitidos en la ventana. |
X-RateLimit-Remaining | number | Pedidos que te quedan. |
X-RateLimit-Reset | number | Epoch en segundos en que se reinicia la ventana. |
Retry-After | number | Solo en 429: segundos a esperar. |
async function request(url, init, attempt = 0) { const res = await fetch(url, init); if (res.status !== 429 || attempt >= 5) return res; const wait = Number(res.headers.get("Retry-After") ?? 1) * 1000; const jitter = Math.random() * 250; await new Promise((r) => setTimeout(r, wait * 2 ** attempt + jitter)); return request(url, init, attempt + 1);}Errores
Los errores traen un objeto error con un code estable para programar contra él y un message en español para mostrar o registrar.
Códigos frecuentes
| Campo | Tipo | Descripción |
|---|---|---|
unauthorized | 401 | Falta el header o la key no es válida. |
forbidden | 403 | La key no tiene el permiso necesario. |
not_found | 404 | El objeto no existe en este workspace. |
validation_error | 422 | El cuerpo no cumple el esquema. Mirá details. |
rate_limited | 429 | Superaste el límite de tasa. |
{ "error": { "code": "validation_error", "message": "Hay campos inválidos.", "details": [ { "path": ["email"], "message": "Formato de email inválido" } ] }}Idempotencia
Todos los POST aceptan el header Idempotency-Key. Si repetís un pedido con la misma clave dentro de 24 horas devolvemos la respuesta original sin volver a crear nada.
Usá un identificador único por operación de negocio, por ejemplo el id del pedido de tu sistema.
curl -X POST https://api.tinkay.app/v1/tickets \ -H "Authorization: Bearer dk_live_xxx" \ -H "Idempotency-Key: order-88213-refund" \ -H "Content-Type: application/json" \ -d '{ "subject": "Reintegro pedido 88213", "contact_email": "[email protected]" }'Versionado
La versión vive en la URL (/v1) y el formato de los eventos en api_version.
Agregar campos o recursos no rompe. Los cambios que rompen se publican como versión nueva, con seis meses de convivencia.
Sandbox y producción
Las keys con prefijo dk_test_ operan sobre datos de prueba y no envían mensajes reales ni disparan webhooks a producción.
Las keys dk_live_ trabajan sobre datos reales. Nunca las uses en el navegador ni las subas al repositorio.
Si una key se filtró, revocala desde Configuración, Desarrolladores. La revocación es inmediata.
