OpenAPI
OpenAPI 3.0 specification for the eCommerce Exchange API. Import the raw YAML file into Postman, Insomnia, or your codegen tool.
openapi: 3.0.3
info:
title: eCommerce Exchange API
version: 1.0
description: |
HTTPS JSON API for catalog, cart, checkout, shopper accounts, and partner orders.
MojoShop is the reference storefront. Institutions whitelabel on their own hostname; the server resolves the institution from the request host and filters catalog, cart, and checkout to that institution’s approved vendor allowlist. Shared shopper accounts work across institution hosts. Public catalog and order payloads may include shop `{ name, slug, fulfillment_mode }` but never `vendor_id`, email, payout, commission, markup %, or webhook fields. Soft anonymity for browsing: the main marketplace catalog UI may omit shop names even when the JSON includes `shop`. `fulfillment_mode` is `delivery`, `pickup`, or `both` and limits which whole-order `shipping_method` values checkout may use for that shop.
**Pricing:** Catalog, cart, checkout, and shopper order amounts are **shopper sale** prices — seller list × (1 + shop markup%). `min_price` / `max_price` and `sort=price_*` use those sale prices. Seller settlement is list − commission; platform keeps markup + commission. Public shopper JSON never exposes list price, `markup_rate`, or `vendor_amount` (those appear only on seller outbound webhooks).
Required on JSON writes: `Content-Type: application/json` and `Accept: application/json`.
Successful payloads wrap in `data`. Paginated lists also include Laravel `links` and `meta`.
**Auth:** shoppers use `Authorization: Bearer {token}` from register/login (Sanctum token named `storefront`). Checkout and delivery quotes require that Bearer token. Partners use `X-Api-Key`. Carts send `X-Cart-Token` (also returned on cart responses) and stay guest-capable until checkout.
**Delivery pin:** delivery quotes, delivery checkout, and saved shopper addresses require `shipping_address.lat` and `shipping_address.lng`. Resolve them with `GET /api/v1/geocode/search` or `GET /api/v1/geocode/reverse` (same Nominatim proxy the MojoShop map pin uses). Pickup checkout may omit `shipping_address`.
**Fulfillment modes:** each shop exposes `fulfillment_mode` (`delivery`|`pickup`|`both`). Checkout and partner `shipping_method` must be allowed by every shop in the cart; delivery quotes also reject pickup-only shops.
**Idempotency:** checkout and partner orders accept optional `idempotency_key` in the JSON body. Keys are scoped per signed-in shopper or partner API client. Reuse the same key only when retrying the same request.
**Vendor webhooks** are configured in `/vendor` (Webhooks). There is no public API to register URLs. Outbound events `order.paid`, `fulfillment.packed`, `fulfillment.shipped`, and `fulfillment.delivered` are signed with HMAC-SHA256 of `{timestamp}.{raw_body}` using headers `X-MojoShop-Event`, `X-MojoShop-Timestamp`, and `X-MojoShop-Signature`. Item `unit_price` / fulfillment `subtotal` are shopper sale amounts; `vendor_amount` is your settle share (list − commission). See `/apidoc` for the full payload shape including `base_unit_price` and `markup_*`.
Rate limits: 60/min default; tighter on register (10), login/reviews (20), password reset (6), checkout (30), and partners (30). Over the limit returns HTTP 429 with `Retry-After`.
servers:
- url: "https://ecom.mojolabx.com"
description: Current application (APP_URL)
tags:
- name: Catalog
- name: Cart
- name: Checkout
- name: Orders
- name: Account
- name: Partners
paths:
/api/v1/products:
get:
tags:
- Catalog
summary: List published products
operationId: listProducts
parameters:
- name: q
in: query
schema:
type: string
maxLength: 200
- name: category_id
in: query
schema:
type: integer
- name: vendor
in: query
description: Approved shop slug
schema:
type: string
maxLength: 100
- name: vendor_id
in: query
schema:
type: integer
- name: min_price
in: query
description: Minimum shopper sale price (list × (1 + shop markup%))
schema:
type: number
minimum: 0
- name: max_price
in: query
description: Maximum shopper sale price
schema:
type: number
minimum: 0
- name: in_stock
in: query
schema:
type: boolean
- name: on_sale
in: query
schema:
type: boolean
- name: min_rating
in: query
schema:
type: number
minimum: 0
maximum: 5
- name: sort
in: query
description: Default for API clients is newest. Shopper storefront uses random. price_asc / price_desc use shopper sale prices. random uses optional seed for stable pagination.
schema:
type: string
enum:
- random
- newest
- popular
- rating
- price_asc
- price_desc
- name
- name: seed
in: query
description: Integer seed for sort=random so page 2 keeps the same shuffle
schema:
type: integer
minimum: 1
maximum: 2147483646
- name: per_page
in: query
schema:
type: integer
minimum: 1
maximum: 48
default: 24
- name: page
in: query
schema:
type: integer
minimum: 1
responses:
"200":
description: Paginated products
content:
application/json:
schema:
"$ref": "#/components/schemas/ProductList"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
"/api/v1/products/{id}":
get:
tags:
- Catalog
summary: Product detail
operationId: getProduct
parameters:
- "$ref": "#/components/parameters/ProductId"
responses:
"200":
description: Product
content:
application/json:
schema:
type: object
properties:
data:
"$ref": "#/components/schemas/Product"
"404":
description: Not found
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"/api/v1/shops/{slug}":
get:
tags:
- Catalog
summary: Public shop profile
operationId: getShop
parameters:
- name: slug
in: path
required: true
schema:
type: string
responses:
"200":
description: Approved shop
content:
application/json:
schema:
type: object
properties:
data:
"$ref": "#/components/schemas/Shop"
"404":
description: Not found
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"/api/v1/products/{id}/related":
get:
tags:
- Catalog
summary: Related products
operationId: listRelatedProducts
parameters:
- "$ref": "#/components/parameters/ProductId"
responses:
"200":
description: Related product cards
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
"$ref": "#/components/schemas/ProductCard"
"404":
description: Not found
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"/api/v1/products/{id}/reviews":
get:
tags:
- Catalog
summary: List reviews
operationId: listProductReviews
parameters:
- "$ref": "#/components/parameters/ProductId"
- name: page
in: query
schema:
type: integer
minimum: 1
responses:
"200":
description: Paginated reviews
"404":
description: Not found
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
post:
tags:
- Catalog
summary: Leave a review
operationId: createProductReview
security:
- bearerAuth: []
parameters:
- "$ref": "#/components/parameters/ProductId"
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- rating
properties:
rating:
type: integer
minimum: 1
maximum: 5
body:
type: string
maxLength: 1000
nullable: true
responses:
"201":
description: Review created
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"403":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"404":
description: Not found
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
/api/v1/categories:
get:
tags:
- Catalog
summary: Active categories
operationId: listCategories
responses:
"200":
description: Categories
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
"$ref": "#/components/schemas/Category"
/api/v1/flash-banner:
get:
tags:
- Catalog
summary: Promo banner slides
operationId: getFlashBanner
responses:
"200":
description: Banner payload
/api/v1/geocode/search:
get:
tags:
- Checkout
summary: Forward-geocode an address into a drop-off pin
operationId: geocodeSearch
parameters:
- name: q
in: query
required: true
schema:
type: string
minLength: 3
maxLength: 255
- name: country
in: query
schema:
type: string
minLength: 2
maxLength: 2
default: GH
responses:
"200":
description: Pin (`data` may be null)
content:
application/json:
schema:
type: object
properties:
data:
"$ref": "#/components/schemas/GeocodeHit"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
"502":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
/api/v1/geocode/reverse:
get:
tags:
- Checkout
summary: Reverse-geocode a map pin into address fields
operationId: geocodeReverse
parameters:
- name: lat
in: query
required: true
schema:
type: number
format: double
minimum: -90
maximum: 90
- name: lng
in: query
required: true
schema:
type: number
format: double
minimum: -180
maximum: 180
responses:
"200":
description: Address (`data` may be null)
content:
application/json:
schema:
type: object
properties:
data:
"$ref": "#/components/schemas/GeocodeHit"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
"502":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
/api/v1/cart:
get:
tags:
- Cart
summary: Get or create cart
operationId: getCart
security:
- cartToken: []
- bearerAuth: []
- cartToken: []
bearerAuth: []
responses:
"200":
description: Cart
headers:
X-Cart-Token:
schema:
type: string
content:
application/json:
schema:
"$ref": "#/components/schemas/CartResponse"
/api/v1/cart/items:
post:
tags:
- Cart
summary: Add cart item
operationId: addCartItem
security:
- cartToken: []
- bearerAuth: []
- cartToken: []
bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- product_variant_id
- quantity
properties:
product_variant_id:
type: integer
quantity:
type: integer
minimum: 1
responses:
"200":
description: Updated cart
headers:
X-Cart-Token:
schema:
type: string
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
"/api/v1/cart/items/{id}":
patch:
tags:
- Cart
summary: Update cart item
operationId: updateCartItem
security:
- cartToken: []
- bearerAuth: []
- cartToken: []
bearerAuth: []
parameters:
- "$ref": "#/components/parameters/CartItemId"
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- quantity
properties:
quantity:
type: integer
minimum: 1
responses:
"200":
description: Updated cart
"404":
description: Not found
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
delete:
tags:
- Cart
summary: Remove cart item
operationId: removeCartItem
security:
- cartToken: []
- bearerAuth: []
- cartToken: []
bearerAuth: []
parameters:
- "$ref": "#/components/parameters/CartItemId"
responses:
"200":
description: Updated cart
"404":
description: Not found
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
/api/v1/checkout/shipping-quote:
post:
tags:
- Checkout
summary: Preview Swoove delivery fee
operationId: quoteShipping
security:
- bearerAuth: []
cartToken: []
requestBody:
required: true
content:
application/json:
schema:
"$ref": "#/components/schemas/ShippingQuoteRequest"
responses:
"200":
description: Quote (`data.shipping_total`, `data.legs`)
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
"503":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
/api/v1/checkout:
post:
tags:
- Checkout
summary: Checkout cart
operationId: checkout
security:
- bearerAuth: []
cartToken: []
requestBody:
required: true
content:
application/json:
schema:
"$ref": "#/components/schemas/CheckoutRequest"
responses:
"201":
description: Order created
content:
application/json:
schema:
"$ref": "#/components/schemas/CheckoutResponse"
"200":
description: Idempotent replay
content:
application/json:
schema:
"$ref": "#/components/schemas/CheckoutResponse"
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
"502":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"503":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"/api/v1/orders/{number}":
get:
tags:
- Orders
summary: Guest order lookup
operationId: getOrder
parameters:
- "$ref": "#/components/parameters/OrderNumber"
- name: email
in: query
required: true
schema:
type: string
format: email
responses:
"200":
description: Order
content:
application/json:
schema:
type: object
properties:
data:
"$ref": "#/components/schemas/Order"
"404":
description: Not found
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
/api/v1/auth/register:
post:
tags:
- Account
summary: Register shopper
operationId: registerCustomer
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- name
- email
- phone
- password
- password_confirmation
properties:
name:
type: string
email:
type: string
format: email
phone:
type: string
password:
type: string
minLength: 8
password_confirmation:
type: string
responses:
"201":
description: Registered
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
/api/v1/auth/login:
post:
tags:
- Account
summary: Shopper login
operationId: loginCustomer
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- email
- password
properties:
email:
type: string
format: email
password:
type: string
responses:
"200":
description: Token issued
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
/api/v1/auth/verify:
post:
tags:
- Account
summary: Verify SMS OTP (email uses confirmation link)
operationId: verifyCustomer
security:
- bearerAuth: []
requestBody:
content:
application/json:
schema:
type: object
properties:
phone_code:
type: string
description: 6-digit SMS OTP
email_code:
type: string
description: Rejected — use the signed email link instead
responses:
"200":
description: Verified
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"403":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
/api/v1/auth/resend-verification:
post:
tags:
- Account
summary: Resend email confirmation link and/or SMS OTP
operationId: resendCustomerVerification
security:
- bearerAuth: []
requestBody:
content:
application/json:
schema:
type: object
properties:
channel:
type: string
enum:
- email
- phone
- both
responses:
"200":
description: Sent
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"403":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
/api/v1/auth/change-email:
post:
tags:
- Account
summary: Correct mistyped email while phone is verified and email is pending
operationId: changeCustomerEmail
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- email
properties:
email:
type: string
format: email
responses:
"200":
description: Email updated; confirmation link sent when Postmark is connected
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"403":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
/api/v1/auth/change-phone:
post:
tags:
- Account
summary: Correct mistyped phone while phone is still unverified
operationId: changeCustomerPhone
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- phone
properties:
phone:
type: string
example: 0240000000
responses:
"200":
description: Phone updated; new SMS code sent when Hubtel is connected
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"403":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
/api/v1/auth/forgot-password:
post:
tags:
- Account
summary: Request password reset
operationId: forgotPassword
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- email
properties:
email:
type: string
format: email
responses:
"200":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
/api/v1/auth/reset-password:
post:
tags:
- Account
summary: Reset password
operationId: resetPassword
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- email
- token
- password
- password_confirmation
properties:
email:
type: string
format: email
token:
type: string
password:
type: string
minLength: 8
password_confirmation:
type: string
responses:
"200":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
/api/v1/auth/logout:
post:
tags:
- Account
summary: Revoke storefront token
operationId: logoutCustomer
security:
- bearerAuth: []
responses:
"200":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
/api/v1/auth/me:
get:
tags:
- Account
summary: Current shopper
operationId: getMe
security:
- bearerAuth: []
responses:
"200":
description: Profile
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"403":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
patch:
tags:
- Account
summary: Update shopper profile
operationId: updateMe
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- name
- email
properties:
name:
type: string
email:
type: string
format: email
phone:
type: string
nullable: true
shipping_address:
nullable: true
allOf:
- "$ref": "#/components/schemas/ShippingAddress"
responses:
"200":
description: Updated profile
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"403":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
/api/v1/account/orders:
get:
tags:
- Account
summary: Shopper order history
operationId: listAccountOrders
security:
- bearerAuth: []
parameters:
- name: page
in: query
schema:
type: integer
minimum: 1
responses:
"200":
description: Paginated orders (10 per page)
content:
application/json:
schema:
"$ref": "#/components/schemas/OrderList"
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"403":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
/api/v1/account/push-vapid:
get:
tags:
- Account
summary: Web Push VAPID public key
operationId: getPushVapidPublicKey
security:
- bearerAuth: []
responses:
"200":
description: Public key for PushManager.subscribe
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"403":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"503":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
/api/v1/account/push-subscription:
post:
tags:
- Account
summary: Save Web Push subscription
operationId: storePushSubscription
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- endpoint
- keys
properties:
endpoint:
type: string
format: uri
keys:
type: object
required:
- p256dh
- auth
properties:
p256dh:
type: string
auth:
type: string
content_encoding:
type: string
enum:
- aesgcm
- aes128gcm
responses:
"201":
description: Subscription saved
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"403":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
"503":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
delete:
tags:
- Account
summary: Delete Web Push subscription
operationId: deletePushSubscription
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- endpoint
properties:
endpoint:
type: string
format: uri
responses:
"200":
description: Subscription removed
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"403":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
/api/v1/external/orders:
post:
tags:
- Partners
summary: Create externally paid order
operationId: createExternalOrder
security:
- apiKey: []
requestBody:
required: true
content:
application/json:
schema:
"$ref": "#/components/schemas/ExternalOrderRequest"
responses:
"201":
description: Paid order created
content:
application/json:
schema:
"$ref": "#/components/schemas/CheckoutResponse"
"200":
description: Idempotent replay
content:
application/json:
schema:
"$ref": "#/components/schemas/CheckoutResponse"
"401":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"403":
description: Error
content:
application/json:
schema:
"$ref": "#/components/schemas/Message"
"422":
description: Validation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValidationError"
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: Sanctum
description: Shopper token from POST /api/v1/auth/login or /auth/register.
apiKey:
type: apiKey
in: header
name: X-Api-Key
description: Partner API key issued in /admin.
cartToken:
type: apiKey
in: header
name: X-Cart-Token
description: Guest or account cart token. Created on first cart call if omitted.
schemas:
Message:
type: object
properties:
message:
type: string
ValidationError:
type: object
properties:
message:
type: string
errors:
type: object
additionalProperties:
type: array
items:
type: string
Category:
type: object
properties:
id:
type: integer
parent_id:
type: integer
nullable: true
name:
type: string
slug:
type: string
description:
type: string
nullable: true
sort_order:
type: integer
ShopCard:
type: object
nullable: true
properties:
name:
type: string
slug:
type: string
fulfillment_mode:
type: string
enum:
- delivery
- pickup
- both
description: How this shop fulfills orders. Checkout shipping_method must be allowed by every shop in the cart.
Shop:
type: object
properties:
name:
type: string
slug:
type: string
description:
type: string
nullable: true
city:
type: string
nullable: true
fulfillment_mode:
type: string
enum:
- delivery
- pickup
- both
description: How this shop fulfills orders.
ProductCard:
type: object
description: Slim catalog card for list, related, and flash embeds. No description, variants list, or gallery. Includes default_variant_id and has_options for quick-add. Prices are shopper sale amounts. Never vendor_id, unit_cost, list price, markup_rate, email, or payout fields.
properties:
id:
type: integer
category_id:
type: integer
nullable: true
name:
type: string
slug:
type: string
brand:
type: string
nullable: true
status:
type: string
image_path:
type: string
nullable: true
from_price:
type: string
nullable: true
description: Lowest active-variant shopper sale price
compare_at_price:
type: string
nullable: true
available_quantity:
type: integer
default_variant_id:
type: integer
nullable: true
description: Cheapest in-stock active variant for quick-add
has_options:
type: boolean
description: True when more than one active variant — open product detail to choose
rating_avg:
type: number
rating_count:
type: integer
sold_count:
type: integer
discount_percent:
type: integer
nullable: true
shop:
"$ref": "#/components/schemas/ShopCard"
category:
type: object
nullable: true
properties:
id:
type: integer
name:
type: string
slug:
type: string
Product:
type: object
description: Product detail. Includes shop { name, slug, fulfillment_mode }, variants, and images. Prices are shopper sale amounts. Never vendor_id, unit_cost, list price, markup_rate, email, or payout fields.
properties:
id:
type: integer
category_id:
type: integer
nullable: true
name:
type: string
slug:
type: string
description:
type: string
nullable: true
brand:
type: string
nullable: true
manufacturer:
type: string
nullable: true
status:
type: string
image_path:
type: string
nullable: true
from_price:
type: string
nullable: true
description: Lowest active-variant shopper sale price
compare_at_price:
type: string
nullable: true
description: Compare-at with the same shop markup applied
available_quantity:
type: integer
rating_avg:
type: number
rating_count:
type: integer
sold_count:
type: integer
discount_percent:
type: integer
nullable: true
published_at:
type: string
format: date-time
nullable: true
shop:
"$ref": "#/components/schemas/ShopCard"
category:
type: object
nullable: true
properties:
id:
type: integer
name:
type: string
slug:
type: string
parent:
type: object
nullable: true
properties:
id:
type: integer
name:
type: string
slug:
type: string
variants:
type: array
items:
"$ref": "#/components/schemas/ProductVariant"
ProductVariant:
type: object
description: Public variant. price / compare_at_price are shopper sale amounts. Never includes unit_cost or list price.
properties:
id:
type: integer
sku:
type: string
name:
type: string
price:
type: string
description: Shopper sale price (list × (1 + shop markup%))
compare_at_price:
type: string
nullable: true
description: Compare-at with shop markup applied
weight_kg:
type: string
nullable: true
unit_of_measure:
type: string
nullable: true
dimensions:
type: string
nullable: true
options:
type: object
nullable: true
is_active:
type: boolean
available_quantity:
type: integer
ProductList:
type: object
properties:
data:
type: array
items:
"$ref": "#/components/schemas/ProductCard"
links:
type: object
meta:
type: object
CartItem:
type: object
properties:
id:
type: integer
product_variant_id:
type: integer
quantity:
type: integer
unit_price:
type: number
nullable: true
description: Shopper sale unit price
line_total:
type: number
nullable: true
product:
type: object
nullable: true
properties:
id:
type: integer
name:
type: string
slug:
type: string
image_path:
type: string
nullable: true
shop:
"$ref": "#/components/schemas/ShopCard"
variant:
"$ref": "#/components/schemas/ProductVariant"
CartResponse:
type: object
properties:
data:
type: object
properties:
token:
type: string
currency:
type: string
items:
type: array
items:
"$ref": "#/components/schemas/CartItem"
meta:
type: object
properties:
cart_token:
type: string
Shipment:
type: object
nullable: true
description: Shopper Track shipment. Differs from seller outbound webhooks (which include provider / shipped_at / delivered_at).
properties:
status:
type: string
nullable: true
tracking_number:
type: string
nullable: true
carrier:
type: string
nullable: true
handover_code:
type: string
nullable: true
tracking_url:
type: string
nullable: true
OrderItem:
type: object
properties:
id:
type: integer
product_id:
type: integer
nullable: true
product_variant_id:
type: integer
nullable: true
product_name:
type: string
variant_name:
type: string
nullable: true
sku:
type: string
nullable: true
quantity:
type: integer
unit_price:
type: string
description: Shopper sale unit price
line_total:
type: string
shop:
"$ref": "#/components/schemas/ShopCard"
image_path:
type: string
nullable: true
Fulfillment:
type: object
description: Shopper packing list. Includes shop { name, slug, fulfillment_mode }; never vendor_id or secrets.
properties:
id:
type: integer
number:
type: string
status:
type: string
shipping_method:
type: string
enum:
- delivery
- pickup
nullable: true
subtotal:
type: string
shop:
"$ref": "#/components/schemas/ShopCard"
items:
type: array
items:
"$ref": "#/components/schemas/OrderItem"
pickup:
type: object
nullable: true
description: Present when shipping_method is pickup. Includes shop name/slug; never lat/lng.
shipment:
"$ref": "#/components/schemas/Shipment"
PaymentSummary:
type: object
nullable: true
properties:
id:
type: integer
gateway:
type: string
nullable: true
status:
type: string
nullable: true
amount:
type: string
nullable: true
currency:
type: string
nullable: true
external_reference:
type: string
nullable: true
paid_at:
type: string
format: date-time
nullable: true
Order:
type: object
description: Shopper order. Nested shops may include name/slug/fulfillment_mode. Never vendor_id, email, payout, commission, markup %, or vendor_amount.
properties:
id:
type: integer
number:
type: string
status:
type: string
customer_name:
type: string
nullable: true
customer_email:
type: string
nullable: true
customer_phone:
type: string
nullable: true
shipping_address:
type: object
nullable: true
billing_address:
type: object
nullable: true
customer_notes:
type: string
nullable: true
shipping_method:
type: string
enum:
- delivery
- pickup
nullable: true
currency:
type: string
subtotal:
type: string
shipping_total:
type: string
tax_total:
type: string
grand_total:
type: string
paid_at:
type: string
format: date-time
nullable: true
created_at:
type: string
format: date-time
nullable: true
items:
type: array
items:
"$ref": "#/components/schemas/OrderItem"
fulfillments:
type: array
items:
"$ref": "#/components/schemas/Fulfillment"
payment:
"$ref": "#/components/schemas/PaymentSummary"
OrderList:
type: object
properties:
data:
type: array
items:
"$ref": "#/components/schemas/Order"
links:
type: object
meta:
type: object
CheckoutResponse:
type: object
properties:
data:
type: object
properties:
order:
"$ref": "#/components/schemas/Order"
payment:
type: object
properties:
id:
type: integer
status:
type: string
amount:
type: string
currency:
type: string
checkout_url:
type: string
nullable: true
external_reference:
type: string
nullable: true
ShippingAddress:
type: object
required:
- line1
- city
- country
- region
- lat
- lng
description: Required for delivery quotes, delivery checkout, and saved shopper addresses. Pickup checkout may omit shipping_address entirely. lat/lng come from GET /api/v1/geocode/search or /geocode/reverse.
properties:
line1:
type: string
line2:
type: string
nullable: true
city:
type: string
region:
type: string
description: Required for GH. GhanaRegion enum value such as greater_accra.
postal_code:
type: string
nullable: true
description: GhanaPost GPS code when available.
country:
type: string
minLength: 2
maxLength: 2
lat:
type: number
format: double
minimum: -90
maximum: 90
description: Drop-off pin latitude (OSM map).
lng:
type: number
format: double
minimum: -180
maximum: 180
description: Drop-off pin longitude (OSM map).
GeocodeHit:
type: object
nullable: true
properties:
lat:
type: number
format: double
lng:
type: number
format: double
display_name:
type: string
address:
type: object
additionalProperties: true
CheckoutRequest:
type: object
required:
- customer_name
- customer_email
- shipping_method
- accept_terms
properties:
shipping_method:
type: string
enum:
- delivery
- pickup
description: Whole-order method. Must be allowed by every cart shop’s fulfillment_mode (delivery|pickup|both).
customer_name:
type: string
customer_email:
type: string
format: email
customer_phone:
type: string
nullable: true
description: Required for delivery.
shipping_address:
"$ref": "#/components/schemas/ShippingAddress"
description: Required for delivery (including lat/lng). Optional for pickup.
billing_address:
type: object
nullable: true
description: Optional; stored on the order when provided.
customer_notes:
type: string
maxLength: 1000
nullable: true
accept_terms:
type: boolean
idempotency_key:
type: string
maxLength: 255
nullable: true
ShippingQuoteRequest:
type: object
required:
- customer_name
- customer_email
- customer_phone
- shipping_address
properties:
customer_name:
type: string
customer_email:
type: string
format: email
customer_phone:
type: string
shipping_address:
"$ref": "#/components/schemas/ShippingAddress"
ExternalOrderRequest:
type: object
required:
- cart_token
- customer_name
- customer_email
- shipping_method
- external_reference
- accept_terms
properties:
cart_token:
type: string
shipping_method:
type: string
enum:
- delivery
- pickup
description: Whole-order method. Must be allowed by every cart shop’s fulfillment_mode (delivery|pickup|both).
customer_name:
type: string
customer_email:
type: string
format: email
customer_phone:
type: string
nullable: true
shipping_address:
"$ref": "#/components/schemas/ShippingAddress"
billing_address:
type: object
nullable: true
external_reference:
type: string
accept_terms:
type: boolean
idempotency_key:
type: string
maxLength: 255
nullable: true
parameters:
ProductId:
name: id
in: path
required: true
schema:
type: integer
CartItemId:
name: id
in: path
required: true
schema:
type: integer
OrderNumber:
name: number
in: path
required: true
schema:
type: string