In short
- Limits are per account and per endpoint over a rolling hour, shared by every key on the account: 2,000 reads, 1,000 product creates, 500 of the other writes, and 1,000 new orders per 24 hours. A call that fails with 400 still counts.
- Over a limit you get 429
{ "error": "Rate limit exceeded" } with no Retry-After header. Wait at least a minute, then back off further.
- Every error is JSON with an
error message, sometimes an errors array naming the items that failed, and on 402 a credits object.
- Reads are always safe to retry. Writes are safe when they carry an idempotent key:
external_order_number on orders, the SKU on products and bundles. A 409 or an “already exists” 400 on a retry means the first attempt worked.
- The one call to never retry blind is
POST /api-bulk-list without explicit SKUs, because the generated SKUs get a number added and you create duplicate drafts. Check GET /api-bulk-list first.
Rate limits
| Endpoints |
Limit |
GET /api-get-pricing, GET /api-get-product, GET /api-get-order, GET /api-get-templates, GET /api-get-bundle, GET /api-bulk-list
|
2,000 an hour |
POST /api-create-product |
1,000 an hour |
PUT /api-update-product, DELETE /api-delete-product, POST /api-bulk-list, POST /api-create-bundle, DELETE /api-delete-bundle
|
500 an hour |
POST /api-create-order |
1,000 new orders per 24 hours |
- Each limit is a rolling window per endpoint, so hitting the product limit doesn’t stop you reading orders.
- Limits are per account, not per key. Extra keys don’t raise them.
- The limit is counted before the body is checked, so invalid requests that come back 400 use it up like any other. Validate on your side first.
- Budget reads for what matters: for many orders, use the list form of
GET /api-get-order (25 per page) rather than one read per order, and poll every 30 to 60 minutes, which is as fast as orders move.
Error format
{ "error": "SKU must be 50 characters or less." }
{ "error": "1 of 2 images failed validation. Nothing was created.",
"errors": [ { "index": 1, "image_url": "https://example.com/oak-leaf.png", "error": "Artwork is 2:3 (1024x1536px) but template \"Botanical\" is A-series. Use a 2:3 template or resize the file." } ] }
{ "error": "Not enough credits: this request needs 12 and you have 4. Top up or send fewer images.",
"credits": { "cost": 12, "available": 4 } }
Successful responses carry "success": true. Keys look like psk_live_ plus 32 characters, and they don’t expire; a revoked key returns 401 immediately.
Status codes
| Code |
Meaning |
What to do |
| 400 |
The request is invalid. The message names the field or item. |
Fix it and resend. Don’t resend the same body. |
| 401 |
Missing or invalid API key (no key, or the wrong prefix) or Invalid API key (unknown or revoked). |
Don’t retry. Check the header, then ask the seller to check Tools → Developer (see Get Access). |
| 402 |
Not enough credits for the whole POST /api-bulk-list request; credits shows the cost and what’s available. Nothing was created. |
Send fewer images, use "generate": false, or wait for the seller’s allowance to reset or upgrade. |
| 404 |
Not found: a product by SKU, a bundle, an order, or pricing for a country. |
For a product or bundle, create it; for an order, check the id or external number; for pricing, check the country code. |
| 405 |
Method not allowed. |
Use the method on the reference: GET for reads, POST to create, PUT to update, DELETE to delete (POST is accepted as an alias for the two deletes). |
| 409 |
Already exists: a product SKU, a bundle SKU, a Bulk List SKU already used by a product or draft, or a product that other records still use on delete. |
On a retry, treat it as done and read the record back. On a first attempt, pick a different SKU or read what’s there. |
| 429 |
Rate limit exceeded, with no Retry-After header. |
Wait at least 60 seconds, then back off further (see below). |
| 500 |
Our error; the wording varies by endpoint. For Bulk List, a 500 with success: false and "No draft could be created. See drafts[].error." means every image failed individually. |
Retry a read after a few seconds. For a write, read the record first to see whether it went through, then retry if it didn’t. |
The 400s you will meet most
-
Orders:
Missing customer information: zip. All of these are required: name, address1, city, zip, country · Country must be a 2-letter ISO code (e.g. "GB", "US", "AU"). Received: "UK" · State/province is required for United States. Please provide a "state" field. · Shipping option "priority" is only available for United Kingdom. Use "standard" for other countries. · Product[0]: Missing required field: paper_type. Accepted values: Matte, Satin, Gloss · Product[1]: Frame orders require a "variant" field. Accepted values: Black, White, Oak · Product[0]: Provide either "sku" (to reference a saved product) or "image.artwork_url" (to provide artwork directly), not both. · An order with external_order_number "SHOP-1001" already exists.
-
Products:
Missing required field: name · SKU must be 50 characters or less. · Too many variants. Maximum 22 variants per request. · variants[1]: Invalid size "a4". Accepted values: ... (sizes are case-sensitive) · variants[0]: Duplicate size "A4". ... · variants[0]: Invalid image_url: <reason> · the account has reached 25,000 products.
-
Pricing:
Missing required query parameter: country · Unknown country: XX.
-
Bulk List and bundles: a 400 with an
errors array, one entry per failing item with its index and reason, and nothing created. Fix the items named and resend the whole request.
Retrying safely
| Call |
Safe to retry? |
Why |
All GET calls |
Yes |
Read-only. |
POST /api-create-order |
Yes, with the same external_order_number
|
A duplicate is refused with 400 already exists, so a retry either places the order or tells you it was placed; look it up with GET /api-get-order. Without an external number a retry can create a second real order, so always send one. |
POST /api-create-product |
Yes |
A repeat with the same SKU is a 409, and the first attempt stands. Read it back with GET /api-get-product. |
PUT /api-update-product |
Yes |
Sending the same change twice leaves the product in the same state. |
DELETE /api-delete-product, DELETE /api-delete-bundle
|
Yes |
A repeat is a 404, meaning it is already gone. |
POST /api-create-bundle |
Yes |
A repeat with the same SKU is a 409. |
POST /api-bulk-list with explicit items[].sku
|
Yes |
A repeat is refused with 409 and nothing is created. |
POST /api-bulk-list without SKUs |
No, not blind |
SKUs default to file names, and a name already in use gets a number added, so a repeat creates duplicate drafts and spends credits. List the newest drafts with GET /api-bulk-list first and resend only the images that are missing. |
Timeouts
A timeout or a dropped connection on a write means you don’t know whether it happened. Don’t guess: read first. GET /api-get-order?external_order_number=..., GET /api-get-product?sku=..., GET /api-get-bundle?sku=... and the list form of GET /api-bulk-list each tell you in one call, and then you retry only what is missing. Give every order an external number and every product and bundle a SKU you chose, and this is always possible.
A backoff that works
- On 429: wait 60 seconds, then 2, 4 and 8 minutes between further attempts. Since the window is a rolling hour, spreading calls out beats bursting.
- On 500 for a read: retry after a few seconds, up to three times, then stop and report it.
- On 400, 401, 404, 405 and 409: don’t retry the same request. Each one is telling you something to change or something already done.
- On a timeout: read, then retry the write if needed.
Other limits
| What |
Limit |
| Prints per order |
50, after bundles are split; one line per print |
| Sizes per product call |
22 (create or update); use further update calls for more |
| Images per Bulk List call |
1 to 10 |
| Products per bundle |
1 to 10, making 2 to 10 prints counting quantities |
| Products per account |
25,000 |
| Drafts in the Listing Hub |
500 (5,000 on the Max plan) |
| API keys |
10 active per account |
| Files |
50 MB, JPEG or PNG (PDF |