The status codes the Loyalty API returns, the error bodies each part of the API sends, and how to handle each one.
Check the HTTP status code first. The error body depends on which part of the API you called, and a few errors have an empty body or plain text. Check the Content-Type header before you parse the body.
Status codes
| Code | Meaning | Common causes |
|---|---|---|
200, 201 | Success | Many write endpoints return an empty {} body. Orders and custom actions are accepted and processed asynchronously, so success doesn't mean points were awarded yet. |
400 | The request is invalid | Reward Instances without discount_type, or with page_info plus other filters. Tags sent as a string instead of an array. A wrong or already-cancelled reward_text on Approve Redemption Cancellation. An invalid page_info cursor. Product Potential Points without variants. |
401 | Not authenticated | A missing or wrong GUID or API key. On token endpoints, an invalid or expired X-Yotpo-Token. On Authenticated Product Potential Points, a missing customer header or an X-Customer-Token that doesn't match. |
403 | Forbidden | On token endpoints (Reward Instances, v3.0 VIP Tiers), a missing X-Yotpo-Token, the Loyalty GUID and API key sent instead of a token, or a token for a different store than store_id. |
404 | Not found | An unknown customer, order, redemption or redemption option. A customer with no anniversary to remove. A store that isn't on the current VIP tiers program (v3.0 VIP Tiers). |
422 | Validation error | A missing required field, no customer identifier, or a value the server rejects: not enough points to redeem, a customer who isn't opted in (Adjust Points), an action_name that doesn't match a Custom Action campaign. Some endpoints also return 422 for an unknown customer. |
429 | Too many requests | You went over your rate limit. See 429 Too Many Requests. |
500 | Server error | Also returned for a request body that isn't valid JSON, and for emails sent as a JSON array to Send Referral Emails. |
Error bodies
v2.0 endpoints (loyalty.yotpo.com/api/v2)
loyalty.yotpo.com/api/v2)Errors are JSON, with the message in error or errors. errors is either a string or an object that maps each field to its message:
{
"errors": {
"customer": "You must provide one of: customer_email, customer_id, pos_account_id or phone_number"
}
}{
"errors": "Customer has to be opt in for manual adjustment."
}{
"error": "merchant not found"
}| Where | Status | Body |
|---|---|---|
| Missing GUID or API key | 401 | {"error":"Providing one of merchant_id or guid is required"} |
| Wrong GUID or API key | 401 | {"error":"merchant not found"} |
| Create Redemption, not enough points | 422 | {"amount":["You don't have enough points for this amount"]}. The message is under amount, not errors. |
| Send Referral Emails, every address rejected | 422 | {"error":"NO_LEGITIMATE_EMAILS","details":[{"email":"…","reason":"SELF_REFERRAL"}]} |
| User data, unknown email | 422 | Empty object {} |
| Upload Coupon Codes, unknown redemption option | 404 | Empty object {} |
| Malformed JSON body | 500 | {"error":"Internal Server Error"} |
Each endpoint page lists the errors it returns, with examples.
Reward Instances (loyalty.yotpo.com/v3) and Product Potential Points
loyalty.yotpo.com/v3) and Product Potential Points| Where | Status | Body |
|---|---|---|
| Validation error | 400 | {"error":"discount_type is required"} |
| Missing token, or the GUID and API key instead of a token | 403 | {"message":"Forbidden"} |
| Token for another store | 403 | {"message":"Forbidden: Store ID mismatch"} |
| Invalid or expired token | 401 | Plain text: unauthorized |
| Product Potential Points, unknown store | 404 | Empty object {} |
| Authenticated Product Potential Points, missing customer email | 401 | {"email":["Customer email is required"],"error":"customer_email_required"} |
| Authenticated Product Potential Points, token or store account ID doesn't match | 401 | Empty object {} |
v3.0 VIP Tiers (api.yotpo.com/loyalty)
api.yotpo.com/loyalty)Errors use the application/problem+json format:
{
"type": "about:blank",
"title": "Program not found",
"status": 404,
"detail": "Can't find 'any' program for merchant 'YOUR_STORE_ID'",
"instance": "/loyalty/v3/stores/YOUR_STORE_ID/vip_tiers/programs/current",
"code": "LOYALTY_VIP_PROGRAM_NOT_FOUND",
"exception_id": "de6b113b-51a9-4b8b-90d8-b65fbdaa02ca"
}code and exception_id aren't on every error. A 400 for an invalid body has title, status, detail and instance only.
Unknown customers don't always return 404
404Fetch Customer Details, Create Redemption and Get Active Campaigns return 404 for an unknown customer. Some endpoints return 422 instead: Customer Anniversary and Customer Tags (No customer found), User data ({}), and Adjust Points, which returns the opt-in message for a customer that doesn't exist. If an identifier might be wrong, check it with Fetch Customer Details first.
How to handle each error
400 Bad Request and 422 Unprocessable Entity
Fix the request before you retry: sending it again unchanged returns the same error. The message names the field at fault.
401 Unauthorized and 403 Forbidden
For v2.0 endpoints, check the X-GUID and X-API-KEY headers (see Authentication). For Reward Instances and v3.0 VIP Tiers, request a new access token and retry once; if that fails, check that the token belongs to the store in the path.
404 Not Found
Check the IDs and the customer identifier. For v3.0 VIP Tiers, a 404 with LOYALTY_VIP_PROGRAM_NOT_FOUND means the store isn't on the current VIP tiers program; use Fetch VIP Tiers instead.
429 Too Many Requests
Limits are set per account and can differ between endpoint groups, so read them from the response headers instead of hard-coding a number. Every response carries:
| Header | Description |
|---|---|
RateLimit-Limit | Requests allowed in the current window |
RateLimit-Remaining | Requests left in the current window |
RateLimit-Reset | Seconds until the window resets |
X-RateLimit-Limit-Minute | Requests allowed per minute |
X-RateLimit-Remaining-Minute | Requests left this minute |
After a 429, wait RateLimit-Reset seconds before you retry, or back off exponentially (1, 2, then 4 seconds). When RateLimit-Remaining gets low, slow down before you hit the limit. Spread bulk syncs (customers, orders) over time instead of sending them in bursts.
500 Internal Server Error
Retry GET requests up to 3 times with exponential backoff. If a request keeps returning 500, check that the body is valid JSON and that list fields have the documented type.
Don't retry a write blindly. If the first attempt went through, a retry of Adjust Points, Record Customer Action or Create Redemption awards or deducts points again or issues a second coupon, and a retry of Create Refund records a second refund. Before you retry, check whether the first call worked with Fetch Customer Details and with_history=true.
What to log
For every failed call, log the HTTP status, the error body, the time, the request method and path, and:
- the
X-Request-Idresponse header (v2.0 endpoints, Reward Instances and Product Potential Points), or - the
exception_idin the body (v3.0 VIP Tiers).
Include them when you contact Yotpo support. Don't log your API key, GUID or access tokens.