API Reference
Base URL: https://www.envpilot.dev/api/v1. All endpoints require the Authorization: Bearer <api key> header and are read-only. Requires the Pro plan (public_api feature). See API Quickstart to create a key and API Security for the auth and audit model.
Every response is JSON unless format=env is passed. Every error response is { "error": string, "code": string } plus an x-request-id header for support.
Endpoints#
GET /v1/organization
Returns metadata for the key's organization. Works with any valid API key of the organization regardless of its resource scope — org metadata is discovery-level information the key holder already has.
curl https://www.envpilot.dev/api/v1/organization \
-H "Authorization: Bearer envpk_..."{
"id": "org_abc123",
"name": "Acme Inc",
"slug": "acme",
"plan": "pro"
}Auth: any valid key. Rate limit: metadata bucket (120/min).
GET /v1/projects
Lists every project in the key's scope (name, slug, and resource counts). Projects outside scope are silently omitted — an empty scope match returns 200 with an empty list, never an error.
curl https://www.envpilot.dev/api/v1/projects \
-H "Authorization: Bearer envpk_..."{
"projects": [
{
"id": "proj_1",
"name": "Backend",
"slug": "backend",
"variableCount": 42,
"accountCount": 3
}
]
}Auth: requires projects in the key's resources. Rate limit: metadata bucket (120/min).
GET /v1/projects/{slug}
Returns a single project's metadata.
curl https://www.envpilot.dev/api/v1/projects/backend \
-H "Authorization: Bearer envpk_..."Auth: projects resource, project must be in scope. 404 if the slug doesn't exist or is outside the key's scope — the two cases are indistinguishable on purpose. Rate limit: metadata bucket (120/min).
GET /v1/projects/{slug}/variables
The workhorse endpoint. Pulls decrypted variable values for one environment, or metadata-only if you don't need values.
| Query param | Required | Description |
|---|---|---|
environment | Yes, unless metadata_only | development, staging, or production |
keys | No | Comma-separated exact key names, e.g. keys=A,B,C |
prefix | No | Only keys starting with this prefix, e.g. prefix=NEXT_PUBLIC_ |
metadata_only | No | true returns key names only — no vault decrypt, no values |
format | No | json (default) or env (dotenv text output) |
curl "https://www.envpilot.dev/api/v1/projects/backend/variables?environment=production&prefix=NEXT_PUBLIC_" \
-H "Authorization: Bearer envpk_..."{
"environment": "production",
"variables": [
{
"key": "NEXT_PUBLIC_API_URL",
"value": "https://api.acme.com",
"updatedAt": 1752192000000
}
]
}Auth: variables resource, project + environment must be in scope. Rate limit: metadata reads (metadata_only=true) count against the 120/min metadata bucket; value pulls count against the 30/min value bucket. 422 if the project has more than 1000 matching variables — the API refuses to return a partial list rather than silently truncating. 503 if vault decryption fails for any variable in the response — the whole request aborts rather than returning a partial or sentinel value.
GET /v1/projects/{slug}/accounts
Lists shared accounts (credentials shared with the team, distinct from variables) for a project.
| Query param | Required | Description |
|---|---|---|
environment | No | Filter to one environment |
metadata_only | No | true returns account names only, no secret values |
curl "https://www.envpilot.dev/api/v1/projects/backend/accounts?environment=production" \
-H "Authorization: Bearer envpk_..."Auth: accounts resource, project must be in scope. Rate limit: metadata reads use the 120/min bucket; value pulls use the 30/min bucket.
GET /api/v1/secrets
Unchanged legacy endpoint used by the GitHub Action — pulls variables for a single project + environment service token. Kept for backward compatibility; new integrations should use GET /v1/projects/{slug}/variables instead.
Errors#
Every non-2xx response is { "error": string, "code": string }.
| Status | When it happens |
|---|---|
401 | Key is missing, malformed, invalid, expired, or revoked — one uniform message so revoked/invalid/expired can't be distinguished by an attacker |
403 | Key is valid but its scope or plan doesn't include this resource/feature (e.g. calling /accounts with a variables-only key, or public_api not enabled for the org's plan) |
404 | Project slug doesn't exist, or exists but is outside the key's scope — both return the same 404 so scope can't be probed by existence |
422 | The response would exceed 1000 variables — refused outright rather than returning a partial/truncated list |
429 | Rate limit exceeded — carries a Retry-After header (seconds until the bucket refills) |
503 | Vault decrypt failed for one or more variables mid-request — the whole request aborts, never a partial result with missing values |
Rate limits#
Rate limits are per API key, enforced with a token-bucket limiter.
| Bucket | Limit | Applies to |
|---|---|---|
| Metadata reads | 120 / min | /organization, /projects, /projects/{slug}, and any request with metadata_only=true |
| Value pulls | 30 / min | /projects/{slug}/variables and /projects/{slug}/accounts when returning decrypted values |
A 429 response includes a Retry-After header with the number of seconds to wait. Requests using an unrecognized key hash are rate-limited separately (per hash) to slow down brute-force probing.
CORS#
The API does not send CORS headers and is not intended to be called from a browser — API keys must never be embedded in client-side code. See API Security.