# Products API

> Manage your product catalog with the Anytime Capital Payments API: create products with a price, SKU and shipping flag, update them and archive them.

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

Your product catalog (the **Products** page): what you sell, with a price, an optional SKU and whether it ships. Products are archived, never deleted, so payment links and receipts that used one keep its name. Images are added in the dashboard, and links that sell products are built there too: the API's payment links take a single price.

| Topic | Detail |
| --- | --- |
| Endpoints | [`POST /api/v1/products`](#create-product), [`GET /api/v1/products`](#list-products), [`GET /api/v1/products/{id}`](#get-product), [`PATCH /api/v1/products/{id}`](#update-product), [`POST /api/v1/products/{id}/archive`](#archive-product) |
| Key permissions | `products:write`, `products: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 product object

```json
{
  "object": "product",
  "livemode": true,
  "id": "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk",
  "name": "Ceramic mug",
  "description": "Hand-glazed, 12 oz.",
  "priceCents": 1800,
  "currency": "usd",
  "sku": "MUG-12",
  "physical": true,
  "imageUrl": null,
  "active": true,
  "createdAt": "2026-10-02T10:00:00.000Z",
  "archivedAt": null
}
```

| Field | Type | Description |
| --- | --- | --- |
| `object` | string | The type of object. Always `product`. |
| `livemode` | boolean | False for test-mode data (an `ak_test_` key); true for live data. |
| `id` | string | `prod_` followed by 20 letters and digits. |
| `name` | string | Name. |
| `description` | string or null | Description. |
| `priceCents` | integer | Price. Integer cents. |
| `currency` | string | usd. |
| `sku` | string or null | SKU. |
| `physical` | boolean | Ships: checkout asks for a shipping address. |
| `imageUrl` | string (uri) or null | Image, while it has one and isn't archived. |
| `active` | boolean | Not archived. |
| `createdAt` | string (date-time) | Added. |
| `archivedAt` | string (date-time) or null | Archived. |

## Endpoints

### Create a product

`POST /api/v1/products`

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

Add a product to the catalog. Images are added in the dashboard.

**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 | Name. Up to 120 characters. |
| `description` | string or null | no | Description. Up to 1000 characters. |
| `priceCents` | integer | yes | Price in cents. From 1 ($0.01) to 10,000,000 ($100,000.00). |
| `sku` | string or null | no | SKU: letters, numbers, spaces and - _ . / #. Up to 64 characters. |
| `physical` | boolean | no | Ships: checkout asks for a shipping address. Default false. |

**Request (curl)**

```bash
curl -X POST https://anytime-capital.com/api/v1/products \
  -H "Authorization: Bearer $ANYTIME_API_KEY" \
  -H "Idempotency-Key: product-mug-12" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ceramic mug",
    "priceCents": 1800,
    "sku": "MUG-12",
    "physical": true
  }'
```

**Request (Node.js)**

```javascript
const res = await fetch("https://anytime-capital.com/api/v1/products", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "product-mug-12",
  },
  body: JSON.stringify({ name: "Ceramic mug", priceCents: 1800, sku: "MUG-12", physical: true }),
});
const product = await res.json();
if (!res.ok) throw new Error(`${product.error.code}: ${product.error.message}`);
```

**Request (Node SDK)**

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

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

const product = await client.products.create(
  { name: "Ceramic mug", priceCents: 1800, sku: "MUG-12", physical: true },
  { idempotencyKey: "product-mug-12" },
);
```

**Request (Python)**

```python
import os

import requests

res = requests.post(
    "https://anytime-capital.com/api/v1/products",
    headers={
        "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}",
        "Idempotency-Key": "product-mug-12",
    },
    json={
        "name": "Ceramic mug",
        "priceCents": 1800,
        "sku": "MUG-12",
        "physical": True,
    },
    timeout=30,
)
product = res.json()
if not res.ok:
    raise RuntimeError(f"{product['error']['code']}: {product['error']['message']}")
```

**Response** (201)

```json
{
  "object": "product",
  "livemode": true,
  "id": "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk",
  "name": "Ceramic mug",
  "description": null,
  "priceCents": 1800,
  "currency": "usd",
  "sku": "MUG-12",
  "physical": true,
  "imageUrl": null,
  "active": true,
  "createdAt": "2026-10-02T10:00:00.000Z",
  "archivedAt": null
}
```

**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). |
| 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 products

`GET /api/v1/products`

Permission: `products:read`.

The product catalog, newest first.

**Query**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `active` | boolean | no | true for live objects, false for deactivated or archived ones. |
| `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/products?active=true" \
  -H "Authorization: Bearer $ANYTIME_API_KEY"
```

**Request (Node.js)**

```javascript
const res = await fetch("https://anytime-capital.com/api/v1/products?active=true", {
  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.products.list({ active: true });
```

**Request (Python)**

```python
import os

import requests

res = requests.get(
    "https://anytime-capital.com/api/v1/products",
    headers={"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"},
    params={"active": "true"},
    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": "product",
      "livemode": true,
      "id": "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk",
      "name": "Ceramic mug",
      "description": "Hand-glazed, 12 oz.",
      "priceCents": 1800,
      "currency": "usd",
      "sku": "MUG-12",
      "physical": true,
      "imageUrl": null,
      "active": true,
      "createdAt": "2026-10-02T10:00:00.000Z",
      "archivedAt": null
    }
  ],
  "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 product

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

Permission: `products:read`.

One product.

**Path**

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

**Request (curl)**

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

**Request (Node.js)**

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

**Request (Node SDK)**

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

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

const product = await client.products.retrieve("prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk");
```

**Request (Python)**

```python
import os

import requests

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

**Response** (200)

```json
{
  "object": "product",
  "livemode": true,
  "id": "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk",
  "name": "Ceramic mug",
  "description": "Hand-glazed, 12 oz.",
  "priceCents": 1800,
  "currency": "usd",
  "sku": "MUG-12",
  "physical": true,
  "imageUrl": null,
  "active": true,
  "createdAt": "2026-10-02T10:00:00.000Z",
  "archivedAt": null
}
```

**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 product

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

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

Change name, description, price, SKU or physical. Payment links already selling it keep the name and price they were made with.

**Path**

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

**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 | Name. Up to 120 characters. |
| `description` | string or null | no | Description. Up to 1000 characters. |
| `priceCents` | integer | no | Price in cents. From 1 ($0.01) to 10,000,000 ($100,000.00). |
| `sku` | string or null | no | SKU: letters, numbers, spaces and - _ . / #. Up to 64 characters. |
| `physical` | boolean | no | Ships: checkout asks for a shipping address. Default false. |

**Request (curl)**

```bash
curl -X PATCH https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk \
  -H "Authorization: Bearer $ANYTIME_API_KEY" \
  -H "Idempotency-Key: product-mug-price-2000" \
  -H "Content-Type: application/json" \
  -d '{
    "priceCents": 2000
  }'
```

**Request (Node.js)**

```javascript
const res = await fetch("https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "product-mug-price-2000",
  },
  body: JSON.stringify({ priceCents: 2000 }),
});
const product = await res.json();
if (!res.ok) throw new Error(`${product.error.code}: ${product.error.message}`);
```

**Request (Node SDK)**

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

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

const product = await client.products.update(
  "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk",
  { priceCents: 2000 },
  { idempotencyKey: "product-mug-price-2000" },
);
```

**Request (Python)**

```python
import os

import requests

res = requests.patch(
    "https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk",
    headers={
        "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}",
        "Idempotency-Key": "product-mug-price-2000",
    },
    json={"priceCents": 2000},
    timeout=30,
)
product = res.json()
if not res.ok:
    raise RuntimeError(f"{product['error']['code']}: {product['error']['message']}")
```

**Response** (200)

```json
{
  "object": "product",
  "livemode": true,
  "id": "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk",
  "name": "Ceramic mug",
  "description": "Hand-glazed, 12 oz.",
  "priceCents": 2000,
  "currency": "usd",
  "sku": "MUG-12",
  "physical": true,
  "imageUrl": null,
  "active": true,
  "createdAt": "2026-10-02T10:00:00.000Z",
  "archivedAt": null
}
```

**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. |
| 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. |

### Archive a product

`POST /api/v1/products/{id}/archive`

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

Take it out of the catalog. Safe to repeat. Nothing is deleted; restore it from **Products** in the dashboard.

**Path**

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

**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). |

**Request (curl)**

```bash
curl -X POST https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk/archive \
  -H "Authorization: Bearer $ANYTIME_API_KEY" \
  -H "Idempotency-Key: product-mug-archive"
```

**Request (Node.js)**

```javascript
const res = await fetch("https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk/archive", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`,
    "Idempotency-Key": "product-mug-archive",
  },
});
const product = await res.json();
if (!res.ok) throw new Error(`${product.error.code}: ${product.error.message}`);
```

**Request (Node SDK)**

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

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

const product = await client.products.archive(
  "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk",
  { idempotencyKey: "product-mug-archive" },
);
```

**Request (Python)**

```python
import os

import requests

res = requests.post(
    "https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk/archive",
    headers={
        "Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}",
        "Idempotency-Key": "product-mug-archive",
    },
    timeout=30,
)
product = res.json()
if not res.ok:
    raise RuntimeError(f"{product['error']['code']}: {product['error']['message']}")
```

**Response** (200)

```json
{
  "object": "product",
  "livemode": true,
  "id": "prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk",
  "name": "Ceramic mug",
  "description": "Hand-glazed, 12 oz.",
  "priceCents": 1800,
  "currency": "usd",
  "sku": "MUG-12",
  "physical": true,
  "imageUrl": null,
  "active": false,
  "createdAt": "2026-10-02T10:00:00.000Z",
  "archivedAt": "2026-10-24T12:00:00.000Z"
}
```

**Errors**

| Status | Code | When |
| --- | --- | --- |
| 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. |
| 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. |

---

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.
