# Authentication

Every request is authenticated with a single **API key**. You send it as the
`X-API-Key` header — there is no OAuth dance, no token exchange.
**Scope of an API key:** An API key grants access to the **PDF upload service** only — the `/api/v1/upload-service/*`
endpoints (upload, job status, download, delete). That is the entire public processing surface.

## Create an API key

The normal way to obtain a key is the **Accessful portal**: sign in, open
**API keys**, and create one. The key is shown **once** at creation — copy it
immediately. We store only a hash, so we can never display it again.

A key looks like this:

```
ak_oCe4cm015zL8vCbBne_wUZDcM6G1RWqD7Ekc2944EAA
```

- Prefix `ak_` followed by 43 URL-safe characters.
- You can hold up to **10 active keys** per account.
- A key can optionally carry an **expiry date**; revoke it any time from the portal.
**Caution:** Treat the key like a password. It is bound to your account and can upload, download,
and **permanently delete** your cases. Never embed it in client-side code you ship to end users.

### Manage keys programmatically (optional)

If you automate key rotation, the key-management endpoints live under
`/api/v1/auth/api-keys` and require an **OAuth2 bearer token** from your portal login
(`Authorization: Bearer <jwt>`) — not an API key:

| Method &amp; path | Purpose |
| --- | --- |
| `POST /api/v1/auth/api-keys` | Create a key — response contains the plaintext **once** |
| `GET /api/v1/auth/api-keys` | List your keys (values masked) |
| `POST /api/v1/auth/api-keys/{id}/revoke` | Revoke a key |
| `DELETE /api/v1/auth/api-keys/{id}` | Delete a key |

## Send the key on requests

Add the `X-API-Key` header to every call:

```bash
curl https://api.accessful.de/api/v1/upload-service/job-status/<caseId> \
  -H "X-API-Key: ak_your_key_here"
```
```js
await fetch(`https://api.accessful.de/api/v1/upload-service/job-status/${caseId}`, {
  headers: { 'X-API-Key': process.env.ACCESSFUL_API_KEY },
});
```
```java
HttpRequest.newBuilder()
    .uri(URI.create("https://api.accessful.de/api/v1/upload-service/job-status/" + caseId))
    .header("X-API-Key", System.getenv("ACCESSFUL_API_KEY"))
    .GET().build();
```
## Verify your key

Smoke-test a key by asking for a random, non-existent case. A working key returns
**404** (the case doesn't exist) — *not* 401.

1. Send a status request with a throwaway case ID:

   ```bash
   curl -i https://api.accessful.de/api/v1/upload-service/job-status/00000000-0000-0000-0000-000000000000 \
     -H "X-API-Key: ak_your_key_here"
   ```

2. Read the status line:

   - **`404 Not Found`** → your key is accepted. ✅
   - **`401 Unauthorized`** → the key is missing, malformed, or revoked.

## Auth errors

| Status | Meaning |
| --- | --- |
| `401 Unauthorized` | No key, malformed key, or a revoked/expired key. |
| `403 Forbidden` | The key is valid but lacks permission for that action. |

A missing or invalid key (**401**) is rejected at the **gateway**, before the request reaches the
application — so a 401 never carries a [Problem Details](https://docs.accessful.de/errors/) body. Rely on the status code.
A **403** comes from the application itself and carries a Problem Details body, like other
application-level errors.