Skip to content

Product Identifiers v4

Sub-resource of products holding the trade identifiers a product is ordered by — the numbers that appear on the barcode and in every catalogue, order and metadata feed.

Each record names its identifier scheme in type. gtin13 is the scheme in common use: ONIX Codelist 5 code 03, the GS1 Global Trade Item Number, formerly known as the EAN article number.

An identifier belongs to one commercially distinct edition rather than to the product as a whole. What separates one record from another is its product_type, its product_format and the channels it is scoped to.


Endpoints


GET /resource/v4/products/{productId}/identifiers

List product identifiers

Description

Returns every identifier of the product, active and inactive alike, in an unspecified order.

This endpoint does not paginate and does not sort. display, page, page_size and sort are accepted and silently ignored, _links contains only self, and the three _pagination fields are always null. A product has a handful of identifiers, so the whole set is always returned.

Filtering is exact-match only: type, product_type, product_format, value, channel and active may each be given a single value, and filters combine with AND. The comparison operators described in Resource Queries are not implemented here and fail with 400 Bad Request, as does an unrecognised parameter name — note that the channel filter is channel, singular.

Input parameters

Path parameters

Parameter Type Default Nullable Description
productId string No UUID of the product.

Query parameters

Parameter Type Default Nullable Description
active boolean No Filter by status. Only `true` and `yes` (in any case) select active identifiers, and every other value — including `1` — selects the inactive ones instead. So `?active=1` returns the opposite of what it looks like; the reliable spellings are `?active=true` and `?active=false`.
channel string No Filter to identifiers scoped to this distribution channel, e.g. `?channel=amazon`. The value is lower-cased before comparison, so the filter is case-insensitive. Note the singular name: `?channels=` is rejected as an unknown parameter. General identifiers, which have an empty `channels` list, never match.
product_format string No Filter by e-book format: `pdf`, `epub`, `mobi` or `ibooks`. Case-sensitive. Identifiers with no format (the common case) have `product_format: null` and are not matched by any value of this filter, including the empty string — there is no way to select them here.
product_type string No Filter by edition: `ebook`, `book`, `audiobook` or `nonbook`. Case-sensitive — only the lower-case form matches, so `?product_type=EBOOK` matches nothing.
type string No Filter by identifier scheme, e.g. `?type=gtin13`. A scheme that none of the product's identifiers uses returns an empty list rather than an error.
value string No Filter by the exact number, e.g. `?value=9783638202060`. No partial or prefix matching. This filter only looks within the one product named in the path; finding which product an EAN belongs to is a search on the products resource.

Responses

{
    "_links": {
        "first": null,
        "last": null,
        "next": null,
        "prev": null,
        "self": "https://api.openpublishing.com/resource/v4/products/a6fc09af-e616-11ee-a221-00505699575c/identifiers"
    },
    "_pagination": {
        "page": null,
        "page_size": null,
        "total": null
    },
    "items": [
        {
            "_id": "36ff9359-cc26-11f0-8321-00505699575c",
            "type": "gtin13",
            "product_type": "ebook",
            "product_format": null,
            "value": "9783638202060",
            "channels": [],
            "active": true
        },
        {
            "_id": "39f506bf-cc26-11f0-8321-00505699575c",
            "type": "gtin13",
            "product_type": "book",
            "product_format": null,
            "value": "9783638771337",
            "channels": [],
            "active": true
        }
    ]
}
Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "_links": {
            "type": "object"
        },
        "_pagination": {
            "type": "object"
        },
        "items": {
            "type": "array",
            "items": {
                "type": "object",
                "required": [
                    "type",
                    "product_type",
                    "product_format",
                    "value",
                    "channels",
                    "_id",
                    "active"
                ],
                "additionalProperties": false,
                "description": "One trade identifier of a product, scoped to one product type and optionally to one e-book format and to named distribution channels.",
                "properties": {
                    "_id": {
                        "type": "string",
                        "description": "UUID of this identifier record — not of the product and not of the EAN. It is the id that\ngoes in the path of `GET`, `PUT` and `DELETE`. Unlike most v4 resources this one carries no\n`_legacy_id`, `_version` or `_links`: the v3 and v4 payloads are identical, so an\nintegration migrating from `/resource/v3/` needs no field mapping here.\n",
                        "example": "36ff9359-cc26-11f0-8321-00505699575c",
                        "nullable": true
                    },
                    "type": {
                        "type": "string",
                        "description": "Which identifier scheme this record uses. `gtin13` is the scheme in common use —\n[ONIX Codelist 5](https://ns.editeur.org/onix/en/5) code `03`, GTIN-13 (GS1 Global Trade\nItem Number, formerly the EAN article number). An unrecognised scheme is rejected with\n`400 Bad Request`, and filtering the list by a scheme none of the product's identifiers\nuses returns an empty result rather than an error.\n\nA GTIN-13 beginning with the Bookland prefix `978` or `9791`–`9799` is at the same time a\nvalid ISBN-13, Codelist 5 code `15`. The ISBN-13 and the GTIN-13 are the same thirteen\ndigits, so one `gtin13` record covers both.\n\nSome other identifiers of a product are currently exposed elsewhere rather than here:\n\n- **DOI** — Codelist 5 code `06` — the product's `doi` field.\n- **Proprietary reference**, such as an ASIN or a partner's own catalogue key — Codelist 5\n  code `01` — the product's `external_identifiers` array.\n",
                        "example": "gtin13",
                        "x-enum-descriptions": {
                            "gtin13": "GS1 Global Trade Item Number, 13 digits — ONIX Codelist 5 code 03."
                        }
                    },
                    "product_type": {
                        "type": "string",
                        "nullable": true,
                        "description": "Which physical or digital edition of the product this number identifies. A product carries\none identifier per commercially distinct edition, and this is the first of the two fields\nthat separate them; `product_format` is the second.\n\nRequired in practice: an identifier saved as active without a `product_type` is rejected\nwith `400 Bad Request`.\n\nFour values are meaningful here:\n\n- `book` — printed edition, including print-on-demand.\n- `ebook` — digital edition, in one or in all e-book file formats.\n- `audiobook` — audiobook edition.\n- `nonbook` — non-book article.\n\nInput is lower-cased, so `\"EBOOK\"` comes back as `\"ebook\"` — but the list filter is\ncase-sensitive and matches only the lower-case form.\n\nTogether with `product_format` this resolves to a single edition, and only these\ncombinations survive a round-trip:\n\n| `product_type` | `product_format` | Identifies |\n|---|---|---|\n| `book`      | `null`   | Printed edition, all print channels |\n| `ebook`     | `null`   | Every e-book format of the product |\n| `ebook`     | `pdf`    | PDF edition only |\n| `ebook`     | `epub`   | EPUB edition only |\n| `ebook`     | `mobi`   | Mobipocket / Kindle edition only |\n| `ebook`     | `ibooks` | Apple Books edition only |\n| `audiobook` | `null`   | Audiobook edition |\n| `nonbook`   | `null`   | Non-book article |\n\nAnything outside the table is either silently normalised (see `product_format`) or\nrejected. In particular, a `product_type` that is not one of the four above leaves the\nrecord with no usable edition, and an active identifier in that state is rejected with\n`400 Bad Request`.\n",
                        "example": "ebook",
                        "x-enum-descriptions": {
                            "book": "Printed edition, including print-on-demand. Product format is always null.",
                            "ebook": "Digital edition. A null product format gives one EAN covering all e-book formats; a named format gives that format its own EAN.",
                            "audiobook": "Audiobook edition. Product format is always null.",
                            "nonbook": "Non-book article (merchandise, kits, and similar). Product format is always null."
                        }
                    },
                    "product_format": {
                        "type": "string",
                        "description": "Narrows an `ebook` identifier to a single file format, so that one e-book format can carry\nits own EAN. `null` means the identifier covers every format of its `product_type`, and is\nthe common case.\n\nOnly meaningful for `product_type: \"ebook\"`, where four formats are recognised:\n\n- `pdf` — PDF edition.\n- `epub` — EPUB edition.\n- `mobi` — Mobipocket / Kindle edition.\n- `ibooks` — Apple Books edition.\n\nFor `book`, `audiobook` and `nonbook` the format is **silently discarded** rather than\nrejected: `product_type: \"book\"` sent together with `product_format: \"pod\"` comes back with\n`product_format: null`, because print-on-demand shares the printed edition's number. A\nvalue that is not one of the four formats above is discarded the same way; the record\nkeeps the format implied by the edition it resolves to, which on a newly created\nidentifier is `null`.\n\nChoosing between `null` and a format is a commercial decision, not a technical one. One\nEAN covering all e-book formats is by far the common case in practice; a per-format EAN is\nneeded when a retailer lists the EPUB and the PDF as separate, separately ordered articles.\nA lookup for a format that has no identifier of its own falls back to the identifier that\nhas no format.\n",
                        "nullable": true,
                        "example": "epub",
                        "x-enum-descriptions": {
                            "pdf": "PDF edition.",
                            "epub": "EPUB edition.",
                            "mobi": "Mobipocket / Kindle edition.",
                            "ibooks": "Apple Books edition."
                        }
                    },
                    "value": {
                        "type": "string",
                        "description": "The number itself, bare — no hyphens, no spaces. For a `gtin13` identifier it is exactly 13\ndigits, and book numbers start with the Bookland prefix `978` or `9791`–`9799`, which is\nwhat makes them valid ISBN-13s as well as GTIN-13s.\n\n**Check digit.** The thirteenth digit of a GTIN-13 is a GS1 modulo-10 check digit over the\nfirst twelve: the digits are multiplied alternately by 1 and 3 from the left and summed, and\nthe check digit is whatever brings the total to a multiple of 10. A wrong one is rejected with\n`400 Bad Request`. So is a hyphenated ISBN such as `978-3-16-148410-0` — at 17 characters it\nis not 13 digits long. A 10-digit ISBN is not accepted either, and has to be converted to\nISBN-13 first.\n\nThe hyphenated, human-readable rendition cannot be set through this endpoint. It is\nderived, and exposed read-only as `isbn` in the product resource's `eans` array.\n\n**Uniqueness.** An active number may identify only one product per realm; a second product\nclaiming it is rejected with `400 Bad Request`. The same number may also not be active twice\non one product under different product types — one EAN cannot be both the printed book and\nthe e-book. Deactivating the record that holds it (`active: false`) releases the number for\nanother record.\n",
                        "example": "9783638202060"
                    },
                    "channels": {
                        "type": "array",
                        "description": "Distribution channels for which this number is the one to use. An **empty list is the\nnormal case**: it makes the identifier the product's general EAN for its product type, used\nby every channel unless a channel-specific identifier exists.\n\nA non-empty list is needed only when a specific partner requires its own article number for\nan edition that already has one — a lending platform, or a retailer that will not share the\ntrade EAN. Such an identifier serves that channel alone, and the general one continues to\nserve everyone else.\n\nValues are the platform's distribution-channel slugs. Those in common use are:\n\n- `amazon`\n- `google`\n- `itunes`\n- `kobo`\n- `spotify`\n- `libri`\n- `ceebo`\n- `divibib`\n- `overdrive`\n- `bookwire`\n\nThe list is open-ended — a realm distributes through whichever partners it has contracts\nwith. Values are lower-cased, sorted and de-duplicated, so `[\"Amazon\", \"itunes\",\n\"AMAZON\"]` comes back as `[\"amazon\", \"itunes\"]`, and on update the list is replaced\nwholesale rather than merged.\n\n**Names are not validated.** A misspelled channel is accepted as given and matches nothing,\nso the identifier is never selected for any channel; there is no error on create or update.\n\nWithin one product and one product type, the same channel may not appear on two active\nidentifiers — `400 Bad Request`. Deactivating an identifier frees its channels, and the same\nchannel may be reused on a different product type.\n",
                        "items": {
                            "type": "string"
                        },
                        "example": [
                            "amazon",
                            "itunes"
                        ]
                    },
                    "active": {
                        "type": "boolean",
                        "description": "Whether this number is in force. Only active identifiers are resolved when a channel looks\nup an EAN, and only active identifiers are counted by the uniqueness and channel checks.\n\nSetting `active: false` is a **soft delete**: the record stays readable and keeps appearing\nin the list, but it stops identifying the product and releases its number and channels for\nreuse. It is the way to withdraw a number whose history is worth keeping. `DELETE` removes\nthe record outright, and after it a `GET` on the same id returns `404`.\n\nNewly created identifiers are **not** active unless the field says so. A `POST` that omits it\ncreates an inactive record, which reports `active: false` and is not resolved when a channel\nlooks up an EAN. The requirement that `product_type` be set is not applied to an inactive\nrecord either, so a missing `product_type` goes unreported until the record is activated.\n`active: true` therefore belongs in every create that is not deliberately inactive.\n",
                        "example": true
                    }
                }
            }
        }
    },
    "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/{productId}/identifiers

Create a new identifier

Description

Adds one identifier to the product.

A usable create carries value, product_type and active: true. Omitting active creates an inactive record: it reports active: false and is not resolved when a channel looks up an EAN. The requirement that product_type be set is not applied to an inactive record either, so a missing product_type goes unreported until the record is activated. type may be omitted; an unrecognised scheme is rejected with 400 Bad Request.

The example below registers the printed edition of a title that already has an e-book EAN. Because product_type differs, the two coexist; had the same number been sent, the request would have failed with 400 Bad Request on the uniqueness rule.

Input parameters

Path parameters

Parameter Type Default Nullable Description
productId string No UUID of the product.

Request body

{
    "type": "gtin13",
    "product_type": "book",
    "product_format": null,
    "value": "9783638771337",
    "channels": [],
    "active": true
}
Schema of the request body
{
    "type": "object",
    "required": [
        "type",
        "product_type",
        "product_format",
        "value",
        "channels",
        "_id",
        "active"
    ],
    "additionalProperties": false,
    "description": "One trade identifier of a product, scoped to one product type and optionally to one e-book format and to named distribution channels.",
    "properties": {
        "_id": {
            "type": "string",
            "description": "UUID of this identifier record — not of the product and not of the EAN. It is the id that\ngoes in the path of `GET`, `PUT` and `DELETE`. Unlike most v4 resources this one carries no\n`_legacy_id`, `_version` or `_links`: the v3 and v4 payloads are identical, so an\nintegration migrating from `/resource/v3/` needs no field mapping here.\n",
            "example": "36ff9359-cc26-11f0-8321-00505699575c",
            "nullable": true
        },
        "type": {
            "type": "string",
            "description": "Which identifier scheme this record uses. `gtin13` is the scheme in common use —\n[ONIX Codelist 5](https://ns.editeur.org/onix/en/5) code `03`, GTIN-13 (GS1 Global Trade\nItem Number, formerly the EAN article number). An unrecognised scheme is rejected with\n`400 Bad Request`, and filtering the list by a scheme none of the product's identifiers\nuses returns an empty result rather than an error.\n\nA GTIN-13 beginning with the Bookland prefix `978` or `9791`–`9799` is at the same time a\nvalid ISBN-13, Codelist 5 code `15`. The ISBN-13 and the GTIN-13 are the same thirteen\ndigits, so one `gtin13` record covers both.\n\nSome other identifiers of a product are currently exposed elsewhere rather than here:\n\n- **DOI** — Codelist 5 code `06` — the product's `doi` field.\n- **Proprietary reference**, such as an ASIN or a partner's own catalogue key — Codelist 5\n  code `01` — the product's `external_identifiers` array.\n",
            "example": "gtin13",
            "x-enum-descriptions": {
                "gtin13": "GS1 Global Trade Item Number, 13 digits — ONIX Codelist 5 code 03."
            }
        },
        "product_type": {
            "type": "string",
            "nullable": true,
            "description": "Which physical or digital edition of the product this number identifies. A product carries\none identifier per commercially distinct edition, and this is the first of the two fields\nthat separate them; `product_format` is the second.\n\nRequired in practice: an identifier saved as active without a `product_type` is rejected\nwith `400 Bad Request`.\n\nFour values are meaningful here:\n\n- `book` — printed edition, including print-on-demand.\n- `ebook` — digital edition, in one or in all e-book file formats.\n- `audiobook` — audiobook edition.\n- `nonbook` — non-book article.\n\nInput is lower-cased, so `\"EBOOK\"` comes back as `\"ebook\"` — but the list filter is\ncase-sensitive and matches only the lower-case form.\n\nTogether with `product_format` this resolves to a single edition, and only these\ncombinations survive a round-trip:\n\n| `product_type` | `product_format` | Identifies |\n|---|---|---|\n| `book`      | `null`   | Printed edition, all print channels |\n| `ebook`     | `null`   | Every e-book format of the product |\n| `ebook`     | `pdf`    | PDF edition only |\n| `ebook`     | `epub`   | EPUB edition only |\n| `ebook`     | `mobi`   | Mobipocket / Kindle edition only |\n| `ebook`     | `ibooks` | Apple Books edition only |\n| `audiobook` | `null`   | Audiobook edition |\n| `nonbook`   | `null`   | Non-book article |\n\nAnything outside the table is either silently normalised (see `product_format`) or\nrejected. In particular, a `product_type` that is not one of the four above leaves the\nrecord with no usable edition, and an active identifier in that state is rejected with\n`400 Bad Request`.\n",
            "example": "ebook",
            "x-enum-descriptions": {
                "book": "Printed edition, including print-on-demand. Product format is always null.",
                "ebook": "Digital edition. A null product format gives one EAN covering all e-book formats; a named format gives that format its own EAN.",
                "audiobook": "Audiobook edition. Product format is always null.",
                "nonbook": "Non-book article (merchandise, kits, and similar). Product format is always null."
            }
        },
        "product_format": {
            "type": "string",
            "description": "Narrows an `ebook` identifier to a single file format, so that one e-book format can carry\nits own EAN. `null` means the identifier covers every format of its `product_type`, and is\nthe common case.\n\nOnly meaningful for `product_type: \"ebook\"`, where four formats are recognised:\n\n- `pdf` — PDF edition.\n- `epub` — EPUB edition.\n- `mobi` — Mobipocket / Kindle edition.\n- `ibooks` — Apple Books edition.\n\nFor `book`, `audiobook` and `nonbook` the format is **silently discarded** rather than\nrejected: `product_type: \"book\"` sent together with `product_format: \"pod\"` comes back with\n`product_format: null`, because print-on-demand shares the printed edition's number. A\nvalue that is not one of the four formats above is discarded the same way; the record\nkeeps the format implied by the edition it resolves to, which on a newly created\nidentifier is `null`.\n\nChoosing between `null` and a format is a commercial decision, not a technical one. One\nEAN covering all e-book formats is by far the common case in practice; a per-format EAN is\nneeded when a retailer lists the EPUB and the PDF as separate, separately ordered articles.\nA lookup for a format that has no identifier of its own falls back to the identifier that\nhas no format.\n",
            "nullable": true,
            "example": "epub",
            "x-enum-descriptions": {
                "pdf": "PDF edition.",
                "epub": "EPUB edition.",
                "mobi": "Mobipocket / Kindle edition.",
                "ibooks": "Apple Books edition."
            }
        },
        "value": {
            "type": "string",
            "description": "The number itself, bare — no hyphens, no spaces. For a `gtin13` identifier it is exactly 13\ndigits, and book numbers start with the Bookland prefix `978` or `9791`–`9799`, which is\nwhat makes them valid ISBN-13s as well as GTIN-13s.\n\n**Check digit.** The thirteenth digit of a GTIN-13 is a GS1 modulo-10 check digit over the\nfirst twelve: the digits are multiplied alternately by 1 and 3 from the left and summed, and\nthe check digit is whatever brings the total to a multiple of 10. A wrong one is rejected with\n`400 Bad Request`. So is a hyphenated ISBN such as `978-3-16-148410-0` — at 17 characters it\nis not 13 digits long. A 10-digit ISBN is not accepted either, and has to be converted to\nISBN-13 first.\n\nThe hyphenated, human-readable rendition cannot be set through this endpoint. It is\nderived, and exposed read-only as `isbn` in the product resource's `eans` array.\n\n**Uniqueness.** An active number may identify only one product per realm; a second product\nclaiming it is rejected with `400 Bad Request`. The same number may also not be active twice\non one product under different product types — one EAN cannot be both the printed book and\nthe e-book. Deactivating the record that holds it (`active: false`) releases the number for\nanother record.\n",
            "example": "9783638202060"
        },
        "channels": {
            "type": "array",
            "description": "Distribution channels for which this number is the one to use. An **empty list is the\nnormal case**: it makes the identifier the product's general EAN for its product type, used\nby every channel unless a channel-specific identifier exists.\n\nA non-empty list is needed only when a specific partner requires its own article number for\nan edition that already has one — a lending platform, or a retailer that will not share the\ntrade EAN. Such an identifier serves that channel alone, and the general one continues to\nserve everyone else.\n\nValues are the platform's distribution-channel slugs. Those in common use are:\n\n- `amazon`\n- `google`\n- `itunes`\n- `kobo`\n- `spotify`\n- `libri`\n- `ceebo`\n- `divibib`\n- `overdrive`\n- `bookwire`\n\nThe list is open-ended — a realm distributes through whichever partners it has contracts\nwith. Values are lower-cased, sorted and de-duplicated, so `[\"Amazon\", \"itunes\",\n\"AMAZON\"]` comes back as `[\"amazon\", \"itunes\"]`, and on update the list is replaced\nwholesale rather than merged.\n\n**Names are not validated.** A misspelled channel is accepted as given and matches nothing,\nso the identifier is never selected for any channel; there is no error on create or update.\n\nWithin one product and one product type, the same channel may not appear on two active\nidentifiers — `400 Bad Request`. Deactivating an identifier frees its channels, and the same\nchannel may be reused on a different product type.\n",
            "items": {
                "type": "string"
            },
            "example": [
                "amazon",
                "itunes"
            ]
        },
        "active": {
            "type": "boolean",
            "description": "Whether this number is in force. Only active identifiers are resolved when a channel looks\nup an EAN, and only active identifiers are counted by the uniqueness and channel checks.\n\nSetting `active: false` is a **soft delete**: the record stays readable and keeps appearing\nin the list, but it stops identifying the product and releases its number and channels for\nreuse. It is the way to withdraw a number whose history is worth keeping. `DELETE` removes\nthe record outright, and after it a `GET` on the same id returns `404`.\n\nNewly created identifiers are **not** active unless the field says so. A `POST` that omits it\ncreates an inactive record, which reports `active: false` and is not resolved when a channel\nlooks up an EAN. The requirement that `product_type` be set is not applied to an inactive\nrecord either, so a missing `product_type` goes unreported until the record is activated.\n`active: true` therefore belongs in every create that is not deliberately inactive.\n",
            "example": true
        }
    }
}

Responses

{
    "_id": "39f506bf-cc26-11f0-8321-00505699575c",
    "type": "gtin13",
    "product_type": "book",
    "product_format": null,
    "value": "9783638771337",
    "channels": [],
    "active": true
}
Schema of the response body
{
    "type": "object",
    "required": [
        "type",
        "product_type",
        "product_format",
        "value",
        "channels",
        "_id",
        "active"
    ],
    "additionalProperties": false,
    "description": "One trade identifier of a product, scoped to one product type and optionally to one e-book format and to named distribution channels.",
    "properties": {
        "_id": {
            "type": "string",
            "description": "UUID of this identifier record — not of the product and not of the EAN. It is the id that\ngoes in the path of `GET`, `PUT` and `DELETE`. Unlike most v4 resources this one carries no\n`_legacy_id`, `_version` or `_links`: the v3 and v4 payloads are identical, so an\nintegration migrating from `/resource/v3/` needs no field mapping here.\n",
            "example": "36ff9359-cc26-11f0-8321-00505699575c",
            "nullable": true
        },
        "type": {
            "type": "string",
            "description": "Which identifier scheme this record uses. `gtin13` is the scheme in common use —\n[ONIX Codelist 5](https://ns.editeur.org/onix/en/5) code `03`, GTIN-13 (GS1 Global Trade\nItem Number, formerly the EAN article number). An unrecognised scheme is rejected with\n`400 Bad Request`, and filtering the list by a scheme none of the product's identifiers\nuses returns an empty result rather than an error.\n\nA GTIN-13 beginning with the Bookland prefix `978` or `9791`–`9799` is at the same time a\nvalid ISBN-13, Codelist 5 code `15`. The ISBN-13 and the GTIN-13 are the same thirteen\ndigits, so one `gtin13` record covers both.\n\nSome other identifiers of a product are currently exposed elsewhere rather than here:\n\n- **DOI** — Codelist 5 code `06` — the product's `doi` field.\n- **Proprietary reference**, such as an ASIN or a partner's own catalogue key — Codelist 5\n  code `01` — the product's `external_identifiers` array.\n",
            "example": "gtin13",
            "x-enum-descriptions": {
                "gtin13": "GS1 Global Trade Item Number, 13 digits — ONIX Codelist 5 code 03."
            }
        },
        "product_type": {
            "type": "string",
            "nullable": true,
            "description": "Which physical or digital edition of the product this number identifies. A product carries\none identifier per commercially distinct edition, and this is the first of the two fields\nthat separate them; `product_format` is the second.\n\nRequired in practice: an identifier saved as active without a `product_type` is rejected\nwith `400 Bad Request`.\n\nFour values are meaningful here:\n\n- `book` — printed edition, including print-on-demand.\n- `ebook` — digital edition, in one or in all e-book file formats.\n- `audiobook` — audiobook edition.\n- `nonbook` — non-book article.\n\nInput is lower-cased, so `\"EBOOK\"` comes back as `\"ebook\"` — but the list filter is\ncase-sensitive and matches only the lower-case form.\n\nTogether with `product_format` this resolves to a single edition, and only these\ncombinations survive a round-trip:\n\n| `product_type` | `product_format` | Identifies |\n|---|---|---|\n| `book`      | `null`   | Printed edition, all print channels |\n| `ebook`     | `null`   | Every e-book format of the product |\n| `ebook`     | `pdf`    | PDF edition only |\n| `ebook`     | `epub`   | EPUB edition only |\n| `ebook`     | `mobi`   | Mobipocket / Kindle edition only |\n| `ebook`     | `ibooks` | Apple Books edition only |\n| `audiobook` | `null`   | Audiobook edition |\n| `nonbook`   | `null`   | Non-book article |\n\nAnything outside the table is either silently normalised (see `product_format`) or\nrejected. In particular, a `product_type` that is not one of the four above leaves the\nrecord with no usable edition, and an active identifier in that state is rejected with\n`400 Bad Request`.\n",
            "example": "ebook",
            "x-enum-descriptions": {
                "book": "Printed edition, including print-on-demand. Product format is always null.",
                "ebook": "Digital edition. A null product format gives one EAN covering all e-book formats; a named format gives that format its own EAN.",
                "audiobook": "Audiobook edition. Product format is always null.",
                "nonbook": "Non-book article (merchandise, kits, and similar). Product format is always null."
            }
        },
        "product_format": {
            "type": "string",
            "description": "Narrows an `ebook` identifier to a single file format, so that one e-book format can carry\nits own EAN. `null` means the identifier covers every format of its `product_type`, and is\nthe common case.\n\nOnly meaningful for `product_type: \"ebook\"`, where four formats are recognised:\n\n- `pdf` — PDF edition.\n- `epub` — EPUB edition.\n- `mobi` — Mobipocket / Kindle edition.\n- `ibooks` — Apple Books edition.\n\nFor `book`, `audiobook` and `nonbook` the format is **silently discarded** rather than\nrejected: `product_type: \"book\"` sent together with `product_format: \"pod\"` comes back with\n`product_format: null`, because print-on-demand shares the printed edition's number. A\nvalue that is not one of the four formats above is discarded the same way; the record\nkeeps the format implied by the edition it resolves to, which on a newly created\nidentifier is `null`.\n\nChoosing between `null` and a format is a commercial decision, not a technical one. One\nEAN covering all e-book formats is by far the common case in practice; a per-format EAN is\nneeded when a retailer lists the EPUB and the PDF as separate, separately ordered articles.\nA lookup for a format that has no identifier of its own falls back to the identifier that\nhas no format.\n",
            "nullable": true,
            "example": "epub",
            "x-enum-descriptions": {
                "pdf": "PDF edition.",
                "epub": "EPUB edition.",
                "mobi": "Mobipocket / Kindle edition.",
                "ibooks": "Apple Books edition."
            }
        },
        "value": {
            "type": "string",
            "description": "The number itself, bare — no hyphens, no spaces. For a `gtin13` identifier it is exactly 13\ndigits, and book numbers start with the Bookland prefix `978` or `9791`–`9799`, which is\nwhat makes them valid ISBN-13s as well as GTIN-13s.\n\n**Check digit.** The thirteenth digit of a GTIN-13 is a GS1 modulo-10 check digit over the\nfirst twelve: the digits are multiplied alternately by 1 and 3 from the left and summed, and\nthe check digit is whatever brings the total to a multiple of 10. A wrong one is rejected with\n`400 Bad Request`. So is a hyphenated ISBN such as `978-3-16-148410-0` — at 17 characters it\nis not 13 digits long. A 10-digit ISBN is not accepted either, and has to be converted to\nISBN-13 first.\n\nThe hyphenated, human-readable rendition cannot be set through this endpoint. It is\nderived, and exposed read-only as `isbn` in the product resource's `eans` array.\n\n**Uniqueness.** An active number may identify only one product per realm; a second product\nclaiming it is rejected with `400 Bad Request`. The same number may also not be active twice\non one product under different product types — one EAN cannot be both the printed book and\nthe e-book. Deactivating the record that holds it (`active: false`) releases the number for\nanother record.\n",
            "example": "9783638202060"
        },
        "channels": {
            "type": "array",
            "description": "Distribution channels for which this number is the one to use. An **empty list is the\nnormal case**: it makes the identifier the product's general EAN for its product type, used\nby every channel unless a channel-specific identifier exists.\n\nA non-empty list is needed only when a specific partner requires its own article number for\nan edition that already has one — a lending platform, or a retailer that will not share the\ntrade EAN. Such an identifier serves that channel alone, and the general one continues to\nserve everyone else.\n\nValues are the platform's distribution-channel slugs. Those in common use are:\n\n- `amazon`\n- `google`\n- `itunes`\n- `kobo`\n- `spotify`\n- `libri`\n- `ceebo`\n- `divibib`\n- `overdrive`\n- `bookwire`\n\nThe list is open-ended — a realm distributes through whichever partners it has contracts\nwith. Values are lower-cased, sorted and de-duplicated, so `[\"Amazon\", \"itunes\",\n\"AMAZON\"]` comes back as `[\"amazon\", \"itunes\"]`, and on update the list is replaced\nwholesale rather than merged.\n\n**Names are not validated.** A misspelled channel is accepted as given and matches nothing,\nso the identifier is never selected for any channel; there is no error on create or update.\n\nWithin one product and one product type, the same channel may not appear on two active\nidentifiers — `400 Bad Request`. Deactivating an identifier frees its channels, and the same\nchannel may be reused on a different product type.\n",
            "items": {
                "type": "string"
            },
            "example": [
                "amazon",
                "itunes"
            ]
        },
        "active": {
            "type": "boolean",
            "description": "Whether this number is in force. Only active identifiers are resolved when a channel looks\nup an EAN, and only active identifiers are counted by the uniqueness and channel checks.\n\nSetting `active: false` is a **soft delete**: the record stays readable and keeps appearing\nin the list, but it stops identifying the product and releases its number and channels for\nreuse. It is the way to withdraw a number whose history is worth keeping. `DELETE` removes\nthe record outright, and after it a `GET` on the same id returns `404`.\n\nNewly created identifiers are **not** active unless the field says so. A `POST` that omits it\ncreates an inactive record, which reports `active: false` and is not resolved when a channel\nlooks up an EAN. The requirement that `product_type` be set is not applied to an inactive\nrecord either, so a missing `product_type` goes unreported until the record is activated.\n`active: true` therefore belongs in every create that is not deliberately inactive.\n",
            "example": true
        }
    }
}

{
    "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/{productId}/identifiers/{identifierId}

Get details of a single identifier

Description

Returns one identifier by its own UUID. Deactivated identifiers are still returned; only a DELETE makes an id disappear.

The example is an e-book identifier narrowed to a single format: this EPUB edition is listed and ordered separately from the title's other e-book formats.

Input parameters

Path parameters

Parameter Type Default Nullable Description
identifierId string No UUID of the identifier record, as returned in `_id`. This is not the EAN — to look up an identifier by its number, filter the list with `?value=`.
productId string No UUID of the product.

Responses

{
    "_id": "666c0dab-cc26-11f0-8321-00505699575c",
    "type": "gtin13",
    "product_type": "ebook",
    "product_format": "epub",
    "value": "9783838700601",
    "channels": [],
    "active": true
}
Schema of the response body
{
    "type": "object",
    "required": [
        "type",
        "product_type",
        "product_format",
        "value",
        "channels",
        "_id",
        "active"
    ],
    "additionalProperties": false,
    "description": "One trade identifier of a product, scoped to one product type and optionally to one e-book format and to named distribution channels.",
    "properties": {
        "_id": {
            "type": "string",
            "description": "UUID of this identifier record — not of the product and not of the EAN. It is the id that\ngoes in the path of `GET`, `PUT` and `DELETE`. Unlike most v4 resources this one carries no\n`_legacy_id`, `_version` or `_links`: the v3 and v4 payloads are identical, so an\nintegration migrating from `/resource/v3/` needs no field mapping here.\n",
            "example": "36ff9359-cc26-11f0-8321-00505699575c",
            "nullable": true
        },
        "type": {
            "type": "string",
            "description": "Which identifier scheme this record uses. `gtin13` is the scheme in common use —\n[ONIX Codelist 5](https://ns.editeur.org/onix/en/5) code `03`, GTIN-13 (GS1 Global Trade\nItem Number, formerly the EAN article number). An unrecognised scheme is rejected with\n`400 Bad Request`, and filtering the list by a scheme none of the product's identifiers\nuses returns an empty result rather than an error.\n\nA GTIN-13 beginning with the Bookland prefix `978` or `9791`–`9799` is at the same time a\nvalid ISBN-13, Codelist 5 code `15`. The ISBN-13 and the GTIN-13 are the same thirteen\ndigits, so one `gtin13` record covers both.\n\nSome other identifiers of a product are currently exposed elsewhere rather than here:\n\n- **DOI** — Codelist 5 code `06` — the product's `doi` field.\n- **Proprietary reference**, such as an ASIN or a partner's own catalogue key — Codelist 5\n  code `01` — the product's `external_identifiers` array.\n",
            "example": "gtin13",
            "x-enum-descriptions": {
                "gtin13": "GS1 Global Trade Item Number, 13 digits — ONIX Codelist 5 code 03."
            }
        },
        "product_type": {
            "type": "string",
            "nullable": true,
            "description": "Which physical or digital edition of the product this number identifies. A product carries\none identifier per commercially distinct edition, and this is the first of the two fields\nthat separate them; `product_format` is the second.\n\nRequired in practice: an identifier saved as active without a `product_type` is rejected\nwith `400 Bad Request`.\n\nFour values are meaningful here:\n\n- `book` — printed edition, including print-on-demand.\n- `ebook` — digital edition, in one or in all e-book file formats.\n- `audiobook` — audiobook edition.\n- `nonbook` — non-book article.\n\nInput is lower-cased, so `\"EBOOK\"` comes back as `\"ebook\"` — but the list filter is\ncase-sensitive and matches only the lower-case form.\n\nTogether with `product_format` this resolves to a single edition, and only these\ncombinations survive a round-trip:\n\n| `product_type` | `product_format` | Identifies |\n|---|---|---|\n| `book`      | `null`   | Printed edition, all print channels |\n| `ebook`     | `null`   | Every e-book format of the product |\n| `ebook`     | `pdf`    | PDF edition only |\n| `ebook`     | `epub`   | EPUB edition only |\n| `ebook`     | `mobi`   | Mobipocket / Kindle edition only |\n| `ebook`     | `ibooks` | Apple Books edition only |\n| `audiobook` | `null`   | Audiobook edition |\n| `nonbook`   | `null`   | Non-book article |\n\nAnything outside the table is either silently normalised (see `product_format`) or\nrejected. In particular, a `product_type` that is not one of the four above leaves the\nrecord with no usable edition, and an active identifier in that state is rejected with\n`400 Bad Request`.\n",
            "example": "ebook",
            "x-enum-descriptions": {
                "book": "Printed edition, including print-on-demand. Product format is always null.",
                "ebook": "Digital edition. A null product format gives one EAN covering all e-book formats; a named format gives that format its own EAN.",
                "audiobook": "Audiobook edition. Product format is always null.",
                "nonbook": "Non-book article (merchandise, kits, and similar). Product format is always null."
            }
        },
        "product_format": {
            "type": "string",
            "description": "Narrows an `ebook` identifier to a single file format, so that one e-book format can carry\nits own EAN. `null` means the identifier covers every format of its `product_type`, and is\nthe common case.\n\nOnly meaningful for `product_type: \"ebook\"`, where four formats are recognised:\n\n- `pdf` — PDF edition.\n- `epub` — EPUB edition.\n- `mobi` — Mobipocket / Kindle edition.\n- `ibooks` — Apple Books edition.\n\nFor `book`, `audiobook` and `nonbook` the format is **silently discarded** rather than\nrejected: `product_type: \"book\"` sent together with `product_format: \"pod\"` comes back with\n`product_format: null`, because print-on-demand shares the printed edition's number. A\nvalue that is not one of the four formats above is discarded the same way; the record\nkeeps the format implied by the edition it resolves to, which on a newly created\nidentifier is `null`.\n\nChoosing between `null` and a format is a commercial decision, not a technical one. One\nEAN covering all e-book formats is by far the common case in practice; a per-format EAN is\nneeded when a retailer lists the EPUB and the PDF as separate, separately ordered articles.\nA lookup for a format that has no identifier of its own falls back to the identifier that\nhas no format.\n",
            "nullable": true,
            "example": "epub",
            "x-enum-descriptions": {
                "pdf": "PDF edition.",
                "epub": "EPUB edition.",
                "mobi": "Mobipocket / Kindle edition.",
                "ibooks": "Apple Books edition."
            }
        },
        "value": {
            "type": "string",
            "description": "The number itself, bare — no hyphens, no spaces. For a `gtin13` identifier it is exactly 13\ndigits, and book numbers start with the Bookland prefix `978` or `9791`–`9799`, which is\nwhat makes them valid ISBN-13s as well as GTIN-13s.\n\n**Check digit.** The thirteenth digit of a GTIN-13 is a GS1 modulo-10 check digit over the\nfirst twelve: the digits are multiplied alternately by 1 and 3 from the left and summed, and\nthe check digit is whatever brings the total to a multiple of 10. A wrong one is rejected with\n`400 Bad Request`. So is a hyphenated ISBN such as `978-3-16-148410-0` — at 17 characters it\nis not 13 digits long. A 10-digit ISBN is not accepted either, and has to be converted to\nISBN-13 first.\n\nThe hyphenated, human-readable rendition cannot be set through this endpoint. It is\nderived, and exposed read-only as `isbn` in the product resource's `eans` array.\n\n**Uniqueness.** An active number may identify only one product per realm; a second product\nclaiming it is rejected with `400 Bad Request`. The same number may also not be active twice\non one product under different product types — one EAN cannot be both the printed book and\nthe e-book. Deactivating the record that holds it (`active: false`) releases the number for\nanother record.\n",
            "example": "9783638202060"
        },
        "channels": {
            "type": "array",
            "description": "Distribution channels for which this number is the one to use. An **empty list is the\nnormal case**: it makes the identifier the product's general EAN for its product type, used\nby every channel unless a channel-specific identifier exists.\n\nA non-empty list is needed only when a specific partner requires its own article number for\nan edition that already has one — a lending platform, or a retailer that will not share the\ntrade EAN. Such an identifier serves that channel alone, and the general one continues to\nserve everyone else.\n\nValues are the platform's distribution-channel slugs. Those in common use are:\n\n- `amazon`\n- `google`\n- `itunes`\n- `kobo`\n- `spotify`\n- `libri`\n- `ceebo`\n- `divibib`\n- `overdrive`\n- `bookwire`\n\nThe list is open-ended — a realm distributes through whichever partners it has contracts\nwith. Values are lower-cased, sorted and de-duplicated, so `[\"Amazon\", \"itunes\",\n\"AMAZON\"]` comes back as `[\"amazon\", \"itunes\"]`, and on update the list is replaced\nwholesale rather than merged.\n\n**Names are not validated.** A misspelled channel is accepted as given and matches nothing,\nso the identifier is never selected for any channel; there is no error on create or update.\n\nWithin one product and one product type, the same channel may not appear on two active\nidentifiers — `400 Bad Request`. Deactivating an identifier frees its channels, and the same\nchannel may be reused on a different product type.\n",
            "items": {
                "type": "string"
            },
            "example": [
                "amazon",
                "itunes"
            ]
        },
        "active": {
            "type": "boolean",
            "description": "Whether this number is in force. Only active identifiers are resolved when a channel looks\nup an EAN, and only active identifiers are counted by the uniqueness and channel checks.\n\nSetting `active: false` is a **soft delete**: the record stays readable and keeps appearing\nin the list, but it stops identifying the product and releases its number and channels for\nreuse. It is the way to withdraw a number whose history is worth keeping. `DELETE` removes\nthe record outright, and after it a `GET` on the same id returns `404`.\n\nNewly created identifiers are **not** active unless the field says so. A `POST` that omits it\ncreates an inactive record, which reports `active: false` and is not resolved when a channel\nlooks up an EAN. The requirement that `product_type` be set is not applied to an inactive\nrecord either, so a missing `product_type` goes unreported until the record is activated.\n`active: true` therefore belongs in every create that is not deliberately inactive.\n",
            "example": true
        }
    }
}

{
    "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."
                    }
                }
            }
        }
    }
}

PUT /resource/v4/products/{productId}/identifiers/{identifierId}

Update a single identifier

Description

Updates one identifier. Despite being a PUT, this is a partial update: only the fields present in the body are applied, and every omitted field keeps its current value. Sending {} changes nothing, and a field cannot be cleared by leaving it out — an explicit null is required.

Two consequences are worth planning for:

  • Changing value re-points the identifier, it does not add one. The old number stops identifying the product entirely, with no record that it ever did. Retiring a number while keeping its history means creating a new identifier and setting the old one to active: false.
  • Reactivating is validated like a create. Setting active: true on an inactive record re-runs the uniqueness and channel checks against everything currently active, so a reactivation can fail with 400 Bad Request even though the record was accepted when it was first created.

The example withdraws an identifier by deactivating it — the whole body is one field, and the response returns the complete merged record.

Input parameters

Path parameters

Parameter Type Default Nullable Description
identifierId string No UUID of the identifier record, as returned in `_id`.
productId string No UUID of the product.

Request body

{
    "active": false
}
Schema of the request body
{
    "type": "object",
    "required": [
        "type",
        "product_type",
        "product_format",
        "value",
        "channels",
        "_id",
        "active"
    ],
    "additionalProperties": false,
    "description": "One trade identifier of a product, scoped to one product type and optionally to one e-book format and to named distribution channels.",
    "properties": {
        "_id": {
            "type": "string",
            "description": "UUID of this identifier record — not of the product and not of the EAN. It is the id that\ngoes in the path of `GET`, `PUT` and `DELETE`. Unlike most v4 resources this one carries no\n`_legacy_id`, `_version` or `_links`: the v3 and v4 payloads are identical, so an\nintegration migrating from `/resource/v3/` needs no field mapping here.\n",
            "example": "36ff9359-cc26-11f0-8321-00505699575c",
            "nullable": true
        },
        "type": {
            "type": "string",
            "description": "Which identifier scheme this record uses. `gtin13` is the scheme in common use —\n[ONIX Codelist 5](https://ns.editeur.org/onix/en/5) code `03`, GTIN-13 (GS1 Global Trade\nItem Number, formerly the EAN article number). An unrecognised scheme is rejected with\n`400 Bad Request`, and filtering the list by a scheme none of the product's identifiers\nuses returns an empty result rather than an error.\n\nA GTIN-13 beginning with the Bookland prefix `978` or `9791`–`9799` is at the same time a\nvalid ISBN-13, Codelist 5 code `15`. The ISBN-13 and the GTIN-13 are the same thirteen\ndigits, so one `gtin13` record covers both.\n\nSome other identifiers of a product are currently exposed elsewhere rather than here:\n\n- **DOI** — Codelist 5 code `06` — the product's `doi` field.\n- **Proprietary reference**, such as an ASIN or a partner's own catalogue key — Codelist 5\n  code `01` — the product's `external_identifiers` array.\n",
            "example": "gtin13",
            "x-enum-descriptions": {
                "gtin13": "GS1 Global Trade Item Number, 13 digits — ONIX Codelist 5 code 03."
            }
        },
        "product_type": {
            "type": "string",
            "nullable": true,
            "description": "Which physical or digital edition of the product this number identifies. A product carries\none identifier per commercially distinct edition, and this is the first of the two fields\nthat separate them; `product_format` is the second.\n\nRequired in practice: an identifier saved as active without a `product_type` is rejected\nwith `400 Bad Request`.\n\nFour values are meaningful here:\n\n- `book` — printed edition, including print-on-demand.\n- `ebook` — digital edition, in one or in all e-book file formats.\n- `audiobook` — audiobook edition.\n- `nonbook` — non-book article.\n\nInput is lower-cased, so `\"EBOOK\"` comes back as `\"ebook\"` — but the list filter is\ncase-sensitive and matches only the lower-case form.\n\nTogether with `product_format` this resolves to a single edition, and only these\ncombinations survive a round-trip:\n\n| `product_type` | `product_format` | Identifies |\n|---|---|---|\n| `book`      | `null`   | Printed edition, all print channels |\n| `ebook`     | `null`   | Every e-book format of the product |\n| `ebook`     | `pdf`    | PDF edition only |\n| `ebook`     | `epub`   | EPUB edition only |\n| `ebook`     | `mobi`   | Mobipocket / Kindle edition only |\n| `ebook`     | `ibooks` | Apple Books edition only |\n| `audiobook` | `null`   | Audiobook edition |\n| `nonbook`   | `null`   | Non-book article |\n\nAnything outside the table is either silently normalised (see `product_format`) or\nrejected. In particular, a `product_type` that is not one of the four above leaves the\nrecord with no usable edition, and an active identifier in that state is rejected with\n`400 Bad Request`.\n",
            "example": "ebook",
            "x-enum-descriptions": {
                "book": "Printed edition, including print-on-demand. Product format is always null.",
                "ebook": "Digital edition. A null product format gives one EAN covering all e-book formats; a named format gives that format its own EAN.",
                "audiobook": "Audiobook edition. Product format is always null.",
                "nonbook": "Non-book article (merchandise, kits, and similar). Product format is always null."
            }
        },
        "product_format": {
            "type": "string",
            "description": "Narrows an `ebook` identifier to a single file format, so that one e-book format can carry\nits own EAN. `null` means the identifier covers every format of its `product_type`, and is\nthe common case.\n\nOnly meaningful for `product_type: \"ebook\"`, where four formats are recognised:\n\n- `pdf` — PDF edition.\n- `epub` — EPUB edition.\n- `mobi` — Mobipocket / Kindle edition.\n- `ibooks` — Apple Books edition.\n\nFor `book`, `audiobook` and `nonbook` the format is **silently discarded** rather than\nrejected: `product_type: \"book\"` sent together with `product_format: \"pod\"` comes back with\n`product_format: null`, because print-on-demand shares the printed edition's number. A\nvalue that is not one of the four formats above is discarded the same way; the record\nkeeps the format implied by the edition it resolves to, which on a newly created\nidentifier is `null`.\n\nChoosing between `null` and a format is a commercial decision, not a technical one. One\nEAN covering all e-book formats is by far the common case in practice; a per-format EAN is\nneeded when a retailer lists the EPUB and the PDF as separate, separately ordered articles.\nA lookup for a format that has no identifier of its own falls back to the identifier that\nhas no format.\n",
            "nullable": true,
            "example": "epub",
            "x-enum-descriptions": {
                "pdf": "PDF edition.",
                "epub": "EPUB edition.",
                "mobi": "Mobipocket / Kindle edition.",
                "ibooks": "Apple Books edition."
            }
        },
        "value": {
            "type": "string",
            "description": "The number itself, bare — no hyphens, no spaces. For a `gtin13` identifier it is exactly 13\ndigits, and book numbers start with the Bookland prefix `978` or `9791`–`9799`, which is\nwhat makes them valid ISBN-13s as well as GTIN-13s.\n\n**Check digit.** The thirteenth digit of a GTIN-13 is a GS1 modulo-10 check digit over the\nfirst twelve: the digits are multiplied alternately by 1 and 3 from the left and summed, and\nthe check digit is whatever brings the total to a multiple of 10. A wrong one is rejected with\n`400 Bad Request`. So is a hyphenated ISBN such as `978-3-16-148410-0` — at 17 characters it\nis not 13 digits long. A 10-digit ISBN is not accepted either, and has to be converted to\nISBN-13 first.\n\nThe hyphenated, human-readable rendition cannot be set through this endpoint. It is\nderived, and exposed read-only as `isbn` in the product resource's `eans` array.\n\n**Uniqueness.** An active number may identify only one product per realm; a second product\nclaiming it is rejected with `400 Bad Request`. The same number may also not be active twice\non one product under different product types — one EAN cannot be both the printed book and\nthe e-book. Deactivating the record that holds it (`active: false`) releases the number for\nanother record.\n",
            "example": "9783638202060"
        },
        "channels": {
            "type": "array",
            "description": "Distribution channels for which this number is the one to use. An **empty list is the\nnormal case**: it makes the identifier the product's general EAN for its product type, used\nby every channel unless a channel-specific identifier exists.\n\nA non-empty list is needed only when a specific partner requires its own article number for\nan edition that already has one — a lending platform, or a retailer that will not share the\ntrade EAN. Such an identifier serves that channel alone, and the general one continues to\nserve everyone else.\n\nValues are the platform's distribution-channel slugs. Those in common use are:\n\n- `amazon`\n- `google`\n- `itunes`\n- `kobo`\n- `spotify`\n- `libri`\n- `ceebo`\n- `divibib`\n- `overdrive`\n- `bookwire`\n\nThe list is open-ended — a realm distributes through whichever partners it has contracts\nwith. Values are lower-cased, sorted and de-duplicated, so `[\"Amazon\", \"itunes\",\n\"AMAZON\"]` comes back as `[\"amazon\", \"itunes\"]`, and on update the list is replaced\nwholesale rather than merged.\n\n**Names are not validated.** A misspelled channel is accepted as given and matches nothing,\nso the identifier is never selected for any channel; there is no error on create or update.\n\nWithin one product and one product type, the same channel may not appear on two active\nidentifiers — `400 Bad Request`. Deactivating an identifier frees its channels, and the same\nchannel may be reused on a different product type.\n",
            "items": {
                "type": "string"
            },
            "example": [
                "amazon",
                "itunes"
            ]
        },
        "active": {
            "type": "boolean",
            "description": "Whether this number is in force. Only active identifiers are resolved when a channel looks\nup an EAN, and only active identifiers are counted by the uniqueness and channel checks.\n\nSetting `active: false` is a **soft delete**: the record stays readable and keeps appearing\nin the list, but it stops identifying the product and releases its number and channels for\nreuse. It is the way to withdraw a number whose history is worth keeping. `DELETE` removes\nthe record outright, and after it a `GET` on the same id returns `404`.\n\nNewly created identifiers are **not** active unless the field says so. A `POST` that omits it\ncreates an inactive record, which reports `active: false` and is not resolved when a channel\nlooks up an EAN. The requirement that `product_type` be set is not applied to an inactive\nrecord either, so a missing `product_type` goes unreported until the record is activated.\n`active: true` therefore belongs in every create that is not deliberately inactive.\n",
            "example": true
        }
    }
}

Responses

{
    "_id": "36ff9359-cc26-11f0-8321-00505699575c",
    "type": "gtin13",
    "product_type": "ebook",
    "product_format": null,
    "value": "9783638202060",
    "channels": [],
    "active": false
}
Schema of the response body
{
    "type": "object",
    "required": [
        "type",
        "product_type",
        "product_format",
        "value",
        "channels",
        "_id",
        "active"
    ],
    "additionalProperties": false,
    "description": "One trade identifier of a product, scoped to one product type and optionally to one e-book format and to named distribution channels.",
    "properties": {
        "_id": {
            "type": "string",
            "description": "UUID of this identifier record — not of the product and not of the EAN. It is the id that\ngoes in the path of `GET`, `PUT` and `DELETE`. Unlike most v4 resources this one carries no\n`_legacy_id`, `_version` or `_links`: the v3 and v4 payloads are identical, so an\nintegration migrating from `/resource/v3/` needs no field mapping here.\n",
            "example": "36ff9359-cc26-11f0-8321-00505699575c",
            "nullable": true
        },
        "type": {
            "type": "string",
            "description": "Which identifier scheme this record uses. `gtin13` is the scheme in common use —\n[ONIX Codelist 5](https://ns.editeur.org/onix/en/5) code `03`, GTIN-13 (GS1 Global Trade\nItem Number, formerly the EAN article number). An unrecognised scheme is rejected with\n`400 Bad Request`, and filtering the list by a scheme none of the product's identifiers\nuses returns an empty result rather than an error.\n\nA GTIN-13 beginning with the Bookland prefix `978` or `9791`–`9799` is at the same time a\nvalid ISBN-13, Codelist 5 code `15`. The ISBN-13 and the GTIN-13 are the same thirteen\ndigits, so one `gtin13` record covers both.\n\nSome other identifiers of a product are currently exposed elsewhere rather than here:\n\n- **DOI** — Codelist 5 code `06` — the product's `doi` field.\n- **Proprietary reference**, such as an ASIN or a partner's own catalogue key — Codelist 5\n  code `01` — the product's `external_identifiers` array.\n",
            "example": "gtin13",
            "x-enum-descriptions": {
                "gtin13": "GS1 Global Trade Item Number, 13 digits — ONIX Codelist 5 code 03."
            }
        },
        "product_type": {
            "type": "string",
            "nullable": true,
            "description": "Which physical or digital edition of the product this number identifies. A product carries\none identifier per commercially distinct edition, and this is the first of the two fields\nthat separate them; `product_format` is the second.\n\nRequired in practice: an identifier saved as active without a `product_type` is rejected\nwith `400 Bad Request`.\n\nFour values are meaningful here:\n\n- `book` — printed edition, including print-on-demand.\n- `ebook` — digital edition, in one or in all e-book file formats.\n- `audiobook` — audiobook edition.\n- `nonbook` — non-book article.\n\nInput is lower-cased, so `\"EBOOK\"` comes back as `\"ebook\"` — but the list filter is\ncase-sensitive and matches only the lower-case form.\n\nTogether with `product_format` this resolves to a single edition, and only these\ncombinations survive a round-trip:\n\n| `product_type` | `product_format` | Identifies |\n|---|---|---|\n| `book`      | `null`   | Printed edition, all print channels |\n| `ebook`     | `null`   | Every e-book format of the product |\n| `ebook`     | `pdf`    | PDF edition only |\n| `ebook`     | `epub`   | EPUB edition only |\n| `ebook`     | `mobi`   | Mobipocket / Kindle edition only |\n| `ebook`     | `ibooks` | Apple Books edition only |\n| `audiobook` | `null`   | Audiobook edition |\n| `nonbook`   | `null`   | Non-book article |\n\nAnything outside the table is either silently normalised (see `product_format`) or\nrejected. In particular, a `product_type` that is not one of the four above leaves the\nrecord with no usable edition, and an active identifier in that state is rejected with\n`400 Bad Request`.\n",
            "example": "ebook",
            "x-enum-descriptions": {
                "book": "Printed edition, including print-on-demand. Product format is always null.",
                "ebook": "Digital edition. A null product format gives one EAN covering all e-book formats; a named format gives that format its own EAN.",
                "audiobook": "Audiobook edition. Product format is always null.",
                "nonbook": "Non-book article (merchandise, kits, and similar). Product format is always null."
            }
        },
        "product_format": {
            "type": "string",
            "description": "Narrows an `ebook` identifier to a single file format, so that one e-book format can carry\nits own EAN. `null` means the identifier covers every format of its `product_type`, and is\nthe common case.\n\nOnly meaningful for `product_type: \"ebook\"`, where four formats are recognised:\n\n- `pdf` — PDF edition.\n- `epub` — EPUB edition.\n- `mobi` — Mobipocket / Kindle edition.\n- `ibooks` — Apple Books edition.\n\nFor `book`, `audiobook` and `nonbook` the format is **silently discarded** rather than\nrejected: `product_type: \"book\"` sent together with `product_format: \"pod\"` comes back with\n`product_format: null`, because print-on-demand shares the printed edition's number. A\nvalue that is not one of the four formats above is discarded the same way; the record\nkeeps the format implied by the edition it resolves to, which on a newly created\nidentifier is `null`.\n\nChoosing between `null` and a format is a commercial decision, not a technical one. One\nEAN covering all e-book formats is by far the common case in practice; a per-format EAN is\nneeded when a retailer lists the EPUB and the PDF as separate, separately ordered articles.\nA lookup for a format that has no identifier of its own falls back to the identifier that\nhas no format.\n",
            "nullable": true,
            "example": "epub",
            "x-enum-descriptions": {
                "pdf": "PDF edition.",
                "epub": "EPUB edition.",
                "mobi": "Mobipocket / Kindle edition.",
                "ibooks": "Apple Books edition."
            }
        },
        "value": {
            "type": "string",
            "description": "The number itself, bare — no hyphens, no spaces. For a `gtin13` identifier it is exactly 13\ndigits, and book numbers start with the Bookland prefix `978` or `9791`–`9799`, which is\nwhat makes them valid ISBN-13s as well as GTIN-13s.\n\n**Check digit.** The thirteenth digit of a GTIN-13 is a GS1 modulo-10 check digit over the\nfirst twelve: the digits are multiplied alternately by 1 and 3 from the left and summed, and\nthe check digit is whatever brings the total to a multiple of 10. A wrong one is rejected with\n`400 Bad Request`. So is a hyphenated ISBN such as `978-3-16-148410-0` — at 17 characters it\nis not 13 digits long. A 10-digit ISBN is not accepted either, and has to be converted to\nISBN-13 first.\n\nThe hyphenated, human-readable rendition cannot be set through this endpoint. It is\nderived, and exposed read-only as `isbn` in the product resource's `eans` array.\n\n**Uniqueness.** An active number may identify only one product per realm; a second product\nclaiming it is rejected with `400 Bad Request`. The same number may also not be active twice\non one product under different product types — one EAN cannot be both the printed book and\nthe e-book. Deactivating the record that holds it (`active: false`) releases the number for\nanother record.\n",
            "example": "9783638202060"
        },
        "channels": {
            "type": "array",
            "description": "Distribution channels for which this number is the one to use. An **empty list is the\nnormal case**: it makes the identifier the product's general EAN for its product type, used\nby every channel unless a channel-specific identifier exists.\n\nA non-empty list is needed only when a specific partner requires its own article number for\nan edition that already has one — a lending platform, or a retailer that will not share the\ntrade EAN. Such an identifier serves that channel alone, and the general one continues to\nserve everyone else.\n\nValues are the platform's distribution-channel slugs. Those in common use are:\n\n- `amazon`\n- `google`\n- `itunes`\n- `kobo`\n- `spotify`\n- `libri`\n- `ceebo`\n- `divibib`\n- `overdrive`\n- `bookwire`\n\nThe list is open-ended — a realm distributes through whichever partners it has contracts\nwith. Values are lower-cased, sorted and de-duplicated, so `[\"Amazon\", \"itunes\",\n\"AMAZON\"]` comes back as `[\"amazon\", \"itunes\"]`, and on update the list is replaced\nwholesale rather than merged.\n\n**Names are not validated.** A misspelled channel is accepted as given and matches nothing,\nso the identifier is never selected for any channel; there is no error on create or update.\n\nWithin one product and one product type, the same channel may not appear on two active\nidentifiers — `400 Bad Request`. Deactivating an identifier frees its channels, and the same\nchannel may be reused on a different product type.\n",
            "items": {
                "type": "string"
            },
            "example": [
                "amazon",
                "itunes"
            ]
        },
        "active": {
            "type": "boolean",
            "description": "Whether this number is in force. Only active identifiers are resolved when a channel looks\nup an EAN, and only active identifiers are counted by the uniqueness and channel checks.\n\nSetting `active: false` is a **soft delete**: the record stays readable and keeps appearing\nin the list, but it stops identifying the product and releases its number and channels for\nreuse. It is the way to withdraw a number whose history is worth keeping. `DELETE` removes\nthe record outright, and after it a `GET` on the same id returns `404`.\n\nNewly created identifiers are **not** active unless the field says so. A `POST` that omits it\ncreates an inactive record, which reports `active: false` and is not resolved when a channel\nlooks up an EAN. The requirement that `product_type` be set is not applied to an inactive\nrecord either, so a missing `product_type` goes unreported until the record is activated.\n`active: true` therefore belongs in every create that is not deliberately inactive.\n",
            "example": true
        }
    }
}

{
    "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/{productId}/identifiers/{identifierId}

Delete an identifier

Description

Removes the identifier record permanently. A subsequent GET on the same id returns 404.

PUT {"active": false} is the better choice for a number that has ever been distributed. Deactivating frees the number and its channels for reuse exactly as deleting does, but keeps the record visible in the list so that partners holding the old number can still be reconciled.

Input parameters

Path parameters

Parameter Type Default Nullable Description
identifierId string No UUID of the identifier record, as returned in `_id`.
productId string No UUID 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."
                    }
                }
            }
        }
    }
}

Schemas

Product Identifier

Name Type Description Example
_id string| null

UUID of this identifier record — not of the product and not of the EAN. It is the id that goes in the path of GET, PUT and DELETE. Unlike most v4 resources this one carries no _legacy_id, _version or _links: the v3 and v4 payloads are identical, so an integration migrating from /resource/v3/ needs no field mapping here.

36ff9359-cc26-11f0-8321-00505699575c
active boolean

Whether this number is in force. Only active identifiers are resolved when a channel looks up an EAN, and only active identifiers are counted by the uniqueness and channel checks.

Setting active: false is a soft delete: the record stays readable and keeps appearing in the list, but it stops identifying the product and releases its number and channels for reuse. It is the way to withdraw a number whose history is worth keeping. DELETE removes the record outright, and after it a GET on the same id returns 404.

Newly created identifiers are not active unless the field says so. A POST that omits it creates an inactive record, which reports active: false and is not resolved when a channel looks up an EAN. The requirement that product_type be set is not applied to an inactive record either, so a missing product_type goes unreported until the record is activated. active: true therefore belongs in every create that is not deliberately inactive.

true
channels Array<string>

Distribution channels for which this number is the one to use. An empty list is the normal case: it makes the identifier the product's general EAN for its product type, used by every channel unless a channel-specific identifier exists.

A non-empty list is needed only when a specific partner requires its own article number for an edition that already has one — a lending platform, or a retailer that will not share the trade EAN. Such an identifier serves that channel alone, and the general one continues to serve everyone else.

Values are the platform's distribution-channel slugs. Those in common use are:

  • amazon
  • google
  • itunes
  • kobo
  • spotify
  • libri
  • ceebo
  • divibib
  • overdrive
  • bookwire

The list is open-ended — a realm distributes through whichever partners it has contracts with. Values are lower-cased, sorted and de-duplicated, so ["Amazon", "itunes", "AMAZON"] comes back as ["amazon", "itunes"], and on update the list is replaced wholesale rather than merged.

Names are not validated. A misspelled channel is accepted as given and matches nothing, so the identifier is never selected for any channel; there is no error on create or update.

Within one product and one product type, the same channel may not appear on two active identifiers — 400 Bad Request. Deactivating an identifier frees its channels, and the same channel may be reused on a different product type.

[ "amazon", "itunes" ]
product_format string| null

Narrows an ebook identifier to a single file format, so that one e-book format can carry its own EAN. null means the identifier covers every format of its product_type, and is the common case.

Only meaningful for product_type: "ebook", where four formats are recognised:

  • pdf — PDF edition.
  • epub — EPUB edition.
  • mobi — Mobipocket / Kindle edition.
  • ibooks — Apple Books edition.

For book, audiobook and nonbook the format is silently discarded rather than rejected: product_type: "book" sent together with product_format: "pod" comes back with product_format: null, because print-on-demand shares the printed edition's number. A value that is not one of the four formats above is discarded the same way; the record keeps the format implied by the edition it resolves to, which on a newly created identifier is null.

Choosing between null and a format is a commercial decision, not a technical one. One EAN covering all e-book formats is by far the common case in practice; a per-format EAN is needed when a retailer lists the EPUB and the PDF as separate, separately ordered articles. A lookup for a format that has no identifier of its own falls back to the identifier that has no format.

epub
product_type string| null

Which physical or digital edition of the product this number identifies. A product carries one identifier per commercially distinct edition, and this is the first of the two fields that separate them; product_format is the second.

Required in practice: an identifier saved as active without a product_type is rejected with 400 Bad Request.

Four values are meaningful here:

  • book — printed edition, including print-on-demand.
  • ebook — digital edition, in one or in all e-book file formats.
  • audiobook — audiobook edition.
  • nonbook — non-book article.

Input is lower-cased, so "EBOOK" comes back as "ebook" — but the list filter is case-sensitive and matches only the lower-case form.

Together with product_format this resolves to a single edition, and only these combinations survive a round-trip:

product_type product_format Identifies
book null Printed edition, all print channels
ebook null Every e-book format of the product
ebook pdf PDF edition only
ebook epub EPUB edition only
ebook mobi Mobipocket / Kindle edition only
ebook ibooks Apple Books edition only
audiobook null Audiobook edition
nonbook null Non-book article

Anything outside the table is either silently normalised (see product_format) or rejected. In particular, a product_type that is not one of the four above leaves the record with no usable edition, and an active identifier in that state is rejected with 400 Bad Request.

ebook
type string

Which identifier scheme this record uses. gtin13 is the scheme in common use — ONIX Codelist 5 code 03, GTIN-13 (GS1 Global Trade Item Number, formerly the EAN article number). An unrecognised scheme is rejected with 400 Bad Request, and filtering the list by a scheme none of the product's identifiers uses returns an empty result rather than an error.

A GTIN-13 beginning with the Bookland prefix 978 or 97919799 is at the same time a valid ISBN-13, Codelist 5 code 15. The ISBN-13 and the GTIN-13 are the same thirteen digits, so one gtin13 record covers both.

Some other identifiers of a product are currently exposed elsewhere rather than here:

  • DOI — Codelist 5 code 06 — the product's doi field.
  • Proprietary reference, such as an ASIN or a partner's own catalogue key — Codelist 5 code 01 — the product's external_identifiers array.
gtin13
value string

The number itself, bare — no hyphens, no spaces. For a gtin13 identifier it is exactly 13 digits, and book numbers start with the Bookland prefix 978 or 97919799, which is what makes them valid ISBN-13s as well as GTIN-13s.

Check digit. The thirteenth digit of a GTIN-13 is a GS1 modulo-10 check digit over the first twelve: the digits are multiplied alternately by 1 and 3 from the left and summed, and the check digit is whatever brings the total to a multiple of 10. A wrong one is rejected with 400 Bad Request. So is a hyphenated ISBN such as 978-3-16-148410-0 — at 17 characters it is not 13 digits long. A 10-digit ISBN is not accepted either, and has to be converted to ISBN-13 first.

The hyphenated, human-readable rendition cannot be set through this endpoint. It is derived, and exposed read-only as isbn in the product resource's eans array.

Uniqueness. An active number may identify only one product per realm; a second product claiming it is rejected with 400 Bad Request. The same number may also not be active twice on one product under different product types — one EAN cannot be both the printed book and the e-book. Deactivating the record that holds it (active: false) releases the number for another record.

9783638202060