Quickstart
This quickstart takes you from a new test account to a booked delivery. You will save two addresses, get a quote and book it. Nothing here costs money or sends a rider.
The cURL steps use jq to keep the token and ids in shell variables, so each step can use the one before it.
1. Create a test account
Sign up and verify your email and phone number. Your account opens in Test mode, with a ₦100,000 test wallet ready.
2. Copy your test credentials
In the dashboard, make sure the switch in the header says Test, then open Developers and create your credentials. Copy the client id (rci_test_...) and the client secret (rcs_test_...). The secret is shown once.
Put them in environment variables on your machine:
export REEDS_CLIENT_ID="rci_test_xxxxxxxxxxxx"
export REEDS_CLIENT_SECRET="rcs_test_xxxxxxxxxxxx"
3. Get an access token
Exchange the credentials for an access token. A token lasts one hour. See Authentication for caching.
- cURL
- Node.js
- PHP
- Python
export REEDS_ACCESS_TOKEN=$(curl -s https://api.getreeds.co/oauth/token \
-d grant_type=client_credentials \
-d client_id="$REEDS_CLIENT_ID" \
-d client_secret="$REEDS_CLIENT_SECRET" | jq -r .access_token)
const res = await fetch('https://api.getreeds.co/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'client_credentials',
client_id: process.env.REEDS_CLIENT_ID,
client_secret: process.env.REEDS_CLIENT_SECRET,
}),
});
const { access_token } = await res.json();
process.env.REEDS_ACCESS_TOKEN = access_token; // or keep it in a variable
<?php
$ch = curl_init('https://api.getreeds.co/oauth/token');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => http_build_query([
'grant_type' => 'client_credentials',
'client_id' => getenv('REEDS_CLIENT_ID'),
'client_secret' => getenv('REEDS_CLIENT_SECRET'),
]),
]);
$token = json_decode(curl_exec($ch), true);
curl_close($ch);
putenv('REEDS_ACCESS_TOKEN=' . $token['access_token']);
import os
import requests
res = requests.post(
"https://api.getreeds.co/oauth/token",
data={
"grant_type": "client_credentials",
"client_id": os.environ["REEDS_CLIENT_ID"],
"client_secret": os.environ["REEDS_CLIENT_SECRET"],
},
)
os.environ["REEDS_ACCESS_TOKEN"] = res.json()["access_token"]
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "shipping",
"livemode": false
}
The examples below read the token from REEDS_ACCESS_TOKEN. Every response has "status": "success" and your result in data.
Each example runs on its own. Save a Node.js example as a .mjs file and run it with Node 18 or later. A token set in code lasts only for that run, so to follow along, export REEDS_ACCESS_TOKEN in your shell as the cURL tab does. In your app, keep the token in a variable.
4. Save the pickup address
Send a map pin as well as the text, so the outcome is predictable: Reeds trusts latitude and longitude, so this one comes back verified and serviceable in Ikeja.
- cURL
- Node.js
- PHP
- Python
PICKUP_ID=$(curl -s https://api.getreeds.co/shipping/v1/addresses \
-H "Authorization: Bearer $REEDS_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Ada Okafor",
"phone": "08031234567",
"address": "23 Allen Avenue, Ikeja, Lagos",
"latitude": 6.6018,
"longitude": 3.3515
}' | jq -r .data.id)
echo "$PICKUP_ID"
const res = await fetch('https://api.getreeds.co/shipping/v1/addresses', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REEDS_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Ada Okafor',
phone: '08031234567',
address: '23 Allen Avenue, Ikeja, Lagos',
latitude: 6.6018,
longitude: 3.3515,
}),
});
console.log(await res.json());
<?php
$ch = curl_init('https://api.getreeds.co/shipping/v1/addresses');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REEDS_ACCESS_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Ada Okafor',
'phone' => '08031234567',
'address' => '23 Allen Avenue, Ikeja, Lagos',
'latitude' => 6.6018,
'longitude' => 3.3515,
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
import os
import requests
res = requests.post(
"https://api.getreeds.co/shipping/v1/addresses",
headers={
"Authorization": "Bearer " + os.environ["REEDS_ACCESS_TOKEN"],
},
json={
"name": "Ada Okafor",
"phone": "08031234567",
"address": "23 Allen Avenue, Ikeja, Lagos",
"latitude": 6.6018,
"longitude": 3.3515,
},
)
print(res.json())
{
"status": "success",
"data": {
"id": "adr_7Hq2Lk9P",
"livemode": false,
"status": "verified",
"serviceable": true,
"contact": { "name": "Ada Okafor", "phone": "08031234567", "email": null },
"location": { "lga": "Ikeja", "state": "Lagos", "country": "NG" }
}
}
5. Save the drop-off address
- cURL
- Node.js
- PHP
- Python
DROPOFF_ID=$(curl -s https://api.getreeds.co/shipping/v1/addresses \
-H "Authorization: Bearer $REEDS_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Chidi Eze",
"phone": "08098765432",
"address": "14 Admiralty Way, Lekki, Lagos",
"latitude": 6.4474,
"longitude": 3.4723
}' | jq -r .data.id)
echo "$DROPOFF_ID"
const res = await fetch('https://api.getreeds.co/shipping/v1/addresses', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REEDS_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Chidi Eze',
phone: '08098765432',
address: '14 Admiralty Way, Lekki, Lagos',
latitude: 6.4474,
longitude: 3.4723,
}),
});
console.log(await res.json());
<?php
$ch = curl_init('https://api.getreeds.co/shipping/v1/addresses');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REEDS_ACCESS_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Chidi Eze',
'phone' => '08098765432',
'address' => '14 Admiralty Way, Lekki, Lagos',
'latitude' => 6.4474,
'longitude' => 3.4723,
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
import os
import requests
res = requests.post(
"https://api.getreeds.co/shipping/v1/addresses",
headers={
"Authorization": "Bearer " + os.environ["REEDS_ACCESS_TOKEN"],
},
json={
"name": "Chidi Eze",
"phone": "08098765432",
"address": "14 Admiralty Way, Lekki, Lagos",
"latitude": 6.4474,
"longitude": 3.4723,
},
)
print(res.json())
{
"status": "success",
"data": {
"id": "adr_5Vb1Jc8D",
"livemode": false,
"status": "verified",
"serviceable": true,
"contact": { "name": "Chidi Eze", "phone": "08098765432", "email": null },
"location": { "lga": "Lekki", "state": "Lagos", "country": "NG" }
}
}
To reach the other outcomes (needs confirmation, not serviceable, inter-state), see Test addresses.
6. Get a quote
Send both address ids and the package. Use the id values from steps 4 and 5: the ids on this page are examples and won't work on your account. Money is in kobo, so 280000 is ₦2,800. Your price will differ from this example, because it depends on the road distance.
- cURL
- Node.js
- PHP
- Python
BODY=$(jq -n --arg pickup "$PICKUP_ID" --arg dropoff "$DROPOFF_ID" '{
pickup_address_id: $pickup,
dropoff_address_id: $dropoff,
package: {
description: "2 pairs of sneakers",
quantity: 1,
weight_kg: 1.5,
declared_value: 4500000
}
}')
QUOTE_ID=$(curl -s https://api.getreeds.co/shipping/v1/quotes \
-H "Authorization: Bearer $REEDS_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "$BODY" | jq -r .data.id)
echo "$QUOTE_ID"
const res = await fetch('https://api.getreeds.co/shipping/v1/quotes', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REEDS_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
pickup_address_id: 'adr_7Hq2Lk9P',
dropoff_address_id: 'adr_5Vb1Jc8D',
package: {
description: '2 pairs of sneakers',
quantity: 1,
weight_kg: 1.5,
declared_value: 4500000,
},
}),
});
console.log(await res.json());
<?php
$ch = curl_init('https://api.getreeds.co/shipping/v1/quotes');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REEDS_ACCESS_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'pickup_address_id' => 'adr_7Hq2Lk9P',
'dropoff_address_id' => 'adr_5Vb1Jc8D',
'package' => [
'description' => '2 pairs of sneakers',
'quantity' => 1,
'weight_kg' => 1.5,
'declared_value' => 4500000,
],
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
import os
import requests
res = requests.post(
"https://api.getreeds.co/shipping/v1/quotes",
headers={
"Authorization": "Bearer " + os.environ["REEDS_ACCESS_TOKEN"],
},
json={
"pickup_address_id": "adr_7Hq2Lk9P",
"dropoff_address_id": "adr_5Vb1Jc8D",
"package": {
"description": "2 pairs of sneakers",
"quantity": 1,
"weight_kg": 1.5,
"declared_value": 4500000,
},
},
)
print(res.json())
{
"status": "success",
"data": {
"id": "qt_2bX9Wn4K",
"available": true,
"livemode": false,
"expires_at": "2026-10-27T10:20:00+01:00",
"service": "standard",
"route": { "type": "via_hub", "distance_km": 13.5 },
"fee": {
"delivery_fee": 255000,
"vat": 19125,
"service_charge": 5875,
"discount": 0,
"total": 280000,
"currency": "NGN"
},
"schedule": {
"pickup_by": "2026-10-26T19:00:00+01:00",
"deliver_by": "2026-10-27T19:00:00+01:00",
"earliest_delivery": "2026-10-26T19:00:00+01:00"
}
}
}
The quote is valid for 24 hours and books one delivery. If available is false, the response gives a reason instead of a price. See Quotes.
7. Book the delivery
Book with the quote id. Booking needs an Idempotency-Key header, which makes a retry safe: the same key with the same body returns the original delivery and never books twice. Use a key that identifies the attempt, such as order-10045-delivery-1.
- cURL
- Node.js
- PHP
- Python
BODY=$(jq -n --arg quote "$QUOTE_ID" '{
quote_id: $quote,
reference: "ORDER-10045",
dropoff_instructions: "Call on arrival"
}')
curl https://api.getreeds.co/shipping/v1/deliveries \
-H "Authorization: Bearer $REEDS_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-10045-delivery-1" \
-d "$BODY"
const res = await fetch('https://api.getreeds.co/shipping/v1/deliveries', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REEDS_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-10045-delivery-1',
},
body: JSON.stringify({
quote_id: 'qt_2bX9Wn4K',
reference: 'ORDER-10045',
dropoff_instructions: 'Call on arrival',
}),
});
console.log(await res.json());
<?php
$ch = curl_init('https://api.getreeds.co/shipping/v1/deliveries');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REEDS_ACCESS_TOKEN'),
'Content-Type: application/json',
'Idempotency-Key: order-10045-delivery-1',
],
CURLOPT_POSTFIELDS => json_encode([
'quote_id' => 'qt_2bX9Wn4K',
'reference' => 'ORDER-10045',
'dropoff_instructions' => 'Call on arrival',
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
import os
import requests
res = requests.post(
"https://api.getreeds.co/shipping/v1/deliveries",
headers={
"Authorization": "Bearer " + os.environ["REEDS_ACCESS_TOKEN"],
"Idempotency-Key": "order-10045-delivery-1",
},
json={
"quote_id": "qt_2bX9Wn4K",
"reference": "ORDER-10045",
"dropoff_instructions": "Call on arrival",
},
)
print(res.json())
{
"status": "success",
"data": {
"id": "dlv_9Kd3Qx7M",
"livemode": false,
"reference": "ORDER-10045",
"status": "scheduled",
"required_actions": [],
"cancellable": true,
"tracking_code": "HQ3W5F2",
"tracking_url": "https://getreeds.co/packages/HQ3W5F2",
"fee": {
"total": 280000,
"currency": "NGN",
"status": "charged",
"billing": "wallet"
},
"schedule": {
"pickup_by": "2026-10-26T19:00:00+01:00",
"deliver_by": "2026-10-27T19:00:00+01:00"
}
}
}
Your test wallet was debited the quote's fee.total and the delivery is scheduled.
If you run this step again with a new quote, change the key too (for example order-10045-delivery-2). The same key with a different body gets 422 IDEMPOTENCY_KEY_REUSED.
What happens next
- Tracking.
tracking_urlis a public page you can send to your customer. It shows progress without a login. - Webhooks. Set a webhook endpoint for Test mode in Dashboard → Developers. Reeds then sends
delivery.createdanddelivery.booked, then adelivery.status_changedfor each status untildelivered. See Webhooks. - Test deliveries don't move by themselves. Move yours along with the sandbox endpoints. See Sandbox.
- Changed your mind? You can cancel while the delivery's
cancellableistrue, until the rider sets off for the pickup. See Cancellations.
Branch on the HTTP status and error.code, never on the message. Common first-run errors are INSUFFICIENT_BALANCE (fund the test wallet) and QUOTE_EXPIRED (quote again). See Errors.
Next steps
- Shipping overview: drafts, "not ready yet" and platforms
- Verify webhook signatures
- Going live