← API documentation

Live: https://api.areye.app/api · Sandbox: https://sandbox.areye.app/api · Sandbox guide

Dijo sales channel — partner contract v1 (DRAFT for agreement)

Areye sells Dijo goods inside the Areye app. Dijo is merchant of record and the authority for catalogue, basket, order, payment, substitutions and refunds. Areye is a sales channel and, separately, the fulfilment network (through Dijo's existing Areye Business account and the shopping fulfilment v1 contract, unchanged).

Hosted by Dijo under /channel/v1. Versioned: breaking changes only in /channel/v2; additive fields may appear at any time and must be ignored if unknown. This is a partner contract, independent of Dijo's own mobile-app API.

Conventions

Customer identity

Areye sends a channel customer, never a Dijo login:

"channel_customer": {
  "channel": "areye",
  "channel_customer_id": "acu_7f3c…",          // opaque, stable per Areye customer
  "first_names": "Kagiso", "surname": "Molefe",
  "phone": "+26771000000", "email": null,
  "marketing_consent": false
}

Dijo may keep it for order history and support. Areye passes no password or token; Dijo cannot reach the Areye account and markets directly only with marketing_consent: true.

Categories

Areye owns the customer-facing categories (grocery, restaurant, pharmacy, alcohol, hardware, …). Dijo keeps its own codes and is never asked to remodel its catalogue. Dijo puts its native codes on every store, item, category and validated line:

"source_vertical": "grocery",          // Dijo's module or store type
"source_category": "dairy"             // Dijo's category, where it has one

Areye keeps an approved, versioned mapping from those codes to its own categories:

Areye passes source_verticals=a,b on store and search reads to ask only for what the tile shows, and filters again on what comes back. The vertical is frozen when the basket is validated; every validated line must carry its codes, and Areye refuses lines outside it. It is checked again when the order is created.

Delivery coverage: the native fence applies

Each merchant's contract records its own delivery coverage, and the channel applies exactly what the merchant's native app applies (Nii, 11 Oct 2026). For Dijo that is zone-polygon containment, through the same code as the Dijo app (Dijo commit c708f5e):

1. Catalogue reads

Call Purpose
GET /stores?lat=&lng= Stores serving that point: id, name, logo, open now, next opening, minimum order, delivery fee estimate, mall id if any
GET /stores/{store_id}/categories Category tree
GET /stores/{store_id}/items?category=&cursor= Items with their purchasable options, price, was-price (only when genuinely discounted), availability, images, quantity limits. Each option (Dijo variation + add-ons together) carries an opaque option_token minted by Dijo; Areye never models the structure and echoes the token back
GET /search?q=&lat=&lng=&cursor= Items and stores

Areye may cache for a few minutes for speed. The cache is never authoritative.

2. Basket validation

POST /baskets/validate

{
  "channel_reference": "ab_9d1…",
  "channel_customer": { … },
  "delivery": { "address": "…", "lat": -24.65, "lng": 25.91, "instructions": null },
  "substitution_preference": "refund | ask | match",
  "mall_id": null,                               // set when the lines span several stores of one mall
  "lines": [ { "store_id": "…", "item_id": "…", "option_token": null, "qty": 2, "shown_unit_price": "28.00" } ]
}

Basket scope: a basket is either one store, or several stores of ONE mall (mall_id set, up to the mall's store limit). Lines from stores outside a single mall are refused (basket_scope_invalid). The delivery address must fall in a Dijo-served zone (outside_service_area).

Response: validation_id, expires_at, per line (status: ok | price_changed | reduced | unavailable, current unit price, quantity allowed), fees named separately (delivery, which depends on distance, zone, speed tier and surge; service; tax, which is added, never included), discounts, total, currency, changes_require_acceptance: true|false, and any basket rule refusals (store closed, minimum order, mall combination).

3. Order creation (pending payment)

POST /orders

{
  "validation_id": "…",
  "channel_order_reference": "ao_51c…",        // idempotency key
  "accepted_changes": true,                     // required when the validation had changes
  "channel_customer": { … },
  "return_url": "https://areye.app/channel/dijo/return?ref=ao_51c…"
}

Response: order_id, status: "pending_payment", total, currency, payment_session { url, expires_at }. One payment session per order: a repeated call or a reopened page reuses or re-queries the same session and never creates a second one (a second session risks a double charge). An expired or changed validation is refused (409 validation_stale) and Areye validates again.

4. Payment outcome (authoritative)

Areye opens payment_session.url in its payment webview. On return, or when the page is closed, Areye calls:

GET /orders/{order_id} → payment_status: pending | paid | failed | expired

Closing the page or hitting return_url is never proof. Dijo activates the order, and sends order.payment_confirmed, only after it has confirmed capture with the gateway server to server, then books fulfilment through its Areye Business account with sales_channel: "areye" and channel_order_reference, so Areye can join the fulfilment trip to this order.

5. Order retrieval and cancellation

6. Substitution decisions

Dijo stays the order-state authority:

  1. The Areye shopper proposes; Dijo receives shopping.substitution_requested (fulfilment v1).
  2. Dijo records it and sends the channel event order.substitution_decision_requested (order_id, line_id, request_id, proposed name, unit price, quantity, expires_at).
  3. The Areye app asks the customer.
  4. Areye sends POST /orders/{order_id}/lines/{line_id}/substitution-decision { "request_id": "…", "decision": "accept | reject" }.
  5. Dijo confirms with order.line_changed, then relays the decision to fulfilment as today.

Areye never changes the shopping order itself.

7. Events Dijo → Areye

order.payment_confirmed, order.payment_failed, order.accepted, order.substitution_decision_requested, order.line_changed, order.refunded, order.cancelled, order.completed. Each carries order_id, channel_order_reference and the order's current commerce status. Areye's backend keeps the source of every status. The customer sees one order screen that combines Dijo's commerce status with Areye's fulfilment status: shopper, stores collected, driver, live tracking and proof.

8. Native shopping parity (added 12 Oct 2026)

Areye mirrors the partner's own shopping, catalogue through checkout. Each item below follows the partner's native rule; the partner's validate total is always the authoritative money.

Tips

Tips are for the driver and belong to Areye's relationship with its drivers, not to the merchant. They never pass through Dijo: validate returns no tip, the Dijo payment session never includes one, and Dijo adds none to channel orders. In the Areye app the customer tips the driver of the fulfilment trip through Areye's own tipping (from the Areye wallet, once per trip, after delivery).

Proven in the end-to-end dry run (11 Oct 2026)

Both partners passed these journeys on an isolated Areye stage with no live drivers and sandbox or demo payments: complete with an ID check, tip and settlement; cancel; substitution ask and answer; refused ID returned to the store. The rules below came out of it, and apply to every partner:

Out of scope for v1

Areye-wallet payment for Dijo goods (needs a collection-agent agreement), cross-listing of Storefront merchants on Dijo, and per-store Areye accounts.