Errors
v1 responses use a stable envelope. Every response also has request_id and an X-Request-Id header.
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid provider checkout payload",
"details": {
"missing_fields": ["student.email"]
},
"request_id": "req_3f9c2a1b8e7d6c5a4b3d2e1f"
},
"request_id": "req_3f9c2a1b8e7d6c5a4b3d2e1f"
}
Send your own id with X-Request-Id when correlating logs. Otherwise StudentPay generates req_….
Authentication
| Code | HTTP | When |
|---|---|---|
MISSING_API_KEY |
401 | Authorization header missing or empty |
INVALID_API_KEY |
403 | Bearer token is not a recognised provider key |
PROVIDER_KEY_MISMATCH |
403 | Key does not match provider.provider_code |
Validation
| Code | HTTP | When |
|---|---|---|
VALIDATION_ERROR |
400 | Required fields missing or invalid (create or confirm) |
Create details may include missing_fields and invalid_fields (for example pricing.course_price, plan.first_payment_date, course.course_code). For interest_free_payment_plan, invalid_fields also includes commercial fields that do not reconcile in integer cents (course_price − upfront_payment = amount_to_finance = number_of_instalments × instalment_amount), and catalogue mismatches when a client-supplied amount does not match the resolved course.
PIC create failures that are not “unknown provider” return HTTP 403 (inactive, API disabled, Environment mismatch, or missing Account).
Enrolment Integration uses these same /v1 errors. Demo routes under /api/demos/bela-beauty/ are not part of the product contract.
Confirm requires checkout.checkout_token, checkout.opportunity_id, checkout.dda_id, payment.payment_method, payment.first_payment_date, payment.deposit_confirmed: true, and all three declaration booleans true.
Checkout token
Confirm verifies the DDA setup JWT (checkout.checkout_token).
| Code | HTTP | When |
|---|---|---|
EXPIRED_CHECKOUT_TOKEN |
401 | Token expired |
INVALID_CHECKOUT_TOKEN |
401 | Token missing, malformed, or not a valid setup token |
Token / identifier mismatches on confirm return HTTP 403 with a string message, for example:
The Opportunity ID does not match the checkout token.The direct debit authority ID does not match the checkout token.The provider code does not match the checkout token.
On the v1 confirm route those string errors are mapped to error.code: INTERNAL_ERROR while the HTTP status stays 403. Treat the status and message as authoritative. This mapping is a known product gap, not a dedicated mismatch code.
Not found
| Code | HTTP | When |
|---|---|---|
PROVIDER_NOT_FOUND |
404 | Unknown or unusable provider_code on create |
CHECKOUT_NOT_FOUND |
404 | Checkout not found for this provider (GET), or confirm cannot load the checkout / authority |
Checkout state
| Code | HTTP | When |
|---|---|---|
DIRECT_DEBIT_SETUP_INCOMPLETE |
409 | Confirm called before setup complete. May include billing_request_status and mandate_status |
CONFLICT |
409 | Confirm identifiers do not match the stored checkout (provider order id, checkout id, or DDA / opportunity pairing) |
IDEMPOTENCY_CONFLICT |
409 | Create reused provider_order_id / Idempotency-Key with a different canonical enrolment intent |
DIRECT_DEBIT_SETUP_INCOMPLETE is not “not Authorised”. Wait until GET shows setup_complete: true.
Create replay of the same canonical enrolment intent returns 200 with idempotent_replay: true. See Idempotency.
Routing and server errors
| Code | HTTP | When |
|---|---|---|
METHOD_NOT_ALLOWED |
405 | Wrong HTTP method, or confirm without {checkoutId} in the path |
INTERNAL_ERROR |
500 | Unexpected failure |
INTERNAL_ERROR |
403 | Confirm token / identifier mismatch (see Checkout token). HTTP 403, not 500 |
Configuration
GET /v1/environment reports missing configuration by variable name in readiness.missing_required. It does not return secret values.
If GoCardless is not configured, hosted setup cannot start. Check payment_processor_status on the environment endpoint before you send payers to setup_url.
PIC Environment must match the API runtime (Sandbox vs Production). A usable PIC that is Active and API-enabled still fails closed when the Environment field does not match the host you called.
Common failures (quick map)
| Situation | Typical result |
|---|---|
Missing Authorization |
401 MISSING_API_KEY |
| Unrecognised key | 403 INVALID_API_KEY |
Key vs provider_code |
403 PROVIDER_KEY_MISMATCH |
| Wrong / inactive PIC | 403 or 404 PROVIDER_NOT_FOUND |
| Invalid or unbalanced plan | 400 VALIDATION_ERROR |
Unknown / mismatched course_code |
400 VALIDATION_ERROR (course.course_code) |
| Client price or instalment override | 400 VALIDATION_ERROR |
Unknown checkout_id |
404 CHECKOUT_NOT_FOUND |
Confirm before setup_complete |
409 DIRECT_DEBIT_SETUP_INCOMPLETE |
| Idempotent create replay (same intent) | 200 idempotent_replay: true (not an error) |
| Same order id, different course/plan | 409 IDEMPOTENCY_CONFLICT |
| Confirm replay | 200 already_confirmed: true |
| Sandbox key on Production host | 403 INVALID_API_KEY |