Skip to main content

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:

LevelScopeWhen applied
Client-levelAll requests from your clientId share a single rate limit bucketWhen no caller_id is provided
Caller-levelEach unique caller_id gets its own rate limit bucketWhen caller_id is provided
Production Risk Without caller_id

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

ValueExampleRecommended?
Customer's browser IP address192.168.1.100✅ Best option
Unique session identifierbrowser-session-abc123✅ Good alternative
Internal customer identifiercustomer_12345⚠️ Acceptable
A static or shared valuemy-backend❌ Defeats the purpose
Unique Identifier

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:

EndpointMethodcaller_id Location
GET /v1/users/existsGETQuery parameter
POST .../passwordless/startPOSTRequest body field
POST .../passwordless/verifyPOSTRequest 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

PracticeDescription
Always send caller_idUse the customer's browser IP or a unique session identifier on every endpoint that supports it
Extract the IP server-sideRetrieve the client IP from request headers (e.g., X-Forwarded-For) in your backend before calling the XCO API
Use exponential backoffOn 429 responses, back off exponentially with jitter to avoid thundering herd effects
Limit retry attemptsCap retries at 3 attempts — persistent 429 errors indicate a deeper issue
Monitor rate limit hitsLog and alert on 429 responses to detect abuse or misconfiguration early
Never share caller_id valuesUsing the same caller_id for multiple customers groups them into one bucket, negating the benefit

Next Steps