Areye Shop API (app ⇄ Areye backend) — v1
One shopping experience in the Areye app over two commercial sources:
dijo: Dijo is merchant of record; Dijo validates, charges and creates the order.storefront: an Areye Storefront merchant (an Areye Business account); Storefront validates, charges and creates the merchant's order.
Carts and checkouts are never mixed: a cart belongs to one source, and to one store (or one Dijo mall). Areye's backend is the only thing the app talks to; it calls the source server to server.
All routes are under /api/customer/shop, use the customer's Bearer token,
and return {success, data, source, error?}. When a source can't be reached,
the route returns 503 channel_unavailable: the app shows "temporarily
unavailable" and sells nothing.
Sources and categories
GET /sources → [{ "source": "dijo"|"storefront", "label", "verticals": [{ "key", "label" }] }].
Only sources and categories switched on are listed. The home tile shows
"Shopping"; inside it, the categories (Groceries, Food, Flowers, …) and the
Storefront shops.
Catalogue
| Route | Notes |
|---|---|
GET /{source}/stores?lat&lng&vertical |
[{ id, name, logo_url, is_open, opens_at?, eta_minutes?, delivery_fee?, currency, areye_vertical, mall_id? }] |
GET /{source}/stores/{store}/categories |
[{ id, name, parent_id?, areye_vertical }] |
GET /{source}/stores/{store}/items?category&cursor&vertical |
{ items: [ItemCard], next_cursor? } |
GET /{source}/stores/{store}/items/{item} |
ItemDetail |
GET /{source}/search?q&lat&lng&vertical?&store_id?&mall_id?&cursor |
{ items: [ItemCard + store_id, store_name], stores?: [...], next_cursor? } |
GET /{source}/stores/{store}/slots?date=YYYY-MM-DD |
{ schedule_order, date, lead_minutes, slot_duration_minutes, windows: [{from, to}], slots } (order for later) |
GET /{source}/stores/{store}/bundles |
{ bundles: [{ id, title, bundle_price, currency, items, age_restricted }] } |
GET /{source}/stores/{store}/reviews?cursor / GET /{source}/items/{item}/reviews?cursor |
{ reviews: [{ rating, comment, at }], next_cursor } |
GET /{source}/items/{item}/bought-together |
{ items: [ItemCard] } (goes well with) |
ItemCard:{ id, name, image_url?, unit_price, was_price?, currency, available, max_qty?, has_options }.was_priceappears only for a genuine discount.ItemDetail:ItemCardplusdescription?andoption_groups: [{ id, name, min, max, options: [{ id, name, price_delta, available }] }]. Variations and add-ons are both option groups: a variation ismin 1, max 1, an add-on ismin 0, max n.
Basket and checkout
-
POST /{source}/basket/validate{ vertical, delivery: { address, lat, lng, instructions? }, substitution_preference: refund|ask|match, mall_id?, lines: [{ store_id, item_id, selected_options: [option ids], qty, shown_unit_price }] }Returns
{ validation_id, expires_at, lines: [{ item_id, status: ok|price_changed|reduced|unavailable, unit_price, qty_allowed }], fees: [{ code, label, amount }], discounts: [...], total, currency, changes_require_acceptance }. It may also return 422 withline_outside_vertical,basket_scope_invalidoroutside_service_area. -
POST /{source}/orders{ validation_id, accepted_changes, client_key: <uuid>, marketing_consent? }client_keymakes a retry safe: the same key returns the same order. The response is anOrder, includingcommerce.payment_url. A409 validation_stalemeans: validate again, and have the customer accept the changes. -
Payment: the app opens
payment_urlin the payment webview, then pollsGET /orders/{ref}untilcommerce.payment_statusispaidorfailed. Closing the page proves nothing.
Checkout options (each by the source's own rule)
schedule_at(ISO): order for later; the store's slots say which times it takes.order_type:delivery(default) orpickup(collect at the shop: no delivery fee, no driver).payment_methodon the order:card(default) orcash_on_deliverywhere the price check'scod.allowedis true; a cash order has no payment page andpayment_status: "cod".delivery_tier_id: the shop's delivery speed (store carddelivery_tiers); a priority tier is dispatched as express.lines[].note(255) andorder_note(500; the delivery instructions by default).- A line may be
{ store_id, bundle_id, qty, shown_unit_price }for one of the shop's bundles. - The order response adds
order_type,vertical,payment_methodandreorder(what was ordered, without prices, for "Order again").
Orders (both sources)
| Route | Notes |
|---|---|
GET /orders |
the customer's orders, newest first |
GET /orders/{ref} |
refreshed from the source, joined with Areye's delivery |
POST /orders/{ref}/cancel { reason } |
the source decides what is refunded |
POST /orders/{ref}/decision `{ request_id, decision: accept |
reject }` |
GET /orders/{ref}/refund-reasons |
the source's reasons for a refund after delivery |
POST /orders/{ref}/refund-request { customer_reason, customer_note? } |
asks the source for a refund; it reviews it |
Order:
{ reference, source,
commerce: { order_id, status, payment_status, total, currency, payment_url?, lines, refunds, receipt_url? },
decision: null | { request_id, line_id, proposed: { name, unit_price, qty }, expires_at },
fulfilment: null | { trip_id, trip_reference, status, driver_first_name, eta_minutes, can_tip },
commerce_status_stale: bool }
A tip uses the existing POST /api/customer/trip/{trip_id}/tip {amount}, which
is allowed for the customer of the channel order.
Push
The data payload { type: "shop_decision", reference } opens the order with
the substitution question. { type: "shop_order", reference } opens the order.
The earlier /api/customer/groceries/* routes remain as aliases of
/api/customer/shop/dijo/* until the app ships.
Sandbox
The same API runs at https://sandbox.areye.app/api/customer/shop, buying
from the partners' own sandboxes. Sign in there as a sandbox customer:
nothing is ever sent to a phone in the sandbox, so every one-time code is
123456. Orders are fulfilled by sandbox deliveries that you drive with the
simulation endpoints, or the
partner's sandbox account's simulated driver.