Skip to content

Product Files v4

Sub-resource of products holding the binary assets that belong to a product: the author's source manuscript, the e-book files distributors receive, the print-ready PDFs a print-on-demand supplier needs, the marketing cover image, the reading extract and the HTML preview of the opening pages. One product can carry many of them.

Each record describes one asset. What the asset is for is carried by its type; the fields that describe the bytes — file name, size, MIME type, SHA-1, image dimensions, EPUB version — are derived by the server rather than supplied by the client. A record may instead register a file hosted elsewhere, in which case it holds a reference and the platform never receives the bytes at all.


Endpoints


GET /resource/v4/products/{product_id}/files

Retrieve the files of a product

Description

Returns all file records of the product, paginated (100 per page by default). Exactly six fields are filterable — type, labels, mime_type, file_name, property_epub_version and channel — and any other query parameter that is not a pagination or sorting parameter is rejected with 400 Bad Request. The scalar operators apply on all six except labels, which accepts equality only; see Resource Queries.

Two behaviours are easy to trip over: labels takes a single exact label and returns nothing for a comma-separated list, and channel needs the literal value NULL to select channel-independent files.

…/assets is a deprecated alias for …/files. It returns the same records, but the download and validation sub-paths are not available under it. New integrations should address …/files.

Input parameters

Path parameters

Parameter Type Default Nullable Description
product_id string No UUID (or legacy integer id) of the product.

Query parameters

Parameter Type Default Nullable Description
channel string No Filter by distribution channel, e.g. `?channel=google`. The literal `?channel=NULL` is special-cased to select files that are *not* restricted to a channel — the usual case.
file_name string No Filter by the uploaded file name. An operator gives partial matches, e.g. `?file_name__endswith=.epub`.
labels string No Filter by a single label, matched exactly — `?labels=auto` does not match `autogenerated`, and equality is the only operator accepted (`?labels__contains` is a `400 Bad Request`). A comma-separated list is accepted but always returns an empty list, even for a file that carries every label named; one request per label, intersected client-side, is the way to express "and".
mime_type string No Filter by media type, e.g. `?mime_type=application/epub+zip`. Records that carry no media type at all — URL registrations and older records — are selected with `?mime_type__isnull`.
property_epub_version string No Filter EPUBs by version: `2` or `3`. Any other value is rejected with `400 Bad Request`. Because detection falls back to EPUB 2 whenever EPUB 3 was not positively identified, `?property_epub_version=2` also returns files that were merely not recognised as EPUB 3; files for which no version was detected at all are returned by neither value.
sort string No Sort order. Two field names are defined for this resource and are the two to rely on: `id` and `timestamp`. Other names may or may not resolve, and one that does not is rejected with `400 Bad Request`. The two defined orders do not always agree, because old records carry the timestamp of the original import rather than of the date the record was added here. For the syntax see [Resource Queries](/resource_queries/).
type string No Filter by file type, e.g. `?type=ebook_epub` to fetch only the EPUB editions. An unknown type is rejected with `400 Bad Request` rather than returning an empty result. `?type=audiobook` matches nothing, because no record ever carries that type — see `type` in the Product File schema below.

Responses

{
    "_links": {},
    "_pagination": {},
    "items": [
        {
            "_id": "46033f29-9733-45fa-8069-7e7a23dbe864",
            "_legacy_id": 20882910,
            "_version": "4.0",
            "_links": {},
            "type": "cover_marketing",
            "timestamp": 1782319836,
            "file_size": 267184,
            "file_name": "v14937.jpg",
            "mime_type": "image/jpeg",
            "labels": [
                "autogenerated"
            ],
            "properties": {
                "height": 2560,
                "width": 1809
            },
            "validation": {
                "result": "failed",
                "errors": [
                    "NCX-001: OPS/toc.ncx(-1:-1): NCX identifier (\"com.zeilenquarz.V7\") does not match OPF identifier (\"9783161484100\")."
                ],
                "warnings": []
            },
            "sha1": "9f4806983de39ef7b503c4c658d37427d7caf5f4",
            "channel": null,
            "href": "https://api.openpublishing.com/resource/v4/products/a6fc09af-e616-11ee-a221-00505699575c/files/46033f29-9733-45fa-8069-7e7a23dbe864/file"
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "_links": {
            "type": "object"
        },
        "_pagination": {
            "type": "object"
        },
        "items": {
            "type": "array",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "description": "One binary asset attached to a product, identified by its file type.",
                "properties": {
                    "_id": {
                        "type": "string",
                        "description": "UUID of the file record. Stable for the lifetime of the record; a replaced file gets a new one.",
                        "example": "46033f29-9733-45fa-8069-7e7a23dbe864"
                    },
                    "_legacy_id": {
                        "type": "integer",
                        "description": "Legacy integer identifier of this resource, kept so existing integrations can correlate their own records. This is the id the v3 API returns as `_id`, and it is accepted in place of the UUID anywhere in the path.",
                        "example": 20882910
                    },
                    "_version": {
                        "type": "string",
                        "description": "API version that produced this representation.",
                        "example": "4.0"
                    },
                    "_links": {
                        "type": "object",
                        "description": "`self` — this file record. `tests` — the collection of validation checks run\nagainst it (`/resource/v4/products/{product_id}/files/{file_id}/tests`), which is\nthe itemised form of the summary in `validation`.\n"
                    },
                    "type": {
                        "type": "string",
                        "description": "What the file is for — a manuscript, an e-book edition, a cover image, a preview.\nThis is the only field that decides how the platform treats the asset, and the only\none that is mandatory on upload; neither the file name nor the MIME type carries that\nmeaning. Twenty values are accepted; anything else is rejected with\n`400 Bad Request`.\n\n**A file record is immutable.** There is no `PUT`. An asset is replaced by a `DELETE`\nof the record followed by a `POST` of the new file under the same `type`. Apart from\n`channel` and `labels`, every remaining field is derived by the server from the\nuploaded bytes, so there is nothing else a client could update.\n\nSource material:\n\n- `dropbox` — author-supplied source manuscript: the document a publication is\n  produced *from*, not a deliverable. Unrelated to the Dropbox service.\n\nE-book editions:\n\n- `ebook_epub` — the EPUB edition. Inspected on upload: the version lands in\n  `properties.epub_version` and structural features in `labels`.\n- `ebook_pdf` — the PDF edition.\n- `ebook_pdf_cover` — cover page bound into the PDF edition.\n- `ebook_pdf_bastard_title` — half-title (bastard title) page bound into the PDF\n  edition.\n- `ebook_mobi` — the Mobipocket / Kindle edition.\n- `ebook_ibook` — the Apple iBooks edition.\n\nPrint-on-demand:\n\n- `pod_bookblock` — the book block, i.e. the interior pages, as a print-ready PDF.\n- `pod_cover` — the cover as one wrap: front, spine and back together.\n- `pod_cover_front` — front cover only.\n- `pod_cover_back` — back cover only.\n\nCovers, previews and samples:\n\n- `cover_marketing` — the marketing cover image. **Must be a JPEG**: the file name\n  has to end in `.jpg` or `.jpeg`, or the upload is rejected with `400 Bad Request`.\n  The test is on the extension alone, so a PNG renamed to `.jpg` passes it.\n- `extract` — a reading extract offered as a sample of the work.\n- `shop_preview` — HTML preview of the opening pages.\n- `shop_preview_pdf` — a preview offered as a downloadable PDF.\n- `screenshot` — screenshot of the product, for app-like or interactive titles.\n\nAudiobooks:\n\n- `audiobook` — an instruction to unpack a complete audiobook delivery. No record ever\n  carries it: an upload with `type=audiobook` is unpacked and each entry becomes a\n  record of one of the three types below, chosen from the entry's own media type —\n  `audio/*` becomes a track, `image/*` the cover, `application/*` the booklet. Only a\n  delivery that unpacks to exactly one record can be answered with that record, so an\n  archive of several tracks is stored and then reported as `400 Bad Request`; the\n  derived records are visible on the next `GET`. Re-uploading replaces the previously\n  derived set.\n- `audiobook_track` — a single audio track of an audiobook.\n- `audiobook_booklet` — the booklet accompanying an audiobook.\n- `audiobook_cover` — cover artwork for an audiobook.\n\nOnly the twenty values listed above are accepted on input, and only they are ever\nreturned.\n\nFilterable — `?type=ebook_epub`; an unknown type is rejected with `400 Bad Request`\nrather than returning an empty result. See\n[Resource Queries](/resource_queries/).\n\n`null` on legacy records that carry no type at all. A legacy record whose type is\nneither absent nor one of the twenty cannot be represented and answers\n`400 Bad Request` when read.\n",
                        "nullable": true,
                        "example": "cover_marketing",
                        "x-enum-descriptions": {
                            "pod_bookblock": "Print-on-demand book block — the interior pages, print-ready PDF.",
                            "pod_cover": "Print-on-demand cover — the complete wrap (front, spine, back) as one print-ready PDF.",
                            "pod_cover_front": "Print-on-demand front cover only.",
                            "pod_cover_back": "Print-on-demand back cover only.",
                            "ebook_pdf": "The PDF edition of the e-book, as delivered to customers and channels.",
                            "ebook_pdf_cover": "Cover page bound into the PDF e-book.",
                            "ebook_pdf_bastard_title": "Half-title (bastard title) page bound into the PDF e-book.",
                            "ebook_epub": "The EPUB edition. Automatically inspected on upload: EPUB version lands in `properties.epub_version` and structural features in `labels`.",
                            "ebook_mobi": "The Mobipocket/Kindle edition.",
                            "ebook_ibook": "The Apple iBooks edition.",
                            "cover_marketing": "The marketing cover image. Must be a JPEG — any other format is rejected with `400 Bad Request`.",
                            "extract": "A reading extract offered as a sample of the work.",
                            "shop_preview": "HTML preview rendered inside the storefront product page.",
                            "shop_preview_pdf": "PDF preview offered for download from the storefront.",
                            "screenshot": "Screenshot of the product, e.g. for app-like or interactive titles.",
                            "dropbox": "Author-supplied source manuscript. The original document a publication is produced from, not a deliverable.",
                            "audiobook": "An instruction to unpack a complete audiobook delivery, normally a ZIP. Never carried by a record: each entry becomes an `audiobook_track`, `audiobook_cover` or `audiobook_booklet` record.",
                            "audiobook_track": "A single audio track of an audiobook.",
                            "audiobook_booklet": "The booklet accompanying an audiobook.",
                            "audiobook_cover": "Cover artwork for an audiobook."
                        }
                    },
                    "timestamp": {
                        "type": "integer",
                        "description": "When the file was added, as a Unix timestamp (seconds since 1970-01-01 UTC, integer,\nnot a string).\n\nSortable. `id` and `timestamp` are the two field names defined for this resource and\nthe two to rely on; other names may or may not resolve, and one that does not is\nrejected with `400 Bad Request`. For the syntax see\n[Resource Queries](/resource_queries/).\n\nOld records carry the timestamp of the original import, which can predate the platform\nitself, so the two defined orders do not always agree: the oldest file on a\nlong-running title can be decades older than its `_id` ordering suggests.\n",
                        "example": 1782319836
                    },
                    "file_size": {
                        "type": "integer",
                        "description": "Size of the stored file in bytes. `null` for records registered by URL, where the\nplatform never receives the bytes.\n\nThere is an upload size limit, and exceeding it answers `413` rather than\n`400 Bad Request`. For a file of 4 GiB or more the value reported here is not the true\nbyte count, even though the file itself is stored whole.\n",
                        "nullable": true,
                        "example": 267184
                    },
                    "file_name": {
                        "type": "string",
                        "description": "The name the file was uploaded under, with any directory part stripped. Informational\nonly — the platform never dispatches on it, and duplicate names within one product are\nallowed. It is echoed back in the `Content-Disposition` header when the content is\ndownloaded, so it is what a browser will save the file as.\n",
                        "nullable": true,
                        "example": "v14937.jpg"
                    },
                    "mime_type": {
                        "type": "string",
                        "description": "Media type of the file. Guessed by the server from the file name extension at\nupload time — the `Content-Type` of the multipart part is ignored, and so is the\ncontent of the file.\n\nThere is no allow-list: an extension the server does not recognise is recorded as\n`application/octet-stream` rather than rejected, so this field never reports failure.\n\n`null` for records registered by URL, whose bytes the platform never receives, and for\nolder records created before media types were recorded. Both are selectable with\n`?mime_type__isnull`.\n\nThis drives which extras appear in `properties`: `image/*` yields `height` and\n`width`, `application/epub+zip` yields `epub_version`.\n\nFilterable — `?mime_type=application/epub+zip`. See\n[Resource Queries](/resource_queries/).\n",
                        "nullable": true,
                        "example": "image/jpeg"
                    },
                    "labels": {
                        "type": "array",
                        "description": "Free-form lowercase tags on the file. Always present, and empty when the file has no\ntags. Client-supplied tags are passed on upload as a comma-separated `labels`\nparameter; they are lowercased and trimmed, and there is no allow-list. They are only\napplied to an uploaded file: a `labels` parameter sent alongside `url` is accepted and\nsilently dropped, leaving the registered record untagged.\n\nTwo families of label are set by the platform itself. The first records how the file\ncame to be:\n\n- `autogenerated` — the file was produced by the platform's own conversion pipeline\n  rather than uploaded by a human. Replacing an `autogenerated` file by hand is\n  usually futile, because the next conversion run overwrites it again.\n- `book-converter` — names the pipeline that produced the file.\n\nThe second describes the structure of an EPUB. Uploading an `ebook_epub` makes the\nserver open the book and tag it with what it finds:\n\n- `fixedlayout` — fixed-layout rather than reflowable\n- `svgimage` — images supplied as SVG\n- `svg` — SVG content\n- `video` — embedded video\n- `audio` — embedded audio\n- `object` — embedded objects\n\nFilterable by one exact label at a time, equality only: `?labels=autogenerated`\nworks, `?labels=auto` matches nothing, and `?labels__contains=auto` is a\n`400 Bad Request`. A comma-separated list is accepted in a *filter* but always returns\nan empty list, even for a file carrying every label named. One request per label,\nintersected client-side, is the way to express \"and\". See\n[Resource Queries](/resource_queries/).\n",
                        "items": {
                            "type": "string"
                        },
                        "example": [
                            "autogenerated"
                        ]
                    },
                    "properties": {
                        "type": "object",
                        "additionalProperties": true,
                        "description": "Format-specific attributes the server extracted from the file. The keys depend on\n`mime_type` and are absent — the object is then `{}` — for formats that carry none:\n\n- `height`, `width` — pixel dimensions, **integers**, for any `image/*` file. They\n  are the only record of the resolution of a `cover_marketing` image.\n- `epub_version` — `\"2\"` or `\"3\"`, a **string**, for `application/epub+zip`. Determined\n  at upload time, and it defaults to `\"2\"` whenever the server did not positively\n  identify an EPUB 3, so `\"2\"` means \"not detected as EPUB 3\" rather than \"confirmed\n  EPUB 2\".\n\nOne key is filterable: `?property_epub_version=3`. Only `2` and `3` are accepted\nthere; any other value is a `400 Bad Request`. The filter matches the version the\nserver detected, so a record for which no version was ever detected is returned by\nneither `2` nor `3`.\n",
                        "example": {
                            "height": 2560,
                            "width": 1809
                        }
                    },
                    "validation": {
                        "type": "object",
                        "additionalProperties": false,
                        "description": "Rolled-up outcome of every validation check run against this file, or `null` when no\ncheck has ever run — which is the normal state for formats nobody validates, such as\ncover images and HTML previews. `null` therefore means *unknown*, not *passed*.\n\nChecks are not run by the upload request. A file is created with `validation: null`\nand acquires a result seconds to minutes later, once a checker has looked at it, so a\nread-back immediately after an upload still shows `null`. The checkers whose verdicts\nappear here are `pdf-check` for PDFs and `epubchecker` for EPUBs; the per-checker\ndetail behind this summary is at `_links.tests`.\n",
                        "nullable": true,
                        "properties": {
                            "result": {
                                "type": "string",
                                "description": "`passed` if every check on the file passed, `failed` if any one of them failed.\nA `failed` file is still stored and still downloadable: this resource reports the\nverdict, it does not act on it.\n",
                                "example": "failed"
                            },
                            "errors": {
                                "type": "array",
                                "description": "Every error message from every check, flattened into one list. For EPUBs these\nare EPUBCheck messages and keep their original identifier and location prefix,\nwhich is what identifies the fault inside the source file.\n",
                                "items": {
                                    "type": "string"
                                },
                                "example": [
                                    "NCX-001: OPS/toc.ncx(-1:-1): NCX identifier (\"com.zeilenquarz.V7\") does not match OPF identifier (\"9783161484100\")."
                                ]
                            },
                            "warnings": {
                                "type": "array",
                                "description": "Every warning message from every check, flattened into one list. Warnings do not make `result` `failed`.",
                                "items": {
                                    "type": "string"
                                },
                                "example": []
                            }
                        }
                    },
                    "sha1": {
                        "type": "string",
                        "description": "SHA-1 digest of the stored bytes, lowercase hex. It is what confirms that a download\nmatches what was stored, and — since file names may repeat and a replaced file keeps\nneither its `_id` nor necessarily a new name — what identifies the bytes themselves.\n`null` for files stored before hashing was introduced, and for URL-registered records\nunless a `sha1` was supplied at creation, in which case the supplied value is returned\nunverified.\n",
                        "nullable": true,
                        "example": "9f4806983de39ef7b503c4c658d37427d7caf5f4"
                    },
                    "channel": {
                        "type": "string",
                        "description": "Restricts the file to one distribution channel, identified by the same lowercase\nchannel slug used elsewhere in the API (`google`, `itunes`, …). It exists for the case\nwhere one channel needs its own variant of an asset — a differently sized cover, a\nwatermarked extract — that must not reach the others.\n\n`null`, the usual case, means the file is not restricted to a channel. Filtering for\nthose records needs the literal, case-sensitive `?channel=NULL`; a plain `?channel=`\ndoes not match them.\n",
                        "nullable": true,
                        "example": null
                    },
                    "href": {
                        "type": "string",
                        "description": "Where the bytes are. For a stored file this is the download endpoint of this API\n(`.../files/{file_id}/file`), and requires the same `Authorization` header as any\nother call — it is not a public or pre-signed link, and it does not expire. For a\nrecord created with `?url=` it is the external URL exactly as registered, which is\nneither fetched nor validated.\n",
                        "example": "https://api.openpublishing.com/resource/v4/products/a6fc09af-e616-11ee-a221-00505699575c/files/46033f29-9733-45fa-8069-7e7a23dbe864/file"
                    }
                },
                "required": []
            }
        }
    },
    "required": [
        "_links",
        "_pagination",
        "items"
    ]
}

{
    "errors": [
        {
            "_id": "string",
            "stack_trace": [
                "string"
            ],
            "message": "string",
            "vars": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "required": [
        "errors"
    ],
    "properties": {
        "errors": {
            "type": "array",
            "description": "List of errors occurred",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "_id",
                    "message",
                    "stack_trace",
                    "vars"
                ],
                "properties": {
                    "_id": {
                        "type": "string",
                        "description": "The identifier of the error"
                    },
                    "stack_trace": {
                        "type": "array",
                        "description": "A stacktrace of the error occurred. Only for testing systems.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "message": {
                        "type": "string",
                        "description": "A translated message of the error"
                    },
                    "vars": {
                        "type": "object",
                        "description": "A dict containing additional values of the context of the error."
                    }
                }
            }
        }
    }
}

POST /resource/v4/products/{product_id}/files

Upload a file for a product

Description

The bytes travel as a multipart/form-data part named file; all the metadata is passed as query parameters on the same request, not as form fields:

POST /resource/v4/products/a6fc09af-e616-11ee-a221-00505699575c/files
     ?type=ebook_epub&labels=distribution:google
Content-Type: multipart/form-data; boundary=...

--...
Content-Disposition: form-data; name="file"; filename="v14937-final.epub"

<bytes>

An externally hosted file is registered by omitting the body and passing url instead.

Only type is mandatory. There is no MIME-type allow-list: an unrecognised extension is accepted and recorded as application/octet-stream, and nothing checks that the bytes are the kind of file the type claims. There is an upload size limit, and exceeding it answers 413.

The response is the complete file record, including the fields the server derived from the bytes. validation is null in it even for formats that are checked, because checking happens afterwards.

type=audiobook behaves differently from every other value: it unpacks the archive into one record per entry, so it can only answer 201 when the archive holds a single entry. A multi-track archive is stored and then reported as 400 Bad Request, with the derived records visible on the next GET.

Input parameters

Path parameters

Parameter Type Default Nullable Description
product_id string No UUID (or legacy integer id) of the product.

Query parameters

Parameter Type Default Nullable Description
channel string No Restrict the file to one distribution channel. Left out — the usual case — the file is available to all channels.
labels string No Comma-separated tags to attach to the file. Lowercased and trimmed; empty entries are dropped. Any string is accepted. Applied only to an uploaded file — sent together with `url` it is accepted and silently ignored.
md5 string No MD5 digest of the external file. Only meaningful together with `url`; recorded as supplied and not verified.
sha1 string No SHA-1 digest of the external file. Only meaningful together with `url`; recorded as supplied and not verified, and returned as the record's `sha1`.
type string No What the file is for. Mandatory. The twenty accepted values, and what each one means, are listed under `type` in the Product File schema below; an unknown value is rejected with `400 Bad Request`.
url string No Register a file hosted elsewhere rather than uploading one. Mutually exclusive with the `file` part; if both are present the uploaded part wins. The URL is recorded as supplied and is neither fetched nor checked.

Request body

{
    "file": "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ="
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the request body
{
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "file": {
            "type": "string",
            "format": "binary",
            "description": "The file itself. The part's `filename` becomes the record's `file_name` and its extension decides the reported `mime_type`; the part's own `Content-Type` is ignored. The whole body is left out when registering a `url` instead."
        }
    },
    "required": [
        "file"
    ]
}

Responses

{
    "_id": "46033f29-9733-45fa-8069-7e7a23dbe864",
    "_legacy_id": 20882910,
    "_version": "4.0",
    "_links": {},
    "type": "cover_marketing",
    "timestamp": 1782319836,
    "file_size": 267184,
    "file_name": "v14937.jpg",
    "mime_type": "image/jpeg",
    "labels": [
        "autogenerated"
    ],
    "properties": {
        "height": 2560,
        "width": 1809
    },
    "validation": {
        "result": "failed",
        "errors": [
            "NCX-001: OPS/toc.ncx(-1:-1): NCX identifier (\"com.zeilenquarz.V7\") does not match OPF identifier (\"9783161484100\")."
        ],
        "warnings": []
    },
    "sha1": "9f4806983de39ef7b503c4c658d37427d7caf5f4",
    "channel": null,
    "href": "https://api.openpublishing.com/resource/v4/products/a6fc09af-e616-11ee-a221-00505699575c/files/46033f29-9733-45fa-8069-7e7a23dbe864/file"
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "description": "One binary asset attached to a product, identified by its file type.",
    "properties": {
        "_id": {
            "type": "string",
            "description": "UUID of the file record. Stable for the lifetime of the record; a replaced file gets a new one.",
            "example": "46033f29-9733-45fa-8069-7e7a23dbe864"
        },
        "_legacy_id": {
            "type": "integer",
            "description": "Legacy integer identifier of this resource, kept so existing integrations can correlate their own records. This is the id the v3 API returns as `_id`, and it is accepted in place of the UUID anywhere in the path.",
            "example": 20882910
        },
        "_version": {
            "type": "string",
            "description": "API version that produced this representation.",
            "example": "4.0"
        },
        "_links": {
            "type": "object",
            "description": "`self` — this file record. `tests` — the collection of validation checks run\nagainst it (`/resource/v4/products/{product_id}/files/{file_id}/tests`), which is\nthe itemised form of the summary in `validation`.\n"
        },
        "type": {
            "type": "string",
            "description": "What the file is for — a manuscript, an e-book edition, a cover image, a preview.\nThis is the only field that decides how the platform treats the asset, and the only\none that is mandatory on upload; neither the file name nor the MIME type carries that\nmeaning. Twenty values are accepted; anything else is rejected with\n`400 Bad Request`.\n\n**A file record is immutable.** There is no `PUT`. An asset is replaced by a `DELETE`\nof the record followed by a `POST` of the new file under the same `type`. Apart from\n`channel` and `labels`, every remaining field is derived by the server from the\nuploaded bytes, so there is nothing else a client could update.\n\nSource material:\n\n- `dropbox` — author-supplied source manuscript: the document a publication is\n  produced *from*, not a deliverable. Unrelated to the Dropbox service.\n\nE-book editions:\n\n- `ebook_epub` — the EPUB edition. Inspected on upload: the version lands in\n  `properties.epub_version` and structural features in `labels`.\n- `ebook_pdf` — the PDF edition.\n- `ebook_pdf_cover` — cover page bound into the PDF edition.\n- `ebook_pdf_bastard_title` — half-title (bastard title) page bound into the PDF\n  edition.\n- `ebook_mobi` — the Mobipocket / Kindle edition.\n- `ebook_ibook` — the Apple iBooks edition.\n\nPrint-on-demand:\n\n- `pod_bookblock` — the book block, i.e. the interior pages, as a print-ready PDF.\n- `pod_cover` — the cover as one wrap: front, spine and back together.\n- `pod_cover_front` — front cover only.\n- `pod_cover_back` — back cover only.\n\nCovers, previews and samples:\n\n- `cover_marketing` — the marketing cover image. **Must be a JPEG**: the file name\n  has to end in `.jpg` or `.jpeg`, or the upload is rejected with `400 Bad Request`.\n  The test is on the extension alone, so a PNG renamed to `.jpg` passes it.\n- `extract` — a reading extract offered as a sample of the work.\n- `shop_preview` — HTML preview of the opening pages.\n- `shop_preview_pdf` — a preview offered as a downloadable PDF.\n- `screenshot` — screenshot of the product, for app-like or interactive titles.\n\nAudiobooks:\n\n- `audiobook` — an instruction to unpack a complete audiobook delivery. No record ever\n  carries it: an upload with `type=audiobook` is unpacked and each entry becomes a\n  record of one of the three types below, chosen from the entry's own media type —\n  `audio/*` becomes a track, `image/*` the cover, `application/*` the booklet. Only a\n  delivery that unpacks to exactly one record can be answered with that record, so an\n  archive of several tracks is stored and then reported as `400 Bad Request`; the\n  derived records are visible on the next `GET`. Re-uploading replaces the previously\n  derived set.\n- `audiobook_track` — a single audio track of an audiobook.\n- `audiobook_booklet` — the booklet accompanying an audiobook.\n- `audiobook_cover` — cover artwork for an audiobook.\n\nOnly the twenty values listed above are accepted on input, and only they are ever\nreturned.\n\nFilterable — `?type=ebook_epub`; an unknown type is rejected with `400 Bad Request`\nrather than returning an empty result. See\n[Resource Queries](/resource_queries/).\n\n`null` on legacy records that carry no type at all. A legacy record whose type is\nneither absent nor one of the twenty cannot be represented and answers\n`400 Bad Request` when read.\n",
            "nullable": true,
            "example": "cover_marketing",
            "x-enum-descriptions": {
                "pod_bookblock": "Print-on-demand book block — the interior pages, print-ready PDF.",
                "pod_cover": "Print-on-demand cover — the complete wrap (front, spine, back) as one print-ready PDF.",
                "pod_cover_front": "Print-on-demand front cover only.",
                "pod_cover_back": "Print-on-demand back cover only.",
                "ebook_pdf": "The PDF edition of the e-book, as delivered to customers and channels.",
                "ebook_pdf_cover": "Cover page bound into the PDF e-book.",
                "ebook_pdf_bastard_title": "Half-title (bastard title) page bound into the PDF e-book.",
                "ebook_epub": "The EPUB edition. Automatically inspected on upload: EPUB version lands in `properties.epub_version` and structural features in `labels`.",
                "ebook_mobi": "The Mobipocket/Kindle edition.",
                "ebook_ibook": "The Apple iBooks edition.",
                "cover_marketing": "The marketing cover image. Must be a JPEG — any other format is rejected with `400 Bad Request`.",
                "extract": "A reading extract offered as a sample of the work.",
                "shop_preview": "HTML preview rendered inside the storefront product page.",
                "shop_preview_pdf": "PDF preview offered for download from the storefront.",
                "screenshot": "Screenshot of the product, e.g. for app-like or interactive titles.",
                "dropbox": "Author-supplied source manuscript. The original document a publication is produced from, not a deliverable.",
                "audiobook": "An instruction to unpack a complete audiobook delivery, normally a ZIP. Never carried by a record: each entry becomes an `audiobook_track`, `audiobook_cover` or `audiobook_booklet` record.",
                "audiobook_track": "A single audio track of an audiobook.",
                "audiobook_booklet": "The booklet accompanying an audiobook.",
                "audiobook_cover": "Cover artwork for an audiobook."
            }
        },
        "timestamp": {
            "type": "integer",
            "description": "When the file was added, as a Unix timestamp (seconds since 1970-01-01 UTC, integer,\nnot a string).\n\nSortable. `id` and `timestamp` are the two field names defined for this resource and\nthe two to rely on; other names may or may not resolve, and one that does not is\nrejected with `400 Bad Request`. For the syntax see\n[Resource Queries](/resource_queries/).\n\nOld records carry the timestamp of the original import, which can predate the platform\nitself, so the two defined orders do not always agree: the oldest file on a\nlong-running title can be decades older than its `_id` ordering suggests.\n",
            "example": 1782319836
        },
        "file_size": {
            "type": "integer",
            "description": "Size of the stored file in bytes. `null` for records registered by URL, where the\nplatform never receives the bytes.\n\nThere is an upload size limit, and exceeding it answers `413` rather than\n`400 Bad Request`. For a file of 4 GiB or more the value reported here is not the true\nbyte count, even though the file itself is stored whole.\n",
            "nullable": true,
            "example": 267184
        },
        "file_name": {
            "type": "string",
            "description": "The name the file was uploaded under, with any directory part stripped. Informational\nonly — the platform never dispatches on it, and duplicate names within one product are\nallowed. It is echoed back in the `Content-Disposition` header when the content is\ndownloaded, so it is what a browser will save the file as.\n",
            "nullable": true,
            "example": "v14937.jpg"
        },
        "mime_type": {
            "type": "string",
            "description": "Media type of the file. Guessed by the server from the file name extension at\nupload time — the `Content-Type` of the multipart part is ignored, and so is the\ncontent of the file.\n\nThere is no allow-list: an extension the server does not recognise is recorded as\n`application/octet-stream` rather than rejected, so this field never reports failure.\n\n`null` for records registered by URL, whose bytes the platform never receives, and for\nolder records created before media types were recorded. Both are selectable with\n`?mime_type__isnull`.\n\nThis drives which extras appear in `properties`: `image/*` yields `height` and\n`width`, `application/epub+zip` yields `epub_version`.\n\nFilterable — `?mime_type=application/epub+zip`. See\n[Resource Queries](/resource_queries/).\n",
            "nullable": true,
            "example": "image/jpeg"
        },
        "labels": {
            "type": "array",
            "description": "Free-form lowercase tags on the file. Always present, and empty when the file has no\ntags. Client-supplied tags are passed on upload as a comma-separated `labels`\nparameter; they are lowercased and trimmed, and there is no allow-list. They are only\napplied to an uploaded file: a `labels` parameter sent alongside `url` is accepted and\nsilently dropped, leaving the registered record untagged.\n\nTwo families of label are set by the platform itself. The first records how the file\ncame to be:\n\n- `autogenerated` — the file was produced by the platform's own conversion pipeline\n  rather than uploaded by a human. Replacing an `autogenerated` file by hand is\n  usually futile, because the next conversion run overwrites it again.\n- `book-converter` — names the pipeline that produced the file.\n\nThe second describes the structure of an EPUB. Uploading an `ebook_epub` makes the\nserver open the book and tag it with what it finds:\n\n- `fixedlayout` — fixed-layout rather than reflowable\n- `svgimage` — images supplied as SVG\n- `svg` — SVG content\n- `video` — embedded video\n- `audio` — embedded audio\n- `object` — embedded objects\n\nFilterable by one exact label at a time, equality only: `?labels=autogenerated`\nworks, `?labels=auto` matches nothing, and `?labels__contains=auto` is a\n`400 Bad Request`. A comma-separated list is accepted in a *filter* but always returns\nan empty list, even for a file carrying every label named. One request per label,\nintersected client-side, is the way to express \"and\". See\n[Resource Queries](/resource_queries/).\n",
            "items": {
                "type": "string"
            },
            "example": [
                "autogenerated"
            ]
        },
        "properties": {
            "type": "object",
            "additionalProperties": true,
            "description": "Format-specific attributes the server extracted from the file. The keys depend on\n`mime_type` and are absent — the object is then `{}` — for formats that carry none:\n\n- `height`, `width` — pixel dimensions, **integers**, for any `image/*` file. They\n  are the only record of the resolution of a `cover_marketing` image.\n- `epub_version` — `\"2\"` or `\"3\"`, a **string**, for `application/epub+zip`. Determined\n  at upload time, and it defaults to `\"2\"` whenever the server did not positively\n  identify an EPUB 3, so `\"2\"` means \"not detected as EPUB 3\" rather than \"confirmed\n  EPUB 2\".\n\nOne key is filterable: `?property_epub_version=3`. Only `2` and `3` are accepted\nthere; any other value is a `400 Bad Request`. The filter matches the version the\nserver detected, so a record for which no version was ever detected is returned by\nneither `2` nor `3`.\n",
            "example": {
                "height": 2560,
                "width": 1809
            }
        },
        "validation": {
            "type": "object",
            "additionalProperties": false,
            "description": "Rolled-up outcome of every validation check run against this file, or `null` when no\ncheck has ever run — which is the normal state for formats nobody validates, such as\ncover images and HTML previews. `null` therefore means *unknown*, not *passed*.\n\nChecks are not run by the upload request. A file is created with `validation: null`\nand acquires a result seconds to minutes later, once a checker has looked at it, so a\nread-back immediately after an upload still shows `null`. The checkers whose verdicts\nappear here are `pdf-check` for PDFs and `epubchecker` for EPUBs; the per-checker\ndetail behind this summary is at `_links.tests`.\n",
            "nullable": true,
            "properties": {
                "result": {
                    "type": "string",
                    "description": "`passed` if every check on the file passed, `failed` if any one of them failed.\nA `failed` file is still stored and still downloadable: this resource reports the\nverdict, it does not act on it.\n",
                    "example": "failed"
                },
                "errors": {
                    "type": "array",
                    "description": "Every error message from every check, flattened into one list. For EPUBs these\nare EPUBCheck messages and keep their original identifier and location prefix,\nwhich is what identifies the fault inside the source file.\n",
                    "items": {
                        "type": "string"
                    },
                    "example": [
                        "NCX-001: OPS/toc.ncx(-1:-1): NCX identifier (\"com.zeilenquarz.V7\") does not match OPF identifier (\"9783161484100\")."
                    ]
                },
                "warnings": {
                    "type": "array",
                    "description": "Every warning message from every check, flattened into one list. Warnings do not make `result` `failed`.",
                    "items": {
                        "type": "string"
                    },
                    "example": []
                }
            }
        },
        "sha1": {
            "type": "string",
            "description": "SHA-1 digest of the stored bytes, lowercase hex. It is what confirms that a download\nmatches what was stored, and — since file names may repeat and a replaced file keeps\nneither its `_id` nor necessarily a new name — what identifies the bytes themselves.\n`null` for files stored before hashing was introduced, and for URL-registered records\nunless a `sha1` was supplied at creation, in which case the supplied value is returned\nunverified.\n",
            "nullable": true,
            "example": "9f4806983de39ef7b503c4c658d37427d7caf5f4"
        },
        "channel": {
            "type": "string",
            "description": "Restricts the file to one distribution channel, identified by the same lowercase\nchannel slug used elsewhere in the API (`google`, `itunes`, …). It exists for the case\nwhere one channel needs its own variant of an asset — a differently sized cover, a\nwatermarked extract — that must not reach the others.\n\n`null`, the usual case, means the file is not restricted to a channel. Filtering for\nthose records needs the literal, case-sensitive `?channel=NULL`; a plain `?channel=`\ndoes not match them.\n",
            "nullable": true,
            "example": null
        },
        "href": {
            "type": "string",
            "description": "Where the bytes are. For a stored file this is the download endpoint of this API\n(`.../files/{file_id}/file`), and requires the same `Authorization` header as any\nother call — it is not a public or pre-signed link, and it does not expire. For a\nrecord created with `?url=` it is the external URL exactly as registered, which is\nneither fetched nor validated.\n",
            "example": "https://api.openpublishing.com/resource/v4/products/a6fc09af-e616-11ee-a221-00505699575c/files/46033f29-9733-45fa-8069-7e7a23dbe864/file"
        }
    },
    "required": []
}

{
    "errors": [
        {
            "_id": "string",
            "stack_trace": [
                "string"
            ],
            "message": "string",
            "vars": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "required": [
        "errors"
    ],
    "properties": {
        "errors": {
            "type": "array",
            "description": "List of errors occurred",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "_id",
                    "message",
                    "stack_trace",
                    "vars"
                ],
                "properties": {
                    "_id": {
                        "type": "string",
                        "description": "The identifier of the error"
                    },
                    "stack_trace": {
                        "type": "array",
                        "description": "A stacktrace of the error occurred. Only for testing systems.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "message": {
                        "type": "string",
                        "description": "A translated message of the error"
                    },
                    "vars": {
                        "type": "object",
                        "description": "A dict containing additional values of the context of the error."
                    }
                }
            }
        }
    }
}

{
    "errors": [
        {
            "_id": "string",
            "stack_trace": [
                "string"
            ],
            "message": "string",
            "vars": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "required": [
        "errors"
    ],
    "properties": {
        "errors": {
            "type": "array",
            "description": "List of errors occurred",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "_id",
                    "message",
                    "stack_trace",
                    "vars"
                ],
                "properties": {
                    "_id": {
                        "type": "string",
                        "description": "The identifier of the error"
                    },
                    "stack_trace": {
                        "type": "array",
                        "description": "A stacktrace of the error occurred. Only for testing systems.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "message": {
                        "type": "string",
                        "description": "A translated message of the error"
                    },
                    "vars": {
                        "type": "object",
                        "description": "A dict containing additional values of the context of the error."
                    }
                }
            }
        }
    }
}

GET /resource/v4/products/{product_id}/files/{file_id}

Retrieve a single product file

Input parameters

Path parameters

Parameter Type Default Nullable Description
file_id string No UUID (or legacy integer id) of the file.
product_id string No UUID (or legacy integer id) of the product.

Responses

{
    "_id": "46033f29-9733-45fa-8069-7e7a23dbe864",
    "_legacy_id": 20882910,
    "_version": "4.0",
    "type": "cover_marketing",
    "file_name": "v14937.jpg",
    "file_size": 267184,
    "mime_type": "image/jpeg",
    "properties": {
        "height": 2560,
        "width": 1809
    },
    "labels": [
        "autogenerated"
    ],
    "sha1": "9f4806983de39ef7b503c4c658d37427d7caf5f4",
    "channel": null,
    "timestamp": 1782319836,
    "validation": null,
    "href": "https://api.openpublishing.com/resource/v4/products/a6fc09af-e616-11ee-a221-00505699575c/files/46033f29-9733-45fa-8069-7e7a23dbe864/file"
}
Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "description": "One binary asset attached to a product, identified by its file type.",
    "properties": {
        "_id": {
            "type": "string",
            "description": "UUID of the file record. Stable for the lifetime of the record; a replaced file gets a new one.",
            "example": "46033f29-9733-45fa-8069-7e7a23dbe864"
        },
        "_legacy_id": {
            "type": "integer",
            "description": "Legacy integer identifier of this resource, kept so existing integrations can correlate their own records. This is the id the v3 API returns as `_id`, and it is accepted in place of the UUID anywhere in the path.",
            "example": 20882910
        },
        "_version": {
            "type": "string",
            "description": "API version that produced this representation.",
            "example": "4.0"
        },
        "_links": {
            "type": "object",
            "description": "`self` — this file record. `tests` — the collection of validation checks run\nagainst it (`/resource/v4/products/{product_id}/files/{file_id}/tests`), which is\nthe itemised form of the summary in `validation`.\n"
        },
        "type": {
            "type": "string",
            "description": "What the file is for — a manuscript, an e-book edition, a cover image, a preview.\nThis is the only field that decides how the platform treats the asset, and the only\none that is mandatory on upload; neither the file name nor the MIME type carries that\nmeaning. Twenty values are accepted; anything else is rejected with\n`400 Bad Request`.\n\n**A file record is immutable.** There is no `PUT`. An asset is replaced by a `DELETE`\nof the record followed by a `POST` of the new file under the same `type`. Apart from\n`channel` and `labels`, every remaining field is derived by the server from the\nuploaded bytes, so there is nothing else a client could update.\n\nSource material:\n\n- `dropbox` — author-supplied source manuscript: the document a publication is\n  produced *from*, not a deliverable. Unrelated to the Dropbox service.\n\nE-book editions:\n\n- `ebook_epub` — the EPUB edition. Inspected on upload: the version lands in\n  `properties.epub_version` and structural features in `labels`.\n- `ebook_pdf` — the PDF edition.\n- `ebook_pdf_cover` — cover page bound into the PDF edition.\n- `ebook_pdf_bastard_title` — half-title (bastard title) page bound into the PDF\n  edition.\n- `ebook_mobi` — the Mobipocket / Kindle edition.\n- `ebook_ibook` — the Apple iBooks edition.\n\nPrint-on-demand:\n\n- `pod_bookblock` — the book block, i.e. the interior pages, as a print-ready PDF.\n- `pod_cover` — the cover as one wrap: front, spine and back together.\n- `pod_cover_front` — front cover only.\n- `pod_cover_back` — back cover only.\n\nCovers, previews and samples:\n\n- `cover_marketing` — the marketing cover image. **Must be a JPEG**: the file name\n  has to end in `.jpg` or `.jpeg`, or the upload is rejected with `400 Bad Request`.\n  The test is on the extension alone, so a PNG renamed to `.jpg` passes it.\n- `extract` — a reading extract offered as a sample of the work.\n- `shop_preview` — HTML preview of the opening pages.\n- `shop_preview_pdf` — a preview offered as a downloadable PDF.\n- `screenshot` — screenshot of the product, for app-like or interactive titles.\n\nAudiobooks:\n\n- `audiobook` — an instruction to unpack a complete audiobook delivery. No record ever\n  carries it: an upload with `type=audiobook` is unpacked and each entry becomes a\n  record of one of the three types below, chosen from the entry's own media type —\n  `audio/*` becomes a track, `image/*` the cover, `application/*` the booklet. Only a\n  delivery that unpacks to exactly one record can be answered with that record, so an\n  archive of several tracks is stored and then reported as `400 Bad Request`; the\n  derived records are visible on the next `GET`. Re-uploading replaces the previously\n  derived set.\n- `audiobook_track` — a single audio track of an audiobook.\n- `audiobook_booklet` — the booklet accompanying an audiobook.\n- `audiobook_cover` — cover artwork for an audiobook.\n\nOnly the twenty values listed above are accepted on input, and only they are ever\nreturned.\n\nFilterable — `?type=ebook_epub`; an unknown type is rejected with `400 Bad Request`\nrather than returning an empty result. See\n[Resource Queries](/resource_queries/).\n\n`null` on legacy records that carry no type at all. A legacy record whose type is\nneither absent nor one of the twenty cannot be represented and answers\n`400 Bad Request` when read.\n",
            "nullable": true,
            "example": "cover_marketing",
            "x-enum-descriptions": {
                "pod_bookblock": "Print-on-demand book block — the interior pages, print-ready PDF.",
                "pod_cover": "Print-on-demand cover — the complete wrap (front, spine, back) as one print-ready PDF.",
                "pod_cover_front": "Print-on-demand front cover only.",
                "pod_cover_back": "Print-on-demand back cover only.",
                "ebook_pdf": "The PDF edition of the e-book, as delivered to customers and channels.",
                "ebook_pdf_cover": "Cover page bound into the PDF e-book.",
                "ebook_pdf_bastard_title": "Half-title (bastard title) page bound into the PDF e-book.",
                "ebook_epub": "The EPUB edition. Automatically inspected on upload: EPUB version lands in `properties.epub_version` and structural features in `labels`.",
                "ebook_mobi": "The Mobipocket/Kindle edition.",
                "ebook_ibook": "The Apple iBooks edition.",
                "cover_marketing": "The marketing cover image. Must be a JPEG — any other format is rejected with `400 Bad Request`.",
                "extract": "A reading extract offered as a sample of the work.",
                "shop_preview": "HTML preview rendered inside the storefront product page.",
                "shop_preview_pdf": "PDF preview offered for download from the storefront.",
                "screenshot": "Screenshot of the product, e.g. for app-like or interactive titles.",
                "dropbox": "Author-supplied source manuscript. The original document a publication is produced from, not a deliverable.",
                "audiobook": "An instruction to unpack a complete audiobook delivery, normally a ZIP. Never carried by a record: each entry becomes an `audiobook_track`, `audiobook_cover` or `audiobook_booklet` record.",
                "audiobook_track": "A single audio track of an audiobook.",
                "audiobook_booklet": "The booklet accompanying an audiobook.",
                "audiobook_cover": "Cover artwork for an audiobook."
            }
        },
        "timestamp": {
            "type": "integer",
            "description": "When the file was added, as a Unix timestamp (seconds since 1970-01-01 UTC, integer,\nnot a string).\n\nSortable. `id` and `timestamp` are the two field names defined for this resource and\nthe two to rely on; other names may or may not resolve, and one that does not is\nrejected with `400 Bad Request`. For the syntax see\n[Resource Queries](/resource_queries/).\n\nOld records carry the timestamp of the original import, which can predate the platform\nitself, so the two defined orders do not always agree: the oldest file on a\nlong-running title can be decades older than its `_id` ordering suggests.\n",
            "example": 1782319836
        },
        "file_size": {
            "type": "integer",
            "description": "Size of the stored file in bytes. `null` for records registered by URL, where the\nplatform never receives the bytes.\n\nThere is an upload size limit, and exceeding it answers `413` rather than\n`400 Bad Request`. For a file of 4 GiB or more the value reported here is not the true\nbyte count, even though the file itself is stored whole.\n",
            "nullable": true,
            "example": 267184
        },
        "file_name": {
            "type": "string",
            "description": "The name the file was uploaded under, with any directory part stripped. Informational\nonly — the platform never dispatches on it, and duplicate names within one product are\nallowed. It is echoed back in the `Content-Disposition` header when the content is\ndownloaded, so it is what a browser will save the file as.\n",
            "nullable": true,
            "example": "v14937.jpg"
        },
        "mime_type": {
            "type": "string",
            "description": "Media type of the file. Guessed by the server from the file name extension at\nupload time — the `Content-Type` of the multipart part is ignored, and so is the\ncontent of the file.\n\nThere is no allow-list: an extension the server does not recognise is recorded as\n`application/octet-stream` rather than rejected, so this field never reports failure.\n\n`null` for records registered by URL, whose bytes the platform never receives, and for\nolder records created before media types were recorded. Both are selectable with\n`?mime_type__isnull`.\n\nThis drives which extras appear in `properties`: `image/*` yields `height` and\n`width`, `application/epub+zip` yields `epub_version`.\n\nFilterable — `?mime_type=application/epub+zip`. See\n[Resource Queries](/resource_queries/).\n",
            "nullable": true,
            "example": "image/jpeg"
        },
        "labels": {
            "type": "array",
            "description": "Free-form lowercase tags on the file. Always present, and empty when the file has no\ntags. Client-supplied tags are passed on upload as a comma-separated `labels`\nparameter; they are lowercased and trimmed, and there is no allow-list. They are only\napplied to an uploaded file: a `labels` parameter sent alongside `url` is accepted and\nsilently dropped, leaving the registered record untagged.\n\nTwo families of label are set by the platform itself. The first records how the file\ncame to be:\n\n- `autogenerated` — the file was produced by the platform's own conversion pipeline\n  rather than uploaded by a human. Replacing an `autogenerated` file by hand is\n  usually futile, because the next conversion run overwrites it again.\n- `book-converter` — names the pipeline that produced the file.\n\nThe second describes the structure of an EPUB. Uploading an `ebook_epub` makes the\nserver open the book and tag it with what it finds:\n\n- `fixedlayout` — fixed-layout rather than reflowable\n- `svgimage` — images supplied as SVG\n- `svg` — SVG content\n- `video` — embedded video\n- `audio` — embedded audio\n- `object` — embedded objects\n\nFilterable by one exact label at a time, equality only: `?labels=autogenerated`\nworks, `?labels=auto` matches nothing, and `?labels__contains=auto` is a\n`400 Bad Request`. A comma-separated list is accepted in a *filter* but always returns\nan empty list, even for a file carrying every label named. One request per label,\nintersected client-side, is the way to express \"and\". See\n[Resource Queries](/resource_queries/).\n",
            "items": {
                "type": "string"
            },
            "example": [
                "autogenerated"
            ]
        },
        "properties": {
            "type": "object",
            "additionalProperties": true,
            "description": "Format-specific attributes the server extracted from the file. The keys depend on\n`mime_type` and are absent — the object is then `{}` — for formats that carry none:\n\n- `height`, `width` — pixel dimensions, **integers**, for any `image/*` file. They\n  are the only record of the resolution of a `cover_marketing` image.\n- `epub_version` — `\"2\"` or `\"3\"`, a **string**, for `application/epub+zip`. Determined\n  at upload time, and it defaults to `\"2\"` whenever the server did not positively\n  identify an EPUB 3, so `\"2\"` means \"not detected as EPUB 3\" rather than \"confirmed\n  EPUB 2\".\n\nOne key is filterable: `?property_epub_version=3`. Only `2` and `3` are accepted\nthere; any other value is a `400 Bad Request`. The filter matches the version the\nserver detected, so a record for which no version was ever detected is returned by\nneither `2` nor `3`.\n",
            "example": {
                "height": 2560,
                "width": 1809
            }
        },
        "validation": {
            "type": "object",
            "additionalProperties": false,
            "description": "Rolled-up outcome of every validation check run against this file, or `null` when no\ncheck has ever run — which is the normal state for formats nobody validates, such as\ncover images and HTML previews. `null` therefore means *unknown*, not *passed*.\n\nChecks are not run by the upload request. A file is created with `validation: null`\nand acquires a result seconds to minutes later, once a checker has looked at it, so a\nread-back immediately after an upload still shows `null`. The checkers whose verdicts\nappear here are `pdf-check` for PDFs and `epubchecker` for EPUBs; the per-checker\ndetail behind this summary is at `_links.tests`.\n",
            "nullable": true,
            "properties": {
                "result": {
                    "type": "string",
                    "description": "`passed` if every check on the file passed, `failed` if any one of them failed.\nA `failed` file is still stored and still downloadable: this resource reports the\nverdict, it does not act on it.\n",
                    "example": "failed"
                },
                "errors": {
                    "type": "array",
                    "description": "Every error message from every check, flattened into one list. For EPUBs these\nare EPUBCheck messages and keep their original identifier and location prefix,\nwhich is what identifies the fault inside the source file.\n",
                    "items": {
                        "type": "string"
                    },
                    "example": [
                        "NCX-001: OPS/toc.ncx(-1:-1): NCX identifier (\"com.zeilenquarz.V7\") does not match OPF identifier (\"9783161484100\")."
                    ]
                },
                "warnings": {
                    "type": "array",
                    "description": "Every warning message from every check, flattened into one list. Warnings do not make `result` `failed`.",
                    "items": {
                        "type": "string"
                    },
                    "example": []
                }
            }
        },
        "sha1": {
            "type": "string",
            "description": "SHA-1 digest of the stored bytes, lowercase hex. It is what confirms that a download\nmatches what was stored, and — since file names may repeat and a replaced file keeps\nneither its `_id` nor necessarily a new name — what identifies the bytes themselves.\n`null` for files stored before hashing was introduced, and for URL-registered records\nunless a `sha1` was supplied at creation, in which case the supplied value is returned\nunverified.\n",
            "nullable": true,
            "example": "9f4806983de39ef7b503c4c658d37427d7caf5f4"
        },
        "channel": {
            "type": "string",
            "description": "Restricts the file to one distribution channel, identified by the same lowercase\nchannel slug used elsewhere in the API (`google`, `itunes`, …). It exists for the case\nwhere one channel needs its own variant of an asset — a differently sized cover, a\nwatermarked extract — that must not reach the others.\n\n`null`, the usual case, means the file is not restricted to a channel. Filtering for\nthose records needs the literal, case-sensitive `?channel=NULL`; a plain `?channel=`\ndoes not match them.\n",
            "nullable": true,
            "example": null
        },
        "href": {
            "type": "string",
            "description": "Where the bytes are. For a stored file this is the download endpoint of this API\n(`.../files/{file_id}/file`), and requires the same `Authorization` header as any\nother call — it is not a public or pre-signed link, and it does not expire. For a\nrecord created with `?url=` it is the external URL exactly as registered, which is\nneither fetched nor validated.\n",
            "example": "https://api.openpublishing.com/resource/v4/products/a6fc09af-e616-11ee-a221-00505699575c/files/46033f29-9733-45fa-8069-7e7a23dbe864/file"
        }
    },
    "required": []
}

{
    "errors": [
        {
            "_id": "string",
            "stack_trace": [
                "string"
            ],
            "message": "string",
            "vars": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "required": [
        "errors"
    ],
    "properties": {
        "errors": {
            "type": "array",
            "description": "List of errors occurred",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "_id",
                    "message",
                    "stack_trace",
                    "vars"
                ],
                "properties": {
                    "_id": {
                        "type": "string",
                        "description": "The identifier of the error"
                    },
                    "stack_trace": {
                        "type": "array",
                        "description": "A stacktrace of the error occurred. Only for testing systems.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "message": {
                        "type": "string",
                        "description": "A translated message of the error"
                    },
                    "vars": {
                        "type": "object",
                        "description": "A dict containing additional values of the context of the error."
                    }
                }
            }
        }
    }
}

DELETE /resource/v4/products/{product_id}/files/{file_id}

Delete a product file

Description

Removes the record and the stored bytes, along with the file's labels and validation checks. Deletion is the only way to change a file — a re-upload with the same type follows it.

Deleting one track of an audiobook removes that track only; the other records unpacked from the same archive are unaffected. They are replaced as a set only by uploading a new archive.

Input parameters

Path parameters

Parameter Type Default Nullable Description
file_id string No UUID (or legacy integer id) of the file.
product_id string No UUID (or legacy integer id) of the product.

Responses

{
    "errors": [
        {
            "_id": "string",
            "stack_trace": [
                "string"
            ],
            "message": "string",
            "vars": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "required": [
        "errors"
    ],
    "properties": {
        "errors": {
            "type": "array",
            "description": "List of errors occurred",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "_id",
                    "message",
                    "stack_trace",
                    "vars"
                ],
                "properties": {
                    "_id": {
                        "type": "string",
                        "description": "The identifier of the error"
                    },
                    "stack_trace": {
                        "type": "array",
                        "description": "A stacktrace of the error occurred. Only for testing systems.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "message": {
                        "type": "string",
                        "description": "A translated message of the error"
                    },
                    "vars": {
                        "type": "object",
                        "description": "A dict containing additional values of the context of the error."
                    }
                }
            }
        }
    }
}

{
    "errors": [
        {
            "_id": "string",
            "stack_trace": [
                "string"
            ],
            "message": "string",
            "vars": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "required": [
        "errors"
    ],
    "properties": {
        "errors": {
            "type": "array",
            "description": "List of errors occurred",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "_id",
                    "message",
                    "stack_trace",
                    "vars"
                ],
                "properties": {
                    "_id": {
                        "type": "string",
                        "description": "The identifier of the error"
                    },
                    "stack_trace": {
                        "type": "array",
                        "description": "A stacktrace of the error occurred. Only for testing systems.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "message": {
                        "type": "string",
                        "description": "A translated message of the error"
                    },
                    "vars": {
                        "type": "object",
                        "description": "A dict containing additional values of the context of the error."
                    }
                }
            }
        }
    }
}

GET /resource/v4/products/{product_id}/files/{file_id}/file

Download the file content

Description

Streams the stored bytes. Requires the same Authorization header as every other endpoint; the URL is neither public nor time-limited, so it can be stored and re-used. The response sets Content-Type from the record's mime_type, falling back to application/octet-stream, and Content-Disposition: attachment; filename="<file_name>".

This path does not exist for records registered by url; their bytes are wherever the external href points.

Input parameters

Path parameters

Parameter Type Default Nullable Description
file_id string No UUID (or legacy integer id) of the file.
product_id string No UUID (or legacy integer id) of the product.

Responses

"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ="
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "string",
    "format": "binary"
}

{
    "errors": [
        {
            "_id": "string",
            "stack_trace": [
                "string"
            ],
            "message": "string",
            "vars": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "required": [
        "errors"
    ],
    "properties": {
        "errors": {
            "type": "array",
            "description": "List of errors occurred",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "_id",
                    "message",
                    "stack_trace",
                    "vars"
                ],
                "properties": {
                    "_id": {
                        "type": "string",
                        "description": "The identifier of the error"
                    },
                    "stack_trace": {
                        "type": "array",
                        "description": "A stacktrace of the error occurred. Only for testing systems.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "message": {
                        "type": "string",
                        "description": "A translated message of the error"
                    },
                    "vars": {
                        "type": "object",
                        "description": "A dict containing additional values of the context of the error."
                    }
                }
            }
        }
    }
}

GET /resource/v4/products/{product_id}/files/{file_id}/tests

Retrieve the validation checks of a file

Description

Lists every validation check run against the file, oldest first. This is the itemised form of the file's validation summary: it names the checker, when it ran, how long it took, and every message including the info-level ones that the summary omits.

An empty list corresponds to validation: null on the file.

Input parameters

Path parameters

Parameter Type Default Nullable Description
file_id string No UUID (or legacy integer id) of the file.
product_id string No UUID (or legacy integer id) of the product.

Query parameters

Parameter Type Default Nullable Description
errors string No `?errors=yes` returns only checks that produced at least one error; `?errors=no` only those that produced none.
result string No Filter by verdict — `passed` or `failed`.
type string No Filter by checker, e.g. `?type=epubchecker`. There is at most one record per checker per file.
warnings string No `?warnings=yes` returns only checks that produced at least one warning; `?warnings=no` only those that produced none.

Responses

{
    "_links": {},
    "_pagination": {},
    "items": [
        {
            "_id": 1481212,
            "type": "epubchecker",
            "checker": "Epub Checker",
            "result": "failed",
            "checked_at": 1699362317,
            "duration_in_ms": 23360,
            "messages": [
                {
                    "check": "error",
                    "text": "NCX-001: OPS/toc.ncx(-1:-1): NCX identifier (\"com.zeilenquarz.V7\") does not match OPF identifier (\"9783161484100\")."
                }
            ]
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "_links": {
            "type": "object"
        },
        "_pagination": {
            "type": "object"
        },
        "items": {
            "type": "array",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "description": "The result of one validation checker run against one product file.",
                "properties": {
                    "_id": {
                        "type": "integer",
                        "description": "Identifier of the check record. Unlike every other v4 resource this is a **plain\ninteger, not a UUID**, and there is no `_legacy_id` alongside it — the check\nresource was never migrated to UUIDs, so the v4 identity convention does not apply\nhere.\n",
                        "example": 1481212
                    },
                    "type": {
                        "type": "string",
                        "description": "Which checker produced this record, lowercased. Values are not a closed set, but the\nones that occur are:\n\n- `epubchecker` — the EPUB conformance checker (EPUBCheck)\n- `pdf-check` — the PDF checker\n\nAt most one record exists per (file, `type`) pair, so re-running a checker\noverwrites its previous verdict rather than adding to a history.\n\nFilterable: `?type=epubchecker`. See [Resource Queries](/resource_queries/).\n",
                        "example": "epubchecker"
                    },
                    "checker": {
                        "type": "string",
                        "description": "Human-readable name of the tool that ran, for display in an operator UI. `null` when\nthe checker did not identify itself. Unlike `type` this is free text whose casing is\npreserved as the checker reported it, so it is not a value to branch on; `type` is.\n",
                        "nullable": true,
                        "example": "Epub Checker"
                    },
                    "result": {
                        "type": "string",
                        "description": "`passed` or `failed`. A check can be `passed` and still have produced warnings — the\nverdict only reflects errors. If any check on a file is `failed`, the file's\n`validation.result` is `failed` too.\n",
                        "example": "failed"
                    },
                    "checked_at": {
                        "type": "integer",
                        "description": "When the check ran, as a Unix timestamp (seconds since 1970-01-01 UTC). Records are\nreturned oldest first by this field. Compared against the file's `timestamp` it shows\nwhether a verdict still describes the bytes currently stored.\n",
                        "example": 1699362317
                    },
                    "duration_in_ms": {
                        "type": "integer",
                        "description": "How long the checker took, in milliseconds. EPUB conformance checking unpacks and\nparses the whole book and routinely takes tens of seconds, which is why validation\nresults appear after the upload response rather than in it.\n",
                        "example": 23360
                    },
                    "messages": {
                        "type": "array",
                        "description": "Everything the checker reported, in the order it reported it, or `null` when it\nreported nothing at all. An empty list is not used — a silent successful check has\n`messages: null`.\n",
                        "nullable": true,
                        "items": {
                            "type": "object",
                            "additionalProperties": false,
                            "properties": {
                                "check": {
                                    "type": "string",
                                    "description": "Severity of this message: `error` (makes the check `failed`), `warning`\n(surfaced but harmless to the verdict), or `info` (informational only).\n`error` and `warning` entries are also what the file's `validation.errors` and\n`validation.warnings` lists are built from; `info` entries appear only here.\n",
                                    "example": "error"
                                },
                                "text": {
                                    "type": "string",
                                    "description": "The checker's message verbatim. EPUBCheck messages keep their code and source\nlocation (`NCX-001: OPS/toc.ncx(-1:-1): ...`), which is what identifies the\nfault inside the EPUB.\n",
                                    "example": "NCX-001: OPS/toc.ncx(-1:-1): NCX identifier (\"com.zeilenquarz.V7\") does not match OPF identifier (\"9783161484100\")."
                                }
                            }
                        }
                    }
                },
                "required": []
            }
        }
    },
    "required": [
        "_links",
        "_pagination",
        "items"
    ]
}

{
    "errors": [
        {
            "_id": "string",
            "stack_trace": [
                "string"
            ],
            "message": "string",
            "vars": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "required": [
        "errors"
    ],
    "properties": {
        "errors": {
            "type": "array",
            "description": "List of errors occurred",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "_id",
                    "message",
                    "stack_trace",
                    "vars"
                ],
                "properties": {
                    "_id": {
                        "type": "string",
                        "description": "The identifier of the error"
                    },
                    "stack_trace": {
                        "type": "array",
                        "description": "A stacktrace of the error occurred. Only for testing systems.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "message": {
                        "type": "string",
                        "description": "A translated message of the error"
                    },
                    "vars": {
                        "type": "object",
                        "description": "A dict containing additional values of the context of the error."
                    }
                }
            }
        }
    }
}

GET /resource/v4/products/{product_id}/files/{file_id}/tests/{test_id}

Retrieve a single validation check

Input parameters

Path parameters

Parameter Type Default Nullable Description
file_id string No UUID (or legacy integer id) of the file.
product_id string No UUID (or legacy integer id) of the product.
test_id string No Integer id of the check. Check records have no UUID.

Responses

{
    "_id": 1481212,
    "type": "epubchecker",
    "checker": "Epub Checker",
    "result": "failed",
    "checked_at": 1699362317,
    "duration_in_ms": 23360,
    "messages": [
        {
            "check": "error",
            "text": "NCX-001: OPS/toc.ncx(-1:-1): NCX identifier (\"com.zeilenquarz.V7\") does not match OPF identifier (\"9783161484100\")."
        }
    ]
}
Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "description": "The result of one validation checker run against one product file.",
    "properties": {
        "_id": {
            "type": "integer",
            "description": "Identifier of the check record. Unlike every other v4 resource this is a **plain\ninteger, not a UUID**, and there is no `_legacy_id` alongside it — the check\nresource was never migrated to UUIDs, so the v4 identity convention does not apply\nhere.\n",
            "example": 1481212
        },
        "type": {
            "type": "string",
            "description": "Which checker produced this record, lowercased. Values are not a closed set, but the\nones that occur are:\n\n- `epubchecker` — the EPUB conformance checker (EPUBCheck)\n- `pdf-check` — the PDF checker\n\nAt most one record exists per (file, `type`) pair, so re-running a checker\noverwrites its previous verdict rather than adding to a history.\n\nFilterable: `?type=epubchecker`. See [Resource Queries](/resource_queries/).\n",
            "example": "epubchecker"
        },
        "checker": {
            "type": "string",
            "description": "Human-readable name of the tool that ran, for display in an operator UI. `null` when\nthe checker did not identify itself. Unlike `type` this is free text whose casing is\npreserved as the checker reported it, so it is not a value to branch on; `type` is.\n",
            "nullable": true,
            "example": "Epub Checker"
        },
        "result": {
            "type": "string",
            "description": "`passed` or `failed`. A check can be `passed` and still have produced warnings — the\nverdict only reflects errors. If any check on a file is `failed`, the file's\n`validation.result` is `failed` too.\n",
            "example": "failed"
        },
        "checked_at": {
            "type": "integer",
            "description": "When the check ran, as a Unix timestamp (seconds since 1970-01-01 UTC). Records are\nreturned oldest first by this field. Compared against the file's `timestamp` it shows\nwhether a verdict still describes the bytes currently stored.\n",
            "example": 1699362317
        },
        "duration_in_ms": {
            "type": "integer",
            "description": "How long the checker took, in milliseconds. EPUB conformance checking unpacks and\nparses the whole book and routinely takes tens of seconds, which is why validation\nresults appear after the upload response rather than in it.\n",
            "example": 23360
        },
        "messages": {
            "type": "array",
            "description": "Everything the checker reported, in the order it reported it, or `null` when it\nreported nothing at all. An empty list is not used — a silent successful check has\n`messages: null`.\n",
            "nullable": true,
            "items": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "check": {
                        "type": "string",
                        "description": "Severity of this message: `error` (makes the check `failed`), `warning`\n(surfaced but harmless to the verdict), or `info` (informational only).\n`error` and `warning` entries are also what the file's `validation.errors` and\n`validation.warnings` lists are built from; `info` entries appear only here.\n",
                        "example": "error"
                    },
                    "text": {
                        "type": "string",
                        "description": "The checker's message verbatim. EPUBCheck messages keep their code and source\nlocation (`NCX-001: OPS/toc.ncx(-1:-1): ...`), which is what identifies the\nfault inside the EPUB.\n",
                        "example": "NCX-001: OPS/toc.ncx(-1:-1): NCX identifier (\"com.zeilenquarz.V7\") does not match OPF identifier (\"9783161484100\")."
                    }
                }
            }
        }
    },
    "required": []
}

{
    "errors": [
        {
            "_id": "string",
            "stack_trace": [
                "string"
            ],
            "message": "string",
            "vars": {}
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "required": [
        "errors"
    ],
    "properties": {
        "errors": {
            "type": "array",
            "description": "List of errors occurred",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                    "_id",
                    "message",
                    "stack_trace",
                    "vars"
                ],
                "properties": {
                    "_id": {
                        "type": "string",
                        "description": "The identifier of the error"
                    },
                    "stack_trace": {
                        "type": "array",
                        "description": "A stacktrace of the error occurred. Only for testing systems.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "message": {
                        "type": "string",
                        "description": "A translated message of the error"
                    },
                    "vars": {
                        "type": "object",
                        "description": "A dict containing additional values of the context of the error."
                    }
                }
            }
        }
    }
}

Schemas

Product File

Name Type Description Example
_id string

UUID of the file record. Stable for the lifetime of the record; a replaced file gets a new one.

46033f29-9733-45fa-8069-7e7a23dbe864
_legacy_id integer

Legacy integer identifier of this resource, kept so existing integrations can correlate their own records. This is the id the v3 API returns as _id, and it is accepted in place of the UUID anywhere in the path.

20882910
_links object

self — this file record. tests — the collection of validation checks run against it (/resource/v4/products/{product_id}/files/{file_id}/tests), which is the itemised form of the summary in validation.

_version string

API version that produced this representation.

4.0
channel string| null

Restricts the file to one distribution channel, identified by the same lowercase channel slug used elsewhere in the API (google, itunes, …). It exists for the case where one channel needs its own variant of an asset — a differently sized cover, a watermarked extract — that must not reach the others.

null, the usual case, means the file is not restricted to a channel. Filtering for those records needs the literal, case-sensitive ?channel=NULL; a plain ?channel= does not match them.

file_name string| null

The name the file was uploaded under, with any directory part stripped. Informational only — the platform never dispatches on it, and duplicate names within one product are allowed. It is echoed back in the Content-Disposition header when the content is downloaded, so it is what a browser will save the file as.

v14937.jpg
file_size integer| null

Size of the stored file in bytes. null for records registered by URL, where the platform never receives the bytes.

There is an upload size limit, and exceeding it answers 413 rather than 400 Bad Request. For a file of 4 GiB or more the value reported here is not the true byte count, even though the file itself is stored whole.

267184
href string

Where the bytes are. For a stored file this is the download endpoint of this API (.../files/{file_id}/file), and requires the same Authorization header as any other call — it is not a public or pre-signed link, and it does not expire. For a record created with ?url= it is the external URL exactly as registered, which is neither fetched nor validated.

https://api.openpublishing.com/resource/v4/products/a6fc09af-e616-11ee-a221-00505699575c/files/46033f29-9733-45fa-8069-7e7a23dbe864/file
labels Array<string>

Free-form lowercase tags on the file. Always present, and empty when the file has no tags. Client-supplied tags are passed on upload as a comma-separated labels parameter; they are lowercased and trimmed, and there is no allow-list. They are only applied to an uploaded file: a labels parameter sent alongside url is accepted and silently dropped, leaving the registered record untagged.

Two families of label are set by the platform itself. The first records how the file came to be:

  • autogenerated — the file was produced by the platform's own conversion pipeline rather than uploaded by a human. Replacing an autogenerated file by hand is usually futile, because the next conversion run overwrites it again.
  • book-converter — names the pipeline that produced the file.

The second describes the structure of an EPUB. Uploading an ebook_epub makes the server open the book and tag it with what it finds:

  • fixedlayout — fixed-layout rather than reflowable
  • svgimage — images supplied as SVG
  • svg — SVG content
  • video — embedded video
  • audio — embedded audio
  • object — embedded objects

Filterable by one exact label at a time, equality only: ?labels=autogenerated works, ?labels=auto matches nothing, and ?labels__contains=auto is a 400 Bad Request. A comma-separated list is accepted in a filter but always returns an empty list, even for a file carrying every label named. One request per label, intersected client-side, is the way to express "and". See Resource Queries.

[ "autogenerated" ]
mime_type string| null

Media type of the file. Guessed by the server from the file name extension at upload time — the Content-Type of the multipart part is ignored, and so is the content of the file.

There is no allow-list: an extension the server does not recognise is recorded as application/octet-stream rather than rejected, so this field never reports failure.

null for records registered by URL, whose bytes the platform never receives, and for older records created before media types were recorded. Both are selectable with ?mime_type__isnull.

This drives which extras appear in properties: image/* yields height and width, application/epub+zip yields epub_version.

Filterable — ?mime_type=application/epub+zip. See Resource Queries.

image/jpeg
properties object

Format-specific attributes the server extracted from the file. The keys depend on mime_type and are absent — the object is then {} — for formats that carry none:

  • height, width — pixel dimensions, integers, for any image/* file. They are the only record of the resolution of a cover_marketing image.
  • epub_version"2" or "3", a string, for application/epub+zip. Determined at upload time, and it defaults to "2" whenever the server did not positively identify an EPUB 3, so "2" means "not detected as EPUB 3" rather than "confirmed EPUB 2".

One key is filterable: ?property_epub_version=3. Only 2 and 3 are accepted there; any other value is a 400 Bad Request. The filter matches the version the server detected, so a record for which no version was ever detected is returned by neither 2 nor 3.

{ "height": 2560, "width": 1809 }
sha1 string| null

SHA-1 digest of the stored bytes, lowercase hex. It is what confirms that a download matches what was stored, and — since file names may repeat and a replaced file keeps neither its _id nor necessarily a new name — what identifies the bytes themselves. null for files stored before hashing was introduced, and for URL-registered records unless a sha1 was supplied at creation, in which case the supplied value is returned unverified.

9f4806983de39ef7b503c4c658d37427d7caf5f4
timestamp integer

When the file was added, as a Unix timestamp (seconds since 1970-01-01 UTC, integer, not a string).

Sortable. id and timestamp are the two field names defined for this resource and the two to rely on; other names may or may not resolve, and one that does not is rejected with 400 Bad Request. For the syntax see Resource Queries.

Old records carry the timestamp of the original import, which can predate the platform itself, so the two defined orders do not always agree: the oldest file on a long-running title can be decades older than its _id ordering suggests.

1782319836
type string| null

What the file is for — a manuscript, an e-book edition, a cover image, a preview. This is the only field that decides how the platform treats the asset, and the only one that is mandatory on upload; neither the file name nor the MIME type carries that meaning. Twenty values are accepted; anything else is rejected with 400 Bad Request.

A file record is immutable. There is no PUT. An asset is replaced by a DELETE of the record followed by a POST of the new file under the same type. Apart from channel and labels, every remaining field is derived by the server from the uploaded bytes, so there is nothing else a client could update.

Source material:

  • dropbox — author-supplied source manuscript: the document a publication is produced from, not a deliverable. Unrelated to the Dropbox service.

E-book editions:

  • ebook_epub — the EPUB edition. Inspected on upload: the version lands in properties.epub_version and structural features in labels.
  • ebook_pdf — the PDF edition.
  • ebook_pdf_cover — cover page bound into the PDF edition.
  • ebook_pdf_bastard_title — half-title (bastard title) page bound into the PDF edition.
  • ebook_mobi — the Mobipocket / Kindle edition.
  • ebook_ibook — the Apple iBooks edition.

Print-on-demand:

  • pod_bookblock — the book block, i.e. the interior pages, as a print-ready PDF.
  • pod_cover — the cover as one wrap: front, spine and back together.
  • pod_cover_front — front cover only.
  • pod_cover_back — back cover only.

Covers, previews and samples:

  • cover_marketing — the marketing cover image. Must be a JPEG: the file name has to end in .jpg or .jpeg, or the upload is rejected with 400 Bad Request. The test is on the extension alone, so a PNG renamed to .jpg passes it.
  • extract — a reading extract offered as a sample of the work.
  • shop_preview — HTML preview of the opening pages.
  • shop_preview_pdf — a preview offered as a downloadable PDF.
  • screenshot — screenshot of the product, for app-like or interactive titles.

Audiobooks:

  • audiobook — an instruction to unpack a complete audiobook delivery. No record ever carries it: an upload with type=audiobook is unpacked and each entry becomes a record of one of the three types below, chosen from the entry's own media type — audio/* becomes a track, image/* the cover, application/* the booklet. Only a delivery that unpacks to exactly one record can be answered with that record, so an archive of several tracks is stored and then reported as 400 Bad Request; the derived records are visible on the next GET. Re-uploading replaces the previously derived set.
  • audiobook_track — a single audio track of an audiobook.
  • audiobook_booklet — the booklet accompanying an audiobook.
  • audiobook_cover — cover artwork for an audiobook.

Only the twenty values listed above are accepted on input, and only they are ever returned.

Filterable — ?type=ebook_epub; an unknown type is rejected with 400 Bad Request rather than returning an empty result. See Resource Queries.

null on legacy records that carry no type at all. A legacy record whose type is neither absent nor one of the twenty cannot be represented and answers 400 Bad Request when read.

cover_marketing
validation Properties: result, errors, warnings| null

Rolled-up outcome of every validation check run against this file, or null when no check has ever run — which is the normal state for formats nobody validates, such as cover images and HTML previews. null therefore means unknown, not passed.

Checks are not run by the upload request. A file is created with validation: null and acquires a result seconds to minutes later, once a checker has looked at it, so a read-back immediately after an upload still shows null. The checkers whose verdicts appear here are pdf-check for PDFs and epubchecker for EPUBs; the per-checker detail behind this summary is at _links.tests.

validation.result string

passed if every check on the file passed, failed if any one of them failed. A failed file is still stored and still downloadable: this resource reports the verdict, it does not act on it.

failed
validation.errors Array<string>

Every error message from every check, flattened into one list. For EPUBs these are EPUBCheck messages and keep their original identifier and location prefix, which is what identifies the fault inside the source file.

[ "NCX-001: OPS/toc.ncx(-1:-1): NCX identifier (\"com.zeilenquarz.V7\") does not match OPF identifier (\"9783161484100\")." ]
validation.warnings Array<string>

Every warning message from every check, flattened into one list. Warnings do not make result failed.

[]

Product File Test

Name Type Description Example
_id integer

Identifier of the check record. Unlike every other v4 resource this is a plain integer, not a UUID, and there is no _legacy_id alongside it — the check resource was never migrated to UUIDs, so the v4 identity convention does not apply here.

1481212
checked_at integer

When the check ran, as a Unix timestamp (seconds since 1970-01-01 UTC). Records are returned oldest first by this field. Compared against the file's timestamp it shows whether a verdict still describes the bytes currently stored.

1699362317
checker string| null

Human-readable name of the tool that ran, for display in an operator UI. null when the checker did not identify itself. Unlike type this is free text whose casing is preserved as the checker reported it, so it is not a value to branch on; type is.

Epub Checker
duration_in_ms integer

How long the checker took, in milliseconds. EPUB conformance checking unpacks and parses the whole book and routinely takes tens of seconds, which is why validation results appear after the upload response rather than in it.

23360
messages Array<Properties: check, text>| null

Everything the checker reported, in the order it reported it, or null when it reported nothing at all. An empty list is not used — a silent successful check has messages: null.

messages[].check string

Severity of this message: error (makes the check failed), warning (surfaced but harmless to the verdict), or info (informational only). error and warning entries are also what the file's validation.errors and validation.warnings lists are built from; info entries appear only here.

error
messages[].text string

The checker's message verbatim. EPUBCheck messages keep their code and source location (NCX-001: OPS/toc.ncx(-1:-1): ...), which is what identifies the fault inside the EPUB.

NCX-001: OPS/toc.ncx(-1:-1): NCX identifier ("com.zeilenquarz.V7") does not match OPF identifier ("9783161484100").
result string

passed or failed. A check can be passed and still have produced warnings — the verdict only reflects errors. If any check on a file is failed, the file's validation.result is failed too.

failed
type string

Which checker produced this record, lowercased. Values are not a closed set, but the ones that occur are:

  • epubchecker — the EPUB conformance checker (EPUBCheck)
  • pdf-check — the PDF checker

At most one record exists per (file, type) pair, so re-running a checker overwrites its previous verdict rather than adding to a history.

Filterable: ?type=epubchecker. See Resource Queries.

epubchecker