Rate Limiting
The XCO API enforces rate limiting to protect the platform from abuse and ensure fair usage across all merchants. This guide explains how rate limiting works, which endpoints are affected, and how to use the caller_id parameter to avoid blocking legitimate customers.
How Rate Limiting Works
Rate limiting in the XCO API operates on two levels, depending on whether you provide a caller_id parameter:
| Level | Scope | When applied |
|---|---|---|
| Client-level | All requests from your clientId share a single rate limit bucket | When no caller_id is provided |
| Caller-level | Each unique caller_id gets its own rate limit bucket | When caller_id is provided |
Without caller_id, all requests from your backend share the same rate limit bucket. In production, a single abusive user (or a bot) can exhaust the limit and block legitimate customers from accessing Express Checkout features.
Always provide a caller_id in production environments.
The caller_id Parameter
The caller_id is an optional identifier that enables granular, per-user rate limiting. It allows the XCO API to distinguish between different end-users behind your backend, so that one user's activity does not impact another.
What to Use as caller_id
| Value | Example | Recommended? |
|---|---|---|
| Customer's browser IP address | 192.168.1.100 | ✅ Best option |
| Unique session identifier | browser-session-abc123 | ✅ Good alternative |
| Internal customer identifier | customer_12345 | ⚠️ Acceptable |
| A static or shared value | my-backend | ❌ Defeats the purpose |
The caller_id must be unique per end-user to be effective. Using a shared or static value provides no benefit over omitting it entirely.
Where to Send caller_id
The caller_id parameter is accepted on different endpoints in different ways:
| Endpoint | Method | caller_id Location |
|---|---|---|
GET /v1/users/exists | GET | Query parameter |
POST .../passwordless/start | POST | Request body field |
POST .../passwordless/verify | POST | Request body field |
Usage Examples
Check User Exists (Query Parameter)
Pass caller_id as a query parameter:
curl -X GET 'https://xco-api.purse-sandbox.com/v1/users/[email protected]&caller_id=192.168.1.100' \
--header "Authorization: Bearer ${ACCESS_TOKEN}"
Passwordless Start (Request Body)
Pass caller_id as a field in the JSON body:
curl -X POST 'https://xco-api.purse-sandbox.com/v1/merchants/${MERCHANT_ID}/users/passwordless/start' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-raw '{
"email": "[email protected]",
"caller_id": "192.168.1.100"
}'
Passwordless Verify (Request Body)
Pass caller_id as a field in the JSON body:
curl -X POST 'https://xco-api.purse-sandbox.com/v1/merchants/${MERCHANT_ID}/users/passwordless/verify' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-raw '{
"email": "[email protected]",
"verification_code": "123456",
"caller_id": "192.168.1.100"
}'
Best Practices
| Practice | Description |
|---|---|
Always send caller_id | Use the customer's browser IP or a unique session identifier on every endpoint that supports it |
| Extract the IP server-side | Retrieve the client IP from request headers (e.g., X-Forwarded-For) in your backend before calling the XCO API |
| Use exponential backoff | On 429 responses, back off exponentially with jitter to avoid thundering herd effects |
| Limit retry attempts | Cap retries at 3 attempts — persistent 429 errors indicate a deeper issue |
| Monitor rate limit hits | Log and alert on 429 responses to detect abuse or misconfiguration early |
Never share caller_id values | Using the same caller_id for multiple customers groups them into one bucket, negating the benefit |
Next Steps
- Error Handling — Full error response format and handling strategies for all status codes
- Getting Started — Step-by-step integration guide
- XCO API Reference — Full OpenAPI specification