API Reference

Introduction

Toome Developer API is a REST API that lets any external system integrate with the Toome platform. You can use it to send WhatsApp notifications, receive orders from external stores, and manage customer data in the CRM — all authenticated through a secure Bearer Token issued to each registered application.

Base URL: https://toome.cloud/api/v1
All requests and responses are in JSON. Every request must include the headers Content-Type: application/json and Accept: application/json.
Prefer testing in Postman? Import the full Toome collection — every endpoint, organized into folders, ready to run.
Run in Postman or copy the collection URL

Authentication

The API uses the OAuth2 Client Credentials flow, the pattern designed for server-to-server connections where no end user is involved in the authentication process. Steps to get started:

  1. Register a new application from the Developer Portal to get your client_id and client_secret.
  2. Send a POST /api/v1/token request with these credentials to obtain an access_token.
  3. Attach the access token to every subsequent request in the header: Authorization: Bearer {token}.
Access tokens have a limited lifetime. When a token expires, the server returns 401 Unauthorized, and you must issue a new one.

Scopes

Each application is granted a specific set of scopes when it's created. No request can exceed the scopes assigned to it.

ScopeGrants
notifySend WhatsApp notifications via Meta-approved message templates
send_notificationsSend WhatsApp order-status notifications (confirmed, shipped, delivered...) via Toome's built-in templates — consumes credits per message
orders.writeCreate external orders and update their status in Toome
orders.readRetrieve and view stored orders
customers.writeAdd new customers and edit their data in the CRM
customers.readRetrieve customer data with search and filtering support
brain.chatInteract with the AI engine linked to your account

Error Codes

The API returns standard HTTP status codes along with a JSON body containing error details.

HTTP CodeMeaning
401Access token missing or expired — issue a new token
402INSUFFICIENT_CREDITS — account balance is not enough to complete the operation — check balance and required in the response
403The application doesn't have the required scope, or the feature is disabled (FEATURE_DISABLED) — contact support to enable it
404The requested resource doesn't exist (e.g. an undefined message template)
422Validation error on the submitted data — check the errors field in the response
429You've exceeded the allowed request rate — wait before retrying
502Message delivery failed via the Meta API — may be a temporary WhatsApp-side issue
Endpoints

POST /token

Issue an access token using the application's credentials. This token is used in the Authorization header for all subsequent API requests.

POST /api/v1/token

Request Body

FieldTypeRequiredDescription
client_idstringrequiredYour application's unique identifier — starts with tm_
client_secretstringrequiredYour application's secret key — keep it in environment variables, never in code
curl -X POST https://toome.cloud/api/v1/token \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "client_id": "tm_xxxxxxxxxxxxxxxx",
    "client_secret": "your_secret_here"
  }'
$response = Http::post('https://toome.cloud/api/v1/token', [
    'client_id'     => 'tm_xxxxxxxxxxxxxxxx',
    'client_secret' => 'your_secret_here',
]);
$token = $response->json('access_token');
import requests
res = requests.post('https://toome.cloud/api/v1/token', json={
    'client_id': 'tm_xxxxxxxxxxxxxxxx',
    'client_secret': 'your_secret_here'
})
token = res.json()['access_token']
200 { "access_token": "...", "token_type": "Bearer", "scopes": ["notify","orders.write"] }
401 { "error": "invalid_client", "message": "Invalid credentials" }

GET /test-connection

Verify that an access token is valid and inspect the scopes granted to the application. Useful for diagnostics and confirming a successful integration before launch.

GET/api/v1/test-connection
curl https://toome.cloud/api/v1/test-connection \
  -H "Authorization: Bearer {your_token}"
200 { "status":"success", "app":{"name":"My App","tenant_id":42}, "scopes":{...} }

GET /templates

Retrieve all Meta-approved message templates available on your account. A template message can only be sent once it appears in this list with an approved status.

GET/api/v1/templates
Templates are created from inside the Toome dashboard and submitted to Meta for review automatically. Once approved, they appear here ready to use — no extra action required from the developer.
200
{
  "success": true,
  "count": 2,
  "data": [
    {
      "id": 5,
      "name": "order_confirmed",
      "language": "en",
      "category": "UTILITY",
      "body": "Hi 1, your order #2 has been confirmed ✅",
      "variables_count": 2
    }
  ]
}

POST /notify

Send a WhatsApp template message to any phone number — even with no prior conversation with the user. Use it for transactional notifications like order confirmations, appointment reminders, and shipping alerts. Requires the notify scope.

POST/api/v1/notify

Request Parameters

FieldTypeRequiredDescription
phonestringrequiredPhone number in international format without + (example: 201012345678)
template_namestringrequiredApproved template name as it appears in GET /templates
variablesarrayoptionalDynamic variable values in order, matching {{1}}, {{2}}, ...
languagestringoptionalLanguage code (default: ar)
curl -X POST https://toome.cloud/api/v1/notify \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "201012345678",
    "template_name": "order_confirmed",
    "variables": ["Ahmed", "#1042", "150 EGP"]
  }'
Http::withToken($token)->post('/api/v1/notify', [
    'phone'         => '201012345678',
    'template_name' => 'order_confirmed',
    'variables'     => ['Ahmed', '#1042', '150 EGP'],
]);
requests.post('/api/v1/notify',
    headers={'Authorization': f'Bearer {token}'},
    json={
        'phone': '201012345678',
        'template_name': 'order_confirmed',
        'variables': ['Ahmed', '#1042', '150 EGP']
    }
)
200 { "success": true, "message": "Notification sent successfully.", "phone": "201012345678", "template_used": "order_confirmed" }
404 { "error": "template_not_found", "message": "Template not found or not yet approved by Meta." }

POST /notifications/order

Send a ready-made WhatsApp notification for a specific order status using Toome's Meta-approved templates. No prior conversation with the customer is required — the message always gets through. Consumes credits from your balance on every successful send. Requires the send_notifications scope plus the Order Notifications feature enabled by the admin.

POST/api/v1/notifications/order
Credit consumption: Credits are only deducted once the message is actually delivered via WhatsApp. If the message fails for a technical reason, no credits are consumed.
Order confirmation: 0.5 credit — status updates: 0.3 credit (values configurable by the admin).

Request Parameters

FieldTypeRequiredDescription
phonestringrequiredCustomer's phone number in international format without + (example: 201012345678)
statusstringrequiredOrder status — see the available values below
trackingstringrequiredThe order number or tracking code in your system
customer_namestringoptionalCustomer name shown in the message
store_namestringoptionalStore or platform name shown in the message
totalstringoptionalOrder total as text (example: "350 EGP") — shown in the confirmation message only

Available status values

ValueMeaningCredits
order_confirmedOrder confirmed ✅0.5
assignedDelivery agent assigned0.3
out_for_deliveryOrder out for delivery 🚗0.3
at_dropoff_pointArrived at drop-off point0.3
deliveredDelivered ✅0.3
failed_deliveryDelivery failed ❌0.3
rescheduledDelivery rescheduled0.3
returnedReturned0.3
curl -X POST https://toome.cloud/api/v1/notifications/order \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "phone":         "201012345678",
    "status":        "order_confirmed",
    "tracking":      "ORD-1042",
    "customer_name": "Ahmed Ali",
    "store_name":    "My Store",
    "total":         "350 EGP"
  }'
Http::withToken($token)->post('https://toome.cloud/api/v1/notifications/order', [
    'phone'         => $order->customer_phone,
    'status'        => 'order_confirmed',
    'tracking'      => $order->reference,
    'customer_name' => $order->customer_name,
    'store_name'    => 'My Store',
    'total'         => $order->total . ' EGP',
]);
requests.post('https://toome.cloud/api/v1/notifications/order',
    headers={'Authorization': f'Bearer {token}'},
    json={
        'phone':         '201012345678',
        'status':        'out_for_delivery',
        'tracking':      'ORD-1042',
        'customer_name': 'Ahmed Ali',
    }
)
200
{
  "success": true,
  "message_id": "wamid.HBgM...",
  "status_sent": "order_confirmed",
  "credits_charged": 0.5,
  "balance_after": 49.5
}
402 { "error": "INSUFFICIENT_CREDITS", "balance": 0.2, "required": 0.5 }
403 { "error": "FEATURE_DISABLED", "message": "Order notifications feature is not enabled for this application" }

POST /orders

Receive an order from your external system and save it in Toome, with the option to notify the customer via WhatsApp at the same time. Requires the orders.write scope.

POST/api/v1/orders

Request Parameters

FieldTypeRequiredDescription
order_refstringrequiredUnique order number in your external system
customer_phonestringrequiredCustomer's phone number in international format
customer_namestringoptionalCustomer's full name
totalnumberoptionalTotal order value
currencystringoptionalISO 4217 currency code (example: EGP, SAR, USD)
itemsarrayoptionalList of products — each item: {name, qty, price}
statusstringoptionalOrder status: pending | confirmed | shipped | cancelled
notify_templatestringoptionalName of the notification template to send the customer as soon as the order is recorded
notify_variablesarrayoptionalTemplate variable values in the order they appear in the message body
metaobjectoptionalCustom extra data stored alongside the order for later reference
curl -X POST https://toome.cloud/api/v1/orders \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "order_ref": "ORD-1042",
    "customer_phone": "201012345678",
    "customer_name": "Ahmed Ali",
    "total": 350.00,
    "currency": "EGP",
    "items": [
      {"name": "White T-Shirt", "qty": 2, "price": 175}
    ],
    "notify_template": "order_confirmed",
    "notify_variables": ["Ahmed", "ORD-1042"]
  }'
Http::withToken($token)->post('/api/v1/orders', [
    'order_ref'        => 'ORD-1042',
    'customer_phone'   => '201012345678',
    'total'            => 350.00,
    'notify_template'  => 'order_confirmed',
    'notify_variables' => ['Ahmed', 'ORD-1042'],
]);
201 { "success": true, "order_id": 7, "order_ref": "ORD-1042", "notified": true }

GET /orders

Retrieve the list of external orders stored in Toome, with pagination support. Requires the orders.read scope.

GET/api/v1/orders?per_page=20

POST /customers

Add a new customer to Toome's CRM, or update their data if a customer with the same phone number already exists. Requires the customers.write scope.

POST/api/v1/customers

Request Parameters

FieldTypeRequiredDescription
phonestringrequiredPhone number — used as the customer's unique identifier in the system
namestringoptionalCustomer's full name
emailstringoptionalEmail address
genderstringoptionalmale | female | unknown
sourcestringoptionalAcquisition source (example: shopify, website, referral)
statusstringoptionalCustomer stage in the sales pipeline: lead | interested | customer
curl -X POST https://toome.cloud/api/v1/customers \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "201012345678",
    "name": "Ahmed Ali",
    "email": "ahmed@example.com",
    "source": "shopify",
    "status": "customer"
  }'
201 { "success": true, "action": "created", "customer": {"id":15,"name":"Ahmed Ali","phone":"201012345678"} }
200 { "success": true, "action": "updated", ... }

GET /customers

Retrieve the customer list with text search, status filtering, and pagination support. Requires the customers.read scope.

GET/api/v1/customers?status=customer&search=ahmed&per_page=20
Outbound Webhooks

Overview

Instead of polling GET /customers, you can register a callback_url in your app's settings (Webhook tab) and Toome will push events to it in real time as they happen — including customers your AI agent captures on its own during a conversation (for example, a visitor who chats with your floating widget and shares their name and phone number naturally — Toome creates/updates a real customer record for them and fires this webhook automatically, with no extra code on your side).

Configure this from Developer Platform → your app → Webhook tab: set your callback_url, tick the events you want to receive, and copy your webhook_secret (auto-generated the first time you open that tab). A "Send test event" button is available there to confirm your endpoint is reachable before going live.

Available Events

EventFires when
customer.createdA new customer record is created for your tenant — via POST /customers, or automatically when the AI widget captures a new visitor's lead data during chat.
customer.updatedAn existing customer's name, phone, email, address, or profile_data changes.
order.createdA new order or service order is created for your tenant — including one the AI widget opens automatically when a visitor confirms a purchase (products, with a shipping address) or a service interest (a pending inquiry, no address needed) that maps to a specific item in your catalog.

  The Webhook tab lets you tick other events too (message.received, order.updated, etc.) for future use — today only customer.created, customer.updated, and order.created actually fire. Ticking the others has no effect yet.

Verifying the Signature

Every request carries an X-Toome-Signature header: sha256=<hmac>, an HMAC-SHA256 of the raw JSON body using your webhook_secret. Verify it before trusting the payload:

// In your webhook route handler
$payload   = $request->getContent();
$expected  = 'sha256=' . hash_hmac('sha256', $payload, $webhookSecret);
$signature = $request->header('X-Toome-Signature');

// constant-time compare, never ===
if (!hash_equals($expected, $signature)) {
    abort(401);
}

$event = $request->header('X-Toome-Event'); // e.g. "customer.created"
$data  = $request->json()->all();

Example payload for customer.created / customer.updated:

{ "event": "customer.created", "app_id": "tm_xxxxx", "timestamp": "2026-08-28T21:28:02+03:00", "data": { "id": 1913, "name": "محمد سالم", "phone": "+962791234567", "email": null, "platform_source": "widget", "profile_data": { "interest": "..." }, "created_at": "..." } }

If you configured customer_fields_out in the Scopes tab, only those fields (plus id) are included in data. Delivery retries up to 3 times with backoff if your endpoint doesn't return a 2xx response.

Use Cases

E-commerce store integration

When a new order arrives from a Shopify webhook, forward it to Toome to save it and instantly notify the customer via WhatsApp:

// Shopify webhook handler
$order = $request->json()->all();

Http::withToken($token)->post('https://toome.cloud/api/v1/orders', [
    'order_ref'        => $order['order_number'],
    'customer_phone'   => $order['phone'],
    'customer_name'    => $order['customer']['first_name'],
    'total'            => $order['total_price'],
    'notify_template'  => 'order_confirmed',
    'notify_variables' => [$order['customer']['first_name'], $order['order_number']],
]);

Booking confirmations

Right after a booking is confirmed in your system, send the notification template directly to the customer without needing any prior conversation:

Http::withToken($token)->post('https://toome.cloud/api/v1/notify', [
    'phone'         => $booking->customer_phone,
    'template_name' => 'booking_confirmed',
    'variables'     => [
        $booking->customer_name,
        $booking->service_name,
        $booking->date->format('Y-m-d H:i'),
    ],
]);

Automatic customer import

When a user completes registration on your site, add them to Toome's CRM automatically so the AI engine can start engaging with them right away:

// After a user completes registration on your site
Http::withToken($token)->post('https://toome.cloud/api/v1/customers', [
    'phone'  => $user->phone,
    'name'   => $user->name,
    'email'  => $user->email,
    'source' => 'website_registration',
    'status' => 'lead',
]);
Toome Auth

🔐 One-Time Password (OTP) verification service

Toome Auth is a standalone service for sending verification codes (OTP) over WhatsApp — and soon SMS. It lets developers add a secure verification layer to any app or website in a few simple steps. The service supports two integration paths:

Integration Path Best For Route Prefix Auth Mechanism
Type A — TenantApp A store or app owner with an active Toome subscription who has enabled the OTP feature /api/v1/otp/* Bearer Token (via POST /token + otp scope)
Type B — Standalone OTP App A standalone app dedicated purely to verification, with no Toome subscription needed /api/auth/v1/otp/* Authorization: Bearer {api_key} + X-App-Secret

This section covers Type B (the standalone app). If you're using Type A, see the Authentication section and make sure the otp scope is enabled.

Toome Auth OTP Authentication

Every request on this path must include two HTTP headers: one carrying your application's API key, and the other its shared App Secret to verify your identity.

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx
X-App-Secret: your_app_secret_here

You can find your API Key and App Secret in the Developer Portal → OTP Apps → View Keys. The application must be in active status (after being reviewed and approved by the admin team).

Key security: Never embed the API Key or App Secret in frontend code (JavaScript/Mobile). All OTP requests must be made exclusively from your backend server.

POST /api/auth/v1/otp/send

Send an OTP verification code to a phone number over WhatsApp. Returns a unique request_id used in the following steps.

Free WhatsApp verification (default and mandatory for the WhatsApp channel)
Instead of immediately sending a paid template message, the response returns a deep_link_url — a wa.me link with a pre-filled message. Prompt the user to open it and tap "Send" inside WhatsApp, and their reply reaches us as a normal free message, which we verify instantly via webhook (at no cost to you). If the user doesn't reply within fallback_after_seconds seconds, we automatically send the regular paid template message as a fallback — meaning verification is guaranteed either way, and cost only applies when the template is actually needed. Track the status in real time with GET /otp/status/{request_id} instead of waiting for the user to type a code — on the free verification path, they won't type anything at all.
POST /api/auth/v1/otp/send
Authorization: Bearer sk_live_xxxx
X-App-Secret: your_secret

{
  "phone":   "201012345678",
  "channel": "whatsapp",
  "locale":  "en",
  "metadata": { "order_id": "ORD-789" }
}
{
  "success":                true,
  "request_id":              "req_01JXXXXXXXXXXXXXXXXXXX",
  "channel":                 "whatsapp",
  "phone":                   "+201012345678",
  "expires_in":               600,
  "expires_at":               "2026-06-12T14:05:00Z",
  "verification_method":      "whatsapp_deeplink",
  "deep_link_url":            "https://wa.me/9687xxxxxxx?text=TOOME-VERIFY%3Areq_01JXXX...",
  "fallback_after_seconds":   25
}
$res = Http::withHeaders([
    'Authorization' => 'Bearer ' . $apiKey,
    'X-App-Secret'  => $appSecret,
])->post('https://toome.cloud/api/auth/v1/otp/send', [
    'phone'  => $user->phone,
    'locale' => 'en',
]);
$requestId = $res->json('request_id');
$deepLink  = $res->json('deep_link_url'); // show this as a "Verify via WhatsApp" button in your UI
FieldTypeDescription
phone *stringPhone number in E.164 format without the + sign (example: 201012345678)
channelstringDelivery channel: whatsapp (default) | sms
localestringLanguage of the fallback OTP template message: ar (default) | en
metadataobjectOptional contextual data returned to you upon successful verification (example: order number, session id)

The fields verification_method, deep_link_url, and fallback_after_seconds are only returned when channel = whatsapp. The sms channel doesn't have a free path yet (only the base fields are returned).

POST /api/auth/v1/otp/verify

Verify the code entered by the user. On success, the code is invalidated immediately and can't be reused. The request returns success: true along with any metadata attached at send time (including verified_via set to template). This route is only for the paid fallback case — if you're using free WhatsApp verification, watch GET /otp/status/{request_id} instead.

{
  "request_id": "req_01JXXXXXXXXXXXXXXXXXXX",
  "code":       "482917"
}
// success
{
  "success":  true,
  "phone":    "201012345678",
  "metadata": { "order_id": "ORD-789", "verified_via": "template" },
  "verified_at": "2026-06-12T14:03:22Z"
}

// wrong code
{
  "success":    false,
  "error_code": "INVALID_CODE",
  "attempts_remaining": 2
}
$res = Http::withHeaders([
    'Authorization' => 'Bearer ' . $apiKey,
    'X-App-Secret'  => $appSecret,
])->post('https://toome.cloud/api/auth/v1/otp/verify', [
    'request_id' => $requestId,
    'code'       => $userInput,
]);
if ($res->json('success')) {
    // Verified — continue user registration or grant access
}

POST /api/auth/v1/otp/resend

Resend an OTP code to the same number using the original request_id. The cooldown period between sends (60 seconds by default) must elapse before this request is accepted.

{
  "request_id": "req_01JXXXXXXXXXXXXXXXXXXX"
}

If this is called before the cooldown period ends, the server returns the error COOLDOWN_ACTIVE along with a retry_after field indicating the remaining seconds.

GET /api/auth/v1/otp/status/{request_id}

Poll the current status of an OTP request — its primary use is the free WhatsApp verification path: the user won't type a code in your UI, so there's nothing to call /otp/verify with. Call this endpoint every 2-3 seconds after showing the user the deep_link_url, until status becomes verified.

GET /api/auth/v1/otp/status/req_01JXXXXXXXXXXXXXXXXXXX
Authorization: Bearer sk_live_xxxx
X-App-Secret: your_secret
// still waiting for the user's reply on WhatsApp
{ "success": true, "found": true, "status": "pending", "verified_via": null }

// verified for free via a WhatsApp reply
{ "success": true, "found": true, "status": "verified", "verified_via": "webhook" }

// verified via the template code (after calling /otp/verify)
{ "success": true, "found": true, "status": "verified", "verified_via": "template" }

// expired or not found
{ "success": false, "found": false, "status": "not_found" }
ValueMeaning
status: pendingStill waiting — either the WhatsApp reply or the fallback timeout
status: verifiedVerified — proceed with user registration immediately
status: failedThe fallback message failed to send (rare) — try /otp/resend
verified_viawebhook (free) or template (paid) — informational only, no difference in outcome

DELETE /api/auth/v1/otp/{request_id}

Cancel an OTP request and invalidate its associated code immediately. Use it when the user verifies through an alternate method, backs out of the flow, or their session ends before verification completes.

DELETE /api/auth/v1/otp/req_01JXXXXXXXXXXXXXXXXXXX

GET /api/auth/v1/otp/logs

Retrieve a historical log of OTP requests, with filtering by phone number and status, plus pagination support. Useful for review and auditing purposes.

GET /api/auth/v1/otp/logs?status=verified&per_page=20&page=1
{
  "data": [
    {
      "request_id": "req_01JXXX",
      "phone":      "201012345678",
      "channel":    "whatsapp",
      "status":     "verified",
      "created_at": "2026-06-12T14:00:00Z"
    }
  ],
  "meta": { "total": 148, "per_page": 20, "current_page": 1 }
}

Available values for filtering by the status field: pending (pending) | verified (verified) | expired (expired) | cancelled (cancelled) | failed (send failed)

GET /api/auth/v1/apps/stats

Retrieve OTP app performance statistics for the last 30 days, broken down by channel.

{
  "total_sent":     1420,
  "total_verified": 1184,
  "verify_rate":    "83.4%",
  "today":          47,
  "by_channel": {
    "whatsapp": { "sent": 1400, "verified": 1170 },
    "sms":      { "sent": 20,   "verified": 14   }
  }
}

Test Phones

This feature lets you register up to 10 phone numbers with fixed verification codes for testing purposes. When an OTP is sent to any of these numbers:

  • No actual WhatsApp message is sent.
  • No credits or tokens are consumed.
  • The fixed code you pre-configured, stored in Redis, is returned.
  • The feature works in both Live and Sandbox modes.

You can manage these numbers from the Developer Portal → OTP Apps → View App → Test Phones section. The response includes a test_phone: true field for every request to a test number.

Test phones are meant for development environments only. When you go live, no code change is needed — just remove the numbers from the list, or stop sending to them.

OTP Error Codes

The OTP API returns a specific text error_code to make handling different error cases easier programmatically.

error_codeHTTP CodeCause & Suggested Handling
MISSING_CREDENTIALS401The Authorization or X-App-Secret header is missing from the request
INVALID_API_KEY401The API key doesn't exist in the system — verify the key is correct
INVALID_SECRET401App Secret doesn't match — check your application's credentials in the Developer Portal
APP_PENDING_REVIEW403The application is still under review and hasn't been activated yet
APP_REJECTED403The application request was rejected — contact support for details
RATE_LIMITED429Exceeded the allowed rate (60 requests/minute per application)
COOLDOWN_ACTIVE429The cooldown period between sends hasn't elapsed — check retry_after in the response
INVALID_CODE422The entered code is incorrect — check attempts_remaining for how many tries are left
OTP_EXPIRED410The OTP code has expired — send a new request via POST /otp/send
OTP_ALREADY_VERIFIED409This code has already been verified — it can't be used again
NOT_FOUND404The request_id doesn't exist or has expired in the database