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:
https://api.getodeen.com
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.
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).
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.
Products & services
Live items with prices, stock and photos. Businesses are addressed by handle — the part after getodeen.com/.
/v1/businesses/:handle/productsList 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. shopivomodule_keyquery · optionalItem module to listcategoryquery · optionalCategory id — parent includes subcategoriesqquery · optionalSearch names and descriptionslimitquery · optionalMax items, default 100curl "https://api.getodeen.com/v1/businesses/shopivo/products?module_key=menu" \ -H "Authorization: Bearer odn_pk_…"
{
"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" }] }]
}]
}/v1/businesses/:handle/products/:idOne item with full details, stock state and variations.
:handlepathBusiness handle:idpathProduct id/v1/businesses/:handle/servicesBookable services with duration, price and photo. Supports the same ?category= and ?q= filters as products.
:handlepathBusiness handlecategoryquery · optionalCategory id — parent includes subcategoriesqquery · optionalSearch names and descriptions/v1/businesses/:handle/slots?service_id=:id&date=:yyyy-mm-ddFree start times for a service on a day, derived from opening hours, duration and existing bookings. Past and taken slots are excluded.
service_idqueryService iddatequeryDay as YYYY-MM-DD (not in the past){ "success": true, "date": "2026-09-20",
"slots": ["2026-09-20T09:00:00", "2026-09-20T09:30:00"] }/v1/businesses/:handle/products/:id/reviewsReview list with the live average and count.
:handlepathBusiness handle:idpathProduct id{ "success": true, "rating_average": 4.5, "rating_count": 12,
"reviews": [{ "id": "653…", "customer_name": "Efua",
"rating": 5, "title": "Worth it", "comment": "Fast delivery" }] }/v1/businesses/:handle/products/:id/reviewsPost a 1–5 star review. Visible immediately; the product average recomputes on write.
customer_namebody · requiredReviewer nameratingbody · required1 to 5title / commentbody · optionalOptional words// 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"] }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.
/api/products/business/:business_id/categoriesFlat list of categories and subcategories with per-shelf item counts. Owner JWT.
:business_idpathBusiness id{ "success": true, "categories": [
{ "id": "64f…", "name": "Food", "parent_id": null, "product_count": 12 },
{ "id": "650…", "name": "Jollof", "parent_id": "64f…", "product_count": 3 }
] }/api/products/business/:business_id/categoriesCreate a category, or a subcategory with parent_id. A subcategory cannot have children of its own. Owner JWT.
namebody · requiredShelf name, max 60 charsparent_idbody · optionalParent category id for a subcategory/api/products/business/:business_id/categories/:idRename a category. Owner JWT.
namebody · requiredNew name/api/products/business/:business_id/categories/:idDelete a shelf without losing data: items become uncategorised, subcategories step up to top level. Owner JWT.
:idpathCategory idOrders & payments
Checkout that writes straight into the Odeen orders inbox. Stock is checked and reserved with the same rules as the page checkout.
/v1/businesses/:handle/ordersCreate 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 namecustomer_phonebody · requiredPhone or WhatsAppcustomer_emailbody · optionalReceipt addressitemsbody · required[{"product_id, qty (or quantity), variations?}]shipping_methodbody · optional'pickup' (default), 'standard' or 'express'delivery_street / city / zipbody · optionalStructured address; required for deliverydelivery_addressbody · optionalFree-form fallback linecustomer_note / notesbody · optionalNote for the businesspayment_methodbody · optional'online' initialises a card charge in the same callcurl -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 }]
}'{
"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" }]
}
}/v1/orders/:idOrder status and lines. Also works as /v1/orders/:id?key=odn_pk_… for status links.
:idpathOrder id/v1/orders/:id/payCharge 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 numberproviderbody · momo required'mtn', 'vodafone' or 'airteltigo'{ "success": true, "authorization_url": "https://checkout.paystack.com/…",
"reference": "PSK-…", "order": { "id": "650…", "payment_status": "unpaid" } }/v1/orders/:id/paymentPayment 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{ "success": true, "payment": { "order_id": "650…",
"payment_status": "unpaid", "payment_method": "online",
"amount": 123.15, "currency": "GHS", "reference": "PSK-…" } }/api/business/:business_idOwner 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 deliveryshipping_standard_feebody · optionale.g. 30shipping_express_feebody · optionale.g. 60free_shipping_thresholdbody · optionale.g. 1500, 0 = nonetax_ratebody · optionale.g. 15 for VAT, 0–100curl -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}'Bookings & quotes
Appointments into the Odeen calendar, quote requests into the inbox.
/v1/businesses/:handle/bookingsBook an appointment. The end time is derived from the service duration; past start times are refused.
service_idbody · requiredService idstart_timebody · requiredISO 8601 datetime, in the futurecustomer_namebody · requiredCustomer full namecustomer_phonebody · requiredPhone or WhatsAppcustomer_note / notesbody · optionalAnything the business should knowcurl -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"
}'{ "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" } }/v1/businesses/:handle/quotesRequest a quote. Refused with 403 when the business hasn't enabled quote requests.
customer_namebody · requiredCustomer full namecustomer_phone / customer_emailbody · one requiredAt least one way to replydetailsbody · requiredWhat needs quoting, in own wordsbudgetbody · optionalFree text, e.g. "around 2000"{ "success": true, "message": "Request sent. They will get back to you.",
"quote": { "id": "652…", "status": "new" } }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.
<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.