Odeen
API docs

The Odeen API

Every module on an Odeen page — products, checkout, payments, bookings, quotes — is callable over HTTPS and embeddable as a widget. Base URL for everything below:

odeen
https://api.getodeen.com
Start

Authentication

Two key types. Both travel as a Bearer token — never in the URL, except order-status links which also accept ?key=.

odn_pk_…

Publishable

Safe in browsers, widgets and mobile apps. Can list products and services, and create orders, bookings and quotes. Can never read other businesses or secrets.

odn_sk_…

Secret

Server-to-server only. Same scopes, higher trust. Store in environment variables — never commit it, never paste it into a webpage.

auth
curl "https://api.getodeen.com/v1/businesses/shopivo/products" \
  -H "Authorization: Bearer odn_pk_YOUR_KEY"

Rate limits

120 requests/minute per key. Over it → 429. Slow down and retry.

Errors

401 bad/revoked key · 403 missing scope or wrong account · 404 unknown handle/id · 400/409 validation (out of stock → 409).

Scopes

Least privilege

Ask for what the integration needs, nothing more. Scopes are set when the key is created in your dashboard.

  • products:readRead products & menu. List items, prices, stock and images.
  • orders:writeCreate orders. Place orders / start checkouts from your site.
  • orders:readRead orders. Look up order status and history.
  • bookings:readRead services & slots. List services and availability.
  • bookings:writeCreate bookings. Book appointments from your site.
  • quotes:writeRequest quotes. Send quote / enquiry requests.
  • reviews:writeSubmit reviews. Post customer star ratings from your site.
  • payments:writeInit payments. Initialise Paystack / MoMo checkout.
Catalog

Products & services

Live items with prices, stock and photos. Businesses are addressed by handle — the part after getodeen.com/.

GET/v1/businesses/:handle/products
products:read

List items. Filter with ?module_key=products|menu|services|catalogue|portfolio|events|courses, ?category=<id> (a parent includes its subcategories) and ?q= (searches names and descriptions). Cap with ?limit= (max 100).

:handlepathBusiness handle, e.g. shopivo
module_keyquery · optionalItem module to list
categoryquery · optionalCategory id — parent includes subcategories
qquery · optionalSearch names and descriptions
limitquery · optionalMax items, default 100
request
curl "https://api.getodeen.com/v1/businesses/shopivo/products?module_key=menu" \
  -H "Authorization: Bearer odn_pk_…"
response
{
  "success": true,
  "products": [{
    "id": "64f…", "name": "Jollof Rice", "description": "Party style",
    "price": "45", "currency": "GHS", "image": "https://…",
    "images": ["https://…", "https://…/2.jpg"],
    "track_stock": true, "stock_quantity": 20, "in_stock": true,
    "module_key": "menu",
    "category_id": "64e…",
    "category": { "id": "64e…", "name": "Mains", "parent_id": null },
    "list_price": "45", "discount_type": "percent", "discount_value": "10",
    "sale_price": "40.5", "has_discount": true,
    "badge": "bestseller", "rating_average": 4.5, "rating_count": 12,
    "details": { "features": ["Serves 2", "Extra spicy on request"] },
    "variations": [{ "name": "Colour",
      "options": ["Red", { "label": "Green", "swatch": "#1B7A46" }] }]
  }]
}
GET/v1/businesses/:handle/products/:id
products:read

One item with full details, stock state and variations.

:handlepathBusiness handle
:idpathProduct id
GET/v1/businesses/:handle/services
bookings:read

Bookable services with duration, price and photo. Supports the same ?category= and ?q= filters as products.

:handlepathBusiness handle
categoryquery · optionalCategory id — parent includes subcategories
qquery · optionalSearch names and descriptions
GET/v1/businesses/:handle/slots?service_id=:id&date=:yyyy-mm-dd
bookings:read

Free start times for a service on a day, derived from opening hours, duration and existing bookings. Past and taken slots are excluded.

service_idqueryService id
datequeryDay as YYYY-MM-DD (not in the past)
response
{ "success": true, "date": "2026-09-20",
  "slots": ["2026-09-20T09:00:00", "2026-09-20T09:30:00"] }
GET/v1/businesses/:handle/products/:id/reviews
products:read

Review list with the live average and count.

:handlepathBusiness handle
:idpathProduct id
response
{ "success": true, "rating_average": 4.5, "rating_count": 12,
  "reviews": [{ "id": "653…", "customer_name": "Efua",
    "rating": 5, "title": "Worth it", "comment": "Fast delivery" }] }
POST/v1/businesses/:handle/products/:id/reviews
reviews:write

Post a 1–5 star review. Visible immediately; the product average recomputes on write.

customer_namebody · requiredReviewer name
ratingbody · required1 to 5
title / commentbody · optionalOptional words
shapes
// variations: strings, or colour objects with a hex swatch
"variations": [{ "name": "Colour",
  "options": ["Red", { "label": "Green", "swatch": "#1B7A46" }] }]
// customers pick by label: {"name": "Colour", "option": "Green"}

// details: free-form object — feature bullets live here
"details": { "features": ["Serves 2", "Extra spicy on request"] }
Organize

Categories

Owner-named shelves, per business — Food, Food / Jollof. Assignment is optional and products carry category: {id, name, parent_id} in every response. These four run on the owner's login JWT (dashboard session), not on API keys.

GET/api/products/business/:business_id/categories

Flat list of categories and subcategories with per-shelf item counts. Owner JWT.

:business_idpathBusiness id
response
{ "success": true, "categories": [
  { "id": "64f…", "name": "Food", "parent_id": null, "product_count": 12 },
  { "id": "650…", "name": "Jollof", "parent_id": "64f…", "product_count": 3 }
] }
POST/api/products/business/:business_id/categories

Create a category, or a subcategory with parent_id. A subcategory cannot have children of its own. Owner JWT.

namebody · requiredShelf name, max 60 chars
parent_idbody · optionalParent category id for a subcategory
PUT/api/products/business/:business_id/categories/:id

Rename a category. Owner JWT.

namebody · requiredNew name
DELETE/api/products/business/:business_id/categories/:id

Delete a shelf without losing data: items become uncategorised, subcategories step up to top level. Owner JWT.

:idpathCategory id
Sell

Orders & payments

Checkout that writes straight into the Odeen orders inbox. Stock is checked and reserved with the same rules as the page checkout.

POST/v1/businesses/:handle/orders
orders:write

Create an order. Variations are validated against what the owner defined; tracked stock returns 409 when short. Lines charge the sale price. Totals are computed server-side: total = subtotal + shipping_fee + taxes — the client quotes, the server decides. Delivery methods require an address (structured parts win, delivery_address is the fallback).

customer_namebody · requiredCustomer full name
customer_phonebody · requiredPhone or WhatsApp
customer_emailbody · optionalReceipt address
itemsbody · required[{"product_id, qty (or quantity), variations?}]
shipping_methodbody · optional'pickup' (default), 'standard' or 'express'
delivery_street / city / zipbody · optionalStructured address; required for delivery
delivery_addressbody · optionalFree-form fallback line
customer_note / notesbody · optionalNote for the business
payment_methodbody · optional'online' initialises a card charge in the same call
request
curl -X POST "https://api.getodeen.com/v1/businesses/shopivo/orders" \
  -H "Authorization: Bearer odn_pk_…" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_name": "Ama Serwaa",
    "customer_phone": "+233240000000",
    "customer_email": "[email protected]",
    "shipping_method": "standard",
    "delivery_street": "12 Osu Badu St",
    "delivery_city": "Accra",
    "delivery_zip": "00233",
    "items": [{ "product_id": "64f…", "qty": 2 }]
  }'
response
{
  "success": true,
  "order": {
    "id": "650…", "business_id": "64f…",
    "customer_name": "Ama Serwaa", "customer_phone": "+233240000000",
    "customer_email": "[email protected]",
    "price": 81, "currency": "GHS",
    "shipping_method": "standard", "shipping_fee": 30,
    "taxes": 12.15, "total": 123.15,
    "delivery_address": "12 Osu Badu St, Accra, 00233",
    "status": "pending", "payment_method": null, "payment_status": "unpaid",
    "items": [{ "product_id": "64f…", "name": "Jollof Rice",
                "quantity": 2, "price_per_unit": 40.5,
                "list_price_per_unit": 45, "currency": "GHS" }]
  }
}
GET/v1/orders/:id
orders:read

Order status and lines. Also works as /v1/orders/:id?key=odn_pk_… for status links.

:idpathOrder id
POST/v1/orders/:id/pay
orders:writepayments:write

Charge an unpaid order. Card returns authorization_url (redirect, verification lands back on the Odeen page). MoMo charges the wallet directly: the customer approves on their phone, then poll payment status until it flips — never mark paid off the pay response.

channelbody'card' or 'momo'
phonebody · momo requiredMoMo wallet number
providerbody · momo required'mtn', 'vodafone' or 'airteltigo'
response
{ "success": true, "authorization_url": "https://checkout.paystack.com/…",
  "reference": "PSK-…", "order": { "id": "650…", "payment_status": "unpaid" } }
GET/v1/orders/:id/payment
orders:read

Payment state without the full order. For MoMo, each poll re-verifies with Paystack and settles the order the moment the charge succeeds — poll until paid or failed.

:idpathOrder id
response
{ "success": true, "payment": { "order_id": "650…",
  "payment_status": "unpaid", "payment_method": "online",
  "amount": 123.15, "currency": "GHS", "reference": "PSK-…" } }
PUT/api/business/:business_id

Owner checkout settings (login JWT, not API key). Everything defaults off/zero so totals never change until set: standard_fee and express_fee per order, free_shipping_threshold making standard free above it (express always charged), tax_rate as a percent of the goods subtotal. Quoted back publicly under business.shipping for any checkout to read.

shipping_enabledbody · optionaltrue to charge delivery
shipping_standard_feebody · optionale.g. 30
shipping_express_feebody · optionale.g. 60
free_shipping_thresholdbody · optionale.g. 1500, 0 = none
tax_ratebody · optionale.g. 15 for VAT, 0–100
request
curl -X PUT "https://api.getodeen.com/api/business/64f…" \
  -H "Authorization: Bearer YOUR_LOGIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{"shipping_enabled": true, "shipping_standard_fee": 30,
       "shipping_express_fee": 60, "free_shipping_threshold": 1500,
       "tax_rate": 15}'
Book

Bookings & quotes

Appointments into the Odeen calendar, quote requests into the inbox.

POST/v1/businesses/:handle/bookings
bookings:write

Book an appointment. The end time is derived from the service duration; past start times are refused.

service_idbody · requiredService id
start_timebody · requiredISO 8601 datetime, in the future
customer_namebody · requiredCustomer full name
customer_phonebody · requiredPhone or WhatsApp
customer_note / notesbody · optionalAnything the business should know
request
curl -X POST "https://api.getodeen.com/v1/businesses/glow-studio/bookings" \
  -H "Authorization: Bearer odn_pk_…" \
  -H "Content-Type: application/json" \
  -d '{
    "service_id": "64f…", "start_time": "2026-09-20T10:00:00Z",
    "customer_name": "Kwame Mensah", "customer_phone": "+233240000000"
  }'
response
{ "success": true, "booking": { "id": "651…", "service_id": "64f…",
  "customer_name": "Kwame Mensah", "start_time": "2026-09-20T10:00:00",
  "end_time": "2026-09-20T10:30:00", "status": "pending" } }
POST/v1/businesses/:handle/quotes
quotes:write

Request a quote. Refused with 403 when the business hasn't enabled quote requests.

customer_namebody · requiredCustomer full name
customer_phone / customer_emailbody · one requiredAt least one way to reply
detailsbody · requiredWhat needs quoting, in own words
budgetbody · optionalFree text, e.g. "around 2000"
response
{ "success": true, "message": "Request sent. They will get back to you.",
  "quote": { "id": "652…", "status": "new" } }
Embed

Drop-in widgets

No-API option: six widgets that render any module in an iframe. Same data, zero code on your side beyond a snippet from your dashboard.

Product list

Live grid with search and add-to-cart.

Buy button

Single-item order button for blog posts and landing pages.

Checkout

Cart + details + payment step.

Service list

Bookable services with duration and price.

Booking form

Service + date/time + details.

Quote form

Requests that land in the quotes inbox.

embed
<div data-odeen data-business="shopivo" data-widget="product-list"></div>
<script async src="https://getodeen.com/odeen-embed.js"></script>

Build snippets visually: dashboard → Developers → Widgets. The SDK auto-resizes iframes and emits odeen:order, odeen:booking and odeen:quote events on your page.

Start building

Create a page, grab a key, paste a widget — live in minutes.