Retrieve details and pricing for a specific product — its price fields as on GET /v1/products: the list price (our price book's, for a product of our own plant's that the book prices), the same on every plan since there is no platform fee — from the catalog of the facility whose capabilities cover it (a sticker SKU is answered by the flat-goods facility; apparel by the default facility). Pass provider to ask one facility's catalog explicitly. Returns 404 when that catalog has no such SKU — 'does this SKU exist?' is answerable here before ordering. When the facility gives no answer about the SKU (5xx, a refused credential, a timeout, a rate limit, or an open circuit), the row is served out of the last-good snapshot with X-Catalog-Stale: true, as GET /v1/products does — so the list and this read agree during an outage. A SKU the snapshot does not hold is a 502, never a 404: a stale list missing a row says nothing about a product added since. On our own plant's catalog, a SKU our price book lists is served from the book instead, as the list serves it (source: "price_book", X-Catalog-Stale: true), and so is another size of a product it lists, spelled as the list spells it (sticker-die-cut-2x2 beside the listed 3×3), which a test key's quote and order take at $0 while the plant does not answer. A SKU our catalog lists (a sticker's, a wall decal's, a poster's or a magnet's, in any canonical spelling of a row or of its shapes and finishes) is never a 404: where our plant's catalog has no row for it, it is the catalog's row under the SKU's canonical spelling, source: "listed", with not_orderable_code: plant_unmapped; where the plant gives no answer, the same row, stale. A stale answer is sent Cache-Control: private, no-store, as on the list.
- Credential
- An API key in
x-api-key - Scopes
products:read
Parameters
skupath · stringrequiredProduct SKU identifier
providerquery · stringWhich catalog to ask:
printedsimply, our own plant's. It is the default and the only value; anything else is a 400.Always
printedsimplyDefault printedsimply
Request
GET /api/v1/products/{sku}
curl "https://printedsimply.com/api/v1/products/sticker-die-cut-3x3" \
-H "x-api-key: ps_test_YOUR_KEY"Response 200
The catalog row for this SKU. data is the facility's own row, unwrapped — the same shape as one element of GET /v1/products, and open for the same reason. Note that category appears here only if the facility publishes one: the derivation that fills it runs on the list, not on this route. May have been served out of the last-good snapshot — read X-Catalog-Stale, which is sent both ways; a stale row from the local synced catalog carries only sku, name and priceCents, and one from our price book sku, name, base_price, category and source: "price_book" (with alt_skus where the book knows the product by another SKU too). Every row our own plant makes, whatever its source, also carries size_in, finish, family, minimum_quantity, quantity_breaks, price_interpolation, min_unit_cents, material, options, orderable, not_orderable_code and not_orderable_reason, as on the list; a row the catalog built carries sku, name, base_price, category and source: "listed".
data
fulfillment_cost_basisstringApproved per-size US standard shipping benchmark; actual postage, manufacturing cost and profit remain unknown.
Always
approved_size_shipping_benchmarkgoods_cost_centsanyActual manufacturing cost is unknown, never represented by zero.
manufacturing_cost_centsanyactual_contribution_centsanymaximum_quantityintegerOne product, one SKU, quantity 1 in its own order.
Always
1shipping_constraintsobjectshipping_constraints.destination_countrystringrequiredAlways
USshipping_constraints.methodstringrequiredAlways
standardshipping_constraints.separate_orderbooleanrequiredAlways
trueshipping_benchmark_centsintegerThe approved quantity-1 US standard benchmark, packing included.
at least 0
packagingstringAlways
included_in_shipping_benchmarkartwork_layoutstringAlways
prepared_combined_allartwork_instructionsstringPrepare one combined ALL PNG: flag front and back, or Santa hat front and band, according to the exact guide; remove guide lines.
artwork_guide_urlstringLocal path to the layout guide SVG. This reference is not customer artwork and must not be sent as print_file.
skustring | numberThe identifier every other operation takes: an order item's
sku, the{sku}path parameter, a recommendation'ssku. Numeric on some facility catalogs — compare it as a string.namestringdescriptionstringcategorystringThe facility's own category, where it publishes one — an open vocabulary, not this API's.
GET /v1/productsalways fills this field (seeProductListItem); by SKU it is present only if the facility sent it.sourcestringPresent only on a row this API built itself; absent on every row a facility sent.
price_book: built from our own price book because our plant's catalog had nothing of its own to serve (the answer is thenX-Catalog-Stale: true, withX-Catalog-Degradednaming why). Its price is the book's, which is what an order of the SKU is charged; the row carriessku,name,base_priceandcategory.listed: a row of our catalog (a sticker, a wall decal, a poster or a magnet) that our plant's catalog, as this read found it, does not hold, listed after the rows read, with the same fields and its price from the catalog;not_orderable_codesays whether our plant carries it.One of
price_booklistedsize_innumber[] | nullOur plant's rows only: [width, height] in inches, read from the size the SKU names (the size the price book and an order price). Null when the SKU names no size, as an unsized sticker roll does.
2 to 2 entries
finishstring | nullOur plant's rows only: the finish the plant's row names, else the one the SKU names (glossy, matte, holographic, clear, satin); null when neither names one. No finish changes the price. A SKU of our sticker catalog is read by its own grammar: the finish it names after the size, else the bare stem's (glossy); there
clearandholographicname amaterial, not a finish. A wall decal of our catalog: its sheet's one finish,matte; a poster of our catalog: its one finish,matte(matte paper). A magnet, bumper sticker or single-sheet sticker of our catalog: null, as it names none.materialstring | nullOur catalog's rows only: the material the SKU is made in, as our catalog names it — an economy die-cut or kiss-cut sticker's white, clear, glitter or holographic adhesive vinyl; a bumper sticker's white surface or a single-sheet sticker's white, clear (Transparent) or holographic surface (no finish inferred); a wall decal's
film(Removable Wall Film, the barewall-decal-stem) orfabric(Polyester Fabric, the Textured Wall Decal), both indoor rated; a poster'spaper(matte paper, the bareposter-stem and its only material); UV DTFtransparent-film, roll labelsbopp, and catalog-only flags and hatspolyester. Sheet packs preserve white, clear or holographic surfaces. Null on every other row, a magnet of our catalog's included: our catalog names no material for one.One of
whiteclearglitterholographicfilmfabricpapertransparent-filmbopppolyesteroptionsobject | nullEconomy die-cut and kiss-cut stickers and listed magnets: the shapes and finishes an order line may name. Magnet cuts share their size's price and have no finish choice. Null for bumper stickers, single sticker sheets, UV DTF packs, label rolls, sheet packs, wall decals, posters, catalog-only flags and hats, and other rows with no choices. Pack count and surface are distinct SKUs.
options.shapesstring[]requiredThe shapes a SKU may name after its size, the bare SKU's first. Empty for a kiss cut, whose shape is the cut.
options.finishesstring[]requiredThe finishes a SKU may name after its size, the bare SKU's first.
options.default_shapestringrequiredWhat the bare SKU is cut to:
contour(the artwork's outline), orkissfor a kiss cut. A magnet's bare SKU is custom contour.One of
contourcirclesquarerectangleovalkissoptions.default_finishstring | nullrequiredWhat the bare SKU is finished in; null when no finish is offered, including magnets.
One of
glossmatteoptions.variant_skusstring[]requiredEvery SKU an order line may name for this product, canonically spelled, the bare SKU first (
sticker-die-cut-3x3,sticker-die-cut-3x3-matte,sticker-die-cut-3x3-circle, …): the spellings our plant is asked to carry, and so the ones to send. A shape or finish never changes the price, and an order's lines of one product count as one quantity. A test key orders any of them now. A live key orders each once our plant's catalog carries that SKU itself: the row'sorderablespeaks for the row's own SKU only, and a live quote or order of a variant the plant does not carry yet is refused 422sku_not_orderable_yetbefore anything is charged. Any other spelling (-gloss,-glossy, the words in another order) is asked of our plant under that spelling and is not one it was asked to carry.familystringOur plant's rows only: the product family. Modeled rows read pricing and cost under their family. The approved flag and Santa hat policy has bounded fulfillment while actual manufacturing cost remains unknown.
One of
stickersdecalsmagnetspostersflagsheadwearcatalog_readinessstringPresent on provider-product additions. Modeled sticker goods have estimated cost/weight. owner_exception identifies only the four approved flag sizes and one Santa hat: separate quantity-1 US standard orders with prepared combined ALL artwork and explicit benchmark/unknown-cost disclosures. Both test and live requests require reviewed exact production mappings and confirmed product evidence. catalog_only references cannot be quoted or ordered automatically.
One of
modeledowner_exceptioncatalog_onlyselling_unitstringOne order quantity buys this complete selling unit. A roll or pack is never priced or ordered as one individual label or sheet.
One of
piecerollpackphysical_piecesintegerPhysical pieces inside one selling unit: labels per roll, sheets per sheet pack, pieces per UV DTF pack, or one single product. Two ordered packs contain twice this number; order quantity remains two packs.
at least 1
minimum_quantityintegerOur plant's rows only: the fewest units of this SKU one order line takes, which is 1. A run of one is an order, priced at the first
quantity_breaksentry; POST /v1/order refuses a quantity below it with 400 validation_error.Always
1quantity_breaksobject[]Our plant's rows only: what an order of this SKU charges, from one unit up — computed by the function POST /v1/orders/quote and POST /v1/order charge with (no platform fee is added on any plan). The first entry (
min_qty1) is the row's own price.price_interpolationsays how to read between entries. Withnone, one entry per quantity where the unit price changes, each unit holding until the next entry; a single entry when the SKU climbs no ladder (a SKU our price book does not price, and a wall decal or a poster of our catalog: its listed price, one price each, at any quantity). Withlinear_total(our sticker catalog's rows), one entry at each of the catalog's pack quantities (1, 5, 10, 25, 50, 100, 250, 500, 1,000 and 2,500): an order of exactlymin_qtyis chargedtotal_cents, and any other quantity follows the ruleprice_interpolationstates, to the cent. Withformula(our magnet catalog's rows), one entry at each of 1, 5, 10, 25, 50, 100, 250, 500, 1,000, 2,500, 5,000 and 10,000 units: an order of exactlymin_qtyis chargedtotal_cents, and any other quantity is the price list's own total for it, which POST /v1/orders/quote gives. Counted on an order's whole quantity of the SKU, across its lines (on our sticker catalog's rows, across every shape and finish of one product). A negotiated price for your account takes no break and replaces these. Empty when the row carries no usable price (not_orderable_code: no_price) or is a catalog-only reference (not_orderable_code: catalog_only), whose displayed reference price cannot be charged.quantity_breaks[].min_qtyintegerrequiredFrom this many units of the SKU on one order (counted across its lines).
at least 1
quantity_breaks[].unit_centsintegerrequiredWhat an order of
min_qtycharges per unit, in cents. On a row whoseprice_interpolationisnoneit holds until the next entry'smin_qty; on alinear_totalorformularow it istotal_centsovermin_qty, rounded, and the units between entries follow the row's rule instead.quantity_breaks[].total_centsintegerWhat an order of exactly
min_qtyunits of the SKU is charged for the goods, in cents: the quote'sline_total_centsfor that many. On every entry.quantity_breaks[].list_total_centsintegerOur sticker catalog's rows only (
price_interpolation: linear_total): the catalog's own pack total formin_qty, beforemin_unit_centsraises it; equal tototal_centswherever it is not raised. The rule between entries reads these. Absent on every other row, a magnet's (formula) included.price_interpolationstringOur plant's rows only: how an order's goods total moves between two
quantity_breaksentries.none: each entry'sunit_centsholds from itsmin_qtyuntil the next entry's, so the total is that unit times the quantity (a wall decal or a poster of our catalog: its one entry, at any quantity).linear_total(our sticker catalog's rows, priced as a pack total): between two entries the total is the straight line between theirlist_total_cents, rounded half up to the cent — for a quantity q between entries a and b,round_half_up(list_a + (list_b − list_a) × (q − a) ÷ (b − a)); from the last entry on, its unit rate holds:round_half_up(q × list_last ÷ min_qty_last); and never less thanmin_unit_cents× q where the row states one. That is the quote's goods total at every quantity, exactly;unit_centsis the total over the quantity, rounded, and is not a bound on the units between entries.formula(our magnet catalog's rows): the total at every quantity is the magnet price list's own, computed from the size and the quantity, which no entry restates between two of them: quote the quantity. It never falls as the quantity grows. Rounding can make a unit rise slightly between neighboring quantities, but the 85% bulk share continues at 250 and beyond; the discount no longer expires there. Its entries carry nolist_total_cents.One of
nonelinear_totalformulamin_unit_centsinteger | nullOur plant's rows only: the least a unit of this SKU is charged, where that raises the goods total of some quantity a line can carry (a large sticker at volume is charged this, not less); its
quantity_breaksentries from there on already show it as theirunit_cents. Null where it raises no quantity, and on every row whoseprice_interpolationisnone.orderablebooleanOur plant's rows only: true when nothing POST /v1/orders/quote checks before pricing would refuse this SKU right now. False says why in
not_orderable_reason(a sentence) andnot_orderable_code(a code). A hint for a cart, read when the catalog was: the quote and the order decide.not_orderable_reasonstring | nullWhy not, in the order path's own words — the same field, meaning the same thing, as on every other row of this endpoint that says
orderable. Null whenorderable. On our plant's rowsnot_orderable_codeis the same answer as a code.not_orderable_codestring | nullOur plant's rows only: null when
orderable; otherwise why not, as a code, in the order the quote meets them.catalog_only— a flag or hat whose artwork, production cost and shipping weight remain unconfirmed: automatic test and live quotes/orders are refused, even if the plant feed carries the SKU.facility_not_configured— the plant has no credentials in this environment (a live quote or order answers 503 provider_unavailable; a test key's degrades to $0 markedpricing_degraded, except a row of our catalog, a sticker's, a wall decal's, a poster's or a magnet's, which a test key orders at its listed price).plant_unreachable— the plant's catalog did not answer this read (the row is a snapshot or the price book's); a quote, estimate or order asks the plant about the SKU, and answers 503 provider_unavailable withRetry-After(details.reason: plant_unreachable) until it does.plant_unmapped— a row of our catalog (a sticker's, a wall decal's, a poster's or a magnet's) that our plant's catalog, as this read found it, does not carry yet: a live quote or order of it is refused 422sku_not_orderable_yetbefore anything is asked or charged, and a test key orders it now, at the listed price.plant_price_held— the plant's prices are held after one of its launch goods read far above its book price, and this SKU has no book price to show it is not wrong the same way; the quote refuses it until the hold lifts.no_price— the row carries no usable list price and our price book does not price the SKU, so the quote refuses it.One of
catalog_onlyfacility_not_configuredplant_unreachableplant_unmappedplant_price_heldno_price
meta
pageintegertotalintegerlimitintegerThe page size actually applied, which is not necessarily the one asked for — the operation's own ceiling wins.
pagesintegertotaloverlimit, rounded up.next_cursorstring | nullOn a cursor-paged operation: send this back as
cursorfor the next page. Null on the last page. Absent on operations that do not page by cursor.has_morebooleanOn a list operation whose
metaisListMeta: whether another page follows this one.
{
"status": "success",
"data": {
"sku": "sticker-die-cut-3x3",
"name": "Die-Cut Sticker, 3×3 in",
"price": "0.70",
"regular_price": "0.70",
"category": "stickers",
"purchasable": true,
"stock_status": "instock",
"size_in": [
3,
3
],
"finish": "glossy",
"family": "stickers",
"minimum_quantity": 1,
"quantity_breaks": [
{
"min_qty": 1,
"unit_cents": 70,
"total_cents": 70,
"list_total_cents": 70
},
{
"min_qty": 5,
"unit_cents": 70,
"total_cents": 350,
"list_total_cents": 350
},
{
"min_qty": 10,
"unit_cents": 70,
"total_cents": 700,
"list_total_cents": 700
},
{
"min_qty": 25,
"unit_cents": 70,
"total_cents": 1750,
"list_total_cents": 1750
},
{
"min_qty": 50,
"unit_cents": 70,
"total_cents": 3500,
"list_total_cents": 3500
},
{
"min_qty": 100,
"unit_cents": 70,
"total_cents": 7000,
"list_total_cents": 7000
},
{
"min_qty": 250,
"unit_cents": 48,
"total_cents": 12083,
"list_total_cents": 12083
},
{
"min_qty": 500,
"unit_cents": 37,
"total_cents": 18667,
"list_total_cents": 18667
},
{
"min_qty": 1000,
"unit_cents": 30,
"total_cents": 30083,
"list_total_cents": 30083
},
{
"min_qty": 2500,
"unit_cents": 24,
"total_cents": 59167,
"list_total_cents": 59167
}
],
"price_interpolation": "linear_total",
"min_unit_cents": null,
"material": "white",
"options": {
"shapes": [
"contour",
"circle",
"square"
],
"finishes": [
"gloss",
"matte"
],
"default_shape": "contour",
"default_finish": "gloss",
"variant_skus": [
"sticker-die-cut-3x3",
"sticker-die-cut-3x3-matte",
"sticker-die-cut-3x3-circle",
"sticker-die-cut-3x3-circle-matte",
"sticker-die-cut-3x3-square",
"sticker-die-cut-3x3-square-matte"
]
},
"orderable": true,
"not_orderable_code": null,
"not_orderable_reason": null
},
"request_id": "4e3753e1-48dd-4313-bfd8-48e4af4e116a"
}Errors
Invalid request body or parameters. When the request failed the operation's schema,
detailscarriesformErrors(problems with the request as a whole),fieldErrors(messages keyed by top-level field) anderrors: one{ pointer, code, message }per refused value,pointeran RFC 6901 JSON pointer into the body (or into the object of query parameters, for a query parameter) —/items/0/sku, with~written~0and/written~1, and""for the request as a whole. POST /v1/orders/bulk answerserrorsas one{ index, details }per refused order, eachdetailswith its own pointers from the request body (/orders/3/items/0/sku); POST /v1/orders/multi-ship keepserrorsas the{ formErrors, fieldErrors }it always published; POST /v1/mockups/generate and POST /v1/products/recommend keep their fields at the top ofdetails, witherrorsbeside them. A refusal of a rule the schema cannot state (an asset that does not exist, an expiry too soon) names it inmessageand carries noerrors.Missing, invalid or expired API key:
missing_api_key(none sent),invalid_api_key(unknown or revoked),key_expired(past the expiry set when it was created; create a new key).404Resource not found
Rate limit exceeded. Two windows can produce this status and
error.codesays which:rate_limit_exceededfor the per-minute sliding window,daily_quota_exceededfor the plan's daily quota. Both carryRetry-After; the daily one also carriesX-Daily-LimitandX-Daily-Remaining. The per-minute limits are plan-based — see Rate Limits above, or call GET /v1/me for this credential's own figures.502The facility catalog gave no answer and no snapshot holds the SKU (nor, on our own plant's catalog, our price book) — or, with not_configured, the facility has no credentials in this environment, which is an operator fact rather than an outage.
X-Catalog-Degradedcarries the reason on every 502, so a failure is diagnosable rather than indistinguishable from an empty catalog. This is not a verdict on the SKU: retry, or readGET /v1/products.