Errors
Error shapes that apply across the API, the auth failures every endpoint can return, and the per-endpoint 400s in one place.
Trailing slash required
Every route ends in / (e.g. /api/v1/subscriptions/).
- A
GETwithout the slash 301-redirects to the slashed URL. - A
POST/ write without the slash fails — the redirect can't preserve the request body.
Always include the trailing slash.
Error format
Field-specific validation errors return an object keyed by field name, with an array (or string) message:
{
"field_name": ["Error message."]
}General errors (auth failures, not found) return a detail key:
{
"detail": "Error message."
}Authentication and KYC
Returned by every endpoint. See Authenticate your requests.
| Status | Trigger | Body |
|---|---|---|
401 | Missing / malformed / inactive key | {"detail": "Invalid or inactive API key."} |
403 | Owner not KYC-verified | {"status_code": "<kyc_status>", "message": "KYC verification needed to perform this action."} |
GET /api/v1/products/ is the exception to the 403: it works before KYC verification.
Creating a subscription
From Create a subscription.
| Status | Trigger | Body |
|---|---|---|
400 | pbp_id unknown / not visible for this seller | {"pbp_id": "PlanBillingPeriod with public_id '...' does not exist or is not visible."} |
400 | Account has no API usage type | {"api_usage_type": "Your account does not have an API usage type configured."} |
400 | return_url missing | {"return_url": "A return URL is required for reminder-and-payment accounts."} |
400 | Active subscription already exists for this buyer + product + billing period | {"detail": "An active subscription already exists for this buyer, product, and billing period."} |
The last one only fires for a currently active subscription — an inactive or expired one is reused instead. See Reuse behaviour.
Managing a subscription
From Cancel or reschedule.
| Status | Trigger | Body |
|---|---|---|
400 | Date in the past | {"next_billing_cycle": ["next_billing_cycle must be in the future or today."]} |
400 | Subscription not found / not owned | {"subscription_id": "Subscription '...' does not exist."} |