Syncing foundation entities
Before carrier invoices can match or customer invoices can bill, Upwell needs the data they reference: customers, carriers, shipments, companies, and (for AP) bills. This page covers how to push those records from your TMS.If you haven’t read it yet, start with Core concepts & data model — it defines every object mentioned here and how they connect. For patterns that apply to every endpoint (integer-cents money, the
input wrapper, the presigned upload), see Integration patterns.Sync order
Entities reference each other, so push them in dependency order:1
Companies (only if you invoice under more than one identity)
Your own legal entities. Most tenants already have the one they need and can skip this entirely — see Companies.
2
Customers and carriers
These two are independent — sync them in any order (or in parallel). Everything downstream needs at least one of them.
3
Shipments
A shipment references one customer and one carrier by their Upwell IDs (
customerId, carrierId). Sync those first, store the returned IDs, then sync shipments.4
Bills (AP only)
A bill references a shipment and a carrier. Optional for AR-only integrations.
5
Invoices, carrier invoices, payments
These are the transactional records — each guide has its own page: Customer invoices (AR), Carrier invoice submission (AP), Customer payments.
Customers
POST /api/rest/customers creates a customer (shipper). Store the returned id — you’ll need it on shipments and invoices.
To update an existing customer, use
PUT /api/rest/customers/{id} (by Upwell ID) or PUT /api/rest/customers with sourceSystem + sourceSystemId in the body to update by your external key.
The customer’s billing address is a separate record. It isn’t a field on the customer — it’s
an address row with
type: "CUSTOMER_BILLING", and it’s what the invoice bill-to block and
delivery routing read. You can nest it in this POST, but never in a PUT. A customer may
hold at most one. See Working with addresses.Bill-to hierarchy and merging
A customer record carries a few fields beyond the basics above that model billing hierarchies and account cleanup:Carriers
POST /api/rest/carriers creates a carrier.
Updates:
PUT /api/rest/carriers/{id} (by Upwell ID) or PUT /api/rest/carriers (by sourceSystem + sourceSystemId in the request body).
Carriers are the one entity that holds its own address link. Unlike customers and
companies — where the address points at the parent — a carrier carries
addressId and
billingAddressId, and both are settable on update. See
Which direction the link runs.Shipments
POST /api/rest/shipments creates a shipment (load). A shipment ties together a customer and a carrier and is the hub that invoices, bills, and documents hang off.
Sending a numeric status code
Many TMSs store load status as an integer. Upwell’sstatus is typed String, so send the
label, not the code — translate on your side before the call:
1 = Available, 2 = Dispatched, 3 = In Transit, 4 = Delivered, keep that map
in your integration layer and send "DELIVERED" for 4. Sending "4" is accepted — the column
is unvalidated text — but it is strongly discouraged, for a concrete reason: the status is not
just display data. It gates carrier-invoice approval.
Upwell’s TMS Status Mismatch exception lets an operator nominate which statuses mean “this
load is far enough along that its carrier invoice can be approved.” In Payables Settings those
statuses come from two places: a dropdown listing the statuses registered for your
integration, plus a free-text field for anything not registered. Registration is a step on our
side — statuses are not discovered automatically from the loads you send, so a value we have not
registered will never appear in that dropdown and has to be typed in by hand, matching exactly.
That is the practical reason to send labels rather than codes. An operator choosing between
AVAILABLE / DISPATCHED / IN_TRANSIT / DELIVERED can configure the rule correctly; one typing
1 / 2 / 3 / 4 from memory cannot, and a wrong pick either blocks valid invoices or approves
loads that never delivered.
Two practical notes:
- Matching is case-insensitive and whitespace-trimmed, but otherwise exact —
"Delivered","DELIVERED", and"delivered "are the same status;"Delivered to consignee"is a different one. Pick one spelling per status and stay consistent, or the exception rule will silently stop matching. - A shipment with no status at all trips the same exception once the rule is enabled, so
populate
statuson create and keep it current withPUT /api/rest/shipments.
One customer per shipment
This is the modeling question that trips up most TMS integrations, so it’s worth stating flatly:- A shipment has exactly one customer.
customerIdis a single field, not a list. It is the party you will invoice — the bill-to. There is no “customers” collection on a shipment. - The shipper and the consignee are addresses, not customers. They go in the shipment’s own slots:
pickupAddress/pickupAddressId(origin) andconsigneeAddress/consigneeAddressId(destination), typedSHIPMENT_PICKUPandSHIPMENT_CONSIGNEE. - Additional stops go in
stops, each with its own address. A stop has two type fields and they behave differently:stopTypeis free text — nothing validates it, so a typo is accepted silently — whiletypeis constrained by an enum (ORIGIN,PICKUP,DELIVERY,DROP_OFF,TRANSFER,CROSS_DOCK,TERMINAL,CUSTOMS,LAYOVER,RETURN,STORAGE,FUEL_STOP,CONSOLIDATION,DECONSOLIDATION,OTHER). Both are read, by different surfaces — proof-of-delivery exception evaluation and the carrier-invoice shipments view read the constrainedtype, while some outbound EDI and partner payloads readstopType— so populate both, drawing the same value from the enum. One caveat: this endpoint (POST /api/rest/shipments) accepts onlystopType, so a stop created here cannot carry the constrainedtypeuntil it is set through another path. The Vocabulary page defines every value.
customerId and two address records — not two customer records. Only create a customer for a
party you actually invoice.
Because shipper/consignee is a per-shipment role rather than a property of a business, the
same warehouse being the consignee on one load and the pickup on another is completely normal.
That’s two address records with two types, and no deduplication will merge them. See
Addresses.
PUT /api/rest/shipments/{id}. Note that the pickup and consignee links are fixed
at creation — see Working with addresses.
Bills (AP only)
A bill is the expected payable — what your TMS says you should owe a carrier for a shipment. When a carrier invoice arrives, Upwell audits it against the bill and flags mismatches as exceptions.POST /api/rest/bills creates a bill:
Updates:
PUT /api/rest/bills/{id}.
Companies
A company is one of your own legal entities or divisions — not an external business. It carries the identity that appears on the invoices you issue: name, logo, terms of service, MC and DUNS numbers, primary contact details. Your tenant is seeded with one from your account settings, and most integrations never need to create another. You need more than one company when you invoice under multiple identities — separate subsidiaries, acquired brokerages still billing under their own name, or divisions with distinct remit-to branding. If you invoice everything under one name, one company is correct.
Updates:
PUT /api/rest/companies/{id}. As with customers, the update body takes no nested
addresses — write the address with POST/PUT /api/rest/addresses and its companyId.
DELETE /api/rest/companies/{id} is a soft delete, unlike customers and carriers. The
record is retained with deletedAt set and disappears from the app, which frees its name for
reuse. Its addresses are not removed. See
Deleting and archiving.GET /api/rest/companies and GET /api/rest/companies/{id} return the company’s own fields
but not its addresses, and neither accepts a filter.
Vendors
A vendor is a payable party that isn’t a hauling carrier — a lumper service, a factor, a utility, a non-freight supplier.POST /api/rest/vendors creates one:
Updates:
PUT /api/rest/vendors/{id}.
Keeping records in sync
Most TMS integrations run a two-phase sync:- Initial load — push all active customers, carriers, and shipments via
POST. Store the returned Upwell IDs in your system. - Ongoing sync — when a record changes in your TMS, push the update via
PUT. You can update by Upwell ID (PUT /api/rest/<resource>/{id}) or by source-system key (PUT /api/rest/<resource>withsourceSystem+sourceSystemIdin the body).
Webhook-push (real-time)
Webhook-push (real-time)
Your TMS fires a webhook on every create/update, and your integration pushes the change to Upwell immediately. This is the lowest-latency option — records are usually in Upwell within seconds. Best for TMS platforms with outbound webhook support.
Scheduled batch (periodic)
Scheduled batch (periodic)
A cron job queries your TMS for records changed since the last run and pushes them to Upwell in bulk. Simpler to build; latency depends on your schedule (typically 5–15 minutes). Best for TMS platforms with a “changed since” query API.
API-pull (Upwell-initiated)
API-pull (Upwell-initiated)
Upwell calls your TMS’s API on a schedule to pull new/changed records. Requires your TMS to expose a query API. Contact Upwell to set this up.
Bulk create
For the initial load,bulk-customers, bulk-carriers, and bulk-bills let you create many records in one request instead of looping individual POST calls. Each accepts { "inputs": [...] } (plural — see Wrapping POST payloads):
What’s next
Once foundation entities are in Upwell, you can build on top of them:Customer invoices (AR)
Create customer invoices tied to shipments.
Carrier invoice submission (AP)
Submit carrier invoices for audit and matching.
Shipment documents
Attach BOLs, PODs, and other documents to shipments.
Customer payments
Record remittances from customers.

