Product Suppliers v4
Sub-resource of products recording who a retailer orders a given product from, and whether it can be ordered right now. Each record names one supplier — a print-on-demand producer, a wholesaler, a distribution platform — and states that supplier's current stance on the title, how long delivery takes, and what one copy costs to manufacture.
A product carries at most one record per supplier.
Endpoints
GET /resource/v4/products/{product_id}/suppliers
Retrieve suppliers for a product
Description
Returns every supplier record for the given product, paginated. Most products have one record or none, so pagination rarely matters here.
name is the only filter. Every other field — status, available,
availability_days, production_costs — is rejected with 400 Bad Request.
Identifying the suppliers that can actually fulfil a title therefore means fetching
the records and testing available on each.
The query syntax name accepts is documented in
Resource Queries. Matching is case-insensitive, so ?name=BOD
and ?name=bod return the same record.
sort is accepted but ignored. Records always come back in the same order,
whatever is passed, so nothing on this endpoint is sortable. Page size is set with
display or page_size; a value below 1 is clamped to 1.
Input parameters
Path parameters
| Parameter | Type | Default | Nullable | Description |
|---|---|---|---|---|
product_id |
string | No | UUID of the product. The legacy integer product id also resolves. |
Query parameters
| Parameter | Type | Default | Nullable | Description |
|---|---|---|---|---|
display |
integer | No | Number of records per page. `page_size` is accepted as a synonym. Values below 1 are clamped to 1. | |
name |
string | No | Filter by supplier name (e.g. `bod`, `cpi`, `zeitfrachtbs`). Case-insensitive. This is the only field that can be filtered on this endpoint; for the operator syntax see [Resource Queries](/resource_queries/). | |
page |
integer | No | 1-based page number. Requesting a page past the end returns an empty `items` list rather than an error. |
Responses
{
"_links": {
"self": "https://api.openpublishing.com/resource/v4/products/a6fc09af-e616-11ee-a221-00505699575c/suppliers?page_size=100",
"first": "https://api.openpublishing.com/resource/v4/products/a6fc09af-e616-11ee-a221-00505699575c/suppliers?page=1&page_size=100",
"last": "https://api.openpublishing.com/resource/v4/products/a6fc09af-e616-11ee-a221-00505699575c/suppliers?page=1&page_size=100",
"next": null,
"prev": null
},
"_pagination": {
"page": 1,
"page_size": 100,
"total": 1
},
"items": [
{
"_id": "c37f2019-73a3-11f0-a221-00505699575c",
"_legacy_id": 3315,
"_version": "4.0",
"name": "bod",
"status": "pod",
"available": true,
"availability_days": null,
"production_costs": {
"value": 1.54,
"currency": "EUR"
}
}
]
}
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 supply relationship between a product and a supplier, identified by the supplier's name.",
"properties": {
"_id": {
"type": "string",
"description": "UUID of the supplier record. Note that this identifies the *record*, not the supplier — the supplier itself is identified by `name`.",
"example": "c37f2019-73a3-11f0-a221-00505699575c"
},
"_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\ncorrelate their own records. This is the value v3 returns as `_id`. Both forms work\nin the path: `.../suppliers/3315` and `.../suppliers/{uuid}` resolve to the same\nrecord on the v4 endpoint.\n",
"example": 3315
},
"name": {
"type": "string",
"description": "Which supplier this record is about, as a lowercase identifier. This is **not** an\nONIX code and not free text: it must match one of the suppliers the platform\nrecognises, and an unknown name is rejected with `400 Bad Request`. Matching is\ncase-insensitive on write and on the `name` filter, but the value is always returned\nlowercase.\n\nThe accepted names, grouped by the kind of partner they stand for:\n\n- **Print / print-on-demand producers and wholesalers** — `bod`, `cpi`, `lulu`,\n `brocom`, `knv`, `zeitfrachtbs`, `libri`, `agorando`. These are the names that\n matter for this sub-resource, because a print supplier is what makes a physical\n book orderable.\n- **E-book and audiobook retail or distribution platforms** — `amazon_kindle`,\n `google_play`, `itunes`, `ciando`, `libreka`, `bookwire`, `beam`, `ceebo`,\n `digitalstores`, `softdistribution`, `barnesnoble`, `tolino`.\n- **Library and academic lending platforms** — `overdrive`, `skoobe`, `proquest`,\n `genios`.\n- **Also accepted** — `grin`. Nothing in this API treats it differently from the\n other names.\n\nThere is no endpoint that lists these, and no way to register a new one through the\nAPI; the vocabulary is fixed by the platform, not by the caller.\n\nThere is no companion supplier-role field on this resource, so nothing here\ncorresponds to ONIX [Codelist 93](https://ns.editeur.org/onix/en/93).\n\nA product holds at most one record per supplier, so `name` is what tells one of a\nproduct's records from another. Changing a supplier's status is therefore a `PUT`\nagainst the existing record, not a second `POST` for the same `name`.\n",
"example": "bod"
},
"status": {
"type": "string",
"description": "What this supplier can currently do with the title. The vocabulary is specific to this\nAPI — five values, **not** [ONIX Codelist 65](https://ns.editeur.org/onix/en/65). The\nvalues listed below are the only ones accepted, and an ONIX availability code is not\none of them.\n\nThe permitted values, each with the `available` it produces:\n\n- `in_stock` — the supplier holds physical stock and ships immediately.\n `available: true`.\n- `pod` — the supplier manufactures a copy on demand for each order. Ordering works;\n delivery takes as long as `availability_days`. `available: true`.\n- `back_order` — the supplier cannot ship today but accepts the order and fulfils it\n later. `available: true`.\n- `out_of_stock` — the supplier has nothing to ship and is not accepting the order.\n `available: false`.\n- `withdrawn_from_pod` — print-on-demand production for this title has been stopped\n permanently, not as a temporary shortage. `available: false`.\n- `null` — no statement is made. The effect is the same as a refusal: the record does\n not make the title orderable. `available: false`.\n\nAny other value is rejected with `400 Bad Request`. `null` is accepted, and is the one\ncase where a record exists but contributes nothing — a supplier record with a `null`\nstatus has the same effect as no record at all.\n\n`pod` and `withdrawn_from_pod` are the pair that describes a print-on-demand\nrelationship, one value for each direction of the switch; the other three describe a\nsupplier's stock position — holding stock, taking back-orders, or having nothing to\nship.\n\nThe statement a `status` makes is global and open-ended. Unlike a price entry, a\nsupplier record carries no `country_codes_included` / `region_codes_included` and no\n`effective_from` / `effective_until`: a supplier either can supply the title or cannot,\neverywhere and from now on. Restricting a title to particular countries or particular\nperiods is done with prices and sales rights, never here.\n",
"nullable": true,
"example": "pod",
"x-enum-descriptions": {
"in_stock": "Supplier holds stock and ships immediately. Makes the title available.",
"pod": "Supplier produces a copy per order (print-on-demand). Makes the title available; delivery takes `availability_days`.",
"back_order": "Supplier cannot ship now but accepts the order for later fulfilment. Makes the title available.",
"out_of_stock": "Supplier has no stock and is not accepting orders. Does not make the title available.",
"withdrawn_from_pod": "Print-on-demand production has been permanently discontinued for this title. Does not make the title available."
}
},
"available": {
"type": "boolean",
"description": "Whether the title can be ordered from this supplier. **Read-only and derived** — the\nplatform recomputes it from `status` on every write (`true` for `in_stock`,\n`back_order` and `pod`; `false` for `out_of_stock`, `withdrawn_from_pod` and a `null`\nstatus), so it can never be out of step with `status` and cannot be set independently.\nSending it in a request body has no effect.\n\nIt is the field to test when the question is whether this supplier can fulfil the\ntitle; `status` says why.\n",
"readOnly": true,
"example": true
},
"availability_days": {
"type": "integer",
"description": "How many days the supplier needs before the title is with the customer — this\nsupplier's delivery estimate for this title. Whole days; there is no separate unit\nfield.\n\n`null` means no estimate is recorded, not \"immediately\", so zero should not be\ninferred from it. The field is most meaningful on `pod` records, where production time\nis the whole reason a customer waits.\n",
"nullable": true,
"example": 10
},
"production_costs": {
"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}/suppliers
Create a new product supplier
Description
Adds one supplier record to the product. name is required, as is status unless a
record that makes no statement is intended; the rest is optional. available is
ignored if sent — it is computed from status.
A product carries at most one record per supplier, so a second POST for a name
the product already has is not the way to change that supplier's status; a PUT
against the existing record is.
name has to be one of the suppliers the platform recognises, so a misspelt or
unaccepted name fails with 400 Bad Request rather than creating anything.
production_costs is what this supplier charges the publisher to manufacture
one copy — a unit cost, not an order total and not a customer-facing price. It
appears in the schema table below only as the shared Monetary Amount type, so its
meaning is recorded here:
valueis in major currency units.1.54means 1.54 EUR, not 1.54 cents.- It is rounded to whole minor units on write, so sub-cent precision is silently lost.
- This is a v3 → v4 change. v3 carried
production_costsas an integer of minor units, alongside a separateproduction_costs_currencyfield; v4 carries a single object ofvalueandcurrency. - The field is purely informational: it does not affect
available, pricing or fulfilment. nullwhen no cost is recorded, which is what a wholesaler carries, since a wholesaler does not manufacture.
The example below registers a print-on-demand producer for a paperback edition of Der unsignierte Teppich: available to order, ten days to the customer, and 1.54 EUR per copy to manufacture.
Input parameters
Path parameters
| Parameter | Type | Default | Nullable | Description |
|---|---|---|---|---|
product_id |
string | No | UUID of the product. |
Request body
{
"name": "bod",
"status": "pod",
"availability_days": 10,
"production_costs": {
"value": 1.54,
"currency": "EUR"
}
}
Schema of the request body
{
"type": "object",
"additionalProperties": false,
"description": "One supply relationship between a product and a supplier, identified by the supplier's name.",
"properties": {
"_id": {
"type": "string",
"description": "UUID of the supplier record. Note that this identifies the *record*, not the supplier — the supplier itself is identified by `name`.",
"example": "c37f2019-73a3-11f0-a221-00505699575c"
},
"_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\ncorrelate their own records. This is the value v3 returns as `_id`. Both forms work\nin the path: `.../suppliers/3315` and `.../suppliers/{uuid}` resolve to the same\nrecord on the v4 endpoint.\n",
"example": 3315
},
"name": {
"type": "string",
"description": "Which supplier this record is about, as a lowercase identifier. This is **not** an\nONIX code and not free text: it must match one of the suppliers the platform\nrecognises, and an unknown name is rejected with `400 Bad Request`. Matching is\ncase-insensitive on write and on the `name` filter, but the value is always returned\nlowercase.\n\nThe accepted names, grouped by the kind of partner they stand for:\n\n- **Print / print-on-demand producers and wholesalers** — `bod`, `cpi`, `lulu`,\n `brocom`, `knv`, `zeitfrachtbs`, `libri`, `agorando`. These are the names that\n matter for this sub-resource, because a print supplier is what makes a physical\n book orderable.\n- **E-book and audiobook retail or distribution platforms** — `amazon_kindle`,\n `google_play`, `itunes`, `ciando`, `libreka`, `bookwire`, `beam`, `ceebo`,\n `digitalstores`, `softdistribution`, `barnesnoble`, `tolino`.\n- **Library and academic lending platforms** — `overdrive`, `skoobe`, `proquest`,\n `genios`.\n- **Also accepted** — `grin`. Nothing in this API treats it differently from the\n other names.\n\nThere is no endpoint that lists these, and no way to register a new one through the\nAPI; the vocabulary is fixed by the platform, not by the caller.\n\nThere is no companion supplier-role field on this resource, so nothing here\ncorresponds to ONIX [Codelist 93](https://ns.editeur.org/onix/en/93).\n\nA product holds at most one record per supplier, so `name` is what tells one of a\nproduct's records from another. Changing a supplier's status is therefore a `PUT`\nagainst the existing record, not a second `POST` for the same `name`.\n",
"example": "bod"
},
"status": {
"type": "string",
"description": "What this supplier can currently do with the title. The vocabulary is specific to this\nAPI — five values, **not** [ONIX Codelist 65](https://ns.editeur.org/onix/en/65). The\nvalues listed below are the only ones accepted, and an ONIX availability code is not\none of them.\n\nThe permitted values, each with the `available` it produces:\n\n- `in_stock` — the supplier holds physical stock and ships immediately.\n `available: true`.\n- `pod` — the supplier manufactures a copy on demand for each order. Ordering works;\n delivery takes as long as `availability_days`. `available: true`.\n- `back_order` — the supplier cannot ship today but accepts the order and fulfils it\n later. `available: true`.\n- `out_of_stock` — the supplier has nothing to ship and is not accepting the order.\n `available: false`.\n- `withdrawn_from_pod` — print-on-demand production for this title has been stopped\n permanently, not as a temporary shortage. `available: false`.\n- `null` — no statement is made. The effect is the same as a refusal: the record does\n not make the title orderable. `available: false`.\n\nAny other value is rejected with `400 Bad Request`. `null` is accepted, and is the one\ncase where a record exists but contributes nothing — a supplier record with a `null`\nstatus has the same effect as no record at all.\n\n`pod` and `withdrawn_from_pod` are the pair that describes a print-on-demand\nrelationship, one value for each direction of the switch; the other three describe a\nsupplier's stock position — holding stock, taking back-orders, or having nothing to\nship.\n\nThe statement a `status` makes is global and open-ended. Unlike a price entry, a\nsupplier record carries no `country_codes_included` / `region_codes_included` and no\n`effective_from` / `effective_until`: a supplier either can supply the title or cannot,\neverywhere and from now on. Restricting a title to particular countries or particular\nperiods is done with prices and sales rights, never here.\n",
"nullable": true,
"example": "pod",
"x-enum-descriptions": {
"in_stock": "Supplier holds stock and ships immediately. Makes the title available.",
"pod": "Supplier produces a copy per order (print-on-demand). Makes the title available; delivery takes `availability_days`.",
"back_order": "Supplier cannot ship now but accepts the order for later fulfilment. Makes the title available.",
"out_of_stock": "Supplier has no stock and is not accepting orders. Does not make the title available.",
"withdrawn_from_pod": "Print-on-demand production has been permanently discontinued for this title. Does not make the title available."
}
},
"available": {
"type": "boolean",
"description": "Whether the title can be ordered from this supplier. **Read-only and derived** — the\nplatform recomputes it from `status` on every write (`true` for `in_stock`,\n`back_order` and `pod`; `false` for `out_of_stock`, `withdrawn_from_pod` and a `null`\nstatus), so it can never be out of step with `status` and cannot be set independently.\nSending it in a request body has no effect.\n\nIt is the field to test when the question is whether this supplier can fulfil the\ntitle; `status` says why.\n",
"readOnly": true,
"example": true
},
"availability_days": {
"type": "integer",
"description": "How many days the supplier needs before the title is with the customer — this\nsupplier's delivery estimate for this title. Whole days; there is no separate unit\nfield.\n\n`null` means no estimate is recorded, not \"immediately\", so zero should not be\ninferred from it. The field is most meaningful on `pod` records, where production time\nis the whole reason a customer waits.\n",
"nullable": true,
"example": 10
},
"production_costs": {
"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": "c37f2019-73a3-11f0-a221-00505699575c",
"_legacy_id": 3315,
"_version": "4.0",
"name": "bod",
"status": "pod",
"available": true,
"availability_days": 10,
"production_costs": {
"value": 1.54,
"currency": "EUR"
}
}
Schema of the response body
{
"type": "object",
"additionalProperties": false,
"description": "One supply relationship between a product and a supplier, identified by the supplier's name.",
"properties": {
"_id": {
"type": "string",
"description": "UUID of the supplier record. Note that this identifies the *record*, not the supplier — the supplier itself is identified by `name`.",
"example": "c37f2019-73a3-11f0-a221-00505699575c"
},
"_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\ncorrelate their own records. This is the value v3 returns as `_id`. Both forms work\nin the path: `.../suppliers/3315` and `.../suppliers/{uuid}` resolve to the same\nrecord on the v4 endpoint.\n",
"example": 3315
},
"name": {
"type": "string",
"description": "Which supplier this record is about, as a lowercase identifier. This is **not** an\nONIX code and not free text: it must match one of the suppliers the platform\nrecognises, and an unknown name is rejected with `400 Bad Request`. Matching is\ncase-insensitive on write and on the `name` filter, but the value is always returned\nlowercase.\n\nThe accepted names, grouped by the kind of partner they stand for:\n\n- **Print / print-on-demand producers and wholesalers** — `bod`, `cpi`, `lulu`,\n `brocom`, `knv`, `zeitfrachtbs`, `libri`, `agorando`. These are the names that\n matter for this sub-resource, because a print supplier is what makes a physical\n book orderable.\n- **E-book and audiobook retail or distribution platforms** — `amazon_kindle`,\n `google_play`, `itunes`, `ciando`, `libreka`, `bookwire`, `beam`, `ceebo`,\n `digitalstores`, `softdistribution`, `barnesnoble`, `tolino`.\n- **Library and academic lending platforms** — `overdrive`, `skoobe`, `proquest`,\n `genios`.\n- **Also accepted** — `grin`. Nothing in this API treats it differently from the\n other names.\n\nThere is no endpoint that lists these, and no way to register a new one through the\nAPI; the vocabulary is fixed by the platform, not by the caller.\n\nThere is no companion supplier-role field on this resource, so nothing here\ncorresponds to ONIX [Codelist 93](https://ns.editeur.org/onix/en/93).\n\nA product holds at most one record per supplier, so `name` is what tells one of a\nproduct's records from another. Changing a supplier's status is therefore a `PUT`\nagainst the existing record, not a second `POST` for the same `name`.\n",
"example": "bod"
},
"status": {
"type": "string",
"description": "What this supplier can currently do with the title. The vocabulary is specific to this\nAPI — five values, **not** [ONIX Codelist 65](https://ns.editeur.org/onix/en/65). The\nvalues listed below are the only ones accepted, and an ONIX availability code is not\none of them.\n\nThe permitted values, each with the `available` it produces:\n\n- `in_stock` — the supplier holds physical stock and ships immediately.\n `available: true`.\n- `pod` — the supplier manufactures a copy on demand for each order. Ordering works;\n delivery takes as long as `availability_days`. `available: true`.\n- `back_order` — the supplier cannot ship today but accepts the order and fulfils it\n later. `available: true`.\n- `out_of_stock` — the supplier has nothing to ship and is not accepting the order.\n `available: false`.\n- `withdrawn_from_pod` — print-on-demand production for this title has been stopped\n permanently, not as a temporary shortage. `available: false`.\n- `null` — no statement is made. The effect is the same as a refusal: the record does\n not make the title orderable. `available: false`.\n\nAny other value is rejected with `400 Bad Request`. `null` is accepted, and is the one\ncase where a record exists but contributes nothing — a supplier record with a `null`\nstatus has the same effect as no record at all.\n\n`pod` and `withdrawn_from_pod` are the pair that describes a print-on-demand\nrelationship, one value for each direction of the switch; the other three describe a\nsupplier's stock position — holding stock, taking back-orders, or having nothing to\nship.\n\nThe statement a `status` makes is global and open-ended. Unlike a price entry, a\nsupplier record carries no `country_codes_included` / `region_codes_included` and no\n`effective_from` / `effective_until`: a supplier either can supply the title or cannot,\neverywhere and from now on. Restricting a title to particular countries or particular\nperiods is done with prices and sales rights, never here.\n",
"nullable": true,
"example": "pod",
"x-enum-descriptions": {
"in_stock": "Supplier holds stock and ships immediately. Makes the title available.",
"pod": "Supplier produces a copy per order (print-on-demand). Makes the title available; delivery takes `availability_days`.",
"back_order": "Supplier cannot ship now but accepts the order for later fulfilment. Makes the title available.",
"out_of_stock": "Supplier has no stock and is not accepting orders. Does not make the title available.",
"withdrawn_from_pod": "Print-on-demand production has been permanently discontinued for this title. Does not make the title available."
}
},
"available": {
"type": "boolean",
"description": "Whether the title can be ordered from this supplier. **Read-only and derived** — the\nplatform recomputes it from `status` on every write (`true` for `in_stock`,\n`back_order` and `pod`; `false` for `out_of_stock`, `withdrawn_from_pod` and a `null`\nstatus), so it can never be out of step with `status` and cannot be set independently.\nSending it in a request body has no effect.\n\nIt is the field to test when the question is whether this supplier can fulfil the\ntitle; `status` says why.\n",
"readOnly": true,
"example": true
},
"availability_days": {
"type": "integer",
"description": "How many days the supplier needs before the title is with the customer — this\nsupplier's delivery estimate for this title. Whole days; there is no separate unit\nfield.\n\n`null` means no estimate is recorded, not \"immediately\", so zero should not be\ninferred from it. The field is most meaningful on `pod` records, where production time\nis the whole reason a customer waits.\n",
"nullable": true,
"example": 10
},
"production_costs": {
"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": {}
}
]
}
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}/suppliers/{id}
Retrieve a single product supplier
Description
Returns one supplier record. The example is a title whose print-on-demand production
has been discontinued: status: "withdrawn_from_pod" and therefore
available: false. The corresponding price entries are usually still present and
still valid, because prices and supply are separate sub-resources, so an unavailable
supplier record is not visible in the product's prices.
A record that belongs to a different product returns 403, not 404 — the id exists, it just is not this product's. A genuinely unknown id returns 404.
Input parameters
Path parameters
| Parameter | Type | Default | Nullable | Description |
|---|---|---|---|---|
id |
string | No | UUID of the supplier record. The legacy integer id (returned as `_legacy_id`) also resolves on this endpoint. | |
product_id |
string | No | UUID of the product. |
Responses
{
"_id": "c54ae3cd-73a3-11f0-a221-00505699575c",
"_legacy_id": 66671,
"_version": "4.0",
"name": "bod",
"status": "withdrawn_from_pod",
"available": false,
"availability_days": null,
"production_costs": null
}
Schema of the response body
{
"type": "object",
"additionalProperties": false,
"description": "One supply relationship between a product and a supplier, identified by the supplier's name.",
"properties": {
"_id": {
"type": "string",
"description": "UUID of the supplier record. Note that this identifies the *record*, not the supplier — the supplier itself is identified by `name`.",
"example": "c37f2019-73a3-11f0-a221-00505699575c"
},
"_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\ncorrelate their own records. This is the value v3 returns as `_id`. Both forms work\nin the path: `.../suppliers/3315` and `.../suppliers/{uuid}` resolve to the same\nrecord on the v4 endpoint.\n",
"example": 3315
},
"name": {
"type": "string",
"description": "Which supplier this record is about, as a lowercase identifier. This is **not** an\nONIX code and not free text: it must match one of the suppliers the platform\nrecognises, and an unknown name is rejected with `400 Bad Request`. Matching is\ncase-insensitive on write and on the `name` filter, but the value is always returned\nlowercase.\n\nThe accepted names, grouped by the kind of partner they stand for:\n\n- **Print / print-on-demand producers and wholesalers** — `bod`, `cpi`, `lulu`,\n `brocom`, `knv`, `zeitfrachtbs`, `libri`, `agorando`. These are the names that\n matter for this sub-resource, because a print supplier is what makes a physical\n book orderable.\n- **E-book and audiobook retail or distribution platforms** — `amazon_kindle`,\n `google_play`, `itunes`, `ciando`, `libreka`, `bookwire`, `beam`, `ceebo`,\n `digitalstores`, `softdistribution`, `barnesnoble`, `tolino`.\n- **Library and academic lending platforms** — `overdrive`, `skoobe`, `proquest`,\n `genios`.\n- **Also accepted** — `grin`. Nothing in this API treats it differently from the\n other names.\n\nThere is no endpoint that lists these, and no way to register a new one through the\nAPI; the vocabulary is fixed by the platform, not by the caller.\n\nThere is no companion supplier-role field on this resource, so nothing here\ncorresponds to ONIX [Codelist 93](https://ns.editeur.org/onix/en/93).\n\nA product holds at most one record per supplier, so `name` is what tells one of a\nproduct's records from another. Changing a supplier's status is therefore a `PUT`\nagainst the existing record, not a second `POST` for the same `name`.\n",
"example": "bod"
},
"status": {
"type": "string",
"description": "What this supplier can currently do with the title. The vocabulary is specific to this\nAPI — five values, **not** [ONIX Codelist 65](https://ns.editeur.org/onix/en/65). The\nvalues listed below are the only ones accepted, and an ONIX availability code is not\none of them.\n\nThe permitted values, each with the `available` it produces:\n\n- `in_stock` — the supplier holds physical stock and ships immediately.\n `available: true`.\n- `pod` — the supplier manufactures a copy on demand for each order. Ordering works;\n delivery takes as long as `availability_days`. `available: true`.\n- `back_order` — the supplier cannot ship today but accepts the order and fulfils it\n later. `available: true`.\n- `out_of_stock` — the supplier has nothing to ship and is not accepting the order.\n `available: false`.\n- `withdrawn_from_pod` — print-on-demand production for this title has been stopped\n permanently, not as a temporary shortage. `available: false`.\n- `null` — no statement is made. The effect is the same as a refusal: the record does\n not make the title orderable. `available: false`.\n\nAny other value is rejected with `400 Bad Request`. `null` is accepted, and is the one\ncase where a record exists but contributes nothing — a supplier record with a `null`\nstatus has the same effect as no record at all.\n\n`pod` and `withdrawn_from_pod` are the pair that describes a print-on-demand\nrelationship, one value for each direction of the switch; the other three describe a\nsupplier's stock position — holding stock, taking back-orders, or having nothing to\nship.\n\nThe statement a `status` makes is global and open-ended. Unlike a price entry, a\nsupplier record carries no `country_codes_included` / `region_codes_included` and no\n`effective_from` / `effective_until`: a supplier either can supply the title or cannot,\neverywhere and from now on. Restricting a title to particular countries or particular\nperiods is done with prices and sales rights, never here.\n",
"nullable": true,
"example": "pod",
"x-enum-descriptions": {
"in_stock": "Supplier holds stock and ships immediately. Makes the title available.",
"pod": "Supplier produces a copy per order (print-on-demand). Makes the title available; delivery takes `availability_days`.",
"back_order": "Supplier cannot ship now but accepts the order for later fulfilment. Makes the title available.",
"out_of_stock": "Supplier has no stock and is not accepting orders. Does not make the title available.",
"withdrawn_from_pod": "Print-on-demand production has been permanently discontinued for this title. Does not make the title available."
}
},
"available": {
"type": "boolean",
"description": "Whether the title can be ordered from this supplier. **Read-only and derived** — the\nplatform recomputes it from `status` on every write (`true` for `in_stock`,\n`back_order` and `pod`; `false` for `out_of_stock`, `withdrawn_from_pod` and a `null`\nstatus), so it can never be out of step with `status` and cannot be set independently.\nSending it in a request body has no effect.\n\nIt is the field to test when the question is whether this supplier can fulfil the\ntitle; `status` says why.\n",
"readOnly": true,
"example": true
},
"availability_days": {
"type": "integer",
"description": "How many days the supplier needs before the title is with the customer — this\nsupplier's delivery estimate for this title. Whole days; there is no separate unit\nfield.\n\n`null` means no estimate is recorded, not \"immediately\", so zero should not be\ninferred from it. The field is most meaningful on `pod` records, where production time\nis the whole reason a customer waits.\n",
"nullable": true,
"example": 10
},
"production_costs": {
"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}/suppliers/{id}
Update product supplier
Description
Updates one supplier record. Despite being a PUT, this is a partial update:
only the fields present in the body are applied. Every omitted field keeps its
current value, so {} changes nothing, and a field cannot be cleared by leaving it
out — send an explicit null.
available is not writable. It is recomputed from status as the update is applied,
so changing status changes available in the same response.
Note that name is writable: sending a different supplier name repoints this
record at another supplier rather than creating a second one. That is rarely the
intention — deleting the record and creating a new one is the clearer expression of
a change of supplier.
The example takes the title off sale at this supplier by discontinuing
print-on-demand production. Only status is sent; availability_days and
production_costs are left as they were, and available flips to false on its
own.
Input parameters
Path parameters
| Parameter | Type | Default | Nullable | Description |
|---|---|---|---|---|
id |
string | No | UUID or legacy integer id of the supplier record. | |
product_id |
string | No | UUID of the product. |
Request body
{
"status": "withdrawn_from_pod"
}
Schema of the request body
{
"type": "object",
"additionalProperties": false,
"description": "One supply relationship between a product and a supplier, identified by the supplier's name.",
"properties": {
"_id": {
"type": "string",
"description": "UUID of the supplier record. Note that this identifies the *record*, not the supplier — the supplier itself is identified by `name`.",
"example": "c37f2019-73a3-11f0-a221-00505699575c"
},
"_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\ncorrelate their own records. This is the value v3 returns as `_id`. Both forms work\nin the path: `.../suppliers/3315` and `.../suppliers/{uuid}` resolve to the same\nrecord on the v4 endpoint.\n",
"example": 3315
},
"name": {
"type": "string",
"description": "Which supplier this record is about, as a lowercase identifier. This is **not** an\nONIX code and not free text: it must match one of the suppliers the platform\nrecognises, and an unknown name is rejected with `400 Bad Request`. Matching is\ncase-insensitive on write and on the `name` filter, but the value is always returned\nlowercase.\n\nThe accepted names, grouped by the kind of partner they stand for:\n\n- **Print / print-on-demand producers and wholesalers** — `bod`, `cpi`, `lulu`,\n `brocom`, `knv`, `zeitfrachtbs`, `libri`, `agorando`. These are the names that\n matter for this sub-resource, because a print supplier is what makes a physical\n book orderable.\n- **E-book and audiobook retail or distribution platforms** — `amazon_kindle`,\n `google_play`, `itunes`, `ciando`, `libreka`, `bookwire`, `beam`, `ceebo`,\n `digitalstores`, `softdistribution`, `barnesnoble`, `tolino`.\n- **Library and academic lending platforms** — `overdrive`, `skoobe`, `proquest`,\n `genios`.\n- **Also accepted** — `grin`. Nothing in this API treats it differently from the\n other names.\n\nThere is no endpoint that lists these, and no way to register a new one through the\nAPI; the vocabulary is fixed by the platform, not by the caller.\n\nThere is no companion supplier-role field on this resource, so nothing here\ncorresponds to ONIX [Codelist 93](https://ns.editeur.org/onix/en/93).\n\nA product holds at most one record per supplier, so `name` is what tells one of a\nproduct's records from another. Changing a supplier's status is therefore a `PUT`\nagainst the existing record, not a second `POST` for the same `name`.\n",
"example": "bod"
},
"status": {
"type": "string",
"description": "What this supplier can currently do with the title. The vocabulary is specific to this\nAPI — five values, **not** [ONIX Codelist 65](https://ns.editeur.org/onix/en/65). The\nvalues listed below are the only ones accepted, and an ONIX availability code is not\none of them.\n\nThe permitted values, each with the `available` it produces:\n\n- `in_stock` — the supplier holds physical stock and ships immediately.\n `available: true`.\n- `pod` — the supplier manufactures a copy on demand for each order. Ordering works;\n delivery takes as long as `availability_days`. `available: true`.\n- `back_order` — the supplier cannot ship today but accepts the order and fulfils it\n later. `available: true`.\n- `out_of_stock` — the supplier has nothing to ship and is not accepting the order.\n `available: false`.\n- `withdrawn_from_pod` — print-on-demand production for this title has been stopped\n permanently, not as a temporary shortage. `available: false`.\n- `null` — no statement is made. The effect is the same as a refusal: the record does\n not make the title orderable. `available: false`.\n\nAny other value is rejected with `400 Bad Request`. `null` is accepted, and is the one\ncase where a record exists but contributes nothing — a supplier record with a `null`\nstatus has the same effect as no record at all.\n\n`pod` and `withdrawn_from_pod` are the pair that describes a print-on-demand\nrelationship, one value for each direction of the switch; the other three describe a\nsupplier's stock position — holding stock, taking back-orders, or having nothing to\nship.\n\nThe statement a `status` makes is global and open-ended. Unlike a price entry, a\nsupplier record carries no `country_codes_included` / `region_codes_included` and no\n`effective_from` / `effective_until`: a supplier either can supply the title or cannot,\neverywhere and from now on. Restricting a title to particular countries or particular\nperiods is done with prices and sales rights, never here.\n",
"nullable": true,
"example": "pod",
"x-enum-descriptions": {
"in_stock": "Supplier holds stock and ships immediately. Makes the title available.",
"pod": "Supplier produces a copy per order (print-on-demand). Makes the title available; delivery takes `availability_days`.",
"back_order": "Supplier cannot ship now but accepts the order for later fulfilment. Makes the title available.",
"out_of_stock": "Supplier has no stock and is not accepting orders. Does not make the title available.",
"withdrawn_from_pod": "Print-on-demand production has been permanently discontinued for this title. Does not make the title available."
}
},
"available": {
"type": "boolean",
"description": "Whether the title can be ordered from this supplier. **Read-only and derived** — the\nplatform recomputes it from `status` on every write (`true` for `in_stock`,\n`back_order` and `pod`; `false` for `out_of_stock`, `withdrawn_from_pod` and a `null`\nstatus), so it can never be out of step with `status` and cannot be set independently.\nSending it in a request body has no effect.\n\nIt is the field to test when the question is whether this supplier can fulfil the\ntitle; `status` says why.\n",
"readOnly": true,
"example": true
},
"availability_days": {
"type": "integer",
"description": "How many days the supplier needs before the title is with the customer — this\nsupplier's delivery estimate for this title. Whole days; there is no separate unit\nfield.\n\n`null` means no estimate is recorded, not \"immediately\", so zero should not be\ninferred from it. The field is most meaningful on `pod` records, where production time\nis the whole reason a customer waits.\n",
"nullable": true,
"example": 10
},
"production_costs": {
"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": "c37f2019-73a3-11f0-a221-00505699575c",
"_legacy_id": 3315,
"_version": "4.0",
"name": "bod",
"status": "withdrawn_from_pod",
"available": false,
"availability_days": 10,
"production_costs": {
"value": 1.54,
"currency": "EUR"
}
}
Schema of the response body
{
"type": "object",
"additionalProperties": false,
"description": "One supply relationship between a product and a supplier, identified by the supplier's name.",
"properties": {
"_id": {
"type": "string",
"description": "UUID of the supplier record. Note that this identifies the *record*, not the supplier — the supplier itself is identified by `name`.",
"example": "c37f2019-73a3-11f0-a221-00505699575c"
},
"_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\ncorrelate their own records. This is the value v3 returns as `_id`. Both forms work\nin the path: `.../suppliers/3315` and `.../suppliers/{uuid}` resolve to the same\nrecord on the v4 endpoint.\n",
"example": 3315
},
"name": {
"type": "string",
"description": "Which supplier this record is about, as a lowercase identifier. This is **not** an\nONIX code and not free text: it must match one of the suppliers the platform\nrecognises, and an unknown name is rejected with `400 Bad Request`. Matching is\ncase-insensitive on write and on the `name` filter, but the value is always returned\nlowercase.\n\nThe accepted names, grouped by the kind of partner they stand for:\n\n- **Print / print-on-demand producers and wholesalers** — `bod`, `cpi`, `lulu`,\n `brocom`, `knv`, `zeitfrachtbs`, `libri`, `agorando`. These are the names that\n matter for this sub-resource, because a print supplier is what makes a physical\n book orderable.\n- **E-book and audiobook retail or distribution platforms** — `amazon_kindle`,\n `google_play`, `itunes`, `ciando`, `libreka`, `bookwire`, `beam`, `ceebo`,\n `digitalstores`, `softdistribution`, `barnesnoble`, `tolino`.\n- **Library and academic lending platforms** — `overdrive`, `skoobe`, `proquest`,\n `genios`.\n- **Also accepted** — `grin`. Nothing in this API treats it differently from the\n other names.\n\nThere is no endpoint that lists these, and no way to register a new one through the\nAPI; the vocabulary is fixed by the platform, not by the caller.\n\nThere is no companion supplier-role field on this resource, so nothing here\ncorresponds to ONIX [Codelist 93](https://ns.editeur.org/onix/en/93).\n\nA product holds at most one record per supplier, so `name` is what tells one of a\nproduct's records from another. Changing a supplier's status is therefore a `PUT`\nagainst the existing record, not a second `POST` for the same `name`.\n",
"example": "bod"
},
"status": {
"type": "string",
"description": "What this supplier can currently do with the title. The vocabulary is specific to this\nAPI — five values, **not** [ONIX Codelist 65](https://ns.editeur.org/onix/en/65). The\nvalues listed below are the only ones accepted, and an ONIX availability code is not\none of them.\n\nThe permitted values, each with the `available` it produces:\n\n- `in_stock` — the supplier holds physical stock and ships immediately.\n `available: true`.\n- `pod` — the supplier manufactures a copy on demand for each order. Ordering works;\n delivery takes as long as `availability_days`. `available: true`.\n- `back_order` — the supplier cannot ship today but accepts the order and fulfils it\n later. `available: true`.\n- `out_of_stock` — the supplier has nothing to ship and is not accepting the order.\n `available: false`.\n- `withdrawn_from_pod` — print-on-demand production for this title has been stopped\n permanently, not as a temporary shortage. `available: false`.\n- `null` — no statement is made. The effect is the same as a refusal: the record does\n not make the title orderable. `available: false`.\n\nAny other value is rejected with `400 Bad Request`. `null` is accepted, and is the one\ncase where a record exists but contributes nothing — a supplier record with a `null`\nstatus has the same effect as no record at all.\n\n`pod` and `withdrawn_from_pod` are the pair that describes a print-on-demand\nrelationship, one value for each direction of the switch; the other three describe a\nsupplier's stock position — holding stock, taking back-orders, or having nothing to\nship.\n\nThe statement a `status` makes is global and open-ended. Unlike a price entry, a\nsupplier record carries no `country_codes_included` / `region_codes_included` and no\n`effective_from` / `effective_until`: a supplier either can supply the title or cannot,\neverywhere and from now on. Restricting a title to particular countries or particular\nperiods is done with prices and sales rights, never here.\n",
"nullable": true,
"example": "pod",
"x-enum-descriptions": {
"in_stock": "Supplier holds stock and ships immediately. Makes the title available.",
"pod": "Supplier produces a copy per order (print-on-demand). Makes the title available; delivery takes `availability_days`.",
"back_order": "Supplier cannot ship now but accepts the order for later fulfilment. Makes the title available.",
"out_of_stock": "Supplier has no stock and is not accepting orders. Does not make the title available.",
"withdrawn_from_pod": "Print-on-demand production has been permanently discontinued for this title. Does not make the title available."
}
},
"available": {
"type": "boolean",
"description": "Whether the title can be ordered from this supplier. **Read-only and derived** — the\nplatform recomputes it from `status` on every write (`true` for `in_stock`,\n`back_order` and `pod`; `false` for `out_of_stock`, `withdrawn_from_pod` and a `null`\nstatus), so it can never be out of step with `status` and cannot be set independently.\nSending it in a request body has no effect.\n\nIt is the field to test when the question is whether this supplier can fulfil the\ntitle; `status` says why.\n",
"readOnly": true,
"example": true
},
"availability_days": {
"type": "integer",
"description": "How many days the supplier needs before the title is with the customer — this\nsupplier's delivery estimate for this title. Whole days; there is no separate unit\nfield.\n\n`null` means no estimate is recorded, not \"immediately\", so zero should not be\ninferred from it. The field is most meaningful on `pod` records, where production time\nis the whole reason a customer waits.\n",
"nullable": true,
"example": 10
},
"production_costs": {
"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": {}
}
]
}
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}/suppliers/{id}
Delete product supplier
Description
Removes the supply relationship outright, taking with it the record that this supplier ever handled the title.
A PUT with status: "out_of_stock" for a temporary shortage, or
status: "withdrawn_from_pod" for a permanent stop, is the less destructive option:
both set available to false while keeping the relationship on record, and both
are reversible. DELETE is for the case where the relationship itself was recorded
in error.
Input parameters
Path parameters
| Parameter | Type | Default | Nullable | Description |
|---|---|---|---|---|
id |
string | No | UUID or legacy integer id of the supplier record. | |
product_id |
string | No | UUID of the product. |
Responses
{
"errors": [
{
"_id": "string",
"stack_trace": [
"string"
],
"message": "string",
"vars": {}
}
]
}
Schema of the response body
{
"type": "object",
"additionalProperties": false,
"required": [
"errors"
],
"properties": {
"errors": {
"type": "array",
"description": "List of errors occurred",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"_id",
"message",
"stack_trace",
"vars"
],
"properties": {
"_id": {
"type": "string",
"description": "The identifier of the error"
},
"stack_trace": {
"type": "array",
"description": "A stacktrace of the error occurred. Only for testing systems.",
"items": {
"type": "string"
}
},
"message": {
"type": "string",
"description": "A translated message of the error"
},
"vars": {
"type": "object",
"description": "A dict containing additional values of the context of the error."
}
}
}
}
}
}
Schemas
Product Supplier
| Name | Type | Description | Example |
|---|---|---|---|
_id |
string |
UUID of the supplier record. Note that this identifies the record, not the supplier — the supplier itself is identified by |
c37f2019-73a3-11f0-a221-00505699575c |
_legacy_id |
integer |
Legacy integer identifier of this resource, kept so existing integrations can
correlate their own records. This is the value v3 returns as |
3315 |
_version |
string |
API Version |
4.0 |
availability_days |
integer| null |
How many days the supplier needs before the title is with the customer — this supplier's delivery estimate for this title. Whole days; there is no separate unit field.
|
10 |
available |
boolean |
Whether the title can be ordered from this supplier. Read-only and derived — the
platform recomputes it from It is the field to test when the question is whether this supplier can fulfil the
title; |
true |
name |
string |
Which supplier this record is about, as a lowercase identifier. This is not an
ONIX code and not free text: it must match one of the suppliers the platform
recognises, and an unknown name is rejected with The accepted names, grouped by the kind of partner they stand for:
There is no endpoint that lists these, and no way to register a new one through the API; the vocabulary is fixed by the platform, not by the caller. There is no companion supplier-role field on this resource, so nothing here corresponds to ONIX Codelist 93. A product holds at most one record per supplier, so |
bod |
production_costs |
Properties: value, currency| null |
Monetary Amount |
|
production_costs.value |
number |
Value of the monetary amount |
42.23 |
production_costs.currency |
string |
ISO 4217 currency code |
EUR |
status |
string| null |
What this supplier can currently do with the title. The vocabulary is specific to this API — five values, not ONIX Codelist 65. The values listed below are the only ones accepted, and an ONIX availability code is not one of them. The permitted values, each with the
Any other value is rejected with
The statement a |
pod |