Skip to content
ROAD RUNNER

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.

FIXED

`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.

CHANGED

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_level and cash_to_collect, with over a minute left) returns that quote, with the same id, instead of a new one. Ask for a new quote when the trip changes, and reuse one until its expires_at.
  • Quote requests are limited to 30 a minute per account, on top of the general limit. Past it you get 429 rate_limit_exceeded with Retry-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 429 daily_quote_limit, with Retry-After until midnight in Harare. Talk to us if you need more.

Bookings are not limited by these. Nothing was removed or renamed.

CHANGED

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 pricing shows the new fee. If you did not send a delivery_fee of your own when booking, delivery_fee and total follow the new price too; one you set is left as it was. An address outside the area we cover is refused with outside_service_area: cancel the delivery and book it again instead. A delivery held for review stays held. Re-pricing can also fail with route_not_found or pricing_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.phone must be a Zimbabwean number, as it already had to be when booking.

Nothing was removed or renamed.

ADDED

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 (scope deliveries:read) lists your saved locations, the default first: id (pl_…), label, name, phone, address, instructions, is_default.
  • pickup_location on POST /v1/deliveries and POST /v1/quotes: a location's id, instead of a pickup object. Send one or the other, not both. Send neither and the default location is used, as before.
  • pickup.location on a delivery: the id of the saved location it was collected from, or null when 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.

ADDED

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 to null if the courier is taken off the job.
  • eta: when the delivery is expected to arrive (ISO 8601), while it is on its way. null once 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.

CHANGED

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 receive delivery.cancelled, and it is not charged. Book it again if you still need it.
  • [added] cancellation_reason on a delivery says why it was cancelled: customer_cancelled, merchant_cancelled, duplicate, address_problem, other, or hold_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.

CHANGED

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_mode on a quote is now per_km or zones. promotion, distance and none are retired.
  • fee_reason is now per_km, per_km_minimum, zone or beyond_last_zone. flat_fee, order_total_above_threshold, distance_tier and free_mode are retired.
  • What your goods are worth no longer affects the price. items_value is 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_radius is no longer returned. A far address is quoted, and the booking is accepted but held until we have spoken to you (hold on the delivery). On zone pricing, a dropoff past your last zone is held the same way, with hold.reasons of ["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.

CHANGED

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_metres at 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.

ADDED

`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.

FIXED

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.