Your first pack in 3 minutes
No dashboard needed — mint a key from the command line and call the endpoint. Full field list in the API reference.
Get a test key
curl -X POST https://packfit.dev/api/signup \
-H 'content-type: application/json' \
-d '{ "email": "you@company.com", "company": "Company GmbH" }'
# → { "accountId": "…", "apiKey": "pf_test_…", "plan": "free" }The key is shown once. We store only its HMAC, so it cannot be recovered — losing it means minting a new one. The free plan includes 1,000 packs per month.
Describe the order and your cartons
curl https://packfit.dev/v1/pack \
-H "Authorization: Bearer $PACKFIT_KEY" \
-H 'content-type: application/json' \
-d '{
"items": [
{ "sku": "MUG", "lengthMm": 120, "widthMm": 100, "heightMm": 110,
"weightG": 380, "quantity": 6 },
{ "sku": "PAN", "lengthMm": 240, "widthMm": 240, "heightMm": 60,
"weightG": 1450 },
{ "sku": "KNIFE-BLOCK", "lengthMm": 140, "widthMm": 110, "heightMm": 240,
"weightG": 1900, "thisSideUp": true }
],
"cartons": [
{ "id": "S", "innerLengthMm": 200, "innerWidthMm": 150,
"innerHeightMm": 100, "maxPayloadG": 5000, "costCents": 35 },
{ "id": "M", "innerLengthMm": 400, "innerWidthMm": 300,
"innerHeightMm": 260, "maxPayloadG": 20000, "costCents": 55 }
],
"dimWeight": { "divisorCm3PerKg": 5000 }
}'Millimetres and grams, always integers. The packer only chooses from the cartons you send.
Use the plan
{
"cartons": [{
"cartonId": "M",
"fillRate": 0.64,
"grossWeightG": 6690,
"billableWeightG": 7000,
"placements": [
{ "sku": "PAN", "x": 0, "y": 0, "z": 0,
"lengthMm": 240, "widthMm": 240, "heightMm": 60, "orientation": 0 }
]
}],
"unpacked": [],
"inputFingerprint": "12zzv44c"
}Three ways to spend it: quote billableWeightG at checkout for an exact shipping price; hand placements to the warehouse as a pack instruction; or store inputFingerprint and skip the call entirely next time the same basket appears.
Same call from your stack
const res = await fetch("https://packfit.dev/v1/pack", {
method: "POST",
headers: {
authorization: `Bearer ${process.env.PACKFIT_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({ items, cartons, dimWeight: { divisorCm3PerKg: 5000 } }),
});
if (res.status === 402) throw new Error("Packfit quota exhausted");
const plan = await res.json();
console.log(plan.cartons.length, "parcel(s)", plan.totalBillableWeightG, "g billable");import os, requests
r = requests.post(
"https://packfit.dev/v1/pack",
headers={"Authorization": f"Bearer {os.environ['PACKFIT_KEY']}"},
json={"items": items, "cartons": cartons, "dimWeight": {"divisorCm3PerKg": 5000}},
timeout=10,
)
r.raise_for_status()
plan = r.json()
print(len(plan["cartons"]), "parcel(s)", plan["totalBillableWeightG"], "g billable")Watch your quota
Packfit-Quota-Limit: 1000
Packfit-Quota-Used: 37
Packfit-Quota-Remaining: 963
Packfit-Quota-Period: 2026-07
RateLimit-Limit: 60
RateLimit-Remaining: 59A 402 means the monthly quota is gone; 429 means you are calling too fast — back off using Retry-After. Note that a 422 (bad item data) never costs quota.
Upgrade when you outgrow the free tier
curl https://packfit.dev/api/billing/checkout \
-H "Authorization: Bearer $PACKFIT_KEY" \
-H 'content-type: application/json' \
-d '{ "plan": "growth" }'
# → { "url": "https://checkout.stripe.com/…" } ← open in a browserPaid plans allow overage instead of hard-stopping your checkout at the quota — see pricing.
Not sure it is worth it?
Send a month of historical orders through the savings audit and see the delta before you integrate anything.