> ## Documentation Index
> Fetch the complete documentation index at: https://docs.upwell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Working with addresses

> The address playbook: per-parent link direction, create/attach/update tasks, load-bearing address types, visibility in the app, the one-billing-address rule, and read-back paths.

Addresses behave differently from every other object in the API, and the difference causes real integration bugs. Read [Addresses on the data-model page](/concepts/data-model#addresses) for *why*; this section is the *how*.

Three rules govern everything below:

1. **An address belongs to one parent.** It is not shared master data and is never deduplicated by content. The same physical place used in two roles is two address records.
2. **There is no external key.** Addresses have no `sourceSystem`/`sourceSystemId`, so there is no upsert-by-your-id. **Store the `addr_` id Upwell returns**, keyed by parent and address type, or you lose your ability to update that address later.
3. **You can embed an address when you create its parent. You can never embed one on an update.** Updates always take a separate call.

<Warning>
  The nested `addresses` object exists on **create** inputs only. Sending it on a `PUT`/`PATCH` does nothing useful — the update inputs for customers, companies, and shipments have no address field at all. If an address "silently didn't save" on an update, this is why.
</Warning>

## Which direction the link runs

This is the part that surprises people: **the foreign key lives on a different side depending on the entity.**

| Parent   | Where the link lives                                          | To attach an existing address                                                                                                                                                  |
| -------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Customer | On the **address** (`customerId`)                             | `PUT /api/rest/addresses/{addressId}` with `{ "customerId": "cus_…" }`                                                                                                         |
| Company  | On the **address** (`companyId`)                              | `PUT /api/rest/addresses/{addressId}` with `{ "companyId": "comp_…" }`                                                                                                         |
| Contact  | On the **address** (`contactId`)                              | `PUT /api/rest/addresses/{addressId}` with `{ "contactId": "…" }` — but note contacts aren't exposed as a REST resource, so this only applies if you already hold a contact id |
| Carrier  | On the **carrier** (`addressId`, `billingAddressId`)          | `PUT /api/rest/carriers/{carrierId}` with `{ "billingAddressId": "addr_…" }`                                                                                                   |
| Shipment | On the **shipment** (`pickupAddressId`, `consigneeAddressId`) | **Not possible after creation** — see the warning below                                                                                                                        |

So for a customer or company you patch *the address*; for a carrier you patch *the carrier*. Patching the customer itself does nothing, because there is no field there to accept it.

<Warning>
  **A shipment's pickup and consignee addresses can only be *linked* when the shipment is created.** At creation you can either embed a new address (`pickupAddress` / `consigneeAddress`) or point at an existing one (`pickupAddressId` / `consigneeAddressId`). After that the link is fixed: the shipment update input exposes neither id field, and there is no dedicated endpoint to relink them.

  This is rarely a problem in practice, because correcting the **address record** is almost always what you actually want — `PUT /api/rest/addresses/{id}` changes it in place and the shipment keeps pointing at it. You only hit the wall if you need to swap a shipment onto a *different* address row.
</Warning>

## Task: create a record with its address

Embed the address under its parent. Use `{ "data": [ … ] }` for a list of addresses (customers, companies) and `{ "data": { … } }` for a single slot (a shipment's pickup or consignee).

```bash theme={null}
# A customer with its billing address, in one call
curl -X POST https://api.upwell.com/api/rest/customers \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": {
      "name": "Acme Manufacturing",
      "sourceSystem": "YOUR_TMS",
      "sourceSystemId": "CUST-10042",
      "addresses": {
        "data": [
          {
            "type": "CUSTOMER_BILLING",
            "streetLine1": "123 Main St",
            "city": "Chicago",
            "stateOrProvince": "IL",
            "zipOrPostalCode": "60601",
            "country": "US"
          }
        ]
      }
    }
  }'
```

Don't set `customerId` inside a nested address — the parent supplies it. **Read the created address ids out of the response and store them**, because this is the only moment they're handed to you without a lookup.

<Warning>
  **Companies are the exception, and it's a trap.** `POST /api/rest/companies` returns the
  company's own fields only — the nested addresses come back with **no ids**. Since a company
  exposes no address read path either, and addresses have no `sourceSystem`/`sourceSystemId` to
  look them up by, an address created that way is recoverable only by paging the unfiltered
  `GET /api/rest/addresses` list.

  If you need the id — and you do, to ever update that address — create it standalone instead:
  `POST /api/rest/addresses` with `companyId` and `type: "COMPANY_ADDRESS"`, which returns the
  `addr_` id directly. Nested creation is fine for customers and shipments, whose responses do
  include the new ids.
</Warning>

## Task: add an address to a record that already exists

Two calls, and the second one depends on the entity (see the direction table above).

<Steps>
  <Step title="Create the address on its own">
    `POST /api/rest/addresses` with the address fields, its `type`, and — for a customer or company — the parent id (`customerId` / `companyId`) directly on the address.
  </Step>

  <Step title="Link it, if the parent holds the link">
    For a **carrier**, `PUT /api/rest/carriers/{id}` with `addressId` or `billingAddressId`. For a customer or company you're already done: step 1 set the link.
  </Step>
</Steps>

```bash theme={null}
# Step 1 — create the address, pointing it at its customer
curl -X POST https://api.upwell.com/api/rest/addresses \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": {
      "type": "CUSTOMER_BILLING",
      "customerId": "cus_abc123",
      "streetLine1": "456 Dock Rd",
      "city": "Joliet",
      "stateOrProvince": "IL",
      "zipOrPostalCode": "60431",
      "country": "US"
    }
  }'
# -> { "createAddress": { "id": "addr_xYz789", ... } }   <- store this id
```

## Task: change an existing address

If you kept the `addr_` id, this is one call:

```bash theme={null}
curl -X PUT https://api.upwell.com/api/rest/addresses/addr_xYz789 \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "input": { "streetLine1": "789 New Dock Rd" } }'
```

If you didn't keep it, fetch the parent and read the address off it first — `GET /api/rest/customers/{id}` returns the customer's addresses, and `GET /api/rest/carriers/{id}` returns `addressId` / `billingAddressId`. Match on the address **`type`** to pick the right one.

<Note>
  Updating the address in place is almost always what you want. Because the parent points at the same `addr_` id, every record using that address sees the change — no relinking needed.
</Note>

## Address types

`type` is **required on every address** — there is no default and no fallback. It records the
role the address plays, and several product surfaces query it directly, so a valid address
carrying the wrong type saves successfully and then does nothing.

These are the ones an integration sets:

| Type                 | Use for                                     | What depends on it                                                                                                                                                                                                                                                          |
| -------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CUSTOMER_BILLING`   | A customer's billing address                | **Load-bearing.** The invoice bill-to block, EDI output, invoice delivery routing, and the customer detail page all select on this exact value — the wrong type means no billing address is found.                                                                          |
| `COMPANY_ADDRESS`    | One of your own entity's addresses          | **Load-bearing.** The company pages query `type = COMPANY_ADDRESS` and render nothing else — an address on a company with any other type is invisible in the app. Companies have a single address type today; there's no separate street/mailing/billing distinction.       |
| `SHIPMENT_PICKUP`    | A shipment's origin                         | Routing reads the shipment's pickup *slot*, so the slot matters more than the label — but set it correctly anyway.                                                                                                                                                          |
| `SHIPMENT_CONSIGNEE` | A shipment's destination                    | Same as pickup: the slot is what's read.                                                                                                                                                                                                                                    |
| `CARRIER`            | A carrier's primary address                 | Descriptive.                                                                                                                                                                                                                                                                |
| `CARRIER_BILLING`    | Where a carrier wants payment sent          | Descriptive; remit-to logic is driven by factoring documents, not this field.                                                                                                                                                                                               |
| `CONTACT`            | An address on a contact record              | Descriptive.                                                                                                                                                                                                                                                                |
| `STOP`               | An address used by a route stop             | Descriptive.                                                                                                                                                                                                                                                                |
| `ADDRESS_BOOK`       | A saved address not tied to a specific role | **Inert — nothing reads it.** No product surface queries `ADDRESS_BOOK`: it won't appear on a customer or company page, won't be used as a bill-to, and won't drive routing. It is not a safe generic default. Always use the specific type for the role the address plays. |

Upwell also writes `SUPPORTING_DOC_PARTY`, `DELIVERY_RECEIPT`, and `LUMPER_RECEIPT` addresses from its own document-parsing pipelines. You'll see them on reads; you shouldn't create them.

<Note>
  **One physical place, two roles, two records.** If the same warehouse is both the consignee on one shipment and the pickup on another, that's two address records with two different types. This is expected — there's no way to share one row across two type slots, and no dedup will merge them.
</Note>

## Where an address shows up in the app

An address can save cleanly — `201`, real `addr_` id, readable back over the API — and still
appear nowhere in the UI. That is not a bug and it produces no error: **the pages filter on
address `type`**, and they select a narrow set of fields.

| Page            | Address types it will render                                                         | Fields it loads                                                                 |
| --------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
| Company detail  | Only `COMPANY_ADDRESS`                                                               | street lines, city, state/province, postal code, country — **and nothing else** |
| Company list    | Only `COMPANY_ADDRESS`, and only the **oldest** one (`createdAt` ascending, limit 1) | same                                                                            |
| Customer detail | Only `CUSTOMER_BILLING`                                                              | the full address record, including `name`, `companyName` and `phoneNumber`      |

Two consequences worth internalizing before you debug a "missing" address:

* **An `ADDRESS_BOOK` address on a customer or company is invisible.** It exists, it's linked, and no page will ever draw it. This is the single most common cause of "the API accepted it but I can't see it."
* **An address `phoneNumber` never reaches a company page.** The company pages don't load that field at all, so putting a phone number on a `COMPANY_ADDRESS` row is a dead end. The phone shown on a company record is the company's own `primaryPhone` — a field on the company itself, set with `PUT /api/rest/companies/{id}`. (Customer pages *do* load the address phone number, so this asymmetry is company-specific.)

<Tip>
  Debug in this order:

  1. **`GET /api/rest/addresses/{addressId}`** — confirms the row saved and returns its `type`. Check that against the table above: the type is wrong far more often than the write failed.
  2. **Confirm the link from the parent** — the address endpoints never return `customerId` or `companyId`, so an orphaned address is indistinguishable from a linked one when you fetch it directly. For a **customer**, `GET /api/rest/customers/{id}` and look for it in `addresses`. For a **carrier**, `GET /api/rest/carriers/{id}` and check `addressId` / `billingAddressId`.

  <Warning>
    **Step 2 has no company equivalent.** `GET /api/rest/companies/{id}` returns no `addresses`, and the address endpoints return no `companyId`, so the link is exposed by neither side. Step 1 still works — you can always read the `type` back — but if the type is right and the address still doesn't render, you cannot confirm the link over the API. Two things to check before assuming it's broken: that the address carries a `companyId` at all — if you created it standalone you had to send one, whereas the nested form on `POST /api/rest/companies` sets it for you — and, on the company **list** view only, the oldest-address rule above.
  </Warning>
</Tip>

## One billing address per customer

A customer can hold many addresses, but **at most one `CUSTOMER_BILLING` address**. This is a
database constraint, not a convention — a second one is rejected outright:

```
Uniqueness violation. duplicate key value violates unique constraint
"idx_addresses_unique_customer_billing"
```

So "create the billing address" is only safe the first time. The supported pattern for keeping
one in sync — the same one Upwell's own TMS integrations use — is **match by type, then
update in place**:

<Steps>
  <Step title="Read the customer's existing addresses">
    `GET /api/rest/customers/{id}` returns its `addresses`, each with its `type`.
  </Step>

  <Step title="Find the CUSTOMER_BILLING row">
    Match on `type`, not on street or city — the address content is what changes.
  </Step>

  <Step title="Update it if present, create it if absent">
    Present → `PUT /api/rest/addresses/{addressId}`. Absent → `POST /api/rest/addresses` with `customerId` and `type: "CUSTOMER_BILLING"`.
  </Step>
</Steps>

This is also why re-sending a full customer payload with a nested `addresses` block is not a
safe "upsert" — the nested block always *inserts*. Use it on first creation only.

<Note>
  No other address type is constrained this way. A customer may hold any number of
  `ADDRESS_BOOK` or `CONTACT` rows, and a company any number of `COMPANY_ADDRESS` rows (though
  only the oldest shows in the company list view).
</Note>

## Reading addresses back

Read paths are narrower than write paths, which matters when you're verifying a sync:

| To read                       | Use                                   | Caveat                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| ----------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A customer's addresses        | `GET /api/rest/customers/{id}`        | Returns `id`, `type`, street lines 1–2, city, state/province, postal code, country. Does **not** return `phoneNumber`, `name`, or `streetLine3`.                                                                                                                                                                                                                                                                                                        |
| A company's addresses         | `GET /api/rest/addresses/{addressId}` | `GET /api/rest/companies/{id}` and `GET /api/rest/companies` **return no addresses at all.** You need the `addr_` id from the create response, or the full address list.                                                                                                                                                                                                                                                                                |
| A carrier's addresses         | `GET /api/rest/carriers/{id}`         | Returns `addressId` / `billingAddressId`; fetch each address separately.                                                                                                                                                                                                                                                                                                                                                                                |
| One address, widest field set | `GET /api/rest/addresses/{addressId}` | Returns every address field **except its links**: no `customerId`, `companyId`, `contactId`, `canonicalAddressId` or `locationCodeId`, even though all five are writable. To learn which parent an address belongs to you must read the parent, and only two parents expose it — a customer (`addresses`) and a carrier (`addressId` / `billingAddressId`). Company, contact, canonical-address and location-code links are readable from neither side. |

<Warning>
  **The list endpoints take no filters.** `GET /api/rest/addresses`, `GET /api/rest/companies`
  and the other collection `GET`s accept only `limit` and `offset` — there is no `where`
  parameter. Server-side filtering exists only on the `POST /…/search` endpoints, and those
  exist for customers, carriers, shipments, shipment line items, invoices, bills, bill payments,
  customer payments, customer payment line items, vendors, purchase orders and vendor invoices —
  **not** for addresses or companies.

  Practically: **store the `addr_` id when you create an address.** Recovering it later means
  paging the whole collection or going through the parent.
</Warning>
