401 Missing or invalid Bearer token, or missing/invalid X-Api-Key (partners may see a more specific message).
{ "message": "Unauthenticated." }
Versioned catalog, cart, checkout, customer account, and partner endpoints for the Exchange.
MojoShop is the reference storefront; institutions can whitelabel against the same
/api/v1 contract. Public catalog payloads never include vendor identity.
Send JSON. Use Accept: application/json and
Content-Type: application/json on POST, PATCH, and DELETE.
Successful payloads wrap in data. Lists that paginate also include Laravel
links and meta (page, per_page, total).
Customer auth: Authorization: Bearer {token} from login or register
(Sanctum token named storefront). Partners:
X-Api-Key. Carts: send X-Cart-Token and read it
back on every cart response.
Checkout and partner orders accept optional idempotency_key (max 255).
Keys are scoped per shopper, guest email, or partner API client so one client cannot replay another’s key.
Reuse the same key to get the original order instead of creating another. Empty carts and stock problems
return 422 with an errors.cart array.
Rate limits: 60 requests per minute per IP or customer by default.
Register 10/min, login and reviews 20/min, password reset and OTP resend 6/min, checkout 30/min,
guest order lookup 60/min, partner orders 30/min. Over the limit returns 429 with
Retry-After.
401 Missing or invalid Bearer token, or missing/invalid X-Api-Key (partners may see a more specific message).
{ "message": "Unauthenticated." }
403 Authenticated but not allowed (non-customer token, or partner key without external payment).
{ "message": "This endpoint is for customer accounts." }
404 Unknown product, cart item, or order number + email pair.
{ "message": "Not found." }
422 Validation failed, empty cart, unavailable variant, not enough stock, or delivery quote failed.
{ "message": "…", "errors": { "field": ["…"] } }
429 Rate limit exceeded. Retry-After is set. Default 60/min; register, login, password reset, checkout, and partners are tighter.
{ "message": "Too Many Attempts." }
502 MojoPay rejected the checkout session.
{ "message": "Could not create a MojoPay checkout session: …" }
503 MojoPay or Swoove Delivery is not configured in /admin.
{ "message": "Delivery is temporarily unavailable. …" }
GET
/api/v1/products
Public
List published products. Query: q (max 200), category_id, min_price, max_price, in_stock, on_sale, min_rating (0–5), sort (newest|popular|rating|price_asc|price_desc|name), per_page (1–48, default 24), page. Invalid filters return 422. Includes rating_avg, rating_count, sold_count, discount_percent, and pagination meta/links. Soft anonymity — no vendor identity.
GET
/api/v1/products/{id}
Public
Product detail with variants, images, related, and recent anonymous reviews. Unpublished IDs return 404.
GET
/api/v1/products/{id}/related
Public
Related products in the same category.
GET
/api/v1/products/{id}/reviews
Public
Paginated approved reviews (20 per page). Soft anonymity — labelled Verified buyer, no names.
POST
/api/v1/products/{id}/reviews
Bearer
Leave a 1–5 star review after buying the product. Body: rating, optional body. Returns 201 with the review and updated product.
GET
/api/v1/categories
Public
Active categories (id, parent_id, name, slug, description, sort_order).
GET
/api/v1/flash-banner
Public
Permanent header promo slides: staff promo lines, featured products, and sale items. Soft anonymity — no vendor identity.
GET
/api/v1/cart
Optional Bearer
Get or create cart. Send and read header X-Cart-Token (also in data.token and meta.cart_token). Merges into the account cart when signed in.
POST
/api/v1/cart/items
Optional Bearer
Add item. Body: product_variant_id, quantity. Variant must be active on a published product. Response includes X-Cart-Token.
PATCH
/api/v1/cart/items/{id}
Optional Bearer
Update item quantity. Unknown items for this cart return 404.
DELETE
/api/v1/cart/items/{id}
Optional Bearer
Remove cart item. Unknown items for this cart return 404.
POST
/api/v1/checkout/shipping-quote
Optional Bearer
Preview Swoove delivery fee for the current cart. Requires customer_name, customer_email, customer_phone, and shipping_address. Returns data.shipping_total, currency, and anonymized legs (no seller names). 503 when Delivery is not configured.
POST
/api/v1/checkout
Optional Bearer
Create order from cart and initiate Mojo Omni payment. Body requires shipping_method (delivery|pickup), accept_terms, and optional idempotency_key. Delivery re-quotes Swoove server-side and adds shipping_total to grand_total (phone + Ghana shipping_address required). Pickup has zero shipping; anonymized pickup addresses appear on order lookup after pay. Returns 201 with data.order + data.payment.checkout_url.
GET
/api/v1/orders/{number}
Public
Guest order lookup. Query: email (must match checkout email, case-insensitive). Includes shipping_method, shipping_total, fulfillment status, anonymized pickup blocks when shipping_method is pickup, and tracking when a Swoove shipment exists. No seller identity.
POST
/api/v1/auth/register
Public
Create a customer account (phone required). Sends email + SMS OTP when Postmark/Hubtel are connected. Returns 201 with token + user (needs_verification when codes are pending).
POST
/api/v1/auth/login
Public
Customer login. Returns Sanctum token. Resends OTPs when the account still needs verification. Vendor/admin accounts must use their portals (422).
POST
/api/v1/auth/verify
Bearer
Confirm email_code and/or phone_code (6 digits). Customer tokens only.
POST
/api/v1/auth/resend-verification
Bearer
Resend OTP. Body: channel = email | phone | both.
POST
/api/v1/auth/forgot-password
Public
Email a shop password reset link when Postmark is connected. Always returns the same message.
POST
/api/v1/auth/reset-password
Public
Set a new shopper password with the emailed token. Body: email, token, password, password_confirmation.
POST
/api/v1/auth/logout
Bearer
Revoke the current storefront token.
GET
/api/v1/auth/me
Bearer
Current customer profile. Non-customer tokens return 403.
PATCH
/api/v1/auth/me
Bearer
Update name, email, phone, and optional shipping_address (Ghana region required when country is GH). Clear by omitting address lines.
GET
/api/v1/account/orders
Bearer
Paginated order history for the signed-in customer (20 per page).
POST
/api/v1/external/orders
X-Api-Key
Create a paid order using a third-party payment reference. Requires shipping_method (delivery|pickup), accept_terms, and Ghana shipping_address.region when delivering. Optional idempotency_key is scoped per API client. Response shape matches checkout: data.order + data.payment.
Vendors enter an HTTPS callback URL in the vendor panel at /vendor (Webhooks).
There is no API endpoint to register or change webhook URLs. MojoShop POSTs JSON to your URL when events happen.
Respond with 2xx; failed deliveries retry a few times.
Headers: X-MojoShop-Event,
X-MojoShop-Timestamp,
X-MojoShop-Signature
(HMAC-SHA256 of {timestamp}.{raw_body} using your signing secret).
POST
order.paid
Your URL
Customer payment confirmed. JSON includes the order (your customer + shipping) and only your fulfillment and line items.
{
"event": "order.paid",
"occurred_at": "2026-08-20T19:00:00+00:00",
"data": {
"order": {
"number": "MS-ABCDEF1234",
"status": "paid",
"customer_name": "Ama Buyer",
"customer_email": "ama@example.com",
"customer_phone": "0240000000",
"shipping_address": {
"line1": "12 Independence Ave",
"city": "Accra",
"region": "greater_accra",
"country": "GH"
},
"currency": "GHS",
"paid_at": "2026-08-20T19:00:00+00:00"
},
"fulfillment": {
"number": "FF-ABCDEF1234",
"status": "pending",
"subtotal": 80.00,
"vendor_amount": 72.00,
"items": [
{
"product_name": "Shea Butter",
"variant_name": "Jar",
"sku": "SHEA-JAR",
"quantity": 2,
"unit_price": 40.00,
"line_total": 80.00
}
],
"shipment": null
}
}
}
POST
fulfillment.packed
Your URL
Your packing list was marked packed (or a Swoove delivery was booked). Full order/fulfillment shape (same customer + shipping fields as order.paid); shipment may include tracking.
{
"event": "fulfillment.packed",
"occurred_at": "2026-08-20T19:10:00+00:00",
"data": {
"order": {
"number": "MS-ABCDEF1234",
"status": "partially_fulfilled",
"customer_name": "Ama Buyer",
"customer_email": "ama@example.com",
"customer_phone": "0240000000",
"shipping_address": {
"line1": "12 Independence Ave",
"city": "Accra",
"region": "greater_accra",
"country": "GH"
},
"currency": "GHS",
"paid_at": "2026-08-20T19:00:00+00:00"
},
"fulfillment": {
"number": "FF-ABCDEF1234",
"status": "packed",
"subtotal": 80.00,
"vendor_amount": 72.00,
"items": [
{
"product_name": "Shea Butter",
"variant_name": "Jar",
"sku": "SHEA-JAR",
"quantity": 2,
"unit_price": 40.00,
"line_total": 80.00
}
],
"shipment": {
"provider": "swoove",
"status": "label_created",
"tracking_number": "SD-123456",
"carrier": "swoove",
"shipped_at": null,
"delivered_at": null
}
}
}
}
POST
fulfillment.shipped
Your URL
Parcel is in transit. Same full order/fulfillment payload shape as order.paid; shipment status and tracking are populated when available.
{
"event": "fulfillment.shipped",
"occurred_at": "2026-08-20T19:30:00+00:00",
"data": {
"order": {
"number": "MS-ABCDEF1234",
"status": "partially_fulfilled",
"customer_name": "Ama Buyer",
"customer_email": "ama@example.com",
"customer_phone": "0240000000",
"shipping_address": {
"line1": "12 Independence Ave",
"city": "Accra",
"region": "greater_accra",
"country": "GH"
},
"currency": "GHS",
"paid_at": "2026-08-20T19:00:00+00:00"
},
"fulfillment": {
"number": "FF-ABCDEF1234",
"status": "shipped",
"subtotal": 80.00,
"vendor_amount": 72.00,
"items": [
{
"product_name": "Shea Butter",
"variant_name": "Jar",
"sku": "SHEA-JAR",
"quantity": 2,
"unit_price": 40.00,
"line_total": 80.00
}
],
"shipment": {
"provider": "swoove",
"status": "in_transit",
"tracking_number": "SD-123456",
"carrier": "swoove",
"shipped_at": "2026-08-20T19:30:00+00:00",
"delivered_at": null
}
}
}
}
POST
fulfillment.delivered
Your URL
Parcel marked delivered. Same full order/fulfillment payload shape as order.paid.
{
"event": "fulfillment.delivered",
"occurred_at": "2026-08-20T20:00:00+00:00",
"data": {
"order": {
"number": "MS-ABCDEF1234",
"status": "fulfilled",
"customer_name": "Ama Buyer",
"customer_email": "ama@example.com",
"customer_phone": "0240000000",
"shipping_address": {
"line1": "12 Independence Ave",
"city": "Accra",
"region": "greater_accra",
"country": "GH"
},
"currency": "GHS",
"paid_at": "2026-08-20T19:00:00+00:00"
},
"fulfillment": {
"number": "FF-ABCDEF1234",
"status": "delivered",
"subtotal": 80.00,
"vendor_amount": 72.00,
"items": [
{
"product_name": "Shea Butter",
"variant_name": "Jar",
"sku": "SHEA-JAR",
"quantity": 2,
"unit_price": 40.00,
"line_total": 80.00
}
],
"shipment": {
"provider": "swoove",
"status": "delivered",
"tracking_number": "SD-123456",
"carrier": "swoove",
"shipped_at": "2026-08-20T19:30:00+00:00",
"delivered_at": "2026-08-20T20:00:00+00:00"
}
}
}
}