This document describes all Zid API endpoints used by the Raff platform, their expected responses, and error handling.
- Authentication
- Products API
- Categories API
- Orders API
- Store API
- Webhooks API
- Error Codes
- Rate Limiting
Authorization Endpoint: https://oauth.zid.sa/oauth/authorize
Token Endpoint: https://oauth.zid.sa/oauth/token
GET /oauth/authorize
Query Parameters:
- client_id: Your Zid app client ID
- redirect_uri: Your callback URL (must match registered URL)
- response_type: "code"
- scope: Space-separated list (e.g., "products.read orders.read")
- state: CSRF protection token
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
Body:
- grant_type: "authorization_code"
- code: Authorization code from callback
- client_id: Your Zid app client ID
- client_secret: Your Zid app client secret
- redirect_uri: Same URL used in authorization
Response:
{
"Authorization": "Bearer xxx", // Bearer token for API calls
"access_token": "yyy", // Manager token for API calls
"refresh_token": "zzz",
"token_type": "Bearer",
"expires_in": 31536000,
"store_id": "store-123",
"store_url": "https://store.zid.store"
}
Important: Zid returns two tokens —
Authorization(Bearer token) andaccess_token(Manager token). Both are required for all API requests.
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
Body:
- grant_type: "refresh_token"
- refresh_token: Stored refresh token
- client_id: Your Zid app client ID
- client_secret: Your Zid app client secret
- redirect_uri: Same callback URL (required for Zid)
Response: Same as token exchange
Note: Unlike Salla, Zid requires the
redirect_uriin refresh token requests.
Error Handling:
invalid_client: Invalid client credentialsinvalid_grant: Invalid or expired refresh token → Merchant needs to reconnectunauthorized_client: Client not authorized for this grant type
All authenticated Zid API requests require three headers:
Authorization: Bearer {authorization_token}
X-Manager-Token: {access_token}
Access-Token: {access_token}
Additionally, most endpoints require a Store-Id header:
Store-Id: {store_id}
Exception: The
/managers/account/profileendpoint does not requireStore-Id.
GET https://api.zid.sa/v1/products/
Headers:
- Authorization: Bearer {token}
- X-Manager-Token: {manager_token}
- Access-Token: {manager_token}
- Store-Id: {store_id}
Query Parameters:
- page: Page number (default: 1)
- page_size: Items per page (default: 50)
- extended: "true" to include variants
- ordering: "updated_at" or "created_at"
Response:
{
"results": [
{
"id": 123456,
"name": "Product Name", // string or { en: "...", ar: "..." }
"short_description": "...", // string or { en: "...", ar: "..." }
"description": "...", // string or { en: "...", ar: "..." }
"price": 99.99,
"sale_price": 79.99,
"compare_price": 99.99,
"currency": "SAR",
"sku": "SKU123",
"is_published": true,
"is_infinite": false,
"quantity": 10,
"stocks": [
{
"available_quantity": 10
}
],
"images": [
{
"url": "https://media.zid.store/image.jpg",
"alt": "Product image"
}
],
"categories": [
{
"id": 789,
"name": "Category Name"
}
],
"slug": "product-name",
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-15T14:30:00Z"
}
],
"next": "https://api.zid.sa/v1/products/?page=2&page_size=50",
"previous": null,
"count": 150
}
GET https://api.zid.sa/v1/products/{product_id}/
Headers: Same as above
Response:
{
"id": 123456,
// Same structure as product in list, with additional details
"options": [...],
"variants": [...],
"metadata": {...}
}
- Name/Description: Can be a plain string or an i18n object
{ en: "English", ar: "عربي" } - Price Priority:
sale_price→price→0 - Stock Calculation: If
is_infinite = true, stock is unlimited. Otherwise, sumstocks[].available_quantity - Active Status:
is_published = trueorstatusin["active", "published", "available"]
GET https://api.zid.sa/v1/managers/store/categories
Headers: Same as products
Response:
{
"results": [
{
"id": 789,
"name": "Category Name",
"slug": "category-name",
"parent_id": null,
"products_count": 25
}
]
}
GET https://api.zid.sa/v1/managers/store/categories/{category_id}/view
Headers: Same as products
Response:
{
"id": 789,
"name": "Category Name",
"slug": "category-name",
"description": "Category description",
"parent_id": null,
"image": "https://media.zid.store/category.jpg"
}
GET https://api.zid.sa/v1/managers/store/orders
Headers: Same as products
Query Parameters:
- payload_type: "default" (REQUIRED — includes products in response)
- page: Page number
- per_page: Items per page
- date_from: Filter from date (YYYY-MM-DD)
- date_to: Filter to date (YYYY-MM-DD)
- order_status: Filter by status
Response:
{
"results": [
{
"id": 456789,
"order_number": "ORD-2024-001",
"status": "pending",
"order_status": {
"code": "pending",
"name": "Pending"
},
"payment_status": "paid",
"payment_method": "credit_card",
"total": 299.99,
"currency": "SAR",
"customer": {
"name": "Customer Name",
"email": "customer@example.com",
"phone": "+966501234567"
},
"created_at": "2024-01-15T14:30:00Z",
"updated_at": "2024-01-15T15:00:00Z"
}
],
"next": null,
"previous": null,
"count": 50
}
Important: Always pass
payload_type=default— without it, order products are not included in the response.
GET https://api.zid.sa/v1/managers/store/orders/{order_id}/view
Headers: Same as products
Response:
{
"id": 456789,
"order_number": "ORD-2024-001",
"status": "pending",
"order_status": {
"code": "pending",
"name": "Pending"
},
"payment_status": "paid",
"total": 299.99,
"currency": "SAR",
"products": [
{
"id": 123456,
"name": "Product Name",
"quantity": 2,
"price": 99.99
}
],
"shipping": {
"address": "123 Street Name",
"city": "Riyadh",
"country": "Saudi Arabia"
},
"created_at": "2024-01-15T14:30:00Z"
}
GET https://api.zid.sa/v1/managers/account/profile
Headers:
- Authorization: Bearer {token}
- X-Manager-Token: {manager_token}
- Access-Token: {manager_token}
(No Store-Id header needed)
Response:
{
"user": {
"id": "user-123",
"name": "Manager Name",
"email": "manager@example.com"
},
"store": {
"id": "store-123",
"name": "Store Name",
"url": "https://store.zid.store",
"currency": "SAR",
"country": "SA"
}
}
POST https://api.zid.sa/v1/managers/webhooks
Headers:
- Authorization: Bearer {token}
- X-Manager-Token: {manager_token}
- Content-Type: application/json
Body:
{
"url": "https://yourapp.com/api/zid/webhook",
"event": "product.create",
"app_id": "your-app-id"
}
Response:
{
"id": "webhook-id-123",
"url": "https://yourapp.com/api/zid/webhook",
"event": "product.create",
"created_at": "2024-01-01T12:00:00Z"
}
Webhook Events:
product.create— New product addedproduct.update— Product modifiedproduct.publish— Product publishedproduct.delete— Product removedorder.create— New order placedorder.status.update— Order status changedorder.payment_status.update— Payment status changed
| Code | Meaning | Action |
|---|---|---|
| 200 | Success | Process response |
| 400 | Bad Request | Check request parameters |
| 401 | Unauthorized | Refresh access token or reauth |
| 403 | Forbidden | Check app permissions/scopes |
| 404 | Not Found | Resource doesn't exist |
| 429 | Rate Limited | Back off and retry with delay |
| 500 | Server Error | Retry with exponential backoff |
| 503 | Service Unavailable | Retry later |
{
"error": {
"code": "invalid_parameters",
"message": "The provided parameters are invalid"
}
}If you get a 403 error, check if the required scope is granted:
products.read— Required for product syncorders.read— Required for order synccategories.read— Required for category syncwebhooks.read/webhooks.write— Required for webhook management
Zid enforces 60 requests per minute per store.
When you receive a 429 Too Many Requests:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Best Practices:
- Respect the
Retry-Afterheader - Implement exponential backoff
- Don't retry more than 2–3 times
- Use pagination with reasonable
page_sizevalues (max 50)
if (response.status === 429) {
const retryAfter = response.headers.get("retry-after");
const delayMs = retryAfter ? parseInt(retryAfter) * 1000 : 5000;
await sleep(delayMs);
// Retry request
}| Feature | Zid | Salla |
|---|---|---|
| Auth Tokens | Two tokens required (Bearer + Manager) | Single Bearer token |
| Store ID | Sent via Store-Id header |
Included in payload |
| Pagination | next/previous URLs, count |
pagination object with totalPages |
| Product Names | Can be i18n objects { en, ar } |
Always strings |
| Stock Model | is_infinite flag + stocks[] array |
Simpler quantity field |
| Order Query | Requires payload_type=default |
Products included by default |
| Webhook Signature | Default: plain comparison | Default: HMAC-SHA256 |
| Token Refresh | Requires redirect_uri |
Does not require redirect_uri |
| Rate Limit | 60 req/min per store | Varies by endpoint |
- Send both tokens: Always include
Authorization,X-Manager-Token, andAccess-Tokenheaders - Use
extended=true: When listing products, include extended data for full details - Use
payload_type=default: For orders, always include this parameter - Handle i18n fields: Product names and descriptions may be objects or strings
- Handle token expiry: Implement automatic token refresh on 401 errors
- Respect rate limits: Stay within 60 req/min per store
- Use pagination: Don't request all data at once — paginate with
page_size=50 - Log all errors: Use structured logging for debugging API issues
- Auth URL:
https://oauth.zid.sa/oauth/authorize - API Base:
https://api.zid.sa/v1 - Developer Portal: https://web.zid.sa/
Use tools like ngrok to test webhooks locally:
ngrok http 3000
# Use the ngrok URL as your webhook callback- Developer Portal: https://web.zid.sa/
- API Documentation: https://docs.zid.sa/
- Support: Contact Zid Partner Support