{"openapi":"3.1.0","info":{"title":"CPonto API","version":"1.0.0","description":"Integração com ERP/folha de pagamento. Autentique com `Authorization: Bearer <chave>` (crie a chave em Admin → Integrações). Datas sem hora (`YYYY-MM-DD`) são interpretadas no fuso da empresa. Horários em ISO-8601 com fuso. Limites por chave: 30 requisições/min e 1.000/dia (429 com Retry-After); 10 chaves inválidas por IP em 15 min bloqueiam o IP. Webhooks (punch.created, time_entries.imported, adjustment.decided) com novas tentativas automáticas e X-CPonto-Delivery estável para descartar repetidos — assinados com `X-CPonto-Signature: sha256=HMAC(segredo, corpo)`."},"servers":[{"url":"/api/v1"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"Chave cpk_…"}},"responses":{"Error":{"description":"Erro","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}}}}}}}}},"schemas":{"Employee":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"cpf":{"type":"string","description":"Somente dígitos"},"email":{"type":"string"},"role":{"type":"string","enum":["employee","manager","admin"]},"active":{"type":"boolean"},"hourlyRateCents":{"type":"integer","description":"Custo da hora em centavos"},"scheduleId":{"type":["string","null"]},"scheduleName":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"}}}}},"paths":{"/employees":{"get":{"summary":"Listar funcionários","parameters":[{"name":"active","in":"query","schema":{"type":"string","enum":["true","false"]}},{"name":"cpf","in":"query","schema":{"type":"string"}},{"name":"email","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":100,"maximum":1000}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Lista paginada","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Employee"}},"pagination":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Error"}}},"post":{"summary":"Cadastrar funcionário (escopo write)","description":"Não envie senha: o CPonto gera uma senha provisória (válida por 7 dias) e a envia só ao e-mail do funcionário. O acesso fica restrito até ele criar a própria senha. A resposta traz accessPending=true e welcomeEmail=sent|failed.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","cpf","email"],"properties":{"name":{"type":"string"},"cpf":{"type":"string"},"email":{"type":"string"},"hourlyRateCents":{"type":"integer","default":0},"role":{"type":"string","enum":["employee","manager","admin"],"default":"employee"},"scheduleId":{"type":["string","null"]},"active":{"type":"boolean","default":true}}}}}},"responses":{"201":{"description":"Criado"},"409":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"}}}},"/employees/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Consultar funcionário","responses":{"200":{"description":"Funcionário"},"404":{"$ref":"#/components/responses/Error"}}},"patch":{"summary":"Alterar funcionário (escopo write)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"hourlyRateCents":{"type":"integer"},"role":{"type":"string","enum":["employee","manager","admin"]},"scheduleId":{"type":["string","null"]},"active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Atualizado"},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"}}}},"/schedules":{"get":{"summary":"Listar jornadas","responses":{"200":{"description":"Jornadas"}}}},"/time-entries":{"get":{"summary":"Coletar marcações ainda não lidas (lote)","description":"Entrega só as marcações que ESTA integração ainda não consumiu, num lote reservado por 10 min. Confirme com POST /batches/{id}/ack; sem confirmação o lote volta a ficar disponível. Itens consumidos não voltam, a não ser que um gestor force nova coleta (chegam com reread=true e lastReadAt).","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date"},"description":"Filtro opcional"},{"name":"to","in":"query","schema":{"type":"string","format":"date"},"description":"Filtro opcional"},{"name":"employeeId","in":"query","schema":{"type":"string"}},{"name":"cpf","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":500,"maximum":1000}}],"responses":{"200":{"description":"{ batch: {id, leaseExpiresAt} | null, data: [...com reread/lastReadAt], hasMore }"},"400":{"$ref":"#/components/responses/Error"}}},"post":{"summary":"Enviar marcações em lote (escopo write)","description":"Até 1000 por chamada. Tudo ou nada: com qualquer erro, responde 422 e não grava. Marcações iguais às existentes (mesma pessoa, menos de 1 min) são ignoradas — reenviar é seguro.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["entries"],"properties":{"entries":{"type":"array","maxItems":1000,"items":{"type":"object","required":["at"],"properties":{"employeeId":{"type":"string"},"cpf":{"type":"string"},"at":{"type":"string","format":"date-time","example":"2026-09-28T08:02:00-03:00"},"note":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"Nada novo (todas já existiam)"},"201":{"description":"{ data: { created, ignored } }"},"422":{"$ref":"#/components/responses/Error"}}}},"/timesheet":{"get":{"summary":"Coletar espelho de ponto ainda não lido (lote)","description":"Só dias ENCERRADOS (até ontem) que esta integração ainda não consumiu, num lote a confirmar com POST /batches/{id}/ack. Por pessoa: totais dos dias do lote e cada dia com horas trabalhadas, previstas, saldo, extras por adicional, noturno, banco, feriado e marcações. Dia corrigido depois da leitura só volta se um gestor forçar nova coleta.","parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date"},"description":"Data inicial"},{"name":"to","in":"query","required":true,"schema":{"type":"string","format":"date"},"description":"Data final (máx. 62 dias)"},{"name":"employeeId","in":"query","schema":{"type":"string"}},{"name":"cpf","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Espelho","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"employee":{"type":"object"},"totals":{"type":"object","properties":{"workedMinutes":{"type":"integer","description":"Minutos"},"expectedMinutes":{"type":"integer","description":"Minutos"},"balanceMinutes":{"type":"integer","description":"Minutos"},"overtimeMinutes":{"type":"integer","description":"Minutos"},"overtimeByPct":{"type":"object","additionalProperties":{"type":"integer","description":"Minutos"}},"nightMinutes":{"type":"integer","description":"Minutos"},"bankMinutes":{"type":"integer","description":"Minutos"},"absences":{"type":"integer"},"costCents":{"type":"integer"}}},"days":{"type":"array","items":{"type":"object"}}}}}}}}}},"400":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/batches/{id}/ack":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Confirmar lote de leitura","description":"Os itens do lote viram consumidos (não serão entregues de novo) e recebem a tag lastReadAt. Idempotente.","responses":{"200":{"description":"{ data: { batchId, acknowledged, alreadyAcked } }"},"404":{"$ref":"#/components/responses/Error"}}}},"/hour-bank":{"get":{"summary":"Saldos de banco de horas","parameters":[{"name":"employeeId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Ciclo atual e fechamento do anterior (toPayAsOvertimeMinutes)"}}}}}}