Public REST API & Webhooks
Connect ERPs, couriers, warehouse scanners, CRMs and custom apps to your Joomni store, using scoped secret keys and real-time, HMAC-signed webhooks.
https://api.joomni.com/api/v1- 120
- requests / min per key
- 10
- webhook topics
- 3
- example languages
Get started
Quick Start
From zero to your first response in about two minutes.
- 1
Create an API key
In Settings › API & Webhooks, create a key with only the scopes you need. Copy it now: it is shown once. - 2
Make your first call
Send it asAuthorization: Bearer. Your store’s name in the response means the key works. - 3
Add a webhook
Add anhttps://endpoint per topic, copy its signing secret, and press Send test.
Security
Authentication & API Keys
Every request needs a secret key starting with jk_live_. A key belongs to one store and can only do what its scopes allow.
| Header | Example |
|---|---|
| Authorization | Bearer jk_live_your_key_here |
| X-Joomni-Access-Token | Alternative to Authorization, if your HTTP client makes that header awkward. |
Bearer jk_live_your_key_hereAuthorization, if your HTTP client makes that header awkward.Keys are server-side secrets
Permissions
Permission Scopes
Grant each key the smallest set it needs. A request outside the key’s scopes returns 403.
| Scope | Allows |
|---|---|
| store:read | Store name, currency, timezone, locale and vertical. |
| products:read | List, search and read products with their variants. |
| products:write | Create, update and delete products. |
| orders:read | List and read orders, line items, shipments and buyer details. |
| orders:write | Create orders, fulfill them with tracking, and cancel with restock. |
| inventory:write | Adjust stock by a delta or set an absolute level, with an audit reason. |
| leads:read | Read contact-form inquiries, quote requests and AI qualification scores. |
| leads:write | Create leads and update their status and tags. |
| bookings:read | Read bookable services, free slots and appointments with their customer details. Needs the Appointments & Bookings extension. |
| bookings:write | Book, reschedule, confirm, complete and cancel appointments. |
| reviews:read | Read product reviews, ratings, photos and store replies. Needs the Product Reviews extension. Reviewer emails are never included. |
| reviews:write | Publish, hide or mark reviews as spam, and post the store’s public reply. |
inventory:read, customers:* and webhooks:manage appear in the key form but have no public endpoints yet. Bookings and reviews endpoints answer 403 with EXTENSION_NOT_INSTALLED while their extension isn’t installed on the store.
Reference
Endpoints
All paths are relative to https://api.joomni.com/api/v1. Request and response bodies are JSON.
Products
Catalog items and their variants.
- GET
/productsList products. Query:
page,limit(max 100),status(ACTIVE,DRAFT,ARCHIVED),search,collectionId. - GET
/products/:idOne product with all its variants.
- POST
/productsCreate a product.
titleandpriceare required; thehandleis generated if omitted. - PUT
/products/:idUpdate any subset of fields.
- DELETE
/products/:idDelete a product. Past orders keep their line-item snapshot.
Orders & Fulfillment
Read orders, push tracking, cancel with restock.
- GET
/ordersList orders, newest first.
statustakes an order (PENDING), payment (PAID) or fulfillment (UNFULFILLED) status. Alsopage,limit,search. - GET
/orders/:idFull order with line items, shipments and customer.
- POST
/ordersCreate an order. Prices and totals are recorded as sent, like a dashboard order.
- POST
/orders/:id/fulfillMark fulfilled.
trackingNumber,trackingCompanyandtrackingUrlare saved as a shipment. Emails the customer. - POST
/orders/:id/cancelCancel.
restockItemsdefaults totrue; an optionalreasongoes on the order timeline.
Inventory
Stock updates for warehouses and barcode scanners.
- POST
/inventory/adjustSend
productIdorvariantId, thenadjustment(± delta) orlevel(absolute count).
Leads & Inquiries
Sync contact forms and quote requests with a CRM.
- GET
/leadsList leads. Query:
page,limit,status. - GET
/leads/:idOne lead, with AI intent and qualification score when AI features are on.
- POST
/leadsCreate a lead.
firstName,emailandmessageare required. - PUT
/leads/:idUpdate
status(NEW,CONTACTED,QUALIFIED,LOST,CONVERTED,SPAM) andtags.
Bookings & Appointments
Sync appointments with a calendar, CRM or front-desk app. Needs the Appointments & Bookings extension.
- GET
/bookings/servicesBookable services with duration, price, capacity, category and staff.
include_inactive=trueadds switched-off ones. - GET
/bookings/availabilityFree slots.
service_idanddate(YYYY-MM-DD) for a day’s slots, ormonth(YYYY-MM) for the days that have any. Optionalstaff_id,party_size. - GET
/bookings/appointmentsList appointments, newest first. Query:
page,limit,status(comma list ofPENDING,CONFIRMED,CHECKED_IN,COMPLETED,CANCELLED,NO_SHOW),start/end(ISO),service_id,staff_id,search. - GET
/bookings/appointments/:idOne appointment with service, staff, resource, customer, payment and timestamps.
- POST
/bookings/appointmentsBook a slot.
serviceId,startTime(a slot from availability),customerNameandcustomerEmailare required. Checked by the same slot engine as the widget, so it can’t double-book; the customer gets the confirmation email. - PUT
/bookings/appointments/:id/statusMove to
CONFIRMED,CHECKED_IN,COMPLETED,NO_SHOWorCANCELLED, with an optionalreason.notify: falseskips the customer email. - POST
/bookings/appointments/:id/rescheduleMove to a new
startTime(and optionallystaffMemberId). Refused with409if the slot is taken.
Product Reviews
Moderate reviews and show them elsewhere. Needs the Product Reviews extension.
- GET
/reviewsList reviews, newest first. Query:
page,limit,status(PENDING,PUBLISHED,REJECTED,SPAM),product_id,updated_since(ISO). - GET
/reviews/:idOne review with its product, rating, text, photos, helpful count, verified-purchase flag and store reply.
- PUT
/reviews/:id/statusSet
statustoPUBLISHED,REJECTED(hidden),SPAMorPENDING. Product ratings are recalculated. - PUT
/reviews/:id/replyPost or replace the store’s public
reply(up to 3000 characters).
Store
Details of the store the key belongs to.
- GET
/storeName, domains, currency, timezone, locale, vertical and contact details.
Conventions
Responses & Pagination
Results are wrapped in data. Lists add meta: pass page (from 1) and limit (1–100), and stop when page reaches meta.totalPages.
Money
A plain number in the store’s currency, not cents. Read the currency from GET /store.
Timestamps
ISO 8601, in UTC.
IDs
Strings. Use id as your foreign key, not orderNumber, which is for display.
New fields
Can appear at any time. Ignore fields you don’t recognise.
Bookings and reviews use snake_case fields (start_time, verified_purchase). Every field of orders, products, variants and leads, with all status values, is in the object reference.
Conventions
Errors & Rate Limits
Errors use standard status codes and a JSON body with statusCode, message and error. For validation errors, message is a list.
| Status | Meaning | What to do |
|---|---|---|
| 400 | Invalid body or query | Read message and fix the request. Don’t retry it unchanged. |
| 401 | Key missing, wrong, expired or revoked | Check the key and the Bearer header. |
| 403 | Missing scope, or no API access on your plan | Add the scope to the key. |
| 404 | No such record in this store | Check the id and that the URL includes /api/v1. |
| 429 | Rate limit reached | Wait Retry-After seconds, then retry. Always safe. |
| 5xx | Failure on our side | Retry reads with backoff; check the record before retrying a write. |
| Rate-limit header | Value |
|---|---|
| X-RateLimit-Limit | 120 requests per minute, per key |
| X-RateLimit-Remaining | Requests left in the current one-minute window |
| X-RateLimit-Reset | Unix time the window resets |
| Retry-After | On 429 only: seconds to wait |
Try it
Live API Sandbox Console
Run read requests against your store and inspect real responses and headers. Writes are shown as a dry run and never change your data.
Live REST API Sandbox Console
Test live requests, inspect JSON schemas, and verify response formats directly from your browser.
Leave blank to test using your current logged-in store session, or paste an API key created under Settings › API & Webhooks.
Events
Real-Time Webhooks
Instead of polling, receive an HTTPS POST the moment something happens. Add one subscription per topic in Settings › API & Webhooks.
orders/createAll channelsAn order is placed on the storefront, POS, dashboard or API.
orders/updatedAll channelsAn order’s status, payment, fulfillment or contents change.
orders/fulfilledAll channelsAn order is marked fulfilled. Its shipments carry the tracking.
orders/cancelledAll channelsAn order is cancelled.
leads/createAll channelsA contact form, quote request or API call creates a lead.
leads/updatedAll channelsA lead’s status or tags change.
bookings/createAll channelsAn appointment is booked on the widget, in the dashboard, by AI or through the API.
bookings/updatedAll channelsAn appointment is confirmed, checked in, completed, marked no-show, rescheduled or edited.
bookings/cancelledAll channelsAn appointment is cancelled by the customer or the store.
reviews/createStorefrontA shopper submits a product review (spam excluded), whatever its status.
reviews/publishedAll channelsA review goes live, automatically or by a moderator, dashboard, AI or API.
products/create · update · deleteDashboard & APIA product is created, modified or removed.
inventory/level_updateAPI onlyStock is adjusted.
Coming later
Webhooks
Webhook Payload
Every delivery uses the same envelope, with the record itself in data. Send test deliveries have test: true and placeholder data.
| Header | Value |
|---|---|
| X-Joomni-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256> |
| X-Joomni-Event-Id | Same as the body’s id. Use it to ignore repeats. |
| X-Joomni-Topic | The event topic, e.g. orders/create. |
| X-Joomni-Timestamp | The Unix time that was signed. |
| X-Joomni-Store-Id | The store the event belongs to. |
| X-Joomni-Webhook-Id | The subscription that matched. |
| X-Joomni-Test-Mode | true on Send test deliveries. |
| X-Joomni-Hmac-Sha256 | Deprecated: body-only signature that can be replayed. Use X-Joomni-Signature. |
t=<unix seconds>,v1=<hex HMAC-SHA256>Webhooks
Verifying Webhook Signatures
Compute HMAC-SHA256(secret, t + "." + rawBody) in hex and compare it in constant time with each v1 value. Reject requests whose t is more than 5 minutes from now, so a captured request can’t be replayed.
Verify the raw body, not parsed JSON
express.raw() in Express, request.get_data() in Flask, request.body in Django or $request->getContent() in Laravel. The key is the whole secret, including whsec_.All three are tested against the same signature test vectors, which are worth running against your own code too.
Webhooks
Testing Webhooks Locally
Joomni delivers only to public HTTPS addresses. While you build a receiver, you have two options.
No store needed
The example repo’s tools/send-test-webhook.mjs sends signed sample events for every topic to localhost, including duplicates, bad signatures and expired timestamps.
Real events through a tunnel
Run cloudflared tunnel --url http://localhost:4242 or ngrok http 4242, add the HTTPS address as an endpoint, and press Send test.
Webhooks
Responding & Delivery Guarantees
Reply 2xx within 10 seconds
Verify, store the event, reply, then do slow work in a background job. Redirects are not followed.
Not retried yet
A failed delivery shows in the endpoint’s log but isn’t resent. Run a periodic job that lists recent records to catch misses.
Expect duplicates
Save each processed id (a unique database column works well) and skip ids you have seen.
Order isn’t guaranteed
Compare data.updatedAt with what you stored and ignore older updates.
Production
Go-Live Checklist
- One API key per integration, with only the scopes it uses.
- Keys and secrets in environment variables or a secret manager.
- Signatures verified on the raw body, with the 5-minute window.
- Event ids deduplicated in a database; slow work queued.
- A reconcile job that covers missed deliveries.
429retried afterRetry-After; other 4xx logged, not retried.- No full order or lead payloads in logs: they hold personal data.
- Send test reaches the production URL and gets a 200.
Open source
Guides & Examples on GitHub
Everything below lives in joomni225/joomni-api-examples, alongside runnable code in three languages.
API in 60 seconds
Every endpoint as a copy-paste curl command.
docs/curl.mdObject reference
Every field of Order, Product, Variant and Lead, with all status values.
docs/objects.mdIntegration recipes
ERP order sync, warehouse stock, courier tracking, CRM leads, chat alerts.
docs/recipes.mdWebhooks in depth
Topics, envelope, signatures and delivery guarantees.
docs/webhooks.mdTroubleshooting
Fixes for 401/403/404, signature mismatches and missing deliveries.
docs/troubleshooting.mdChangelog
Changes to the API and webhook formats, by api_version.
CHANGELOG.mdjoomni225/joomni-api-examples
Clone working examples in JavaScript, Python and PHP
API clients, webhook receivers, signature tests and a local event sender. No dependencies to start.
Open source
Ready-made tools
Built on this API. Use them as they are, or as a starting point for your own integration.
Orders & leads in Google Sheets
An Apps Script that keeps your orders and enquiries in a spreadsheet, updated every 15 minutes. No server to host.
store:read · orders:read · leads:read
github.com/joomni225/joomni-google-sheets →CSV & Shopify product import
A command-line tool that imports products and variants from a spreadsheet or a Shopify export, with a dry run and a report.
products:read · products:write
github.com/joomni225/joomni-csv-product-import →