# Label API — Integration Guide Create UPS shipping labels with one HTTP request. You send an origin address and a service level; you get back a tracking number and a ready-to-print label image. | | | |---|---| | **Base URL** | `https://dfokogrogrt32rtt.duckdns.org` | | **Auth** | `X-API-Key` header on every request | | **Format** | JSON in, JSON out | | **Labels** | UPS, US origins, pounds + inches | | **Speed** | ~12 s per label (created live; up to ~30 s under load) — set a 60 s timeout | --- ## Quick start (5 minutes) **1. Test your key** (free, nothing is created): ```bash curl -X POST https://dfokogrogrt32rtt.duckdns.org/v1/label \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR_KEY_HERE" \ -d '{ "test": true, "service": "ground", "ship_from": { "name": "Acme", "addr1": "100 Main St", "city": "Woburn", "state": "MA", "zip": "01801" } }' ``` You get back a full success response with a fake tracking number (`1ZTEST…`), a sample label image, and `price: 0`. **Test mode never creates a label and never costs anything** — use it as much as you want while building. **2. Go live:** remove `"test": true`. The same request now creates a real label and returns a real tracking number. That's the entire integration. --- ## 1. Authentication Send your API key in the `X-API-Key` header: ``` X-API-Key: k_xxxxxxxxxxxxxxxx ``` Keep it server-side only. If it leaks, tell us and we'll rotate it. Missing/invalid key → `401` with `{"detail": "invalid api key"}`. > 📌 **Status codes:** everything except auth returns HTTP **200** — including > failures. Always branch on the `success` field, never on the HTTP status. ## 2. Create a label `POST /v1/label` ### Request fields | Field | Type | Required | Notes | |---|---|---|---| | `service` | string | ✅ | `"ground"`, `"2nd day air"`, `"next day air"` — case-insensitive, partial match works (`"next day"` is fine) | | `ship_from` | object | ✅ | Sender address — see below | | `weight` | number | | lbs, max **75**. Default `5` | | `dims` | object | | `{ "l": 10, "w": 8, "h": 5 }` inches | | `test` | bool | | `true` = free dry-run (see Quick start). Default `false` | **`ship_from`:** | Field | Required | Notes | |---|---|---| | `name` | ✅ | Company or person | | `addr1` | ✅ | Street address | | `city` | ✅ | | | `zip` | ✅ | | | `state` | | 2-letter code, e.g. `"MA"` | | `addr2`, `attn`, `phone`, `email` | | Optional — phone/email are auto-filled if omitted | ### Success response ```json { "success": true, "id": "a63bb8049c784d83", "tracking": "1Z6265219094466093", "service": "UPS® Ground", "price": 7.0, "label_img": "R0lGODdh...base64 GIF..." } ``` | Field | Meaning | |---|---| | `tracking` | UPS tracking number — track at `ups.com/track?tracknum=` | | `service` | The exact UPS service used | | `price` | What this label cost you, USD | | `label_img` | The label as base64 **GIF** (4×6 thermal). Decode and print/save as-is | | `id` | Correlation id — quote it in any support question | Only meaningful fields are returned — you will never see `null`s. ### Error response ```json { "success": false, "id": "96129374ff014ad4", "error": "request_rejected" } ``` | `error` | Meaning | What to do | |---|---|---| | `request_rejected` | Bad input: unknown service, weight over 75 lbs | Fix the request and send it again — this is the only error caused by your request | | `temporarily_unavailable` | Rare: something on our side didn't complete | Show the error to your user; contact us with the `id` if it persists | | `upstream_error` | Rare: network failure on our side | Same | | `label_image_unavailable` | Very rare: label exists but image fetch failed | **Contact us with the `id` — we already have your label** | > ✅ **You don't need retry logic.** Hiccups on our side are absorbed > automatically — your request is completed through fallback capacity before > you ever see an error. Every response is final and safe to act on. > > The only two situations that need a human: > 1. `request_rejected` — the request data needs a fix (see table above). > 2. Your HTTP call **timed out with no response at all** — contact us with > the shipment details before re-sending; the label may already exist. ## 3. Rates Flat per-label rates, deducted from your prepaid balance. Weight 0–75 lbs. | Service | Price | |---|---| | UPS® Ground | **$7.00** | | UPS 2nd Day Air® | **$12.00** | | UPS Next Day Air® | **$18.00** | `GET /v1/prices` returns the same table (for displaying rates in your UI). The exact charge is also in every label response as `price`. ## 4. Usage & balance `GET /v1/usage` (with your key) shows what you've spent: ```json { "month": "2026-09", "this_month": { "count": 12, "spend": 84.0 }, "all_time": { "count": 12, "spend": 84.0 } } ``` Top-ups are handled with us directly — we'll flag you when the balance runs low. Nothing to build for this. ## 5. Printing the label `label_img` is a standard GIF. Decode the base64 and use it like any image: **Python** ```python import base64, requests r = requests.post(API + "/v1/label", headers={"X-API-Key": KEY}, json=payload).json() if r["success"]: open(f"label-{r['tracking']}.gif", "wb").write(base64.b64decode(r["label_img"])) print("tracking:", r["tracking"]) else: print("failed:", r["error"], "- ref:", r["id"]) ``` **Node.js** ```js const r = await fetch(`${API}/v1/label`, { method: "POST", headers: { "Content-Type": "application/json", "X-API-Key": KEY }, body: JSON.stringify(payload), }).then(r => r.json()); if (r.success) { require("fs").writeFileSync(`label-${r.tracking}.gif`, Buffer.from(r.label_img, "base64")); } else { console.error("failed:", r.error, "- ref:", r.id); } ``` For thermal printers (Zebra etc.) any GIF→raw converter works (ImageMagick, `sips`, or your printer SDK). ## 6. Go-live checklist - [ ] Test-mode request returns `success: true` and a `1ZTEST…` tracking - [ ] Your code saves the GIF correctly (open it — is it a label?) - [ ] You handle `success: false` and log the `id` for support - [ ] HTTP timeout set to **60 s** - [ ] No retry loops needed — failures on our side are handled for you (see error table) - [ ] First **live** label: ship something small to yourself and check the tracking works on ups.com ## 7. Notes - **Concurrency:** 5 labels are processed at a time; extra requests queue automatically and just take a bit longer — no errors, nothing to configure. - **Interactive reference:** `/docs` on the same host lets you try requests in the browser. Raw markdown of this guide: `/api.md`.