Public API
REST API
Base path /api/v1. Authenticate with a store API key (wjh_…). All responses use the same JSON envelope: status, message, and data.
Base URL & envelope
Call the API on your Wijha API host under /api/v1. Successful and error responses share this shape:
{
"status": 200,
"message": "ok",
"data": { }
}HTTP status matches the status field (e.g. 201 for creates, 401/403/429 for auth and rate limits).
Authentication
Create an API key in Merchant Portal → Store settings → Developers → API keys. Send it with either header:
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# or
x-api-key: wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxKeys are shown once at creation. Prefix format is always wjh_.
Rate limits
Limits are enforced per authenticated API key (and a separate IP pre-cap before auth):
- 120 requests per minute per API key
- 300 requests per minute per IP (pre-auth)
{
"status": 429,
"message": "Too many requests",
"data": null,
"code": "RATE_LIMITED"
}Scopes
Each key is granted one or more scopes. Missing scope → 403.
- orders.crud — create, read, update, cancel orders
- products.crud — create, read, update, delete products
- collections.crud — create, read, update, delete collections
- expenses.crud — create, read, update, delete expenses
- store.read — read store profile
- customers.read — read customers (customers.crud also grants read)
- analytics.read — read analytics hub
- discounts.read — read discount codes
- reviews.read — read product reviews
- shipping_fees.read — read client shipping fees by zone
- balance.read — read shipping / wallet balance
- webhooks.read — list webhook endpoints and events
- webhooks.write — create webhook endpoints
Enums reference
String enums returned and accepted by /api/v1. Use exact values (case-sensitive).
Order status (order.status)
| Value | Meaning |
|---|---|
| pending | New order; not yet ready for pickup / booking |
| waiting_for_pickup | Awaiting carrier pickup |
| ready | Ready — stock reserved / booking attempted as configured |
| packed | Packed and waiting to ship |
| shipped | Handed to carrier / in transit |
| delivered | Delivered to customer |
| returned | Returned after shipment |
| cancelled | Cancelled (DELETE /orders/:id transitions here when allowed) |
Order channel (order.channel)
| Value | Meaning |
|---|---|
| manual | Created outside automation (API / ops) |
| portal | Created in Merchant Portal |
| storefront | Created from the live storefront checkout |
| shopify | Synced from Shopify (when connected) |
Payment method (order.payment.method)
| Value | Meaning |
|---|---|
| cod | Cash on delivery — typically set payment.codAmount |
| prepaid | Paid before ship (card / wallet / link) |
| other | Fallback for uncommon methods |
Shipment status (order.shipping.status / shipment.status)
| Value | Meaning |
|---|---|
| pending | Shipment record created; not booked yet |
| created | Booked with carrier |
| in_transit | In transit |
| out_for_delivery | Out for delivery |
| delivered | Delivered |
| returned | Return to origin |
| failed | Booking or delivery failed — see error / failureReason |
| cancelled | Shipment cancelled |
Delivery timing (order.deliveryTiming)
| Value | Meaning |
|---|---|
| pending | ETA not evaluated yet (usually before pickup) |
| on_time | Within estimated arrival window |
| early | Delivered / progressing earlier than ETA |
| late | Past estimated arrival |
Product / collection status
| Value | Meaning |
|---|---|
| active | Live / visible as configured |
| draft | Not published |
| archived | Hidden; kept for history (product delete may archive) |
Review status
| Value | Meaning |
|---|---|
| pending | Awaiting merchant moderation |
| approved | Visible on storefront |
| rejected | Rejected by merchant |
| hidden | Hidden from storefront |
Webhook events (subscribe via events[])
| Value | Meaning |
|---|---|
| OrderCreated | New order |
| OrderUpdated | Order fields changed |
| OrderStatusChanged | Order status transition |
| ProductUpdated | Product created, updated, or deleted |
| CollectionUpdated | Collection created, updated, or deleted |
| CustomerCreated | New customer |
| CustomerUpdated | Customer fields changed |
| ShipmentCreated | Shipment booked |
| ShipmentDelivered | Shipment marked delivered |
| ShipmentCreateFailed | Carrier booking failed |
| InventoryAdjusted | Stock level adjusted |
| StockReceived | Inbound stock received |
Order fields
Important properties on order objects from GET/POST/PATCH /orders. Waybill / tracking lives under shipping when a carrier booking exists.
Core
| Field | Meaning |
|---|---|
| id | Stable Wijha order id (use in paths) |
| number | Human-facing order number |
| status | Lifecycle status — see enums |
| channel | Where the order originated |
| customerId | Linked CRM customer id when matched |
| customerName | Buyer name snapshot |
| customerPhone | Buyer phone (prefer E.164 / local EG formats your store uses) |
| shippingAddress | Delivery address (city, area, line1, phone, …) |
| lineItems[] | Lines with variantId, sku, qty, price, unitCost, … |
| payment | method + codAmount + currency |
| totals | subtotal, shipping, discount, total, currency |
| shipmentIds | Linked shipment record ids |
| shipping | Primary carrier booking summary (waybill) |
| notes | Merchant / customer notes |
| tags | Order tags |
| createdAt / updatedAt | ISO timestamps |
shipping (waybill) object
| Field | Meaning |
|---|---|
| shipping.shipmentId | Primary Wijha shipment id |
| shipping.carrier | Carrier code (e.g. mega) |
| shipping.trackingNumber | Waybill / AWB / tracking number from the carrier |
| shipping.waybill | Alias of trackingNumber (same value) for integrators |
| shipping.megaShipmentId | Carrier-side shipment id when booked |
| shipping.status | Carrier shipment status — see enums |
| shipping.qrCode | Carrier QR payload when provided (display only) |
| shipping.labelUrl | Label URL when available |
| shipping.error | Booking error message if booking failed |
| shipping.failureReason | Carrier delivery/exception reason |
| shipping.awbPrinted | Whether AWB was marked printed |
| shipping.estimatedDeliveryAt | ETA after pickup when known |
| shipping.deliveryTiming | on_time / early / late / pending |
Line items
| Field | Meaning |
|---|---|
| variantId | Catalog variant id (required on create when selling variants) |
| productId | Parent product id |
| sku | Stock-keeping unit — used for fulfill / warehouse match |
| qty | Ordered quantity |
| price | Unit sell price snapshot |
| unitCost | COGS snapshot at create (for profit) |
| costSource | variant | stock | missing |
| imageUrl | Line thumb snapshot when available |
DELETE /orders/:id cancels when the current status allows a transition to cancelled — it does not hard-delete history.
Product fields
Important properties on product objects from /products.
Product
| Field | Meaning |
|---|---|
| id | Stable product id |
| title | Display name |
| sku | Product-level SKU when used |
| status | active | draft | archived |
| description / descriptionHtml | Long copy (plain / HTML) |
| tags | String tags (e.g. import markers) |
| collectionIds | Membership in merchant collections |
| variants[] | Sellable variants (price, sku, inventory, options) |
| media / mediaUrls | Image paths or absolute CDN URLs |
| trackInventory | Whether stock is enforced |
| inventoryPolicy | e.g. deny — block oversell when tracking |
| storeQuantity | Aggregated sellable qty across variants |
| categoryId / subcategoryId | Catalog taxonomy ids when set |
| brand | Brand label when set |
| price / currency | Display price helpers when present |
| importSource | Import channel when product was imported (e.g. vondera) |
| importExternalId | External id from the import source (sync / idempotency) |
| warehouseQuantity | Wijha warehouse on-hand when store is warehouse-enrolled |
| shopifyProductId | Linked Shopify product id when connected |
Variant
| Field | Meaning |
|---|---|
| id | Variant id (use on order lineItems.variantId) |
| sku | Unique per-store SKU |
| title | Variant title (e.g. size label) |
| price | Sell price |
| compareAtPrice | Compare-at / was price |
| attributes | Option map e.g. { Size: "M", Color: "Black" } |
| inventoryQuantity | On-hand / sellable qty for this variant |
| cost | Unit COGS when set (finance / profit) |
| barcode | Barcode when set |
| weightGrams | Variant weight when set |
| hsCode / taxCode | Customs / tax codes when set |
Shipping fees & balance fields
Read endpoints for zone fees and carrier wallet / settlement balance.
Shipping fee zone (GET /shipping-fees)
| Field | Meaning |
|---|---|
| sectorId | Zone / sector id (pass to /shipping-fees/resolve) |
| name | Zone display name |
| govId / govName | Governorate grouping |
| fee | Customer-facing delivery fee |
| currency | Fee currency (usually EGP) |
| isOverride | True when store overrode the Wijha default |
| source | wijha_default | override | … |
| estimatedArrivalHours | ETA hours for the zone when known |
Balance (GET /balance)
| Field | Meaning |
|---|---|
| available | Spendable / payable balance |
| pending | Pending amount |
| settled | Settled remittance when available |
| currency | Balance currency |
| lowThreshold | Low-balance warning threshold |
| isLow | True when available ≤ lowThreshold (non-simulated) |
| source | cached | live | simulated |
| syncedAt | Last sync timestamp |
Pass refresh=1 on /balance to force a fresh carrier sync.
Orders
Scope: orders.crud. DELETE cancels the order (status transition) — there is no hard delete.
GET /api/v1/orders?pageSize=20
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json{
"status": 200,
"message": "ok",
"data": {
"items": [
{
"id": "ord_01JABC",
"number": "10042",
"status": "pending",
"customerName": "Ahmed Hassan",
"customerPhone": "+201012345678",
"payment": { "method": "cod", "codAmount": 495, "currency": "EGP" },
"totals": { "subtotal": 450, "shipping": 45, "total": 495, "currency": "EGP" },
"createdAt": "2026-10-08T17:12:04.000Z"
}
],
"nextPageToken": null
}
}POST /api/v1/orders
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"customerName": "Ahmed Hassan",
"customerPhone": "+201012345678",
"shippingAddress": {
"city": "Cairo",
"area": "Nasr City",
"line1": "12 Nile St",
"phone": "+201012345678"
},
"lineItems": [
{
"productId": "prd_01J",
"variantId": "var_01J",
"sku": "TEE-BLK-M",
"title": "Cotton tee",
"qty": 2,
"price": 225
}
],
"payment": { "method": "cod", "codAmount": 495, "currency": "EGP" }
}{
"status": 201,
"message": "created",
"data": {
"id": "ord_01JABC",
"number": "10042",
"status": "waiting_for_pickup",
"channel": "manual",
"customerName": "Ahmed Hassan",
"customerPhone": "+201012345678",
"lineItems": [
{
"variantId": "var_01J",
"productId": "prd_01J",
"sku": "TEE-BLK-M",
"title": "Cotton tee",
"qty": 2,
"price": 225,
"unitCost": 80,
"costSource": "variant"
}
],
"payment": { "method": "cod", "codAmount": 495, "currency": "EGP" },
"totals": { "subtotal": 450, "shipping": 45, "total": 495, "currency": "EGP" },
"shipmentIds": ["shp_01J"],
"shipping": {
"shipmentId": "shp_01J",
"carrier": "mega",
"status": "created",
"trackingNumber": "WJH123456789",
"waybill": "WJH123456789",
"megaShipmentId": "mega_987654",
"qrCode": "MEGA-QR-PAYLOAD",
"labelUrl": "https://labels.example.com/WJH123456789.pdf",
"awbPrinted": false
},
"createdAt": "2026-10-08T17:12:04.000Z"
}
}DELETE /api/v1/orders/ord_01JABC
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx{
"status": 200,
"message": "cancelled",
"data": {
"id": "ord_01JABC",
"status": "cancelled"
}
}Also: GET /api/v1/orders/:id and PATCH /api/v1/orders/:id.
Products
Scope: products.crud.
GET /api/v1/products?pageSize=20
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json{
"status": 200,
"message": "ok",
"data": {
"items": [
{
"id": "prd_01J",
"title": "Cotton tee",
"status": "active",
"sku": "TEE-BLK",
"slug": "cotton-tee",
"brand": "Demo",
"categoryId": "apparel",
"subcategoryId": "tshirts",
"description": "Soft cotton tee",
"descriptionHtml": "<p>Soft cotton tee</p>",
"tags": ["basics"],
"collectionIds": ["col_01J"],
"seo": { "title": "Cotton tee", "description": "Soft cotton tee" },
"media": ["media/tee.jpg"],
"mediaUrls": ["https://cdn.example.com/media/tee.jpg"],
"trackInventory": true,
"inventoryPolicy": "deny",
"storeQuantity": 24,
"warehouseQuantity": 10,
"importSource": "vondera",
"importExternalId": "43254206",
"variants": [
{
"id": "var_01J",
"sku": "TEE-BLK-M",
"title": "M",
"price": 225,
"compareAtPrice": 275,
"cost": 80,
"barcode": "6220000000000",
"attributes": { "Color": "Black", "Size": "M" },
"inventoryQuantity": 12,
"weightGrams": 180
}
]
}
],
"nextPageToken": null,
"warehouseEnrolled": true
}
}POST /api/v1/products
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"title": "Cotton tee",
"sku": "TEE-BLK",
"status": "active",
"description": "Soft cotton tee",
"variants": [
{
"sku": "TEE-BLK-M",
"price": 225,
"compareAtPrice": 275,
"cost": 80,
"attributes": { "Color": "Black", "Size": "M" },
"inventoryQuantity": 12
}
]
}{
"status": 201,
"message": "created",
"data": {
"id": "prd_01J",
"title": "Cotton tee",
"sku": "TEE-BLK",
"status": "active",
"storeQuantity": 12,
"variants": [
{
"id": "var_01J",
"sku": "TEE-BLK-M",
"price": 225,
"compareAtPrice": 275,
"cost": 80,
"attributes": { "Color": "Black", "Size": "M" },
"inventoryQuantity": 12
}
]
}
}Also: GET / PATCH / DELETE /api/v1/products/:id.
Collections
Scope: collections.crud. Merchant product groupings (not catalog taxonomy categories).
GET /api/v1/collections
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json{
"status": 200,
"message": "ok",
"data": {
"items": [
{ "id": "col_01J", "title": "Summer", "slug": "summer", "status": "active" }
],
"nextPageToken": null
}
}POST /api/v1/collections
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"title": "Summer",
"slug": "summer",
"status": "active",
"productIds": ["prd_01J"]
}{
"status": 201,
"message": "created",
"data": {
"id": "col_01J",
"title": "Summer",
"slug": "summer",
"status": "active",
"productIds": ["prd_01J"]
}
}Expenses
Scope: expenses.crud.
GET /api/v1/expenses?from=2026-10-01&to=2026-10-31
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json{
"status": 200,
"message": "ok",
"data": {
"items": [
{
"id": "exp_01J",
"categoryName": "Rent",
"amount": 5000,
"currency": "EGP",
"expenseDate": "2026-10-01"
}
]
}
}POST /api/v1/expenses
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"categoryId": "cat_rent",
"amount": 5000,
"currency": "EGP",
"expenseDate": "2026-10-01",
"vendor": "Landlord Co",
"notes": "October rent",
"recurringRule": "monthly"
}{
"status": 201,
"message": "created",
"data": {
"id": "exp_01J",
"categoryId": "cat_rent",
"categoryName": "Rent",
"amount": 5000,
"currency": "EGP",
"expenseDate": "2026-10-01",
"vendor": "Landlord Co",
"recurringRule": "monthly"
}
}Also: GET / PATCH / DELETE /api/v1/expenses/:id.
Analytics
Scope: analytics.read. Query from / to as YYYY-MM-DD.
GET /api/v1/analytics?from=2026-10-01&to=2026-10-17
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json{
"status": 200,
"message": "ok",
"data": {
"from": "2026-10-01",
"to": "2026-10-17",
"summary": {
"orders": 128,
"revenue": 64250,
"currency": "EGP"
},
"series": [
{ "day": "2026-10-01", "orders": 8, "revenue": 4200 }
]
}
}Discount codes
Scope: discounts.read (read-only).
GET /api/v1/discounts
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json{
"status": 200,
"message": "ok",
"data": {
"items": [
{
"id": "dsc_01J",
"code": "SUMMER10",
"type": "percent",
"value": 10,
"active": true
}
]
}
}Also: GET /api/v1/discounts/:id.
Customers
Scope: customers.read (or customers.crud). Read-only on /api/v1.
GET /api/v1/customers?pageSize=20
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json{
"status": 200,
"message": "ok",
"data": {
"items": [
{
"id": "cus_01JDEF",
"name": "Ahmed Hassan",
"phone": "+201012345678",
"email": "ahmed@example.com"
}
],
"nextPageToken": null
}
}Also: GET /api/v1/customers/:id.
Reviews
Scope: reviews.read.
GET /api/v1/reviews?status=approved&limit=20
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json{
"status": 200,
"message": "ok",
"data": {
"items": [
{
"id": "rev_01J",
"productId": "prd_01J",
"rating": 5,
"body": "Great quality",
"status": "approved",
"createdAt": "2026-10-08T10:00:00.000Z"
}
]
}
}Also: GET /api/v1/reviews/:id.
Client shipping fees
Scope: shipping_fees.read. Zone fees charged to customers (defaults from Wijha/Mega, plus store overrides).
GET /api/v1/shipping-fees?govId=cairo&limit=50
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json{
"status": 200,
"message": "ok",
"data": {
"currency": "EGP",
"megaDefaultsSource": "work_places",
"items": [
{
"sectorId": "sec_nasr_city",
"name": "Nasr City",
"govId": "cairo",
"govName": "Cairo",
"fee": 45,
"currency": "EGP",
"isOverride": false,
"source": "wijha_default",
"estimatedArrivalHours": 48
}
]
}
}GET /api/v1/shipping-fees/resolve?sectorId=sec_nasr_city
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json{
"status": 200,
"message": "ok",
"data": {
"sectorId": "sec_nasr_city",
"fee": 45,
"currency": "EGP",
"isOverride": false,
"source": "wijha_default",
"estimatedArrivalHours": 48
}
}Shipping balance
Scope: balance.read. Settlement / wallet balance for the store’s shipping account.
GET /api/v1/balance
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
# Optional: force a fresh sync from the carrier
# GET /api/v1/balance?refresh=1{
"status": 200,
"message": "ok",
"data": {
"available": 1250.5,
"pending": 320,
"settled": 0,
"currency": "EGP",
"lowThreshold": 200,
"isLow": false,
"source": "cached",
"syncedAt": "2026-10-08T12:00:00.000Z"
}
}Store info
Scope: store.read. Safe public profile only (no carrier secrets).
GET /api/v1/store
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json{
"status": 200,
"message": "ok",
"data": {
"id": "str_01JSTORE",
"name": "Demo Store",
"slug": "demo-store",
"status": "active",
"country": "EG",
"timezone": "Africa/Cairo",
"currency": "EGP",
"contact": {
"supportEmail": "hello@example.com",
"supportPhone": "+201012345678",
"tagline": "Quality basics",
"logoUrl": "https://cdn.example.com/logo.png"
},
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-10-08T12:00:00.000Z"
}
}Webhooks (via API)
Scopes: webhooks.read to list; webhooks.write to create. Same endpoints as the portal Developers → Webhooks UI.
GET /api/v1/webhooks/endpoints
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json{
"status": 200,
"message": "ok",
"data": {
"items": [
{
"id": "wh_01J",
"nickname": "ERP sync",
"url": "https://example.com/wijha/hooks",
"events": ["OrderCreated", "OrderStatusChanged"],
"active": true,
"hasSecret": true
}
],
"nextPageToken": null
}
}POST /api/v1/webhooks/endpoints
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"url": "https://example.com/wijha/hooks",
"method": "POST",
"nickname": "ERP sync",
"events": ["OrderCreated", "OrderStatusChanged"],
"secret": "super-secret-min-8",
"active": true
}{
"status": 201,
"message": "created",
"data": {
"id": "wh_01J",
"url": "https://example.com/wijha/hooks",
"method": "POST",
"nickname": "ERP sync",
"events": ["OrderCreated", "OrderStatusChanged"],
"hasSecret": true,
"secret": "super-secret-min-8",
"active": true,
"status": "active"
}
}Also: GET /api/v1/webhooks/events and GET /api/v1/webhooks/endpoints/:id.