An order is one aggregate with three legs: the order itself, the payment and the fulfilment. Your integration almost never needs the legs — the phase on every event is already the answer to "what do I tell the person waiting".
Creating an order
A create takes a market, a merchant, lines with item ids and quantities, a place and a payment method. Prices are not sent: the store owns them, and sending one that disagrees answers 409 rather than quietly charging the wrong amount.
https://api.palopgo.com/v1/orders \ -H "Authorization: Bearer $PALOPGO_KEY" \ -H "Idempotency-Key: cmd_office_lunch_2026w41" \ -d '{ "market": "gw-oxb", "merchant_id": "m-gw-001", "currency": "XOF", "fulfilment": "delivery", "lines": [{ "item_id": "it-gw-001-01", "qty": 40, "options": [{ "group_id": "proteina", "option_id": "frango" }] }], "place": { "bairro": "Bandim", "landmark": "Edifício do banco, segundo portão depois do mercado", "phone": "+245955221104" }, "payment_method": "orange_money", "reference": "PO-2026-0041" }'Fulfilment kinds
| fulfilment | What happens |
|---|---|
| delivery | A courier is offered the job, collects at the store and proves delivery with an OTP or a photo. |
| pickup | No courier events at all. The order goes READY and waits; the customer collects. |
| at_customer | A service performed at the address — a hairdresser, a sound system. No pickup leg. |
| ticket | Nothing physical moves. Tickets are issued against the event's tiers. |
The state machine
States are coarse on purpose. A new state is a new customer-facing sentence, not a new internal step, which is why there are twenty of them and not two hundred.
| state | phase | What it means |
|---|---|---|
| CREATED | PAYMENT_PENDING | Accepted and queued. Nothing has been charged. |
| CONFIRMED | CONFIRMED | Payment confirmed, or cash promised. Waiting for the store. |
| ACCEPTED | ACCEPTED | The store said yes and gave a ready time. |
| PREPARING | PREPARING | In the kitchen. |
| READY | READY | Waiting for the courier, or for collection. |
| NO_COURIER | NO_COURIER | Nobody took the offer inside the lease. Re-offered automatically. |
| COURIER_ASSIGNED | COURIER_ASSIGNED | A courier accepted and is heading to the store. |
| PICKED_UP | PICKED_UP | Collected. |
| IN_TRANSIT | IN_TRANSIT | On the way. |
| ARRIVING | ARRIVING | Inside the arrival geofence. A hint, never proof. |
| AT_DROPOFF | AT_DROPOFF | At the door, asking for the customer's code. |
| DELIVERED | DELIVERED | Proof accepted. Cash, if any, is collected. |
| DELIVERY_FAILED | DELIVERY_FAILED | Nobody at the address. The order returns to the store. |
| CANCELLED | CANCELLED | Cancelled with a reason code and a refund amount. |
Addresses a courier can find
In Luanda, Maputo, Bissau and São Tomé the street is not the address. The pin, the bairro, the landmark and a phone number are, in that order of usefulness. Praia is mixed, and only Lisboa is street-first.
{ "pin": { "lat": -8.9192, "lng": 13.1844 }, "bairro": "Talatona", "landmark": "Condomínio Jardim de Rosas, portão 3, depois da bomba Sonangol", "phone": "+244923456789", "access_notes": "Dizer ao guarda que é para a casa 14"}Cash, change and the float
Cash is the default rail in most of the platform, not a fallback. A cash order carries what the customer will hand over and what the courier must give back, and the dispatcher only offers the job to a courier whose float can cover it.
{ "payment": { "method": "cash", "state": "PROMISED" }, "cash": { "amount_minor": 1580000, "change_for_minor": 2000000, "change_minor": 420000, "courier_float_ok": }}- Nothing is charged before delivery; payment.confirmed fires when the courier marks the cash collected.
- change_for_minor is the note the customer says they will pay with. Exact change is change_for_minor equal to amount_minor.
- If no nearby courier can break the note, courier_float_ok is false and the order is still accepted — you decide whether to ask for exact change or switch rail.
Cancelling and refunding
A cancel always carries a reason code, and the answer tells you what will be refunded and when. Cancelling after the store has accepted may be refused, in which case the order continues and you can offer the customer a refund instead.
https://api.palopgo.com/v1/orders/ord-2026-pt-1079/cancel \ -H "Authorization: Bearer $PALOPGO_KEY" \ -H "Idempotency-Key: cmd_cancel_1079" \ -d '{ "reason_code": "customer_changed_mind" }'