> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.paymentkit.com/api-reference/introduction/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. > **Note** > > 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: ` 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.