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).
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) |
DIRECT_DEBIT_SETUP_INCOMPLETE is not “not Authorised”. Wait until GET shows setup_complete: true.
Create does not return an idempotency-conflict error. A repeated create reuses the existing checkout. 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.