Skip to main content

Error Handling

The XCO API uses standard HTTP status codes and a consistent error format based on RFC 7807 (Problem Details). This guide explains how to interpret and handle errors across all Express Checkout endpoints.


Error Response Format

All error responses follow the application/problem+json content type with this structure:

{
"type": "https://api.purse.eu/problems/validation-error",
"title": "Validation Error",
"status": 400,
"detail": "The request body contains invalid fields.",
"instance": "/v1/merchants/123e4567-e89b-12d3-a456-426614174000/users",
"errors": [
{
"detail": "must be a valid email address",
"pointer": "/user/email"
}
]
}
FieldTypeDescription
typestringA URI reference identifying the problem type
titlestringA short, human-readable summary of the problem
statusintegerThe HTTP status code (mirrored in the body for convenience)
detailstringA human-readable explanation specific to this occurrence
instancestringThe API path that generated the error
errorstringSingle error detail (when applicable)
errorsarrayList of field-level validation errors (when applicable)
Field-Level Errors

When present, each entry in the errors array contains:

  • detail — A description of what's wrong (e.g., "must be a valid email address")
  • pointer — A JSON Pointer to the offending field (e.g., /user/email)

HTTP Status Codes

Overview

Status CodeMeaningRetryable?
400Bad Request — Invalid input, missing required fields, or malformed data❌ Fix the request
401Unauthorized — Missing, expired, or invalid access token⚠️ Refresh token, then retry
403Forbidden — Valid token but insufficient permissions or unsupported origin❌ Check configuration
404Not Found — Merchant, user, or consent challenge not found❌ Verify identifiers
429Too Many Requests — Rate limit exceeded✅ Retry with backoff
500Internal Server Error — Unexpected server-side failure✅ Retry with backoff

Status Codes per Endpoint

Endpoint400401403404429500
GET /v1/users/exists
POST /v1/merchants/{merchantId}/users
POST .../passwordless/start
POST .../passwordless/verify
POST .../consents
GET .../users/{customer_reference}

Common Error Scenarios

1. Validation Errors (400)

Validation errors occur when the request payload is malformed or missing required fields.

Scenario: Calling GET /v1/users/exists without email or phone_number.

{
"type": "https://api.purse.eu/problems/validation-error",
"title": "Bad Request",
"status": 400,
"detail": "At least one of email or phone_number must be provided",
"instance": "/v1/users/exists"
}

Resolution: Provide at least one of email or phone_number as a query parameter.

2. Authentication Errors (401)

Never Expose Credentials

Never expose your CLIENT_SECRET or ACCESS_TOKEN in frontend code or client-side logs.

A 401 error means the request is not authenticated. Common causes:

CauseResolution
Missing Authorization headerAdd Authorization: Bearer ${ACCESS_TOKEN} to the request
Expired access tokenRequest a new token via the OAuth2 flow
Invalid tokenVerify the token was generated with the xco-api scope

Example handling:

async function callXcoApi(url, options) {
let response = await fetch(url, options);

if (response.status === 401) {
// Token expired — refresh and retry once
const newToken = await refreshAccessToken();
options.headers['Authorization'] = `Bearer ${newToken}`;
response = await fetch(url, options);
}

return response;
}

3. Forbidden Errors (403)

A 403 error indicates that the token is valid but the request is not allowed. This typically happens when:

  • The merchant origin is not whitelisted for the XCO API.
  • The OAuth2 token does not include the xco-api scope.
Whitelisting

Contact the Purse Integration Team to ensure your domain and merchant account are properly configured for Express Checkout access.

4. Not Found Errors (404)

A 404 error means the referenced resource does not exist.

EndpointPossible Cause
POST /v1/merchants/{merchantId}/usersThe merchantId is invalid or does not exist
POST .../passwordless/startThe merchantId does not exist
POST .../passwordless/verifyThe merchantId does not exist
POST .../consentsThe merchantId or consent_challenge_id is invalid
GET .../users/{customer_reference}The customer_reference does not exist for this merchant, or no active consent exists
Consent Challenge Expiration

The consent_challenge_id returned by the passwordless verify step is short-lived. If you receive a 404 when creating a consent, the challenge may have expired. Restart the passwordless flow from the start step.

5. Rate Limiting (429)

A 429 response means you have exceeded the allowed number of requests. See the Rate Limiting guide for full details.

Key points:

  • Always pass a caller_id parameter (e.g., the customer's browser IP) to enable granular rate limiting per end-user rather than per clientId.
  • Without caller_id, all requests from your backend share the same rate limit bucket, which may block legitimate customers.

Example response:

{
"type": "https://api.purse.eu/problems/rate-limit-exceeded",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded. Please retry after a short delay.",
"instance": "/v1/merchants/{merchantId}/users/passwordless/start"
}

6. Server Errors (500)

A 500 error indicates an internal failure on the XCO platform side. This can occur on the passwordless endpoints when the authentication service is temporarily unavailable.

Resolution: Implement retry logic with exponential backoff (see Retry Strategy below). If the issue persists, contact Purse support.


Error Handling by Flow

User Existence Check

User Creation

Passwordless Flow


Best Practice Summary

PracticeDescription
Always send caller_idPrevents rate limit issues by enabling per-user granularity
Parse the errors arrayFor 400 responses, iterate over errors to display field-specific messages to the user
Implement token refreshAutomatically refresh the OAuth2 token on 401 before retrying
Use exponential backoffFor 429 and 500 errors, backoff with jitter to avoid thundering herd
Handle challenge expirationIf consent creation returns 404, restart the passwordless flow
Log error detailsLog the full ProblemDetail response for debugging — but never log tokens or secrets
Show user-friendly messagesTranslate API errors into actionable messages for the end user

Next Steps