Skip to main content

Changelog

The Shipping API v1 is live

Reeds Engineering
Reeds Engineering
Developer platform

The Reeds Shipping API v1 is live at https://api.getreeds.co/shipping/v1. From your own checkout, store or system you can save addresses, get a quote, book a delivery with that quote, and follow it through signed webhooks.

Get started​

  1. Sign up and verify your email and phone. Your account opens in Test mode, with a ₦100,000 test wallet.
  2. Create test credentials in Dashboard → Developers, then follow the quickstart.
  3. When you're ready, complete the activation checklist. Reeds reviews it, and Live mode opens when your account is approved.

Test and Live use the same base URL and the same token endpoint, https://api.getreeds.co/oauth/token. Your credentials decide the mode.

What you can use now​

  • Deliveries across Lagos. Addresses, quotes that hold their price for 24 hours, bookings, drafts for orders that aren't ready, labels, cancellations and refunds. See Shipping.
  • A prepaid wallet funded through Paystack. See Wallet.
  • Platform accounts, by application, for businesses that ship for many merchants: each delivery names the merchant, and you pay by monthly invoice. See Platforms.
  • Webhooks, signed and retried for two days. See Webhooks.
  • A sandbox with a delivery simulator and real webhooks. See Sandbox.
  • The API Reference. Send a request from the page with Test Request, using test credentials. The API refuses live credentials from a browser.
  • The OpenAPI file and a Postman collection.

What isn't included​

  • Inter-state delivery. GET /coverage lists Lagos to the FCT with available: false, and a quote on that route returns INTERSTATE_NOT_AVAILABLE.
  • Official SDKs and plugins. The API is plain JSON over HTTPS, so any HTTP client works. See Libraries and plugins.
  • A status page. If you suspect an outage, email support@getreeds.co.

How we'll announce changes​

Every change to a public Reeds API gets an entry here. Additive changes ship within /v1, and breaking changes go to /v2 with six months of overlap. See Versioning.

The delivery promise is now a two-day range

Reeds Engineering
Reeds Engineering
Developer platform

Within a state, the delivery promise is now a two-day range, and the cut-offs have moved.

  • The cut-offs. Booked and ready before 13:45 Monday to Friday, or before 11:45 on a Saturday: delivered that day or the next working day. At or after the cut-off: the next working day or the working day after. The old cut-off was 16:30 every working day.
  • Sundays and public holidays are still off. Booked on one, the delivery is the next working day or the working day after.
  • schedule.earliest_delivery is filled on standard quotes, deliveries and webhooks. It is the range's first day, and deliver_by is the last. It used to be null outside inter-state routes. It is written at close of business (19:00) like the other two, so read the day: a delivery can arrive at any time on it.
  • null still happens. A delivery booked before this change has no earliest date, and after a delivery.schedule_changed the new deliver_by is the whole promise, so earliest_delivery is null and previous_schedule keeps the range. Show deliver_by alone when it is null.

pickup_by and deliver_by keep their meaning, and close of business is still 19:00, but a delivery booked between 13:45 and 16:30 now gets later dates than it did. Nothing is removed or renamed.

Test addresses corrected, and other small fixes to the docs

Reeds Engineering
Reeds Engineering
Developer platform

We checked these docs against the running API and corrected what didn't match.

  • Test addresses. Test mode treats every address as Live does, so Test addresses now shows what to send for each outcome, and the quickstart saves its addresses with map pins.
  • wallet.funded carries reference. The webhook now has the reference that POST /wallet/fund returned, so you can match it to the top-up you started. See Events. The amount limits, ₦1,000 to ₦100,000,000, are now in the wallet guide.
  • Invoicing. Monthly invoicing is for platform accounts. GET /invoices also accepts status=void.
  • Examples. GET /wallet shows every field it returns, the cancellation block shows note, and GET /coverage lists Lagos as the covered state, with the FCT as a route.
  • Ids. txn_ and inv_ are listed with the other prefixes, and real ids are longer than the examples: allow 64 characters.

The reference is the one change to the API, and it only adds a field. Everything else here corrects the docs.

Addresses and quotes, and hold_reason on ACCOUNT_ON_HOLD

Reeds Engineering
Reeds Engineering
Developer platform

How the address and quote endpoints behave in v1, in Test and Live mode.

Addresses​

  • POST /addresses resolves an address from a place_id, your latitude and longitude, or the text. A precise place or your coordinates are verified; an area, a whole road, or a text match that is only approximate is needs_confirmation with Reeds' suggestion. match.confidence is exact, approximate, none or confirmed.
  • Saving the same place with the same contact again returns the existing address as it is. Use PATCH /addresses/{id} to change its contact, notes or metadata; sending address, place_id, latitude or longitude there is 422 INVALID_REQUEST.
  • An address placed from your coordinates has location.place_id: null.
  • POST /addresses/{id}/confirm takes exactly one of accept_suggestion: true, a place_id, or latitude and longitude, and sends the address.confirmed webhook. An address that is already verified gets 409 ACTION_NOT_ALLOWED.
  • DELETE /addresses/{id} expires the address's unused quotes at once, and is refused with 409 ADDRESS_IN_USE while a draft uses it.
  • GET /addresses lists saved addresses only; addresses saved inline by POST /quotes are not listed (read them by id), and are deleted 30 days after their last use if no delivery was made from them.
  • metadata takes up to 20 keys of up to 40 characters, with string values of up to 500.

Quotes​

  • POST /quotes returns 201 with a qt_ quote that lasts 24 hours and books one delivery, or 200 with available: false and a reason when Reeds can't serve the route. When Reeds can't measure the route at all, it is 503 SERVICE_UNAVAILABLE: retry shortly.
  • pickup_date is Lagos today up to 7 days ahead; outside that range is 422 PICKUP_DATE_OUT_OF_RANGE, with the field in error.fields. The quote's schedule counts from that day.

ACCOUNT_ON_HOLD​

ACCOUNT_ON_HOLD now documents error.hold_reason: overdue_invoice (pay the overdue invoice) or reeds (contact Reeds). Branch on it, not on the message.

Nothing is removed or renamed. Clients that ignore unknown fields need no change.

Deliveries, wallet funding and the sandbox

Reeds Engineering
Reeds Engineering
Developer platform

How the delivery and money endpoints behave in v1, in Test and Live mode.

Deliveries​

  • POST /deliveries books from a quote (quote_id): 201 with status scheduled, the tracking code and the wallet debited at once. A quote books one delivery: using it again is 409 QUOTE_ALREADY_USED with error.delivery_id, and an expired quote (or one whose address you deleted) is 409 QUOTE_EXPIRED.
  • If the wallet doesn't cover it you get 402 INSUFFICIENT_BALANCE (402 CREDIT_LIMIT_REACHED for an invoice account): nothing is created and the quote stays unused. Fund the wallet and retry with the same Idempotency-Key.
  • Leave out quote_id to create a draft from two saved addresses in any status. A draft never gets a 402: it waits with CONFIRM_PICKUP_ADDRESS, CONFIRM_DROPOFF_ADDRESS, AWAITING_PRICE, BOOK or FUND_WALLET. With auto_book: true it books itself once it is priced and the price is within max_total; above max_total it waits with BOOK. max_total never limits POST /deliveries/{id}/book.
  • pickup_instructions and dropoff_instructions (up to 500 characters) are for that delivery only; the rider sees them before the address's own notes. PATCH /deliveries/{id} changes them, reference and metadata (null clears a field): the pickup instructions until the parcel is collected, the drop-off instructions until it is delivered, and never on a cancelled delivery (409 ACTION_NOT_ALLOWED). Sending an address or the package is 422.
  • POST /deliveries/{id}/book (402 when short, 409 ACTION_NOT_ALLOWED when not priced or already booked, 503 while live booking is paused), POST /deliveries/{id}/ready (409 on a delivered or cancelled delivery) and POST /deliveries/{id}/cancel (reason becomes cancellation.note; 409 CANCEL_NOT_ALLOWED once the rider has set off) answer.
  • A delivery booked as it is created sends delivery.created (already scheduled) then delivery.booked. A fresh draft's events is empty: the timeline lists statuses the delivery moved to.

Idempotency​

Requests that fail with 401, 402, 403, 409 or 429 release the Idempotency-Key, so retry with it once the cause is fixed. 400, 404 and 422 answers are saved and replayed with Idempotent-Replayed: true, like a success. A key you also used on the dashboard is 422 IDEMPOTENCY_KEY_REUSED.

Money​

  • POST /wallet/fund returns { amount, currency, payment_url, reference }. Your credentials act for the account: any live credentials can start a top-up, and Paystack sends the receipt to the account Owner. A test token, or an invoice account, gets 409 ACTION_NOT_ALLOWED.
  • POST /sandbox/wallet/fund credits the test wallet at once and books drafts waiting on FUND_WALLET. An Idempotency-Key is optional there: a retry with the same key never credits twice. Invoice accounts get 409.
  • POST /sandbox/deliveries/{id}/advance moves a sandbox delivery and fires the real webhooks. Both sandbox endpoints answer 403 TEST_MODE_ONLY to live credentials.

Nothing is removed or renamed. Clients that ignore unknown fields need no change.

How the live IP allowlist is checked, and webhook endpoints Reeds turns off

Reeds Engineering
Reeds Engineering
Developer platform

How the API checks the live IP allowlist announced on 28 October. It is optional, and off while your list is empty.

What the API checks​

  • With at least one address on your list, POST /oauth/token with a live client id from any other address returns 400 with { "error": "unauthorized_client" }, and the secret isn't checked. Every /shipping/v1/* call with a live token from any other address returns 403 IP_NOT_ALLOWED, naming the address Reeds saw, before anything runs.
  • Test credentials and test tokens are never checked.
  • Reeds reads your address from its own proxy, never from an X-Forwarded-For header you send. An IPv4 address that reaches Reeds as IPv6 (::ffff:41.58.10.5) is matched as the IPv4 address.
  • A change takes effect within a minute.
  • Refused calls are in the dashboard's API request log, and the addresses they came from are in the allowlist card's Recent IP addresses, marked as refused.

Changing the list​

  • Adding and removing an address each need a 6-digit code from your authenticator app (recovery codes aren't accepted), so turn on two-factor authentication first. After 5 wrong codes in 15 minutes, that person's changes are paused for up to 15 minutes.
  • The account's Owners get an email for every change: who made it, when, from which IP address, and whether it turned the list on or off.
  • Removing an address always works, even while adding is paused.

Webhook endpoints Reeds turns off​

Reeds can turn a webhook endpoint off, for example while investigating a problem with it. Your audit log says why. Nothing is sent to it then, not even a resend or a test event, and saving the URL again doesn't turn it back on; contact support. While it is off, POST /webhooks/test returns 409 ACTION_NOT_ALLOWED. An endpoint that was disabled after failing for 3 days works as before: save its URL again to turn it back on.

Nothing is removed or renamed.

When wallet.low_balance is sent

Reeds Engineering
Reeds Engineering
Developer platform

The wallet.low_balance webhook now documents exactly when it is sent. Its payload is unchanged: balance, low_balance_threshold and currency.

  • It is sent once, by the debit that takes the balance to or below the threshold you set in the dashboard. A batch of bookings that crosses it sends one event, not one per booking.
  • It is not sent again until the balance has risen back to the threshold plus a margin (₦1,000 or 10% of the threshold, whichever is more) and fallen again, and at most once an hour. A small refund that lifts the balance just over the line doesn't count as recovering.
  • Changing the threshold re-arms it without sending one: the next debit at or below the new threshold sends it.
  • It is sent in test mode too, so you can test your handler. The email to the account's Owners and Admins goes in live mode only.

Nothing is removed or renamed.

Webhooks

Reeds Engineering
Reeds Engineering
Developer platform

Webhooks send in Test and Live mode, for every event type in the list.

The endpoint​

  • Save one HTTPS endpoint per mode in Dashboard → Developers. Reeds checks the URL when you save it and again on every send: it must use a host name (not an IP address, localhost or an internal name) that resolves only to public addresses, and each send goes to the address Reeds checked. Redirects are never followed.
  • The signing secret (whsec_test_... or whsec_live_...) is shown in the dashboard. Rotating it keeps the old secret signing for 24 hours: each event then carries two v1 values in Reeds-Signature, so a receiver holding either secret verifies. Rotating again within the 24 hours drops the oldest secret.

Sending and retries​

  • Every event is a POST with Reeds-Event-Id, Reeds-Event-Type and Reeds-Signature, signed afresh on each attempt. Reply 2xx within 10 seconds.
  • A failed attempt (an error status, a redirect, a timeout, a connection or TLS failure) is retried 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours after the previous attempt, each with up to 15 seconds of jitter. After the 8th attempt the event is failed.
  • An endpoint that keeps failing for 3 days is disabled: waiting events are marked failed, and the account's Owners, Admins and Developers are emailed. Save its URL again to turn it back on.

The event log and test events​

  • The dashboard's event log shows every event, its attempts and your server's answers for 30 days. Resend makes one more attempt at once, without changing the retry schedule, even while the endpoint is disabled.
  • POST /webhooks/test takes an optional type (any event type; delivery.status_changed by default) and answers { event_id, type, sent }. A test event is sent once and never retried. No endpoint for the mode, or an unknown type, is 422 INVALID_REQUEST.

Nothing is removed or renamed. Clients that ignore unknown fields and event types need no change.

Invoice numbers, void, the invoice PDF, and two new webhooks

Reeds Engineering
Reeds Engineering
Developer platform

For platform accounts billed monthly, invoices now carry everything you need to reconcile them.

The Invoice object​

  • number: RDS-2026-000123 (TEST-2026-000123 in test mode), numbered per mode and year with no gaps.
  • discounts and adjustments (credit notes, debit notes, credit carried from an earlier invoice), so delivery_fees + vat + service_charges - discounts + adjustments = total.
  • amount_paid, amount_credited and amount_due: what is still owed after payments and credits.
  • voided_at, and a new status, void: Reeds cancelled the invoice and it owes nothing. A corrected one is issued in its place with a new number. GET /invoices?status=void lists them.

VAT is still on the delivery fee only; the service charge carries none.

The PDF​

GET /invoices/{id}/pdf returns the invoice as an A4 PDF, with every line. It answers 304 to a matching If-None-Match.

New webhooks​

  • invoice.paid: an invoice was paid in full, by a payment, a transfer Reeds recorded, or a credit that settled it.
  • invoice.overdue: an invoice passed its due date unpaid. The account is on hold until it is paid: new bookings get 403 ACCOUNT_ON_HOLD.

Both carry data.invoice, like invoice.issued.

Nothing is removed or renamed. Clients that ignore unknown fields and unknown event types need no change.

Optional IP allowlist for live credentials

Reeds Engineering
Reeds Engineering
Developer platform

You can now limit your live credentials to the IP addresses of your own servers. It is optional and off by default, so nothing changes for you unless you turn it on. We recommend it.

What changes once it is on​

  • POST /oauth/token with a live client id from an address that isn't on the list returns 400 with { "error": "unauthorized_client" }, and no token is issued.
  • Every /shipping/v1/* call with a live token from an address that isn't on the list returns 403 with the new code IP_NOT_ALLOWED.
  • Test credentials and test tokens are never restricted.

An empty list accepts any address, as before. Set it up in the dashboard under Developers, in Live mode. See Authentication.

Parcel limits, prohibited items, liability and the merchant filter

Reeds Engineering
Reeds Engineering
Developer platform

What Reeds carries is now part of the contract, and platforms can filter by merchant.

New error codes (422)​

  • PARCEL_TOO_HEAVY: package.weight_kg over 30 kg.
  • PARCEL_TOO_LARGE: the longest side in the new, optional package.dimensions_cm (length, width, height, in centimetres) over 120 cm.
  • PROHIBITED_ITEM: the description names something Reeds can't carry (cash, weapons, drugs, hazardous goods such as petrol or gas cylinders, perishable food). The message names the category.
  • SHIPPER_REQUIRED: a platform account created a delivery without shipper.

Liability​

Every delivery now has liability: { covered_amount, cap } (kobo): what Reeds covers if the parcel is lost or damaged, which is the declared value up to ₦50,000. A higher declared_value is still accepted.

Merchants​

GET /deliveries takes merchant_id: platform accounts get only that merchant's deliveries (shipper.merchant_id).

Nothing is removed or renamed. Clients that ignore unknown fields need no change.

Sandbox data is now kept for 90 days

Reeds Engineering
Reeds Engineering
Developer platform

Sandbox (test-mode) data is now kept for 90 days. Earlier text said 30 days. Live data is unaffected, and your sandbox wallet's starting balance is never removed. See Sandbox.

Clearer rules for retries and webhook failures

Reeds Engineering
Reeds Engineering
Developer platform

Two clarifications in the API reference; the API's behaviour is unchanged.

  • Idempotency-Key and 5xx errors. A 5xx releases the key only if nothing was saved. If the work was saved and the request failed afterwards, a retry with the same key replays the saved result, like any success. See Idempotency.
  • Webhook retries. After the last retry (24 hours), the event is marked failed. An endpoint that keeps failing for 3 days is disabled, and the account is emailed.

Postman collection

Reeds Engineering
Reeds Engineering
Developer platform

You can now download a Postman collection of the Shipping API from Libraries and plugins.

It has every endpoint, grouped like the API reference, with OAuth 2.0 client credentials set up: put your test client id and secret in the collection variables and Postman fetches the access token for you. It is generated from the OpenAPI file each time these docs are published, so the two always match.