# Zentrik API v1

Base: `/api/v1/t/{company-slug}`. Use `Authorization: Bearer YOUR_TOKEN` and `Accept: application/json`. Create/revoke tokens from Company settings > API access after password confirmation. Tokens are bound to the issuing user and expire within 90 days; current role/module checks still apply. MFA enrollment is optional for the issuing account. The token bucket allows 120 calls per minute. Request bodies are limited to 256 KiB.

## Read resources

`GET /resources/{resource}` returns a Laravel pagination envelope with 50 records per page. Pass `?page=2` for subsequent pages. Resources: organization, positions, employees, documents, recruitment, career, attendance, leave, payroll, requests, assets, fleet and workflows. Required ability is `{resource}:read`, plus the corresponding current role permission and module. Only explicit registered columns are serialized. Document content, bank details, token hashes, integration secrets, decrypted files and custom form answers are excluded. Candidate documents are not exposed through the generic document register.

## Business commands

`POST /commands/{command}` accepts the existing typed domain service inputs. Send a unique UUID `Idempotency-Key`. A successful command returns `{ "ok": true, "reference": "command.name", "id": 123 }`. Repeating an identical parsed body/key/token returns the committed result without a second mutation. Reusing that key with a different command/body returns HTTP 409. The receipt commits in the same transaction as the business change.

| Commands | Ability |
| --- | --- |
| organization.unit, organization.position | organization:write |
| employees.save | employees:write |
| recruitment.vacancy | recruitment:write |
| career.create, career.decide | career:write |
| attendance.record | attendance:write |
| leave.request, leave.decide | leave:write |
| requests.create, requests.decide | requests:write |
| assets.category, assets.create, assets.issue, assets.return | assets:write |
| fleet.create, fleet.licence, fleet.issue, fleet.return | fleet:write |
| payroll.create, payroll.transition | payroll:write |

Create/configuration commands use the same named fields and validators as their corresponding company screens. Decision/return commands use integer `id` plus `decision` (approved/rejected) and `note`, or `evidence` for equipment returns. Career decisions also accept subject_type record/vacancy/offer; the typed service still requires the relevant recruitment or lifecycle permissions. Payroll create takes `period` YYYY-MM; transitions take id/action/note, with action submit/approve/return/release. Equipment issue takes the asset id and employee_id/evidence. Asset scope cannot issue/return a vehicle through the shared equipment record.

Approval separation, verified licences, live custody, payroll source hashes, period locks and tenant checks remain enforced by domain services. There is no unrestricted model CRUD endpoint. Encrypted file operations and bank exports continue through their authenticated UI routes.

## Webhooks

Configure events from the central registry. JSON contains id, type, subject and occurred_at. Headers: X-Zentrik-Event, X-Zentrik-Timestamp, X-Zentrik-Signature. Verify the exact raw body with `HMAC-SHA256(secret, timestamp + "." + body)` and compare to the hex digest after `v1=` in constant time. Reject stale timestamps (for example outside a five-minute window), deduplicate event IDs and return 2xx only after recording acceptance.

Delivery is at least once: a process failure after the receiver commits may cause a retry with the same event ID. Failed sends back off, stop after five attempts and show sanitized diagnostics. Receiver response contents are never saved. DNS/HTTPS restrictions are applied again on every attempt. No scheduler or delivery worker was started on the working local setup.

Local audit extension: `employees.save` accepts an optional `custom_values` object keyed by active company field keys. Server-defined type/choice/required rules apply; unknown/inactive keys are rejected. These values remain encrypted and excluded from the public employee read registry. Configure definitions through the authorized Settings screen.
