Nezal API

The contract is openapi.yaml. Base path /api/v1.

Authentication

Things a client has to get right

CSRF. A cookie-authenticated request that changes anything must carry an Origin or Referer the API recognises. Bearer-token clients are unaffected.

Side-effecting GETs refuse the cookie entirely (terminal and audit export as well as logs). A browser will issue those cross-origin so a cookie there would be exploitable from any page. Use a bearer token or mint a single-use 60-second ticket with POST /auth/ticket and pass it as a query parameter.

Sessions are revocable. Every token carries a jti that is checked against the sessions table on each request so revocation takes effect immediately rather than at expiry. The endpoints are GET /me/sessions and DELETE /me/sessions/{jti} and POST /me/sessions/revoke-all (which spares the calling session).

Empty lists are [] and never null. One that is not is a bug.

Errors

{"code": "nezal.env.quota.perUser", "params": {"owner": "amr", "current": "3", "max": "3"}}

Admission failures carry causes[] with one entry per rule. Each entry has field and code and params. Render texts in English and Arabic from api/pkg/i18n (Render(lang, code, params)).

Licence

PUT /licence {"key": "NZL2...."} installs a key. NZL1. keys are still accepted. States: valid and grace and expired and invalid and missing. Issue keys with nezal-licence issue (vendor only).

Expired or missing refuses environment creation and extension and sync. Nothing else: waking and previews and sign-in all keep working and so does the audit log. A licensed feature that is absent answers 402 with the feature name in params. A client should render that as "not in your plan" rather than as an error. GET /licence/usage-report returns a signed report of monthly active seats.

Audit

Entries are hash-chained and can never be altered or deleted.