The Order Create API allows you to send bulk cart paid notifications to Releva. Any cart paid notifications submitted through this API will be live within 1 minute of receiving a successful response.
Do I need to call it? #
You need to call this in order to fully use Releva. Without implementing this, store revenue, attribution tracking, and order-based segmentation will not function.
Endpoint #
| URL | POST https://releva.ai/api/v0/carts |
| Authentication | Authorization: Bearer <secretKey> โ the secret key of the domain the order belongs to. |
| Content type | application/json |
| Max request body | 800 KB. Send large order backfills in several requests. |
Request Parameters #
You may perform requests to the API using the following curl equivalent:
curl -H 'Content-Type: application/json' -H 'Authorization: Bearer <secretKey>' -XPOST https://releva.ai/api/v0/carts -d'{
"carts": [
{
"orderId": "orderId1",
"cartPaid": true,
"email": "user@email.com",
"phoneNumber": "+359888123456",
"timestamp": "2026-03-27T14:05:00.000Z",
"products": [
{
"id": "1",
"variantId": "1001",
"price": 10,
"currency": "BGN",
"quantity": 2,
"custom": {
"string": [
{"key": "color", "values": ["red"]}
],
"numeric": [
{"key": "size", "values": [18]}
]
}
}
],
"custom": {
"string": [
{"key": "city", "values": ["San Francisco"]}
],
"numeric": [
{"key": "rewardPoints", "values": [18]}
]
}
}
]
}'
Note: timestamp must be a full ISO-8601 date-time with a timezone offset โ 2026-03-27T14:05:00.000Z, 2026-03-27T14:05:00Z or 2026-03-27T14:05:00+0200. A date on its own, or a date-time without an offset, is rejected with HTTP 400.
The id of each product must be the same id you send to the Product Create / Update API. Releva looks the line item up in your catalog and copies the product’s name, categories and custom fields onto the order โ that is what makes segments such as โbought in category X in the last 30 daysโ work. A line item whose id matches nothing in the catalog is still counted towards revenue, but carries no product attributes.
Identifying the shopper #
Every cart needs at least one of email, phoneNumber or profileId โ this is how the order is attached to a profile. Send as many of them as you have; Releva resolves them to a single profile.
email / phoneNumber |
The shopper’s contact details as they appear on the order. Phone numbers should be in international format (+ followed by digits). |
profileId |
Your own customer id, if you have one. Use it consistently across the Profile APIs and this one, and orders will be attached to the right profile even for shoppers who change their email address. |
mergeProfileIds |
Ids of profiles to fold into this one. On a guest checkout, pass the anonymous id the shopper browsed under so their pre-purchase browsing history and campaign attribution follow them onto the identified profile. |
Carts that carry none of the three are skipped. In a batch, the remaining carts are still processed; if no cart in the request carries an identifier, the whole request is answered with HTTP 400.
Improving ad platform attribution #
Orders reported from your backend carry none of the browser context the ad platforms use to match a purchase to an ad click, because the request comes from your server and not from the shopper’s device. Forward what you have and the Purchase events Releva sends to Meta, Google and TikTok get materially better match quality:
curl -H 'Content-Type: application/json' -H 'Authorization: Bearer <secretKey>' -XPOST https://releva.ai/api/v0/carts -d'{
"carts": [
{
"orderId": "orderId1",
"cartPaid": true,
"email": "user@email.com",
"timestamp": "2026-03-27T14:05:00.000Z",
"ip": "78.90.12.34",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1",
"url": "https://my-awesome-shop.com/checkout/thank-you",
"address": {
"city": "Sofia",
"region": "23",
"postalCode": "1000",
"country": "BG"
},
"eventId": "b7f3c1e2-4a5d-4f0a-9c3e-1d2b3a4c5d6e",
"products": [
{"id": "1", "price": 10, "currency": "BGN", "quantity": 2}
]
}
]
}'
Send the shopper’s values, not your server’s: ip and userAgent are the ones the shopper was browsing with (most platforms let you stash them on the cart or session during checkout), and url is the storefront page the order was completed on. Omit any of them you cannot supply โ no value at all is better than your datacenter’s IP address or your HTTP client’s user agent, both of which would be matched as if they were the customer’s.
One order, one record #
An orderId is recorded once per shop. If the same order reaches Releva twice โ a webhook redelivery, a retry after a timeout, a backfill that overlaps live traffic โ only the first one counts, so repeating a call can never double-count revenue. The flip side is that a mistake cannot be corrected by re-sending the same orderId; contact support if an order needs changing.
Keep orderId under 255 characters, and use the order identifier your own systems use โ it is what support, refunds and order-level reports match on.
Orders are held for about 30 seconds before processing, so that a matching browser event from the same checkout can be merged with them.
The cart Object #
Each entry in carts is an object with the following properties. Any property that is not listed below is rejected with HTTP 400.
| Field | Type | Description |
|---|---|---|
carts[].orderId |
String | A unique order ID that identifies the completed order once a cart has been checked out. Max 255 characters. An order ID is only ever recorded once per shop. |
carts[].cartPaid |
Boolean | Send true. This endpoint reports paid orders; a cart sent with false records no order, no revenue and no event. Use the JavaScript SDK (or the Cart Update API) for carts that have not been paid for. |
carts[].email |
String (Optional) | The email of the user who has placed the order. You need to pass an email, a phone number, or a profileId. |
carts[].phoneNumber |
String (Optional) | The phone number of the user who has placed the order. Phone numbers must start with โ+โ followed by a sequence of numbers. Valid phone number: +49875345976. You need to pass an email, a phone number, or a profileId. |
carts[].profileId |
String (Optional) | Your own id for the customer who placed the order. You need to pass an email, a phone number, or a profileId. |
carts[].mergeProfileIds |
Array[String] (Optional) | Ids of other profiles to merge into this shopper’s profile โ typically the anonymous id a guest browsed under before checking out. |
carts[].timestamp |
ISO-8601 String (Optional) | The date and time when this visitor created their order. Must include the time and a timezone offset, e.g. 2026-03-27T14:05:00.000Z. We will use the current date and time if you don’t send this field. |
carts[].custom |
Object (Optional) | The custom fields associated with the cart, for example the reward points gained for this order, and/or the order address. See custom for details. |
carts[].companyId |
String (Optional) | The id of the company to associate this cart with. |
carts[].acceptPrivacyPolicy |
Boolean (Optional) | The consent of the user with our privacy policy. When true, the consent is recorded on the shopper’s profile. |
carts[].ip |
String (Optional) | The shopper’s IP address, forwarded from the storefront session. Used for ad platform match quality and geo. Max 45 characters. Do not send your server’s IP address. |
carts[].userAgent |
String (Optional) | The shopper’s browser user agent, forwarded from the storefront session. Max 1024 characters. Do not send your HTTP client’s user agent. |
carts[].url |
Absolute URL String (Optional) | The storefront URL the order was completed on. Max 2048 characters. Lets ad platforms record the purchase as a website conversion rather than an offline one. |
carts[].address |
Object (Optional) | The order’s postal address, used for advanced matching with the ad platforms. |
carts[].address.city |
String (Optional) | City. Max 128 characters. |
carts[].address.region |
String (Optional) | Region / state / province code, not the display name. Max 128 characters. |
carts[].address.postalCode |
String (Optional) | Postal code. Max 32 characters. |
carts[].address.country |
String (Optional) | Two-letter ISO-3166-1 alpha-2 country code, e.g. BG, not the display name. Max 32 characters. |
carts[].eventId |
String (Optional) | A deduplication id shared with your browser pixel for the same purchase, forwarded to the ad platforms as their event id so one purchase is not counted twice. Max 200 characters. |
carts[].products[] |
Array[Object] | A list of products in the cart. Required; may be empty for an order with no line items. |
carts[].products[].id |
String (Optional) | The id of the product in the cart. Use the same id you send to the Product Create / Update API, including the locale and market suffix if your catalog uses one. |
carts[].products[].variantId |
String (Optional) | The id of the purchased variant, when id identifies the product rather than the variant. Recorded on the order for variant-level reporting. |
carts[].products[].price |
Float | The unit price at which the product was purchased. Revenue is this price multiplied by quantity, summed over the line items. |
carts[].products[].quantity |
Integer | The units purchased. |
carts[].products[].currency |
ISO-4217 String (Optional) | The currency of the product. Omit it only when the price is already in your shop’s currency โ a price with no currency is not converted. |
carts[].products[].custom |
Object (Optional) | The custom fields associated with the product in the cart, for example the chosen size and color. See custom for details. |
Response Format #
A successful response will return HTTP Status 202 with an empty response body ({}).
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 orders will be live within a minute. |
| 400 | The request body failed validation, no cart carried an identifier, or the Authorization header is missing, malformed or does not match a known domain. message names the offending field. |
| 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 orders in smaller batches. |