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:

Response envelope
{
  "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:

Headers
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# or
x-api-key: wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Keys 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)
429 response
{
  "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)

ValueMeaning
pendingNew order; not yet ready for pickup / booking
waiting_for_pickupAwaiting carrier pickup
readyReady — stock reserved / booking attempted as configured
packedPacked and waiting to ship
shippedHanded to carrier / in transit
deliveredDelivered to customer
returnedReturned after shipment
cancelledCancelled (DELETE /orders/:id transitions here when allowed)

Order channel (order.channel)

ValueMeaning
manualCreated outside automation (API / ops)
portalCreated in Merchant Portal
storefrontCreated from the live storefront checkout
shopifySynced from Shopify (when connected)

Payment method (order.payment.method)

ValueMeaning
codCash on delivery — typically set payment.codAmount
prepaidPaid before ship (card / wallet / link)
otherFallback for uncommon methods

Shipment status (order.shipping.status / shipment.status)

ValueMeaning
pendingShipment record created; not booked yet
createdBooked with carrier
in_transitIn transit
out_for_deliveryOut for delivery
deliveredDelivered
returnedReturn to origin
failedBooking or delivery failed — see error / failureReason
cancelledShipment cancelled

Delivery timing (order.deliveryTiming)

ValueMeaning
pendingETA not evaluated yet (usually before pickup)
on_timeWithin estimated arrival window
earlyDelivered / progressing earlier than ETA
latePast estimated arrival

Product / collection status

ValueMeaning
activeLive / visible as configured
draftNot published
archivedHidden; kept for history (product delete may archive)

Review status

ValueMeaning
pendingAwaiting merchant moderation
approvedVisible on storefront
rejectedRejected by merchant
hiddenHidden from storefront

Webhook events (subscribe via events[])

ValueMeaning
OrderCreatedNew order
OrderUpdatedOrder fields changed
OrderStatusChangedOrder status transition
ProductUpdatedProduct created, updated, or deleted
CollectionUpdatedCollection created, updated, or deleted
CustomerCreatedNew customer
CustomerUpdatedCustomer fields changed
ShipmentCreatedShipment booked
ShipmentDeliveredShipment marked delivered
ShipmentCreateFailedCarrier booking failed
InventoryAdjustedStock level adjusted
StockReceivedInbound 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

FieldMeaning
idStable Wijha order id (use in paths)
numberHuman-facing order number
statusLifecycle status — see enums
channelWhere the order originated
customerIdLinked CRM customer id when matched
customerNameBuyer name snapshot
customerPhoneBuyer phone (prefer E.164 / local EG formats your store uses)
shippingAddressDelivery address (city, area, line1, phone, …)
lineItems[]Lines with variantId, sku, qty, price, unitCost, …
paymentmethod + codAmount + currency
totalssubtotal, shipping, discount, total, currency
shipmentIdsLinked shipment record ids
shippingPrimary carrier booking summary (waybill)
notesMerchant / customer notes
tagsOrder tags
createdAt / updatedAtISO timestamps

shipping (waybill) object

FieldMeaning
shipping.shipmentIdPrimary Wijha shipment id
shipping.carrierCarrier code (e.g. mega)
shipping.trackingNumberWaybill / AWB / tracking number from the carrier
shipping.waybillAlias of trackingNumber (same value) for integrators
shipping.megaShipmentIdCarrier-side shipment id when booked
shipping.statusCarrier shipment status — see enums
shipping.qrCodeCarrier QR payload when provided (display only)
shipping.labelUrlLabel URL when available
shipping.errorBooking error message if booking failed
shipping.failureReasonCarrier delivery/exception reason
shipping.awbPrintedWhether AWB was marked printed
shipping.estimatedDeliveryAtETA after pickup when known
shipping.deliveryTimingon_time / early / late / pending

Line items

FieldMeaning
variantIdCatalog variant id (required on create when selling variants)
productIdParent product id
skuStock-keeping unit — used for fulfill / warehouse match
qtyOrdered quantity
priceUnit sell price snapshot
unitCostCOGS snapshot at create (for profit)
costSourcevariant | stock | missing
imageUrlLine 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

FieldMeaning
idStable product id
titleDisplay name
skuProduct-level SKU when used
statusactive | draft | archived
description / descriptionHtmlLong copy (plain / HTML)
tagsString tags (e.g. import markers)
collectionIdsMembership in merchant collections
variants[]Sellable variants (price, sku, inventory, options)
media / mediaUrlsImage paths or absolute CDN URLs
trackInventoryWhether stock is enforced
inventoryPolicye.g. deny — block oversell when tracking
storeQuantityAggregated sellable qty across variants
categoryId / subcategoryIdCatalog taxonomy ids when set
brandBrand label when set
price / currencyDisplay price helpers when present
importSourceImport channel when product was imported (e.g. vondera)
importExternalIdExternal id from the import source (sync / idempotency)
warehouseQuantityWijha warehouse on-hand when store is warehouse-enrolled
shopifyProductIdLinked Shopify product id when connected

Variant

FieldMeaning
idVariant id (use on order lineItems.variantId)
skuUnique per-store SKU
titleVariant title (e.g. size label)
priceSell price
compareAtPriceCompare-at / was price
attributesOption map e.g. { Size: "M", Color: "Black" }
inventoryQuantityOn-hand / sellable qty for this variant
costUnit COGS when set (finance / profit)
barcodeBarcode when set
weightGramsVariant weight when set
hsCode / taxCodeCustoms / tax codes when set

Shipping fees & balance fields

Read endpoints for zone fees and carrier wallet / settlement balance.

Shipping fee zone (GET /shipping-fees)

FieldMeaning
sectorIdZone / sector id (pass to /shipping-fees/resolve)
nameZone display name
govId / govNameGovernorate grouping
feeCustomer-facing delivery fee
currencyFee currency (usually EGP)
isOverrideTrue when store overrode the Wijha default
sourcewijha_default | override | …
estimatedArrivalHoursETA hours for the zone when known

Balance (GET /balance)

FieldMeaning
availableSpendable / payable balance
pendingPending amount
settledSettled remittance when available
currencyBalance currency
lowThresholdLow-balance warning threshold
isLowTrue when available ≤ lowThreshold (non-simulated)
sourcecached | live | simulated
syncedAtLast 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.

List — request
GET /api/v1/orders?pageSize=20
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
List — response
{
  "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
  }
}
Create — request
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" }
}
Create — response
{
  "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"
  }
}
Cancel — request
DELETE /api/v1/orders/ord_01JABC
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Cancel — response
{
  "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.

List — request
GET /api/v1/products?pageSize=20
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
List — response
{
  "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
  }
}
Create — request
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
    }
  ]
}
Create — response
{
  "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).

List — request
GET /api/v1/collections
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
List — response
{
  "status": 200,
  "message": "ok",
  "data": {
    "items": [
      { "id": "col_01J", "title": "Summer", "slug": "summer", "status": "active" }
    ],
    "nextPageToken": null
  }
}
Create — request
POST /api/v1/collections
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

{
  "title": "Summer",
  "slug": "summer",
  "status": "active",
  "productIds": ["prd_01J"]
}
Create — response
{
  "status": 201,
  "message": "created",
  "data": {
    "id": "col_01J",
    "title": "Summer",
    "slug": "summer",
    "status": "active",
    "productIds": ["prd_01J"]
  }
}

Expenses

Scope: expenses.crud.

List — request
GET /api/v1/expenses?from=2026-10-01&to=2026-10-31
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
List — response
{
  "status": 200,
  "message": "ok",
  "data": {
    "items": [
      {
        "id": "exp_01J",
        "categoryName": "Rent",
        "amount": 5000,
        "currency": "EGP",
        "expenseDate": "2026-10-01"
      }
    ]
  }
}
Create — request
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"
}
Create — response
{
  "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.

Request
GET /api/v1/analytics?from=2026-10-01&to=2026-10-17
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
Response
{
  "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).

Request
GET /api/v1/discounts
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
Response
{
  "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.

Request
GET /api/v1/customers?pageSize=20
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
Response
{
  "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.

Request
GET /api/v1/reviews?status=approved&limit=20
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
Response
{
  "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).

List — request
GET /api/v1/shipping-fees?govId=cairo&limit=50
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
List — response
{
  "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
      }
    ]
  }
}
Resolve one zone — request
GET /api/v1/shipping-fees/resolve?sectorId=sec_nasr_city
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
Resolve one zone — response
{
  "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.

Request
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
Response
{
  "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).

Request
GET /api/v1/store
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
Response
{
  "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.

List — request
GET /api/v1/webhooks/endpoints
Authorization: Bearer wjh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
List — response
{
  "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
  }
}
Create — request
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
}
Create — response
{
  "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.