A REST API for pushing orders in and reading/updating inventory, plus outgoing webhooks so your system hears about order and stock changes in real time. Works with any backend — React, PHP, Node.js, a Shopify custom app, or anything else — no Tijar Suite plugin required.
| Method | Endpoint | Description |
|---|---|---|
| GET | /ping | Confirm your API Key/Secret work |
| Method | Endpoint | Description |
|---|---|---|
| POST | /orders | Create an order |
| GET | /orders/:id | Get an order by id or your externalOrderId |
| PATCH | /orders/:id | Update an order |
| Method | Endpoint | Description |
|---|---|---|
| GET | /inventory | List inventory items (paginated, filterable) |
| GET | /inventory/:id | Get one item by id or SKU |
| PATCH | /inventory/:id/stock | Set or adjust stock for an item |
Generate these once from Integration Settings → Custom / Other Platform. Send them as headers on every request:
| Header | Value |
|---|---|
| X-Api-Key | Your API Key (safe to log; it identifies the credential, not a secret by itself) |
| X-Api-Secret | Your API Secret — shown once at generation time. Treat it like a password. |
A missing or invalid key/secret returns 401 UNAUTHORIZED. A disabled or revoked credential, or an inactive workspace, returns 403 FORBIDDEN. Both cases use the same response envelope documented under Errors, below.
HTTPS is required. Send every request to https://tijarsuite.tech/api/v1 over HTTPS — plain HTTP is not supported.
There is no separate "login" step and no token to refresh — the API Key and Secret are sent on every request, the same way on every call.
These endpoints call the exact same order logic the Tijar Suite dashboard itself uses — stock is deducted the same way, the same validation rules apply, and orders created here show up immediately in the dashboard.
POST /orders
| Field | Type | Description | |
|---|---|---|---|
| externalOrderId | string | optional | Your own order id. Used for duplicate detection — creating an order with an externalOrderId that already exists returns the existing order instead of a duplicate. |
| customer.name | string | required | Recipient name. |
| customer.phone | string | required | Recipient phone number. |
| shipping.address / .city / .zone / .area | string | optional | Delivery address. billing.* is accepted as a fallback if shipping is omitted. |
| items | array | required | Non-empty array of order items (see below). Every sku must already exist in your Tijar Suite inventory. |
| items[].sku | string | required | Must match an existing inventory item’s SKU/code. |
| items[].quantity | integer | required | A positive whole number. |
| items[].unitPrice | number | required | A non-negative number. |
| items[].name / .size | string | optional | Display name and variant size. |
| items[].discount | number | optional | A fixed amount taken off this line item. |
| orderTotal | number | required | The order grand total. |
| shippingCharge | number | optional | Delivery charge, default 0. |
| paymentMethod | string | optional | Free-text payment method label. |
| paymentStatus | "paid" | "unpaid" | "partial" | optional | If "partial", amountPaid is required and must be a positive number. |
| amountPaid | number | optional | Required when paymentStatus is "partial". |
| orderStatus | string | optional | One of PENDING, CONFIRMED, DISPATCHED, DELIVERED, COMPLETED, HOLD, RETURNED, EXCHANGED, CANCELLED. Defaults to PENDING. |
| orderDate | string | optional | Defaults to the current date. |
| notes | string | optional | Free-text order notes. |
Send an Idempotency-Key header to make retries safe — if a request with the same key and endpoint was already handled for your credential, the exact same response is replayed instead of creating a second order. This is independent from the externalOrderId duplicate check above; use either or both.
On success, responds 201 Created with the order (see response shape under Get Order, below). If externalOrderId already existed, responds 200 OK with the existing order and "duplicate": true.
GET /orders/:id
:id may be either the Tijar Suite order id or the externalOrderId you created it with.
Response shape (data):
| Field | Type | Description | |
|---|---|---|---|
| id | string | optional | Tijar Suite order id. |
| externalOrderId | string | null | optional | Your own order id, if provided. |
| status | string | optional | Current order status. |
| orderDate | string | optional | |
| customer | object | optional | { name, phone, email } |
| shipping | object | optional | { address, city, zone, area } |
| items | array | optional | [{ sku, name, quantity, unitPrice, discount }] |
| subtotal, discountType, discountValue, shippingCharge, orderTotal | number/string | optional | Order totals. |
| paymentMethod, amountPaid, amountDue | string/number | optional | Payment summary. |
| notes | string | null | optional | |
| createdAt, updatedAt | string | optional | ISO timestamps. |
PATCH /orders/:id
Accepts the same fields as Create Order, all optional — only fields you include are changed. externalOrderId and the order type cannot be changed once set. Changing orderStatus to a different value triggers an order.status_changed webhook (see Webhooks, below) in addition to order.updated.
Stock updates go through the same stock-movement system the dashboard uses, so every change is recorded in your stock history — nothing is silently overwritten.
GET /inventory
Pagination: ?page= (default 1) and ?limit= (default 50, max 200).
Filters: ?sku=, ?id=, ?category=, ?locationId=, ?updatedSince= (ISO date/time — returns items updated at or after this time). Archived items are excluded.
Response: { items: [...], total, page, pages }
GET /inventory/:id
:id may be either the internal item id or the product SKU/code.
Item shape (list items use the same shape):
| Field | Type | Description | |
|---|---|---|---|
| id | string | optional | Inventory item id. |
| sku | string | optional | Product code. |
| name | string | null | optional | |
| category | string | optional | |
| location | object | null | optional | { id, name } |
| type | "Single" | "Variable" | optional | |
| currentStock | number | optional | Total across all variants for a Variable item. |
| variants | array | optional | [{ size, stock }] — empty for a Single-type item. |
| stockStatus | "in_stock" | "out_of_stock" | optional | No finer-grained low-stock threshold exists. |
| price | number | optional | The item’s MRP. Cost price is never exposed by this API. |
| updatedAt | string | optional | ISO timestamp. |
PATCH /inventory/:id/stock
| Field | Type | Description | |
|---|---|---|---|
| mode | "set" | "adjust" | required | "adjust" applies quantity as a +/- delta. "set" treats quantity as the new absolute stock level. |
| quantity | integer | required | A whole number. Cannot be negative when mode is "set". |
| size | string | optional | Required if the item has variants (Variable type); ignored for Single-type items. |
| reason | string | optional | Freeform note, recorded on the stock movement. |
Response is the updated item (as above) plus movement: { previousStock, newStock, quantity, reason, actionType, timestamp } (or null if the requested change resulted in no change).
Configure a webhook URL from Integration Settings → Custom / Other Platform. Tijar Suite will POST an event to it whenever one of the events below occurs — the same Webhook Secret you already have (shown alongside your API Key) is used to sign every delivery, no separate secret needed.
| Event | Fired when |
|---|---|
| order.created | A new order is created through this API or the Tijar Suite dashboard. |
| order.updated | Any field on an order changes. |
| order.status_changed | An order’s status specifically changes (also fires alongside order.updated). |
| inventory.stock_changed | An item’s stock quantity changes, for any reason (order, return, manual adjustment via this API). |
The "Send Test Webhook" button in Integration Settings sends a webhook.test event with a fixed message payload — useful for confirming your endpoint and signature verification work before going live.
Every delivery is a JSON POST with this envelope:
Envelope{
"id": "evt_cjk3x8f2a0001qs08a1b2c3d4",
"type": "order.created",
"createdAt": "2026-10-01T09:12:00.000Z",
"data": { ... }
}order.created / order.updated
data{
"id": "order_id",
"externalOrderId": "your-order-123",
"status": "PENDING",
"orderTotal": 1250,
"recipientName": "Jane Doe",
"recipientPhone": "01700000000",
"itemCount": 2,
"updatedAt": "2026-10-01T09:12:00.000Z"
}order.status_changed
data{
...same fields as above,
"previousStatus": "PENDING",
"newStatus": "CONFIRMED"
}inventory.stock_changed — data{
"id": "inventory_item_id",
"sku": "TS-SHIRT-01",
"size": "M",
"previousStock": 20,
"newStock": 15,
"quantityChanged": -5,
"reason": "Order #1234"
}Each request carries these headers:
| Header | Value |
|---|---|
| X-Webhook-Id | Same as the envelope’s id — stable across retries of the same event. |
| X-Webhook-Timestamp | Unix timestamp (seconds) of this delivery attempt. |
| X-Webhook-Signature | Hex-encoded HMAC-SHA256 signature. |
The signature is computed as HMAC-SHA256(webhookSecret, "{timestamp}.{raw JSON body}"). To verify a delivery: read the raw request body (don’t re-serialize it — use the exact bytes received), concatenate the timestamp header, a period, and the raw body, then HMAC it with your Webhook Secret and compare the result to X-Webhook-Signature using a constant-time comparison. See the Node.js and PHP examples below.
A delivery is considered successful on any 2xx response, with a 10-second timeout per attempt. A non-2xx response or timeout schedules a retry — up to 5 attempts total, spaced 1 minute, 5 minutes, 15 minutes, 1 hour, then 6 hours apart. After the 5th failed attempt the delivery is marked exhausted and is not retried again.
Duplicate protection: X-Webhook-Id (and the envelope’s id) stays the same across every retry of the same event — store ids you’ve already processed and skip repeats, in case a retry arrives after your first response was lost in transit rather than genuinely failing.
Delivery status, HTTP response status and attempt count for every event are visible in Integration Settings → Custom / Other Platform → Recent Deliveries.
Every error response has this shape:
Error response{
"success": false,
"error": {
"code": "INVALID_SKU",
"message": "Unknown SKU(s): TS-DOES-NOT-EXIST",
"requestId": "req_a1b2c3d4e5f6"
}
}Every response — success or error — also carries the request id as an X-Request-Id header. Quote it when contacting support about a specific call.
| Status | Code(s) | Meaning |
|---|---|---|
| 400 | MALFORMED_REQUEST / MISSING_FIELD | The request body is not valid JSON, or a required field is missing. |
| 401 | UNAUTHORIZED | The X-Api-Key / X-Api-Secret headers are missing, or the credentials are invalid. |
| 403 | FORBIDDEN | The credential has been disabled, or the workspace is not active. |
| 404 | NOT_FOUND | The order or inventory item does not exist. |
| 409 | — | Reserved for future use. No endpoint currently returns 409 — creating an order with an externalOrderId that already exists returns the existing order with 200 instead (see Idempotency, below). |
| 422 | INVALID_SKU / INVALID_QUANTITY / INVALID_PRICE / INVALID_STATUS | A field is present but has an invalid value (e.g. an unknown SKU, a negative quantity). |
| 429 | RATE_LIMITED | Too many requests. The limit is 300 requests per 15 minutes per API credential. |
| 500 | INTERNAL_ERROR | An unexpected server error. Safe to retry; contact support if it persists. |
cURLcurl https://tijarsuite.tech/api/v1/ping \
-H "X-Api-Key: tsk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "X-Api-Secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Node.js (fetch)const res = await fetch('https://tijarsuite.tech/api/v1/ping', {
headers: {
'X-Api-Key': process.env.TIJAR_API_KEY,
'X-Api-Secret': process.env.TIJAR_API_SECRET,
},
});
const json = await res.json();
console.log(json); // { success: true, data: { message: 'Connection successful', requestId: '...' } }PHP (cURL)$ch = curl_init('https://tijarsuite.tech/api/v1/ping');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-Api-Key: ' . getenv('TIJAR_API_KEY'),
'X-Api-Secret: ' . getenv('TIJAR_API_SECRET'),
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);cURLcurl -X POST https://tijarsuite.tech/api/v1/orders \
-H "X-Api-Key: tsk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "X-Api-Secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-123-attempt-1" \
-d '{
"externalOrderId": "order-123",
"customer": { "name": "Jane Doe", "phone": "01700000000" },
"shipping": { "address": "123 Road", "city": "Dhaka", "zone": "Gulshan", "area": "Gulshan 1" },
"items": [
{ "sku": "TS-SHIRT-01", "quantity": 2, "unitPrice": 850 }
],
"orderTotal": 1700,
"paymentStatus": "unpaid"
}'Node.js (fetch)const res = await fetch('https://tijarsuite.tech/api/v1/orders', {
method: 'POST',
headers: {
'X-Api-Key': process.env.TIJAR_API_KEY,
'X-Api-Secret': process.env.TIJAR_API_SECRET,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-123-attempt-1',
},
body: JSON.stringify({
externalOrderId: 'order-123',
customer: { name: 'Jane Doe', phone: '01700000000' },
shipping: { address: '123 Road', city: 'Dhaka', zone: 'Gulshan', area: 'Gulshan 1' },
items: [{ sku: 'TS-SHIRT-01', quantity: 2, unitPrice: 850 }],
orderTotal: 1700,
paymentStatus: 'unpaid',
}),
});
const order = await res.json();PHP (cURL)$body = [
'externalOrderId' => 'order-123',
'customer' => ['name' => 'Jane Doe', 'phone' => '01700000000'],
'shipping' => ['address' => '123 Road', 'city' => 'Dhaka', 'zone' => 'Gulshan', 'area' => 'Gulshan 1'],
'items' => [['sku' => 'TS-SHIRT-01', 'quantity' => 2, 'unitPrice' => 850]],
'orderTotal' => 1700,
'paymentStatus' => 'unpaid',
];
$ch = curl_init('https://tijarsuite.tech/api/v1/orders');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_HTTPHEADER => [
'X-Api-Key: ' . getenv('TIJAR_API_KEY'),
'X-Api-Secret: ' . getenv('TIJAR_API_SECRET'),
'Content-Type: application/json',
'Idempotency-Key: order-123-attempt-1',
],
]);
$order = json_decode(curl_exec($ch), true);
curl_close($ch);cURLcurl "https://tijarsuite.tech/api/v1/inventory?page=1&limit=50&category=Shirts" \
-H "X-Api-Key: tsk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "X-Api-Secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Node.js (fetch)const url = new URL('https://tijarsuite.tech/api/v1/inventory');
url.searchParams.set('page', '1');
url.searchParams.set('limit', '50');
const res = await fetch(url, {
headers: {
'X-Api-Key': process.env.TIJAR_API_KEY,
'X-Api-Secret': process.env.TIJAR_API_SECRET,
},
});
const { data } = await res.json();
console.log(data.items, data.total, data.pages);PHP (cURL)$ch = curl_init('https://tijarsuite.tech/api/v1/inventory?page=1&limit=50');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-Api-Key: ' . getenv('TIJAR_API_KEY'),
'X-Api-Secret: ' . getenv('TIJAR_API_SECRET'),
],
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($result['data']['items'] as $item) {
echo $item['sku'] . ': ' . $item['currentStock'] . "\n";
}This runs on your own server, in the handler that receives Tijar Suite’s webhook POSTs — there’s no outbound API call here, just a signature check against the raw request body.
Node.js (Express)const crypto = require('crypto');
app.post('/webhooks/tijar-suite', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.headers['x-webhook-timestamp'];
const signature = req.headers['x-webhook-signature'];
const rawBody = req.body.toString('utf8'); // raw bytes, not re-serialized JSON
const expected = crypto
.createHmac('sha256', process.env.TIJAR_WEBHOOK_SECRET)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const valid = signature &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!valid) return res.status(401).send('Invalid signature');
const event = JSON.parse(rawBody);
console.log('Received event:', event.id, event.type);
// ...handle event.type / event.data, then:
res.status(200).send('ok');
});PHP$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$rawBody = file_get_contents('php://input'); // raw bytes, not re-encoded JSON
$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, getenv('TIJAR_WEBHOOK_SECRET'));
if (!$signature || !hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($rawBody, true);
// ...handle $event['type'] / $event['data'], then:
http_response_code(200);
echo 'ok';This page requires JavaScript to run. Please enable JavaScript in your browser and reload the page.