API quickstart
The intranet exposes a versioned JSON API at /api/v1, sharing the app's RBAC,
billing engine, and audit trail. The interactive Swagger UI lives at
/api-docs, and the OpenAPI 3.1 document at GET /api/v1/openapi (public).
Get a token
Two options, both created by an admin:
- Personal token — acts as a user, inherits their role, optional scopes.
See Personal API tokens. Starts with
cxp_. - Service client — exchange client credentials for a short-lived token. See Service clients.
curl -s -X POST https://intranet.example/api/v1/auth/token \
-H 'content-type: application/json' \
-d '{"grant_type":"client_credentials","client_id":"accounting-sync","client_secret":"..."}'
Call the API
export TOKEN=cxp_...
curl -s https://intranet.example/api/v1/me \
-H "Authorization: Bearer $TOKEN"
Create a time entry:
curl -s https://intranet.example/api/v1/time-entries \
-H "Authorization: Bearer $TOKEN" \
-H 'content-type: application/json' \
-d '{ "projectId": "...", "serviceDate": "2026-10-05", "hours": 2, "minutes": 30,
"description": "Terraform review" }'
Conventions
-
Durations are integer seconds; money is a decimal string in the record's currency; dates are
YYYY-MM-DD. -
Lists accept
limit(max 200, default 50) andoffsetand return{ "data": [...], "meta": { "total", "limit", "offset" } }. -
Errors use a stable envelope:
{ "error": { "code": "validation_failed", "message": "...", "details": [] } }Codes:
bad_request,invalid_json,unauthorized,forbidden,not_found,conflict,validation_failed,internal_error.
Endpoint map
| Area | Endpoints |
|---|---|
| Identity | GET /me, POST /auth/token |
| Customers | GET/POST /customers, GET/PATCH/DELETE /customers/{id} |
| Projects | GET/POST /projects, GET/PATCH/DELETE /projects/{id}, GET/POST /projects/{id}/assignments, DELETE /projects/{id}/assignments/{engineerId} |
| Time | GET/POST /time-entries, GET/PATCH/DELETE /time-entries/{id}, POST /time-entries/{id}/submit, POST .../approve, POST .../reject |
| Agreements | GET/POST /agreements, GET/PATCH/DELETE /agreements/{id}, GET/POST /agreements/{id}/allowances, DELETE .../allowances/{allowanceId} |
| Rates | GET/POST /rates, DELETE /rates/{id} |
| Billing | GET /billing/periods, GET /billing/periods/{id}, POST .../close, POST .../reopen, POST /billing/recalculate |
| Reports | GET /reports/timesheet, GET /reports/sla, GET /reports/export |
| Admin | GET /admin/users, PATCH /admin/users/{id}, GET /admin/audit, GET/POST /admin/tokens, DELETE /admin/tokens/{id}, GET/POST /admin/service-clients, DELETE /admin/service-clients/{id} |
Scopes
customers:read, customers:write, projects:read, projects:write,
time:read, time:write, time:review, agreements:read, agreements:write,
billing:read, billing:write, reports:read, admin:read, admin:write, or
*.
Effective permission = scopes ∩ role. A scope alone never exceeds the role.
Local demo credentials
Seeded for local testing only:
| Kind | Value | Role | Scopes |
|---|---|---|---|
| Personal token | cxp_demo_admin_000000000000000000000000 | admin | * |
| Service client | demo-accounting / demo-accounting-secret | finance | billing:read, reports:read |
Not yet built
- Rate limiting / per-token quotas.
- Token introspection endpoint.
- Service-client secret rotation.