/ping—Check that a key works and see its mode (test or live).
{ "ok": true, "mode": "test", "merchant": { "id": "…", "name": "My Store" } }Developers
Available nowGet rates, create shipments, print labels and receive tracking updates — straight from your store or system.
In the merchant portal → Developers & API. Test keys work right away.
Test shipments are practice ones — a practice label and statuses that move by themselves; nothing is charged and no courier comes.
Verify your business, create a live key and swap the key — same URL, same code.
Base URL
https://os.ghayaex.com/v1/api
The same URL for test and live — the key decides.
Send your key in the Authorization header on every request. Each key only has the access you chose when creating it (for example: shipments read & write, cities read).
Authorization: Bearer gx_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Idempotency-Key: order-10045 # POST only — safe retries
Content-Type: application/json/ping—Check that a key works and see its mode (test or live).
{ "ok": true, "mode": "test", "merchant": { "id": "…", "name": "My Store" } }/ratesshipments:readYour prices from every carrier (cheapest first) before booking. Nothing is booked or charged.
{
"parcel": { "weightKg": 2 },
"cod": { "amount": 249 },
"receiverCity": "Jeddah"
}{
"object": "list",
"data": [
{ "carrier": "smsa", "name": { "ar": "سمسا", "en": "SMSA" }, "price": { "net": 22, "vat": 3.3, "total": 25.3, "currency": "SAR" } },
{ "carrier": "aramex", "name": { "ar": "أرامكس", "en": "Aramex" }, "price": { "net": 24, "vat": 3.6, "total": 27.6, "currency": "SAR" } }
]
}/shipmentsshipments:writeBook a shipment and get the AWB, the price and the label link. The sender is your pickup address. Send an Idempotency-Key so a retry never books twice.
{
"carrier": "aramex",
"reference": "order-10045",
"receiver": { "name": "Noura", "phone": "0551234567", "city": "Jeddah", "line1": "Al Andalus, Tahlia St 12" },
"parcel": { "weightKg": 2, "pieces": 1, "description": "Perfume" },
"cod": { "amount": 249 }
}{
"id": "6f0c2a1e-…",
"object": "shipment",
"mode": "live",
"awb": "44012345678",
"carrier": "aramex",
"status": "created",
"reference": "order-10045",
"cod": { "amount": 249, "currency": "SAR" },
"price": { "net": 24, "vat": 3.6, "total": 27.6, "currency": "SAR" },
"labelUrl": "https://os.ghayaex.com/v1/api/shipments/6f0c2a1e-…/label",
"createdAt": "2026-10-27T09:41:12.000Z",
"events": [ { "status": "created", "description": "Booked with carrier", "at": "2026-10-27T09:41:14.000Z" } ]
}/shipments/{id | awb}shipments:readCurrent status and full tracking history, by our id or the AWB.
{
"awb": "44012345678",
"status": "out_for_delivery",
"events": [
{ "status": "out_for_delivery", "location": "Jeddah", "at": "2026-10-28T08:30:00.000Z" },
{ "status": "in_transit", "location": "Jeddah hub", "at": "2026-10-28T06:15:00.000Z" },
{ "status": "picked_up", "location": "Riyadh", "at": "2026-10-27T13:02:00.000Z" }
],
…
}/shipments?status=delivered&page=1&pageSize=25shipments:readYour shipments, newest first, filtered by status, reference or date.
{ "object": "list", "data": [ … ], "page": 1, "pageSize": 25, "total": 1, "hasMore": false }/shipments/{id}/labelshipments:readDownload the label as a print-ready PDF.
%PDF-1.7 … (application/pdf)/shipments/{id}/cancelshipments:writeCancel before pickup; the price returns to your wallet.
{ "id": "6f0c2a1e-…", "status": "canceled", … }/carriersshipping-companies:readCarriers your account can book.
{ "object": "list", "data": [ { "code": "aramex", "name": { "ar": "أرامكس", "en": "Aramex" }, "cod": true, "cancelable": false } ] }/cities?q=jedcities:readCities in Arabic and English, optionally only those a carrier serves.
{ "object": "list", "data": [ { "name": { "en": "Jeddah", "ar": "جدة" } } ], "total": 1 }/accountwallet:readWallet and cash-on-delivery balances.
{ "object": "account", "wallet": { "balance": 512.4, "currency": "SAR" }, "cod": { "balance": 1890, "currency": "SAR" } }Add an HTTPS endpoint in the portal and choose the events you want. We send a POST with a JSON body, signed with your endpoint’s secret.
shipment.createdshipment.status_changedshipment.deliveredshipment.returnedshipment.canceledcod.settledwallet.low_balancePOST https://your-store.com/webhooks/ghaya
X-GHAYA-Event: shipment.delivered
X-GHAYA-Delivery: evt_3f9c…
X-GHAYA-Signature: t=1761559200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
{
"id": "evt_3f9c…",
"object": "event",
"type": "shipment.delivered",
"mode": "live",
"created": "2026-10-28T10:04:00.000Z",
"data": { "id": "6f0c2a1e-…", "awb": "44012345678", "reference": "order-10045", "status": "delivered", "previousStatus": "out_for_delivery", … }
}import crypto from 'node:crypto'
// rawBody = the exact request body as received (before JSON.parse)
export function verifyGhaya(rawBody, signatureHeader, secret) {
const p = Object.fromEntries(signatureHeader.split(',').map((x) => x.split('=')))
if (Math.abs(Date.now() / 1000 - Number(p.t)) > 300) return false // older than 5 min
const expected = crypto.createHmac('sha256', secret).update(`${p.t}.${rawBody}`).digest('hex')
return expected.length === p.v1.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(p.v1))
}Errors use standard HTTP status codes and a JSON body: { "error": { "code": "invalid", "message": "…", "fields": ["receiver.phone"] }, "requestId": "req_…" }. Every answer carries X-Request-Id — quote it to support.
| 400 | invalid · invalid_weight · bad_json |
| 401 | unauthorized · key_revoked · key_expired |
| 402 | insufficient_balance · outstanding_balance |
| 403 | forbidden_scope · ip_not_allowed |
| 404 | not_found · shipment_not_found |
| 409 | no_rate · not_cancelable · cancel_not_supported |
| 422 | carrier_rejected |
| 429 | rate_limited |
| 500 | internal |
Need a hand with your integration? Contact