# How the Immich API Is Structured and API Key Authentication Works

> Explore the Immich API structure built with NestJS and learn how to authenticate requests using SHA-256 hashed API keys in the x-api-key header or apiKey query parameter.

- Repository: [Immich/immich](https://github.com/immich-app/immich)
- Tags: internals
- Published: 2026-02-27

---

**Immich organizes its REST API under a `/api` prefix using NestJS controllers, and secures access via SHA‑256 hashed API keys that must be sent in the `x-api-key` header or `apiKey` query parameter for every request.**

The Immich backend, located in the `immich-app/immich` repository, is built with NestJS and exposes a conventional REST API grouped by functional domain. Understanding the **Immich API structure** and **API key authentication** mechanisms is essential for building secure integrations, whether using direct HTTP calls or the official TypeScript SDK.

## NestJS API Architecture and Routing Structure

All public endpoints live under the `/api` prefix and are grouped by functional domain—users, assets, albums, and authentication. Each domain is implemented by a **controller** class decorated with `@Controller('<route>')`, with individual methods annotated using `@Get`, `@Post`, `@Put`, or `@Delete`.

Common route patterns include:

- `/api/users` → `UserController`
- `/api/assets` → `AssetController`
- `/api/albums` → `AlbumController`
- `/api/api-keys` → `ApiKeyController`
- `/api/auth` → `AuthController`

To protect a route, developers apply the custom **`@Authenticated`** decorator. This decorator tells the **`AuthGuard`**—implemented in [`server/src/middleware/auth.guard.ts`](https://github.com/immich-app/immich/blob/main/server/src/middleware/auth.guard.ts)—which permission is required and whether the route is admin-only or shared-link-aware.

## API Key Authentication Workflow

API keys in Immich follow a strict creation and validation pipeline that ensures secrets are never stored in plain text.

### Creating API Keys

A client initiates access by sending a `POST /api/api-keys` request with a JSON body describing the key name and desired permissions. The `ApiKeyController.createApiKey` method forwards this to `ApiKeyService.create` in [`server/src/controllers/api-key.controller.ts`](https://github.com/immich-app/immich/blob/main/server/src/controllers/api-key.controller.ts).

In [`server/src/services/api-key.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/api-key.service.ts), the service generates a random 32-byte token, stores **only its SHA‑256 hash** in the `api_key` table, and returns the **plain token once** in the response (`APIKeyCreateResponseDto`). The clear-text secret is never persisted to the database.

### Sending API Keys in Requests

Subsequent API calls must include the key **once per request** using one of two methods:

- As the header `x-api-key` (preferred and case-insensitive), defined in `ImmichHeader.ApiKey` in [`server/src/enum.ts`](https://github.com/immich-app/immich/blob/main/server/src/enum.ts)
- Or as the query parameter `apiKey`

### Validation and Authorization

The `AuthGuard.canActivate` method extracts the candidate value from the header or query string and passes it to `AuthService.authenticate`. Inside [`server/src/services/auth.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/auth.service.ts), the `validate` method invokes `validateApiKey` when an `apiKey` is present.

The validation process:
1. Hashes the supplied token using SHA‑256
2. Looks up the hash in the `api_key` repository
3. If found, builds an `AuthDto` containing the owning user and the **permission list** stored with the key

### Permission Enforcement

When a route requires a specific permission, `AuthService.validate` calls `isGranted` from [`server/src/utils/access.ts`](https://github.com/immich-app/immich/blob/main/server/src/utils/access.ts). This helper checks whether the key’s permission set contains the requested permission or the special wildcard `Permission.All`.

Inside controller methods, the current user and key are available via the **`@Auth()`** parameter decorator. For example:

```typescript
@Get('me')
@Authenticated({ permission: false })
async getMyApiKey(@Auth() auth: AuthDto): Promise<APIKeyResponseDto> {
  return this.service.getMine(auth);
}

```

## Using the Official TypeScript SDK

Immich provides a **TypeScript SDK** that mirrors the server API structure. The SDK handles authentication globally via the `init` function:

```typescript
import { init } from 'immich-openapi-typescript-sdk';

init({
  baseUrl: 'https://my-immich-instance/api',
  apiKey: '<your-x-api-key>',
});

```

As implemented in [`open-api/typescript-sdk/src/index.ts`](https://github.com/immich-app/immich/blob/main/open-api/typescript-sdk/src/index.ts), the `init` function stores the key in default fetch options as the `x-api-key` header, automatically injecting it into all subsequent requests. The SDK rejects any attempt to set that header manually on individual calls.

## Practical Code Examples

### Creating an API Key via cURL

```bash
curl -X POST https://my-immich/api/api-keys \
  -H "Authorization: Bearer <user-jwt>" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "mobile-app",
        "permissions": ["asset.read","asset.upload"]
      }'

```

The response returns the secret only once:

```json
{
  "secret": "a1b2c3d4…",
  "apiKey": {
    "id": "c5d8e9f0-1234-5678-abcd-ef0123456789",
    "name": "mobile-app",
    "createdAt": "2024-05-01T12:34:56Z",
    "updatedAt": "2024-05-01T12:34:56Z",
    "permissions": ["asset.read","asset.upload"]
  }
}

```

### Authenticating Requests with cURL

```bash
curl -X GET https://my-immich/api/assets \
  -H "x-api-key: a1b2c3d4…"

```

The `AuthGuard` validates the key, loads the permission list, and allows the request because `Permission.AssetRead` is present.

### Using the TypeScript SDK

```typescript
import {
  init,
  getAssetOriginalPath,
  listAssets,
} from 'immich-openapi-typescript-sdk';

// Initialise SDK with base URL and API key
init({
  baseUrl: 'https://my-immich/api',
  apiKey: process.env.IMMICH_API_KEY!, // store secret in env, never hard-code
});

// List assets (the SDK automatically adds the x-api-key header)
const assets = await listAssets({});

// Download a specific asset
const downloadUrl = `${init.getBaseUrl()}${getAssetOriginalPath('abcd1234')}`;
const data = await fetch(downloadUrl);

```

### Server-Side Permission Checks

In `server/src/controllers`, protected routes use the `@Authenticated` decorator with specific permissions:

```typescript
@Delete(':id')
@Authenticated({ permission: Permission.AssetDelete })
async deleteAsset(
  @Auth() auth: AuthDto,
  @Param('id') id: string,
) {
  // auth.apiKey?.permissions already guaranteed to contain AssetDelete
  return this.assetService.delete(auth.user.id, id);
}

```

## Key Implementation Files

Understanding the **Immich API structure** requires familiarity with these core files:

- **[`server/src/controllers/api-key.controller.ts`](https://github.com/immich-app/immich/blob/main/server/src/controllers/api-key.controller.ts)** – Defines REST routes for API key management (`POST /api-keys`, `GET /api-keys`, etc.)
- **[`server/src/services/api-key.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/api-key.service.ts)** – Generates tokens, stores SHA‑256 hashes, and validates permissions
- **[`server/src/dtos/api-key.dto.ts`](https://github.com/immich-app/immich/blob/main/server/src/dtos/api-key.dto.ts)** – Schema definitions for create/update requests and responses
- **[`server/src/middleware/auth.guard.ts`](https://github.com/immich-app/immich/blob/main/server/src/middleware/auth.guard.ts)** – Extracts headers/query parameters and invokes `AuthService.authenticate`
- **[`server/src/services/auth.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/auth.service.ts)** – Contains `validateApiKey` logic for hashing and lookup, plus `AuthDto` construction
- **[`server/src/enum.ts`](https://github.com/immich-app/immich/blob/main/server/src/enum.ts)** – Central list of `Permission.*` constants and `ImmichHeader.ApiKey` definition
- **[`server/src/utils/access.ts`](https://github.com/immich-app/immich/blob/main/server/src/utils/access.ts)** – Implements the `isGranted` helper for permission verification
- **[`open-api/typescript-sdk/src/index.ts`](https://github.com/immich-app/immich/blob/main/open-api/typescript-sdk/src/index.ts)** – SDK entry point showing how clients initialize and store API keys globally

## Summary

- **Immich API structure**: NestJS controllers group endpoints under `/api` by domain (users, assets, albums), protected by the `@Authenticated` decorator and `AuthGuard`.
- **API key creation**: Generated as 32-byte random tokens, stored as SHA‑256 hashes only, with the plain secret returned once during creation in `ApiKeyService.create`.
- **Authentication methods**: Send the key via `x-api-key` header (preferred) or `apiKey` query parameter on every request.
- **Validation flow**: `AuthGuard` extracts the key, `AuthService.validateApiKey` hashes and lookups the token, returning an `AuthDto` with user and permissions.
- **Permission model**: Fine-grained access control via `isGranted` in [`server/src/utils/access.ts`](https://github.com/immich-app/immich/blob/main/server/src/utils/access.ts), supporting specific permissions or the `Permission.All` wildcard.
- **SDK integration**: The TypeScript SDK handles `x-api-key` header injection automatically through the `init()` function.

## Frequently Asked Questions

### How are API keys stored securely in Immich?

API keys are never stored in plain text. According to the source code in [`server/src/services/api-key.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/api-key.service.ts), Immich generates a random 32-byte token, calculates its SHA‑256 hash, and persists only the hash to the `api_key` database table. The original secret is returned to the client exactly once during creation and then discarded.

### Can I restrict an API key to specific permissions?

Yes. When creating a key via `POST /api/api-keys`, you specify a `permissions` array in the request body (e.g., `["asset.read", "asset.upload"]`). The `AuthService.validate` method in [`server/src/services/auth.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/auth.service.ts) enforces these restrictions via the `isGranted` helper, which checks if the key's permission set includes the required action or the `Permission.All` wildcard.

### What header should I use to send an API key?

Use the `x-api-key` header, defined in [`server/src/enum.ts`](https://github.com/immich-app/immich/blob/main/server/src/enum.ts) as `ImmichHeader.ApiKey`. This is the preferred, case-insensitive method. Alternatively, you may include the key as an `apiKey` query parameter, though the header approach is recommended for better security and logging practices.

### How does the TypeScript SDK handle authentication?

The SDK exports an `init` function from [`open-api/typescript-sdk/src/index.ts`](https://github.com/immich-app/immich/blob/main/open-api/typescript-sdk/src/index.ts) that accepts an `apiKey` parameter. This value is stored in the default fetch configuration as the `x-api-key` header, ensuring all generated client functions automatically include authentication without manual header manipulation.