# Making requests

> Base URLs, headers, HTTP methods, update semantics, lookups by external ID, timestamps and retries for the Kustomer REST API.

Source: https://developer.kustomer.com/docs/get-started/making-requests

The Kustomer API is a JSON-over-HTTPS REST API. Each resource has a URL, HTTP verbs act on it, and every request is authenticated with a bearer token.

_Interactive diagram of the stages an API request passes through: client, org host routing, authentication, authorization by role, rate limiting, validation and the response. Each stage shows the error it can return: 4xx for a region mismatch, 401 for a bad token, 403 for a missing role, 429 when over the rate limit, 400 for an invalid body._

## Base URL

Send requests to your org's API host:

```text
https://<your-org>.api.kustomerapp.com
```

The org-specific host always routes to the region your org lives in, so prefer it to the regional hosts. See [Regions and base URLs](https://developer.kustomer.com/docs/get-started/regions-and-base-urls) for the US, EU and India hosts and when you need them. All requests must use HTTPS.

## Headers

| Header          | Value                                   | Notes                                                              |
| --------------- | --------------------------------------- | ------------------------------------------------------------------ |
| `Authorization` | `Bearer <token>`                        | Required on every request. Note the space after `Bearer`.          |
| `Content-Type`  | `application/json`                      | Required when you send a body.                                     |
| `Accept`        | `application/json`                      | Optional. Responses are always JSON.                               |
| `User-Agent`    | `acme-sync/2.3 (+https://acme.example)` | Recommended. Identifies your integration when you contact support. |

## Methods

| Method   | Use                                               | Typical success                |
| -------- | ------------------------------------------------- | ------------------------------ |
| `GET`    | Read a resource or list                           | `200`                          |
| `POST`   | Create a resource, run a search, start a bulk job | `201` (create), `200` (search) |
| `PUT`    | Update a resource                                 | `200`                          |
| `PATCH`  | Update specific attributes                        | `200`                          |
| `DELETE` | Delete a resource                                 | `204` (most), `200` (some)     |

### Update semantics

`PUT` and `PATCH` both **merge** by default: send only the attributes you want to change, and the rest stay as they are. Endpoints that support it accept `?replace=true` to replace the whole resource instead.

`PUT /v1/customers/{id}` ([reference](https://developer.kustomer.com/api/core-resources/update-customer))

```bash title="cURL"
# Merge: only the tags change
curl -X PUT "https://acme.api.kustomerapp.com/v1/customers/$ID" \
  -H "Authorization: Bearer $KUSTOMER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tags": ["vip", "beta-tester"] }'
```

> **Read each endpoint's notes:** Some attributes have their own rules. For example, on a customer update, omitting `company` unlinks the customer from their company. Sending `tags` sets the full list rather than appending to it. To add or remove single tags on a conversation, use the dedicated [append tags](https://developer.kustomer.com/api/core-resources/append-tags-to-conversation) and [remove tags](https://developer.kustomer.com/api/core-resources/remove-tags-from-conversation) endpoints.

## Looking up by your own IDs

Most objects accept an `externalId`, the ID from your system. Store it when you create the record, and you can fetch it later without keeping a Kustomer ID mapping. Lookups use an `attribute=value` path segment:

`GET /v1/customers/externalId={externalId}` ([reference](https://developer.kustomer.com/api/core-resources/get-customer-by-external-id))

`GET /v1/customers/email={email}` ([reference](https://developer.kustomer.com/api/core-resources/get-customer-by-email))

`GET /v1/klasses/{name}/externalId={externalId}` ([reference](https://developer.kustomer.com/api/core-resources/get-kobject-by-external-id))

URL-encode the value: `jane+test@example.com` becomes `jane%2Btest%40example.com`. A lookup that finds nothing returns `404`, which makes a simple find-or-create pattern:

```ts title="Node.js"
async function upsertCustomer(c: { externalId: string; name: string; email: string }) {
  const found = await fetch(`${base}/v1/customers/externalId=${encodeURIComponent(c.externalId)}`, { headers })
  if (found.status === 404) {
    return fetch(`${base}/v1/customers`, {
      method: 'POST',
      headers,
      body: JSON.stringify({ name: c.name, externalId: c.externalId, emails: [{ type: 'home', email: c.email }] }),
    }).then((r) => r.json())
  }
  const { data } = await found.json()
  return fetch(`${base}/v1/customers/${data.id}`, { method: 'PUT', headers, body: JSON.stringify({ name: c.name }) }).then((r) => r.json())
}
```

## Common query parameters

| Parameter          | Example                  | Effect                                                                                          |
| ------------------ | ------------------------ | ----------------------------------------------------------------------------------------------- |
| `page`, `pageSize` | `?page=2&pageSize=100`   | Page through lists. See [Pagination](https://developer.kustomer.com/docs/concepts/pagination).  |
| `include`          | `?include=company`       | Side-load related resources into an `included` array. Supported values are listed per endpoint. |
| `fields`           | `?fields=name,createdAt` | Return only some attributes, where supported.                                                   |
| `sort`             | `?sort=desc`             | Sort direction or field, where supported.                                                       |

Each endpoint's reference page lists exactly which of these it accepts.

## IDs and timestamps

- **IDs** are 24-character hexadecimal strings such as `5822a4a5aa5f92110005d69a`.
- **Timestamps** are ISO 8601 in UTC, for example `2026-10-21T14:02:11.925Z`. Send them the same way.
- **Imported history.** Set `importedAt` when you load historical conversations, messages or custom objects. Records with `importedAt` skip the per-customer create rate limit, and you can backdate them with `createdAt`.
- **Author fields.** `createdAt`, `createdBy`, `modifiedAt` and `modifiedBy` are normally set from the token. Overriding them needs the `org.admin` role.

## Idempotency and retries

The REST API has no idempotency-key header. Design writes so they can be repeated safely:

- Set `externalId` on everything you create, and look it up before creating (as above).
- For bulk jobs, set `_clientRef` on each item to match results to inputs. See [Bulk operations](https://developer.kustomer.com/docs/guides/bulk-operations).
- Retry `429` after the time in `x-ratelimit-reset`. Retry `5xx` and network errors with exponential backoff and jitter.
- Don't retry other `4xx` responses: fix the request instead.

```ts title="Node.js"
async function kfetch(path: string, init: RequestInit = {}, attempt = 0): Promise<Response> {
  const res = await fetch(`${base}${path}`, { ...init, headers: { ...headers, ...init.headers } })
  if (res.status === 429 && attempt < 5) {
    const reset = Number(res.headers.get('x-ratelimit-reset')) * 1000
    await sleep(Math.max(reset - Date.now(), 1000))
    return kfetch(path, init, attempt + 1)
  }
  if (res.status >= 500 && attempt < 5) {
    await sleep(2 ** attempt * 500 + Math.random() * 250)
    return kfetch(path, init, attempt + 1)
  }
  return res
}

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
```

## Try it from the reference

Every operation in the [API reference](https://developer.kustomer.com/api) has a **Try it** panel. Paste a key, pick your region, and send a real request from your browser. Keys stay in memory and are never stored. Write requests ask you to confirm first.

## Use Postman

Download the [Postman collection](https://developer.kustomer.com/api/kustomer.postman_collection.json) and import it into Postman. It has every operation in the reference, grouped by API, with example parameters and bodies. Set the `orgName` and `apiKey` collection variables, and requests go to `https://{orgName}.api.kustomerapp.com` with your key as a bearer token. To use a regional host instead, change `baseUrl`. Optional query parameters and headers are included but turned off. Each request's description lists the roles it requires and links to its reference page.
