> ## Documentation Index
> Fetch the complete documentation index at: https://firmngin.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Enrollment (DPoP)

> Bind a client installation to an ES256 key and use short-lived DPoP access tokens

The flash API has two authentication modes. This page covers **enrollment + DPoP**; for the API key mode, see [API authentication](https://firmngin.dev/docs/local-flash/api.md).

Enrollment gives the client a **per-installation identity**:

* The API key is used once to enroll the installation.
* The client creates an **ES256 (P-256)** key pair inside a platform secure store. The private key stays there; the app only gets the public JWK and a signing handle.
* The client receives a 15-minute access token and a 30-day refresh token.
* Every data request presents `Authorization: DPoP <access-token>` plus a `DPoP` proof header.

Choose enrollment when the app is distributed across user machines, when a leaked API key should not be enough to act as an existing installation, or when you need to revoke one machine without revoking the whole key. For a simple internal tool, the API key mode is usually enough.

> [!NOTE]
>
> One API key can have at most **3 active installations**. Revoke an old installation before enrolling a replacement.

## Flow

```mermaid actions={false} theme={null}
sequenceDiagram
  participant App as Client app
  participant API as Firmngin API
  participant KS as Secure key store

  App->>KS: Create ES256 key pair
  KS-->>App: Public JWK + signing handle
  Note over KS: Private key never leaves the store
  App->>API: POST /flash/enroll/challenge (API key)
  API-->>App: challenge + expires_at
  App->>KS: Sign SHA-256(challenge)
  KS-->>App: signature
  App->>API: POST /flash/enroll (API key + public JWK + signature)
  API-->>App: installation_id + access token + refresh token
  App->>API: GET /flash/firmwares (DPoP + access token)
  API-->>App: firmware list
  App->>API: POST /flash/token (refresh + DPoP)
  API-->>App: rotated access + refresh tokens
```

## 1. Request a challenge

```http theme={null}
POST /api/saas/v1/flash/enroll/challenge
Authorization: Bearer fng_pat_...
```

```json theme={null}
{
  "data": {
    "challenge": "…",
    "expires_at": "2026-11-04T10:10:00Z"
  }
}
```

The challenge is single-use and expires after five minutes.

## 2. Enroll the installation

Sign **SHA-256(challenge)** with the installation private key inside the secure store, then send the public JWK and signature.

```http theme={null}
POST /api/saas/v1/flash/enroll
Authorization: Bearer fng_pat_...
Content-Type: application/json
```

```json theme={null}
{
  "challenge": "…",
  "public_key_jwk": {
    "kty": "EC",
    "crv": "P-256",
    "x": "…",
    "y": "…"
  },
  "signature": "…",
  "app_name": "flash-tool",
  "app_version": "1.0.0",
  "build_hash": "…",
  "os": "your-os",
  "machine_hash": "…"
}
```

`signature` is the ES256 signature over **SHA-256(challenge)**, base64url or standard base64. `public_key_jwk` must be an EC P-256 key.

```json theme={null}
{
  "data": {
    "installation_id": "…",
    "status": "active",
    "access_token": "…",
    "token_type": "DPoP",
    "expires_in": 900,
    "refresh_token": "…"
  }
}
```

The refresh token is returned once. Store it in the same platform secure store.

## 3. Call data endpoints

```http theme={null}
GET /api/saas/v1/flash/firmwares
Authorization: DPoP <access-token>
DPoP: <proof-jwt>
```

The proof is an ES256 JWT with:

| Claim | Value |
| - | - |
| `htm` | HTTP method, for example `GET` |
| `htu` | Full public URL without query, for example `https://api.firmngin.dev/api/saas/v1/flash/firmwares` |
| `iat` | Current Unix time; accepted within 60 seconds |
| `jti` | Unique proof id |
| `ath` | Base64url SHA-256 of the access token |

The proof header must include `typ: "dpop+jwt"` and the installation public key in `jwk`. The `htu` must match the public URL the API sees; when the API runs behind a proxy, set `URL_BACKEND` so the expected URL matches. The private key never leaves the client; only the public JWK is sent.

## 4. Refresh

```http theme={null}
POST /api/saas/v1/flash/token
Content-Type: application/json
DPoP: <proof-jwt>

{ "refresh_token": "…" }
```

The refresh request uses a DPoP proof without `ath`. Refresh tokens rotate on every use; reusing an old token revokes the whole family.

## 5. Manage installations

```http theme={null}
GET /api/saas/v1/flash/installations
Authorization: Bearer fng_pat_...
```

```http theme={null}
POST /api/saas/v1/flash/installations/{id}/revoke
Authorization: Bearer fng_pat_...
```

Revoking an installation invalidates its access tokens (checked on the next request) and its refresh tokens.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.