---
title: Enrollment
path: reference/enrollment
status: published
---

# Enrollment

Enrollment tokens let an unprovisioned host obtain its own service account and API key, so you never have to copy a long-lived credential onto every machine.

Copying one key to a fleet is the least auditable step in a rollout: it is valid forever, identical everywhere, and nothing records where it went. An enrollment token is the opposite — short-lived, single-use by default, and every redemption is recorded with the hostname and source IP that consumed it.

## Mint a token

```http
POST /api/v1/enrollment/tokens
```

Requires `service_accounts:write`.

```json
{
  "roles": ["pki-agent"],
  "description": "web tier rollout",
  "ttl_seconds": 3600,
  "max_uses": 1,
  "fqdn_pattern": "web*.example.com"
}
```

| Field | Default | Notes |
|---|---|---|
| `roles` | required | Granted to every service account minted from this token. Keep them narrow — an agent doing local-CSR renewal needs `pki:write` and no more. |
| `ttl_seconds` | 3600 | Maximum 86400. A token that outlives the rollout window is just a long-lived credential under another name. |
| `max_uses` | 1 | Raise it for an autoscaling group, but a reusable credential is a credential. |
| `fqdn_pattern` | none | Glob restricting which hostnames may redeem. |

The response includes the plaintext `token` **once**. Only its SHA-256 is stored; it is not recoverable afterwards.

```bash
curl -sS -X POST https://scaivault.scailabs.ai/api/v1/enrollment/tokens \
  -H "X-API-Key: $ADMIN_KEY" -H 'Content-Type: application/json' \
  -d '{"roles":["pki-agent"],"ttl_seconds":3600,"fqdn_pattern":"web*.example.com"}'
```

## Redeem a token

```http
POST /api/v1/enrollment/redeem
```

**Unauthenticated** — the caller is a host that has no credential yet, which is the problem enrollment exists to solve.

```json
{ "token": "enroll_...", "fqdn": "web01.example.com" }
```

Returns the host's own API key, once:

```json
{
  "api_key": "key_...",
  "service_account_id": "sva_...",
  "service_account_name": "agent-web01.example.com",
  "roles": ["pki-agent"]
}
```

In practice the agent does this for you:

```bash
scaivault-agent enroll --token enroll_...
```

## List and revoke

```http
GET    /api/v1/enrollment/tokens          # service_accounts:read
DELETE /api/v1/enrollment/tokens/{id}     # service_accounts:write
```

Listing shows `uses` / `max_uses` and a `redemptions` array recording which hosts consumed the token, from which IP, and when. Revocation takes effect immediately, whether or not the token has been used.

## How the endpoint is protected

Because redemption cannot require authentication, the safety lives in the token and the endpoint:

- **256 bits of entropy**, stored only as a hash.
- **Short TTL** — one hour by default, 24 hours maximum.
- **Use-limited**, single-use by default. The claim is atomic, so two hosts racing the same token cannot both succeed.
- **Optional hostname glob** via `fqdn_pattern`.
- **Rate-limited per source IP.**
- **Uniform failure.** Unknown, expired, revoked, spent and wrong-hostname all return the same `401 enrollment_failed`. The specifics go to the audit log, not to the caller, so the endpoint cannot be used as an oracle.

### What `fqdn_pattern` does and does not do

The hostname is **self-asserted**. It names the resulting service account and is recorded for audit, so `fqdn_pattern` narrows the blast radius of a leaked token — it does not prove who the caller is. Binding to a hardware or cloud instance identity would be the next step; today, treat the pattern as a guard rail rather than authentication.

## Operational notes

Each host gets a distinct service account named `agent-<fqdn>`, so revoking one machine does not affect the rest. A rebuilt host that enrols again receives a fresh account rather than colliding with its old one.

Grant tokens the narrowest roles that work. For the certificate agent that is `pki:write` in `csr` mode — enough to request certificates, not enough to export existing private keys.

## What's next

- [Certificate Agent](../sdks/certificate-agent) — the main consumer of enrollment.
- [Authentication](./authentication) — scopes and API keys.
- [Fleet](./fleet) — what the enrolled hosts report back.
