# Customers API

> Create, update and look up your saved customers with the Anytime Capital Payments API, one per email address, to match payments and invoices to them.

Source: https://anytime-capital.com/docs/customers · Markdown: https://anytime-capital.com/docs/customers.md · All docs: https://anytime-capital.com/docs/llms-full.txt

Customers are the clients saved on your **Customers** page. The API lists them, finds one by email, adds them and updates them, with the same rules as the dashboard: one customer per email address, in any letter case. Card buyers are added automatically when they first pay, so look a customer up by email before you create one.

| Topic | Detail |
| --- | --- |
| Endpoints | [`POST /api/v1/customers`](#create-customer), [`GET /api/v1/customers`](#list-customers), [`GET /api/v1/customers/{id}`](#get-customer), [`PATCH /api/v1/customers/{id}`](#update-customer) |
| Key permissions | `customers:write`, `customers:read` |
| Samples | curl; Node.js 18+ with the built-in `fetch` (in an ES module); the [Node SDK](https://anytime-capital.com/docs/node-sdk.md) (preview: not on npm yet); Python with `requests`. |
| Every request can also answer | 401 `UNAUTHORIZED`, 401 `API_KEY_EXPIRED`, 403 `NOT_ENROLLED`, 403 `PERMISSION_DENIED`, 403 `IP_NOT_ALLOWED`, 429 `RATE_LIMITED`, 500 `INTERNAL_ERROR`. See [Error codes](https://anytime-capital.com/docs/error-codes.md). |

## The customer object

```json
{
  "object": "customer",
  "livemode": true,
  "id": "cus_1042",
  "name": "Jordan Lee",
  "email": "jordan@example.com",
  "phone": "+1 555 0100",
  "company": "Lee Studio",
  "notes": null,
  "billingAddress": null,
  "taxId": null,
  "country": "US",
  "createdAt": "2026-09-02T11:20:00.000Z"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `object` | string | The type of object. Always `customer`. |
| `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. |
| `id` | string | `cus_` followed by digits. |
| `name` | string | Name. |
| `email` | string or null | Email. |
| `phone` | string or null | Phone. |
| `company` | string or null | Company. |
| `notes` | string or null | Your notes; never shown to the customer. |
| `billingAddress` | object or null | A postal address printed on invoices and quotes. |
| `billingAddress.line1` | string | Street address. Up to 200 characters. |
| `billingAddress.line2` | string or null | Apartment, suite, unit. Up to 200 characters. |
| `billingAddress.city` | string | City. Up to 100 characters. |
| `billingAddress.state` | string or null | State, province or region. Up to 100 characters. |
| `billingAddress.postalCode` | string | Postal code. Up to 20 characters. |
| `billingAddress.country` | string | ISO 3166-1 alpha-2 country code. |
| `taxId` | string or null | VAT, EIN or other tax number. |
| `country` | string or null | ISO 3166-1 alpha-2, from what you saved or their card's billing address. |
| `createdAt` | string (date-time) | Added. |

## Endpoints

### Create a customer

`POST /api/v1/customers`

Permission: `customers:write`. Supports `Idempotency-Key`.

One customer per email: 409 DUPLICATE_CUSTOMER otherwise. Card buyers are also added on their own when they first pay, so look up by email before creating.

**Headers**

| Header | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). |

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes | The customer's name. Up to 255 characters. |
| `email` | string (email) | no | One customer per email address. Up to 255 characters. |
| `phone` | string | no | Phone. Up to 50 characters. |
| `company` | string | no | Company. Up to 255 characters. |
| `notes` | string | no | Your notes. Up to 2000 characters. |
| `billingAddress` | object or null | no | A postal address printed on invoices and quotes. |
| `billingAddress.line1` | string | yes | Street address. Up to 200 characters. |
| `billingAddress.line2` | string or null | no | Apartment, suite, unit. Up to 200 characters. |
| `billingAddress.city` | string | yes | City. Up to 100 characters. |
| `billingAddress.state` | string or null | no | State, province or region. Up to 100 characters. |
| `billingAddress.postalCode` | string | yes | Postal code. Up to 20 characters. |
| `billingAddress.country` | string | yes | ISO 3166-1 alpha-2 country code. |
| `taxId` | string or null | no | VAT, EIN or other tax number. Up to 64 characters. |

**Request (curl)**

```bash
curl -X POST https://anytime-capital.com/api/v1/customers \
  -H "Authorization: Bearer $ANYTIME_API_KEY" \
  -H "Idempotency-Key: customer-jordan" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jordan Lee",
    "email": "jordan@example.com",
    "company": "Lee Studio"
  }'
```

**Request (Node.js)**

```javascript
const res = await fetch("https://anytime-capital.com/api/v1/customers", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "customer-jordan",
  },
  body: JSON.stringify({
    name: "Jordan Lee",
    email: "jordan@example.com",
    company: "Lee Studio",
  }),
});
const customer = await res.json();
if (!res.ok) throw new Error(`${customer.error.code}: ${customer.error.message}`);
```

**Request (Node SDK)**

```typescript
import { AnytimeCapital } from "@anytimecapital/node";

const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! });

const customer = await client.customers.create(
  {
    name: "Jordan Lee",
    email: "jordan@example.com",
    company: "Lee Studio",
  },
  { idempotencyKey: "customer-jordan" },
);
```

**Request (Python)**

```python
import os

import requests

res = requests.post(
    "https://anytime-capital.com/api/v1/customers",
    headers={
        "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}",
        "Idempotency-Key": "customer-jordan",
    },
    json={
        "name": "Jordan Lee",
        "email": "jordan@example.com",
        "company": "Lee Studio",
    },
    timeout=30,
)
customer = res.json()
if not res.ok:
    raise RuntimeError(f"{customer['error']['code']}: {customer['error']['message']}")
```

**Response** (201)

```json
{
  "object": "customer",
  "livemode": true,
  "id": "cus_1042",
  "name": "Jordan Lee",
  "email": "jordan@example.com",
  "phone": "+1 555 0100",
  "company": "Lee Studio",
  "notes": null,
  "billingAddress": null,
  "taxId": null,
  "country": "US",
  "createdAt": "2026-09-02T11:20:00.000Z"
}
```

**Errors**

| Status | Code | When |
| --- | --- | --- |
| 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. |
| 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. |
| 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). |
| 409 | `DUPLICATE_CUSTOMER` | Another saved customer already has this email address, in any letter case. Find them with `GET /api/v1/customers?email=…` and update that customer instead. |
| 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. |

### List customers

`GET /api/v1/customers`

Permission: `customers:read`.

Saved customers, newest first, or one by email.

**Query**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `email` | string | no | Only customers with this email, any case. Up to 255 characters. |
| `limit` | integer | no | How many objects per page, 1–100. Default 25. See [Pagination](https://anytime-capital.com/docs/pagination.md). |
| `cursor` | string | no | The previous page's `nextCursor`, for the page after it. |

**Request (curl)**

```bash
curl "https://anytime-capital.com/api/v1/customers?email=jordan%40example.com" \
  -H "Authorization: Bearer $ANYTIME_API_KEY"
```

**Request (Node.js)**

```javascript
const res = await fetch("https://anytime-capital.com/api/v1/customers?email=jordan%40example.com", {
  headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` },
});
const page = await res.json();
if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`);
```

**Request (Node SDK)**

```typescript
import { AnytimeCapital } from "@anytimecapital/node";

const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! });

const page = await client.customers.list({ email: "jordan@example.com" });
```

**Request (Python)**

```python
import os

import requests

res = requests.get(
    "https://anytime-capital.com/api/v1/customers",
    headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"},
    params={"email": "jordan@example.com"},
    timeout=30,
)
page = res.json()
if not res.ok:
    raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}")
```

**Response** (200)

```json
{
  "object": "list",
  "data": [
    {
      "object": "customer",
      "livemode": true,
      "id": "cus_1042",
      "name": "Jordan Lee",
      "email": "jordan@example.com",
      "phone": "+1 555 0100",
      "company": "Lee Studio",
      "notes": null,
      "billingAddress": null,
      "taxId": null,
      "country": "US",
      "createdAt": "2026-09-02T11:20:00.000Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

**Errors**

| Status | Code | When |
| --- | --- | --- |
| 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. |

### Get a customer

`GET /api/v1/customers/{id}`

Permission: `customers:read`.

One saved customer. Customers removed in the dashboard answer `404`.

**Path**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | `cus_…` |

**Request (curl)**

```bash
curl https://anytime-capital.com/api/v1/customers/cus_1042 \
  -H "Authorization: Bearer $ANYTIME_API_KEY"
```

**Request (Node.js)**

```javascript
const res = await fetch("https://anytime-capital.com/api/v1/customers/cus_1042", {
  headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` },
});
const customer = await res.json();
if (!res.ok) throw new Error(`${customer.error.code}: ${customer.error.message}`);
```

**Request (Node SDK)**

```typescript
import { AnytimeCapital } from "@anytimecapital/node";

const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! });

const customer = await client.customers.retrieve("cus_1042");
```

**Request (Python)**

```python
import os

import requests

res = requests.get(
    "https://anytime-capital.com/api/v1/customers/cus_1042",
    headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"},
    timeout=30,
)
customer = res.json()
if not res.ok:
    raise RuntimeError(f"{customer['error']['code']}: {customer['error']['message']}")
```

**Response** (200)

```json
{
  "object": "customer",
  "livemode": true,
  "id": "cus_1042",
  "name": "Jordan Lee",
  "email": "jordan@example.com",
  "phone": "+1 555 0100",
  "company": "Lee Studio",
  "notes": null,
  "billingAddress": null,
  "taxId": null,
  "country": "US",
  "createdAt": "2026-09-02T11:20:00.000Z"
}
```

**Errors**

| Status | Code | When |
| --- | --- | --- |
| 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. |

### Update a customer

`PATCH /api/v1/customers/{id}`

Permission: `customers:write`. Supports `Idempotency-Key`.

Send only what changes; null clears an optional field.

**Path**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | `cus_…` |

**Headers**

| Header | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | no | Recommended. A unique key such as a UUID: a retry with the same key within 24 hours gets the first response back instead of acting twice. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). |

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no | The customer's name. Up to 255 characters. |
| `email` | string (email) or null | no | One customer per email address. Up to 255 characters. |
| `phone` | string or null | no | Phone. Up to 50 characters. |
| `company` | string or null | no | Company. Up to 255 characters. |
| `notes` | string or null | no | Your notes. Up to 2000 characters. |
| `billingAddress` | object or null | no | A postal address printed on invoices and quotes. |
| `billingAddress.line1` | string | yes | Street address. Up to 200 characters. |
| `billingAddress.line2` | string or null | no | Apartment, suite, unit. Up to 200 characters. |
| `billingAddress.city` | string | yes | City. Up to 100 characters. |
| `billingAddress.state` | string or null | no | State, province or region. Up to 100 characters. |
| `billingAddress.postalCode` | string | yes | Postal code. Up to 20 characters. |
| `billingAddress.country` | string | yes | ISO 3166-1 alpha-2 country code. |
| `taxId` | string or null | no | VAT, EIN or other tax number. Up to 64 characters. |

**Request (curl)**

```bash
curl -X PATCH https://anytime-capital.com/api/v1/customers/cus_1042 \
  -H "Authorization: Bearer $ANYTIME_API_KEY" \
  -H "Idempotency-Key: customer-jordan-phone" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+1 555 0199",
    "notes": null
  }'
```

**Request (Node.js)**

```javascript
const res = await fetch("https://anytime-capital.com/api/v1/customers/cus_1042", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "customer-jordan-phone",
  },
  body: JSON.stringify({ phone: "+1 555 0199", notes: null }),
});
const customer = await res.json();
if (!res.ok) throw new Error(`${customer.error.code}: ${customer.error.message}`);
```

**Request (Node SDK)**

```typescript
import { AnytimeCapital } from "@anytimecapital/node";

const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! });

const customer = await client.customers.update(
  "cus_1042",
  { phone: "+1 555 0199", notes: null },
  { idempotencyKey: "customer-jordan-phone" },
);
```

**Request (Python)**

```python
import os

import requests

res = requests.patch(
    "https://anytime-capital.com/api/v1/customers/cus_1042",
    headers={
        "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}",
        "Idempotency-Key": "customer-jordan-phone",
    },
    json={"phone": "+1 555 0199", "notes": None},
    timeout=30,
)
customer = res.json()
if not res.ok:
    raise RuntimeError(f"{customer['error']['code']}: {customer['error']['message']}")
```

**Response** (200)

```json
{
  "object": "customer",
  "livemode": true,
  "id": "cus_1042",
  "name": "Jordan Lee",
  "email": "jordan@example.com",
  "phone": "+1 555 0199",
  "company": "Lee Studio",
  "notes": null,
  "billingAddress": null,
  "taxId": null,
  "country": "US",
  "createdAt": "2026-09-02T11:20:00.000Z"
}
```

**Errors**

| Status | Code | When |
| --- | --- | --- |
| 400 | `INVALID_JSON` | The body isn't valid JSON, or couldn't be read. An empty body reads as `{}`. Send `Content-Type: application/json` and a JSON object. |
| 400 | `INVALID_INPUT` | A field, filter, `limit` or `cursor` is missing or invalid. The message starts with the field's name, as in `amountCents: …`, or reads `Invalid cursor.` Fix the named field. Don't retry the same request unchanged. |
| 400 | `INVALID_IDEMPOTENCY_KEY` | The `Idempotency-Key` header is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See [Idempotency](https://anytime-capital.com/docs/idempotency.md). |
| 404 | `NOT_FOUND` | No such object on your business (an id from another business reads the same), or no such endpoint under `/api/v1`, which still answers in JSON. Check the id and the path. Ids are case-sensitive. |
| 409 | `DUPLICATE_CUSTOMER` | Another saved customer already has this email address, in any letter case. Find them with `GET /api/v1/customers?email=…` and update that customer instead. |
| 422 | `IDEMPOTENCY_KEY_REUSED` | The `Idempotency-Key` was used in the last 24 hours with a different body or endpoint. Use a new key for a new request; reuse a key only to retry the same request. |

## Find or create

To keep one record per person, look the email up first and create only when nothing comes back. Two processes doing this at once can race: the second create answers `409 DUPLICATE_CUSTOMER`, so treat that as “found” and list by email again.

**Node.js 18+**

```javascript
const API = "https://anytime-capital.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` };

export async function findOrCreateCustomer({ name, email }) {
  const find = async () => {
    const res = await fetch(`${API}/customers?email=${encodeURIComponent(email)}`, { headers });
    const page = await res.json();
    if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`);
    return page.data[0] ?? null;
  };
  const existing = await find();
  if (existing) return existing;

  const res = await fetch(`${API}/customers`, {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify({ name, email }),
  });
  const customer = await res.json();
  // Someone else created them first, or our earlier attempt went through:
  // one customer per email makes the retry safe without an Idempotency-Key.
  if (res.status === 409 && customer.error.code === "DUPLICATE_CUSTOMER") return find();
  if (!res.ok) throw new Error(`${customer.error.code}: ${customer.error.message}`);
  return customer;
}
```

`customer.created` and `customer.updated` webhooks carry this object whenever a customer is added or changed, by hand, by the API or when they first pay.

---

Anytime Capital is a fintech company, not a bank. Payment processing, banking and payout services are provided by Anytime Capital's licensed partners. Rates shown are for US card payments; international cards, currency conversion and disputes may carry additional fees shown in your dashboard before you accept a payment.
