> ## Documentation Index
> Fetch the complete documentation index at: https://komga-sdk.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Administration

> Manage Komga server settings, tasks, duplicates, and imports

Administrative operations require the role documented by the endpoint. The generated functions accept flat parameters and an options object containing the client. They return `{ data, error, request, response }` by default.

> [!WARNING]
>
> Use an HTTPS base URL when sending Basic credentials. The claim endpoint is the only operation in this guide that is intended to run before authentication.

## Claim the server

Check whether the initial admin account has been created:

```typescript theme={null}
import { getClaimStatus } from 'komga-sdk';

const result = await getClaimStatus({ client });
if (result.error) {
  console.error(result.response?.status, result.error);
} else if (result.data?.isClaimed) {
  console.log('Server is already configured');
} else {
  console.log('Server needs initial setup');
}
```

Create the initial admin account. The generated operation sends the credentials as the `X-Komga-Email` and `X-Komga-Password` headers:

```typescript theme={null}
import { claimServer } from 'komga-sdk';

const result = await claimServer(
  {
    'X-Komga-Email': 'admin@example.com',
    'X-Komga-Password': 'secure-admin-password',
  },
  { client },
);

if (result.error) {
  console.error(result.response?.status, result.error);
} else {
  console.log(`Created admin user ${result.data?.id}`);
}
```

The claim endpoint can be called only once. After a successful claim, create an authenticated client with `createKomgaClient` before calling admin operations.

## Server settings

Read settings:

```typescript theme={null}
import { getServerSettings } from 'komga-sdk';

const result = await getServerSettings({ client });
if (result.error) {
  console.error(result.response?.status, result.error);
} else {
  console.log(result.data);
}
```

Update only the fields you need. `updateServerSettings` takes a `settingsUpdateDto` flat parameter; the generated call returns a no-content response on success:

```typescript theme={null}
import { updateServerSettings } from 'komga-sdk';

const result = await updateServerSettings(
  {
    settingsUpdateDto: {
      deleteEmptyCollections: true,
      deleteEmptyReadLists: true,
      rememberMeDurationDays: 30,
      thumbnailSize: 'LARGE',
      taskPoolSize: 4,
    },
  },
  { client },
);

if (result.error) {
  console.error(result.response?.status, result.error);
} else {
  // Success is HTTP 204; there is no response payload.
  console.log(`Updated settings (${result.response?.status})`);
}
```

Common `SettingsUpdateDto` fields include:

| Field | Type | Description |
| - | - | - |
| `deleteEmptyCollections` | `boolean` | Delete collections when they become empty |
| `deleteEmptyReadLists` | `boolean` | Delete read lists when they become empty |
| `rememberMeDurationDays` | `number` | Remember-me cookie duration |
| `thumbnailSize` | `"DEFAULT" \| "MEDIUM" \| "LARGE" \| "XLARGE"` | Generated thumbnail size |
| `taskPoolSize` | `number` | Concurrent background tasks |
| `serverPort` | `number` | Server port (restart required) |
| `serverContextPath` | `string` | URL context path |
| `serverPort` and `serverContextPath` affect how the server is reached and may require a restart or a matching reverse-proxy configuration. Settings updates are partial: omit fields you do not intend to change, and treat the `204` response as confirmation rather than expecting updated settings in `result.data`. | | |

## Operational cautions

The task queue is shared by scans, metadata refreshes, imports, and thumbnail generation. `emptyTaskQueue` cancels pending work; it does not undo work that has already completed and it does not return a task list. Use it as a maintenance action, not as a polling mechanism.

Historical events are paginated. `HistoricalEventDto` includes an `id`, `timestamp`, `type`, string-valued `properties`, and optional `bookId` and `seriesId`; keep requesting pages until the page is marked `last` when exporting an audit trail.

The announcements response is a JSON feed. Its `items` contain `id`, `title`, optional `summary`/`content_html`, and a `_komga.read` flag. Mark only the IDs you have displayed; `markAnnouncementsRead` accepts an array in its flat `body` parameter and returns `204` with no payload.

Directory browsing is an admin helper for choosing library roots. Send both `path` and `showFiles` in `directoryRequestDto`; the server can reject paths that are outside its configured filesystem or container mount. Do not treat a listing as permission to read arbitrary files.

> [!WARNING]
>
> `emptyTaskQueue`, settings updates, imports, duplicate-page actions, directory browsing, and thumbnail management are administrative operations. A `403` means the authenticated account lacks the endpoint's required role; it is not a malformed SDK call.

## Review known duplicate pages

Known hashes can be filtered by one or more `action` values (`DELETE_AUTO`, `DELETE_MANUAL`, or `IGNORE`) in addition to `page`, `size`, and `sort`. The result is paginated and each entry includes its match count, optional byte size, action, and timestamps:

```typescript theme={null}
import {
  getKnownPageHashes,
  getKnownPageHashThumbnail,
} from 'komga-sdk';

const known = await getKnownPageHashes(
  {
    action: ['DELETE_AUTO'],
    page: 0,
    size: 50,
    sort: ['lastModified,desc'],
  },
  { client },
);

for (const entry of known.data?.content ?? []) {
  const preview = await getKnownPageHashThumbnail(
    { pageHash: entry.hash },
    { client },
  );
  if (!preview.error && preview.data) {
    const previewUrl = URL.createObjectURL(preview.data);
    console.log(entry.hash, entry.matchCount, previewUrl);
    // Revoke previewUrl when the preview leaves the UI.
  }
}
```

Use `getPageHashMatches` before deleting when you need to review the affected books. `deleteDuplicatePagesByPageHash` removes all matches; `deleteSingleMatchByPageHash` removes one `PageHashMatchDto` returned by the matches endpoint. Both actions are destructive and should be followed by a fresh query rather than assuming the previous page is still current.

> [!NOTE]
>
> The duplicate thumbnail endpoints accept an optional `resize` query parameter only for unknown hashes. Image results are `Blob | File`; they are not JSON metadata.

## Duplicate-review recipe

For a review UI, list a small page of unknown hashes, load previews sequentially (or with a bounded queue), and classify each hash before deleting:

```typescript theme={null}
import {
  createOrUpdateKnownPageHash,
  deleteDuplicatePagesByPageHash,
  getUnknownPageHashThumbnail,
  getUnknownPageHashes,
} from 'komga-sdk';

const unknown = await getUnknownPageHashes({ page: 0, size: 10 }, { client });

for (const candidate of unknown.data?.content ?? []) {
  const preview = await getUnknownPageHashThumbnail(
    { pageHash: candidate.hash, resize: 320 },
    { client },
  );
  if (preview.error) continue;

  // Decide from the preview and matchCount in your UI.
  const decision = window.confirm(
    `Delete ${candidate.matchCount} matches for ${candidate.hash}?`,
  )
    ? 'DELETE_AUTO'
    : 'IGNORE';
  const marked = await createOrUpdateKnownPageHash(
    {
      pageHashCreationDto: {
        hash: candidate.hash,
        action: decision,
        size: candidate.size,
      },
    },
    { client },
  );
  if (marked.error) continue;

  if (decision === 'DELETE_AUTO') {
    await deleteDuplicatePagesByPageHash({ pageHash: candidate.hash }, { client });
  }
}
```

Do not create object URLs for previews without revoking them, and do not run an unbounded parallel delete loop against a large library.

## Server information and announcements

These operations take only the client in their options object:

```typescript theme={null}
import { getActuatorInfo, getReleases, getAnnouncements } from 'komga-sdk';

const [info, releases, announcements] = await Promise.all([
  getActuatorInfo({ client }),
  getReleases({ client }),
  getAnnouncements({ client }),
]);

if (info.data) console.log('Runtime:', info.data);
if (releases.data) console.log(releases.data);
if (announcements.data) {
  for (const announcement of announcements.data.items) {
    console.log(`${announcement.id}: ${announcement.title}`);
  }
}
```

Mark announcements as read by passing the array as the flat `body` parameter. Success is HTTP 204:

```typescript theme={null}
import { markAnnouncementsRead } from 'komga-sdk';

const result = await markAnnouncementsRead(
  { body: ['announcement-1', 'announcement-2'] },
  { client },
);
if (result.error) console.error(result.response?.status, result.error);
```

## Tasks and historical events

Clear pending tasks with `emptyTaskQueue`. It returns HTTP 204 and no payload:

```typescript theme={null}
import { emptyTaskQueue } from 'komga-sdk';

const result = await emptyTaskQueue({ client });
if (result.error) {
  console.error(result.response?.status, result.error);
} else {
  console.log('Task queue cleared');
}
```

List historical events with flat pagination parameters:

```typescript theme={null}
import { getHistoricalEvents } from 'komga-sdk';

const result = await getHistoricalEvents(
  { page: 0, size: 50, sort: ['timestamp,desc'] },
  { client },
);

if (result.data) {
  for (const event of result.data.content) {
    console.log(`${event.timestamp}: ${event.type}`);
  }
}
```

## Directory browsing

`getDirectoryListing` maps the `directoryRequestDto` parameter to the request body. The DTO requires both `path` and `showFiles`:

```typescript theme={null}
import { getDirectoryListing } from 'komga-sdk';

const result = await getDirectoryListing(
  {
    directoryRequestDto: {
      path: '/media/comics',
      showFiles: false,
    },
  },
  { client },
);

if (result.data) {
  console.log(`Parent: ${result.data.parent ?? '/'}`);
  for (const directory of result.data.directories) {
    console.log(directory.name);
  }
}
```

## Duplicate page detection

List unknown hashes and inspect a thumbnail. Both operations use flat parameters; thumbnail responses are `Blob | File` values:

```typescript theme={null}
import { getUnknownPageHashes, getUnknownPageHashThumbnail } from 'komga-sdk';

const hashes = await getUnknownPageHashes({ page: 0, size: 20 }, { client });
if (hashes.data) {
  for (const entry of hashes.data.content) {
    const thumbnail = await getUnknownPageHashThumbnail(
      { pageHash: entry.hash },
      { client },
    );
    if (!thumbnail.error && thumbnail.data) {
      const url = URL.createObjectURL(thumbnail.data);
      console.log(entry.hash, url);
    }
  }
}
```

List matches for a hash and classify it:

```typescript theme={null}
import {
  getPageHashMatches,
  createOrUpdateKnownPageHash,
  deleteDuplicatePagesByPageHash,
} from 'komga-sdk';

const pageHash = 'abc123def456';
const matches = await getPageHashMatches(
  { pageHash, page: 0, size: 20 },
  { client },
);
if (matches.data) console.log(matches.data.content);

const marked = await createOrUpdateKnownPageHash(
  {
    pageHashCreationDto: {
      hash: pageHash,
      action: 'DELETE_AUTO',
      size: 12345,
    },
  },
  { client },
);
if (marked.error) console.error(marked.response?.status, marked.error);

const deleted = await deleteDuplicatePagesByPageHash({ pageHash }, { client });
if (deleted.error) console.error(deleted.response?.status, deleted.error);
```

`getKnownPageHashes` accepts `action`, `page`, `size`, and `sort` directly. `getKnownPageHashThumbnail({ pageHash }, { client })` returns `Blob | File`.

Deleting one match uses the generated `pageHashMatchDto` parameter. Pass a match returned by `getPageHashMatches` when you need this narrower operation:

```typescript theme={null}
import { deleteSingleMatchByPageHash } from 'komga-sdk';

const result = await deleteSingleMatchByPageHash(
  {
    pageHash: 'abc123def456',
    pageHashMatchDto: {
      bookId: 'book-123',
      fileName: 'book.cbz',
      fileSize: 123456,
      mediaType: 'image/jpeg',
      pageNumber: 5,
      url: '/api/v1/books/book-123/pages/5',
    },
  },
  { client },
);
if (result.error) console.error(result.response?.status, result.error);
```

## Transient books

Scan a folder, analyze a transient book, and fetch its page representation:

```typescript theme={null}
import {
  scanTransientBooks,
  analyzeTransientBook,
  getPageByTransientBookId,
} from 'komga-sdk';

const scanned = await scanTransientBooks(
  { scanRequestDto: { path: '/downloads/comics' } },
  { client },
);
if (scanned.data?.[0]) {
  const transientBook = await analyzeTransientBook(
    { id: scanned.data[0].id },
    { client },
  );
  console.log(transientBook.data);

  const page = await getPageByTransientBookId(
    { id: scanned.data[0].id, pageNumber: 1 },
    { client },
  );
  if (!page.error && page.data) {
    // The generated response is a string, not a Blob.
    console.log(page.data);
  }
}
```

Import books with the `bookImportBatchDto` parameter. The endpoint returns HTTP 202 with an `unknown` response body, so do not read `result.data` as an imported book list:

```typescript theme={null}
import { importBooks } from 'komga-sdk';

const result = await importBooks(
  {
    bookImportBatchDto: {
      books: [
        {
          sourceFile: '/downloads/comics/book1.cbz',
          seriesId: 'series-123',
          destinationName: 'Chapter 1.cbz',
        },
      ],
      copyMode: 'COPY',
    },
  },
  { client },
);
if (result.error) console.error(result.response?.status, result.error);
else console.log(`Import accepted (${result.response?.status})`);
```

`copyMode` controls what happens to the source file:

| Mode | Behavior |
| - | - |
| `COPY` | Copy into the destination and keep the source |
| `MOVE` | Move into the destination and remove the source |
| `HARDLINK` | Create a hard link; source and destination must share a filesystem |

Each `BookImportDto` requires `sourceFile` and `seriesId`. Set `destinationName` to control the library filename, or `upgradeBookId` when the import should replace an existing book. The import is accepted asynchronously (`202`), so monitor the library or history rather than expecting imported books in `result.data`.

## Client settings

Global settings are sent as a map from setting name to `ClientSettingGlobalUpdateDto`:

```typescript theme={null}
import { saveGlobalSetting, deleteGlobalSettings } from 'komga-sdk';

const saved = await saveGlobalSetting(
  {
    body: {
      'webui.theme.mode': {
        allowUnauthorized: false,
        value: 'dark',
      },
    },
  },
  { client },
);
if (saved.error) console.error(saved.response?.status, saved.error);

const removed = await deleteGlobalSettings(
  { body: ['webui.theme.mode'] },
  { client },
);
if (removed.error) console.error(removed.response?.status, removed.error);
// Successful deletion is HTTP 204 and has no response payload.
```

## Error handling

Generated calls are non-throwing by default. Check `result.error` and use `result.response?.status` for transport status. To make the throwing boundary explicit, use `unwrap`; it raises a typed `KomgaApiError`:

```typescript theme={null}
import { getServerSettings, KomgaApiError, unwrap } from 'komga-sdk';

try {
  const settings = unwrap(await getServerSettings({ client }));
  console.log(settings.thumbnailSize);
} catch (error) {
  if (error instanceof KomgaApiError) {
    console.error(`Komga returned ${error.status}`, error.body);
  } else {
    throw error;
  }
}
```

## Next steps

> **[Users](/guides/users)**
>
> Manage users and their permissions.

> **[Libraries](/guides/libraries)**
>
> Configure and scan libraries.

> **[Authentication](/authentication)**
>
> Configure Basic or API-key authentication.

> **[Troubleshooting](/guides/troubleshooting)**
>
> Diagnose authentication and request failures.


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