The Refund API nets refunded money back out of the revenue Releva reports. Send it what was refunded and against which order, and Releva does the rest โ it looks the order up, subtracts the refunded value from the day and the marketing channel the order was originally credited to, and claws back any loyalty points the order earned.
Do I need to call it? #
Call it if you want the revenue figures in Releva to match your books. Without it, a refunded order keeps counting in full towards store revenue, campaign revenue and channel attribution, so the performance of the email, ad or recommender that drove the sale is overstated.
This API only corrects the numbers. It does not trigger a workflow โ if you want to react to a refund (a follow-up email, a win-back flow), send a custom event alongside it.
Endpoint #
| URL | POST https://releva.ai/api/v0/refunds |
| Authentication | Authorization: Bearer <secretKey> โ the secret key of the domain the order belongs to. |
| Content type | application/json |
| Max request body | 800 KB. Split larger batches into several requests. |
What you send, and what Releva works out for you #
You send only what was refunded and which order it belongs to. Everything else is read from the order Releva already has: the customer, the date, the prices, the currency, and the campaign, email, ad or recommender the sale was attributed to. That is why there are no identity, date or price fields in the request โ and why sending any of them is an error rather than being ignored.
The refund is recorded against the original order’s date, not today’s, so the correction lands in the period the sale was counted in. A report you have already looked at can therefore change after a refund arrives โ that is the point.
Request Parameters #
A refund of specific line items:
curl -H 'Content-Type: application/json' -H 'Authorization: Bearer <secretKey>' -XPOST https://releva.ai/api/v0/refunds -d'{
"refunds": [
{
"orderId": "orderId1",
"refundId": "refund-9001",
"products": [
{
"id": "1",
"variantId": "1001",
"quantity": 1
}
]
}
]
}'
A refund of an amount, with no line-item detail โ a shipping refund, a goodwill credit, a partial discount after the fact:
curl -H 'Content-Type: application/json' -H 'Authorization: Bearer <secretKey>' -XPOST https://releva.ai/api/v0/refunds -d'{
"refunds": [
{
"orderId": "orderId1",
"refundId": "refund-9002",
"value": 12.5
}
]
}'
| Field | Type | Description |
|---|---|---|
refunds[].orderId |
String | The order being refunded, exactly as you sent it to the Cart Paid API. This is how Releva finds the original sale. |
refunds[].refundId |
String | Your own unique, stable id for this refund. It is the deduplication key โ a refund id is processed at most once, so a webhook redelivery or a retry cannot subtract the same money twice. One order may have many refunds, each with its own id. |
refunds[].products |
Array[Object] (Optional) | The refunded line items. Omit for an amount-only refund and send value instead. |
refunds[].products[].id |
String | The product id as it was sent on the original order. |
refunds[].products[].variantId |
String (Optional) | The refunded variant, when the order carried one. Send it whenever you have it โ it is what tells two lines of the same product apart. |
refunds[].products[].quantity |
Number (Optional) | How many units were refunded. Omit it to refund the whole line. More than the order contained is capped at the full line. |
refunds[].value |
Number (Optional) | The total refunded amount, in the order’s currency. Used only when no line items are sent. |
Send either products or value. A refund with neither is accepted but does nothing.
How the two forms differ #
| With line items | Each refunded line is matched against the original order and its value subtracted, in proportion to the refunded quantity. Every line keeps the attribution it had on the order, so the refund is subtracted from the same campaign, email or recommender that earned it. A line that matches nothing in the original order is skipped. |
| Amount only | The amount is spread across the order’s products in proportion to their value, so each channel gives back its share. It is capped at the order’s product total, so a refund that includes shipping or tax cannot push the order’s revenue below zero. |
If you send both, the line items win and value is ignored. This means a partial refund that also returns shipping or tax nets only the goods โ deliberately, because shipping and tax were never counted as revenue on the original order either, so subtracting them would take back more than was ever added. Send an amount-only refund if you want the full amount netted.
Refunds are only possible for 30 days #
Releva keeps the priced, attributed copy of an order for 30 days after the sale, and that copy is what a refund is netted against. A refund for an order older than that is accepted with a 202 and then skipped โ there is nothing left to subtract it from. The same applies to an order Releva never received: send your orders through the Cart Paid API first, or the refunds have nothing to correct.
Loyalty points #
If the order earned loyalty points, they are reversed along with the money. How much comes back depends on the rule that granted them: points granted in proportion to order value are reversed in proportion to the refunded value, while a rule configured to reverse in full does so on any refund of its order. Points from a grant marked as non-reversible, and points that have already expired, are left alone.
Response Format #
A successful response will return HTTP Status 202 with an empty response body ({}).
The 202 means the refunds were accepted for processing, not that every one of them was applied. A refund for an unknown order, an order past the 30-day window, or a refund id that has already been processed is accepted and then skipped โ this is what makes the endpoint safe to retry.
An error response will return a HTTP 4xx or 5xx status code and have the following structure:
{
"message": "A description of the error and how to fix it, if it's a client error."
}
| Status | Meaning |
|---|---|
| 202 | Accepted. The refunds will be reflected in reporting within a minute. |
| 400 | The request body failed validation, or the Authorization header is missing, malformed or does not match a known domain. message names the offending field โ including a field this endpoint does not accept, such as an email address or a date. |
| 402 | The domain is disabled. Please reach out to support@releva.ai to enable it. |
| 413 | The request body is larger than 800 KB. Send the refunds in smaller batches. |