Alaamah One exposes three integration surfaces. Store & System API: you add read-only endpoints to your store, POS, ERP or accounting system and we pull products, customers, orders, suppliers, invoices and more. Webhooks: your system pushes changes to us in real time. MCP server: AI assistants (Claude and any MCP client) act on an Alaamah One account with the owner's consent.
To connect, the account owner opens Connected accounts in Alaamah One and picks Stores → Custom store (API) or Systems → Custom system (API), then pastes your API base URL and API key. We refresh daily or whenever “Sync” is pressed.
Read-only — we never write to your system. One endpoint is enough to start; we re-check the missing ones on every sync, so they start working the day you publish them. Stores built on Salla or Zid, and Odoo, Daftra, Qoyod and Zoho Books, have built-in connectors — see Connection guides.
Endpoints
| Endpoint | Status | What we do with it |
|---|---|---|
GET /products | required* | Items, images, prices and stock → Brand Library. |
GET /customers | required* | Customers → Customers (name · phone · email · city). |
GET /orders | optional | Orders or invoices → purchase history: order count, spend and last order per customer. |
* Any one of the three is enough to connect; all three is best.
Authentication
We send the key in both headers — read whichever you prefer:
Authorization: Bearer YOUR_API_KEY
X-API-Key: YOUR_API_KEY
- HTTPS on a public host is required. Redirects (301/302) are not followed — give us the final URL.
- The base URL is what
/products,/customersand/ordersare appended to, e.g.https://api.example.com/v1. - Use a dedicated read-only key that can be revoked at any time. Each key belongs to one account (company or branch) and returns only its data.
Pagination & response shape
Every endpoint accepts page (1-based) and per_page (we request 200; you may return fewer, e.g. 100, and say so in meta). JSON response:
{ "data": [ ... ], "meta": { "page": 1, "per_page": 200, "total": 1240, "last_page": 7 } }
We also accept "has_more": true|false instead of last_page, or a bare array (we stop at a shorter page). A missing endpoint should return 404. Our default pace is 60 requests per minute, and we honor 429 with Retry-After.
Products
| Field | Type | Description |
|---|---|---|
id required | string | Your stable item ID. |
name required | string | Item name. |
images required | string[] | https image URLs (first = main). Items without an image are skipped — the library is for design; we show the skipped count. |
price | number | Regular price. |
sale_price | number|null | Sale price, if any. |
currency | string | ISO currency code, e.g. SAR. |
sku | string | SKU. |
code | string | Public item code (stored when there is no sku). |
brand | string | Item brand. |
url | string | Item page URL, if any. |
description | string | Plain-text description (no HTML; up to 2000 chars). The agent reads it to answer customer questions. |
attributes | object[] | Specifications: [{"name":"Thickness","value":"1.85 mm"}] — or an object {"Thickness":"1.85 mm"}. Up to 30. |
categories | string[] | Categories from broad to specific (stored alongside category). |
unit | string | Selling unit: piece, meter, carton… |
category | string | Category (string or array of strings). |
stock | int|null | Quantity on hand (null = not tracked). |
in_stock | bool | Available or sold out — wins over stock when sent. |
updated_at | ISO 8601 | Last modified. |
{
"data": [
{
"id": "1001",
"code": "P-1001",
"name": "Cordless Drill 18V",
"brand": "Example Tools",
"price": 349.00,
"sale_price": 299.00,
"currency": "SAR",
"sku": "DRL-18V",
"images": ["https://example.com/img/drill-1.jpg", "https://example.com/img/drill-2.jpg"],
"url": "https://example.com/products/cordless-drill-18v",
"category": "Power Tools",
"categories": ["Power Tools", "Drills"],
"description": "18V cordless drill with 2 batteries and a charger. Keyless chuck 13 mm.",
"attributes": [{"name": "Voltage", "value": "18V"}, {"name": "Chuck", "value": "13 mm"}, {"name": "Weight", "value": "1.6 kg"}],
"unit": "piece",
"stock": 25,
"in_stock": true,
"updated_at": "2026-10-01T09:30:00Z"
}
],
"meta": { "page": 1, "per_page": 200, "total": 1240, "last_page": 7 }
}
Customers
updated_since is optional: return customers whose details or orders changed since then (used by the daily sync; manual syncs request everything). If you don't support it, return everything.
| Field | Type | Description |
|---|---|---|
id required | string | Your stable customer ID. |
name | string | Full name. |
phone | string | Mobile, preferably international +9665XXXXXXXX (local Saudi 05XXXXXXXX accepted). |
email | string | Email address. |
city | string | City. |
country | string | ISO 2-letter country code, e.g. SA. |
orders_count | int | Number of completed orders (ignored when you publish /orders). |
total_spent | number | Lifetime spend (same). |
currency | string | Spend currency. |
last_order_at | ISO 8601 | Date of the last order (same). |
{
"data": [
{
"id": "C-5521",
"name": "Sara Ahmed",
"phone": "+966500000000",
"email": "sara@example.com",
"city": "Riyadh",
"country": "SA",
"orders_count": 4,
"total_spent": 1890.50,
"currency": "SAR",
"last_order_at": "2026-09-28T17:05:00Z"
}
],
"meta": { "page": 1, "per_page": 200, "total": 8312, "last_page": 42 }
}
Orders optional
When published, we build each customer's purchase history from it (order count · spend · last order) and read it in full on every customer sync. An order without customer_id (a walk-in POS sale) is matched by the phone or email in customer; an order with neither only counts toward orders scanned.
| Field | Type | Description |
|---|---|---|
id required | string | Order or invoice number. |
customer_id | string|null | Customer ID as in /customers. |
customer | object | name · phone · email · city · country — for walk-in customers. |
status | string | paid · completed · processing count; cancelled · refunded · pending · failed · void · draft don't. Missing = paid. |
total | number | Order total. |
currency | string | Currency. |
created_at | ISO 8601 | Order date. |
{
"data": [
{
"id": "INV-90017",
"customer_id": "C-5521",
"status": "paid",
"total": 472.50,
"currency": "SAR",
"created_at": "2026-09-28T17:05:00Z"
},
{
"id": "INV-90018",
"customer_id": null,
"customer": { "name": "Walk-in customer", "phone": "+966511111111", "email": null },
"status": "paid",
"total": 89.00,
"currency": "SAR",
"created_at": "2026-09-28T18:40:00Z"
}
],
"meta": { "page": 1, "per_page": 200, "has_more": false }
}
Version 2 — data for AI agents optional additions
Version 2 is fully backward compatible with version 1: a system implementing v1 keeps working unchanged. Everything below is optional and lets your WhatsApp agents use real data: the sales agent shows items with prices and stock, the purchasing agent knows each item's suppliers and purchase prices, and the customer agent knows orders, invoices and payments (to notify customers about receipts, payments and overdue invoices — with approved templates only).
Every request carries the header X-Alaamah-Spec: 2. updated_since (ISO 8601) is accepted on every endpoint: used by the daily sync; manual syncs request everything.
Extra product fields /products
| Field | Type | Description |
|---|---|---|
cost | number | Purchase cost. Never shown to the sales agent — purchasing agent only. |
barcode | string | Barcode. |
unit | string | Unit (piece · carton · kg…). |
name_en | string | English name. |
low_stock_threshold | int | Reorder level. |
supplier_id | string | Default supplier (as in /suppliers). |
active | bool | false = inactive. |
Items without an image don't enter the Brand Library (it is for design), but they do enter the agents' catalog.
Detailed orders /orders
On top of v1 fields add: number · payment_status (unpaid · partial · paid · refunded) · subtotal · tax · discount · updated_at · items[] (product_id · sku · name · quantity · price · total). Status: pending · processing · completed · cancelled · refunded (paid accepted).
{
"id": "SO-5531",
"number": "SO-5531",
"customer_id": "C-77",
"customer": { "name": "Sara Ahmed", "phone": "+966500000001", "email": "sara@example.com" },
"status": "processing",
"payment_status": "unpaid",
"currency": "SAR",
"subtotal": 600.00, "tax": 90.00, "discount": 0, "total": 690.00,
"created_at": "2026-10-03T12:10:00+03:00",
"updated_at": "2026-10-03T12:15:00+03:00",
"items": [
{ "product_id": "1001", "sku": "DRL-18V", "name": "Cordless Drill 18V", "quantity": 2, "price": 300.00, "total": 600.00 }
]
}
Suppliers GET /suppliers optional
| Field | Type | Description |
|---|---|---|
id required | string | Stable supplier ID. |
name | string | Supplier or company name. |
contact_name · phone · email · city · country | string | Contact details. |
tax_number | string | VAT number. |
products[] | array | What they supply: product_id · sku · name · price (purchase price) · currency · min_qty · lead_time_days. The list you send is the truth: missing lines are removed on our side. |
{
"data": [
{
"id": "S-12",
"name": "Example Trading Co.",
"contact_name": "Khalid",
"phone": "+966512345678",
"email": "sales@example.com",
"city": "Jeddah",
"country": "SA",
"tax_number": "300000000000003",
"currency": "SAR",
"updated_at": "2026-10-02T08:00:00Z",
"products": [
{ "product_id": "1001", "sku": "DRL-18V", "name": "Cordless Drill 18V", "price": 210.00, "currency": "SAR", "min_qty": 10, "lead_time_days": 5 }
]
}
],
"meta": { "page": 1, "per_page": 200, "has_more": false }
}
Invoices GET /invoices optional
Status: draft · issued · partial · paid · overdue · void. We also mark an invoice overdue ourselves once due_at passes with an amount still due.
{
"data": [
{
"id": "INV-2026-0091",
"number": "INV-2026-0091",
"order_id": "SO-5531",
"customer_id": "C-77",
"status": "partial",
"currency": "SAR",
"total": 690.00, "amount_paid": 200.00, "amount_due": 490.00,
"issued_at": "2026-10-03", "due_at": "2026-10-17",
"updated_at": "2026-10-05T09:00:00Z",
"items": [ { "product_id": "1001", "name": "Cordless Drill 18V", "quantity": 2, "price": 300.00 } ]
}
],
"meta": { "page": 1, "per_page": 200, "last_page": 1 }
}
Payments GET /payments optional
Each payment links to its invoice (invoice_id) so we update paid and due amounts. Status: completed · pending · failed · refunded.
{
"data": [
{
"id": "PAY-881",
"invoice_id": "INV-2026-0091",
"order_id": "SO-5531",
"customer_id": "C-77",
"amount": 200.00,
"currency": "SAR",
"method": "mada",
"status": "completed",
"paid_at": "2026-10-04T16:20:00+03:00"
}
],
"meta": { "page": 1, "per_page": 200, "has_more": false }
}
Version 3 — full system data for internal agents optional additions
Version 3 is fully backward compatible with 1 and 2. It adds optional read endpoints for sales, purchases, inventory, people, finance and the audit log, read by the “agent team”: internal agents behind the customer-facing agent
(for example a sales manager who approves discounts, or an auditor who reviews quotes before they are sent) and the account owner. Publish only what fits you; a missing endpoint returns 404 and is not an error.
We send X-Alaamah-Spec: 3 on these endpoints (and 2 elsewhere, as before). Read-only — we never write to your system.
Data classification: public and internal
public may be read by the customer-facing agent, limited to the customer it is talking to (catalog without cost · that customer's orders, invoices, quotes and returns). internal is for internal agents and the owner only: purchases, purchase prices, costs, margins, reps, finance, the audit log and all sales. Alaamah One enforces this itself: a customer-facing agent can never read an internal resource, even if someone enables it, and cost fields never reach it. Send complete data; we do the withholding.
Common shape
- Every endpoint:
GET {base}/{path}?page=1&per_page=200&updated_since=…with the same{ "data": [...], "meta": {...} }envelope and pagination. - Optional filters we may send:
from·to(document date) ·branch_id·warehouse_id·customer_id·supplier_id·user_id·status. Ignore what you don't support. - A document (quote · purchase order · supplier invoice · goods receipt · return · transfer · adjustment · expense · supplier payment):
idrequired ·number·status·customer_idorsupplier_id(pluscustomer/supplierwith a name) ·user_idorsalesperson_id·branch_id·warehouse_id·currency·subtotal·discount·tax·total·amount_paid·amount_due·date·due_at·approved_at·approved_by·updated_at·items[]. - A line:
product_id·sku·name·quantity·unit·price·discount(amount) ·tax(amount) ·total·cost(internal). We keep up to 200 lines per document, stored compactly. - Documents, movements and the audit log are kept for 24 months by default; older rows are deleted.
{
"data": [
{
"id": "PO-1042",
"number": "PO-1042",
"status": "approved",
"supplier_id": "S-12",
"supplier": { "name": "Example Trading Co." },
"user_id": "U-7",
"branch_id": "BR-1",
"warehouse_id": "WH-1",
"currency": "SAR",
"subtotal": 4200.00, "discount": 200.00, "tax": 600.00, "total": 4600.00,
"amount_paid": 0, "amount_due": 4600.00,
"date": "2026-10-03T10:00:00+03:00",
"due_at": "2026-11-02",
"approved_at": "2026-10-03T12:30:00+03:00", "approved_by": "U-2",
"updated_at": "2026-10-03T12:30:00+03:00",
"items": [
{ "product_id": "1001", "sku": "DRL-18V", "name": "Cordless Drill 18V", "quantity": 20, "unit": "piece",
"price": 210.00, "discount": 10.00, "tax": 30.00, "total": 4200.00, "cost": 210.00 }
]
}
],
"meta": { "page": 1, "per_page": 200, "has_more": false }
}
Endpoints
| Endpoint | Group | Class | Content |
|---|---|---|---|
GET /quotations | Sales | public | Quotes / estimates |
GET /credit-notes | Sales | public | Sales returns / credit notes |
GET /receivables | Sales | internal | Receivables aging |
GET /purchase-requests | Purchases | internal | Purchase requests |
GET /purchase-orders | Purchases | internal | Purchase orders |
GET /purchase-invoices | Purchases | internal | Supplier (purchase) invoices |
GET /goods-receipts | Purchases | internal | Goods receipts |
GET /purchase-returns | Purchases | internal | Purchase returns / debit notes |
GET /supplier-payments | Purchases | internal | Supplier payments |
GET /supplier-payment-requests | Purchases | internal | Supplier payment requests |
GET /payables | Purchases | internal | Payables aging |
GET /warehouses | Inventory | internal | Warehouses / branches |
GET /stock-movements | Inventory | internal | Stock movements |
GET /transfers | Inventory | internal | Transfers |
GET /stock-adjustments | Inventory | internal | Adjustments / stock counts |
GET /batches | Inventory | internal | Batches / expiry |
GET /users | People & performance | internal | Sales reps / users |
GET /targets | People & performance | internal | Targets & achievement |
GET /commissions | People & performance | internal | Commissions |
GET /expenses | Finance | internal | Expenses |
GET /cash-movements | Finance | internal | Cash / bank movements (summary) |
GET /daily-sales | Finance | internal | Daily sales summary |
GET /profit | Finance | internal | Profit by item / category |
GET /audit-log | Audit | internal | Activity / audit log |
In v2, /orders and /invoices also accept salesperson_id (or rep_id) and branch_id, and their lines discount and tax. Customer payments and receipts stay on /payments.
Specific fields
| Endpoint | Fields |
|---|---|
/quotations | valid_until (or expires_at) · status: draft · sent · approved · rejected · expired · converted. |
/credit-notes · /purchase-returns | invoice_id or purchase_id (origin) · reason. |
/purchase-requests | requested_by · status: draft · submitted · approved · rejected · ordered. |
/purchase-orders | expected_at · approved_at · supplier_invoice_no · received_at (a full-lifecycle order up to invoicing is accepted). |
/goods-receipts | purchase_id · received_by · received_at · lines with optional expiry_date and batch_no. |
/supplier-payments · /supplier-payment-requests | amount (or total) · method · invoice_id/purchase_id · requested_by · approved_by. |
/receivables · /payables | Per customer/supplier: customer_id/supplier_id · as_of · current · d1_30 · d31_60 · d61_90 · d90_plus · total (or amount_due) · overdue_amount · open_invoices. A snapshot: replaced in full on every sync. |
/stock-movements | product_id · warehouse_id · type: sale · purchase · return_in · return_out · transfer_in · transfer_out · adjustment · count · production · other · signed quantity (or positive with direction: in · out) · unit_cost · document_type/document_id. |
/warehouses | name · type (warehouse · branch · store) · branch_id · city · active. |
/transfers · /stock-adjustments | from_warehouse_id · to_warehouse_id · adjustments/counts: type (adjustment · count) and signed line quantities. |
/batches | number · product_id · warehouse_id · qty · expiry_date. |
/users · /targets · /commissions | User: name · role · branch_id · active. Target: user_id · metric · target · achieved · period_start · period_end. Commission: user_id · amount · sales_amount · period_start · period_end. |
/expenses · /cash-movements · /daily-sales · /profit | An expense is a document with category. Cash: date · account_id · account_name · account_type (cash · bank) · inflow · outflow · balance. Daily: date · branch_id · orders_count · gross · discount · tax · returns · net. Profit (when your system computes it): dimension (item · category) · key · name · qty · revenue · cost · profit · margin · period. |
{ "data": [ { "customer_id": "C-77", "name": "Sara Ahmed", "currency": "SAR", "as_of": "2026-10-07",
"current": 1200.00, "d1_30": 490.00, "d31_60": 0, "d61_90": 0, "d90_plus": 300.00, "total": 1990.00,
"rep_id": "U-7", "open_invoices": 3, "oldest_invoice_at": "2026-06-20" } ],
"meta": { "page": 1, "per_page": 200, "last_page": 1 } }
{ "data": [ { "id": "MV-88120", "product_id": "1001", "sku": "DRL-18V", "warehouse_id": "WH-1", "type": "sale",
"quantity": -2, "unit_cost": 210.00, "document_type": "invoice", "document_id": "INV-2026-0091", "user_id": "U-7",
"date": "2026-10-03T12:12:00+03:00" } ],
"meta": { "page": 1, "per_page": 200, "has_more": true } }
Audit log /audit-log
Every create, approve, edit or delete of a document: action (create · approve · edit · delete · cancel · post) · document_type · document_id · document_number · user_id · user_name · summary · at.
We also accept event · subject_type · subject_id · description · created_at. Non-document events (vehicle tracking, sessions) are not stored — a subject_type filter helps.
{ "data": [ { "id": "AU-55012", "action": "approve", "document_type": "purchase_order", "document_id": "PO-1042",
"document_number": "PO-1042", "user_id": "U-2", "user_name": "Finance Manager", "summary": "Approved 4,600.00 SAR",
"at": "2026-10-03T12:30:00+03:00" } ],
"meta": { "page": 1, "per_page": 200, "has_more": false } }
Built-in connector support
Based on each system's documented public API. “Custom system (API)” and Alaamah ERP support whatever they publish with this spec. After the first sync, the card under Systems shows what actually arrived.
| Endpoint | Odoo | Daftra | Qoyod | Zoho Books |
|---|---|---|---|---|
quotations | supported | supported | supported | supported |
credit-notes | supported | supported | supported | supported |
receivables | not supported | not supported | not supported | not supported |
purchase-requests | supported | not supported | not supported | not supported |
purchase-orders | supported | not supported | supported | supported |
purchase-invoices | supported | supported | supported | supported |
goods-receipts | supported | not supported | not supported | not supported |
purchase-returns | supported | supported | supported | supported |
supplier-payments | supported | not supported | supported | supported |
supplier-payment-requests | not supported | not supported | not supported | not supported |
payables | not supported | not supported | not supported | not supported |
warehouses | supported | supported | supported | supported |
stock-movements | supported | supported | not supported | not supported |
transfers | supported | not supported | not supported | not supported |
stock-adjustments | supported | not supported | not supported | not supported |
batches | supported | not supported | not supported | not supported |
users | supported | supported | not supported | supported |
targets | not supported | not supported | not supported | not supported |
commissions | not supported | not supported | not supported | not supported |
expenses | supported | supported | not supported | supported |
cash-movements | not supported | not supported | not supported | not supported |
daily-sales | not supported | not supported | not supported | not supported |
profit | not supported | not supported | not supported | not supported |
audit-log | not supported | not supported | not supported | not supported |
Webhooks from your system to Alaamah One optional
Instead of waiting for the daily sync, push events as they happen. The account owner enables them in Connected accounts → Systems → Webhooks and gets a URL and a secret unique to that connection (the secret is shown once and can be rotated).
| Type | data | What we do |
|---|---|---|
order.created · order.updated | an order shaped like /orders | Store the order, its lines and status. |
payment.received | a payment shaped like /payments | Record it and update the invoice balance. |
invoice.issued · invoice.overdue | an invoice shaped like /invoices | Store the invoice (overdue is marked overdue). |
stock.low | {product_id, sku, name, stock, threshold} | Update stock immediately. |
POST https://one.alaamah.com/webhook/systems/{hook_key}
Content-Type: application/json
X-Alaamah-Signature: t=1791012345,v1=5f2b…(hex)
{
"id": "evt_01J9ZK3M7Q",
"type": "payment.received",
"created_at": "2026-10-04T16:20:05+03:00",
"data": { "id": "PAY-881", "invoice_id": "INV-2026-0091", "customer_id": "C-77", "amount": 200.00, "currency": "SAR", "paid_at": "2026-10-04T16:20:00+03:00" }
}
Purchase tracking (optional)
On order.created and payment.received you may add data.customer.email and a data.tracking object. We use them to send the purchase server-side to the pixels the merchant enabled in One (Meta · Snapchat · TikTok · Google Analytics 4 · Microsoft Advertising). Send the same event_id your browser pixel uses so the platforms merge the two instead of counting twice.
| Field | Use |
|---|---|
event_id · transaction_id | Deduplication key · order number shown in reports. |
value · currency | Purchase value (defaults to the event amount). |
ga_client_id · ga_session_id · gclid | Google Analytics 4 and Google Ads attribution. |
fbp · fbc | Meta attribution. |
ttp · ttclid | TikTok attribution. |
sc_cookie1 · sc_click_id | Snapchat attribution. |
msclkid | Microsoft Advertising (Bing) attribution — the click ID from the landing-page URL; without it we match by the hashed email or phone. |
ip · ua · url | Customer IP, user agent and page — improves matching. |
Signature
Header X-Alaamah-Signature: t=<unix>,v1=<hex> where v1 = HMAC-SHA256(secret, t + "." + raw body). We reject requests whose timestamp differs from our clock by more than 5 minutes, or with a wrong signature (401).
id is unique per event: a re-sent event returns 200 with "duplicate": true and is never applied twice — retry safely on network errors.
# PHP
$t = time();
$sig = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
$header = "t={$t},v1={$sig}";
# Node.js
const t = Math.floor(Date.now() / 1000);
const sig = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
headers['X-Alaamah-Signature'] = `t=${t},v1=${sig}`;
# Python
t = int(time.time())
sig = hmac.new(secret.encode(), f"{t}.{raw_body}".encode(), hashlib.sha256).hexdigest()
| Response | Meaning |
|---|---|
200 | Received (result: applied · ignored) or duplicate. |
401 | Bad signature or timestamp outside the window. |
404 · 410 | Unknown URL · connection disconnected. |
413 · 422 | Body over 256 KB · bad type or shape. |
Version 3 events
Same signature and rules as Webhooks: quote.created · quote.approved · po.created · po.approved · goods.received ·
return.created (data.type: sale · purchase) · payment.made (a supplier payment) · stock.adjusted. data is shaped like the matching endpoint. Internal events: stored, never sent to a customer.
{ "id": "evt_01JA2B3C4D", "type": "po.approved", "created_at": "2026-10-03T12:30:05+03:00",
"data": { "id": "PO-1042", "number": "PO-1042", "supplier_id": "S-12", "status": "approved", "total": 4600.00, "currency": "SAR",
"approved_at": "2026-10-03T12:30:00+03:00", "approved_by": "U-2" } }
Connect from Odoo · Daftra · Qoyod · Zoho · SAP · Oracle
| System | How |
|---|---|
| Alaamah ERP | Ready: in Alaamah ERP → Settings → “Link Alaamah One” create a key and paste it into the “Alaamah ERP” card. No URL, no code. |
| Odoo (Online and 14–18) | Built-in connector beta: Odoo URL + database + user email + API key (Preferences → Account Security). We read via JSON-RPC: items and images, customers, suppliers with purchase prices (supplierinfo), sales orders, invoices and payments. We never write. |
| Daftra | Built-in connector beta: account name + API key (Settings → API). Items with stock and cost, clients, suppliers, invoices and payments. |
| Qoyod | Built-in connector beta: API key from General Settings. Items, customers, vendors, invoices and payments (Qoyod exposes no item images). |
| Zoho Books | Built-in connector beta: “Connect Zoho Books” button with read-only OAuth consent. |
| SAP (Business One · S/4HANA) | No direct connector yet: your developer or SAP partner implements this spec on top of the Service Layer (B1) or OData (S/4HANA) — a small read layer mapping Items, BusinessPartners (C/S), Orders, Invoices and IncomingPayments to the shapes above — then the owner connects it as “Custom system (API)”. |
| Oracle (NetSuite · Fusion) | Same via “Custom system (API)”: a read-only RESTlet or SuiteQL in NetSuite, or the official REST APIs in Fusion, plus a mapping layer to this spec. |
| Anything else (POS · Wafeq · Microsoft Dynamics…) | “Custom system (API)”: implement the endpoints above (v1 is enough to start) and give the account owner the URL and key. |
MCP server
Alaamah One is a remote Model Context Protocol server. Any MCP client can read the brand identity, list works and products, and create images, flyers and videos on the user's plan.
| Item | Value |
|---|---|
| Server URL | https://one.alaamah.com/mcp |
| Transport | Streamable HTTP — POST (JSON-RPC 2.0), GET, DELETE |
| Protocol versions | 2025-11-25 · 2025-06-18 · 2025-03-26 · 2024-11-05 |
| Server info | alaamah-one 1.2.0 |
Connect a client
Add https://one.alaamah.com/mcp as a custom connector (remote MCP server) in your client. The client discovers the authorization server, registers itself and opens the Alaamah One sign-in page, where the user approves access.
{
"mcpServers": {
"alaamah-one": { "url": "https://one.alaamah.com/mcp" }
}
}
OAuth 2.1
| Endpoint | Purpose |
|---|---|
GET /.well-known/oauth-protected-resource | Protected resource metadata (RFC 9728). |
GET /.well-known/oauth-authorization-server | Authorization server metadata (RFC 8414). |
POST /mcp/oauth/register | Dynamic client registration (RFC 7591). |
GET /mcp/oauth/authorize | User sign-in and consent. |
POST /mcp/oauth/token | Grants authorization_code and refresh_token. |
POST /mcp/oauth/revoke | Token revocation. |
- PKCE with
S256is required. - Send the access token as
Authorization: Bearer …on every/mcprequest. - The user can revoke any client at any time from Connected apps in Alaamah One.
Brand selection
An account can own several brands. Studio tools act on the current brand; send X-Alaamah-Brand: <brand id> to choose another. The server accepts it only if the brand belongs to the token owner, otherwise it answers 403.
Preview and confirm
Tools that spend credits never run on the first call. They return a preview with the cost estimate and a plan_token. Ask the user, then call the same tool with the same inputs plus "confirm": true and that plan_token. confirm without a matching token is rejected, and previews expire after 15 minutes.
// 1) preview
{"name": "generate_image", "arguments": {"prompt": "A product shot on a white table"}}
// → a preview with the cost estimate and a plan_token
// 2) run after the user approves
{"name": "generate_image", "arguments": {"prompt": "A product shot on a white table", "confirm": true, "plan_token": "<from the preview>"}}
Tools
53 tools. The list a client sees depends on the user's plan and team permissions.
| Tool | Title | Access |
|---|---|---|
brand_book | Brand book | read |
list_works | My works | read |
job_status | Running jobs | read |
list_products | Brand products | read |
list_social_accounts | Connected social accounts | read |
company_profile | Company profile PDF | write |
generate_image | Generate image | write |
create_flyer | Create flyer | write |
smart_video | Smart video | write |
avatar_video | Talking avatar video | write |
schedule_post | Schedule post | write |
brand_book_status | Brand book completeness | read |
brand_book_update | Edit brand book | write |
brand_logo_set | Set brand logo | write |
agents_list | WhatsApp agents | read |
agent_profile_get | Agent profile | read |
agent_profile_update | Edit agent profile | write |
agent_learned_review | Review what the agent learned | write |
comment_rules_list | Comment auto-replies | read |
comment_rule_upsert | Add or edit an auto-reply rule | write |
comment_rule_toggle | Turn a rule on/off | write |
comment_rule_delete | Delete a rule | write |
comment_replies_suggest | Suggest reply variants | write |
supervisor_settings_get | Supervisors | read |
supervisor_settings_update | Add or edit a supervisor | write |
escalations_list | Pending questions | read |
escalation_get | Escalation | read |
escalation_reply | Answer an escalation | write |
escalation_followup | Send the customer follow-up | write |
agent_knowledge_review | Review agent knowledge | write |
routing_rules_list | Routing rules | read |
routing_test | Test routing (dry run) | read |
routing_decision_explain | Why was a message routed this way | read |
escalation_manage | Assign, reopen, resolve or close | write |
escalation_metrics | Escalation metrics | read |
agent_knowledge_search | Search agent knowledge | read |
agent_activity | Agent activity | read |
platform_services | Services status | read |
analytics_overview | Analytics overview | read |
usage_summary | Usage & points | read |
connections_status | Connections status | read |
scheduled_posts | Scheduled posts | read |
wa_templates | WhatsApp templates | read |
ads_overview | Ads platforms | read |
bio_get | Brand link page | read |
bio_update | Edit the brand link page | write |
agent_create | Create agent | write |
agent_permissions_get | Agent permissions | read |
agent_permissions_set | Change agent permissions | write |
approvals_list | Waiting approvals | read |
approval_decide | Approve or reject | write |
audit_list | Audit log | read |
plans_recommend | Which plan fits? | read |
Error codes & how we handle them
| Response | What it means to us |
|---|---|
200 + data | Success. |
200 without data, or not JSON | Endpoint not published yet (not an error) — re-checked on every sync. |
401 · 403 | Key rejected — the account owner sees “the API rejected the key”, the sync is marked failed, and we retry on the next daily cycle. |
404 · 405 · 501 | Endpoint doesn't exist yet (not an error). |
301 · 302 | Not followed — we show the target URL so it can be used instead. |
429 | We wait for Retry-After (up to 60 s) and retry once. |
5xx · timeout | Retried twice, then the sync is marked failed and retried automatically on the next cycle. |
Quick test
curl -H "Authorization: Bearer YOUR_API_KEY" -H "X-API-Key: YOUR_API_KEY" \
"https://api.example.com/v1/products?page=1&per_page=200"
curl -H "Authorization: Bearer YOUR_API_KEY" -H "X-API-Key: YOUR_API_KEY" \
"https://api.example.com/v1/customers?page=1&per_page=200&updated_since=2026-10-01T00:00:00Z"
curl -H "Authorization: Bearer YOUR_API_KEY" -H "X-API-Key: YOUR_API_KEY" \
"https://api.example.com/v1/orders?page=1&per_page=200"
# expected without a key: 401
curl -i "https://api.example.com/v1/products?page=1&per_page=1"
The connection card in Alaamah One also has a “Test connection” button that shows each endpoint's status before connecting.
Pre-launch checklist
- HTTPS URL on a public host, with no redirect.
- Read-only key, read from
Authorization: BearerorX-API-Key; no key returns401. - Each key returns only its own account's data (company or branch) — never another company's.
pageandper_pagework, andmeta.last_pageorhas_moreare correct.idvalues are stable across syncs.- Every item has at least one https image.
- Phones in international format
+966…where possible. - Dates in ISO 8601 with a timezone (
Zor+03:00). - Unpublished endpoints return
404.
Privacy
Your customer data stays in your brand’s account only, and the key is stored encrypted and never shown again. Disconnecting deletes the key and keeps what was imported (you can delete it). Tags and marketing consent set in Alaamah One are never overwritten by a sync.