This documentation covers all REST API endpoints for the NearbyMe food delivery platform. Use the navigation on the left to browse by category or scroll through all endpoints.
Test Kitchen Lagos
a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5dCustomer Flow: Browse, Order, Track
Create a customer account
Navigate to /auth/sign-up and select "Customer" role
View available restaurants
GET /api/restaurantscurl -X GET "/api/restaurants?latitude=6.5244&longitude=3.3792"
Get menu items for a restaurant
GET /api/restaurants/{id}/menu-itemscurl -X GET "/api/restaurants/a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d/menu-items"
Add items to shopping cart
POST /api/cartcurl -X POST "/api/cart" -H "Authorization: Bearer TOKEN" -d '{"menu_item_id": "aaaa1111-1111-1111-1111-111111111111", "quantity": 2}'Apply a discount code
POST /api/cart/promocurl -X POST "/api/cart/promo" -H "Authorization: Bearer TOKEN" -d '{"promo_code": "WELCOME20"}'Create order from cart
POST /api/orderscurl -X POST "/api/orders" -H "Authorization: Bearer TOKEN" -d '{"delivery_address": "45 Admiralty Way, Lagos", "delivery_latitude": 6.4355, "delivery_longitude": 3.4500}'Monitor order status
GET /api/orders/{id}curl -X GET "/api/orders/ORDER_ID" -H "Authorization: Bearer TOKEN"
| From Status | To Status | Allowed Roles |
|---|---|---|
| placed | accepted | vendor |
| placed | cancelled | customervendoradmin |
| accepted | preparing | vendor |
| accepted | cancelled | vendoradmin |
| preparing | ready | vendor |
| ready | picked_up | rider |
| picked_up | delivered | rider |
{
"success": false,
"error": "Error message",
"code": "ERROR_CODE"
}Overview
The payment gateway is Paystack (Nigerian). There are no webhooks involved in the mobile flow — verification is entirely client-driven. The mobile app calls the verify endpoint after the user completes payment inside the Paystack UI.
access_code + referenceCreate the Order
Must happen before payment. Call this when the customer taps "Proceed to Checkout".
POST /api/orders
Authorization: Bearer <customer_token>
{
"delivery_address": "12 Olu Adesanya Street, Lagos",
"delivery_latitude": 6.5244,
"delivery_longitude": 3.3792,
"special_instructions": "Leave at gate" // optional
}
// Response
{
"success": true,
"data": {
"id": "order-uuid-here", // ← SAVE THIS
"status": "placed",
"total_amount": 24000, // ← SAVE THIS
"subtotal": 22000,
"delivery_fee": 1500,
"service_fee": 500
}
}Initialize Payment
Calls Paystack internally and returns everything the mobile app needs to open the payment UI.
payment_method accepted values:
card— Paystack card payment onlybank_transfer— Bank transfer + bank channelsussd— USSD onlycash_on_delivery— No Paystack — skips verification entirelyPOST /api/payments
Authorization: Bearer <customer_token>
{
"order_id": "order-uuid-here",
"payment_method": "card",
"callback_url": "nearbyme://payment-callback" // your app deep link
}
// Response
{
"success": true,
"data": {
"id": "payment-uuid-here", // ← SAVE → payment_id for verify
"stripe_payment_intent_id": "CHW-1716-abc123", // ← SAVE → reference for verify
"paystack_access_code": "0peioxfhpn", // ← pass to Paystack SDK
"paystack_authorization_url": "https://checkout.paystack.com/0peioxfhpn",
"amount": 24000,
"status": "pending"
}
}stripe_payment_intent_id stores the Paystack reference string — not a Stripe ID. This is a naming artifact. Treat this value as your Paystack reference.Open Paystack Checkout
Open the Paystack UI using the paystack_access_code from Step 2. Use an in-app WebView or the platform SDK — do not open the device browser.
Flutter (flutter_paystack)
final charge = Charge()
..accessCode = payment.paystackAccessCode
..reference = payment.reference;
final response = await _paystack.checkout(
context,
method: CheckoutMethod.selectable,
charge: charge,
);
if (response.status == true) {
// call verifyPayment()
}React Native (WebView)
<WebView
source={{ uri: authorizationUrl }}
onNavigationStateChange={(nav) => {
if (nav.url.startsWith(
'nearbyme://payment-callback'
)) {
closeWebView()
verifyPayment()
}
}}
/>Verify Payment
Call this immediately after the Paystack SDK fires its success callback. Do not wait for user action. Block the UI during this call.
POST /api/payments/verify
Authorization: Bearer <customer_token>
{
"reference": "CHW-1716-abc123", // stripe_payment_intent_id from Step 2
"payment_id": "payment-uuid-here" // id from Step 2
}
// Success Response
{
"success": true,
"data": {
"id": "payment-uuid-here",
"status": "completed",
"order_id": "order-uuid-here",
"amount": 24000
}
}
// Failure Response (abandoned / declined)
{
"success": false,
"error": "Payment abandoned. Please try again."
}✅ What the API does internally:
1. Fetches payment record from DB
2. Fetches order separately (avoids RLS join issue)
3. Calls Paystack live API to verify the reference
4. Confirms amount paid ≥ order total (in kobo)
5. Updates payment status → completed using service-role client (bypasses RLS)
6. Advances order from placed → accepted
7. Creates a payment_success notification for the customer
Cash on Delivery
No Paystack involved. Skip Steps 3 and 4 entirely.
POST /api/payments
{
"order_id": "order-uuid-here",
"payment_method": "cash_on_delivery"
}
// Returns payment with status: "pending"
// Order moves forward when rider marks it delivered
// No verification step neededState the App Must Track
{
orderId: string, // from POST /api/orders
paymentId: string, // from POST /api/payments → data.id
reference: string, // from POST /api/payments → data.stripe_payment_intent_id
accessCode: string, // pass to Paystack SDK
status: 'idle' | 'creating_order' | 'initializing'
| 'awaiting_paystack' | 'verifying' | 'success' | 'failed'
}
// Persist paymentId + reference to local storage immediately after Step 2
// so an interrupted payment can be resumed on app restartLoading & Error States
| State | What to show |
|---|---|
| POST /api/payments in progress | Spinner: "Setting up payment…" |
| WebView / Paystack SDK open | Paystack handles its own loader |
| POST /api/payments/verify in progress | Full-screen overlay: "Confirming payment… Do not close the app" |
| verify → 400 | "Payment unsuccessful. Tap to try again." — safe to retry from Step 2 |
| verify → 500 | "Something went wrong. If you were charged, contact support." |
| verify → 200 | Navigate to Payment Success screen |
Edge Cases
App closed during payment
On restart, check SharedPreferences / AsyncStorage for a saved paymentId + reference. If found, prompt the user to resume and call verify. The endpoint is idempotent — safe to call again.
Verify called twice (double-tap / retry)
The verify endpoint is fully idempotent. If payment is already completed it returns the existing record and safely re-advances the order. No duplicate charges possible.
Amount mismatch
If Paystack reports a lower amount than the order total, verify returns 400 "Payment amount mismatch". Show an error and a support contact message — do not auto-retry.
Stale pending payment for same order
The initialize endpoint auto-deletes any existing pending payment for the order and creates a fresh Paystack session. Retrying Step 2 for the same order is always safe.
Network drop during verify
Retry up to 3× with exponential backoff. Idempotency makes this safe. After 3 failures show the support message.
Common Mistakes to Avoid
| ❌ Mistake | ✅ Fix |
|---|---|
| Using authorization_url as the reference | reference = stripe_payment_intent_id from the init response |
| Opening authorization_url in the device browser | Use a WebView or Paystack SDK — you must intercept the redirect |
| Calling verify before Paystack closes | Only call verify inside the SDK onSuccess callback or after redirect detected |
| Navigating away during verify | Block the UI — partial verify causes stuck orders |
| Showing success based on WebView close alone | Always confirm with verify endpoint — closed ≠ success |
| Not persisting paymentId + reference | Save to local storage immediately after Step 2 in case app is killed |
No Webhooks in the Mobile Flow
A webhook handler type is defined in the codebase but no webhook endpoint exists yet. The entire payment flow is client-driven — the mobile app is responsible for calling verify. Do not wait for a server push.
Restaurants
Manage restaurants and their settings
/api/restaurants/api/restaurants/api/restaurants/{id}/api/restaurants/{id}/api/restaurants/{id}/statusCart
Shopping cart management
/api/cart/api/cart/api/cart/api/cart/items/{id}/api/cart/items/{id}/api/cart/promo/api/cart/promoOrders
Order management and tracking
/api/orders/api/orders/api/orders/{id}/api/orders/{id}/status/api/orders/{id}/assign-riderRiders
Rider management and tracking
/api/riders/api/riders/api/riders/me/api/riders/me/api/riders/me/status/api/riders/me/location/api/riders/me/earningsPayments
Payment processing and earnings
/api/payments/api/payments/{id}/confirm/api/vendor/earningsCustomer Features
Endpoints for the customer mobile app — profile, addresses, search, favorites, notifications, reviews, and order utilities.
/api/users/me/api/users/me/api/users/me/notification-preferences/api/users/me/notification-preferences/api/addresses/api/addresses/api/addresses/{id}/api/categories/api/search/api/search/suggestions/api/search/history/api/search/history/api/favorites/api/favorites/api/favorites/{id}/api/notifications/api/notifications/{id}/read/api/restaurants/{id}/reviews/api/restaurants/{id}/reviews/api/riders/{id}/reviews/api/orders/{id}/track/api/orders/{id}/confirm/api/orders/{id}/reorder/api/orders/{id}/receipt/api/beneficiaries/api/beneficiaries/api/beneficiaries/{id}/api/beneficiaries/{id}/api/restaurants/{id}/banners/api/banners/api/featured-dishes/api/explore/api/recommendations/api/home-sectionsAdmin
Admin-only endpoints. All require an admin JWT. Login: admin@nearbyme.io / Admin@NearbyMe2024!
/api/admin/dashboard/api/admin/analytics/api/admin/users/api/admin/users/{id}/api/admin/users/{id}/api/admin/users/{id}/api/admin/vendors/api/admin/vendors/{id}/api/admin/vendors/{id}/api/admin/riders/api/admin/riders/{id}/api/admin/orders/api/admin/orders/{id}/api/admin/orders/{id}/api/admin/earnings/api/admin/payouts/api/admin/payouts/api/admin/payouts/{id}/api/admin/disputes/api/admin/disputes/{id}/api/admin/promo-codes/api/admin/promo-codes/api/admin/promo-codes/{id}/api/admin/promo-codes/{id}/api/admin/audit-logs/api/admin/banners/api/admin/banners/api/admin/banners/{id}/api/admin/banners/{id}/api/admin/banners/{id}/api/admin/home-sections/api/admin/home-sections/api/admin/home-sections/{id}/api/admin/home-sections/{id}/api/admin/home-sections/{id}/items/api/admin/home-sections/{id}/items/{itemId}/api/admin/home-sections/{id}/items/{itemId}