Shipping API for delivery companies
Your company receives the orders of متاجرة stores automatically and reports each parcel back to the merchant and the customer.
Companies connected in Palestine: سندر Snder
1) How it works
- The متاجرة team gives you two keys: a test key (
mtj_test_…) and a live key (mtj_…). Each is shown once. - When a merchant sets an order to “out for delivery” or “shipped” and the customer chose your company, you get it: a webhook, or by polling
GET /orders. - You report its status with
POST /orders/{id}/status; the merchant and the customer see it at once. “delivered” completes the order in the store. - No developer? Everything here is also available by hand in the partners’ portal → “Deliveries”.
2) Base URL and authentication
Base URL: https://motajrh.com/api/carrier/v1
Header: Authorization: Bearer YOUR_KEY
Accept: application/json
Every answer is JSON. A wrong key answers 401. Limit: 240 calls a minute.
3) Test mode
With the test key you see and change test orders only; no store is touched. Create a test order (it is sent to your webhook too), then try every step on it:
POST https://motajrh.com/api/carrier/v1/test/orders → 201 { "order": { "id": 7, "order_number": "TEST-AB12CD", "test": true, … } }
GET https://motajrh.com/api/carrier/v1/me → { "company": "…", "mode": "test", "open_orders": 1, "zones": { … } }
With the live key this endpoint answers 403. Rates and settlements sent with the test key are checked, not saved.
4) List orders
GET https://motajrh.com/api/carrier/v1/orders?status=new&limit=50&after_id=0
| Parameter | Meaning |
|---|---|
| status | new, accepted, picked, out, delivered, returned, failed, cancelled |
| since | orders changed since this time (ISO 8601) |
| after_id | next page: the next_after_id of the previous answer |
| limit | 1 to 100 (default 50) |
{
"orders": [{
"id": 1024,
"order_number": "MA-10501",
"status": "new",
"tracking_number": null,
"cod_amount": 122, // to collect at the door (0 = already paid)
"currency": "ILS",
"test": false, "settled": false, "settlement_ref": null,
"created_at": "2026-10-08T12:10:00+03:00",
"store": { "id": 551, "name": "Magic Veil", "phone": "+970568823256", "url": "https://magicveil.motajrh.com",
"lat": 32.2211, "lng": 35.2544 },
"pickup": { "city": "نابلس", "district": "", "address": "…",
"lat": 32.2211, "lng": 35.2544 },
"recipient": { "name": "…", "phone": "+970568823256", "country": "فلسطين",
"zone": "wb", "zone_name": "الضفة الغربية", "region": "", "city": "نابلس",
"district": "", "address": "رفيديا، شارع الجامعة", "note": "",
"lat": 32.227, "lng": 35.242 },
"items": [{ "name": "…", "sku": "PRF1", "quantity": 1 }],
"order_total": 122, "delivery_fee": 20, "paid": false,
"quote_id": null, "distance_km": null, // if you price live: the quote the customer saw
"account": "store" // who pays you the fee: store = the store's account with you, platform = the platform's one account
}],
"has_more": false,
"next_after_id": null
}
Order fields: where each piece of data is
| Data | Field | Note |
|---|---|---|
| Our order number | id, order_number | id is the stable number used in URLs; order_number is what the merchant and customer see |
| Your own order number | tracking_number | you send it with your first status update; we keep it and show it to the merchant and the customer |
| Store id | store.id | a stable number for each store: keep the store's place under it once |
| Store name | store.name | |
| Store phone | store.phone | the pickup branch's phone |
| Store coordinates | store.lat, store.lng = pickup.lat, pickup.lng | the pickup branch as the merchant pinned it: decimal numbers (WGS84), latitude then longitude. null if not pinned yet |
| Store address | pickup.city, pickup.district, pickup.address | |
| Customer name | recipient.name | |
| Customer phone | recipient.phone | international format: +970 أو +972 ثم 9 أرقام تبدأ بـ 5 |
| Customer coordinates | recipient.lat, recipient.lng | the place the customer pinned at checkout. If you ask us for «customer location required» no order reaches you without it; otherwise it may be null |
| Customer address | recipient.city, district, address, note | note: the customer's note to the courier |
| Cash to collect | cod_amount, currency | 0 = already paid, collect nothing |
| Delivery fee | delivery_fee, quote_id, distance_km | what the customer paid for delivery. With live pricing: delivery_fee = your quote's price, and quote_id is its number |
| Contents | items[].name, sku, quantity |
Zones of Palestine in recipient.zone: wb الضفة الغربية، jer القدس، in48 الداخل (مناطق 48)، gaza قطاع غزة
5) One order
GET https://motajrh.com/api/carrier/v1/orders/1024 → { "order": { …, "history": [ { "status": "new", "at": "…", "by": "store" } ] } }
6) Update the status
POST https://motajrh.com/api/carrier/v1/orders/1024/status
Content-Type: application/json
{ "status": "picked", "tracking_number": "SN-778812", "note": "" }
| status | Meaning | In the store |
|---|---|---|
| accepted | you accepted it | logged on the order |
| picked | picked up from the store | logged; the customer sees the tracking number |
| out | out with the courier | logged |
| delivered | delivered | the order becomes “delivered”; the customer is told |
| failed | could not deliver (may be tried again) | the merchant is alerted |
| returned | returned to the store (final) | the merchant is alerted |
A closed order (delivered / returned / cancelled) answers 409. An unknown status answers 422.
7) Your prices and coverage
Send your prices: every merchant sees them when setting your company up and takes them with one click. A zone you leave out means you do not deliver there.
PUT https://motajrh.com/api/carrier/v1/rates
{ "country": "PS", "rates": [ { "zone": "wb", "price": 20, "days": "1-3" }, { "zone": "jer", "price": 30, "days": "1-3" }
] }
GET https://motajrh.com/api/carrier/v1/rates → { "rates": { "PS": { … } }, "zones": { "PS": { … } } }
8) Cash collected at the door
What you collected and have not paid to the stores yet, then telling us you paid. The merchant is alerted and confirms from the dashboard.
GET https://motajrh.com/api/carrier/v1/settlements
→ { "pending": [ { "id": 1024, "order_number": "MA-10501", "store": "Magic Veil", "cod_amount": 122, "currency": "ILS" } ],
"totals": { "ILS": 122 } }
POST https://motajrh.com/api/carrier/v1/settlements
{ "order_ids": [1024, 1031], "reference": "TRX-2026-0091" } → { "settled": 2 }
For companies that work by the map: location, live price, paid orders
Three options our team switches on for your company on request, each on its own:
| Option | What it does |
|---|---|
| Customer location required | A customer who chooses your company at checkout cannot finish before pinning their place on the map. So no order reaches you without recipient.lat and recipient.lng. |
| Live price | We call a quote endpoint of yours once the customer pins their place, and show the price you answer as it is. It can be «required» (no fixed price: no answer, your company is not offered for that order) or «optional» (no answer, the merchant's fixed price is shown). |
| Paid orders only | Your company is not offered with cash on delivery, and no order is sent to you before it is paid. So cod_amount is 0 in every order you receive. |
Your quote endpoint (you build it, we call it)
POST https://your-domain/…/shipping/quote
Authorization: Bearer <a key you give us>
Content-Type: application/json
{ "store_id": 551,
"pickup": { "lat": 32.2211, "lng": 35.2544 },
"dropoff": { "lat": 32.227, "lng": 35.242 } }
← 200 { "available": true, "quote_id": "q_8f2a1c", "price": 12, "currency": "ILS",
"distance_km": 3.4, "eta_minutes": 45, "expires_at": "2026-10-08T18:30:00+03:00" }
← 200 { "available": false } // outside your coverage: you are not offered for this order
- price is in the store's currency (ILS in Palestine) and shown to the customer as it is. An answer in another currency is ignored.
- quote_id, distance_km, eta_minutes and expires_at are optional. A quote_id is kept and returned in the order's quote_id, and delivery_fee equals price.
- We keep a quote for 10 minutes at most, never past expires_at minus two minutes; after that we ask again. So an order is always created with a price still valid on your side.
- We wait 6 seconds for your answer. Anything but 200, or without «available», counts as no answer.
- No quote without the store's place: if the merchant has not pinned their branch we do not call you.
Who pays the delivery fee?
Two ways; we agree on one with you (the «account» field in every order and quote request says which):
| account | Meaning |
|---|---|
| platform | One platform account with you: you deduct each order's fee from the platform's balance. We take it from the store's balance with us before sending the order, so no order reaches you unfunded. If the order is cancelled before pickup (order.cancelled) the fee is not charged. |
| store | Each store has its own account and balance with you and tops it up itself. store.id is what you link the store's account to; the merchant sees it in their dashboard. The platform is out of the money. |
9) Webhook — optional
Give the team an https:// link; we POST to it on every new order and when a merchant cancels one:
X-Motajrh-Event: order.created (or order.cancelled)
X-Motajrh-Delivery: 5521 (unique per delivery: use it to ignore a repeat)
X-Motajrh-Signature: hex( HMAC-SHA256( raw body, your webhook secret ) )
{ "event": "order.created", "order": { … the same object as in the list … } }
Check the signature and answer 200 within 8 seconds. If your server does not answer we try again after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, then 12 hours. The order always stays in GET /orders?status=new.
10) Quick example
curl -H "Authorization: Bearer YOUR_KEY" -H "Accept: application/json" \
"https://motajrh.com/api/carrier/v1/orders?status=new"
curl -X POST -H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
-d '{"status":"delivered"}' "https://motajrh.com/api/carrier/v1/orders/1024/status"
11) Errors
| 401 unauthorized | wrong or revoked key |
| 403 test_key_only | the endpoint takes the test key only |
| 404 not_found | not your order, or it does not exist |
| 409 closed | the order is closed |
| 422 | a wrong value (see errors or message) |
| 429 | more than 240 calls a minute |
اربطوا شركة التوصيل مع متاجرة
عبّئوا بيانات الشركة ويتواصل معكم فريقنا ويرسل لكم مفتاح الاختبار.