REFERENCE
Changelog
The rest of this reference is generated from the running code, so it always describes today. This page is written by hand, because a generated document cannot tell you that a field you rely on started meaning something else last week.
`deliveries:cancel` was missing from this reference
Cancelling a delivery has needed its own scope, deliveries:cancel,
since before launch — kept apart from deliveries:write so that an
integration which only books deliveries cannot also cancel them.
The scopes table on this page never listed it, and said deliveries:write
covered cancelling. It did not. If POST /v1/deliveries/{id}/cancel has been
returning 403 insufficient_scope, that is why, and the fix is a permission
on your key rather than anything in your code.
Nothing about the API changed. We can add the scope to the key you already use, without reissuing it: the secret stays the same, there is nothing to swap in your integration, and it applies to your next request. Ask us, or change it yourself under API keys in the portal.
Repeated quotes, and limits on quote requests
POST /v1/quotes:
- The same request gets the same quote. Asking again for a trip you
already have an unused quote for (same pickup, drop-off,
service_levelandcash_to_collect, with over a minute left) returns that quote, with the sameid, instead of a new one. Ask for a new quote when the trip changes, and reuse one until itsexpires_at. - Quote requests are limited to 30 a minute per account, on top of the
general limit. Past it you get
429rate_limit_exceededwithRetry-After. - New trips are limited to 300 a day per account (a trip we have not
priced before; repeats and trips already priced do not count). Past it you
get
429daily_quote_limit, withRetry-Afteruntil midnight in Harare. Talk to us if you need more.
Bookings are not limited by these. Nothing was removed or renamed.
Editing a delivery re-prices it and reaches the courier
PATCH /v1/deliveries/{id} now does what its reference always said:
- A new dropoff address is priced again on your terms, and
pricingshows the new fee. If you did not send adelivery_feeof your own when booking,delivery_feeandtotalfollow the new price too; one you set is left as it was. An address outside the area we cover is refused withoutside_service_area: cancel the delivery and book it again instead. A delivery held for review stays held. Re-pricing can also fail withroute_not_foundorpricing_unavailable, as booking can. - The request body is checked first: an invalid body is a 422 even for a delivery that cannot be edited any more.
- A delivery already with the courier network has the change passed on, so the courier works from the new address, phone or instructions.
dropoff.phonemust be a Zimbabwean number, as it already had to be when booking.
Nothing was removed or renamed.
Saved pickup locations
A business with several branches can save each one as a pickup location in the portal, and choose between them without typing an address.
GET /v1/pickup-locations(scopedeliveries:read) lists your saved locations, the default first:id(pl_…),label,name,phone,address,instructions,is_default.pickup_locationonPOST /v1/deliveriesandPOST /v1/quotes: a location's id, instead of apickupobject. Send one or the other, not both. Send neither and the default location is used, as before.pickup.locationon a delivery: the id of the saved location it was collected from, ornullwhen the pickup was sent with the booking.
An unknown, archived or other account's location id is refused with
pickup_location_not_found. Nothing was removed or renamed.
Courier, ETA and proof of delivery are filled in
Three fields on a delivery that were always null now carry data
from the courier network. Their shape is unchanged where it was documented,
and each is still null when there is nothing to say:
courier:{ "name": "...", "phone": "..." }once a courier has accepted the delivery. Back tonullif the courier is taken off the job.eta: when the delivery is expected to arrive (ISO 8601), while it is on its way.nullonce it has finished, or when there is no estimate.proof_of_delivery:{ "photo_urls": ["https://..."] }when the courier took photos at the door. The links are hosted by the courier network.
hold.message is unchanged in wording. Nothing was removed or renamed.
Held deliveries expire, and how each outcome is charged
Two things your integration may notice:
- A delivery held for us is cancelled if we do not decide within 48 hours
(or 2 hours before its
scheduled_for, if that is sooner, but never less than 2 hours after it was held). You receivedelivery.cancelled, and it is not charged. Book it again if you still need it. - [added]
cancellation_reasonon a delivery says why it was cancelled:customer_cancelled,merchant_cancelled,duplicate,address_problem,other, orhold_expired. Null unless cancelled. - What you are charged now follows our published terms exactly, and your portal month-to-date matches your invoice: a delivery is charged once it finishes, in the month it finishes (midnight to midnight, Harare time). Delivered: the fee. Failed (nobody in, refused): the fee plus the return at the same rate. Cancelled after a courier collected it: the fee in full. Cancelled before collection, or never dispatched: nothing.
Nothing was removed or renamed.
Two pricing modes: distance based and flat fee per zone
Road Runner now prices every delivery in one of two ways, agreed with you and shown on your portal profile:
per_km, distance based. The driving distance from pickup to dropoff times a rate per km, never less than a minimum delivery charge. Part of a km is charged exactly or rounded up to the next whole km, as agreed with you.zones, flat fee per zone. One price per zone of kilometres. A zone includes the km it ends on.
What this changes for your integration:
pricing_modeon a quote is nowper_kmorzones.promotion,distanceandnoneare retired.fee_reasonis nowper_km,per_km_minimum,zoneorbeyond_last_zone.flat_fee,order_total_above_threshold,distance_tierandfree_modeare retired.- What your goods are worth no longer affects the price.
items_valueis still accepted, and ignored. There is no longer free delivery above an order value. - Every quote now measures distance, so it needs a pickup address (in the
request, or set on your account). Without one a quote returns
422 pickup_address_missing; with no driving route,422 route_not_found. A booking in the same situation is still accepted, and held until we have spoken to you. outside_delivery_radiusis no longer returned. A far address is quoted, and the booking is accepted but held until we have spoken to you (holdon the delivery). On zone pricing, a dropoff past your last zone is held the same way, withhold.reasonsof["beyond_last_zone"], and is charged your last zone's price unless we agree otherwise.
No field was added, removed or renamed. Deliveries booked before this change keep the fee they were charged.
We no longer bill from the `delivery_fee` you send
delivery_fee on a delivery is, and always was, meant to be your
own delivery line on your own order: part of the total your customer
pays. It is still that, you can still send it, and it still appears on the
delivery exactly as you sent it.
What has changed is that it is no longer what we invoice you. Until now, a
booking that carried a delivery_fee skipped our pricing entirely, and that
number became both your order line and our revenue figure. Sending 0 meant
the delivery was recorded as free to us.
Every delivery is now priced by our own terms when it is booked, whatever you send, and that is the figure on your invoice and on your portal. Two things follow that are worth knowing:
- Your month-to-date in the portal may move. It is now the price our terms produce, which is what you were always meant to be billed. If you have been sending your own fee, the figure you see will change to match your actual invoice.
- Deliveries you book with your own fee are now measured. They previously
had no
distance_metresat all, because supplying a fee skipped the lookup.
No request or response field was added, removed or renamed. If you send nothing, nothing about your integration changes.
`service_level` on quotes and deliveries
You can now ask for standard, express or scheduled when quoting
or creating a delivery, and the level a quote was priced at is carried onto
the delivery made from it.
Express and scheduled are priced as a percentage of your standard fee rather than as a flat surcharge. Your own percentages are on your account in the portal.
Omitting the field books standard, which is what every delivery was before
this, so existing integrations are unaffected.
Booking against a quote keeps the quote's speed
A delivery created from a quote_id now takes its service level from
the quote rather than from the booking. Asking for express against a standard
quote previously delivered express at the standard price.
Every change that would break an integration is announced here before it ships, and we tell account holders by email as well. If something on this page affects you and you are not sure what to do, ask us.