This document describes all Salla API endpoints used by the Raff platform, their expected responses, and error handling.
Authorization Endpoint: https://accounts.salla.sa/oauth2/auth
Token Endpoint: https://accounts.salla.sa/oauth2/token
GET /oauth2/auth
Query Parameters:
- client_id: Your Salla app client ID
- redirect_uri: Your callback URL (must match registered URL)
- response_type: "code"
- scope: Space-separated list (e.g., "offline_access products.read orders.read")
- state: CSRF protection token
POST /oauth2/token
Content-Type: application/x-www-form-urlencoded
Body:
- grant_type: "authorization_code"
- code: Authorization code from callback
- client_id: Your Salla app client ID
- client_secret: Your Salla app client secret
- redirect_uri: Same URL used in authorization
Response:
{
"access_token": "string",
"refresh_token": "string",
"token_type": "Bearer",
"expires_in": 31536000 // seconds
}
POST /oauth2/token
Content-Type: application/x-www-form-urlencoded
Body:
- grant_type: "refresh_token"
- refresh_token: Stored refresh token
- client_id: Your Salla app client ID
- client_secret: Your Salla app client secret
Response: Same as token exchange
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
GET https://api.salla.dev/admin/v2/products
Headers:
- Authorization: Bearer {access_token}
- Accept: application/json
Query Parameters:
- page: Page number (default: 1)
- per_page: Items per page (default: 15, max: 50)
Response:
{
"success": true,
"data": [
{
"id": 123456,
"name": "Product Name",
"description": "Product description",
"price": 99.99,
"sale_price": null,
"sku": "SKU123",
"status": "active",
"quantity": 10,
"url": "https://store.salla.sa/product/123456",
"images": [
{
"url": "https://cdn.salla.sa/image.jpg",
"alt": "Product image"
}
],
"categories": [
{
"id": 789,
"name": "Category Name"
}
],
"weight": {
"unit": "kg",
"value": 1.5
},
"created_at": {
"date": "2024-01-01 12:00:00",
"timezone": "UTC"
}
}
],
"pagination": {
"count": 50,
"total": 150,
"perPage": 50,
"currentPage": 1,
"totalPages": 3,
"links": {
"next": "https://api.salla.dev/admin/v2/products?page=2",
"prev": null
}
}
}
GET https://api.salla.dev/admin/v2/products/{product_id}
Headers: Same as above
Response:
{
"success": true,
"data": {
// Same structure as product in list, with additional details
"options": [...],
"variants": [...],
"metadata": {...}
}
}
GET https://api.salla.dev/admin/v2/orders
Headers: Same as products
Query Parameters:
- page: Page number
- per_page: Items per page
- from_date: Filter orders from date (YYYY-MM-DD)
- to_date: Filter orders to date (YYYY-MM-DD)
- status: Filter by status
Response:
{
"success": true,
"data": [
{
"id": 456789,
"reference_id": "ORD-2024-001",
"status": {
"id": 1,
"name": "Pending",
"slug": "pending"
},
"payment_method": "credit_card",
"payment_status": "paid",
"total": {
"amount": 299.99,
"currency": "SAR"
},
"date": {
"date": "2024-01-15 14:30:00",
"timezone": "Asia/Riyadh"
}
}
],
"pagination": {...}
}
GET https://api.salla.dev/admin/v2/orders/{order_id}
Query Parameters:
- format: "light" (recommended) or "full"
Response:
{
"success": true,
"data": {
"id": 456789,
"reference_id": "ORD-2024-001",
"customer": {
"name": "Customer Name",
"email": "customer@example.com",
"phone": "+966501234567"
},
"shipping": {
"address": "123 Street Name",
"city": "Riyadh",
"country": "Saudi Arabia",
"postal_code": "12345"
},
"amounts": {
"sub_total": {...},
"shipping_cost": {...},
"tax": {...},
"total": {...}
},
// ... additional fields
}
}
GET https://api.salla.dev/admin/v2/orders/{order_id}/items
Response:
{
"success": true,
"data": [
{
"id": 111,
"product_id": 123456,
"name": "Product Name",
"quantity": 2,
"price": {
"amount": 99.99,
"currency": "SAR"
},
"sku": "SKU123",
"options": {...}
}
]
}
GET https://api.salla.dev/admin/v2/orders/{order_id}/histories
Query Parameters:
- per_page: Number of history entries (default: 15)
Response:
{
"success": true,
"data": {
"items": [
{
"id": 222,
"status": {...},
"comment": "Status changed",
"created_at": "2024-01-15 14:30:00"
}
]
}
}
GET https://api.salla.dev/admin/v2/store/info
Response:
{
"success": true,
"data": {
"id": "store-id-123",
"name": "Store Name",
"domain": "store.salla.sa",
"email": "store@example.com",
"plan": "premium",
"status": "active",
"currency": "SAR",
"country": "SA"
}
}
POST https://api.salla.dev/admin/v2/webhooks/subscribe
Headers:
- Authorization: Bearer {access_token}
- Content-Type: application/json
Body:
{
"name": "Raff Product Updates",
"url": "https://yourapp.com/api/salla/webhook",
"event": "product.created",
"secret": "your-webhook-secret"
}
Response:
{
"success": true,
"data": {
"id": "webhook-id-123",
"name": "Raff Product Updates",
"url": "https://yourapp.com/api/salla/webhook",
"event": "product.created",
"created_at": "2024-01-01T12:00:00Z"
}
}
Important: Salla allows only ONE webhook per event. Register separate webhooks for each event:
app.installedapp.uninstalledproduct.createdproduct.updatedorder.createdorder.updatedorder.cancelledorder.shipped
| 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 |
{
"success": false,
"error": {
"code": "invalid_parameters",
"message": "The provided parameters are invalid",
"fields": {
"email": ["Email is required"]
}
}
}If you get a 403 error, check if the required scope is granted:
products.read- Required for product syncorders.read- Required for order syncstore.read- Required for store infooffline_access- Required for refresh tokens
Salla API uses rate limiting to prevent abuse.
Every response includes rate limit headers:
X-RateLimit-Limit: Total requests allowed per windowX-RateLimit-Remaining: Requests remaining in current windowX-RateLimit-Reset: Unix timestamp when limit resets
When you receive a 429 Too Many Requests:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Retry-After can be:
- Number of seconds:
60 - HTTP date:
Wed, 21 Oct 2025 07:28:00 GMT
Best Practices:
- Respect the
Retry-Afterheader - Implement exponential backoff
- Don't retry more than 2-3 times
- Use concurrent requests sparingly (max 4)
if (response.status === 429) {
const retryAfter = response.headers.get("retry-after");
const delayMs = retryAfter ? parseInt(retryAfter) * 1000 : 5000;
await sleep(delayMs);
// Retry request
}- Use pagination parameters: Always use
pageandper_pagefor list endpoints - Handle token expiry: Implement automatic token refresh on 401 errors
- Verify webhook signatures: Always verify HMAC-SHA256 signature for webhooks
- Store refresh tokens securely: Encrypt refresh tokens at rest
- Respect rate limits: Monitor rate limit headers and back off when needed
- Use format=light: For order details, use
format=lightfor better performance - Batch operations: Use batch queries instead of N+1 queries
- Log all errors: Use structured logging for debugging API issues
Salla provides a sandbox environment for testing:
- Auth URL: Same as production
- API Base: Same as production
- Test Account: Create a test store at https://s.salla.sa/
Use tools like ngrok to test webhooks locally:
ngrok http 3000
# Use the ngrok URL as your webhook callback- API Documentation: https://docs.salla.dev/
- Developer Portal: https://salla.dev/
- Support: Contact Salla Partner Support
- 2024-01-15: Updated to API v2 endpoints
- 2024-01-01: Initial documentation