Quickstart
Go from zero to a shipped test order in about five minutes — catalog, artwork, product, order, simulated production and a signed webhook.
You will create a real product and a test order end to end, using test mode: nothing is printed, shipped or charged, and test credits are free. Every step shows the request in cURL, Node.js and Python and what to look for in the response.
You need: a Swaglists account, a terminal, and (for step 8) any URL that can receive HTTPS requests.
1. Get a test key
- In the dashboard, open Developers → API keys and click Create key.
- Choose Test mode and Full access, name it
quickstart, and copy the secret. It starts withsk_test_and is shown only once. - Store it in an environment variable and call
GET /accountto check it works:
export API_KEY="sk_test_your_key_here"
curl https://swaglists.com/api/v1/account \
-H "Authorization: Bearer $API_KEY"// quickstart.mjs — Node.js 20+. Run with: API_KEY=sk_test_... node quickstart.mjs
import { randomUUID } from "node:crypto";
const API = "https://swaglists.com/api/v1";
async function api(method, path, body) {
const res = await fetch(API + path, {
method,
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
"Content-Type": "application/json",
...(method === "GET" ? {} : { "Idempotency-Key": randomUUID() }),
},
body: body === undefined ? undefined : JSON.stringify(body),
});
const json = await res.json();
if (!res.ok) throw new Error(`${res.status} ${json.error.code}: ${json.error.message}`);
return json;
}
const account = await api("GET", "/account");
console.log(account.name, account.mode); // "Company A" "test"# quickstart.py — Python 3.9+, pip install requests. Run with: API_KEY=sk_test_... python quickstart.py
import os, uuid, requests
API = "https://swaglists.com/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['API_KEY']}"
def api(method, path, json=None):
headers = {} if method == "GET" else {"Idempotency-Key": str(uuid.uuid4())}
res = session.request(method, API + path, json=json, headers=headers)
body = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {body['error']['code']}: {body['error']['message']}")
return body
account = api("GET", "/account")
print(account["name"], account["mode"]) # Company A testYou should see "object": "account" and "mode": "test". A 401 means the key was not sent or mistyped — see Authentication.
Note
The Node.js and Python tabs build one script step by step: keep appending each step's code to the same file.
2. Add test credits
Orders are paid from a prepaid wallet. Test mode has its own wallet; fill it with free test credits ($1,000 per call):
curl -X POST https://swaglists.com/api/v1/test/wallet/fund \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{}'const wallet = await api("POST", "/test/wallet/fund", {});
console.log("Available test credits:", wallet.available / 100);wallet = api("POST", "/test/wallet/fund", {})
print("Available test credits:", wallet["available"] / 100)available is in cents: 100000 = $1,000.
3. Pick a catalog product
List the products you can sell. Every variant carries your wholesale price in cents:
curl "https://swaglists.com/api/v1/catalog/products?limit=3" \
-H "Authorization: Bearer $API_KEY"const catalog = await api("GET", "/catalog/products?limit=3");
const catalogProduct = catalog.data[0];
const placement = catalogProduct.placements[0];
console.log(catalogProduct.id, catalogProduct.title, "from", catalogProduct.price_from / 100);
console.log("Print on:", placement.key, `${placement.recommended_width_px}x${placement.recommended_height_px}px`);catalog = api("GET", "/catalog/products?limit=3")
catalog_product = catalog["data"][0]
placement = catalog_product["placements"][0]
print(catalog_product["id"], catalog_product["title"], "from", catalog_product["price_from"] / 100)
print("Print on:", placement["key"], f"{placement['recommended_width_px']}x{placement['recommended_height_px']}px"){
"object": "list",
"data": [
{
"object": "catalog_product",
"id": "cp_01k8w1a2s3d4f5g6h7j8k9m0n1", // ← keep this
"title": "Classic Unisex Tee",
"price_from": 1299,
"placements": [
{ "key": "front", "recommended_width_px": 4500, "recommended_height_px": 5400, "min_dpi": 150 }
],
"variants": [
{ "id": "cv_01k8w1a2s7d9f1g3h5j7k9m1n3", "title": "Black / M", "price": 1299, "available": true }
]
}
],
"has_more": true
}Keep the product id and a placement key (usually front). Use catalog/products/{id} to see every variant, placement and mockup template — see Catalog & placements.
4. Upload artwork
Upload a PNG (transparent background recommended). No artwork handy? Download our sample — a 3000 × 3000 px PNG:
curl -o artwork.png https://swaglists.com/docs/assets/sample-artwork.pngcurl -X POST https://swaglists.com/api/v1/files \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "file=@artwork.png" \
-F "purpose=artwork"import { readFile } from "node:fs/promises";
const form = new FormData();
form.append("file", new Blob([await readFile("artwork.png")], { type: "image/png" }), "artwork.png");
form.append("purpose", "artwork");
// multipart: let fetch set the Content-Type (with the boundary) itself
const upload = await fetch(`${API}/files`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Idempotency-Key": randomUUID() },
body: form,
});
const file = await upload.json();
console.log(file.id, file.width_px, file.height_px, file.status); // file_… 3000 3000 readywith open("artwork.png", "rb") as f:
upload = session.post(
API + "/files",
headers={"Idempotency-Key": str(uuid.uuid4())},
files={"file": ("artwork.png", f, "image/png")},
data={"purpose": "artwork"},
)
file = upload.json()
print(file["id"], file["width_px"], file["height_px"], file["status"]) # file_… 3000 3000 readyAlready host your artwork? Send JSON instead: {"url": "https://cdn.example.com/art.png"} — we download it once. Keep the file id.
5. Create a product
A product = catalog product + your designs + variants with SKUs. Omitting position centers the artwork and fits it inside the print area; "variants": "all" adds every available color and size:
curl -X POST https://swaglists.com/api/v1/products \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"catalog_product_id": "cp_01k8w1a2s3d4f5g6h7j8k9m0n1",
"title": "Quickstart Tee",
"designs": [{ "placement": "front", "layers": [{ "file_id": "file_01k8x3y1q3w5e7r9t1y3u5i7o9" }] }],
"variants": "all",
"retail_price": 2900
}'const product = await api("POST", "/products", {
catalog_product_id: catalogProduct.id,
title: "Quickstart Tee",
designs: [{ placement: placement.key, layers: [{ file_id: file.id }] }],
variants: "all",
retail_price: 2900,
});
const variant = product.variants.find((v) => v.available) ?? product.variants[0];
const sku = variant.sku;
console.log(product.id, "SKU:", sku, "wholesale:", variant.price / 100);product = api("POST", "/products", {
"catalog_product_id": catalog_product["id"],
"title": "Quickstart Tee",
"designs": [{"placement": placement["key"], "layers": [{"file_id": file["id"]}]}],
"variants": "all",
"retail_price": 2900,
})
variant = next((v for v in product["variants"] if v["available"]), product["variants"][0])
sku = variant["sku"]
print(product["id"], "SKU:", sku, "wholesale:", variant["price"] / 100)In cURL, replace the two ids with yours. The response lists variants[], each with a generated sku (you can set your own — see Products & SKUs); use one whose available is true. It also has a warnings array (for example dpi_low if the artwork is small for the print area). Mockups render in the background.
6. Create a test order
Send the end customer's address and the SKU. Always send an Idempotency-Key and your own order id as external_id, so retries never create duplicates:
curl -X POST https://swaglists.com/api/v1/orders \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"external_id": "quickstart-1001",
"shipping_method": "standard",
"recipient": {
"name": "Jane Doe",
"address1": "123 Market St",
"city": "San Francisco",
"state_code": "CA",
"postal_code": "94107",
"country_code": "US",
"email": "jane@example.com"
},
"items": [{ "sku": "YOUR-SKU", "quantity": 1 }]
}'const order = await api("POST", "/orders", {
external_id: `quickstart-${Date.now()}`,
shipping_method: "standard",
recipient: {
name: "Jane Doe",
address1: "123 Market St",
city: "San Francisco",
state_code: "CA",
postal_code: "94107",
country_code: "US",
email: "jane@example.com",
},
items: [{ sku, quantity: 1 }],
});
console.log(order.id, order.status, "total:", order.total / 100); // ord_… processing …import time
order = api("POST", "/orders", {
"external_id": f"quickstart-{int(time.time())}",
"shipping_method": "standard",
"recipient": {
"name": "Jane Doe",
"address1": "123 Market St",
"city": "San Francisco",
"state_code": "CA",
"postal_code": "94107",
"country_code": "US",
"email": "jane@example.com",
},
"items": [{"sku": sku, "quantity": 1}],
})
print(order["id"], order["status"], "total:", order["total"] / 100) # ord_… processing …The order comes back with "status": "processing" and "funds_status": "held": the total was reserved from your test wallet and the order is on its way to (simulated) production. The response also has a tracking_page_url — your branded tracking page for the customer.
Tip
Want the price first? POST /orders/estimate takes the same body and returns exactly what the order will cost, without creating it.
7. Watch it ship
Test orders progress on their own (in production after about 2 minutes, shipped after about 5, delivered after about 15). Skip the wait: each call to the advance endpoint moves the order one step — in_production → shipped (with tracking) → delivered:
curl -X POST https://swaglists.com/api/v1/test/orders/ord_01k8x4b2q9w7e3r5t1y8u6i0pa/advance \
-H "Authorization: Bearer $API_KEY"for (let i = 0; i < 3; i++) {
const o = await api("POST", `/test/orders/${order.id}/advance`);
console.log(o.status, o.shipments[0]?.tracking_number ?? "");
}
// in_production
// shipped 9400…
// delivered 9400…for _ in range(3):
o = api("POST", f"/test/orders/{order['id']}/advance")
tracking = o["shipments"][0]["tracking_number"] if o["shipments"] else ""
print(o["status"], tracking)
# in_production
# shipped 9400…
# delivered 9400…After the second call, shipments[0] has a carrier, tracking_number and tracking_url. Fetch the order any time with GET /orders/{id} — or by your own id: GET /orders/ext:quickstart-1001.
8. Receive a webhook
Webhooks tell your backend when orders change. Run a small receiver that verifies the signature (it uses the official Standard Webhooks library):
// webhook-server.mjs — npm install standardwebhooks
import http from "node:http";
import { Webhook } from "standardwebhooks";
const wh = new Webhook(process.env.WEBHOOK_SECRET); // "whsec_…"
http
.createServer(async (req, res) => {
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
// the RAW body, decoded once all chunks are in (a character can span two chunks) — verify before parsing
const body = Buffer.concat(chunks).toString("utf8");
try {
const event = wh.verify(body, req.headers);
console.log(event.type, event.data.object.id, event.data.object.status ?? "");
res.writeHead(204).end();
} catch {
res.writeHead(400).end("invalid signature");
}
})
.listen(3000, () => console.log("Listening on :3000"));# webhook_server.py — pip install flask standardwebhooks
import os
from flask import Flask, request
from standardwebhooks.webhooks import Webhook
app = Flask(__name__)
wh = Webhook(os.environ["WEBHOOK_SECRET"]) # "whsec_…"
@app.post("/")
def receive():
try:
event = wh.verify(request.get_data(), dict(request.headers)) # raw body
except Exception:
return "invalid signature", 400
print(event["type"], event["data"]["object"]["id"])
return "", 204
app.run(port=3000)Expose it on a public HTTPS URL (for example with a tunnel: ngrok http 3000 or cloudflared tunnel --url http://localhost:3000), then register the URL. The response contains the signing secret — only this once:
curl -X POST https://swaglists.com/api/v1/webhook_endpoints \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "url": "https://your-tunnel.example.com/", "enabled_events": ["*"] }'Restart the receiver with WEBHOOK_SECRET set to that secret, then send a test event (use the endpoint id from the response):
curl -X POST https://swaglists.com/api/v1/webhook_endpoints/we_01k8x2m4n6b8v0c2x4z6a8s0d2/test \
-H "Authorization: Bearer $API_KEY"Your receiver prints webhook_endpoint.test. Create another order and advance it: you will see order.created, order.processing, order.in_production, shipment.created, order.shipped and order.delivered.
You did it
You created a product, placed an order paid with credits, watched it ship and received signed webhooks. Next:
- Connect your store: Shopify, WooCommerce, BigCommerce or a custom checkout.
- Learn the order lifecycle and how to handle webhooks robustly.
- When you are ready, follow the going live checklist.