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`, `GET /api/v1/products`, `GET /api/v1/products/{id}`, `PATCH /api/v1/products/{id}`, `POST /api/v1/products/{id}/archive` |
| Key permissions | products:write, products:read |
| Samples | curl; Node.js 18+ with the built-in fetch (in an ES module); the Node SDK (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. |
The product object
{
"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
/api/v1/productsproducts:writeSupports Idempotency-Key.Add a product to the catalog. Images are added in the dashboard.
Headers
Idempotency-Keystring- 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.
Body
namestringrequired- Name. Up to 120 characters.
descriptionstring or null- Description. Up to 1000 characters.
priceCentsintegerrequired- Price in cents. From 1 ($0.01) to 10,000,000 ($100,000.00).
skustring or null- SKU: letters, numbers, spaces and - _ . / #. Up to 64 characters.
physicalboolean- Ships: checkout asks for a shipping address. Default false.
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
}'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}`);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" },
);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']}"){
"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
- 400
INVALID_JSON - The body isn't valid JSON, or couldn't be read. An empty body reads as
{}. SendContent-Type: application/jsonand a JSON object. - 400
INVALID_INPUT - A field, filter,
limitorcursoris missing or invalid. The message starts with the field's name, as inamountCents: …, or readsInvalid cursor.Fix the named field. Don't retry the same request unchanged. - 400
INVALID_IDEMPOTENCY_KEY - The
Idempotency-Keyheader is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See Idempotency. - 422
IDEMPOTENCY_KEY_REUSED - The
Idempotency-Keywas 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
/api/v1/productsproducts:readThe product catalog, newest first.
Query
activeboolean- true for live objects, false for deactivated or archived ones.
limitinteger- How many objects per page, 1–100. Default 25. See Pagination.
cursorstring- The previous page's
nextCursor, for the page after it.
curl "https://anytime-capital.com/api/v1/products?active=true" \
-H "Authorization: Bearer $ANYTIME_API_KEY"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}`);import { AnytimeCapital } from "@anytimecapital/node";
const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! });
const page = await client.products.list({ active: true });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']}"){
"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
- 400
INVALID_INPUT - A field, filter,
limitorcursoris missing or invalid. The message starts with the field's name, as inamountCents: …, or readsInvalid cursor.Fix the named field. Don't retry the same request unchanged.
Get a product
/api/v1/products/{id}products:readOne product.
Path
idstringrequiredprod_…
curl https://anytime-capital.com/api/v1/products/prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk \
-H "Authorization: Bearer $ANYTIME_API_KEY"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}`);import { AnytimeCapital } from "@anytimecapital/node";
const client = new AnytimeCapital({ apiKey: process.env.ANYTIME_API_KEY! });
const product = await client.products.retrieve("prod_Lm3Qx8Rt2Vb7Nc4Zp9Wk");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']}"){
"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
- 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
/api/v1/products/{id}products:writeSupports Idempotency-Key.Change name, description, price, SKU or physical. Payment links already selling it keep the name and price they were made with.
Path
idstringrequiredprod_…
Headers
Idempotency-Keystring- 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.
Body
namestring- Name. Up to 120 characters.
descriptionstring or null- Description. Up to 1000 characters.
priceCentsinteger- Price in cents. From 1 ($0.01) to 10,000,000 ($100,000.00).
skustring or null- SKU: letters, numbers, spaces and - _ . / #. Up to 64 characters.
physicalboolean- Ships: checkout asks for a shipping address. Default false.
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
}'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}`);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" },
);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']}"){
"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
- 400
INVALID_JSON - The body isn't valid JSON, or couldn't be read. An empty body reads as
{}. SendContent-Type: application/jsonand a JSON object. - 400
INVALID_INPUT - A field, filter,
limitorcursoris missing or invalid. The message starts with the field's name, as inamountCents: …, or readsInvalid cursor.Fix the named field. Don't retry the same request unchanged. - 400
INVALID_IDEMPOTENCY_KEY - The
Idempotency-Keyheader is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See Idempotency. - 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-Keywas 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
/api/v1/products/{id}/archiveproducts:writeSupports Idempotency-Key.Take it out of the catalog. Safe to repeat. Nothing is deleted; restore it from Products in the dashboard.
Path
idstringrequiredprod_…
Headers
Idempotency-Keystring- 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.
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"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}`);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" },
);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']}"){
"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
- 400
INVALID_IDEMPOTENCY_KEY - The
Idempotency-Keyheader is empty, longer than 255 characters, or has characters other than printable ASCII. Use a UUID. See Idempotency. - 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-Keywas 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.