Import file format
This page is for authors of an external tool (a library manager, a tagger, a
catalogue exporter) who want the file their tool produces to import cleanly into
audiosilo-meta. It is the format contract; the ingestion paths, validation
layers, and authoring rules are in Contributing data to Meta.
If your tool works from local audio files rather than existing metadata, emit the folder-scan envelope instead (see Scanning local files).
Target the audiosilo-books envelope
Emit a single self-identifying JSON object. The format field is the contract:
the intake bot sniffs it and trusts it over the submitter's dropdown, so a
correctly-labelled file can never be misdetected as a bare OpenAudible array
(which would silently drop authors, narrators, and series).
{
"format": "audiosilo-books",
"version": 1,
"books": [
{ "…one flat record per book…": "" }
]
}
formatmust be exactly"audiosilo-books".versionmust be exactly1(a different version is rejected loudly rather than misparsed).booksis an array of flat, factual per-book records (below).
The same shape is read by two mirrored consumers - the Go automated intake
(internal/importer/audiosilobooks.go) and the site's in-browser parser
(site/src/lib/import-parse.ts) - so a file that imports through the bot also
previews in the browser.
Per-book fields
Each entry in books is a flat object with these keys (all snake_case). A book
is imported only if it carries a title, at least one author, at least one
narrator, and a recognized language - a book missing any of those four
required fields is skipped with a warning. Every other field is optional;
omit a field you cannot verify rather than guessing a value.
| Key | Type | Notes |
|---|---|---|
title | string | Required. The work title. If it is already "Title: Subtitle", the subtitle is split back off to form the short work title. |
subtitle | string | The subtitle, when carried separately from the title. |
authors | string array | Required (at least one). Canonical form is an array; a comma-joined string is also accepted. Names should already be clean (no trailing role qualifiers). |
narrators | string array | Required (at least one). Same shape as authors. |
series | string | The series name. |
series_position | string | A number ("1", "2.5") or an omnibus range ("1-3.5"). If it fails the position pattern the book still imports, but is not placed in the series. |
asin | string | The Audible ASIN. This is the identity and dedup key. The envelope carries no marketplace region, so a present ASIN defaults to region us. |
language | string | Required. An ISO 639-1 code (en) or the English language word (English). An absent or unmappable language skips the book. |
release_date | string | YYYY-MM-DD. |
publisher | string | The recording's publisher. |
runtime_min | integer | Total runtime in minutes (positive). |
abridged | boolean | Tri-state: omit when unknown - present only when the source actually states it. |
cover_url | string | Cover image URL. https:// only; any other value is dropped. |
chapters | integer | A chapter count, not a per-chapter array. The importer ignores it (chapter timing lives on recordings, sourced elsewhere), so it is optional and informational. |
Not consumed. isbn may appear in the shape but the bulk importer keys
identity on ASIN alone and does not map ISBN in. Any field not listed above
(genre, ratings, publisher blurb, personal listening state, local file paths) is
dropped - this is a facts-only database.
The rules that govern the values
Both apply to every field, and both are enforced downstream:
- Facts only, never fabricated. Emit only values your source actually provides. If a fact is not known, omit the optional field - do not invent an ASIN, a date, or an abridged flag.
- No marketing or personal data. Publisher copy, genres, ratings, star counts, and per-user state are not imported even if present, so there is no value in emitting them.
A minimal valid file
{
"format": "audiosilo-books",
"version": 1,
"books": [
{
"title": "Fugitive Telemetry",
"authors": ["Martha Wells"],
"narrators": ["Kevin R. Free"],
"series": "The Murderbot Diaries",
"series_position": "6",
"asin": "B08XYZ1234",
"language": "en",
"runtime_min": 273,
"release_date": "2021-04-27",
"publisher": "Recorded Books"
}
]
}
What the importer does with it
When the file is ingested, each book runs through the shared importer pipeline, so the same identity and dedup rules apply as any other import (full detail under bulk importers):
- ASIN dedup. A book whose ASIN already exists in the catalogue is skipped.
An import that produces no new records and dedupes nothing is flagged
needs-human, notduplicate- usually a sign the file did not match its declared format. - Work identity is (title slug + author set), with per-volume disambiguation so distinct series volumes never merge into one work.
- ASIN merge. A same-work, same-narrator entry whose only new fact is another ASIN merges that ASIN into the existing recording (guarded by runtime and abridged deltas) rather than minting a duplicate recording.
- Series joining. A series is found by its name, and a name is not an
identity, so a row joins a same-named catalogued series only when the series
fits it. Its authors must fit (a series one author dominates is closed to
an unrelated author's book of the same name), and so must its language: a
series whose derived language - the strict majority of its members'
languages - is known and differs from the row's is closed to the row, even
when the author is the same (an Italian edition does not join the Spanish
series of the same name). A closed series is stepped past: the row joins or
founds the next series of that name (
lost-fleet-2), or, under--existing-series-only, the claim is dropped and reported. A series whose members tie between two languages, and a row whose language is unknown, are never judged by language. - Series qualifiers. A claim also reaches a catalogued series by what the
two names state, not only by their exact spelling. A trailing edition
decoration (
[German Edition],(Deutsche Ausgabe)) and a trailing reading-order qualifier ((Chronological Order),(Published Order),(in chronologischer Reihenfolge)) are read off the name, which leaves a base name and a language. A claim reaches a held series with the same base in the same language. The language is the decoration's when there is one; otherwise it is the held series' derived language, or the row's own language for the claim. SoThrone of Glass (Deutsche Ausgabe)finds the heldThrone of Glass [German Edition], and a German row naming the plainHarry Potterjoins the German edition series instead of foundingharry-potter-2beside it. A reading-order variant (a series withordering_of), or any series held in chronological or recommended order, is reached only by a claim that states that ordering; an unqualified claim reaches only a series in publication order or one stating none. These series are tried after the same-named ones and are judged by the same author and language rules, so under--existing-series-onlya reached series is a join, not a drop. The importer never writestranslation_of,orderingorordering_of, and a series it founds keeps the source's name exactly as given. Rows in one batch that spell the same series in different ways are judged together, against one record of that series' authors, so a batch of another author's books cannot take over a series by spreading across spellings. A claim that adds a membership to a series stored under a genuinely different name (not merely another case or bracket spelling) is listed in the run's notes. The intake bot andlibex-selectresolve series through this same rule. - Edition markers. A trailing
(Unabridged)/(Abridged)in the title is stripped before identity and, ifabridgedwas not set, seeds the recording's tri-state abridged flag. - Retired slugs. A person, series, or work whose identity resolves to a slug a repair merge has already retired is matched against the surviving record rather than minted fresh at the old address (see the data model).
Submitting a generated file
The file is attached to the Import a library issue form
(data:import), dragged into its Export file field - files up to 25 MiB are
accepted. The intake workflow sniffs the envelope, runs it through the
importer, and - on a clean result - opens a bot pull request with the canonical
records for review. See
Intake automation
for the verdicts and the pull-request flow.
Your tool can also create that issue over the GitHub API. Note that the API drops
labels on issues opened by non-collaborators, so an API-created intake issue
arrives label-less and waits for a maintainer to apply the routing label before
the workflow admits it (the labeled trigger handles this). The sibling
audiosilo-sidecars tool submits this way.
Testing before you submit
- In the browser, offline. Drop the file on
/import. It runs the same parser and diffs against the live catalogue, so you see exactly what is new. Nothing leaves the device. - Locally, end to end. The envelope is ingested through the intake tooling
rather than a dedicated
metaimportsubcommand;internal/importer.IsAudiosiloBooksEnvelopeis the cheap format check andRunAudiosiloBooksperforms a real import (pass a dry-run to plan without writing).
Local files: emit the folder-scan envelope
If your tool inspects audio files directly (tags, ffprobe) rather than
re-exporting existing metadata, target the folder-scan envelope instead - it is
the same idea with per-field provenance, which is what a scanner can honestly
supply:
{ "format": "audiosilo-folder-scan", "version": 1, "books": [ "…" ] }
This is the shape metascan produces (see
Scanning local files); the
pkg/scan package is the reference producer. Each book records where every field
came from (tag / path / filename), and unknown fields are omitted rather
than guessed - the same facts-only rule, expressed through provenance.