> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.paymentkit.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.paymentkit.com/_mcp/server.

# API Reference

The PaymentKit API is organized around REST. Our API has predictable resource-oriented URLs, accepts JSON-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes and authentication.

## Base URL

All API requests should be made to:

```
https://app.paymentkit.com/api
```

For example, to list customers: `https://app.paymentkit.com/api/customers`

## Authentication

The PaymentKit API uses API keys to authenticate requests. You can view and manage your API keys in the [Dashboard](https://app.paymentkit.com).

Authentication is performed via HTTP Bearer token. Include your secret token in the `Authorization` header:

```bash
curl https://app.paymentkit.com/api/customers \
  -H "Authorization: Bearer st_prod_your_secret_token"
```

PaymentKit provides two types of tokens:

| Token type        | Prefix | Usage                                                               |
| ----------------- | ------ | ------------------------------------------------------------------- |
| Secret token      | `st_`  | Server-side API calls. Keep secure, never expose in client code.    |
| Publishable token | `pt_`  | Client-side code (e.g., PaymentKit.js). Safe to expose in browsers. |

Your secret tokens carry many privileges, so keep them secure. Do not share them in publicly accessible areas such as GitHub, client-side code, or browser requests.

## Request format

Send request data as JSON with the `Content-Type: application/json` header:

```bash
curl https://app.paymentkit.com/api/customers \
  -H "Authorization: Bearer st_prod_your_secret_token" \
  -H "Content-Type: application/json" \
  -d '{"email": "customer@example.com", "name": "Jane Smith"}'
```

## Errors

PaymentKit uses conventional HTTP response codes to indicate the success or failure of an API request.

| Code  | Description                                               |
| ----- | --------------------------------------------------------- |
| `200` | Success                                                   |
| `400` | Bad Request — Invalid parameters                          |
| `401` | Unauthorized — Invalid or missing API key                 |
| `403` | Forbidden — Access denied to this resource                |
| `404` | Not Found — Resource doesn't exist                        |
| `409` | Conflict — Clashes with the current state of the resource |
| `422` | Unprocessable Entity — Validation error                   |
| `429` | Too Many Requests — Rate limit exceeded                   |
| `500` | Server Error — Something went wrong on our end            |

Error responses include a JSON body with details:

```json
{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "detail": "Customer email is required",
  "instance": "/api/customers",
  "request_id": "req_abc123"
}
```

The `request_id` can be used when contacting support to help diagnose issues.

### Retryable errors

Most errors mean something about the request needs to change. A few mean the request was
fine but arrived while a conflicting operation was in progress — the **identical**
request will succeed once that finishes.

These carry a top-level `error_code` and `"retryable": true`:

| `error_code`     | Meaning                                                            | What to do                                  |
| ---------------- | ------------------------------------------------------------------ | ------------------------------------------- |
| `invoice_locked` | Another operation is modifying one of the subscription's invoices. | Retry the same request after a few seconds. |

```json
{
  "type": "about:blank",
  "title": "Invoice Locked",
  "status": 409,
  "detail": "Cannot change subscription sub_abc123 right now: invoice in_xyz789 is being modified by another operation. Retry in a few seconds.",
  "instance": "https://app.paymentkit.com/api/acc_abc123/subscriptions/sub_abc123/items",
  "request_id": "req_abc123",
  "error_code": "invoice_locked",
  "invoice_id": "in_xyz789",
  "subscription_id": "sub_abc123",
  "retryable": true
}
```

An `invoice_locked` refusal modifies nothing, so retrying with a short backoff is
enough — there is no cleanup to perform and no need to poll.

Not every retryable `409` uses this shape, and not every one is side-effect free. The
change-request apply flow's `PARTIAL_APPLY_RETRYABLE` reaches you as a `409` whose
`detail` is a **stringified** object rather than nested JSON, and it is raised *after*
payment has been collected with some steps already applied — so retrying it resumes
work rather than repeating it. Check for a top-level `error_code` first; treat anything
else as endpoint-specific.

## Pagination

List endpoints return paginated results. Use `limit` and `offset` query parameters:

```bash
curl "https://app.paymentkit.com/api/customers?limit=20&offset=0" \
  -H "Authorization: Bearer st_prod_your_secret_token"
```

Paginated responses include metadata:

```json
{
  "items": [...],
  "total": 150,
  "has_more": true
}
```

| Parameter | Default | Max | Description               |
| --------- | ------- | --- | ------------------------- |
| `limit`   | 50      | 100 | Number of items to return |
| `offset`  | 0       | —   | Number of items to skip   |

## Expanding responses

Some endpoints support expanding related data. Use the `expand` query parameter:

```bash
curl "https://app.paymentkit.com/api/customers/cus_prod_abc123?expand=custom_fields" \
  -H "Authorization: Bearer st_prod_your_secret_token"
```

Available expansions vary by endpoint. Common expansions include `custom_fields` for entities that support custom field values.

## Deprecations

PaymentKit follows semantic versioning and provides advance notice before deprecating API endpoints or features. Active deprecations are documented in endpoint-specific guides with migration paths to replacement functionality.

**Currently announced deprecations:**

* [Change subscription plan endpoint](/guides/billing/subscriptions/change-subscription-plan) — Replacement: [Subscription change requests](/guides/billing/subscriptions/subscription-change-requests)

When an endpoint is deprecated, we:

1. **Announce** the deprecation in documentation with migration guides
2. Add `Deprecation: true` and `Sunset: <date>` headers to API responses (minimum 6 months notice)
3. **Sunset** the endpoint on the announced date (returns 410 Gone with replacement endpoint)

## IDs

All objects have a unique identifier prefixed with the object type and environment:

| Object           | Prefix   | Example               |
| ---------------- | -------- | --------------------- |
| Account          | `acc_`   | `acc_prod_a1b2c3d4`   |
| Customer         | `cus_`   | `cus_prod_x9y8z7w6`   |
| Subscription     | `sub_`   | `sub_prod_m3n4o5p6`   |
| Invoice          | `in_`    | `in_prod_q1r2s3t4`    |
| Product          | `prod_`  | `prod_prod_u5v6w7x8`  |
| Price            | `price_` | `price_prod_a9b8c7d6` |
| Checkout Session | `cs_`    | `cs_prod_f1g2h3i4`    |

All IDs use the `_prod_` prefix for both live and sandbox accounts.