The standard library, an idempotent order, a tracking link — nothing to install.
In the dashboard, open API keys and create a key in Test mode. It starts with ps_test_ — test keys are free, never charge a card, and never reach a print facility. You can create one before adding a payment method.
Create a test keyexport PRINTEDSIMPLY_API_KEY=ps_test_YOUR_KEYA live key (ps_live_) takes the same requests. A real order also needs a card on file and your own artwork: Going live, at the end of this page, has the four changes.
Every response is the same envelope: status, data, and request_id. Quote the request_id in any support thread and we can find the exact call. Each product's sku is what an order names. Save it as products.py and run python3 products.py: urllib is in the standard library, so there is nothing to install (Python 3.8 and later). On a python.org install on macOS, run its Install Certificates command once if urllib reports CERTIFICATE_VERIFY_FAILED.
import json
import os
import urllib.error
import urllib.request
request = urllib.request.Request(
"https://portal.printedsimply.com/api/v1/products?limit=3",
headers={"x-api-key": os.environ["PRINTEDSIMPLY_API_KEY"]},
)
try:
with urllib.request.urlopen(request, timeout=30) as res:
data = json.load(res)["data"]
except urllib.error.HTTPError as err:
error = json.load(err)["error"]
raise SystemExit(f"{err.code} {error['code']}: {error['message']}")
for product in data["products"]:
print(product["sku"], "-", product["name"])Rows carry more than these three fields, the price of one unit among them; POST /v1/orders/quote prices a whole order. The order below names the first row's sticker as sticker-die-cut-3x3: a SKU is matched whatever its case.
The order's reference doubles as its Idempotency-Key: send the same request again and you get this order back, never a second one; the same key with a different body is refused with 422. A new order needs a new reference as well as a new key: one reference is one order for 30 days, so the same reference under another key is refused with 409 duplicate_reference, naming the order that has it. Run it as you ran products.py: python3 order.py.
import json
import os
import urllib.error
import urllib.request
import uuid
order = {
"items": [
{ "sku": "sticker-die-cut-3x3", "quantity": 100,
"artwork_url": "https://printedsimply.com/samples/sticker-3x3.png" }
],
"address_to": {
"name": "Jane Doe", "address1": "123 Main St",
"city": "Austin", "state": "TX", "zip": "78701", "country": "US"
},
"shipping": { "method": "standard" }
}
request = urllib.request.Request(
"https://portal.printedsimply.com/api/v1/order",
method="POST",
data=json.dumps(order).encode(),
headers={
"x-api-key": os.environ["PRINTEDSIMPLY_API_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": f"order-{uuid.uuid4().hex}",
},
)
try:
with urllib.request.urlopen(request, timeout=30) as res:
data = json.load(res)["data"]
except urllib.error.HTTPError as err:
error = json.load(err)["error"]
raise SystemExit(f"{err.code} {error['code']}: {error['message']}")
# data["order_id"] is the order (test_ord_... on a test key); data["tracking_url"] is its tracking page.
print(data["order_id"], data["charged"])
print(data["tracking_url"])Move the sandbox order straight to shipped instead of waiting for the schedule: python3 ship.py test_ord_… with the order_id order.py printed.
import json
import os
import urllib.error
import urllib.request
import sys
order_id = sys.argv[1] # the order_id order.py printed: test_ord_...
request = urllib.request.Request(
f"https://portal.printedsimply.com/api/v1/order/{order_id}/simulate",
method="POST",
data=json.dumps({"target_state": "shipped"}).encode(),
headers={"x-api-key": os.environ["PRINTEDSIMPLY_API_KEY"], "Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(request, timeout=30) as res:
data = json.load(res)["data"]
except urllib.error.HTTPError as err:
error = json.load(err)["error"]
raise SystemExit(f"{err.code} {error['code']}: {error['message']}")
print(data["order_id"], data["status"])Left alone, a sandbox order moves on by itself: a scheduled job takes it to in_production, shipped and delivered over about five minutes, firing the same signed webhooks at each step. simulate does not wait for that schedule. It takes in_production, shipped, delivered, cancelled or issue_flagged, and only on a test key. An order the schedule has already delivered answers 400 already_terminal; to rehearse the step again, place a new order with a new reference and Idempotency-Key (the sample uses one value for both; the same key replays the delivered order, and the same reference under a new key is refused 409 duplicate_reference).
order.py printed the tracking link. Open it, then read the order: once it has shipped, tracking carries the sandbox tracking number (TRK-TEST-…) and its carrier. Simulate delivered, or wait, to watch it deliver.
import json
import os
import urllib.error
import urllib.request
import sys
order_id = sys.argv[1]
request = urllib.request.Request(
f"https://portal.printedsimply.com/api/v1/order/{order_id}",
headers={"x-api-key": os.environ["PRINTEDSIMPLY_API_KEY"]},
)
try:
with urllib.request.urlopen(request, timeout=30) as res:
data = json.load(res)["data"]
except urllib.error.HTTPError as err:
error = json.load(err)["error"]
raise SystemExit(f"{err.code} {error['code']}: {error['message']}")
print(data["status"], data["tracking"]["number"], data["tracking"]["carrier"])
print(data["tracking"]["page_url"])Sandbox orders run the real lifecycle on a compressed clock: created → in_production → shipped → delivered, firing genuinely HMAC-signed webhooks at every transition through the same delivery pipeline live orders use. Leave the tracking page open.
The sandbox order above never charges, prints or ships. A real one takes four changes.
A live order is charged to the card on file when it is placed. Without one, POST /v1/order answers 402 no_payment_method and nothing is placed.
Open BillingOn the API keys page, create a key in Live mode. It starts with ps_live_ and is shown in full once, so store it where your server reads secrets, never in a browser.
Create a live keySwap in the live key, your customer's address and your artwork: an https link to the file (PNG, PDF or SVG), or the id POST /v1/assets returns, as print_file. A live order's artwork is checked before anything is charged: a line with no artwork answers 422 artwork_required, and a file that cannot be printed answers 422 artwork_not_printable.
A subscription in test mode receives sandbox events only. For real orders, subscribe your endpoint in live mode (or all) on the Webhooks page or with POST /v1/webhooks, and verify X-PS-Signature-V2 on every delivery.
Open Webhooks