Skip to Content
Convenciones

Convenciones del API

Reglas transversales a todos los recursos del v1.

TemaRegla
FormatoJSON UTF-8. Los requests con cuerpo llevan Content-Type: application/json.
IDsUUID públicos. Nunca se exponen IDs numéricos internos.
FechasISO 8601. Instantes en UTC (2026-07-08T09:30:00Z); fechas de calendario YYYY-MM-DD.
Base URLVersionada en el path: https://api.kinmu.app/v1 · Dev: https://api.dev.kinmu.app/v1.

Paginación (cursor)

Todos los listados paginan por cursor:

  • ?limit= (default 25, máx 100) · ?cursor=<opaco>.
  • Respuesta: { "data": [...], "meta": { "next_cursor": "…"|null, "has_more": true|false } }.
  • Para la siguiente página, reenvía cursor=<meta.next_cursor>. has_more=false o next_cursor=null → fin.
curl "https://api.kinmu.app/v1/employees?limit=50&cursor=eyJpZCI6MTIzfQ" \ -H "Authorization: Bearer kinmu_sk_live_…"

El cursor es opaco: no lo construyas ni lo parsees, reenvíalo tal cual.

Sincronización incremental (updated_since)

El parámetro ?updated_since=<ISO 8601> trae solo lo cambiado desde ese instante (polling incremental para BI y ERPs). No es universal: solo filtra en estos listados.

Listado¿Filtra por updated_since?
GET /v1/employees
GET /v1/check-ins
GET /v1/absences
GET /v1/locations
GET /v1/units
GET /v1/webhook-endpoints
GET /v1/vacation-balancesNo — se acepta pero se ignora; obsoleto, se retira el 27-08-2027
GET /v1/work-summariesNo — se acepta pero se ignora; obsoleto, se retira el 27-08-2027
curl "https://api.kinmu.app/v1/employees?updated_since=2026-07-01T00:00:00Z" \ -H "Authorization: Bearer kinmu_sk_live_…"

Guarda el instante de tu última sincronización y úsalo como updated_since en la siguiente. Ver el patrón completo en la guía de BI.

vacation-balances y work-summaries no filtran por updated_since. Son agregados: no hay un delta que pedir. Vuelve a consultar el rango que te interesa (year= en saldos, from/to en resúmenes) o recárgalo entero en cada pasada, y haz upsert por clave en tu almacén. El parámetro se sigue aceptando en esos dos endpoints por compatibilidad con los SDK ya generados, pero no filtra nada: está marcado como obsoleto y se retirará el 27-08-2027.

Errores

Todos los errores son RFC 9457 (application/problem+json):

{ "type": "https://docs.kinmu.app/errors/invalid_scope", "title": "Scope insuficiente", "status": 403, "code": "invalid_scope", "detail": "La API key no tiene el scope requerido: org:empleados:read.", "errors": { "required_scope": "org:empleados:read" } }

Programa contra el campo code (estable), no contra title/detail.

Tabla de códigos

codeHTTPCuándo
unauthenticated401Falta la key, es inválida, revocada o expirada.
invalid_scope403La key no tiene el scope requerido (errors.required_scope).
subscription_inactive403La empresa no tiene servicio activo: suspendida o archivada, sin suscripción, suscripción suspendida, trial caducado, o suscripción cancelada y vencida o expirada.
addon_disabled403El addon Public API no está activo para la empresa.
validation_failed422Cuerpo/parámetros inválidos (errors con detalle por campo).
not_found404El recurso no existe o pertenece a otra empresa.
conflict409Conflicto de estado (p. ej. decidir una ausencia ya decidida).
idempotency_conflict409La Idempotency-Key se reusó con un cuerpo distinto.
rate_limited429Límite por minuto superado (ver Retry-After).
quota_exceeded429Cuota mensual agotada.
billing_required402El plan no permite la acción (p. ej. alta de empleado sin asiento).
internal_error500Error interno.

Aislamiento multitenant. Pedir un recurso de otra empresa devuelve 404 (not_found), nunca un 403 que revele su existencia.

Un trial agotado o una cancelación cortan el /v1 con 403 subscription_inactive, nunca con un 402. El 402 (billing_required) es otra cosa: la suscripción sigue viva pero el plan no da para la acción concreta (p. ej. crear un empleado sin asiento libre). Trata el 403 como «no hay servicio, hay que reactivar en el panel» y el 402 como «hay que ampliar el plan».

Revocar API keys y webhooks (DELETE) sigue funcionando con la suscripción inactiva: es un control de seguridad, no consumo del servicio.

Rate limits y cuota

Cada respuesta (2xx y 4xx) incluye, con dos excepciones documentadas más abajo:

HeaderSignificado
X-RateLimit-LimitLímite por minuto de la key.
X-RateLimit-RemainingPeticiones restantes en la ventana actual.
X-RateLimit-ResetTimestamp Unix en el que se reinicia la ventana.
X-Kinmu-Quota-RemainingPeticiones restantes de la cuota mensual.

Dos respuestas no llevan estas cabeceras. Los 401 (unauthenticated), porque la autenticación corta antes del throttle y la petición nunca llega a contarse; y todas las respuestas de GET /v1/reports/{report}/download, porque la descarga firmada se sirve fuera del pipeline autenticado. No trates su ausencia como un fallo: en esos dos casos es lo esperado.

Límites: live 120/min, test 30/min. Cuota mensual: live 10.000 + 1.000×empleados_activos (máx 100.000); test 5.000.

Al superar el minuto → 429 rate_limited con Retry-After: <segundos>. Al agotar la cuota → 429 quota_exceeded. Reintenta con backoff respetando Retry-After.

import time, requests def call_with_retry(session, method, url, **kwargs): for attempt in range(5): resp = session.request(method, url, **kwargs) if resp.status_code != 429: return resp wait = int(resp.headers.get("Retry-After", 2 ** attempt)) time.sleep(wait) resp.raise_for_status() return resp

Vigila X-RateLimit-Remaining y X-Kinmu-Quota-Remaining para espaciar tus peticiones antes de recibir un 429.

Idempotencia

En métodos que mutan (POST / PATCH / DELETE) puedes enviar Idempotency-Key: <único>:

curl -s -X POST "https://api.kinmu.app/v1/check-ins" \ -H "Authorization: Bearer kinmu_sk_live_…" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 3f9a…-uuid" \ -d '{ "employee_id": "…", "type": "in", "timestamp": "2026-07-08T08:00:00Z" }'

La primera petición se ejecuta y su respuesta se cachea 24 h; un reenvío con el mismo cuerpo devuelve la respuesta original (sin re-ejecutar). Reusar la misma key con un cuerpo distinto409 idempotency_conflict.

La creación de webhook es la única excepción: su secreto copy-once nunca se cachea.

Last updated on