env-master
Documentation sections

API reference

The full reference of the user-facing REST API lives in the repository: docs/API.md. Here is what you need to know before the first request.

Base and format

All paths are relative to /api/v1, format is JSON. Responses and error codes are in English; the interface language is the client's concern. Server liveness — GET /healthz (outside /api/v1).

Authentication

SubjectMechanism
HumanAuthorization: Bearer <access token>, 15 minutes
Browserthe same header + refresh in an httpOnly cookie
Identityits own token from /machine/login or /machine/enroll, scope "project × environment", TTL configurable from 60 to 86400 s

The X-Client header (web, portal, cli, sdk, mobile) goes into the audit log and decides where the refresh token ends up: for a browser it is issued only in a cookie.

Machine routes:

RouteWhat it does
POST /machine/loginclient ID + client secret → access token
POST /machine/enrollenrollment token + public key → permanent credentials
GET /machine/entries?project=&env=environment entries within the identity's scope
POST /machine/heartbeat"alive" mark in the identity's lastUsedAt

All of this is covered in detail on the Services and identities page.

Errors

{ "error": { "code": "VALIDATION", "message": "..." } }

Branch on code, not on text. Every response carries an X-Request-Id — that is the only thing you need to quote in a support request.

HTTPCodeWhen in practice
400VALIDATIONbody or parameters failed validation; the text says what exactly
401UNAUTHENTICATEDno token, wrong token, invalid client ID / client secret, unknown enrollment token
401TOKEN_EXPIREDthe access token expired; log in again
402QUOTA_EXCEEDEDyou hit the plan limit — not a permissions issue
403FORBIDDENthe role is insufficient, the IP is outside allowedCidrs, the identity scope is wrong
403IDENTITY_REVOKEDthe identity is revoked; its tokens are not accepted
403ENROLLMENT_EXPIREDthe enrollment token is older than 10 minutes and was never used
403ENROLLMENT_USEDthe enrollment token has already been used — an alert, see identities
403ORG_BLOCKEDthe organization is blocked by the operator
404NOT_FOUNDno such resource (or no rights to see it)
409CONFLICTslug taken, identity already revoked, nothing to roll back
413PAYLOAD_TOO_LARGEthe request body exceeds the limit (1 MiB by default)
429RATE_LIMITEDtoo frequent; wait for Retry-After
500INTERNALa server-side error; quote the X-Request-Id in your support request

Rate limits are on the Security page.

Pagination

{ "items": [...], "nextCursor": "..." }. The cursor is opaque: pass it back in ?cursor= for as long as it comes.

Rotation

A policy is attached to an entry; runs happen on schedule in a background worker or are triggered out of schedule. Phases, strategies and the pending/current labels are covered on the Secret rotation page.

MethodPathRole
PUT/entries/:id/rotation-policyproject admin
GET/entries/:id/rotation-policyproject viewer
DELETE/entries/:id/rotation-policyproject admin
POST/rotation-policies/:id/run-nowproject admin
GET/rotation-runs?policyId=<id>project viewer
GET/orgs/:id/rotationsorganization member

PUT body:

{
  "rotator": "postgres",
  "strategy": "dual-credential",
  "intervalSeconds": 604800,
  "config": {
    "adminDsnEntryRef": "my-org/billing-api/production/DB_ADMIN_DSN",
    "usernames": ["app_a", "app_b"]
  },
  "enabled": true
}
  • rotator — currently only postgres;
  • strategy — dual-credential or single-user;
  • intervalSeconds — an integer, no less than 60;
  • config — parsed by the rotator; secret references in it must live in the same organization, otherwise VALIDATION, and you must hold a role of at least member in each referenced secret's project, otherwise FORBIDDEN. PUT replaces config wholesale: when editing, resend the activeUsername from GET, otherwise the rotator starts from the first user in the list;
  • enabled — optional, defaults to true.

PUT creates a policy or replaces the existing one and returns it: { id, entryId, rotator, strategy, intervalSeconds, config, enabled, nextRotationAt, createdAt }. GET returns the entry's policy, DELETE returns 204; both answer NOT_FOUND when there is no policy.

POST /rotation-policies/:id/run-now starts a run immediately and returns it. If a run of this policy is already in progress, the response is 409 CONFLICT: the manual trigger and the background worker share one policy lease. A paused (enabled: false) policy still runs — this is an explicit request, not a schedule; every such run is written to the audit log as a rotation.run.requested event. GET /rotation-runs?policyId=<id> returns { items } with the policy's latest runs (up to 50, newest first). A run: { id, policyId, phase, status, error?, resultVersion?, startedAt, completedAt? }, where phase is one of create, set, test, finish, and status is running, testing, succeeded or failed; the error text is present only on failed, and resultVersion is the number of the entry version the run created (present once the run has written a value).

GET /orgs/:id/rotations is the organization-wide summary: up to 5 latest runs per policy, 50 in total, across the projects where you have a role (the organization owner and admins see every project). Each element additionally carries entryKey — the entry's key name.

A common mistake

GET /projects requires ?orgId= — without it the response will be VALIDATION.