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": {}
}
]
}
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": {}
}
]
}
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": {}
}
]
}
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
valuere-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 toactive: false. - Reactivating is validated like a create. Setting
active: trueon an inactive record re-runs the uniqueness and channel checks against everything currently active, so a reactivation can fail with400 Bad Requesteven 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": {}
}
]
}
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": {}
}
]
}
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 |
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 Newly created identifiers are not active unless the field says so. A |
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:
The list is open-ended — a realm distributes through whichever partners it has contracts
with. Values are lower-cased, sorted and de-duplicated, so 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 — |
[
"amazon",
"itunes"
] |
|||||||||||||||||||||||||||
product_format |
string| null |
Narrows an Only meaningful for
For Choosing between |
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; Required in practice: an identifier saved as active without a Four values are meaningful here:
Input is lower-cased, so Together with
Anything outside the table is either silently normalised (see |
ebook |
|||||||||||||||||||||||||||
type |
string |
Which identifier scheme this record uses. A GTIN-13 beginning with the Bookland prefix Some other identifiers of a product are currently exposed elsewhere rather than here:
|
gtin13 |
|||||||||||||||||||||||||||
value |
string |
The number itself, bare — no hyphens, no spaces. For a 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
The hyphenated, human-readable rendition cannot be set through this endpoint. It is
derived, and exposed read-only as Uniqueness. An active number may identify only one product per realm; a second product
claiming it is rejected with |
9783638202060 |