Convenções da API
Regras transversais a todos os recursos da v1.
| Tema | Regra |
|---|---|
| Formato | JSON UTF-8. Requests com corpo levam Content-Type: application/json. |
| IDs | UUIDs públicos. IDs numéricos internos nunca são expostos. |
| Datas | ISO 8601. Instantes em UTC (2026-07-08T09:30:00Z); datas de calendário YYYY-MM-DD. |
| Base URL | Versionada no path: https://api.kinmu.app/v1 · Dev: https://api.dev.kinmu.app/v1. |
Paginação (cursor)
Todas as listagens paginam por cursor:
?limit=(padrão 25, máx. 100) ·?cursor=<opaco>.- Resposta:
{ "data": [...], "meta": { "next_cursor": "…"|null, "has_more": true|false } }. - Para a próxima página, reenvie
cursor=<meta.next_cursor>.has_more=falseounext_cursor=null→ fim.
curl "https://api.kinmu.app/v1/employees?limit=50&cursor=eyJpZCI6MTIzfQ" \
-H "Authorization: Bearer kinmu_sk_live_…"O cursor é opaco: não o construa nem o interprete, reenvie-o tal como veio.
Sincronização incremental (updated_since)
Todas as listagens aceitam ?updated_since=<ISO 8601> para trazer apenas o que mudou desde esse instante (polling incremental para BI e ERPs).
curl "https://api.kinmu.app/v1/employees?updated_since=2026-07-01T00:00:00Z" \
-H "Authorization: Bearer kinmu_sk_live_…"Guarde o instante da sua última sincronização e use-o como updated_since na seguinte. Veja o padrão completo no guia de BI.
Erros
Todos os erros seguem a 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" }
}Programe contra o campo code (estável), não contra title/detail.
Tabela de códigos
code | HTTP | Quando |
|---|---|---|
unauthenticated | 401 | Chave ausente, inválida, revogada ou expirada. |
invalid_scope | 403 | A chave não tem o scope exigido (errors.required_scope). |
subscription_inactive | 403 | A assinatura da empresa está suspensa/ausente. |
addon_disabled | 403 | O addon Public API não está ativo para a empresa. |
validation_failed | 422 | Corpo/parâmetros inválidos (errors com detalhe por campo). |
not_found | 404 | O recurso não existe ou pertence a outra empresa. |
conflict | 409 | Conflito de estado (por exemplo, decidir uma ausência já decidida). |
idempotency_conflict | 409 | A Idempotency-Key foi reutilizada com um corpo diferente. |
rate_limited | 429 | Limite por minuto excedido (veja Retry-After). |
quota_exceeded | 429 | Cota mensal esgotada. |
billing_required | 402 | O plano não permite a ação (por exemplo, admitir funcionário sem assento). |
internal_error | 500 | Erro interno. |
Isolamento multitenant. Pedir um recurso de outra empresa retorna 404 (not_found), nunca um 403 que revele sua existência.
Rate limits e cota
Cada resposta (2xx e 4xx) inclui:
| Header | Significado |
|---|---|
X-RateLimit-Limit | Limite por minuto da chave. |
X-RateLimit-Remaining | Requisições restantes na janela atual. |
X-RateLimit-Reset | Timestamp Unix em que a janela reinicia. |
X-Kinmu-Quota-Remaining | Requisições restantes da cota mensal. |
Limites: live 120/min, test 30/min. Cota mensal: live 10.000 + 1.000×empleados_activos (máx. 100.000); test 5.000.
Ao exceder o minuto → 429 rate_limited com Retry-After: <segundos>. Ao esgotar a cota → 429 quota_exceeded. Tente de novo com backoff respeitando 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 respAcompanhe X-RateLimit-Remaining e X-Kinmu-Quota-Remaining para espaçar suas requisições antes de receber um 429.
Idempotência
Em métodos que mutam (POST / PATCH / DELETE) você pode 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" }'A primeira requisição é executada e sua resposta fica em cache por 24 h; um reenvio com o mesmo corpo retorna a resposta original (sem re-executar). Reutilizar a mesma chave com um corpo diferente → 409 idempotency_conflict.
A criação de webhook é a única exceção: seu segredo copy-once nunca vai para o cache.