Overview
The AION API lets an authorized brand's sales system push transactional events to AION so that a protection cover (policy) is opened for each eligible item, and closed when an item is returned or a sale is cancelled.
There are three core operations, each a single POST with a JSON body:
- Sale — register a new sale and open a cover for the purchased item(s).
- Return — a customer returned an item; the associated cover is closed.
- Cancellation — a sale was voided; the associated cover is closed.
Authentication
Every request to a /v1/external/* endpoint must include your API token in the
x-api-key header, using the Bearer scheme:
x-api-key: Bearer YOUR_API_TOKEN
Your token identifies your brand and the integration it belongs to. AION resolves the brand and event source from the token server-side — you never send a brand identifier yourself.
| Situation | Result |
|---|---|
Header missing, malformed, or not Bearer <token> | 401 Unauthorized |
| Token unknown, revoked, or inactive | 401 Unauthorized |
| Valid, active token | Request is processed |
Conventions
Requests
- All endpoints accept
Content-Type: application/json. - Every payload is wrapped in a single top-level envelope key (
sale,return, orcancellation). - Fields set to
nullare ignored, and surrounding whitespace on string values is trimmed. - Unknown extra fields are accepted and ignored — sending more than is documented is safe.
- Field names are
snake_case. For backward compatibility,sub_order_row_codeandsell_dateare also accepted in their legacy camelCase form (subOrderRowCode,sellDate); new integrations should usesnake_case. - Maximum request body size is 512 KB.
Responses
All responses are JSON and always include a human-readable message field. The HTTP status code
is the source of truth for success or failure. Error responses have the shape:
{ "message": "..." }
Dates
Timestamps are ISO 8601 strings, e.g. 2025-08-26T10:15:00.000.
- A value without a timezone suffix is interpreted as Europe/Rome local time and stored as UTC.
- A value with a
Zor numeric offset (e.g.+02:00) is used as-is. - Date/time fields are optional; when omitted, AION uses the time the request is received.
Idempotency
Covers are keyed on the combination of sale.id and row_id within your brand.
Re-sending an event that was already processed is safe: AION returns 201 with a message stating
the record already existed and performs no duplicate action. This makes retries safe after a network error.
Create Sale
Registers a sale and opens a cover for the item. Send one request per sold line item.
Body
Top-level envelope: { "sale": { ... } }.
| Field | Type | Req. | Description |
|---|---|---|---|
id | string | required | Your unique sale identifier. |
row_id | string | required | Your unique line-item identifier within the sale. |
quantity | integer | required | Units sold on this line. A whole quantity ≥ 2 opens one cover per unit (see note below). |
customer | object | required | Buyer details — see below. |
sub_order_row_code | string | optional | Your sub-order / row reference code. |
sell_date | string (ISO) | required | When the sale occurred. |
item | object | required | The purchased product — see below. |
shop | object | required | The selling location — see below. |
customer object (required)
| Field | Type | Req. | Description |
|---|---|---|---|
email | string | required | Buyer email (lower-cased on storage). Primary match key. |
first_name | string | required | Private customer's first name. |
last_name | string | required | Private customer's last name. |
Business customers may additionally send vat, entity_name, and business_address. When vat is present the record is treated as a business.
item object (required)
| Field | Type | Req. | Description |
|---|---|---|---|
id | string | required | Your product/catalogue identifier. |
name | string | required | Product name. |
category | string | required | Product category. |
recommended_retail_price | number | required | Full retail price (RRP). |
selling_price | number | required | Actual price paid. |
sku | string | required | Stock-keeping unit. |
description | string | required | Product description. |
picture | string (URL) | required | Image URL. |
collection | string | required | Collection name. |
composition | string | required | Material composition. |
shop object (required)
| Field | Type | Req. | Description |
|---|---|---|---|
id | integer or string | required | Your store identifier. |
name | string | required | Store display name. |
quantity is a whole number ≥ 2, AION opens a separate cover
for each unit and returns one result entry per cover. A quantity of 1 (or a fractional quantity)
produces a single cover.
Example request
curl -X POST "https://api.aioncover.com/v1/external/sale" \
-H "Content-Type: application/json" \
-H "x-api-key: Bearer YOUR_API_TOKEN" \
-d '{
"sale": {
"id": "SALE-0001",
"row_id": "ROW-0001",
"sub_order_row_code": "SUB-0001",
"quantity": 1,
"sell_date": "2025-01-15T10:30:00.000",
"customer": {
"email": "customer@example.com",
"first_name": "Jane",
"last_name": "Doe"
},
"item": {
"id": "ITEM-0001",
"sku": "SKU-0001",
"name": "Sample Product",
"description": "Sample product description.",
"picture": "https://example.com/sample.jpg",
"category": "SAMPLE CATEGORY",
"collection": "Sample Collection",
"composition": "Sample material",
"recommended_retail_price": 1000.00,
"selling_price": 900.00
},
"shop": { "id": 1, "name": "Sample Store" }
}
}'
Example response — 200 OK
{
"message": "Sale processed successfully.",
"results": [
{
"status": 200,
"policyId": "…",
"message": "Cover created successfully.",
"policyStatus": "live"
}
]
}
The results array contains one entry per cover. Possible per-cover policyStatus values:
live and blocked (created), existing (already present), or errored.
Whether a new cover is live or blocked is decided by AION's underwriting rules.
Status codes
sale envelope or unprocessable body.results).Return
Records that an item was returned and closes the matching cover.
Body
Top-level envelope: { "return": { ... } }.
| Field | Type | Req. | Description |
|---|---|---|---|
id | string | required | Your unique return identifier. |
sale_id | string | required | The original sale.id. |
row_id | string | required | The original line-item row_id. |
returned_at | string (ISO) | required | When the return occurred. |
shop | object | required | Store where the return was made (id, name). |
Example request
curl -X POST "https://api.aioncover.com/v1/external/return" \
-H "Content-Type: application/json" \
-H "x-api-key: Bearer YOUR_API_TOKEN" \
-d '{
"return": {
"id": "RETURN-0001",
"sale_id": "SALE-0001",
"row_id": "ROW-0001",
"returned_at": "2025-01-20T14:00:00.000",
"shop": { "id": 1, "name": "Sample Store" }
}
}'
Example response — 200 OK
{
"status": 200,
"policyId": "…",
"message": "Cover cancelled successfully.",
"policyStatus": "cancelled"
}
Status codes
sale_id + row_id, or the body is unprocessable.Cancellation
Records that a sale was voided and closes the matching cover. Unlike a return, no shop is required.
Body
Top-level envelope: { "cancellation": { ... } }.
| Field | Type | Req. | Description |
|---|---|---|---|
sale_id | string | required | The original sale.id. |
row_id | string | required | The original line-item row_id. |
id | string | required | Your cancellation identifier. |
cancelled_at | string (ISO) | required | When the cancellation occurred. |
Example request
curl -X POST "https://api.aioncover.com/v1/external/cancellation" \
-H "Content-Type: application/json" \
-H "x-api-key: Bearer YOUR_API_TOKEN" \
-d '{
"cancellation": {
"id": "CANCELLATION-0001",
"sale_id": "SALE-0001",
"row_id": "ROW-0001",
"cancelled_at": "2025-01-20T14:00:00.000"
}
}'
Example response — 200 OK
{
"status": 200,
"policyId": "…",
"message": "Cover cancelled successfully.",
"policyStatus": "cancelled"
}
Status codes
sale_id + row_id, or the body is unprocessable.Health Check
Lightweight liveness probe. Returns 200 when the service is up.
curl "https://api.aioncover.com/health"
# → { "status": "ok" }
Error Reference
Errors always return a JSON body of the form { "message": "..." }. Handle responses by HTTP status:
| Status | Meaning | Typical message |
|---|---|---|
400 | Bad request — malformed JSON or missing required envelope/fields. | Error in processing the request body payload. |
401 | Missing or invalid API token. | Unauthorized |
413 | Request body exceeds 512 KB. | Request body too large. |
429 | Rate limit exceeded. | Too many requests. |
500 | Unexpected server error. | Internal Server Error |
429 and 5xx responses are safe to retry with exponential backoff.
Thanks to idempotency (see Conventions), retrying a request that actually succeeded
will not create a duplicate cover.
Limits & Security
Rate limits
- 120 requests per minute per API token on the write endpoints. Exceeding this returns
429. - Request body limited to 512 KB.
Transport & platform security
- HTTPS only. The API is served exclusively over TLS with HSTS enabled; plain HTTP is not supported.
- Responses set hardening headers (
X-Content-Type-Options,X-Frame-Options,Referrer-Policy,Cache-Control: no-store). - Tokens are never written to logs, and payloads are validated and size-capped before processing.
- Only
GET,POST, andOPTIONSmethods are accepted.
Your responsibilities
- Store your token in a server-side secret manager; never expose it client-side.
- Call the API only from your backend.
- Report a suspected token compromise to AION immediately for rotation.