---
title: API Authentication
description: How API keys, scopes, and the human-only restriction work on the ZevCloud API.
---

All authenticated API requests use a Bearer token:

```bash
curl https://api.zevcloud.net/v1/projects \
  -H "Authorization: Bearer sk_live_your_api_key"
```

API keys are **team-scoped** — every key belongs to exactly one team, and that team context is set automatically by the server from the key. You do not need to (and cannot) override which team an API key acts on by passing a different `x-team-id` header. The server reads the key's team from the database row, not from the request header.

## Scopes

Every API key is created with one of three scopes. They form a strict hierarchy: `read` ⊂ `deploy` ⊂ `full`.

| Scope    | What it can do                                                                                                |
|----------|---------------------------------------------------------------------------------------------------------------|
| `read`   | List and get resources. Read deployments, env-var keys (values are masked for secrets), logs, DNS, billing.   |
| `deploy` | Everything `read` can do, plus: trigger deployments, restart/stop/start services, set or delete env vars, verify a custom domain, approve or reject pending deployments. |
| `full`   | Everything `deploy` can do, plus: create or delete projects/services/environments, manage custom domains, run terminal commands, rotate SFTP passwords, toggle public DB access, manage DNS records on purchased domains. |

`deploy` is the recommended scope for AI agents and CI pipelines. It lets the agent ship updates to existing services without giving it permission to delete projects, change billing, or manage the team.

The full machine-readable permissions table is also surfaced in the [dashboard](https://console.zevcloud.net/settings?tab=api-keys) when you create a key.

## Things no API key can do — even with `full`

The API exposes a category of routes that always require interactive human authentication. A compromised API key cannot reach any of them. They include:

- Creating, listing, or revoking API keys
- Deleting the team or transferring ownership
- Inviting, removing, or changing a member's role
- Setting the team's billing currency
- Upgrading a service plan or applying credit to an invoice
- Funding Zev Credit (top-up)
- Buying, transferring, renewing, or cancelling a domain
- Toggling WHOIS privacy or email forwarding addons on a domain
- Opening the database GUI (Adminer)
- Connecting or disconnecting GitHub
- Accepting or declining team invites
- Saving billing addresses
- Managing notification preferences
- Accepting the Fair Use Policy

API requests to any of these return `403 Forbidden` with the message `This action requires interactive authentication and cannot be performed with an API key`.

## Lifecycle: keys are bound to their creator

Every API key is tied to the team member who created it. The key's effective role is the creator's *current* role on the team — looked up on every request, not snapshotted at creation.

What this means in practice:

- If the creator was an admin when they made the key, then later got demoted to member, the key stops being able to call any route that requires admin. The key's `scope` field is unchanged; the team-membership lookup just no longer satisfies the route's role check.
- If the creator is removed from the team (or leaves), the key is **auto-revoked** with reason `creator-removed`. The key is rejected on its next request and shows as revoked in the dashboard. There is no race window.
- If the team owner transfers ownership, their existing keys keep working at whatever role they now have on the team (typically admin or member after the transfer). Owner-only operations done via `verifyOwnership` will start failing for those keys.

The practical implication for automation: **rotate keys when the operator leaves**. Don't rely on a long-lived key created by a former teammate; create a fresh key under a current member.

## Endpoints

API key management endpoints are themselves human-only (a compromised key cannot use them to create more keys or revoke other keys to lock the team out). Call these with a ZevID identity token, not an API key.

### Generate a key

```
POST /v1/api-keys
```

Owner-or-admin only. Human auth required.

**Headers:**
```
Authorization: Bearer <ZevID token>
x-team-id: <team-id>
```

**Body:**
```json
{
  "name": "CI/CD Pipeline",
  "scope": "deploy"
}
```

**Response:**
```json
{
  "id": "uuid",
  "name": "CI/CD Pipeline",
  "key": "sk_live_a1b2c3d4...",
  "keyPrefix": "sk_live_a1b2c3...",
  "scope": "deploy",
  "createdAt": "2026-03-28T00:00:00Z"
}
```

> **Warning:**
>
> The `key` field is returned once and only once at creation time. After this response, the raw value cannot be retrieved by any caller — store it somewhere durable (a CI secret manager, a password manager) before navigating away.

### List keys

```
GET /v1/api-keys
```

Owner-or-admin only. Human auth required.

Returns all active (non-revoked) keys for the team. The raw key is never returned — only the prefix, plus name, scope, creation timestamp, and last-used timestamp.

### Revoke a key

```
DELETE /v1/api-keys/:keyId
```

Owner-or-admin only. Human auth required.

Revoked keys are immediately invalid for all subsequent requests. The row stays in the database with `revokedAt` set, so the audit trail and the dashboard can show *when* and *why* the key was revoked.

## Errors

The API returns these auth-related status codes:

| Status | Meaning |
|--------|---------|
| `401 Unauthorized` | Missing, malformed, expired, or revoked token. Also returned when the API key's creator has been removed from the team (the key is auto-revoked at this point). |
| `403 Forbidden — scope` | The key's scope is below what the route requires. The error body names the required scope (e.g. `requires 'deploy', key has 'read'`). |
| `403 Forbidden — human-only` | The route requires interactive human authentication and cannot be performed with an API key. |

When a `403` is returned because of scope, the fix is to issue a new key with a higher scope or use a key that already has the right scope. Existing keys cannot have their scope changed — create a new one and revoke the old one.
