Add a new item to an existing order. Charges the price delta to the default payment method: the line's goods, and what the line adds to the order's shipping. The line rides in the parcel the order already ships, so the parcel is re-priced for its new lines by the method the order is recorded as shipping by (standard when it recorded none), on its own lane, folded as POST /order folds it: the lines before and after are priced on today's terms and the difference is charged, so a line that moves the parcel into a heavier class pays for that and nothing else. shipping says what moved; total_delta_cents is the whole charge, the figure the key's spend caps see. On an order shipping by letter, a line the letter cannot take (a parcel-only item, too many units, goods over the ceiling) is refused 400 letter_mail_unavailable with details.reason, as POST /order refuses the same lines, and nothing is charged: change the order's method with PUT /order/{id}/shipping first. A parcel method no longer offered is refused 400 shipping_method_unavailable. The order's size limits hold here as on POST /order: a line past an order's 100th, or a quantity over 10,000, is refused 400 validation_error and nothing is charged. Add-items on one order are taken one at a time, so two sent together cannot pass the line limit between them. On a test order the line is priced $0, marked pricing_degraded, when its price cannot be had just now (the facility unconfigured, or not answering — pricing_unavailable for our plant not answering, which a live key is answered 503 provider_unavailable for); a SKU the catalog does not have is refused 400 unknown_sku with details.skus, and our plant's answer that it does not carry the SKU is the 400 pricing_error a live key gets, as on POST /order.
- Credential
- An API key in
x-api-key - Scopes
orders:write
Parameters
idpath · stringrequiredThe order's
order_id, or itsportal_order_id(ord_…): either resolves the same order. An id the order answered to before it was re-routed resolves too.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
skustringrequirednon-empty
quantityintegerrequired1 to 10000
print_filestring (uri)
Request
POST /api/v1/{id}/item
curl -X POST "https://portal.printedsimply.com/api/v1/test_ord_wVK9ZiG-VYiPrlSeBUBmrUVk/item" \
-H "x-api-key: ps_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: add-item-$(date +%s)" \
-d '{
"sku": "MAGNET-4X4",
"quantity": 25,
"print_file": "https://example.com/magnet-design.png"
}'Response 200
Item added
data
itemobjectrequireditem.idstringitem.skustringitem.quantityintegeritem.unit_pricestringitem.chargedstringThe line's goods. What the card was charged is
total_delta_cents.shippingobjectrequiredWhat an item change did to the order's shipping line. The order ships one parcel, so adding a line or changing a quantity re-prices it: by the order's recorded method on its own lane, the lines before and after priced on today's terms, the difference charged with the goods on an increase and refunded with them on a decrease.
shipping.methodstringrequiredThe method the parcel was priced by: the order's recorded one, or
standardwhen it recorded none.One of
standardpriorityexpresslettershipping.shipping_classobject | nullrequiredThe parcel the order's lines now pack into, as POST /shipping/estimate reports it: a weight band (
parcel_1lb,parcel_2lb,parcel_5lb,parcel_over_5lb), with_tubewhen it ships rolled. Null on a letter.shipping.shipping_class.namestringrequiredshipping.shipping_class.weight_oznumberrequiredEstimated shipped weight in ounces, packing tube included.
shipping.delta_centsintegerrequiredWhat this change moved on the shipping line, in cents: positive when it was charged, negative when it was refunded, 0 when the parcel's price did not change (or a decrease's shipping could not be priced and stays as charged).
shipping.shipping_centsintegerrequiredThe order's shipping line after this change, in cents.
total_delta_centsintegerrequiredEverything this call charged, in cents: the line's goods and
shipping.delta_cents. The figure the key's spend caps see and order.updated'stotal_delta_cents. On a sandbox order nothing is charged and this is what a live order would be.test_modebooleanPresent and true on a sandbox order; nothing was charged.
pricing_degradedstringSandbox only. The SKU's facility catalog was unreachable, so the line was added at $0.00 — an absence of a price, not a price — exactly as POST /order, /orders/quote and /pricing/estimate degrade for a test key. A live order gets 400 pricing_error instead.
Always
provider_unavailableartwork_checkobjectThe verdict on the added line's artwork when the order is made at our own plant, as POST /order's artwork_check (its one line is item_index 0); on a test key,
missing_artworkwhen the line has no print_file. Absent otherwise.artwork_check.linesobject[]requiredOne entry per line made at our own plant (stickers, decals, magnets, posters) that carries artwork, in item order. A line another lane makes is graded there, not here.
artwork_check.lines[].item_indexintegerrequiredThe line's position in the request's
items, 0-based.at least 0
artwork_check.lines[].skustringrequiredartwork_check.lines[].statusstringrequiredpassed: read, nothing to say. warned: read, at least one warning. refused: at least one error (a live order is refused). unverified: the file could not be read just now (see the
unverifiedfinding); the order is not refused for it.One of
passedwarnedrefusedunverifiedartwork_check.lines[].width_pxintegerThe file's width in pixels. Absent when it was not read, or is vector.
at least 1
artwork_check.lines[].height_pxintegerThe file's height in pixels.
at least 1
artwork_check.lines[].print_size_innumber[]The print size resolution was measured against, width and height in inches: the SKU's
WxH, else the line'ssize. Absent when neither names one.2 to 2 entries
artwork_check.lines[].effective_dpiintegerPixels per inch with the artwork printed as large as the print size allows (turned a quarter if that fits it larger).
at least 1
artwork_check.lines[].min_dpiintegerThe product's recommended minimum DPI. Below 75% of it is an error.
at least 1
artwork_check.lines[].findingsobject[]requiredartwork_check.lines[].findings[].codestringrequiredOne of
unreachablenot_an_imagelow_resolutionbelow_recommended_resolutioncmykno_transparencytouches_edgeno_bleedcontent_in_bleedno_cutlinesize_unknownvectorunverifiedformat_not_acceptednot_fetchabledesign_embeds_rastergenerated_designplacement_clippedgeometry_unverifiedartwork_check.lines[].findings[].severitystringrequirederror refuses a live order; warning and info never do.
One of
errorwarninginfoartwork_check.lines[].findings[].messagestringrequiredWhat was found and what to do about it, naming the line.
artwork_check.missing_artworkobject[]Test keys (ps_test_*) only, on POST /order, POST /orders/bulk and POST /{id}/item: the lines made at our own plant that carry no artwork at all, which a live request is refused for with 422
artwork_required. Absent when there are none.artwork_check.missing_artwork[].order_indexintegerPOST /orders/bulk's 422 only: the order's position in
orders.at least 0
artwork_check.missing_artwork[].item_indexintegerrequiredThe line's position in the order's
items, 0-based.at least 0
artwork_check.missing_artwork[].skustringrequiredartwork_check.would_refusebooleanTest keys (ps_test_*) only: whether a live order with these lines would have been refused, with 422
artwork_not_printableor (for a line inmissing_artwork)artwork_required. A test key is never refused for artwork, so placeholder artwork still places a sandbox order. Absent on a live order, which either was refused or was not.
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": {
"item": {
"id": "itm_vNx36IWd8kx2dCQqkFbsTVg3",
"sku": "magnet-4x4",
"quantity": 25,
"unit_price": "$2.47",
"charged": "$61.75"
},
"shipping": {
"method": "standard",
"shipping_class": {
"name": "parcel_5lb",
"weight_oz": 35
},
"delta_cents": 1200,
"shipping_cents": 2099
},
"total_delta_cents": 7375,
"test_mode": true,
"artwork_check": {
"lines": [
{
"item_index": 0,
"sku": "magnet-4x4",
"status": "passed",
"width_px": 1200,
"height_px": 1200,
"findings": [],
"print_size_in": [
4,
4
],
"effective_dpi": 300,
"min_dpi": 300
}
],
"would_refuse": false
}
},
"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).402Payment failed or no payment method configured
403sandbox_onlysandbox_only — the account is restricted to sandbox mode and the request used a live key.
404Resource not found
order_not_modifiable — the order has gone into production (or later), past the point where items can be added. shopper_paid_order — a shopper paid for this order (a drop, a checkout link or an agent checkout), so it takes no charge on your card: its refunds go back to the shopper. Place a separate order instead. concurrent_modification — another line was being added to this order and did not finish within a few seconds: add-items on one order are taken one at a time, so the 100-line limit holds, and nothing was charged. Retry in a moment.
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.
artwork_not_printable — the order is made at our own plant and the added line's artwork (print_file) cannot be printed as it is (unreachable, not_an_image, low_resolution). Checked before the line is priced or the card is charged: nothing was charged and the Idempotency-Key is free for the corrected line.
details.problems[]anddetails.artwork_checkare shaped as on POST /order's 422. artwork_required — the order is made at our own plant and the added line has no print_file, so there is nothing to print; refused before anything is read or charged, withdetails.items[]as on POST /order's 422. A test key is never refused for artwork; its 200 carriesartwork_check.would_refuse(andartwork_check.missing_artworkfor a line with none). sku_not_available — the added line is a product our own plant does not make; refused before it is priced or charged, anddetails.skusnames it.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.not_supported_by_provider — the order was produced by an independent network shop, which does not accept amendments at all. Never retryable, unlike the 409. Live PrintedSimply add-item calls are also refused before any charge because the plant handler is not confirmed.
502Production system error
provider_unavailable (
details.reason: plant_unreachable, withRetry-After) — the order is made at our own plant and the plant gave no answer about the added SKU (the request did not reach it, timed out, or came back 5xx), which is about the plant and not the SKU: a definite answer that it does not carry the SKU is still the 400 pricing_error. Asked before the card is charged, so nothing was charged and the same Idempotency-Key may be retried. A test order is not refused: its line is priced $0 withpricing_degraded: pricing_unavailable, as on POST /order. idempotency_unavailable — the request carried anIdempotency-Keyand the ledger that makes it mean at-most-once could not be reached, so nothing ran and nothing was charged; retry the same request with the same key afterRetry-After.