GoatLabels GoatLabels
One file in. Every label out.

Bulk shipping labels from a CSV


Mapped once. Checked before you pay.

Upload a CSV, map your columns to shipping fields once and save the mapping, then validate every row before any money moves. Rows with a bad address are flagged, the rest are quoted across four carriers, and the file is purchased row by row from one prepaid wallet.

Every row rate-shopped, not the file as a lump

USPS, UPS, FedEx, and DHL quote each row's own parcel, so a heavy row and a light row in the same upload can land on different carriers.

UPSFedExDHLUSPS all quoted
Per-row quotes Not one carrier for the file.
Validated first Before any money moves.
All-in price Nothing added at checkout.

One prepaid wallet for the whole file

Rows are bought one at a time against a single balance, so a half-finished file leaves you with exactly the labels you paid for and nothing in between.

VisaMastercardAmerican ExpressApple PayGoogle PayBitcoinEthereumTether (USDT)
Debited per row Card, Apple Pay, Google Pay, ACH, crypto.
Failed rows retry Individually, not the whole file.
Re-run is safe Bought rows are skipped, not doubled.

What a shipping CSV actually has to contain

Less than most people assume, and in a stricter form than most people expect. Eight fields have to resolve before a row can be bought: recipient name, address line 1, city, a two-letter ISO country code, weight, length, width, and height. That is the whole required set. Everything else in the catalog is optional — order number, company, email, phone, address line 2, state or province, postal code, package type, a pinned carrier, a pinned service, and the three customs columns of declared value, item description, and HS code.

Two of those deserve calling out because they are where clean-looking files break. Country has to be ISO-2 — US, GB, HK — not "United States" and not "USA", because that code is what selects which carrier services are even eligible for the row. And the three dimensions are required, not the nice-to-have they are on a single manual shipment, because a file is quoted without a human looking at each box. The one exception is a USPS flat-rate container: map the package type column instead and the dimensions stop mattering, since the container defines them.

Your file almost certainly does not use those names, and it does not have to. Headers are matched after lower-casing and stripping punctuation, so "Ship To Postal Code", "shipping_zip" and "Zip Code" all land on the same field, and the usual Shopify, Etsy, Amazon and eBay export headers are recognised on sight. One deliberate exception: any column starting with "From" is never auto-mapped, because bulk import always ships from your account's saved sender address and marketplace exports habitually put junk in their From columns.

Mapping columns once and saving it: the second file takes minutes

Auto-detection gets most of the way and then stops, because no heuristic knows whether your "Total" column is the order total or the declared customs value. The mapping step is one dropdown per field, grouped by recipient, address, package, customs, and service, with the required ones marked — you finish what auto-detection started, set whether weights are pounds or kilos and dimensions inches or centimetres, and you are done.

Then save it under a name. "Etsy export", "backer survey", "wholesale PO". The next file of the same shape applies that preset in one click, and the presets list is yours to manage — apply, rename by saving again, delete. This is the difference between a one-off import and a repeatable process: the first file costs you ten minutes of mapping, and every file after it costs you the upload. If you are fulfilling a crowdfunding campaign, the late-pledge file and the replacement-parts file are the same shape as the original survey export, which is exactly the case presets are for — shipping labels for Kickstarter covers the rest of that job. Wholesale sellers get the same benefit from the opposite direction: a Faire purchase order has the same columns every month, and shipping labels for Faire explains why each carton in a multi-carton order needs its own row.

Validating before you pay, not after the boxes come back

The validate step is the reason to do this in a wizard rather than a script that buys as it reads. Every row is checked before any money moves: required fields present, address accepted by the carrier network, country code valid, weights and dimensions in range, phone and email well-formed where they are supplied, and a restricted-goods keyword scan that mirrors the one a single shipment gets — so a row that would be refused at purchase is refused now, at the point where fixing it is free.

Rows come back with their problems named on the row rather than as a count at the top. A missing secondary unit, a postal code that does not match the city, an ISO code that is actually a country name — you edit, re-check, and move on. Good rows do not wait for bad ones; they proceed to quoting while you repair the rest. This is the single highest-value step in the whole flow, because the alternative version of catching these is a pallet of returns three weeks later, and what happens to a parcel that comes back costs the label twice plus the packaging.

Once rows are valid they are quoted in batch against USPS, UPS, FedEx, and DHL, and each row takes the cheapest all-in option for its own parcel — not one carrier chosen for the whole file. Rows that have to go a specific way keep their pinned carrier or service from the mapped columns. International rows use the customs columns you mapped; duties default to the recipient unless you explicitly choose to prepay them, and what to write on the form is on customs forms for small parcels.

What happens when a row fails mid-file

Purchasing runs sequentially, one row at a time, and that is a deliberate design decision rather than a performance limitation. A parallel fan-out would let two rows clear the same wallet-balance check against the same starting balance and over-debit you; serialising the purchases makes the balance arithmetic exact. It also means a failure is contained to its own row instead of poisoning the batch.

So when a row fails — the carrier declines it, the address turns out to be undeliverable at purchase time, the balance will not stretch — that row reports its own error and the file carries on or stops with the reason visible. You get a summary of how many purchased, how many were skipped, and how many failed, and you can retry a single failed row without touching the ones that worked. Re-running a file you have already partly bought is safe: rows with a label already attached are recognised and skipped rather than purchased a second time.

One thing to know before a very large run: on the Free plan the monthly shipment allowance is enforced per row as the batch executes, so a file that starts under the cap stops exactly at it rather than silently overshooting, and the remaining rows report why. A single file is capped at 500 rows, so larger exports get split — which is a reasonable review checkpoint anyway. The plan detail is on pricing.

Four steps, in this order

Upload, map, validate, then buy.

The order matters more than the steps do. Mapping happens before validation so the checker knows which column is the postal code; validation happens before quoting so a broken row never reaches a carrier; quoting happens before purchase so you approve the price. Money moves last, one row at a time.

1
One file in
However your platform exports it
orders-export.csv

Ship To Name, Ship To Address 1, Ship To City, Ship To Zip, Weight (lbs) — whatever your platform decided to call them.

Shopify export
Order CSV, unedited
TikTok Shop export
Different headers, same mapping
Parsed preview
Required fields
8
Optional fields
13
Country format
ISO-2
Row cap per file
500
Drop a .csv — headers auto-detected

Marketplace exports, warehouse extracts, backer surveys, wholesale POs — anything that saves as a .csv.

2 Map & validate

Mapped once, then checked row by row.

Billy AI
  • One dropdown per field, grouped by recipient, address, package, customs, service
  • Saved as a named preset — the next file of the same shape is one click
  • Every row validated before any money moves, with the reason on the row
  • Good rows carry on to quoting while you repair the bad ones
Mapped. Validated. Quoted.
3
Bought row by row
Cheapest of four, per parcel
One prepaid wallet
Debited per row, never in parallel
per row
USPS
Labels purchased
Failed rows retry individually
resumable

Purchased, skipped, and failed are counted separately — and re-running a file never buys a row twice.

Four things that make a batch survivable

The hard part of bulk is not printing.
It is everything before it.

Anyone can loop over a file and call an API. What decides whether a 400-row import is a good afternoon or a bad month is what happens to the rows that are wrong, and whether the money moved before or after you found out.

One wallet,
debited row by row

Rows are purchased sequentially against a single prepaid balance, so two rows can never clear the same balance check and over-debit you. Fund it with a card, Apple Pay, Google Pay, ACH, or crypto. Stop halfway and you own exactly the labels you paid for.

Wallet balance
$1,247.36 USD
AI

Saved mapping presets,
named by you

Headers are auto-detected from the common marketplace exports, you finish the mapping by hand, and then you save it. Apply it to the next file in one click. The first import costs ten minutes; every import after it costs the upload.

Billy LIVE
Where's the package to Maya?
It's in transit via UPS. Expected Friday, Aug 7.
UPS
1Z8923A6B05G4123456
In transit
Austin, TX → Dallas, TX
Ask Billy anything...

Per-row validation
before any money moves

Required fields, carrier address validation, ISO-2 country codes, weight and dimension ranges, phone and email format, and a restricted-goods scan — all at the validate step, all with the failure reason on the offending row. Fixing is free; printing a bad row is not.

Shipment summary
UPS Ground $12.49
Insurance $0.75
Fuel surcharge $0.32
Total $13.56

Four carriers quoted
across the file

USPS, UPS, FedEx, and DHL price each row's own parcel and the cheapest all-in option wins per row, so the file does not get flattened onto one carrier. Rows that must go a specific way keep a pinned carrier or service from their mapped columns.

Compare rates
UPS UPS $12.48
FedEx FedEx $13.22
DHL DHL $14.10
USPS USPS $11.79
Duties & taxes paid

Or skip the wizard
and call the API

If the file is really a database query, the REST API does the same job programmatically with signed webhooks, real idempotency keys so a retried row never buys two labels, and unlimited calls on every plan.

POST /v1/shipments
{
"to" : { "name" : "Maya" },
"from" : { "name" : "Alex" },
"service" : "ups_ground" ,
"label_format" : "pdf"
}
Webhook delivered
Whose orders arrive as a file

Four jobs that are a spreadsheet first.

These are the shapes where orders never arrive one at a time: they arrive as an export, a survey, a purchase order, or a migration — and the label work is a batch from the moment it starts.

Crowdfunding creators

One survey export, hundreds of identical boxes

Highest volume, once

A backer survey is the purest bulk case there is: one file, one parcel spec, and addresses that have been sitting in a form for a year. Map it once, validate every row before you print, and the late-pledge file reuses the same preset.

500 rows per file saved preset per-row validation
goatlabels.io/dashboard/shipments
GoatLabels shipments queue with purchased labels

Wholesale and B2B brands

Purchase orders, not shopping carts

Wholesale orders arrive as a PO or a buyer's spreadsheet, often with several cartons per account and a column layout that never changes from month to month. That stability is exactly what a saved mapping preset is for.

Pinned where it matters

Map the carrier column to hold one account's rows to one carrier

Sellers migrating a backlog

Moving off another platform mid-queue

exported 240 unshipped orders from the old tool
Mapped, validated, and bought in one pass

Anyone whose orders start as a query

Warehouse extracts and internal systems

8

required columns — the rest of the catalog is optional

Bulk CSV labels, answered

Can I upload a CSV to print shipping labels in bulk?
Yes. Upload a .csv, map its columns to shipping fields, validate every row, and purchase the file row by row. It is a four-step wizard — upload, map columns, validate and quote, purchase — and a single file can carry up to 500 rows. Larger exports are split into several files, which is also how you keep a very large batch reviewable.
What columns does the file need?
Eight fields have to resolve for a row to validate: recipient name, address line 1, city, a two-letter ISO country code, weight, and the three box dimensions. Everything else is optional — order number, company, email, phone, address line 2, state, postal code, package type, a pinned carrier or service, and the customs columns of declared value, item description, and HS code. If you use a USPS flat-rate container you map the package type instead of supplying dimensions.
What happens to rows with a bad address?
They are flagged at the validate step, before any money moves, with the reason on the row — a missing required field, an address the carrier rejects, a country code that is not valid ISO-2, a phone or email that is malformed. You fix the row and re-run the check. Nothing about a failed row blocks the good rows; they carry on to quoting while you repair the rest.
Can I rate-shop the whole file, or do I pick a carrier first?
Rate-shop the whole file. Every valid row is quoted against USPS, UPS, FedEx, and DHL and the cheapest option for that specific parcel is selected per row, so a heavy row and a light row in the same upload can end up on different carriers. If a row has to go on a particular carrier or service, map the optional carrier and service columns and that row is pinned while the rest keep rate-shopping.
What happens if my wallet runs out halfway through the file?
Purchasing runs one row at a time on purpose, so the balance arithmetic can never be raced by two rows at once. When the balance will not cover the next row, that row fails with the reason and purchasing stops there — the labels already bought are bought and printable, and nothing partial or duplicated is left behind. Top the wallet up and re-run the file: rows that already purchased are recognised and skipped rather than bought twice.
Do I have to remap the columns every time?
No. Save the mapping under a name once — "Etsy export", "backer survey", "wholesale PO" — and apply it to the next file of the same shape in one click. Headers are auto-detected first anyway, because the common marketplace exports use predictable names, so the saved preset mostly exists to nail the columns that auto-detection cannot guess.
No monthly minimum to print. No per-file fee.

Stop pasting addresses
one row at a time.

Upload the export, map the columns once, let every row be validated before a cent moves, then buy the file against USPS, UPS, FedEx, and DHL from one prepaid wallet.

Sign up in seconds. No card required. Save the mapping and the next file is a one-click job.