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/employeesSí
GET /v1/check-insSí
GET /v1/absencesSí
GET /v1/locationsSí
GET /v1/unitsSí
GET /v1/webhook-endpointsSí
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_scope403Falta el scope requerido (errors.required_scope) o la operación no está permitida con una key, como modificar a un administrador de la empresa.
subscription_inactive403La empresa no tiene servicio activo: suscripción suspendida, prueba terminada o primer pago sin completar. Una cancelación surte efecto según las condiciones del contrato.
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.
conflict409El estado actual no permite la operación: una ausencia ya decidida, una jornada que se solaparía con otra o el reintento de una entrega que no ha fallado.
idempotency_conflict409La Idempotency-Key ya se usó con otro cuerpo, o hay otra petición en curso con la misma clave.
rate_limited429Límite por minuto superado, o demasiados intentos de autenticación fallidos desde la misma IP (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.

Sin servicio activo, /v1 responde 403 subscription_inactive, nunca un 402. El 402 (billing_required) significa otra cosa: el servicio sigue activo pero el plan no da para esa acción, por ejemplo dar de alta un empleado sin asiento libre. Trata el 403 como «hay que reactivar la cuenta» 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.

Los fallos de autenticación (401 y el 429 del límite por IP) no llevan las cabeceras de cuota por key. Tampoco las respuestas de GET /v1/reports/{report}/download, que usa una URL firmada. El 429 de autenticación sí incluye Retry-After.

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.

Si una misma IP acumula 60 intentos de autenticación fallidos en un minuto, los siguientes fallos devuelven 429 rate_limited con Retry-After. Las peticiones con una key válida no cuentan para ese límite ni se bloquean por él.

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 distinto → 409 idempotency_conflict.

Reutilizar una Idempotency-Key con otro cuerpo, o mientras sigue en curso otra petición con esa misma clave, devuelve 409 idempotency_conflict. En el segundo caso, espera a que termine y reintenta con la misma clave y cuerpo.

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

Last updated on