Skip to content

Product Prices v4

Sub-resource of products holding the price entries of a title. Each entry is one amount in one currency, scoped to a single product format, a territory, an optional set of distribution channels and an optional validity period. A product normally carries several entries, covering different countries, currencies and pricing models.

qualifier_code, a value from ONIX Codelist 59, separates the three kinds of entry this sub-resource accepts: a regular consumer price (no qualifier), a library or institutional price ("06"), and a time-limited campaign price ("08").


Endpoints


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

Retrieve prices for a product

Description

Returns all price entries for the given product, paginated. Query parameters narrow the result — ?qualifier_code=06 for library prices, ?qualifier_code=08 for campaign prices, ?channels__contains=divibib for the prices of one channel.

Seven fields are filterable: product_type, qualifier_code, price_code, channels, the currency (as either currency or currency_code), country_codes_included and region_codes_included. The last two accept equality and __contains only. Any other query parameter is rejected with 400 Bad Request; product_id is part of the path, not a filter. See Resource Queries for the operator and sort syntax.

Regular consumer prices carry qualifier_code: null, so they are selected with ?qualifier_code__isnull rather than by a code value, and ?qualifier_code__ne=08 does not return them.

Input parameters

Path parameters

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

Query parameters

Parameter Type Default Nullable Description
channels__contains string No Filter to prices whose `channels` list contains the given value (e.g. `divibib`, `overdrive`).
country_codes_included string No Exact match on included country codes. Takes a comma-separated list of ISO 3166-1 alpha-2 codes (e.g. `DE,AT`). Order does not matter, because the codes are sorted before they are compared.
country_codes_included__contains string No Substring match within the included country codes list (e.g. `D` matches both `DE` and `DK`).
currency string No Filter by ISO 4217 currency code (e.g. `EUR`, `USD`, `CHF`).
qualifier_code string No Filter by ONIX price type qualifier ([Codelist 59](https://ns.editeur.org/onix/en/59)). `06` selects library / institutional prices and `08` campaign prices. Regular consumer prices carry no qualifier at all and are selected with `?qualifier_code__isnull` instead of a code value.
region_codes_included__contains string No Substring match within the included region codes list. `WORLD` is the only region code the platform's territory handling supports, so in practice it is the only value worth matching.

Responses

{
    "_links": {},
    "_pagination": {},
    "items": [
        {
            "_id": "949b0070-d590-4154-bbb9-961051f3f36a",
            "_version": "4.0",
            "_legacy_id": 38054816,
            "_links": {},
            "description": "geb. Ladenpreis",
            "product_type": "ebook",
            "qualifier_code": null,
            "price_code": null,
            "price_type": "fixed",
            "price_status_code": "02",
            "vat_type": "auto",
            "gross_price": true,
            "country_codes_included": [
                "DE",
                "AT"
            ],
            "country_codes_excluded": [],
            "region_codes_included": [],
            "region_codes_excluded": [],
            "effective_from": "2026-08-06",
            "effective_until": "2026-09-06",
            "channels": [],
            "price": {
                "value": 42.23,
                "currency": "EUR"
            }
        }
    ]
}
⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information.

Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "_links": {
            "type": "object"
        },
        "_pagination": {
            "type": "object"
        },
        "items": {
            "type": "array",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "description": "One price entry of a product, scoped to a format, a territory, a channel set and a period.",
                "properties": {
                    "_id": {
                        "type": "string",
                        "description": "Unique identifier for the price.",
                        "example": "949b0070-d590-4154-bbb9-961051f3f36a"
                    },
                    "_version": {
                        "type": "string",
                        "description": "API Version.",
                        "example": "4.0"
                    },
                    "_legacy_id": {
                        "type": "integer",
                        "description": "Legacy integer identifier of this resource, kept so existing integrations can correlate their own records.",
                        "example": 38054816
                    },
                    "_links": {
                        "type": "object"
                    },
                    "description": {
                        "type": "string",
                        "description": "Human-readable label for the price entry, typically describing its purpose or origin.\nExamples from the book trade: `\"geb. Ladenpreis\"` (fixed retail price, gross),\n`\"Netto-Ladenpreis\"` (net retail price), `\"K-Lizenz\"` (quota license for libraries).\n\nFor campaign prices (`qualifier_code: \"08\"`) this holds the promotion's name —\n`\"Summer campaign\"`, `\"Christmas\"` — since it is the only field that distinguishes one\ncampaign of a product from another.\n",
                        "nullable": true,
                        "example": "geb. Ladenpreis"
                    },
                    "product_type": {
                        "type": "string",
                        "description": "The product format this price applies to. Each format is priced independently, so a\nsingle product may carry separate ebook and book prices. One of four values:\n\n- `ebook` — e-book in any file format (EPUB, PDF, …)\n- `book` — physical book, including print-on-demand\n- `audiobook` — audiobook, downloadable or streaming\n- `nonbook` — non-book article\n\nAlways present in a response; a price cannot exist without a format. Values are\nlower-cased on write, so `\"Ebook\"` is accepted and comes back as `ebook`.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
                        "example": "ebook",
                        "x-enum-descriptions": {
                            "ebook": "E-book in any file format (EPUB, PDF, …)",
                            "book": "Physical book, including print-on-demand",
                            "audiobook": "Audiobook, downloadable or streaming",
                            "nonbook": "Non-book article"
                        }
                    },
                    "qualifier_code": {
                        "type": "string",
                        "description": "ONIX price type qualifier from [Codelist 59](https://ns.editeur.org/onix/en/59),\nidentifying the audience or purpose of this price. `null` is an unqualified consumer\nprice, equivalent to Codelist 59 code `\"00\"`. Codelist 59 defines twenty codes, but\nonly three qualifier modes are accepted; any other value is rejected with `400 Bad\nRequest`:\n\n- `null` — regular consumer price (retail).\n- `\"06\"` — corporate / library / education price, for sale to libraries, academic\n  institutions or other corporate customers. The most common non-null qualifier,\n  normally combined with a `price_code` and a `channels` entry.\n- `\"08\"` — promotional offer price: a time-limited campaign price bounded by\n  `effective_from` and `effective_until`, and named in `description`.\n\nA campaign does not shorten, modify or replace the regular price entry. The regular\nprice simply stops being the effective one for the duration of the campaign and\napplies again the day after `effective_until`, without being re-created or amended.\nUnlike regular and library prices, campaigns need not tile the timeline without gaps,\nso a product may carry several unrelated campaign windows with ordinary pricing\nbetween them.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax. Because regular prices\ncarry `null`, they are selected with `?qualifier_code__isnull` and are not returned by\n`?qualifier_code__ne=08`.\n",
                        "nullable": true,
                        "example": null,
                        "x-enum-descriptions": {
                            "06": "Corporate / Library / Education price — for sale to libraries, academic institutions, or other corporate customers (ONIX Codelist 59).",
                            "08": "Promotional offer price — a time-limited campaign price that overrides the regular price for the period between `effective_from` and `effective_until` (ONIX Codelist 59)."
                        }
                    },
                    "price_code": {
                        "type": "string",
                        "description": "A lowercase identifier for the pricing or licensing model, used primarily with library\nprices (`qualifier_code: \"06\"`). Lower-cased on write, and restricted to lowercase\nletters and hyphens (`[a-z-]`); anything else is rejected with `400 Bad Request`.\nCommon values in the book trade:\n\n- `pay-per-loan` — the library pays a fee each time the title is lent to a patron.\n- `quota-license` — the library purchases a fixed number of loans, or a time-limited\n  licence for a set of concurrent users.\n\nRegular consumer prices typically have `price_code: null`.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
                        "nullable": true,
                        "example": null
                    },
                    "price_type": {
                        "type": "string",
                        "description": "Whether this is a recommended retail price or a fixed (legally binding) retail price.\nOne of two values, or `null` when the distinction is not applicable. Anything else is\nrejected with `400 Bad Request`. Both correspond to a pair of codes in\n[ONIX Codelist 58](https://ns.editeur.org/onix/en/58), one for a tax-exclusive and one\nfor a tax-inclusive amount, which is the distinction `gross_price` records:\n\n- `recommended` — Recommended Retail Price (Codelist 58 `01` excluding tax, `02`\n  including tax). A retailer may sell at a different price.\n- `fixed` — Fixed Retail Price (Codelist 58 `03` excluding tax, `04` including tax).\n  The price is legally binding, for instance under German and Austrian fixed book\n  price law (*Buchpreisbindung*).\n",
                        "nullable": true,
                        "example": "fixed"
                    },
                    "price_status_code": {
                        "type": "string",
                        "description": "ONIX price status code from [Codelist 61](https://ns.editeur.org/onix/en/61), stating\nhow settled the price is. Any Codelist 61 code is accepted; anything else is rejected\nwith `400 Bad Request`. The codelist has three entries:\n\n- `\"00\"` — Unspecified. The codelist's own default.\n- `\"01\"` — Provisional: the price is not yet settled.\n- `\"02\"` — Confirmed: the price is settled and in effect. The common value.\n\n`null` when no status is recorded.\n",
                        "nullable": true,
                        "example": "02"
                    },
                    "vat_type": {
                        "type": "string",
                        "description": "How VAT (Value Added Tax) is determined for this price. One of four values; anything\nelse is rejected with `400 Bad Request`:\n\n- `auto` — VAT is calculated from the destination country, the product format\n  (e-books and print books often carry different rates) and the date. The default,\n  and the usual setting.\n- `standard` — the standard VAT rate is applied regardless of product format.\n- `reduced` — the reduced VAT rate is applied.\n- `zero` — no VAT is applied.\n\nAlways present in a response; `auto` applies when the request body omits the field.\n",
                        "example": "auto",
                        "x-enum-descriptions": {
                            "auto": "VAT calculated automatically from country, product format, and date (default).",
                            "standard": "Standard VAT rate applied.",
                            "reduced": "Reduced VAT rate applied.",
                            "zero": "No VAT."
                        }
                    },
                    "gross_price": {
                        "type": "boolean",
                        "description": "`true` if `price.value` is a gross price (tax included), `false` if it is a net price\n(tax excluded). Gross prices are typical for consumer-facing retail in countries with\nfixed book price laws; net prices are common for international or institutional sales,\nwhere the buyer settles tax itself. Together with `price_type` this decides which of\nthe two ONIX Codelist 58 codes the entry corresponds to.\n"
                    },
                    "country_codes_included": {
                        "type": "array",
                        "description": "ISO 3166-1 alpha-2 country codes where this price applies (e.g. `\"DE\"`, `\"AT\"`,\n`\"CH\"`). Each code is checked against the countries the platform recognises, so an\nunrecognised code is rejected with `400 Bad Request`. Values are uppercased,\nde-duplicated and sorted alphabetically on write, so the order in a response need not\nmatch the order sent, and on update the list is replaced wholesale rather than merged.\n\nAlways present in a response, but frequently `[]`, which is meaningful: an empty list\nmeans the price is not country-specific, not that its territory is unknown —\n`region_codes_included` carries the scope instead, typically as `[\"WORLD\"]`. When a\nrequest leaves both include lists empty, `region_codes_included` comes back as\n`[\"WORLD\"]`.\n\nFilterable on the collection endpoint, with equality and `__contains` only; see\n[Resource Queries](/resource_queries/).\n",
                        "items": {
                            "type": "string"
                        },
                        "example": [
                            "DE",
                            "AT"
                        ]
                    },
                    "country_codes_excluded": {
                        "type": "array",
                        "description": "ISO 3166-1 alpha-2 country codes explicitly excluded from this price, validated and\nnormalised exactly as `country_codes_included` is. It carves countries out of the\nincluded set, which is what defines a broad price with country-level exceptions:\n`region_codes_included: [\"WORLD\"]` together with `country_codes_excluded: [\"DE\"]`\nmeans every country except Germany.\n\nAlways present in a response; `[]` means nothing is carved out. Not filterable.\n",
                        "items": {
                            "type": "string"
                        },
                        "example": []
                    },
                    "region_codes_included": {
                        "type": "array",
                        "description": "Region codes where this price applies, validated against ONIX\n[Codelist 49](https://ns.editeur.org/onix/en/49); a code outside that codelist is\nrejected with `400 Bad Request`. In practice `WORLD` is the only usable value, because\nthe platform's territory handling rejects every other Codelist 49 region. `ROW` is\naccepted as a synonym and comes back as `WORLD`.\n\n`[\"WORLD\"]` makes the price the fallback for every country that has no more specific\ncountry-level price, minus anything listed in `country_codes_excluded`. An empty list\nmeans the price is already scoped by `country_codes_included`; when both include lists\nare empty, `[\"WORLD\"]` comes back.\n\nValues are uppercased, de-duplicated and sorted on write. Always present in a response.\nFilterable on the collection endpoint, with equality and `__contains` only; see\n[Resource Queries](/resource_queries/).\n",
                        "items": {
                            "type": "string"
                        },
                        "example": []
                    },
                    "region_codes_excluded": {
                        "type": "array",
                        "description": "Region codes explicitly excluded from this price. Always `[]` in practice: the\nplatform's territory handling does not support a non-empty value, so exclusions are\nexpressed through `country_codes_excluded` instead. Values are uppercased,\nde-duplicated and sorted on write. Always present in a response. Not filterable.\n",
                        "items": {
                            "type": "string"
                        },
                        "example": []
                    },
                    "effective_from": {
                        "type": "string",
                        "description": "The date from which this price becomes effective, in `YYYY-MM-DD` format. `null` means\nthe price has no start date and is effective from the beginning of time.\n\nPrices are grouped by product format, `qualifier_code`, territory and channel, and\nwithin one group the effective periods are expected to form a consistent timeline: no\ntwo periods overlap, and only the earliest price of the group leaves this field open.\nRegular and library prices should additionally form a *gapless* chain, each period\nstarting the day after the previous one ends, so that no date is left without a price.\nCampaign prices (`qualifier_code: \"08\"`) are the exception: each is an isolated window\nwhose first day is set here, and gaps between campaigns are normal.\n\nThis endpoint validates each price entry on its own and does not compare it with the\nother prices of the product, so an overlapping or gapped timeline is accepted on\nwrite.\n",
                        "nullable": true,
                        "example": "2026-08-06"
                    },
                    "effective_until": {
                        "type": "string",
                        "description": "The date until which this price remains effective, in `YYYY-MM-DD` format. The day\nitself is included. `null` means the price has no end date and remains effective\nindefinitely; only the latest price of a group leaves this field open.\n\nFor a campaign price (`qualifier_code: \"08\"`) this is the last day of the promotion.\nThe regular price applies again on the following day, without the regular entry being\nre-created or amended.\n",
                        "nullable": true,
                        "example": "2026-09-06"
                    },
                    "channels": {
                        "type": "array",
                        "description": "Distribution channel identifiers this price is scoped to. An empty list — the normal\ncase — means the price applies to every channel. Only library prices\n(`qualifier_code: \"06\"`) use it in practice, naming a lending platform; regular\nconsumer prices carry an empty list.\n\nValues are the platform's distribution-channel slugs, for example:\n\n- `divibib`\n- `overdrive`\n- `ciando`\n- `skoobe`\n- `libri`\n- `google`\n\nThey are not validated against that list — any string of lowercase letters and hyphens\nis accepted, so a typo silently produces a price no channel matches. Slugs containing\ndigits or underscores (`24symbols`, `manual_sale`, `zeitfracht_sa02e`) are valid\nchannels elsewhere in the API but are rejected here with `400 Bad Request`, because\nthis field allows only `[a-z-]`.\n\nThe list is lower-cased, de-duplicated and sorted alphabetically on write, so the order\nin a response need not match the order sent. On update it is replaced wholesale, never\nmerged. Always present in a response.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
                        "items": {
                            "type": "string"
                        },
                        "example": []
                    },
                    "price": {
                        "type": "object",
                        "description": "Monetary Amount",
                        "additionalProperties": false,
                        "nullable": true,
                        "properties": {
                            "value": {
                                "type": "number",
                                "description": "Value of the monetary amount",
                                "example": 42.23
                            },
                            "currency": {
                                "type": "string",
                                "description": "ISO 4217 currency code",
                                "example": "EUR"
                            }
                        },
                        "required": [
                            "value",
                            "currency"
                        ]
                    }
                },
                "required": []
            }
        }
    },
    "required": [
        "_links",
        "_pagination",
        "items"
    ]
}

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

Create a new product price

Description

Creates one price entry for the product. Fields omitted from the body take their default value.

The example below creates a library price: a divibib quota licence, priced net (gross_price: false) and scoped to the divibib channel.

Input parameters

Path parameters

Parameter Type Default Nullable Description
product_id string No

Request body

{
    "description": "K-Lizenz",
    "product_type": "ebook",
    "qualifier_code": "06",
    "price_code": "quota-license",
    "price_type": null,
    "price_status_code": null,
    "vat_type": "auto",
    "gross_price": false,
    "country_codes_included": [],
    "country_codes_excluded": [],
    "region_codes_included": [
        "WORLD"
    ],
    "region_codes_excluded": [],
    "channels": [
        "divibib"
    ],
    "effective_from": "2024-03-12",
    "effective_until": null,
    "price": {
        "value": 4.19,
        "currency": "EUR"
    }
}
Schema of the request body
{
    "type": "object",
    "additionalProperties": false,
    "description": "One price entry of a product, scoped to a format, a territory, a channel set and a period.",
    "properties": {
        "_id": {
            "type": "string",
            "description": "Unique identifier for the price.",
            "example": "949b0070-d590-4154-bbb9-961051f3f36a"
        },
        "_version": {
            "type": "string",
            "description": "API Version.",
            "example": "4.0"
        },
        "_legacy_id": {
            "type": "integer",
            "description": "Legacy integer identifier of this resource, kept so existing integrations can correlate their own records.",
            "example": 38054816
        },
        "_links": {
            "type": "object"
        },
        "description": {
            "type": "string",
            "description": "Human-readable label for the price entry, typically describing its purpose or origin.\nExamples from the book trade: `\"geb. Ladenpreis\"` (fixed retail price, gross),\n`\"Netto-Ladenpreis\"` (net retail price), `\"K-Lizenz\"` (quota license for libraries).\n\nFor campaign prices (`qualifier_code: \"08\"`) this holds the promotion's name —\n`\"Summer campaign\"`, `\"Christmas\"` — since it is the only field that distinguishes one\ncampaign of a product from another.\n",
            "nullable": true,
            "example": "geb. Ladenpreis"
        },
        "product_type": {
            "type": "string",
            "description": "The product format this price applies to. Each format is priced independently, so a\nsingle product may carry separate ebook and book prices. One of four values:\n\n- `ebook` — e-book in any file format (EPUB, PDF, …)\n- `book` — physical book, including print-on-demand\n- `audiobook` — audiobook, downloadable or streaming\n- `nonbook` — non-book article\n\nAlways present in a response; a price cannot exist without a format. Values are\nlower-cased on write, so `\"Ebook\"` is accepted and comes back as `ebook`.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "example": "ebook",
            "x-enum-descriptions": {
                "ebook": "E-book in any file format (EPUB, PDF, …)",
                "book": "Physical book, including print-on-demand",
                "audiobook": "Audiobook, downloadable or streaming",
                "nonbook": "Non-book article"
            }
        },
        "qualifier_code": {
            "type": "string",
            "description": "ONIX price type qualifier from [Codelist 59](https://ns.editeur.org/onix/en/59),\nidentifying the audience or purpose of this price. `null` is an unqualified consumer\nprice, equivalent to Codelist 59 code `\"00\"`. Codelist 59 defines twenty codes, but\nonly three qualifier modes are accepted; any other value is rejected with `400 Bad\nRequest`:\n\n- `null` — regular consumer price (retail).\n- `\"06\"` — corporate / library / education price, for sale to libraries, academic\n  institutions or other corporate customers. The most common non-null qualifier,\n  normally combined with a `price_code` and a `channels` entry.\n- `\"08\"` — promotional offer price: a time-limited campaign price bounded by\n  `effective_from` and `effective_until`, and named in `description`.\n\nA campaign does not shorten, modify or replace the regular price entry. The regular\nprice simply stops being the effective one for the duration of the campaign and\napplies again the day after `effective_until`, without being re-created or amended.\nUnlike regular and library prices, campaigns need not tile the timeline without gaps,\nso a product may carry several unrelated campaign windows with ordinary pricing\nbetween them.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax. Because regular prices\ncarry `null`, they are selected with `?qualifier_code__isnull` and are not returned by\n`?qualifier_code__ne=08`.\n",
            "nullable": true,
            "example": null,
            "x-enum-descriptions": {
                "06": "Corporate / Library / Education price — for sale to libraries, academic institutions, or other corporate customers (ONIX Codelist 59).",
                "08": "Promotional offer price — a time-limited campaign price that overrides the regular price for the period between `effective_from` and `effective_until` (ONIX Codelist 59)."
            }
        },
        "price_code": {
            "type": "string",
            "description": "A lowercase identifier for the pricing or licensing model, used primarily with library\nprices (`qualifier_code: \"06\"`). Lower-cased on write, and restricted to lowercase\nletters and hyphens (`[a-z-]`); anything else is rejected with `400 Bad Request`.\nCommon values in the book trade:\n\n- `pay-per-loan` — the library pays a fee each time the title is lent to a patron.\n- `quota-license` — the library purchases a fixed number of loans, or a time-limited\n  licence for a set of concurrent users.\n\nRegular consumer prices typically have `price_code: null`.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "nullable": true,
            "example": null
        },
        "price_type": {
            "type": "string",
            "description": "Whether this is a recommended retail price or a fixed (legally binding) retail price.\nOne of two values, or `null` when the distinction is not applicable. Anything else is\nrejected with `400 Bad Request`. Both correspond to a pair of codes in\n[ONIX Codelist 58](https://ns.editeur.org/onix/en/58), one for a tax-exclusive and one\nfor a tax-inclusive amount, which is the distinction `gross_price` records:\n\n- `recommended` — Recommended Retail Price (Codelist 58 `01` excluding tax, `02`\n  including tax). A retailer may sell at a different price.\n- `fixed` — Fixed Retail Price (Codelist 58 `03` excluding tax, `04` including tax).\n  The price is legally binding, for instance under German and Austrian fixed book\n  price law (*Buchpreisbindung*).\n",
            "nullable": true,
            "example": "fixed"
        },
        "price_status_code": {
            "type": "string",
            "description": "ONIX price status code from [Codelist 61](https://ns.editeur.org/onix/en/61), stating\nhow settled the price is. Any Codelist 61 code is accepted; anything else is rejected\nwith `400 Bad Request`. The codelist has three entries:\n\n- `\"00\"` — Unspecified. The codelist's own default.\n- `\"01\"` — Provisional: the price is not yet settled.\n- `\"02\"` — Confirmed: the price is settled and in effect. The common value.\n\n`null` when no status is recorded.\n",
            "nullable": true,
            "example": "02"
        },
        "vat_type": {
            "type": "string",
            "description": "How VAT (Value Added Tax) is determined for this price. One of four values; anything\nelse is rejected with `400 Bad Request`:\n\n- `auto` — VAT is calculated from the destination country, the product format\n  (e-books and print books often carry different rates) and the date. The default,\n  and the usual setting.\n- `standard` — the standard VAT rate is applied regardless of product format.\n- `reduced` — the reduced VAT rate is applied.\n- `zero` — no VAT is applied.\n\nAlways present in a response; `auto` applies when the request body omits the field.\n",
            "example": "auto",
            "x-enum-descriptions": {
                "auto": "VAT calculated automatically from country, product format, and date (default).",
                "standard": "Standard VAT rate applied.",
                "reduced": "Reduced VAT rate applied.",
                "zero": "No VAT."
            }
        },
        "gross_price": {
            "type": "boolean",
            "description": "`true` if `price.value` is a gross price (tax included), `false` if it is a net price\n(tax excluded). Gross prices are typical for consumer-facing retail in countries with\nfixed book price laws; net prices are common for international or institutional sales,\nwhere the buyer settles tax itself. Together with `price_type` this decides which of\nthe two ONIX Codelist 58 codes the entry corresponds to.\n"
        },
        "country_codes_included": {
            "type": "array",
            "description": "ISO 3166-1 alpha-2 country codes where this price applies (e.g. `\"DE\"`, `\"AT\"`,\n`\"CH\"`). Each code is checked against the countries the platform recognises, so an\nunrecognised code is rejected with `400 Bad Request`. Values are uppercased,\nde-duplicated and sorted alphabetically on write, so the order in a response need not\nmatch the order sent, and on update the list is replaced wholesale rather than merged.\n\nAlways present in a response, but frequently `[]`, which is meaningful: an empty list\nmeans the price is not country-specific, not that its territory is unknown —\n`region_codes_included` carries the scope instead, typically as `[\"WORLD\"]`. When a\nrequest leaves both include lists empty, `region_codes_included` comes back as\n`[\"WORLD\"]`.\n\nFilterable on the collection endpoint, with equality and `__contains` only; see\n[Resource Queries](/resource_queries/).\n",
            "items": {
                "type": "string"
            },
            "example": [
                "DE",
                "AT"
            ]
        },
        "country_codes_excluded": {
            "type": "array",
            "description": "ISO 3166-1 alpha-2 country codes explicitly excluded from this price, validated and\nnormalised exactly as `country_codes_included` is. It carves countries out of the\nincluded set, which is what defines a broad price with country-level exceptions:\n`region_codes_included: [\"WORLD\"]` together with `country_codes_excluded: [\"DE\"]`\nmeans every country except Germany.\n\nAlways present in a response; `[]` means nothing is carved out. Not filterable.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "region_codes_included": {
            "type": "array",
            "description": "Region codes where this price applies, validated against ONIX\n[Codelist 49](https://ns.editeur.org/onix/en/49); a code outside that codelist is\nrejected with `400 Bad Request`. In practice `WORLD` is the only usable value, because\nthe platform's territory handling rejects every other Codelist 49 region. `ROW` is\naccepted as a synonym and comes back as `WORLD`.\n\n`[\"WORLD\"]` makes the price the fallback for every country that has no more specific\ncountry-level price, minus anything listed in `country_codes_excluded`. An empty list\nmeans the price is already scoped by `country_codes_included`; when both include lists\nare empty, `[\"WORLD\"]` comes back.\n\nValues are uppercased, de-duplicated and sorted on write. Always present in a response.\nFilterable on the collection endpoint, with equality and `__contains` only; see\n[Resource Queries](/resource_queries/).\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "region_codes_excluded": {
            "type": "array",
            "description": "Region codes explicitly excluded from this price. Always `[]` in practice: the\nplatform's territory handling does not support a non-empty value, so exclusions are\nexpressed through `country_codes_excluded` instead. Values are uppercased,\nde-duplicated and sorted on write. Always present in a response. Not filterable.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "effective_from": {
            "type": "string",
            "description": "The date from which this price becomes effective, in `YYYY-MM-DD` format. `null` means\nthe price has no start date and is effective from the beginning of time.\n\nPrices are grouped by product format, `qualifier_code`, territory and channel, and\nwithin one group the effective periods are expected to form a consistent timeline: no\ntwo periods overlap, and only the earliest price of the group leaves this field open.\nRegular and library prices should additionally form a *gapless* chain, each period\nstarting the day after the previous one ends, so that no date is left without a price.\nCampaign prices (`qualifier_code: \"08\"`) are the exception: each is an isolated window\nwhose first day is set here, and gaps between campaigns are normal.\n\nThis endpoint validates each price entry on its own and does not compare it with the\nother prices of the product, so an overlapping or gapped timeline is accepted on\nwrite.\n",
            "nullable": true,
            "example": "2026-08-06"
        },
        "effective_until": {
            "type": "string",
            "description": "The date until which this price remains effective, in `YYYY-MM-DD` format. The day\nitself is included. `null` means the price has no end date and remains effective\nindefinitely; only the latest price of a group leaves this field open.\n\nFor a campaign price (`qualifier_code: \"08\"`) this is the last day of the promotion.\nThe regular price applies again on the following day, without the regular entry being\nre-created or amended.\n",
            "nullable": true,
            "example": "2026-09-06"
        },
        "channels": {
            "type": "array",
            "description": "Distribution channel identifiers this price is scoped to. An empty list — the normal\ncase — means the price applies to every channel. Only library prices\n(`qualifier_code: \"06\"`) use it in practice, naming a lending platform; regular\nconsumer prices carry an empty list.\n\nValues are the platform's distribution-channel slugs, for example:\n\n- `divibib`\n- `overdrive`\n- `ciando`\n- `skoobe`\n- `libri`\n- `google`\n\nThey are not validated against that list — any string of lowercase letters and hyphens\nis accepted, so a typo silently produces a price no channel matches. Slugs containing\ndigits or underscores (`24symbols`, `manual_sale`, `zeitfracht_sa02e`) are valid\nchannels elsewhere in the API but are rejected here with `400 Bad Request`, because\nthis field allows only `[a-z-]`.\n\nThe list is lower-cased, de-duplicated and sorted alphabetically on write, so the order\nin a response need not match the order sent. On update it is replaced wholesale, never\nmerged. Always present in a response.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "price": {
            "type": "object",
            "description": "Monetary Amount",
            "additionalProperties": false,
            "nullable": true,
            "properties": {
                "value": {
                    "type": "number",
                    "description": "Value of the monetary amount",
                    "example": 42.23
                },
                "currency": {
                    "type": "string",
                    "description": "ISO 4217 currency code",
                    "example": "EUR"
                }
            },
            "required": [
                "value",
                "currency"
            ]
        }
    },
    "required": []
}

Responses

{
    "_id": "7ebf6af1-59f2-4958-88ed-8858e588981d",
    "_legacy_id": 7351,
    "_version": "4.0",
    "_links": {
        "self": "https://api.openpublishing.com/resource/v4/products/ea8af2fa-e616-11ee-a221-00505699575c/prices/7ebf6af1-59f2-4958-88ed-8858e588981d"
    },
    "description": "K-Lizenz",
    "product_type": "ebook",
    "qualifier_code": "06",
    "price_code": "quota-license",
    "price_type": null,
    "price_status_code": null,
    "vat_type": "auto",
    "gross_price": false,
    "country_codes_included": [],
    "country_codes_excluded": [],
    "region_codes_included": [
        "WORLD"
    ],
    "region_codes_excluded": [],
    "channels": [
        "divibib"
    ],
    "effective_from": "2024-03-12",
    "effective_until": null,
    "price": {
        "value": 4.19,
        "currency": "EUR"
    }
}
Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "description": "One price entry of a product, scoped to a format, a territory, a channel set and a period.",
    "properties": {
        "_id": {
            "type": "string",
            "description": "Unique identifier for the price.",
            "example": "949b0070-d590-4154-bbb9-961051f3f36a"
        },
        "_version": {
            "type": "string",
            "description": "API Version.",
            "example": "4.0"
        },
        "_legacy_id": {
            "type": "integer",
            "description": "Legacy integer identifier of this resource, kept so existing integrations can correlate their own records.",
            "example": 38054816
        },
        "_links": {
            "type": "object"
        },
        "description": {
            "type": "string",
            "description": "Human-readable label for the price entry, typically describing its purpose or origin.\nExamples from the book trade: `\"geb. Ladenpreis\"` (fixed retail price, gross),\n`\"Netto-Ladenpreis\"` (net retail price), `\"K-Lizenz\"` (quota license for libraries).\n\nFor campaign prices (`qualifier_code: \"08\"`) this holds the promotion's name —\n`\"Summer campaign\"`, `\"Christmas\"` — since it is the only field that distinguishes one\ncampaign of a product from another.\n",
            "nullable": true,
            "example": "geb. Ladenpreis"
        },
        "product_type": {
            "type": "string",
            "description": "The product format this price applies to. Each format is priced independently, so a\nsingle product may carry separate ebook and book prices. One of four values:\n\n- `ebook` — e-book in any file format (EPUB, PDF, …)\n- `book` — physical book, including print-on-demand\n- `audiobook` — audiobook, downloadable or streaming\n- `nonbook` — non-book article\n\nAlways present in a response; a price cannot exist without a format. Values are\nlower-cased on write, so `\"Ebook\"` is accepted and comes back as `ebook`.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "example": "ebook",
            "x-enum-descriptions": {
                "ebook": "E-book in any file format (EPUB, PDF, …)",
                "book": "Physical book, including print-on-demand",
                "audiobook": "Audiobook, downloadable or streaming",
                "nonbook": "Non-book article"
            }
        },
        "qualifier_code": {
            "type": "string",
            "description": "ONIX price type qualifier from [Codelist 59](https://ns.editeur.org/onix/en/59),\nidentifying the audience or purpose of this price. `null` is an unqualified consumer\nprice, equivalent to Codelist 59 code `\"00\"`. Codelist 59 defines twenty codes, but\nonly three qualifier modes are accepted; any other value is rejected with `400 Bad\nRequest`:\n\n- `null` — regular consumer price (retail).\n- `\"06\"` — corporate / library / education price, for sale to libraries, academic\n  institutions or other corporate customers. The most common non-null qualifier,\n  normally combined with a `price_code` and a `channels` entry.\n- `\"08\"` — promotional offer price: a time-limited campaign price bounded by\n  `effective_from` and `effective_until`, and named in `description`.\n\nA campaign does not shorten, modify or replace the regular price entry. The regular\nprice simply stops being the effective one for the duration of the campaign and\napplies again the day after `effective_until`, without being re-created or amended.\nUnlike regular and library prices, campaigns need not tile the timeline without gaps,\nso a product may carry several unrelated campaign windows with ordinary pricing\nbetween them.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax. Because regular prices\ncarry `null`, they are selected with `?qualifier_code__isnull` and are not returned by\n`?qualifier_code__ne=08`.\n",
            "nullable": true,
            "example": null,
            "x-enum-descriptions": {
                "06": "Corporate / Library / Education price — for sale to libraries, academic institutions, or other corporate customers (ONIX Codelist 59).",
                "08": "Promotional offer price — a time-limited campaign price that overrides the regular price for the period between `effective_from` and `effective_until` (ONIX Codelist 59)."
            }
        },
        "price_code": {
            "type": "string",
            "description": "A lowercase identifier for the pricing or licensing model, used primarily with library\nprices (`qualifier_code: \"06\"`). Lower-cased on write, and restricted to lowercase\nletters and hyphens (`[a-z-]`); anything else is rejected with `400 Bad Request`.\nCommon values in the book trade:\n\n- `pay-per-loan` — the library pays a fee each time the title is lent to a patron.\n- `quota-license` — the library purchases a fixed number of loans, or a time-limited\n  licence for a set of concurrent users.\n\nRegular consumer prices typically have `price_code: null`.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "nullable": true,
            "example": null
        },
        "price_type": {
            "type": "string",
            "description": "Whether this is a recommended retail price or a fixed (legally binding) retail price.\nOne of two values, or `null` when the distinction is not applicable. Anything else is\nrejected with `400 Bad Request`. Both correspond to a pair of codes in\n[ONIX Codelist 58](https://ns.editeur.org/onix/en/58), one for a tax-exclusive and one\nfor a tax-inclusive amount, which is the distinction `gross_price` records:\n\n- `recommended` — Recommended Retail Price (Codelist 58 `01` excluding tax, `02`\n  including tax). A retailer may sell at a different price.\n- `fixed` — Fixed Retail Price (Codelist 58 `03` excluding tax, `04` including tax).\n  The price is legally binding, for instance under German and Austrian fixed book\n  price law (*Buchpreisbindung*).\n",
            "nullable": true,
            "example": "fixed"
        },
        "price_status_code": {
            "type": "string",
            "description": "ONIX price status code from [Codelist 61](https://ns.editeur.org/onix/en/61), stating\nhow settled the price is. Any Codelist 61 code is accepted; anything else is rejected\nwith `400 Bad Request`. The codelist has three entries:\n\n- `\"00\"` — Unspecified. The codelist's own default.\n- `\"01\"` — Provisional: the price is not yet settled.\n- `\"02\"` — Confirmed: the price is settled and in effect. The common value.\n\n`null` when no status is recorded.\n",
            "nullable": true,
            "example": "02"
        },
        "vat_type": {
            "type": "string",
            "description": "How VAT (Value Added Tax) is determined for this price. One of four values; anything\nelse is rejected with `400 Bad Request`:\n\n- `auto` — VAT is calculated from the destination country, the product format\n  (e-books and print books often carry different rates) and the date. The default,\n  and the usual setting.\n- `standard` — the standard VAT rate is applied regardless of product format.\n- `reduced` — the reduced VAT rate is applied.\n- `zero` — no VAT is applied.\n\nAlways present in a response; `auto` applies when the request body omits the field.\n",
            "example": "auto",
            "x-enum-descriptions": {
                "auto": "VAT calculated automatically from country, product format, and date (default).",
                "standard": "Standard VAT rate applied.",
                "reduced": "Reduced VAT rate applied.",
                "zero": "No VAT."
            }
        },
        "gross_price": {
            "type": "boolean",
            "description": "`true` if `price.value` is a gross price (tax included), `false` if it is a net price\n(tax excluded). Gross prices are typical for consumer-facing retail in countries with\nfixed book price laws; net prices are common for international or institutional sales,\nwhere the buyer settles tax itself. Together with `price_type` this decides which of\nthe two ONIX Codelist 58 codes the entry corresponds to.\n"
        },
        "country_codes_included": {
            "type": "array",
            "description": "ISO 3166-1 alpha-2 country codes where this price applies (e.g. `\"DE\"`, `\"AT\"`,\n`\"CH\"`). Each code is checked against the countries the platform recognises, so an\nunrecognised code is rejected with `400 Bad Request`. Values are uppercased,\nde-duplicated and sorted alphabetically on write, so the order in a response need not\nmatch the order sent, and on update the list is replaced wholesale rather than merged.\n\nAlways present in a response, but frequently `[]`, which is meaningful: an empty list\nmeans the price is not country-specific, not that its territory is unknown —\n`region_codes_included` carries the scope instead, typically as `[\"WORLD\"]`. When a\nrequest leaves both include lists empty, `region_codes_included` comes back as\n`[\"WORLD\"]`.\n\nFilterable on the collection endpoint, with equality and `__contains` only; see\n[Resource Queries](/resource_queries/).\n",
            "items": {
                "type": "string"
            },
            "example": [
                "DE",
                "AT"
            ]
        },
        "country_codes_excluded": {
            "type": "array",
            "description": "ISO 3166-1 alpha-2 country codes explicitly excluded from this price, validated and\nnormalised exactly as `country_codes_included` is. It carves countries out of the\nincluded set, which is what defines a broad price with country-level exceptions:\n`region_codes_included: [\"WORLD\"]` together with `country_codes_excluded: [\"DE\"]`\nmeans every country except Germany.\n\nAlways present in a response; `[]` means nothing is carved out. Not filterable.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "region_codes_included": {
            "type": "array",
            "description": "Region codes where this price applies, validated against ONIX\n[Codelist 49](https://ns.editeur.org/onix/en/49); a code outside that codelist is\nrejected with `400 Bad Request`. In practice `WORLD` is the only usable value, because\nthe platform's territory handling rejects every other Codelist 49 region. `ROW` is\naccepted as a synonym and comes back as `WORLD`.\n\n`[\"WORLD\"]` makes the price the fallback for every country that has no more specific\ncountry-level price, minus anything listed in `country_codes_excluded`. An empty list\nmeans the price is already scoped by `country_codes_included`; when both include lists\nare empty, `[\"WORLD\"]` comes back.\n\nValues are uppercased, de-duplicated and sorted on write. Always present in a response.\nFilterable on the collection endpoint, with equality and `__contains` only; see\n[Resource Queries](/resource_queries/).\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "region_codes_excluded": {
            "type": "array",
            "description": "Region codes explicitly excluded from this price. Always `[]` in practice: the\nplatform's territory handling does not support a non-empty value, so exclusions are\nexpressed through `country_codes_excluded` instead. Values are uppercased,\nde-duplicated and sorted on write. Always present in a response. Not filterable.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "effective_from": {
            "type": "string",
            "description": "The date from which this price becomes effective, in `YYYY-MM-DD` format. `null` means\nthe price has no start date and is effective from the beginning of time.\n\nPrices are grouped by product format, `qualifier_code`, territory and channel, and\nwithin one group the effective periods are expected to form a consistent timeline: no\ntwo periods overlap, and only the earliest price of the group leaves this field open.\nRegular and library prices should additionally form a *gapless* chain, each period\nstarting the day after the previous one ends, so that no date is left without a price.\nCampaign prices (`qualifier_code: \"08\"`) are the exception: each is an isolated window\nwhose first day is set here, and gaps between campaigns are normal.\n\nThis endpoint validates each price entry on its own and does not compare it with the\nother prices of the product, so an overlapping or gapped timeline is accepted on\nwrite.\n",
            "nullable": true,
            "example": "2026-08-06"
        },
        "effective_until": {
            "type": "string",
            "description": "The date until which this price remains effective, in `YYYY-MM-DD` format. The day\nitself is included. `null` means the price has no end date and remains effective\nindefinitely; only the latest price of a group leaves this field open.\n\nFor a campaign price (`qualifier_code: \"08\"`) this is the last day of the promotion.\nThe regular price applies again on the following day, without the regular entry being\nre-created or amended.\n",
            "nullable": true,
            "example": "2026-09-06"
        },
        "channels": {
            "type": "array",
            "description": "Distribution channel identifiers this price is scoped to. An empty list — the normal\ncase — means the price applies to every channel. Only library prices\n(`qualifier_code: \"06\"`) use it in practice, naming a lending platform; regular\nconsumer prices carry an empty list.\n\nValues are the platform's distribution-channel slugs, for example:\n\n- `divibib`\n- `overdrive`\n- `ciando`\n- `skoobe`\n- `libri`\n- `google`\n\nThey are not validated against that list — any string of lowercase letters and hyphens\nis accepted, so a typo silently produces a price no channel matches. Slugs containing\ndigits or underscores (`24symbols`, `manual_sale`, `zeitfracht_sa02e`) are valid\nchannels elsewhere in the API but are rejected here with `400 Bad Request`, because\nthis field allows only `[a-z-]`.\n\nThe list is lower-cased, de-duplicated and sorted alphabetically on write, so the order\nin a response need not match the order sent. On update it is replaced wholesale, never\nmerged. Always present in a response.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "price": {
            "type": "object",
            "description": "Monetary Amount",
            "additionalProperties": false,
            "nullable": true,
            "properties": {
                "value": {
                    "type": "number",
                    "description": "Value of the monetary amount",
                    "example": 42.23
                },
                "currency": {
                    "type": "string",
                    "description": "ISO 4217 currency code",
                    "example": "EUR"
                }
            },
            "required": [
                "value",
                "currency"
            ]
        }
    },
    "required": []
}

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

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

GET /resource/v4/products/{product_id}/prices/{id}

Retrieve a single product price

Description

Returns one price entry by id. The example below is a campaign price: the e-book's regular price is 13.99 EUR, and this entry discounts it to 9.55 EUR for the month of the promotion. The regular entry is untouched and applies again on 7 September.

Input parameters

Path parameters

Parameter Type Default Nullable Description
id string No
product_id string No

Responses

{
    "_id": "a29fddb1-0b06-4239-aedb-1eb59deb7bda",
    "_legacy_id": 38054817,
    "_version": "4.0",
    "_links": {
        "self": "https://api.openpublishing.com/resource/v4/products/a6fc09af-e616-11ee-a221-00505699575c/prices/a29fddb1-0b06-4239-aedb-1eb59deb7bda"
    },
    "description": "Summer campaign",
    "product_type": "ebook",
    "qualifier_code": "08",
    "price_code": null,
    "price_type": null,
    "price_status_code": null,
    "vat_type": "auto",
    "gross_price": true,
    "country_codes_included": [],
    "country_codes_excluded": [],
    "region_codes_included": [
        "WORLD"
    ],
    "region_codes_excluded": [],
    "channels": [],
    "effective_from": "2026-08-06",
    "effective_until": "2026-09-06",
    "price": {
        "value": 9.55,
        "currency": "EUR"
    }
}
Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "description": "One price entry of a product, scoped to a format, a territory, a channel set and a period.",
    "properties": {
        "_id": {
            "type": "string",
            "description": "Unique identifier for the price.",
            "example": "949b0070-d590-4154-bbb9-961051f3f36a"
        },
        "_version": {
            "type": "string",
            "description": "API Version.",
            "example": "4.0"
        },
        "_legacy_id": {
            "type": "integer",
            "description": "Legacy integer identifier of this resource, kept so existing integrations can correlate their own records.",
            "example": 38054816
        },
        "_links": {
            "type": "object"
        },
        "description": {
            "type": "string",
            "description": "Human-readable label for the price entry, typically describing its purpose or origin.\nExamples from the book trade: `\"geb. Ladenpreis\"` (fixed retail price, gross),\n`\"Netto-Ladenpreis\"` (net retail price), `\"K-Lizenz\"` (quota license for libraries).\n\nFor campaign prices (`qualifier_code: \"08\"`) this holds the promotion's name —\n`\"Summer campaign\"`, `\"Christmas\"` — since it is the only field that distinguishes one\ncampaign of a product from another.\n",
            "nullable": true,
            "example": "geb. Ladenpreis"
        },
        "product_type": {
            "type": "string",
            "description": "The product format this price applies to. Each format is priced independently, so a\nsingle product may carry separate ebook and book prices. One of four values:\n\n- `ebook` — e-book in any file format (EPUB, PDF, …)\n- `book` — physical book, including print-on-demand\n- `audiobook` — audiobook, downloadable or streaming\n- `nonbook` — non-book article\n\nAlways present in a response; a price cannot exist without a format. Values are\nlower-cased on write, so `\"Ebook\"` is accepted and comes back as `ebook`.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "example": "ebook",
            "x-enum-descriptions": {
                "ebook": "E-book in any file format (EPUB, PDF, …)",
                "book": "Physical book, including print-on-demand",
                "audiobook": "Audiobook, downloadable or streaming",
                "nonbook": "Non-book article"
            }
        },
        "qualifier_code": {
            "type": "string",
            "description": "ONIX price type qualifier from [Codelist 59](https://ns.editeur.org/onix/en/59),\nidentifying the audience or purpose of this price. `null` is an unqualified consumer\nprice, equivalent to Codelist 59 code `\"00\"`. Codelist 59 defines twenty codes, but\nonly three qualifier modes are accepted; any other value is rejected with `400 Bad\nRequest`:\n\n- `null` — regular consumer price (retail).\n- `\"06\"` — corporate / library / education price, for sale to libraries, academic\n  institutions or other corporate customers. The most common non-null qualifier,\n  normally combined with a `price_code` and a `channels` entry.\n- `\"08\"` — promotional offer price: a time-limited campaign price bounded by\n  `effective_from` and `effective_until`, and named in `description`.\n\nA campaign does not shorten, modify or replace the regular price entry. The regular\nprice simply stops being the effective one for the duration of the campaign and\napplies again the day after `effective_until`, without being re-created or amended.\nUnlike regular and library prices, campaigns need not tile the timeline without gaps,\nso a product may carry several unrelated campaign windows with ordinary pricing\nbetween them.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax. Because regular prices\ncarry `null`, they are selected with `?qualifier_code__isnull` and are not returned by\n`?qualifier_code__ne=08`.\n",
            "nullable": true,
            "example": null,
            "x-enum-descriptions": {
                "06": "Corporate / Library / Education price — for sale to libraries, academic institutions, or other corporate customers (ONIX Codelist 59).",
                "08": "Promotional offer price — a time-limited campaign price that overrides the regular price for the period between `effective_from` and `effective_until` (ONIX Codelist 59)."
            }
        },
        "price_code": {
            "type": "string",
            "description": "A lowercase identifier for the pricing or licensing model, used primarily with library\nprices (`qualifier_code: \"06\"`). Lower-cased on write, and restricted to lowercase\nletters and hyphens (`[a-z-]`); anything else is rejected with `400 Bad Request`.\nCommon values in the book trade:\n\n- `pay-per-loan` — the library pays a fee each time the title is lent to a patron.\n- `quota-license` — the library purchases a fixed number of loans, or a time-limited\n  licence for a set of concurrent users.\n\nRegular consumer prices typically have `price_code: null`.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "nullable": true,
            "example": null
        },
        "price_type": {
            "type": "string",
            "description": "Whether this is a recommended retail price or a fixed (legally binding) retail price.\nOne of two values, or `null` when the distinction is not applicable. Anything else is\nrejected with `400 Bad Request`. Both correspond to a pair of codes in\n[ONIX Codelist 58](https://ns.editeur.org/onix/en/58), one for a tax-exclusive and one\nfor a tax-inclusive amount, which is the distinction `gross_price` records:\n\n- `recommended` — Recommended Retail Price (Codelist 58 `01` excluding tax, `02`\n  including tax). A retailer may sell at a different price.\n- `fixed` — Fixed Retail Price (Codelist 58 `03` excluding tax, `04` including tax).\n  The price is legally binding, for instance under German and Austrian fixed book\n  price law (*Buchpreisbindung*).\n",
            "nullable": true,
            "example": "fixed"
        },
        "price_status_code": {
            "type": "string",
            "description": "ONIX price status code from [Codelist 61](https://ns.editeur.org/onix/en/61), stating\nhow settled the price is. Any Codelist 61 code is accepted; anything else is rejected\nwith `400 Bad Request`. The codelist has three entries:\n\n- `\"00\"` — Unspecified. The codelist's own default.\n- `\"01\"` — Provisional: the price is not yet settled.\n- `\"02\"` — Confirmed: the price is settled and in effect. The common value.\n\n`null` when no status is recorded.\n",
            "nullable": true,
            "example": "02"
        },
        "vat_type": {
            "type": "string",
            "description": "How VAT (Value Added Tax) is determined for this price. One of four values; anything\nelse is rejected with `400 Bad Request`:\n\n- `auto` — VAT is calculated from the destination country, the product format\n  (e-books and print books often carry different rates) and the date. The default,\n  and the usual setting.\n- `standard` — the standard VAT rate is applied regardless of product format.\n- `reduced` — the reduced VAT rate is applied.\n- `zero` — no VAT is applied.\n\nAlways present in a response; `auto` applies when the request body omits the field.\n",
            "example": "auto",
            "x-enum-descriptions": {
                "auto": "VAT calculated automatically from country, product format, and date (default).",
                "standard": "Standard VAT rate applied.",
                "reduced": "Reduced VAT rate applied.",
                "zero": "No VAT."
            }
        },
        "gross_price": {
            "type": "boolean",
            "description": "`true` if `price.value` is a gross price (tax included), `false` if it is a net price\n(tax excluded). Gross prices are typical for consumer-facing retail in countries with\nfixed book price laws; net prices are common for international or institutional sales,\nwhere the buyer settles tax itself. Together with `price_type` this decides which of\nthe two ONIX Codelist 58 codes the entry corresponds to.\n"
        },
        "country_codes_included": {
            "type": "array",
            "description": "ISO 3166-1 alpha-2 country codes where this price applies (e.g. `\"DE\"`, `\"AT\"`,\n`\"CH\"`). Each code is checked against the countries the platform recognises, so an\nunrecognised code is rejected with `400 Bad Request`. Values are uppercased,\nde-duplicated and sorted alphabetically on write, so the order in a response need not\nmatch the order sent, and on update the list is replaced wholesale rather than merged.\n\nAlways present in a response, but frequently `[]`, which is meaningful: an empty list\nmeans the price is not country-specific, not that its territory is unknown —\n`region_codes_included` carries the scope instead, typically as `[\"WORLD\"]`. When a\nrequest leaves both include lists empty, `region_codes_included` comes back as\n`[\"WORLD\"]`.\n\nFilterable on the collection endpoint, with equality and `__contains` only; see\n[Resource Queries](/resource_queries/).\n",
            "items": {
                "type": "string"
            },
            "example": [
                "DE",
                "AT"
            ]
        },
        "country_codes_excluded": {
            "type": "array",
            "description": "ISO 3166-1 alpha-2 country codes explicitly excluded from this price, validated and\nnormalised exactly as `country_codes_included` is. It carves countries out of the\nincluded set, which is what defines a broad price with country-level exceptions:\n`region_codes_included: [\"WORLD\"]` together with `country_codes_excluded: [\"DE\"]`\nmeans every country except Germany.\n\nAlways present in a response; `[]` means nothing is carved out. Not filterable.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "region_codes_included": {
            "type": "array",
            "description": "Region codes where this price applies, validated against ONIX\n[Codelist 49](https://ns.editeur.org/onix/en/49); a code outside that codelist is\nrejected with `400 Bad Request`. In practice `WORLD` is the only usable value, because\nthe platform's territory handling rejects every other Codelist 49 region. `ROW` is\naccepted as a synonym and comes back as `WORLD`.\n\n`[\"WORLD\"]` makes the price the fallback for every country that has no more specific\ncountry-level price, minus anything listed in `country_codes_excluded`. An empty list\nmeans the price is already scoped by `country_codes_included`; when both include lists\nare empty, `[\"WORLD\"]` comes back.\n\nValues are uppercased, de-duplicated and sorted on write. Always present in a response.\nFilterable on the collection endpoint, with equality and `__contains` only; see\n[Resource Queries](/resource_queries/).\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "region_codes_excluded": {
            "type": "array",
            "description": "Region codes explicitly excluded from this price. Always `[]` in practice: the\nplatform's territory handling does not support a non-empty value, so exclusions are\nexpressed through `country_codes_excluded` instead. Values are uppercased,\nde-duplicated and sorted on write. Always present in a response. Not filterable.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "effective_from": {
            "type": "string",
            "description": "The date from which this price becomes effective, in `YYYY-MM-DD` format. `null` means\nthe price has no start date and is effective from the beginning of time.\n\nPrices are grouped by product format, `qualifier_code`, territory and channel, and\nwithin one group the effective periods are expected to form a consistent timeline: no\ntwo periods overlap, and only the earliest price of the group leaves this field open.\nRegular and library prices should additionally form a *gapless* chain, each period\nstarting the day after the previous one ends, so that no date is left without a price.\nCampaign prices (`qualifier_code: \"08\"`) are the exception: each is an isolated window\nwhose first day is set here, and gaps between campaigns are normal.\n\nThis endpoint validates each price entry on its own and does not compare it with the\nother prices of the product, so an overlapping or gapped timeline is accepted on\nwrite.\n",
            "nullable": true,
            "example": "2026-08-06"
        },
        "effective_until": {
            "type": "string",
            "description": "The date until which this price remains effective, in `YYYY-MM-DD` format. The day\nitself is included. `null` means the price has no end date and remains effective\nindefinitely; only the latest price of a group leaves this field open.\n\nFor a campaign price (`qualifier_code: \"08\"`) this is the last day of the promotion.\nThe regular price applies again on the following day, without the regular entry being\nre-created or amended.\n",
            "nullable": true,
            "example": "2026-09-06"
        },
        "channels": {
            "type": "array",
            "description": "Distribution channel identifiers this price is scoped to. An empty list — the normal\ncase — means the price applies to every channel. Only library prices\n(`qualifier_code: \"06\"`) use it in practice, naming a lending platform; regular\nconsumer prices carry an empty list.\n\nValues are the platform's distribution-channel slugs, for example:\n\n- `divibib`\n- `overdrive`\n- `ciando`\n- `skoobe`\n- `libri`\n- `google`\n\nThey are not validated against that list — any string of lowercase letters and hyphens\nis accepted, so a typo silently produces a price no channel matches. Slugs containing\ndigits or underscores (`24symbols`, `manual_sale`, `zeitfracht_sa02e`) are valid\nchannels elsewhere in the API but are rejected here with `400 Bad Request`, because\nthis field allows only `[a-z-]`.\n\nThe list is lower-cased, de-duplicated and sorted alphabetically on write, so the order\nin a response need not match the order sent. On update it is replaced wholesale, never\nmerged. Always present in a response.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "price": {
            "type": "object",
            "description": "Monetary Amount",
            "additionalProperties": false,
            "nullable": true,
            "properties": {
                "value": {
                    "type": "number",
                    "description": "Value of the monetary amount",
                    "example": 42.23
                },
                "currency": {
                    "type": "string",
                    "description": "ISO 4217 currency code",
                    "example": "EUR"
                }
            },
            "required": [
                "value",
                "currency"
            ]
        }
    },
    "required": []
}

PUT /resource/v4/products/{product_id}/prices/{id}

Update product price

Description

Updates one price entry. Despite being a PUT, this is a partial update: only the fields present in the body are applied, and every field absent from it keeps its current value. Sending {} therefore changes nothing, and a nullable field is cleared only by an explicit null.

The example below is a regular consumer price being raised from 13.99 EUR to 14.99 EUR. Only price is sent; product_type, territory, VAT handling and the rest stay as they are. The response returns the complete merged entry.

Input parameters

Path parameters

Parameter Type Default Nullable Description
id string No
product_id string No

Request body

{
    "price": {
        "value": 14.99,
        "currency": "EUR"
    }
}
Schema of the request body
{
    "type": "object",
    "additionalProperties": false,
    "description": "One price entry of a product, scoped to a format, a territory, a channel set and a period.",
    "properties": {
        "_id": {
            "type": "string",
            "description": "Unique identifier for the price.",
            "example": "949b0070-d590-4154-bbb9-961051f3f36a"
        },
        "_version": {
            "type": "string",
            "description": "API Version.",
            "example": "4.0"
        },
        "_legacy_id": {
            "type": "integer",
            "description": "Legacy integer identifier of this resource, kept so existing integrations can correlate their own records.",
            "example": 38054816
        },
        "_links": {
            "type": "object"
        },
        "description": {
            "type": "string",
            "description": "Human-readable label for the price entry, typically describing its purpose or origin.\nExamples from the book trade: `\"geb. Ladenpreis\"` (fixed retail price, gross),\n`\"Netto-Ladenpreis\"` (net retail price), `\"K-Lizenz\"` (quota license for libraries).\n\nFor campaign prices (`qualifier_code: \"08\"`) this holds the promotion's name —\n`\"Summer campaign\"`, `\"Christmas\"` — since it is the only field that distinguishes one\ncampaign of a product from another.\n",
            "nullable": true,
            "example": "geb. Ladenpreis"
        },
        "product_type": {
            "type": "string",
            "description": "The product format this price applies to. Each format is priced independently, so a\nsingle product may carry separate ebook and book prices. One of four values:\n\n- `ebook` — e-book in any file format (EPUB, PDF, …)\n- `book` — physical book, including print-on-demand\n- `audiobook` — audiobook, downloadable or streaming\n- `nonbook` — non-book article\n\nAlways present in a response; a price cannot exist without a format. Values are\nlower-cased on write, so `\"Ebook\"` is accepted and comes back as `ebook`.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "example": "ebook",
            "x-enum-descriptions": {
                "ebook": "E-book in any file format (EPUB, PDF, …)",
                "book": "Physical book, including print-on-demand",
                "audiobook": "Audiobook, downloadable or streaming",
                "nonbook": "Non-book article"
            }
        },
        "qualifier_code": {
            "type": "string",
            "description": "ONIX price type qualifier from [Codelist 59](https://ns.editeur.org/onix/en/59),\nidentifying the audience or purpose of this price. `null` is an unqualified consumer\nprice, equivalent to Codelist 59 code `\"00\"`. Codelist 59 defines twenty codes, but\nonly three qualifier modes are accepted; any other value is rejected with `400 Bad\nRequest`:\n\n- `null` — regular consumer price (retail).\n- `\"06\"` — corporate / library / education price, for sale to libraries, academic\n  institutions or other corporate customers. The most common non-null qualifier,\n  normally combined with a `price_code` and a `channels` entry.\n- `\"08\"` — promotional offer price: a time-limited campaign price bounded by\n  `effective_from` and `effective_until`, and named in `description`.\n\nA campaign does not shorten, modify or replace the regular price entry. The regular\nprice simply stops being the effective one for the duration of the campaign and\napplies again the day after `effective_until`, without being re-created or amended.\nUnlike regular and library prices, campaigns need not tile the timeline without gaps,\nso a product may carry several unrelated campaign windows with ordinary pricing\nbetween them.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax. Because regular prices\ncarry `null`, they are selected with `?qualifier_code__isnull` and are not returned by\n`?qualifier_code__ne=08`.\n",
            "nullable": true,
            "example": null,
            "x-enum-descriptions": {
                "06": "Corporate / Library / Education price — for sale to libraries, academic institutions, or other corporate customers (ONIX Codelist 59).",
                "08": "Promotional offer price — a time-limited campaign price that overrides the regular price for the period between `effective_from` and `effective_until` (ONIX Codelist 59)."
            }
        },
        "price_code": {
            "type": "string",
            "description": "A lowercase identifier for the pricing or licensing model, used primarily with library\nprices (`qualifier_code: \"06\"`). Lower-cased on write, and restricted to lowercase\nletters and hyphens (`[a-z-]`); anything else is rejected with `400 Bad Request`.\nCommon values in the book trade:\n\n- `pay-per-loan` — the library pays a fee each time the title is lent to a patron.\n- `quota-license` — the library purchases a fixed number of loans, or a time-limited\n  licence for a set of concurrent users.\n\nRegular consumer prices typically have `price_code: null`.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "nullable": true,
            "example": null
        },
        "price_type": {
            "type": "string",
            "description": "Whether this is a recommended retail price or a fixed (legally binding) retail price.\nOne of two values, or `null` when the distinction is not applicable. Anything else is\nrejected with `400 Bad Request`. Both correspond to a pair of codes in\n[ONIX Codelist 58](https://ns.editeur.org/onix/en/58), one for a tax-exclusive and one\nfor a tax-inclusive amount, which is the distinction `gross_price` records:\n\n- `recommended` — Recommended Retail Price (Codelist 58 `01` excluding tax, `02`\n  including tax). A retailer may sell at a different price.\n- `fixed` — Fixed Retail Price (Codelist 58 `03` excluding tax, `04` including tax).\n  The price is legally binding, for instance under German and Austrian fixed book\n  price law (*Buchpreisbindung*).\n",
            "nullable": true,
            "example": "fixed"
        },
        "price_status_code": {
            "type": "string",
            "description": "ONIX price status code from [Codelist 61](https://ns.editeur.org/onix/en/61), stating\nhow settled the price is. Any Codelist 61 code is accepted; anything else is rejected\nwith `400 Bad Request`. The codelist has three entries:\n\n- `\"00\"` — Unspecified. The codelist's own default.\n- `\"01\"` — Provisional: the price is not yet settled.\n- `\"02\"` — Confirmed: the price is settled and in effect. The common value.\n\n`null` when no status is recorded.\n",
            "nullable": true,
            "example": "02"
        },
        "vat_type": {
            "type": "string",
            "description": "How VAT (Value Added Tax) is determined for this price. One of four values; anything\nelse is rejected with `400 Bad Request`:\n\n- `auto` — VAT is calculated from the destination country, the product format\n  (e-books and print books often carry different rates) and the date. The default,\n  and the usual setting.\n- `standard` — the standard VAT rate is applied regardless of product format.\n- `reduced` — the reduced VAT rate is applied.\n- `zero` — no VAT is applied.\n\nAlways present in a response; `auto` applies when the request body omits the field.\n",
            "example": "auto",
            "x-enum-descriptions": {
                "auto": "VAT calculated automatically from country, product format, and date (default).",
                "standard": "Standard VAT rate applied.",
                "reduced": "Reduced VAT rate applied.",
                "zero": "No VAT."
            }
        },
        "gross_price": {
            "type": "boolean",
            "description": "`true` if `price.value` is a gross price (tax included), `false` if it is a net price\n(tax excluded). Gross prices are typical for consumer-facing retail in countries with\nfixed book price laws; net prices are common for international or institutional sales,\nwhere the buyer settles tax itself. Together with `price_type` this decides which of\nthe two ONIX Codelist 58 codes the entry corresponds to.\n"
        },
        "country_codes_included": {
            "type": "array",
            "description": "ISO 3166-1 alpha-2 country codes where this price applies (e.g. `\"DE\"`, `\"AT\"`,\n`\"CH\"`). Each code is checked against the countries the platform recognises, so an\nunrecognised code is rejected with `400 Bad Request`. Values are uppercased,\nde-duplicated and sorted alphabetically on write, so the order in a response need not\nmatch the order sent, and on update the list is replaced wholesale rather than merged.\n\nAlways present in a response, but frequently `[]`, which is meaningful: an empty list\nmeans the price is not country-specific, not that its territory is unknown —\n`region_codes_included` carries the scope instead, typically as `[\"WORLD\"]`. When a\nrequest leaves both include lists empty, `region_codes_included` comes back as\n`[\"WORLD\"]`.\n\nFilterable on the collection endpoint, with equality and `__contains` only; see\n[Resource Queries](/resource_queries/).\n",
            "items": {
                "type": "string"
            },
            "example": [
                "DE",
                "AT"
            ]
        },
        "country_codes_excluded": {
            "type": "array",
            "description": "ISO 3166-1 alpha-2 country codes explicitly excluded from this price, validated and\nnormalised exactly as `country_codes_included` is. It carves countries out of the\nincluded set, which is what defines a broad price with country-level exceptions:\n`region_codes_included: [\"WORLD\"]` together with `country_codes_excluded: [\"DE\"]`\nmeans every country except Germany.\n\nAlways present in a response; `[]` means nothing is carved out. Not filterable.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "region_codes_included": {
            "type": "array",
            "description": "Region codes where this price applies, validated against ONIX\n[Codelist 49](https://ns.editeur.org/onix/en/49); a code outside that codelist is\nrejected with `400 Bad Request`. In practice `WORLD` is the only usable value, because\nthe platform's territory handling rejects every other Codelist 49 region. `ROW` is\naccepted as a synonym and comes back as `WORLD`.\n\n`[\"WORLD\"]` makes the price the fallback for every country that has no more specific\ncountry-level price, minus anything listed in `country_codes_excluded`. An empty list\nmeans the price is already scoped by `country_codes_included`; when both include lists\nare empty, `[\"WORLD\"]` comes back.\n\nValues are uppercased, de-duplicated and sorted on write. Always present in a response.\nFilterable on the collection endpoint, with equality and `__contains` only; see\n[Resource Queries](/resource_queries/).\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "region_codes_excluded": {
            "type": "array",
            "description": "Region codes explicitly excluded from this price. Always `[]` in practice: the\nplatform's territory handling does not support a non-empty value, so exclusions are\nexpressed through `country_codes_excluded` instead. Values are uppercased,\nde-duplicated and sorted on write. Always present in a response. Not filterable.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "effective_from": {
            "type": "string",
            "description": "The date from which this price becomes effective, in `YYYY-MM-DD` format. `null` means\nthe price has no start date and is effective from the beginning of time.\n\nPrices are grouped by product format, `qualifier_code`, territory and channel, and\nwithin one group the effective periods are expected to form a consistent timeline: no\ntwo periods overlap, and only the earliest price of the group leaves this field open.\nRegular and library prices should additionally form a *gapless* chain, each period\nstarting the day after the previous one ends, so that no date is left without a price.\nCampaign prices (`qualifier_code: \"08\"`) are the exception: each is an isolated window\nwhose first day is set here, and gaps between campaigns are normal.\n\nThis endpoint validates each price entry on its own and does not compare it with the\nother prices of the product, so an overlapping or gapped timeline is accepted on\nwrite.\n",
            "nullable": true,
            "example": "2026-08-06"
        },
        "effective_until": {
            "type": "string",
            "description": "The date until which this price remains effective, in `YYYY-MM-DD` format. The day\nitself is included. `null` means the price has no end date and remains effective\nindefinitely; only the latest price of a group leaves this field open.\n\nFor a campaign price (`qualifier_code: \"08\"`) this is the last day of the promotion.\nThe regular price applies again on the following day, without the regular entry being\nre-created or amended.\n",
            "nullable": true,
            "example": "2026-09-06"
        },
        "channels": {
            "type": "array",
            "description": "Distribution channel identifiers this price is scoped to. An empty list — the normal\ncase — means the price applies to every channel. Only library prices\n(`qualifier_code: \"06\"`) use it in practice, naming a lending platform; regular\nconsumer prices carry an empty list.\n\nValues are the platform's distribution-channel slugs, for example:\n\n- `divibib`\n- `overdrive`\n- `ciando`\n- `skoobe`\n- `libri`\n- `google`\n\nThey are not validated against that list — any string of lowercase letters and hyphens\nis accepted, so a typo silently produces a price no channel matches. Slugs containing\ndigits or underscores (`24symbols`, `manual_sale`, `zeitfracht_sa02e`) are valid\nchannels elsewhere in the API but are rejected here with `400 Bad Request`, because\nthis field allows only `[a-z-]`.\n\nThe list is lower-cased, de-duplicated and sorted alphabetically on write, so the order\nin a response need not match the order sent. On update it is replaced wholesale, never\nmerged. Always present in a response.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "price": {
            "type": "object",
            "description": "Monetary Amount",
            "additionalProperties": false,
            "nullable": true,
            "properties": {
                "value": {
                    "type": "number",
                    "description": "Value of the monetary amount",
                    "example": 42.23
                },
                "currency": {
                    "type": "string",
                    "description": "ISO 4217 currency code",
                    "example": "EUR"
                }
            },
            "required": [
                "value",
                "currency"
            ]
        }
    },
    "required": []
}

Responses

{
    "_id": "19cecc47-32e7-4e29-8e24-7b2f4e6b9f37",
    "_legacy_id": 38054816,
    "_version": "4.0",
    "_links": {
        "self": "https://api.openpublishing.com/resource/v4/products/a6fc09af-e616-11ee-a221-00505699575c/prices/19cecc47-32e7-4e29-8e24-7b2f4e6b9f37"
    },
    "description": null,
    "product_type": "ebook",
    "qualifier_code": null,
    "price_code": null,
    "price_type": null,
    "price_status_code": null,
    "vat_type": "auto",
    "gross_price": true,
    "country_codes_included": [],
    "country_codes_excluded": [],
    "region_codes_included": [
        "WORLD"
    ],
    "region_codes_excluded": [],
    "channels": [],
    "effective_from": null,
    "effective_until": null,
    "price": {
        "value": 14.99,
        "currency": "EUR"
    }
}
Schema of the response body
{
    "type": "object",
    "additionalProperties": false,
    "description": "One price entry of a product, scoped to a format, a territory, a channel set and a period.",
    "properties": {
        "_id": {
            "type": "string",
            "description": "Unique identifier for the price.",
            "example": "949b0070-d590-4154-bbb9-961051f3f36a"
        },
        "_version": {
            "type": "string",
            "description": "API Version.",
            "example": "4.0"
        },
        "_legacy_id": {
            "type": "integer",
            "description": "Legacy integer identifier of this resource, kept so existing integrations can correlate their own records.",
            "example": 38054816
        },
        "_links": {
            "type": "object"
        },
        "description": {
            "type": "string",
            "description": "Human-readable label for the price entry, typically describing its purpose or origin.\nExamples from the book trade: `\"geb. Ladenpreis\"` (fixed retail price, gross),\n`\"Netto-Ladenpreis\"` (net retail price), `\"K-Lizenz\"` (quota license for libraries).\n\nFor campaign prices (`qualifier_code: \"08\"`) this holds the promotion's name —\n`\"Summer campaign\"`, `\"Christmas\"` — since it is the only field that distinguishes one\ncampaign of a product from another.\n",
            "nullable": true,
            "example": "geb. Ladenpreis"
        },
        "product_type": {
            "type": "string",
            "description": "The product format this price applies to. Each format is priced independently, so a\nsingle product may carry separate ebook and book prices. One of four values:\n\n- `ebook` — e-book in any file format (EPUB, PDF, …)\n- `book` — physical book, including print-on-demand\n- `audiobook` — audiobook, downloadable or streaming\n- `nonbook` — non-book article\n\nAlways present in a response; a price cannot exist without a format. Values are\nlower-cased on write, so `\"Ebook\"` is accepted and comes back as `ebook`.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "example": "ebook",
            "x-enum-descriptions": {
                "ebook": "E-book in any file format (EPUB, PDF, …)",
                "book": "Physical book, including print-on-demand",
                "audiobook": "Audiobook, downloadable or streaming",
                "nonbook": "Non-book article"
            }
        },
        "qualifier_code": {
            "type": "string",
            "description": "ONIX price type qualifier from [Codelist 59](https://ns.editeur.org/onix/en/59),\nidentifying the audience or purpose of this price. `null` is an unqualified consumer\nprice, equivalent to Codelist 59 code `\"00\"`. Codelist 59 defines twenty codes, but\nonly three qualifier modes are accepted; any other value is rejected with `400 Bad\nRequest`:\n\n- `null` — regular consumer price (retail).\n- `\"06\"` — corporate / library / education price, for sale to libraries, academic\n  institutions or other corporate customers. The most common non-null qualifier,\n  normally combined with a `price_code` and a `channels` entry.\n- `\"08\"` — promotional offer price: a time-limited campaign price bounded by\n  `effective_from` and `effective_until`, and named in `description`.\n\nA campaign does not shorten, modify or replace the regular price entry. The regular\nprice simply stops being the effective one for the duration of the campaign and\napplies again the day after `effective_until`, without being re-created or amended.\nUnlike regular and library prices, campaigns need not tile the timeline without gaps,\nso a product may carry several unrelated campaign windows with ordinary pricing\nbetween them.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax. Because regular prices\ncarry `null`, they are selected with `?qualifier_code__isnull` and are not returned by\n`?qualifier_code__ne=08`.\n",
            "nullable": true,
            "example": null,
            "x-enum-descriptions": {
                "06": "Corporate / Library / Education price — for sale to libraries, academic institutions, or other corporate customers (ONIX Codelist 59).",
                "08": "Promotional offer price — a time-limited campaign price that overrides the regular price for the period between `effective_from` and `effective_until` (ONIX Codelist 59)."
            }
        },
        "price_code": {
            "type": "string",
            "description": "A lowercase identifier for the pricing or licensing model, used primarily with library\nprices (`qualifier_code: \"06\"`). Lower-cased on write, and restricted to lowercase\nletters and hyphens (`[a-z-]`); anything else is rejected with `400 Bad Request`.\nCommon values in the book trade:\n\n- `pay-per-loan` — the library pays a fee each time the title is lent to a patron.\n- `quota-license` — the library purchases a fixed number of loans, or a time-limited\n  licence for a set of concurrent users.\n\nRegular consumer prices typically have `price_code: null`.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "nullable": true,
            "example": null
        },
        "price_type": {
            "type": "string",
            "description": "Whether this is a recommended retail price or a fixed (legally binding) retail price.\nOne of two values, or `null` when the distinction is not applicable. Anything else is\nrejected with `400 Bad Request`. Both correspond to a pair of codes in\n[ONIX Codelist 58](https://ns.editeur.org/onix/en/58), one for a tax-exclusive and one\nfor a tax-inclusive amount, which is the distinction `gross_price` records:\n\n- `recommended` — Recommended Retail Price (Codelist 58 `01` excluding tax, `02`\n  including tax). A retailer may sell at a different price.\n- `fixed` — Fixed Retail Price (Codelist 58 `03` excluding tax, `04` including tax).\n  The price is legally binding, for instance under German and Austrian fixed book\n  price law (*Buchpreisbindung*).\n",
            "nullable": true,
            "example": "fixed"
        },
        "price_status_code": {
            "type": "string",
            "description": "ONIX price status code from [Codelist 61](https://ns.editeur.org/onix/en/61), stating\nhow settled the price is. Any Codelist 61 code is accepted; anything else is rejected\nwith `400 Bad Request`. The codelist has three entries:\n\n- `\"00\"` — Unspecified. The codelist's own default.\n- `\"01\"` — Provisional: the price is not yet settled.\n- `\"02\"` — Confirmed: the price is settled and in effect. The common value.\n\n`null` when no status is recorded.\n",
            "nullable": true,
            "example": "02"
        },
        "vat_type": {
            "type": "string",
            "description": "How VAT (Value Added Tax) is determined for this price. One of four values; anything\nelse is rejected with `400 Bad Request`:\n\n- `auto` — VAT is calculated from the destination country, the product format\n  (e-books and print books often carry different rates) and the date. The default,\n  and the usual setting.\n- `standard` — the standard VAT rate is applied regardless of product format.\n- `reduced` — the reduced VAT rate is applied.\n- `zero` — no VAT is applied.\n\nAlways present in a response; `auto` applies when the request body omits the field.\n",
            "example": "auto",
            "x-enum-descriptions": {
                "auto": "VAT calculated automatically from country, product format, and date (default).",
                "standard": "Standard VAT rate applied.",
                "reduced": "Reduced VAT rate applied.",
                "zero": "No VAT."
            }
        },
        "gross_price": {
            "type": "boolean",
            "description": "`true` if `price.value` is a gross price (tax included), `false` if it is a net price\n(tax excluded). Gross prices are typical for consumer-facing retail in countries with\nfixed book price laws; net prices are common for international or institutional sales,\nwhere the buyer settles tax itself. Together with `price_type` this decides which of\nthe two ONIX Codelist 58 codes the entry corresponds to.\n"
        },
        "country_codes_included": {
            "type": "array",
            "description": "ISO 3166-1 alpha-2 country codes where this price applies (e.g. `\"DE\"`, `\"AT\"`,\n`\"CH\"`). Each code is checked against the countries the platform recognises, so an\nunrecognised code is rejected with `400 Bad Request`. Values are uppercased,\nde-duplicated and sorted alphabetically on write, so the order in a response need not\nmatch the order sent, and on update the list is replaced wholesale rather than merged.\n\nAlways present in a response, but frequently `[]`, which is meaningful: an empty list\nmeans the price is not country-specific, not that its territory is unknown —\n`region_codes_included` carries the scope instead, typically as `[\"WORLD\"]`. When a\nrequest leaves both include lists empty, `region_codes_included` comes back as\n`[\"WORLD\"]`.\n\nFilterable on the collection endpoint, with equality and `__contains` only; see\n[Resource Queries](/resource_queries/).\n",
            "items": {
                "type": "string"
            },
            "example": [
                "DE",
                "AT"
            ]
        },
        "country_codes_excluded": {
            "type": "array",
            "description": "ISO 3166-1 alpha-2 country codes explicitly excluded from this price, validated and\nnormalised exactly as `country_codes_included` is. It carves countries out of the\nincluded set, which is what defines a broad price with country-level exceptions:\n`region_codes_included: [\"WORLD\"]` together with `country_codes_excluded: [\"DE\"]`\nmeans every country except Germany.\n\nAlways present in a response; `[]` means nothing is carved out. Not filterable.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "region_codes_included": {
            "type": "array",
            "description": "Region codes where this price applies, validated against ONIX\n[Codelist 49](https://ns.editeur.org/onix/en/49); a code outside that codelist is\nrejected with `400 Bad Request`. In practice `WORLD` is the only usable value, because\nthe platform's territory handling rejects every other Codelist 49 region. `ROW` is\naccepted as a synonym and comes back as `WORLD`.\n\n`[\"WORLD\"]` makes the price the fallback for every country that has no more specific\ncountry-level price, minus anything listed in `country_codes_excluded`. An empty list\nmeans the price is already scoped by `country_codes_included`; when both include lists\nare empty, `[\"WORLD\"]` comes back.\n\nValues are uppercased, de-duplicated and sorted on write. Always present in a response.\nFilterable on the collection endpoint, with equality and `__contains` only; see\n[Resource Queries](/resource_queries/).\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "region_codes_excluded": {
            "type": "array",
            "description": "Region codes explicitly excluded from this price. Always `[]` in practice: the\nplatform's territory handling does not support a non-empty value, so exclusions are\nexpressed through `country_codes_excluded` instead. Values are uppercased,\nde-duplicated and sorted on write. Always present in a response. Not filterable.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "effective_from": {
            "type": "string",
            "description": "The date from which this price becomes effective, in `YYYY-MM-DD` format. `null` means\nthe price has no start date and is effective from the beginning of time.\n\nPrices are grouped by product format, `qualifier_code`, territory and channel, and\nwithin one group the effective periods are expected to form a consistent timeline: no\ntwo periods overlap, and only the earliest price of the group leaves this field open.\nRegular and library prices should additionally form a *gapless* chain, each period\nstarting the day after the previous one ends, so that no date is left without a price.\nCampaign prices (`qualifier_code: \"08\"`) are the exception: each is an isolated window\nwhose first day is set here, and gaps between campaigns are normal.\n\nThis endpoint validates each price entry on its own and does not compare it with the\nother prices of the product, so an overlapping or gapped timeline is accepted on\nwrite.\n",
            "nullable": true,
            "example": "2026-08-06"
        },
        "effective_until": {
            "type": "string",
            "description": "The date until which this price remains effective, in `YYYY-MM-DD` format. The day\nitself is included. `null` means the price has no end date and remains effective\nindefinitely; only the latest price of a group leaves this field open.\n\nFor a campaign price (`qualifier_code: \"08\"`) this is the last day of the promotion.\nThe regular price applies again on the following day, without the regular entry being\nre-created or amended.\n",
            "nullable": true,
            "example": "2026-09-06"
        },
        "channels": {
            "type": "array",
            "description": "Distribution channel identifiers this price is scoped to. An empty list — the normal\ncase — means the price applies to every channel. Only library prices\n(`qualifier_code: \"06\"`) use it in practice, naming a lending platform; regular\nconsumer prices carry an empty list.\n\nValues are the platform's distribution-channel slugs, for example:\n\n- `divibib`\n- `overdrive`\n- `ciando`\n- `skoobe`\n- `libri`\n- `google`\n\nThey are not validated against that list — any string of lowercase letters and hyphens\nis accepted, so a typo silently produces a price no channel matches. Slugs containing\ndigits or underscores (`24symbols`, `manual_sale`, `zeitfracht_sa02e`) are valid\nchannels elsewhere in the API but are rejected here with `400 Bad Request`, because\nthis field allows only `[a-z-]`.\n\nThe list is lower-cased, de-duplicated and sorted alphabetically on write, so the order\nin a response need not match the order sent. On update it is replaced wholesale, never\nmerged. Always present in a response.\n\nFilterable on the collection endpoint; see\n[Resource Queries](/resource_queries/) for the operator syntax.\n",
            "items": {
                "type": "string"
            },
            "example": []
        },
        "price": {
            "type": "object",
            "description": "Monetary Amount",
            "additionalProperties": false,
            "nullable": true,
            "properties": {
                "value": {
                    "type": "number",
                    "description": "Value of the monetary amount",
                    "example": 42.23
                },
                "currency": {
                    "type": "string",
                    "description": "ISO 4217 currency code",
                    "example": "EUR"
                }
            },
            "required": [
                "value",
                "currency"
            ]
        }
    },
    "required": []
}

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

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

DELETE /resource/v4/products/{product_id}/prices/{id}

Delete product price

Description

Removes the price entry. No check is made that the remaining entries still cover the timeline.

Input parameters

Path parameters

Parameter Type Default Nullable Description
id string No
product_id string No

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 Price

Name Type Description Example
_id string

Unique identifier for the price.

949b0070-d590-4154-bbb9-961051f3f36a
_legacy_id integer

Legacy integer identifier of this resource, kept so existing integrations can correlate their own records.

38054816
_links object
_version string

API Version.

4.0
channels Array<string>

Distribution channel identifiers this price is scoped to. An empty list — the normal case — means the price applies to every channel. Only library prices (qualifier_code: "06") use it in practice, naming a lending platform; regular consumer prices carry an empty list.

Values are the platform's distribution-channel slugs, for example:

  • divibib
  • overdrive
  • ciando
  • skoobe
  • libri
  • google

They are not validated against that list — any string of lowercase letters and hyphens is accepted, so a typo silently produces a price no channel matches. Slugs containing digits or underscores (24symbols, manual_sale, zeitfracht_sa02e) are valid channels elsewhere in the API but are rejected here with 400 Bad Request, because this field allows only [a-z-].

The list is lower-cased, de-duplicated and sorted alphabetically on write, so the order in a response need not match the order sent. On update it is replaced wholesale, never merged. Always present in a response.

Filterable on the collection endpoint; see Resource Queries for the operator syntax.

[]
country_codes_excluded Array<string>

ISO 3166-1 alpha-2 country codes explicitly excluded from this price, validated and normalised exactly as country_codes_included is. It carves countries out of the included set, which is what defines a broad price with country-level exceptions: region_codes_included: ["WORLD"] together with country_codes_excluded: ["DE"] means every country except Germany.

Always present in a response; [] means nothing is carved out. Not filterable.

[]
country_codes_included Array<string>

ISO 3166-1 alpha-2 country codes where this price applies (e.g. "DE", "AT", "CH"). Each code is checked against the countries the platform recognises, so an unrecognised code is rejected with 400 Bad Request. Values are uppercased, de-duplicated and sorted alphabetically on write, so the order in a response need not match the order sent, and on update the list is replaced wholesale rather than merged.

Always present in a response, but frequently [], which is meaningful: an empty list means the price is not country-specific, not that its territory is unknown — region_codes_included carries the scope instead, typically as ["WORLD"]. When a request leaves both include lists empty, region_codes_included comes back as ["WORLD"].

Filterable on the collection endpoint, with equality and __contains only; see Resource Queries.

[ "DE", "AT" ]
description string| null

Human-readable label for the price entry, typically describing its purpose or origin. Examples from the book trade: "geb. Ladenpreis" (fixed retail price, gross), "Netto-Ladenpreis" (net retail price), "K-Lizenz" (quota license for libraries).

For campaign prices (qualifier_code: "08") this holds the promotion's name — "Summer campaign", "Christmas" — since it is the only field that distinguishes one campaign of a product from another.

geb. Ladenpreis
effective_from string| null

The date from which this price becomes effective, in YYYY-MM-DD format. null means the price has no start date and is effective from the beginning of time.

Prices are grouped by product format, qualifier_code, territory and channel, and within one group the effective periods are expected to form a consistent timeline: no two periods overlap, and only the earliest price of the group leaves this field open. Regular and library prices should additionally form a gapless chain, each period starting the day after the previous one ends, so that no date is left without a price. Campaign prices (qualifier_code: "08") are the exception: each is an isolated window whose first day is set here, and gaps between campaigns are normal.

This endpoint validates each price entry on its own and does not compare it with the other prices of the product, so an overlapping or gapped timeline is accepted on write.

2026-08-06
effective_until string| null

The date until which this price remains effective, in YYYY-MM-DD format. The day itself is included. null means the price has no end date and remains effective indefinitely; only the latest price of a group leaves this field open.

For a campaign price (qualifier_code: "08") this is the last day of the promotion. The regular price applies again on the following day, without the regular entry being re-created or amended.

2026-09-06
gross_price boolean

true if price.value is a gross price (tax included), false if it is a net price (tax excluded). Gross prices are typical for consumer-facing retail in countries with fixed book price laws; net prices are common for international or institutional sales, where the buyer settles tax itself. Together with price_type this decides which of the two ONIX Codelist 58 codes the entry corresponds to.

price Properties: value, currency| null

Monetary Amount

price.value number

Value of the monetary amount

42.23
price.currency string

ISO 4217 currency code

EUR
price_code string| null

A lowercase identifier for the pricing or licensing model, used primarily with library prices (qualifier_code: "06"). Lower-cased on write, and restricted to lowercase letters and hyphens ([a-z-]); anything else is rejected with 400 Bad Request. Common values in the book trade:

  • pay-per-loan — the library pays a fee each time the title is lent to a patron.
  • quota-license — the library purchases a fixed number of loans, or a time-limited licence for a set of concurrent users.

Regular consumer prices typically have price_code: null.

Filterable on the collection endpoint; see Resource Queries for the operator syntax.

price_status_code string| null

ONIX price status code from Codelist 61, stating how settled the price is. Any Codelist 61 code is accepted; anything else is rejected with 400 Bad Request. The codelist has three entries:

  • "00" — Unspecified. The codelist's own default.
  • "01" — Provisional: the price is not yet settled.
  • "02" — Confirmed: the price is settled and in effect. The common value.

null when no status is recorded.

02
price_type string| null

Whether this is a recommended retail price or a fixed (legally binding) retail price. One of two values, or null when the distinction is not applicable. Anything else is rejected with 400 Bad Request. Both correspond to a pair of codes in ONIX Codelist 58, one for a tax-exclusive and one for a tax-inclusive amount, which is the distinction gross_price records:

  • recommended — Recommended Retail Price (Codelist 58 01 excluding tax, 02 including tax). A retailer may sell at a different price.
  • fixed — Fixed Retail Price (Codelist 58 03 excluding tax, 04 including tax). The price is legally binding, for instance under German and Austrian fixed book price law (Buchpreisbindung).
fixed
product_type string

The product format this price applies to. Each format is priced independently, so a single product may carry separate ebook and book prices. One of four values:

  • ebook — e-book in any file format (EPUB, PDF, …)
  • book — physical book, including print-on-demand
  • audiobook — audiobook, downloadable or streaming
  • nonbook — non-book article

Always present in a response; a price cannot exist without a format. Values are lower-cased on write, so "Ebook" is accepted and comes back as ebook.

Filterable on the collection endpoint; see Resource Queries for the operator syntax.

ebook
qualifier_code string| null

ONIX price type qualifier from Codelist 59, identifying the audience or purpose of this price. null is an unqualified consumer price, equivalent to Codelist 59 code "00". Codelist 59 defines twenty codes, but only three qualifier modes are accepted; any other value is rejected with 400 Bad Request:

  • null — regular consumer price (retail).
  • "06" — corporate / library / education price, for sale to libraries, academic institutions or other corporate customers. The most common non-null qualifier, normally combined with a price_code and a channels entry.
  • "08" — promotional offer price: a time-limited campaign price bounded by effective_from and effective_until, and named in description.

A campaign does not shorten, modify or replace the regular price entry. The regular price simply stops being the effective one for the duration of the campaign and applies again the day after effective_until, without being re-created or amended. Unlike regular and library prices, campaigns need not tile the timeline without gaps, so a product may carry several unrelated campaign windows with ordinary pricing between them.

Filterable on the collection endpoint; see Resource Queries for the operator syntax. Because regular prices carry null, they are selected with ?qualifier_code__isnull and are not returned by ?qualifier_code__ne=08.

region_codes_excluded Array<string>

Region codes explicitly excluded from this price. Always [] in practice: the platform's territory handling does not support a non-empty value, so exclusions are expressed through country_codes_excluded instead. Values are uppercased, de-duplicated and sorted on write. Always present in a response. Not filterable.

[]
region_codes_included Array<string>

Region codes where this price applies, validated against ONIX Codelist 49; a code outside that codelist is rejected with 400 Bad Request. In practice WORLD is the only usable value, because the platform's territory handling rejects every other Codelist 49 region. ROW is accepted as a synonym and comes back as WORLD.

["WORLD"] makes the price the fallback for every country that has no more specific country-level price, minus anything listed in country_codes_excluded. An empty list means the price is already scoped by country_codes_included; when both include lists are empty, ["WORLD"] comes back.

Values are uppercased, de-duplicated and sorted on write. Always present in a response. Filterable on the collection endpoint, with equality and __contains only; see Resource Queries.

[]
vat_type string

How VAT (Value Added Tax) is determined for this price. One of four values; anything else is rejected with 400 Bad Request:

  • auto — VAT is calculated from the destination country, the product format (e-books and print books often carry different rates) and the date. The default, and the usual setting.
  • standard — the standard VAT rate is applied regardless of product format.
  • reduced — the reduced VAT rate is applied.
  • zero — no VAT is applied.

Always present in a response; auto applies when the request body omits the field.

auto