Areye REST API

API Documentation

Integrate Areye rides, deliveries, cargo, car rental, and rent-to-own directly into your platform. Manage fleets, employees, wallets, and billing — all through one authenticated API.

Base URL: https://api.areye.app/api
Request API Key

What you can build

One API for rides, deliveries and car rental — plugged into your own website, app or back-office. A few of the ways companies use Areye Business:

🏨

Hotels & lodges

Offer guests airport transfers, city rides and self-drive car rental — booked from your own website or front desk, without owning a single vehicle.

POST /business/bookPOST /hire/{vehicle}/bookGET /airport/terminals

Airlines & travel

Issue airport pickup / drop-off vouchers and bundle them with tickets. Your passenger redeems the ride; you settle centrally.

POST /couponsPOST /business/book
🛡

Insurers

Move policyholders to and from hospital on demand or on a recurring schedule, and stand up a fleet for a claim — all from your own CMS or staff tools.

POST /business/bookPOST /subscriptionsPOST /hire/{vehicle}/book
🏢

Corporate transport desks

Let your booking or calendar system order staff rides with expense codes, and reconcile spend from monthly statements.

POST /business/bookGET /business/tripsGET /business/billing/statements
📦

Retail & wholesale

Trigger Areye the moment an order lands — instant pickup for same-hour delivery, or queue a next-morning batch of up to 50 stops.

POST /business/bookPOST /batches
🍔

Restaurants & takeaways

Hand delivery to Areye the moment an order is ready — dispatched to the nearest driver, with a public tracking link and a webhook when it is done.

POST /business/bookGET /trip-track/{reference}trip.completed
🛒

Grocers & pharmacies

Dispatch every order to a geo-available, crowd-sourced driver pool — one-off for instant delivery or batched for a scheduled run. COD + age-check supported.

POST /business/bookPOST /batchescod.collected

Authentication

The Areye Business API uses API key authentication. Include your key in the X-Business-API-Key header on every request. You can find and rotate your key in the Business Portal under Settings & API.

Request Header
X-Business-API-Key: your_api_key_here

Keep your API key secret. Never expose it in client-side code or public repositories. Use environment variables. Rotate your key immediately from the Business Portal if you suspect it has been compromised.

Endpoints

All paths are relative to the base URL https://api.areye.app/api. Every request is authenticated with your X-Business-API-Key unless marked public.

Bookings & Trips

POST /business/book Book a ride, parcel, cargo or airport transfer on behalf of an employee or guest — supports scheduling and up to 3 stops
GET /business/trips List all trips (filter by date range, employee, status, service type)
GET /business/trips/active List trips currently in progress
GET /business/trips/{id} Retrieve a single trip with receipt and route details
POST /business/trips/{id}/cancel Cancel a trip before driver assignment (full refund to wallet)
GET /business/trips/{id}/waybill Download the printable waybill for a delivery
GET /trip-track/{reference} Public shareable tracking link — no authentication required

Merchant Orders & Delivery

POST /business/book Same booking endpoint — add order_payload, prep_minutes, cod_amount or shop_deliver to turn a delivery into a merchant order
POST /business/trip/{reference}/ready Signal the order is packed and ready for collection — releases the waiting driver immediately
POST /business/book Age-restricted goods: add age_verification_required to force a photo-ID check at the door before handover
GET /trip-track/{reference} Customer-facing live tracking — no auth, safe to embed in your own app or SMS
GET /trip-chat/{reference} Read the customer/driver chat thread for an order (token-authed by reference)
POST /trip-chat/{reference} Post a message into the order chat as the customer

Batches & Subscriptions

POST /business/batches Submit a bulk delivery batch (segment: parcel or cargo, up to 50 stops)
GET /business/batches List batches with per-stop status
GET /business/batches/{id} Retrieve a single batch with all stops
POST /business/subscriptions Create a recurring delivery (parcel/cargo, days of week, pickup time)
GET /business/subscriptions List recurring delivery subscriptions
PATCH /business/subscriptions/{id}/{action} Pause, resume or cancel a subscription

Car Rental & Fleet

GET /business/rentals/vehicles List your rental fleet with rates, deposits and availability
POST /business/rentals/vehicles Add a vehicle to your rental fleet (photos, daily rate, km limits)
POST /business/rentals/vehicles/bulk Bulk-add vehicles to your fleet
PATCH /business/rentals/vehicles/mode Bulk-set listing mode (rental / ride-hailing / both)
PATCH /business/rentals/vehicles/{id} Update a vehicle — rates, deposit, marketplace publishing
DELETE /business/rentals/vehicles/{id} Remove a vehicle from your fleet
GET /business/rentals/bookings List rental bookings against your fleet
POST /business/rentals/bookings Create a rental booking (self-drive or chauffeured)
PATCH /business/rentals/bookings/{id}/{action} Confirm, complete or cancel a rental booking

Rent-to-Own & Driver Applications

GET /business/rto List rent-to-own agreements against your vehicles
POST /business/rto Create a rent-to-own agreement (earnings-skim or direct-pay, chosen cadence)
POST /business/rto/{id}/payments Record an instalment payment toward an agreement
GET /business/fleet-applications List driver applications for your marketplace-published vehicles
PATCH /business/fleet-applications/{id}/{action} Approve or reject a driver application (assigns the vehicle / opens the agreement)

Wallet, Payouts & Billing

GET /business/wallet Get current wallet balance and billing type
GET /business/wallet/transactions List wallet transactions (debits, credits, top-ups)
POST /business/wallet/topup Initiate a wallet top-up via a payment gateway
GET /business/wallet/topup/{ref}/status Poll the status of a wallet top-up
GET /business/payment-gateways List available payment gateways for your zone
GET /business/payouts List earnings payout requests
POST /business/payouts Request a payout of positive earnings
DELETE /business/payouts/{id} Cancel a pending payout request
GET /business/billing/statements List monthly billing statements

People, Groups & Coupons

GET /business/people List all employees and their spending limits
POST /business/people Invite an employee to your business account
PATCH /business/people/{id} Update employee details, group or spending limit
DELETE /business/people/{id} Remove an employee from your account
GET /business/groups List groups for organising employees
POST /business/groups Create a group
GET /business/coupons List employee discount coupons
POST /business/coupons Issue a new coupon (fixed or percent)
PATCH /business/coupons/{id}/deactivate Revoke a coupon

Reference Data

GET /business/vehicle-types List vehicle types with live pricing and availability
GET /business/parcel-sizes List parcel size tiers with rates
GET /business/zones List active service zones

Account

GET /business/profile Retrieve your business account profile and settings
PATCH /business/profile Update company name, billing contact, webhook URL or low-balance threshold
GET /business/api-key Retrieve API key metadata
POST /business/api-key/regenerate Rotate your API key (previous key invalidated immediately)
GET /business/dashboard Aggregate account metrics — trips, spend, active employees

Example: Book a ride for an employee

POST /api/business/book
curl -X POST https://api.areye.app/api/business/book \
  -H "X-Business-API-Key: your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "ride",
    "passenger_name": "Jane Smith",
    "passenger_phone": "+267XXXXXXXX",
    "pickup_address": "Plot 64271, Block 3 Industrial, Gaborone",
    "pickup_lat": -24.6282,
    "pickup_lng": 25.9231,
    "dropoff_address": "Molapo Crossing Mall, Gaborone",
    "dropoff_lat": -24.6540,
    "dropoff_lng": 25.9110,
    "vehicle_type_id": 1,   // optional override — prefer declaring the goods (see below)
    "expense_code": "TRAVEL-Q3"
  }'

Choosing the vehicle

Describe what you are sending and Areye dispatches a vehicle that can carry it. You do not need to know our fleet, and you should not hard-code vehicle_type_id — a refrigerator booked as a sedan job is exactly the failure this prevents.

Call GET /business/parcel-sizes first. It returns the live size ladder — size_key, the kilogram range, the eligible vehicle types and the rates for each band. It is the authoritative answer to "which size is my package?" and it changes without notice to this page, so read it rather than hard-coding the bands below.

All four fields are optional. Send whichever you have. Send none and the booking behaves exactly as it always did. They combine freely: a weight and a body type together are both honoured. If parcel_weight_kg and parcel_size disagree — 2 kg declared as oversized, say — the larger of the two wins, so a consignment is never under-served because two declarations contradicted each other.

Field Values Use it when
parcel_weight_kg any number You know the weight. Simplest option.
parcel_size small · medium · large · oversized Shoebox · shopping bag · car boot · TV, bicycle, appliance.
cargo_body_type closed · open · refrigerated The load needs a particular body — an open bakkie for building materials, a closed body for anything weatherproof. Refrigerated is commercial cold-chain for bulk consignments; a retail order with frozen items does not need it and should not declare it.
cargo_capacity_class half_ton · one_ton · three_ton · eight_ton A minimum capacity, not an exact match.

You are billed for the vehicle your goods require. Areye always dispatches the smallest vehicle that can do the job, so declaring accurately never costs more than the job needs — but over-declaring dispatches and bills a larger vehicle. On a typical route the oversized band costs around four times the small band. Under-declaring causes a failed collection; over-declaring is simply expensive. Declare what you are actually sending.

vehicle_type_id remains an override. When the goods need more vehicle than the class you named, your account decides what happens: by default Areye upgrades and bills the larger class. Accounts can instead cap upgrades at one size, or refuse the order and return 422 with the reason. Declare nothing and nothing changes — existing integrations are unaffected.

Example — a refrigerator

curl -X POST https://api.areye.app/api/business/book \
  -H "X-Business-API-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "parcel",
    "passenger_name": "Recipient Name",
    "passenger_phone": "+267XXXXXXXX",
    "pickup_address": "Store address",
    "pickup_lat": -24.6282, "pickup_lng": 25.9231,
    "dest_address": "Delivery address",
    "dest_lat": -24.6531, "dest_lng": 25.9089,
    "parcel_weight_kg": 85,
    "cargo_body_type": "closed"
  }'

No vehicle_type_id is sent. Areye resolves 85 kg to the oversized band, sees that a closed body is required, and dispatches a cargo van or bakkie — never a sedan or a motorbike.

Batches — one driver, one vehicle

A batch submitted to POST /business/batches is collected by a single driver in a single vehicle, so Areye fits that vehicle to the largest stop in the run. One oversized item among twenty small ones sends a bakkie, not a motorbike.

Set each stop's size to small, medium, large or oversized (s / m / l / xl are accepted too). Each stop is priced on its own size; the vehicle is chosen for the whole run. cargo_body_type and cargo_capacity_class may be set at the top level to apply to the entire batch — a closed-body run, for example.

The per-stop field is size (not parcel_size — that name belongs to /business/book). A stop may also carry weight_kg, and where a stop gives both, the larger of the two decides that stop — for its own price and for the batch's vehicle.

curl -X POST https://api.areye.app/api/business/batches \
  -H "X-Business-API-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "segment": "parcel",
    "stops": [
      {
        "recipient_name": "First Recipient",
        "recipient_phone": "+267XXXXXXXX",
        "pickup_address": "Store", "pickup_lat": -24.6282, "pickup_lng": 25.9231,
        "dest_address": "Drop 1",  "dest_lat": -24.6531,  "dest_lng": 25.9089,
        "size": "small"
      },
      {
        "recipient_name": "Second Recipient",
        "recipient_phone": "+267XXXXXXXX",
        "pickup_address": "Store", "pickup_lat": -24.6282, "pickup_lng": 25.9231,
        "dest_address": "Drop 2",  "dest_lat": -24.6402,  "dest_lng": 25.9155,
        "weight_kg": 85
      }
    ]
  }'

The first stop is priced as a small parcel and the second as oversized, but the whole run is collected by a vehicle able to carry the 85 kg item.

If no vehicle can satisfy the batch, the request is rejected with 422 and nothing is created — a batch fails whole, never part-way through.

Verifying what Areye decided

The 201 response, GET /business/trips and GET /business/trips/{reference} all echo what you declared and what we resolved, so you never have to infer it from the fare:

{
  "parcel_size": "oversized",
  "parcel_size_label": "Oversized",
  "parcel_weight_min_kg": 30.0,
  "parcel_weight_max_kg": 500.0,
  "cargo_body_type": "closed",
  "cargo_capacity_class": null,
  "volumetric_weight_kg": null,
  "vehicle_class": {
    "id": 5, "name": "Cargo Van",
    "body_type": "closed", "capacity_class": "one_ton"
  },
  "vehicle_upgraded": true,
  "vehicle_upgrade_reason": "Budget cannot carry this consignment (Oversized) — dispatched Cargo Van instead.",
  "test": false
}

vehicle_class is the class the order will actually be sent. vehicle_upgraded tells you whether we sent something larger than your request implied, and why.

Custody scanning — proving which bag

A collection photo and a driver signature prove that a handover happened. They do not prove which bag moved — so an unlabelled bag handed to the wrong driver still produces a perfect-looking record. Custody scanning closes that gap, and it is a premium add-on: ask Areye to enable it on your account.

When it is enabled, every merchant order comes back with a scan_code and a scan_label_url. Nothing else about your integration changes.

{
  "reference": "BIZ-1A7DED",
  "scan_code": "4EY2-Y6GD",
  "scanned_at": null,
  "scan_label_url": "https://api.areye.app/api/business/trips/BIZ-1A7DED/label"
}

What your picker app does

  1. Finish picking and packing the order as you do today.
  2. Fetch scan_label_url — an A6 PDF label carrying the QR (encoding scan_code) with your order number printed underneath it. Print it and stick it on the bag.
  3. Show the same code on your handover screen, so the person releasing the bag can match the physical label to the electronic one before the driver takes it.

The QR is Areye's custody token; the order number is for your staff and the driver reading it by eye. Two audiences, one sticker. If you would rather generate the label yourself, encode scan_code verbatim as a QR — the driver's scanner accepts it with or without the hyphen, and in any case.

What the driver does

The driver scans the label (or types the code beneath it) before collecting. They cannot mark the order collected until that scan matches. Scanning a label from a different order is rejected with “that label belongs to a different order” — which is the whole point: the mismatch surfaces while the driver is still standing in your store, not at a customer's door.

Labels do get torn and printers do jam, so a driver may force collection through. That is never silent: the override and its reason are recorded on the order, and scanned_at stays null so a forced collection is always distinguishable from a real scan.

Field Meaning
scan_code The custody token to encode as a QR. Absent when the add-on is off.
scan_label_url A ready-made A6 PDF label. Same auth header as any other call.
scanned_at When the driver matched the bag. Null until then — and stays null on a forced collection.

Sandbox bookings

A booking with "test": true moves no money and is dispatched to nobody. Every trip representation carries "test": true|false, and GET /business/trips?test=0 hides sandbox trips from your live list (?test=1 shows only them; omit it for everything).

201 Created — Response
{
  "success": true,
  "data": {
    "reference": "ARY-8F3A21",
    "status": "pending",
    "type": "ride",
    "fare": 85.00,
    "currency": "BWP",
    "tracking_url": "https://api.areye.app/api/trip-track/ARY-8F3A21"
  }
}

Booking a delivery instead? Set "type": "parcel" or "cargo" and add recipient details. Track any booking via the returned reference.

Webhooks

Set your Webhook URL and reveal your webhook secret in Business Portal → Settings & API. Every POST carries X-Areye-Event (event name), X-Areye-Signature (sha256=HMAC-SHA256(raw_body, secret) — verify constant-time before trusting it) and X-Areye-Delivery (a UUID, also id/event_id in the body). Delivery is at-least-once with retries: a 2xx (5s timeout) succeeds; otherwise Areye retries with exponential backoff — 6 attempts over ~8.5h, then exhausted. Build your receiver idempotent (dedupe on id), tolerant of out-of-order arrival. Match events to your order on data.reference or your own data.external_referencenot the top-level id.

Event Description
trip.accepted A driver accepted the job. data: {..., driver_name, driver_phone, vehicle}
trip.picked_up Collected from your store, en route to the customer (data.status: active)
trip.completed Delivered. Full manifest: amount (final settled), picked_up_at, completed_at, pod_photo_url, pod_signature_url, pod_at, age_verification_outcome, cod_status, cod_collected_amount
trip.cancelled Cancelled. data: {..., cancelled_by, reason}
trip.age_verified Age-restricted: buyer passed the ID check (age_verification_outcome: verified)
trip.age_verification_failed Age-restricted: buyer failed. Goods return to store, no COD. data: {reason, age_verification_outcome: refused}
cod.collected COD cash collected at the door. data: {declared_amount, collected_amount}
cod.refused COD order: recipient did not pay (collected_amount: 0)
cod.settlement_ready A batch of your collected cash is prepared for payout
cod.settled Areye has paid your collected COD cash out to you
coupon.redeemed An employee redeemed a discount coupon on a trip
wallet.low_balance Wallet balance fell below your configured threshold
wallet.auto_topup Your wallet was auto-topped-up from a saved card
fleet.application.received A driver applied for one of your marketplace-published vehicles

Rate Limits

The Business API operates on a fair-use basis suitable for normal booking and fleet-management workloads. If you are planning high-volume or bulk automation, contact our team so we can provision the right limits for your account. Endpoints that trigger external payment gateways may apply stricter per-minute limits.

Integrating a merchant platform

If you run a marketplace, restaurant group, grocery chain or online store and want Areye drivers to deliver your orders, you do not need a separate product. You use the same /business/book endpoint with extra fields. Your customers stay in your app; Areye handles dispatch, the driver relationship and proof of delivery.

What the driver actually sees

When you send order_payload, the driver gets a full order card — your store, your item list with photos, and the money to handle — instead of a bare pickup/drop-off pair. Every field below maps to something on that screen, so send what you want the driver to know.

You send Driver sees
order_payload.store_nameStore name as the card heading
order_payload.external_referenceYour order number, so staff and driver can talk about the same order
order_payload.items[]Itemised list — name, qty, price, options (e.g. "no onions", "sliced")
order_payload.items[].imageProduct photo beside each line. Must be a publicly reachable URL; a broken one degrades to a placeholder rather than failing the booking
order_payload.order_noteHighlighted note ("leave at the gate")
order_payload.order_amount"Order P348.50" chip — the value of goods carried
cod_amount"Collect P348.50 cash" chip
bring_change_amount"Bring change for P500" chip — send it when your customer says what note they will pay with
prep_minutesCountdown so the driver is timed to your kitchen or picking floor

The flow

  1. Customer orders and pays in your app, however you already collect payment.
  2. Your backend calls POST /business/book with pickup (your store), drop-off (your customer) and the order payload.
  3. Areye dispatches a nearby driver and starts firing webhooks.
  4. You call POST /business/trip/{reference}/ready when the order is packed.
  5. Your customer follows the live map at /trip-track/{reference} and can message the driver.
  6. Driver hands over against a delivery code, plus a proof-of-delivery photo if your account requires one. Delivery fees are debited from your Areye wallet.

Important — the handover gate. If you send handover_required: true, the driver is blocked from collecting until you call /ready. Use it when nothing may leave the counter early (restaurants, prescription collection, high-value e-commerce) — but you must wire the /ready call, or orders will sit uncollected. Leave it false (the default) and the driver may collect as soon as they arrive; prep_minutes still paces them.

Creating a merchant order

POST /api/business/book
X-Business-API-Key: your_api_key_here
Content-Type: application/json

{
  "type": "parcel",
  "pickup_address": "Choppies Riverwalk, Gaborone",
  "pickup_lat": -24.6541, "pickup_lng": 25.9237,
  "dest_address": "Plot 22, Phakalane",
  "dest_lat": -24.5821, "dest_lng": 25.9412,
  "passenger_name": "Kagiso M.",
  "passenger_phone": "+26771234567",

  "prep_minutes": 15,
  "handover_required": false,
  "age_verification_required": false,
  "cod_amount": 348.50,
  "bring_change_amount": 500.00,

  "order_payload": {
    "store_name": "Choppies Riverwalk",
    "store_phone": "+2673901234",
    "external_reference": "CHP-88421",
    "module_type": "grocery",
    "order_amount": 348.50,
    "order_note": "Leave at the gate if no answer",
    "items": [
      { "name": "Milk 2L", "qty": 2, "price": 24.00,
        "image": "https://cdn.example.com/milk.jpg" },
      { "name": "Brown bread", "qty": 1, "price": 12.50,
        "options": "Sliced" }
    ]
  }
}

Choosing how money moves

Model Send What Areye does
Prepaid order_payload only Customer already paid you. Driver delivers; your wallet is debited the delivery fee.
Cash on delivery cod_amount (+ optional bring_change_amount) Driver collects cash into a ring-fenced ledger held in custody, then Areye settles it to you and fires cod.settled.
Shop & deliver shop_deliver: true + purchase_amount The driver is advanced a float to buy the goods, uploads the receipt, and the difference is reconciled — order.receipt_reconciled. Restricted to trusted drivers, so dispatch may take longer.

Age-restricted orders (alcohol, tobacco)

Send age_verification_required: true on any order containing alcohol, tobacco or anything else with a minimum age. The driver is then blocked from completing the delivery until they have checked the buyer's photo ID and recorded the outcome. The proof-of-delivery photo and signature captured at that moment are the evidence.

Outcome What happens
verified Buyer proved they are of age. Delivery completes normally.
refused No valid ID, underage, or the buyer declined to show it. The driver is instructed not to hand over and to return the goods to your store. Handle the refund on your side.

Read the result back from any trip response — age_verification_required and age_verification_outcome — and use it to decide whether to refund. A return leg is exempt from the check, since nothing is being handed to a buyer.

The licence obligation stays with you. Areye gives you an ID check at the door and a durable record of it. That is a control, not a transfer of liability — the duty to sell lawfully (licence conditions, permitted hours, refusing intoxicated or underage buyers) remains with the licensed seller. Make sure delivery windows for these orders fall inside your permitted trading hours, and only flag orders you are licensed to sell.

Webhooks you will receive

Set your endpoint on PATCH /business/profile. Every payload carries reference — match it to the external_reference you sent. Each request also carries X-Areye-Event and X-Areye-Signature (sha256=HMAC-SHA256(raw_body, your_webhook_secret)) — your webhook_secret and Webhook URL are on the portal Settings & API page (separate from your API key) — verify it with a constant-time comparison before trusting the payload.

trip.acceptedDriver took the order — includes driver_name. Show "driver on the way".
trip.picked_upCollected from your store, en route to the customer
trip.completedDelivered — close the order on your side
trip.cancelledIncludes cancelled_by and reason
cod.settlement_readyCollected cash reconciled and ready to settle
cod.settledCash settled to your account
order.receipt_reconciledShop & deliver: driver receipt matched against the float
wallet.low_balanceTop up before deliveries start failing

Before you go live

  • Fund your wallet, or ask us to enable invoiced billing — delivery fees are debited per order, and a flat wallet stops dispatch.
  • Send us your store list and service areas so we can confirm driver coverage around each branch.
  • Ask us to enable proof-of-delivery photos if you need them for disputes.
  • Selling alcohol or tobacco? Flag those orders with age_verification_required, keep delivery windows inside your licensed trading hours, and handle refunds for refused handovers.
  • Serve item images over HTTPS from a public URL — signed or expiring links will show as placeholders.
  • Store our reference against your order. It is the key for tracking, chat, webhooks and support.
  • Dedicated driver pools, default prep times and shop-and-deliver access are configured per account — tell us your volumes and we will set them.

Sandbox & test mode

Add "test": true (or "dry_run": true) to POST /business/book to book without dispatching a real driver or moving money. You get a real reference, and the full signed webhook lifecycle (trip.accepted → picked_up → completed) fires to your Webhook URL just after the response returns — so you can verify your receiver end to end. Flip it to false for live orders.

Fetch, cancel & the delivery manifest

GET /business/trips/{reference} returns one order by reference or id; GET /business/trips?reference=… (or ?search=…) does server-side lookup. POST /business/trips/{reference}/cancel (body reason) cancels a not-yet-picked-up order and refunds any charge. The manifest — on both the record and the trip.completed webhook — carries the timeline (accepted/picked_up/arrived/completed), proof of delivery (pod_photo_url, pod_signature_url — absolute HTTPS, downloadable, your dispute evidence — and pod_at), the age result and COD status.

Account billing mode, final amount & /ready

Read billing_type on GET /wallet. Prepaid blocks a booking the wallet can't cover; postpaid dispatches on credit up to credit_limitbalance stays 0 and the amount owed is the positive amount_owed. This account billing mode is separate from the per-order money model (prepaid / COD / shop_deliver). billing_amount on the 201 is a provisional quote; the final settled charge is amount on trip.completed. POST /trip/{reference}/ready is only required for orders booked handover_required: true; a per-booking prep_minutes overrides your account default.

Money-field glossary & test button

Same value, different name by surface: declared COD is cod_amount (trip) / declared_amount (cod.collected); collected COD is cod_collected_amount / collected_amount. The authoritative delivery charge is amount on trip.completed (billing_amount is the provisional quote; final_fare_amount is set only if a completion recalc changed it). On Settings & API, Send test webhook POSTs a signed sample to your URL so you can confirm your HMAC check (and catch a stale secret) in one click.

Rewards & Gift Cards

Areye is the parent registry for coupons and gift cards across the whole suite. One code is minted once, and every platform that accepts it — Areye itself and any merchant integrated with these endpoints — validates and redeems it against the same ledger. That means a customer cannot spend the same code twice on two different platforms, and you get one reconciliation trail instead of several.

Areye is the primary redemption surface. A code minted here is, first and foremost, spend-on-Areye value — rides, deliveries, cargo and car hire. Dijo, Mmaraka and other participating merchants are secondary surfaces that the same code also happens to work on. When you issue a batch you are issuing value your customer can spend on Areye today, and in your own store as a bonus — not the other way round.

Authentication. Every merchant endpoint in this section is authenticated with your business API key in the X-Business-API-Key header — not an Authorization: Bearer token. All paths below are relative to https://api.areye.app/api. The public storefront endpoints at the end of this section take no key at all.

Two instruments, two very different money models

Read this before you issue anything. The difference is not cosmetic — it decides when your wallet is debited and what happens to value the customer does not use.

  Reward coupon (discount) Gift card (gift_card)
Spending behaviour Consumed in full, once. A P20 coupon against a P15 fare gives P15 and the remaining P5 is simply lost — the code is spent either way. One live use per code. Stored value that draws down. A P500 card against a P120 order leaves P380 on the card, and that balance follows the customer across further orders and across platforms until it is gone.
When your wallet is debited Depends on the discount type. A percentage reward costs nothing to mint — each code a customer actually uses debits the issuing merchant’s wallet for the discount granted, and unused codes never cost you a thebe. A fixed-amount reward is different: it is minted as stored value, so the whole batch face value is debited at issuance, exactly like a gift card, and the recipient keeps any unspent balance. At issuance. Gift cards are prepaid stored value: the whole batch face value (unit value × quantity) is debited the moment the batch is minted. The float then sits with Areye and each redemption draws it down.
Solvency check at issue Wallet must cover the batch at maximum exposure (fixed value, or max_discount for percent codes), else 422. Wallet must cover the full face value, else 422 telling you exactly how much to top up.
PIN Not used. Bearer cards carry a 4-digit PIN, returned at issuance and never retrievable again. Send pin on validate, redeem and balance. A card issued straight to a named Areye account carries no PIN — the account itself is the proof of ownership.
Activation Live as soon as it is issued (subject to starts_at). Not spendable until activated — either activate: true at issuance, or at the till via /gift-cards/activate.
Currency No FX conversion, ever. A code prices only in its own currency; a mismatch is refused with “This coupon can only be used in BWP”. Run multi-currency campaigns as sibling batches.

Three ways a gift card is redeemed

All three are live at the same time, and all three move the same balance on the same card. You do not choose one at issuance — the holder chooses at spend time.

1. Drawn down at checkout

The customer types the code (and PIN) at your checkout. You call /validate then /redeem, and only the order amount comes off the balance. The rest stays on the card.

2. Loaded into the Areye wallet

In the Areye app the holder can load the card whole into their wallet. The entire remaining balance is redeemed in one move and becomes wallet credit, spendable on any Areye service without re-entering the code.

3. Assigned to an account

A card can be issued directly against an existing Areye account. It then appears in that customer’s Promotions screen with nothing to type and no PIN to remember — they simply tap it at checkout.

Settlement: who bears the discount, who gets paid back

How a code is consumed

Every code also carries a redemption mode, which decides what happens the first time it is used. You rarely set this yourself — it is derived from the instrument type — but it is returned on every code and is worth understanding.

Mode Behaviour
once Consumed whole on first use, whatever the order is worth. A percentage discount works this way: use it and it is spent.
balance Spends down. Each redemption draws only what the order needs and the remainder stays on the code for next time. Gift cards and fixed-amount rewards work this way, which is why both are prepaid at issuance.
Every code carries a funding mode. It decides two separate money movements at redemption: whether anyone is debited to fund the value, and whether the merchant who honoured the value at the till is reimbursed for it. Both are written in the same database transaction as the redemption itself, so a discount is never granted unfunded.

Funding Where the money comes from Is the honouring merchant credited?
gift_card The buyer already prepaid Areye and Areye holds the float. Nothing is debited at redemption — the card’s stored value is simply drawn down. Yes. Your wallet is credited the amount you honoured.
platform Areye’s own marketing budget. No merchant wallet is touched to fund it. Yes. Areye is running the promotion, so Areye pays for it.
issuer The issuing merchant’s wallet is debited at the moment of redemption. If the balance cannot cover it the whole redemption fails rather than granting an unfunded discount. Yesunless the issuer is also the merchant honouring it. A shop redeeming its own promo owes itself nothing, so both the debit and the credit are skipped and it nets to zero.
merchant The merchant honouring the code bears the discount out of their own margin. No. No reimbursement is written.

Batches minted through POST /business/coupons/issue are issuer-funded reward coupons, or prepaid gift_card stored value when instrument_type says so. Reversing a redemption claws back both sides: the reimbursement is debited off the merchant who no longer gave anything up, and a wallet-funded issuer gets their money back. A gift card gets its drawn-down value returned to the balance. That is why /reverse is not optional.

Redemption lifecycle

Three calls, in this order. /validate is a dry run you make while the customer is still on the checkout screen; /redeem is the committing call and the only place a usage counter or a stored balance ever moves; /reverse puts the value back if the order later fails.

Endpoint Body What it does
POST /business/coupons/validate code, currency, cart_total, optional user_key, merchant_ref, platform, pin Prices the code against the cart and returns the value that would apply. Changes nothing. Safe to call as often as you like.
POST /business/coupons/redeem As above, plus order_amount and external_order_ref (both required) instead of cart_total Commits the redemption under a row lock, re-checking every cap. Idempotent on external_order_ref — retrying the same order reference returns the original redemption rather than double-spending. Always send your own order id. Because you honoured the value, you are the party reimbursed — see settlement above.
POST /business/coupons/reverse redemption_id, or coupon_id + external_order_ref (with platform) Releases a redemption on refund or cancellation. A discount code becomes usable again; a gift card gets its value back on the balance; settlement is unwound on both sides. Idempotent — reversing twice is a safe no-op. If you never call this, the value stays consumed.

/validate returns valid, reason (a message safe to show the customer when invalid), discount_amount, discount_type, coupon_id, currency, instrument_type, stackable, and for gift cards remaining_value plus remaining_after so you can show the customer what will be left on the card. /redeem answers 200 with redemption_id and idempotent, or 422 with a reason (expired, already used, below min_spend, wrong currency, out of scope, bad PIN, issuer wallet short). Treat any non-200 as “no discount applied” and charge the full amount.

POST /api/business/coupons/redeem
X-Business-API-Key: your_api_key_here
Content-Type: application/json

{
  "code": "AREYE-7K2MQX",
  "currency": "BWP",
  "order_amount": 120.00,
  "external_order_ref": "SHOP-88421",
  "user_key": "+26771234567",
  "merchant_ref": "riverwalk",
  "pin": "4821"
}

Issuing a batch

POST /business/coupons/issue mints up to 1,000 codes at once and returns them, along with one-time PINs when the batch is gift cards. Set instrument_type to discount (default) or gift_card. Codes are generated from an unambiguous character set with no 0/O/1/I/L, so they survive being printed and read over the phone.

Field Notes
quantityRequired. 1–1000 codes in the batch.
currencyRequired. The wallet debited is the wallet for this currency.
instrument_typediscount (default) or gift_card.
discount_type / discount_valueRequired for discount batches. fixed or percent; percent cannot exceed 100.
max_discountCaps a percent code — and is what your exposure is calculated against. An uncapped percent batch is treated as zero exposure, so cap it.
valueRequired for gift-card batches. Face value per card. Debited in full at issuance.
activate / activation_channelGift cards activate immediately by default; send activate: false to leave them dormant for till activation. Channel is one of web, retail, ecommerce, admin.
prefix / title / batch_labelYour own branding on the codes and your reference for the batch.
scope_platforms / scope_merchantsArrays restricting where the codes may be spent. Omit for suite-wide (["*"]).
min_spend / starts_at / expires_at / stackableStandard promo controls. expires_at must be in the future.
POST /api/business/coupons/issue
X-Business-API-Key: your_api_key_here
Content-Type: application/json

{
  "instrument_type": "gift_card",
  "quantity": 50,
  "currency": "BWP",
  "value": 200.00,
  "prefix": "STAFF",
  "batch_label": "Q3 staff rewards",
  "activate": true,
  "activation_channel": "admin",
  "expires_at": "2027-12-31T23:59:59+02:00"
}

201 Created
{
  "success": true,
  "data": {
    "batch_id": "BATCH-XXXXXXXXXX",
    "issued": 50,
    "coupons": [
      { "id": 9001, "code": "STAFF-4H8P2Q", "pin": "8140", "value": 200 }
    ],
    "note": "PINs are shown once and cannot be retrieved later ..."
  }
}

Store the PINs on the 201. They are hashed on our side and cannot be recovered. The batch above debits your BWP wallet P10,000 (50 × P200) immediately, before the first card is ever spent. If minting fails after the debit, the float is credited straight back.

Managing batches and cards

Endpoint What it does
GET /business/coupon-batches Lists your batches with total, active, redeemed, outstanding_value and issued_at — your reconciliation view over the float you are carrying.
PATCH /business/coupon-batches/{batch}/revoke Kills the codes in a batch (leaked print run, cancelled campaign) and returns how many were disabled; 404 if the batch is not yours or does not exist. Already-redeemed value is not clawed back.
POST /business/gift-cards/activate Till activation. Body: code, optional activation_channel and purchase_reference. Cards you print and rack unactivated are worthless until this call, so a stolen card off the shelf is not spendable. Only the issuing business may activate its own cards; idempotent on an already-live card.
POST /business/gift-cards/balance Body: code, optional pin. Returns currency, initial_value, remaining_value, activated, expires_at, status. Wrong PIN is a 403; unknown card a 404.

Public gift-card storefront

Consumers buy Areye-issued cards on the storefront at areye.app/gift-cards, which uses the unauthenticated, rate-limited /api/gift-cards/* family. You can drive the same flow from your own site. Nothing spendable is minted until the gateway confirms the money — never from the browser.

Endpoint What it does
GET /gift-cards/options Optional ?currency=. Returns the offered denominations, supported currencies, the gateways that can actually take money in that currency, and the terms.
POST /gift-cards/purchase Records an intent to buy. Body: amount, currency (BWP or ZAR), buyer_name, buyer_email, optional buyer_phone, recipient_name, recipient_email, recipient_phone, message, gateway. Returns a reference. No card exists yet.
POST /gift-cards/pay Body: reference, gateway, optional msisdn for mobile money. Returns either a redirect_url for card gateways or on-screen instructions for a mobile-money push.
POST /gift-cards/confirm Body: reference. Asks the gateway whether the money actually arrived and, if so, mints and delivers the card. Safe to call repeatedly — the browser return, the webhook and the cron sweep all call it and only one card is ever minted. 202 while payment is still unconfirmed.
GET /gift-cards/purchase/{reference} Buyer-facing status poll. Returns status, amount, currency, and the code once paid. The PIN is delivered to the recipient by email and SMS and is never re-exposed here.
POST /gift-cards/balance Body: code, optional pin. The public “how much is left on my card?” lookup. Wrong PIN is a 403.

Stored value can only be backed by money we can see. A gift card is minted only against a gateway confirmation that carries a positive collected amount matching the order. Cash and deferred gateways — the ones that report “paid” because a driver will collect on arrival — are refused outright at both payment initiation and confirmation. A gift card has no delivery leg to collect on, so accepting them would hand out free spendable value. Underpayments and zero-amount confirmations are rejected and logged.

Legacy promos and the unified code box

Areye had its own promotions system before the suite-wide registry existed. The two now resolve through one path: a passenger types a code into a single box, and Areye checks the suite registry first and the legacy promo table second. The customer never has to know which system their code lives in, and the booking screen and the Promotions screen can never disagree about what a code is worth.

Family Endpoints Reach
Suite registry (use this) POST /business/coupons/validate
POST /business/coupons/redeem
POST /business/coupons/reverse
POST /business/coupons/issue
Suite-wide. Coupons and gift cards, one ledger, settlement and reversal built in.
Legacy per-business promos POST /business/coupons
GET /business/coupons
GET /business/coupons/{promo}
PATCH /business/coupons/{promo}/deactivate
Areye trips only. Still supported for existing integrations, and still resolved by the same unified code box — but they carry no stored value, no gift-card behaviour and no cross-platform settlement.

Note the shapes: POST /business/coupons is the legacy single-promo create, while POST /business/coupons/issue mints a suite-wide batch. New integrations should use the /coupons/* family throughout.

Errors

Areye uses standard HTTP status codes. Responses are JSON with a success flag and, on failure, a human-readable message.

Status Meaning
200 Success
201 Created — resource was successfully created
401 Unauthorized — missing or invalid API key
403 Forbidden — your account does not have this feature enabled
404 Not Found — resource does not exist
422 Unprocessable — validation failed, insufficient wallet balance, or credit limit exceeded
429 Too Many Requests — rate limit exceeded
500 Internal Server Error — contact support

SDKs & Libraries

Official SDKs are under development. In the meantime, use the REST API directly with your preferred HTTP client.

JavaScript / Node.js
Coming Q3 2026
Python
Coming Q3 2026
PHP / Laravel
Coming Q4 2026

Ready to build?

Request an API key and sandbox access. Our team will set you up within 24 hours.

Request API Access