Skip to main content

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
}
FieldTypeDescription
scope"all" | "library" | "shelf"Which books to process
scopeIdstring (optional)ID of the library or shelf when scope is not "all"
overwriteboolean (optional)Replace existing field values (default: false)
guidedSelectionsarray (optional)Explicit Open Library key + ISBN per book from disambiguation
throttleMsnumber (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"
}
}

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:

ParameterTypeDefaultDescription
providerMetadataProvideropen-libraryWhich provider to query (open-library, google-books, goodreads, hardcover)
isbnstring (optional)—Override the ISBN-13 lookup value
titlestring (optional)pending book titleOverride the title search term
authorstring (optional)pending book authorsOverride 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 IDClassKey requiredLookup strategy
open-libraryOpenLibraryServiceNoISBN-13, then title/author
google-booksGoogleBooksServiceOptionalISBN-13, then title/author
goodreadsGoodreadsServiceNoISBN-13, then title/author
hardcoverHardcoverServiceYesISBN-13, then title/author
audnexusAudnexusServiceNoASIN 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 — BulkMetadataService always calls the ISBN-13 provider first. The resolved ISBN is injected into EnrichInput.isbn13 for 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 taskId before enrichment begins. Progress is tracked by updating the Task.payload JSON blob after each book.
  • Cancellation — the loop re-fetches the Task row at the start of each iteration. If the status is CANCELLED it exits without processing further books.