Convenzioni dell’API
Regole trasversali a tutte le risorse della v1.
| Tema | Regola |
|---|---|
| Formato | JSON UTF-8. Le richieste con body portano Content-Type: application/json. |
| ID | UUID pubblici. Gli ID numerici interni non vengono mai esposti. |
| Date | ISO 8601. Istanti in UTC (2026-07-08T09:30:00Z); date di calendario YYYY-MM-DD. |
| Base URL | Versionata nel path: https://api.kinmu.app/v1 · Dev: https://api.dev.kinmu.app/v1. |
Paginazione (cursore)
Tutti gli elenchi sono paginati per cursore:
?limit=(default 25, max 100) ·?cursor=<opaco>.- Risposta:
{ "data": [...], "meta": { "next_cursor": "…"|null, "has_more": true|false } }. - Per la pagina successiva, reinvia
cursor=<meta.next_cursor>.has_more=falseonext_cursor=null→ fine.
curl "https://api.kinmu.app/v1/employees?limit=50&cursor=eyJpZCI6MTIzfQ" \
-H "Authorization: Bearer kinmu_sk_live_…"Il cursor è opaco: non costruirlo né parsarlo, reinvialo così com’è.
Sincronizzazione incrementale (updated_since)
Tutti gli elenchi accettano ?updated_since=<ISO 8601> per ottenere solo ciò che è cambiato da quell’istante (polling incrementale per BI ed ERP).
curl "https://api.kinmu.app/v1/employees?updated_since=2026-07-01T00:00:00Z" \
-H "Authorization: Bearer kinmu_sk_live_…"Salva l’istante dell’ultima sincronizzazione e usalo come updated_since nella successiva. Trovi il pattern completo nella guida BI.
Errori
Tutti gli errori sono 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" }
}Programma sul campo code (stabile), non su title/detail.
Tabella dei codici
code | HTTP | Quando |
|---|---|---|
unauthenticated | 401 | Chiave assente, non valida, revocata o scaduta. |
invalid_scope | 403 | La chiave non ha lo scope richiesto (errors.required_scope). |
subscription_inactive | 403 | L’abbonamento dell’azienda è sospeso o assente. |
addon_disabled | 403 | L’addon Public API non è attivo per l’azienda. |
validation_failed | 422 | Body o parametri non validi (errors con il dettaglio per campo). |
not_found | 404 | La risorsa non esiste o appartiene a un’altra azienda. |
conflict | 409 | Conflitto di stato (ad es. decidere un’assenza già decisa). |
idempotency_conflict | 409 | La Idempotency-Key è stata riusata con un body diverso. |
rate_limited | 429 | Limite al minuto superato (vedi Retry-After). |
quota_exceeded | 429 | Quota mensile esaurita. |
billing_required | 402 | Il piano non consente l’azione (ad es. creare un dipendente senza posti disponibili). |
internal_error | 500 | Errore interno. |
Isolamento multitenant. Richiedere una risorsa di un’altra azienda restituisce 404 (not_found), mai un 403 che ne riveli l’esistenza.
Rate limit e quota
Ogni risposta (2xx e 4xx) include:
| Header | Significato |
|---|---|
X-RateLimit-Limit | Limite al minuto della chiave. |
X-RateLimit-Remaining | Richieste rimanenti nella finestra corrente. |
X-RateLimit-Reset | Timestamp Unix in cui la finestra si azzera. |
X-Kinmu-Quota-Remaining | Richieste rimanenti della quota mensile. |
Limiti: live 120/min, test 30/min. Quota mensile: live 10.000 + 1.000×empleados_activos (max 100.000); test 5.000.
Superato il limite al minuto → 429 rate_limited con Retry-After: <secondi>. Esaurita la quota → 429 quota_exceeded. Riprova con backoff rispettando 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 respTieni d’occhio X-RateLimit-Remaining e X-Kinmu-Quota-Remaining per distanziare le richieste prima di ricevere un 429.
Idempotenza
Nei metodi che mutano (POST / PATCH / DELETE) puoi inviare Idempotency-Key: <unico>:
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 prima richiesta viene eseguita e la sua risposta resta in cache per 24 h; un reinvio con lo stesso body restituisce la risposta originale (senza ri-esecuzione). Riusare la stessa chiave con un body diverso → 409 idempotency_conflict.
La creazione di un webhook è l’unica eccezione: il suo secret copy-once non viene mai messo in cache.