# REST API

The API is served at `https://envpass.dev/api/v1`. The full OpenAPI 3.1 document is at [`/api/v1/openapi.json`](https://envpass.dev/api/v1/openapi.json), with an interactive explorer at [`/docs/api`](/docs/api).

## Authentication

Send a bearer token:

```
Authorization: Bearer <token>
```

| Token | Prefix | How to get it | Can do |
|---|---|---|---|
| Service token | `ep_live_` | Project → Tokens | read (or write) secrets in its project and environments |
| CLI session | none | `envpass login` (device flow) | everything the user can do |

Webhooks, connections and syncs can only be managed with a user token. Service tokens get `403 forbidden` there.

### Device flow

```bash
curl -X POST https://envpass.dev/api/auth/device/code \
  -H "content-type: application/json" -d '{"client_id":"envpass-cli"}'
# → { "device_code", "user_code", "verification_uri_complete", "interval", "expires_in" }

curl -X POST https://envpass.dev/api/auth/device/token \
  -H "content-type: application/json" \
  -d '{"grant_type":"urn:ietf:params:oauth:grant-type:device_code","device_code":"...","client_id":"envpass-cli"}'
# → { "access_token": "..." } once the user approves at verification_uri_complete
```

While waiting, the token endpoint returns `authorization_pending` or `slow_down`.

## Resolve values

```
GET /api/v1/resolve?workspace=acme&project=shop&environment=dev&format=json
```

| Parameter | Required | Notes |
|---|---|---|
| `environment` | yes | Environment slug |
| `workspace`, `project` | with a user token | Slugs. Service tokens already know them. |
| `format` | no | `json` (default), `dotenv` or `shell` |

```json
{
  "entries": [
    { "key": "DATABASE_URL", "value": "postgres://localhost/shop", "source": "personal" },
    { "key": "SENTRY_DSN", "value": "https://...", "source": "shared" }
  ]
}
```

`source` is `shared` for the team value and `personal` for your own override. Personal overrides are applied only for user tokens, on environments that allow them. Service tokens always get the team values.

## Main endpoints

| Method | Path | Purpose |
|---|---|---|
| GET | `/me` | Current user or token |
| GET, POST | `/workspaces` | List or create workspaces |
| GET, POST | `/workspaces/{ws}/environments` | Environments |
| GET, POST | `/workspaces/{ws}/projects` | Projects |
| GET | `/workspaces/{ws}/projects/{p}/secrets?environmentId=` | Keys and their sources, no values |
| PUT | `/workspaces/{ws}/projects/{p}/secrets` | Create or update team values, or your personal overrides |
| POST | `/workspaces/{ws}/secrets/{id}/reveal` | Reveal one value (audited) |
| GET | `/workspaces/{ws}/secrets/{id}/versions` | Version history |
| POST | `/workspaces/{ws}/projects/{p}/import` | Import a .env file, with a dry-run preview |
| GET | `/workspaces/{ws}/projects/{p}/compare` | Compare two environments |
| POST | `/workspaces/{ws}/projects/{p}/promote` | Copy keys between environments |
| POST | `/workspaces/{ws}/projects/{p}/share` | Share one project by email |
| GET, POST | `/workspaces/{ws}/roles` | Roles |
| GET, POST | `/workspaces/{ws}/tokens` | Service tokens |
| GET | `/workspaces/{ws}/audit` | Audit log |
| GET, POST | `/workspaces/{ws}/webhooks` | [Webhooks](/docs/webhooks) |
| GET | `/workspaces/{ws}/webhooks/{id}/deliveries` | Recent deliveries, kept for 30 days |
| GET, POST | `/workspaces/{ws}/connections` | Vercel and GitHub connections |
| GET, POST | `/workspaces/{ws}/projects/{p}/syncs` | [Syncs](/docs/syncs) |
| POST | `/workspaces/{ws}/projects/{p}/syncs/{id}/run` | Queue a sync run now |

### Write secrets

```bash
curl -X PUT "https://envpass.dev/api/v1/workspaces/$WS/projects/$PROJECT/secrets" \
  -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -d '{
    "environmentId": "'"$ENV_ID"'",
    "entries": [{ "key": "STRIPE_SECRET_KEY", "value": "sk_live_...", "note": "rotated" }]
  }'
# → { "created": ["STRIPE_SECRET_KEY"], "updated": [], "unchanged": [] }
```

Keys must match `^[A-Z_][A-Z0-9_]*$`. Values can be up to 32 KB. Writing team values needs `write` on the environment.

Add `"personal": true` to write your own overrides instead. That needs only `read`, works with user tokens on environments that allow personal overrides, and never triggers syncs or webhooks.

## Errors

Errors use HTTP status codes and a JSON body:

```json
{ "error": { "code": "forbidden", "message": "You don't have read access to this environment" } }
```

| Status | Code |
|---|---|
| 401 | `unauthorized` |
| 403 | `forbidden`, `limit_reached` (includes `limit`) |
| 404 | `not_found` |
| 409 | `conflict` |
| 422 | `invalid` |
| 429 | `rate_limited` |

## Rate limits

300 requests per minute per token, of which 30 may be `resolve` calls. Responses include `X-RateLimit-Limit` and `X-RateLimit-Remaining`.
