# Quickstart > Go from zero to a shipped test order in about five minutes — catalog, artwork, product, order, simulated production and a signed webhook. Source: https://swaglists.com/docs/quickstart 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 1. In the dashboard, open **Developers → API keys** and click **Create key**. 2. Choose **Test** mode and **Full access**, name it `quickstart`, and copy the secret. It starts with `sk_test_` and is shown only once. 3. Store it in an environment variable and call `GET /account` to check it works: ```bash title="cURL" export API_KEY="sk_test_your_key_here" curl https://swaglists.com/api/v1/account \ -H "Authorization: Bearer $API_KEY" ``` ```js title="Node.js" // 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" ``` ```python title="Python" # 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 test ``` You should see `"object": "account"` and `"mode": "test"`. A `401` means the key was not sent or mistyped — see [Authentication](https://swaglists.com/docs/authentication.md). > **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): ```bash title="cURL" curl -X POST https://swaglists.com/api/v1/test/wallet/fund \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ```js title="Node.js" const wallet = await api("POST", "/test/wallet/fund", {}); console.log("Available test credits:", wallet.available / 100); ``` ```python title="Python" 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: ```bash title="cURL" curl "https://swaglists.com/api/v1/catalog/products?limit=3" \ -H "Authorization: Bearer $API_KEY" ``` ```js title="Node.js" 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`); ``` ```python title="Python" 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") ``` ```jsonc title="Response (abridged)" { "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](https://swaglists.com/docs/catalog.md). ## 4. Upload artwork Upload a PNG (transparent background recommended). No artwork handy? Download our sample — a 3000 × 3000 px PNG: ```bash title="Download the sample artwork" curl -o artwork.png https://swaglists.com/docs/assets/sample-artwork.png ``` ```bash title="cURL" curl -X POST https://swaglists.com/api/v1/files \ -H "Authorization: Bearer $API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -F "file=@artwork.png" \ -F "purpose=artwork" ``` ```js title="Node.js" 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 ready ``` ```python title="Python" with 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 ready ``` Already 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: ```bash title="cURL" 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 }' ``` ```js title="Node.js" 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); ``` ```python title="Python" 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](https://swaglists.com/docs/products.md)); 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: ```bash title="cURL" 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 }] }' ``` ```js title="Node.js" 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 … ``` ```python title="Python" 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`: ```bash title="cURL" curl -X POST https://swaglists.com/api/v1/test/orders/ord_01k8x4b2q9w7e3r5t1y8u6i0pa/advance \ -H "Authorization: Bearer $API_KEY" ``` ```js title="Node.js" 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… ``` ```python title="Python" 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](https://swaglists.com/docs/webhooks.md) library): ```js title="Node.js" // 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")); ``` ```python title="Python" # 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**: ```bash title="cURL" 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): ```bash title="cURL" 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](https://swaglists.com/docs/shopify.md), [WooCommerce](https://swaglists.com/docs/woocommerce.md), [BigCommerce](https://swaglists.com/docs/bigcommerce.md) or a [custom checkout](https://swaglists.com/docs/custom-checkout.md). - Learn the [order lifecycle](https://swaglists.com/docs/orders.md) and how to handle [webhooks](https://swaglists.com/docs/webhooks.md) robustly. - When you are ready, follow the [going live checklist](https://swaglists.com/docs/going-live.md).