REST API behind the PackCheck iOS app — packing lists, templates, and the
photo scan. Designed for reuse by any client (iOS today, web later).
Version: v1.
| Base URL | https://packcheck-black.vercel.app/api/v1 |
|---|---|
| Format | JSON requests and responses (Content-Type: application/json) |
| Auth | No accounts. Every request sends X-Device-Id — a client-generated
UUID (8–128 chars), created once on first launch and persisted on the device.
All data is scoped to that ID. |
| Errors | JSON { "error": "message" } with an appropriate status:
400 bad input · 401 missing device ID · 404 not found ·
503 dependency not configured |
Item = { "name": string, "packed": boolean, "verifiedBy": "scan" | "manual" | null,
"category": string | null } // e.g. Documents, Tech, Clothing, Toiletries, Other
Template = { "id": uuid, "name": string,
"items": string[], // v1-compatible names
"itemsDetailed": [{ "name": string, "category": string | null }] }
Trip = { "id": uuid, "name": string, "tripType": "business" | "leisure",
"startDate": "YYYY-MM-DD" | null, "endDate": "YYYY-MM-DD" | null,
"origin": string | null, "destination": string | null,
"templateName": string | null, "items": Item[],
"reminderOffsetMinutes": number, // 5-2880, default 180 (3 hours)
"createdAt": timestamp, "updatedAt": timestamp }
Liveness probe. db reflects database reachability.
{ "ok": true, "db": true }
The two built-in templates plus this device's saved templates.
{
"builtIn": [
{ "name": "Business", "items": ["Laptop", "Laptop charger", "Dress shoes", "Dress shirts", "Toiletry kit"] },
{ "name": "Leisure", "items": ["Swimsuit", "Sunscreen", "Sunglasses", "Sandals", "Phone charger", "Toiletry kit"] }
],
"mine": [ { "id": "…", "name": "Boise QBR", "items": ["Laptop", "Dress shoes"] } ]
}
Save (or overwrite) a template by name — this is "Save as template".
Upserts on (device, name). Accepts items as plain names or as
{name, category} objects (also accepted under itemsDetailed).
Returns 201 with the template.
{ "name": "Boise QBR",
"items": [ { "name": "Laptop", "category": "Tech" },
{ "name": "Dress shoes", "category": "Clothing" } ] } // 1-100 items
Delete one of this device's templates. Built-ins cannot be deleted.
All trips for this device, soonest start date first.
Create a trip. Items are seeded from templateName — the device's own
template with that name wins, then a built-in; if omitted or unknown, the
built-in matching tripType is used (default Business). Returns 201
with the full Trip.
{ "name": "Q3 Sales Kickoff", // required, 1-120 chars — any name the user wants
"tripType": "business", // optional: "business" (default) | "leisure"
"startDate": "2026-08-10", // optional
"endDate": "2026-08-13", // optional
"origin": "SFO", // optional, ≤80 chars
"destination": "Boise, ID", // optional, ≤80 chars
"reminderOffsetMinutes": 180, // optional, 5-2880 (default 180)
"templateName": "Business" } // optional
PATCH accepts any subset of: name, tripType, startDate,
endDate, origin, destination, reminderOffsetMinutes,
templateName, items (the full replacement array — used for
check-offs, adds, swipe-deletes, and list swaps). Unknown fields are ignored.
PATCH { "items": [ { "name": "Laptop", "packed": true, "verifiedBy": "manual" }, … ] }
The one AI call in PackCheck. Send 1–2 photos of the open bag plus the
item names; the vision model (Claude Haiku) decides which items are visible.
Conservative by design: unsure → missing. Stateless — works without a
database; if tripId is provided, found items are also persisted onto the
trip as packed + verifiedBy: "scan".
// Request — photos are base64 (no data: prefix). Downscale to ≤1280px JPEG (~200-400 KB).
{ "items": ["Laptop", "Laptop charger", "Dress shoes"],
"photos": [ { "mediaType": "image/jpeg", "dataBase64": "…" } ], // 1-2 photos
"tripId": "…" } // optional
// Response — every listed item appears in exactly one array, names verbatim.
{ "found": ["Laptop"], "missing": ["Laptop charger", "Dress shoes"] }
Limits: 1–100 items, ≤ ~2.8 MB base64 per photo, ~4 MB total request. Typical latency 2–6 s.
# every call: -H "X-Device-Id: $DEVICE" (a UUID you generate once)
BASE=https://packcheck-black.vercel.app/api/v1
# 1. templates for the picker
curl -s -H "X-Device-Id: $DEVICE" $BASE/templates
# 2. create the trip (items seeded from Business template)
curl -s -X POST -H "X-Device-Id: $DEVICE" -H "Content-Type: application/json" \
-d '{"name":"Q3 Sales Kickoff","startDate":"2026-08-10","endDate":"2026-08-13"}' \
$BASE/trips
# 3. scan the bag
curl -s -X POST -H "X-Device-Id: $DEVICE" -H "Content-Type: application/json" \
-d "{\"tripId\":\"$TRIP\",\"items\":[\"Laptop\",\"Dress shoes\"],\"photos\":[{\"mediaType\":\"image/jpeg\",\"dataBase64\":\"$B64\"}]}" \
$BASE/scan
# 4. save the edited list for next time
curl -s -X POST -H "X-Device-Id: $DEVICE" -H "Content-Type: application/json" \
-d '{"name":"Boise QBR","items":["Laptop","Dress shoes"]}' $BASE/templates
reminderOffsetHours for future use.