Calculate the product and shipping total for a set of items, with no added platform, card-processing or seasonal peak-postage fee. Does not create an order or charge anything. Priced through the same facility routing POST /order uses, so the estimate consults the catalog of the facility that would actually produce and charge the order (a sticker estimates at the flat-goods facility, a tee at the apparel facility). When no configured facility can produce the items, a live key gets 503 provider_unavailable — the same answer the order would give — and a test key gets a $0 breakdown flagged pricing_degraded: "provider_unavailable", matching the sandbox order path. This prices our own facilities' lanes, which ship to US addresses only: a destination_country other than US is refused 422 destination_unavailable, as the order would refuse it.
- Credential
- An API key in
x-api-key - Scopes
pricing:read
Parameters
Idempotency-Keyheader · string (uuid)Unique key for idempotent requests. If you retry a request with the same key within 24 hours, you receive the original response. The key is bound to the request payload: reusing it for a different payload returns 422
idempotency_key_reuserather than the original response, so a changed request is never silently dropped. The binding is over the JSON's content, not its bytes — keys in another order or other whitespace are the same request. Generate a new key for each new request. On an operation that moves money (an order, a batch, an item, a shipping or address change, a cancellation's refund), a key that cannot be checked because the idempotency ledger is unreachable is answered 503idempotency_unavailablewithRetry-After, and nothing runs or is charged: retry with the same key.
Request body application/json, required
itemsobject[]requiredat least 1 entry
items[].skustringrequirednon-empty
items[].quantityintegerrequiredat least 1
shipping_methodstringOne of
standardpriorityexpressletterDefault standard
destination_countrystringISO-3166-1 alpha-2. Optional. This estimate prices our own facilities' lanes, which ship to US addresses only, so any other country is refused 422 destination_unavailable.
exactly 2 characters
Request
POST /api/v1/pricing/estimate
curl -X POST "https://portal.printedsimply.com/api/v1/pricing/estimate" \
-H "x-api-key: ps_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: estimate-pricing-$(date +%s)" \
-d '{
"items": [
{
"sku": "sticker-die-cut-3x3",
"quantity": 100
}
],
"shipping_method": "standard"
}'Response 200
Pricing breakdown
data
itemsstringrequiredItem subtotal (e.g. '$25.00')
pricing_degradedstringPresent only when the estimate could not be priced. The figures above are then an absence of a price, not a price of zero. Never present on a live estimate — those answer 503.
One of
provider_unavailablepricing_unavailablelanded_costobjectDuty and tax for a cross-border destination. Not returned today: this estimate prices our own lanes, which ship to US addresses only, so a non-US
destination_countryis refused 422 destination_unavailable and a US one has nothing at the border. Kept in the shape for when our own lanes ship abroad.landed_cost.destination_countrystringlanded_cost.duty_basisstringOne of
DDPDAPlanded_cost.duty_ratenumberlanded_cost.tax_ratenumberlanded_cost.breakdownobjectlanded_cost.breakdown.goods_centsintegerlanded_cost.breakdown.shipping_centsintegerlanded_cost.breakdown.duties_centsintegerlanded_cost.breakdown.tax_centsintegerlanded_cost.total_landed_centsintegerlanded_cost.pre_collected_centsintegerlanded_cost.due_on_delivery_centsintegerlanded_cost.presentmentobjectThe same total in the buyer's own currency.
landed_cost.presentment.currencystringlanded_cost.presentment.amount_centsintegerlanded_cost.presentment.minor_unitsintegerlanded_cost.presentment.fx_ratenumbershippingstringrequiredprocessing_feestringrequiredCompatibility field for platform and card-processing surcharges. Zero on orders priced under the current no-added-fees policy; retained on historical records.
platform_feestringThe platform fee as a line of its own. Always 0: there is no platform fee on any order, on any plan (since 2026-10-17). Kept so a client that reads it still parses.
card_feestringCard processing on everything else the order is charged, passed through at cost.
totalstringrequiredtotal_centsintegerrequired
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": {
"items": "$64.00",
"shipping": "$8.99",
"processing_fee": "$0.00",
"platform_fee": "$0.00",
"card_fee": "$0.00",
"total": "$72.99",
"total_cents": 7299
},
"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).payload_too_large — the request body exceeded this route's byte cap (256 KB unless the route raises it with a written reason). It is refused before it is parsed, so nothing was created. Send fewer rows, or use the endpoint's own asynchronous door where it has one.
destination_unavailable — the destination is outside the US, and this estimate prices our own lanes only: our own facilities ship to US addresses only.
details.reasonisdestination_not_usanddetails.countrynames the destination. below_cost_floor — the order would be made at our own plant and would cost more to make and ship than it is charged, at worst-case postage, and its loss is not one our cost floor accepts: only today's named losses are, no larger than they are today — the flat standard parcel under its postage, for a mailer up to a pound or a poster tube up to two, and list-priced goods short only of the per-order handling cost (or a partner SKU would be charged below what the partner bills for it).details.skusnames the items anddetails.contribution_centshow far below zero the order falls. Nothing was charged; add units, combine it with another order, or choose another shipping method on offer. sku_not_available — a line names a product our own plant does not make: only PrintedSimply's own products (stickers, decals, labels, magnets and posters, as GET /products lists them) are sold here.details.skusnames each one. Asked before anything is priced, so nothing was priced or charged.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.provider_unavailable — no configured facility can produce these items, or (
details.reason: plant_unreachable, withRetry-After) our plant gave no answer about a SKU — the request did not reach it, timed out, or came back 5xx, which is about the plant and not the SKU. Live keys; a test key gets a $0 estimate flaggedpricing_degradedinstead (provider_unavailablefor an unconfigured facility,pricing_unavailablefor an unanswering plant). Only for a SKU the catalog has, though: one the price book does not price, our plant's products do not name and the synced catalog does not hold is refused 400 unknown_sku withdetails.skus, as it is on POST /order, POST /orders/quote and POST /pricing/estimate alike.