Metadata Enrichment API
This page documents the bulk metadata enrichment endpoints added under BulkMetadataModule. All routes require a valid JWT and admin role.
Base path
/api/v1/admin/metadata-match
Endpoints
GET /config
Returns the current field-source configuration array stored in ServerSettings (key metadata_field_config). Falls back to the hardcoded default if not yet saved.
Response — array of FieldConfigItem:
[
{ "field": "isbn13", "provider": "open-library", "enabled": true },
{ "field": "title", "provider": "google-books", "enabled": true },
{ "field": "description", "provider": "google-books", "enabled": true },
...
]
PUT /config
Saves the field-source configuration.
Body — UpdateFieldConfigDto:
{
"config": [
{ "field": "isbn13", "provider": "open-library", "enabled": true },
...
]
}
GET /throttle
Returns the current throttle delay (ServerSettings key metadata_match_throttle_ms, default 500).
{ "throttleMs": 500 }
PUT /throttle
Updates the throttle delay (50–5000 ms).
{ "throttleMs": 800 }
POST /candidates
Fetches up to 3 Open Library candidates for a given book, used by the guided disambiguation modal.
Body — CandidatesRequestDto:
{ "bookId": "clxxx...", "limit": 3 }
Response — array of CandidateDto:
[
{
"openLibraryKey": "/works/OL1234W",
"title": "The Hobbit",
"authors": ["J.R.R. Tolkien"],
"year": 1937,
"coverUrl": "https://covers.openlibrary.org/b/id/8406786-M.jpg",
"isbn13": "9780261102217"
}
]
POST /run
Starts a bulk enrichment run as a background job. Returns the created Task ID immediately.
Body — RunBulkMatchDto:
{
"scope": "library",
"scopeId": "clxxx...",
"overwrite": false,
"guidedSelections": [
{
"bookId": "clyyy...",
"openLibraryKey": "/works/OL1234W",
"isbn13": "9780261102217"
}
],
"throttleMs": 500
}
| Field | Type | Description |
|---|---|---|
scope | "all" | "library" | "shelf" | Which books to process |
scopeId | string (optional) | ID of the library or shelf when scope is not "all" |
overwrite | boolean (optional) | Replace existing field values (default: false) |
guidedSelections | array (optional) | Explicit Open Library key + ISBN per book from disambiguation |
throttleMs | number (optional) | Override the saved throttle for this run |
Response:
{ "taskId": "clzzz..." }
POST /cancel/:taskId
Sets the task status to CANCELLED. The background loop checks cancellation at the start of each book and exits cleanly.
Returns 204 No Content.
GET /tasks
Returns the 20 most recent BULK_METADATA_MATCH tasks, ordered newest first.
GET /task/:taskId
Returns a single task for polling. The payload field contains progress information:
{
"id": "clzzz...",
"status": "PROCESSING",
"payload": {
"processed": 12,
"total": 48,
"currentBook": "The Name of the Wind"
}
}
Book Drop metadata search
The book drop review flow exposes a separate per-book search endpoint under BookDropModule. All routes require a valid JWT and admin role.
GET /api/v1/book-drop/:id/search-metadata
Search external metadata providers for a specific pending book. The pending book's title and authors are used as the default query and can be overridden via query parameters.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
provider | MetadataProvider | open-library | Which provider to query (open-library, google-books, goodreads, hardcover) |
isbn | string (optional) | — | Override the ISBN-13 lookup value |
title | string (optional) | pending book title | Override the title search term |
author | string (optional) | pending book authors | Override the author search term |
Response — array of MetadataResultDto (same shape as /books/:id/search-metadata).
This endpoint is used by the Admin Book Review page to allow admins to search and apply metadata to books in the drop queue before approving them to the main library.
Providers
| Provider ID | Class | Key required | Lookup strategy |
|---|---|---|---|
open-library | OpenLibraryService | No | ISBN-13, then title/author |
google-books | GoogleBooksService | Optional | ISBN-13, then title/author |
goodreads | GoodreadsService | No | ISBN-13, then title/author |
hardcover | HardcoverService | Yes | ISBN-13, then title/author |
audnexus | AudnexusService | No | ASIN only (GET /books/:asin) |
PROVIDER_ORDER in metadata.service.ts defines the canonical call order used by both MetadataService and BulkMetadataService. Add new providers there first.
Audnexus
AudnexusService is an ASIN-only provider backed by the community-hosted api.audnex.us REST API. It exposes a single public method:
searchByAsin(asin: string): Promise<MetadataResult | null>
When a book has no ASIN the service is never called — BulkMetadataService checks book.asin before dispatching. The provider is disabled by default in DEFAULT_FIELD_CONFIG; users opt in via the Field Sources UI.
The base URL can be overridden with the optional AUDNEXUS_BASE_URL env var (useful for self-hosted instances). All HTTP failures (non-2xx, network errors) are treated as soft misses and return null.
Architecture notes
- Provider chaining —
BulkMetadataServicealways calls the ISBN-13 provider first. The resolved ISBN is injected intoEnrichInput.isbn13for every subsequent provider call, improving match accuracy. - Field-level apply — only the fields in the active config (enabled + assigned to a provider) are written. Other fields are untouched.
- Background jobs — runs are fire-and-forget async methods; the controller returns the
taskIdbefore enrichment begins. Progress is tracked by updating theTask.payloadJSON blob after each book. - Cancellation — the loop re-fetches the
Taskrow at the start of each iteration. If the status isCANCELLEDit exits without processing further books.