Zid Docs
Apps
ThemesPayments
Apps
ThemesPayments
Help Center
Slack
  1. Authorization
  • πŸ—‚οΈ Overview
  • πŸš€ Start Here
  • Create your First App
  • Responses
  • Rate Limiting
  • Embedded Apps
  • StoreFront Events
  • Zid MUI
  • Zid SDKs
  • Our custom MCP server
  • Zid AI Agent Skill
  • Authorization
    • Authorization
    • Public Apps OAuth Requirements
    • OAuth Errors & Troubleshooting
    • Zid Bridge Tool
  • Merchant APIs
    • Account
      • Me
        • Me
      • Store
        • Store
        • Branding
        • Localization
        • Social
        • Operations
        • Business
        • Upload Logo Image
        • Delete Logo Image
        • Upload Icon Image
        • Delete Icon Image
        • Upload Cover Image
        • Delete Cover Image
    • Orders
      • Order Tags
        • List Order Tags
        • Create Order Tag
      • Validate Order Request
      • Create Order
      • Change Order Status
      • Add Order Comment
      • View Order
      • List Orders
      • List Custom Order Statuses
      • List Order Credit Notes
      • Get Orders Highlights
      • Order Fulfillment Inventories
      • Bulk Create/Update Custom Order Statuses
    • Reverse Orders
      • Create Reverse Orders
      • Create Reverse Orders Waybill
      • Add Reverse Order Reasons
      • List Reverse Reasons
      • Calculate Reverse Totals
      • Create refund for reverse order
      • Upload bank transfer receipt
      • Update return products
      • Order Details – View for Return
    • Abandoned Carts
      • List Carts
      • View Cart
      • Validate Abandoned Cart Messages
      • Send Abandoned Cart Notification Manually
    • Products
      • Managing Products
        • Create Product Types
        • Create Product
        • List Products
        • View Product
        • List Product Settings
        • Update Product
        • Bulk Update Products
        • Delete Product
      • Digital Vouchers
        • List Vouchers
        • Order Vouchers
        • Create Voucher
        • Import Vouchers
        • Export Vouchers
        • Update Voucher
        • Remove Voucher
      • Product Categories
        • List Categories
        • View Category
        • Create Category
        • Create Subcategory
        • Update Category
        • Assign Product to Category
        • Bulk Assign Products to Category
        • Publish Category
        • Detach Category from All Products
        • Remove Product from Category
        • Set Categories Nesting
        • Delete Category Cover Image
        • Upload Category Cover Image
        • Delete Category
        • Delete Category Image
        • Upload Category Image
        • Update Category
      • Product Brands
        • List Brands
        • Single Brand
      • Product Badge
        • List Badges
      • Product Attributes
        • List Attributes
        • View Attribute
        • Create Attribute
        • Update Attribute
        • Delete Attribute
      • Product Attribute Presets
        • List Presets
        • Create Preset
        • Update Preset
        • Delete Preset
      • Product Variants
        • Create Variants
        • Create Custom Option
        • Update Custom Option
        • Delete Custom Option Field
        • Create Custom Input Field
        • Update Custom Input Field
        • Delete Custom Input Field
      • Product Sorting
        • Set Manual Product Order
        • Reset Manual Product Order
      • Product Images
        • List Images
        • Upload Image
        • Update Image Order
        • Delete Image
      • Product Availability Notifications
        • Notification Stats
        • Noticiation Settings
        • List Notifications
        • Create Notification
        • Update Settings
        • Send Notification Email
        • Export Notifications
      • Product Export
        • Email All Products to Store Owner
        • Import Products via CSV or xlsx File
      • Product Stock
        • Retrieve Product Stock
        • List Product Stock Records
        • Create Product Stock
        • Update Product Stock
        • Bulk Update Product Stock Records
      • Product Questions & Answers
        • Retrieve Product Question
        • Retrieve Product Answer
        • List Product Questions
        • List Answers for Question
        • Create Product Question
        • Create Product Answer
        • Update Product Question
        • Update Product Answer
        • Delete Product Question
        • Delete Product Answer
        • List Store Product Questions
      • Product Reviews
        • List Reviews
        • Count Reviews
      • Digital Products
        • Create Downloadable Product
        • Generate Upload URL
        • Upload File to S3
        • Create Product Downloadable
        • Get Product Downloadables
        • Delete Product Downloadable
        • List Store Downloadables
        • Create Store Downloadable
        • Delete Store Downloadable
    • Inventories
      • Create Location
      • Update Location
      • Update Product Stock for Location
      • List Locations
      • View Location
    • Shipping
      • List Store Shipping Methods
    • Marketing
      • Coupons
        • List Coupons
        • View Coupon
        • Create Coupon
        • Generate Bulk Coupons
        • Duplicate Coupon
        • Duplicate Bulk Coupons
        • Update Coupon Note
        • Update Coupon
        • Update Bulk Coupons
        • Update Coupon Status
        • Update Bulk Coupons Status
        • Delete Coupon
        • Delete Bulk Coupons
      • Bundle Offers
        • List Bundle Offers
        • View Bundle Offer
        • Create Bundle Offer
        • Update Bundle Offer
        • Delete Bundle Offer
        • Bulk Delete Bundle Offers
      • Loyalty Program
        • Loyalty Program Status
        • Loyalty Program Details
        • Loyalty Program Info
        • Customer Loyalty Summary
        • Customer Points History
        • Activate Loyalty Program
        • Set Points Expiration Rules
        • Update Cashback Rule
        • Create Points Redemption Method
        • Update Points Redemption Method
        • Delete Points Redemption Method
        • Adjust Customer Points
        • Update Redemption Method Status
        • Loyalty Program Analytics
        • Loyalty Program Settings
      • Customer Wallets
        • Cashback Rules
          • List Cashback Rules
          • Create Cashback Rule
          • Retrieve Cashback Rule
          • Update Cashback Rule
          • Delete Cashback Rule
        • Wallet Analytics
          • Get Wallet Analytics
          • Get Wallet Analytics by Rule
        • Manager Customer Wallet
          • List Customer Wallets
          • Bulk Credit Customer Wallets
          • Bulk Debit Customer Wallets
          • Export Customer Wallets
          • Get Customer Wallet Balance
          • Credit Customer Wallet
          • Debit Customer Wallet
          • Customer Pending Cashbacks
          • Customer Wallet Transactions
    • Customers
      • Customer Tags
        • List Customer Tags
        • Add Customer Tag
        • Ω’Update Customer Tag
        • Delete Customer Tag
      • Customer Portal Tickets
        • List Customer Portal Tickets
        • Customer Portal Ticket Detail
      • List Customers
      • View Customer
      • Primary Customer List
      • FInd Customer By Phone Number
      • Customer Profile
      • Add Customer
      • Update Customer
      • Delete Customer
      • Add Bulk Customers
      • Import Customers
    • Store Settings
      • πŸ“„ User Roles and Permissions
      • Retrieve Store Manager Profile
      • Retrieve VAT Settings
      • List Payment Methods
      • List Store Operating Countries
      • OneSignal Store Settings
      • Create or Update OneSignal Store Settings
      • Get Payment Options
    • Countries and Cities
      • List Countries
      • List Cities for Country
    • Blogs
      • Settings
        • Create
        • List
      • Categories
        • Create
        • List
        • Bulk Activate
        • Bulk Deactivate
        • Bulk Delete
        • Retrieve
        • Destroy
        • Partial Update
        • Bulk Add To Posts
        • Bulk Remove From Posts
        • Bulk Delete Blog Categories
      • Posts
        • Media
          • List
          • Create
          • Destroy
        • Create
        • List
        • Retrieve
        • Partial Update
        • Destroy
        • Archive
        • Publish
        • Bulk Archive
        • Bulk Publish
        • Bulk Delete
        • Export Posts
        • Import Template
        • Import Posts
      • Tags
        • Create
        • List
        • Retrieve
        • Partial Update
        • Destroy
        • Bulk Delete Tags
        • Bulk Add To Posts
        • Bulk Remove From Posts
      • Storefront
        • Posts
          • List
          • Retrieve
        • Categories
          • List
          • Retrieve
        • Tags
          • List
  • Webhooks
    • Overview
    • Webhook Health Tracking
    • Events
      • Order
      • Product
      • Abandoned Cart
      • Customer
      • Product Category
    • APIs
      • Health Summary
      • Broken Webhooks
      • Recover Broken Webhooks
      • List Webhooks
      • Create Webhook
      • Delete Webhook
      • Delete Webhook By subscriber
  • App Management
    • Events
    • APIs
      • Subscription Details
      • Update Usage-Based Charges
  1. Authorization

OAuth Errors & Troubleshooting

Every way Zid can refuse an authorization request or an authenticated API call, what each
one actually means, and what to do about it.
Authorization fails in three different places, and each returns a different format.
Identify where you are before reading further:
#Where it failedWhat you seeJump to
1The authorize redirect β€” the merchant clicked InstallBrowser lands on the merchant's dashboard with ?error_code=EC…Install & authorize errors
2The token endpoint β€” POST https://oauth.zid.sa/oauth/tokenA JSON error bodyToken endpoint errors
3An API call β€” https://api.zid.sa/v1/…Zid's error envelope with a message.codeAPI request errors
If you are in a hurry: go straight to the decision tree.

Two response shapes you must handle#

Zid's standard error envelope#

Used by every API endpoint, and by the token endpoint for most failures:
{
  "status": "error",
  "message": {
    "type": "error",
    "code": "ERROR_SESSION_INVALID",
    "name": "Sorry",
    "description": "Invalid authentication"
  }
}
message.code is what you branch on. It is stable.
message.description is not. It is editorial, localised text. See
Your errors are in Arabic.
code and name are sometimes null β€” the table in each section says when.

Validation errors use a different envelope#

HTTP 422 replaces message with a validations array and sets a different status:
{
  "status": "validation_error",
  "message": {
    "type": "validation_errors",
    "code": "ERROR_POPUP_OK",
    "name": "Inputs",
    "description": "Input errors!",
    "validations": [
      { "field": "email", "errors": ["The email field is required."] }
    ]
  }
}
A parser that assumes message.description is always the whole story will silently drop
the per-field detail. Check status first.

1. Install & authorize errors#

When the authorize step fails, Zid does not show your app an error. It redirects the
merchant's browser to their Zid dashboard with an error_code query parameter:
https://web.zid.sa/…?error_code=EC3
⚠️ The merchant will not notice this. The dashboard does not currently render a
message for error_code, so from the merchant's point of view the install button simply
did nothing. When a merchant reports "installing your app does nothing", ask them to
paste the full URL from their address bar
β€” the error_code is the only diagnostic
signal that exists.

The EC codes#

CodeOAuth equivalentWhat actually happenedWhat to do
EC1invalid_scopeYour app is requesting a scope Zid does not recognise. Usually a scope that was renamed or retired while your app still had it granted.Open your app in the Partner Dashboard, re-save the scope list, and confirm every scope appears in the current scopes list. Contact Partner Support if a scope you need is missing.
EC2invalid_grantThe client_id in the authorize URL does not belong to the app record Zid resolved. This is an identity-hijack guard.You are almost certainly mixing environments or apps β€” a staging client_id with a production authorize URL, or a copied URL from another app. Re-copy client_id from the Partner Dashboard for this app.
EC3access_deniedTwo distinct causes: (a) the merchant clicked Decline on the consent screen; (b) your app is not published and this store has no private grant for it.(a) Nothing to fix β€” ask the merchant to retry and approve. (b) Check your app's listing status. For testing, use a development store or request a private grant for the merchant's store.
EC4access_denied (Zid-specific)Your app has paid plans, and this store has no active plan subscription for it.The merchant must complete or renew the app subscription before the install can finish. Confirm the subscription state in the Partner Dashboard before debugging anything else.
EC5invalid_requestclient_id was missing, empty, or matched no app at all.Check the authorize URL actually carries client_id=…. A common cause is a templating bug emitting an empty value.
UNKNOWNβ€”An OAuth error outside the mapped set.Capture the full redirect URL, the client_id, the store id, and the timestamp, and open a ticket.
EC3 is the ambiguous one. "Merchant declined" and "app not visible to this store"
produce the identical code. Rule out the listing/private-grant cause first, because it is
the one you can fix.

Errors that are not redirects#

Three authorize-step failures render an HTML error page instead, so the merchant sees a
real message:
SituationWhat the merchant sees
Your app needs the Direct Integration (APIs) add-on and the store is not subscribedHTTP 403 page: "This app needs the Direct Integration (APIs) add-on. Subscribe to it to activate the app." β€” with a button to subscribe
The app requires an active Mazeed account that the store does not haveHTTP 403 page: "This app requires your store to be activated in Mazeed. Please activate to complete the installation."
Your app is a shipping app, or the client_id resolved to no appHTTP error page carrying the real OAuth message and status, rather than an EC redirect
If a merchant sends you a screenshot of a Zid-branded error card rather than a dashboard,
you are in this group β€” read the message on the card; it is accurate.

"Opening the authorize URL does nothing at all"#

No error_code, no error page, just the merchant's dashboard. The merchant is not logged
in.
Zid redirects an unauthenticated merchant to their dashboard
(…/apps/{app_id}, or the shipping-providers page for shipping apps) rather than returning
an OAuth error.
Fix: have the merchant sign in to their Zid store in the same browser, then start the
install again.

Install checklist#

Before debugging deeper, verify all five:
1.
client_id matches this app in this environment.
2.
redirect_uri exactly matches a registered allowed redirection URL β€” scheme, host,
port, path, trailing slash.
3.
response_type=code.
4.
The app's listing status permits this store to see it (published, or private-granted).
5.
If the app has plans, this store has an active subscription.

2. Token endpoint errors#

POST https://oauth.zid.sa/oauth/token
⚠️ Zid's token endpoint returns two different error shapes. Most failures come back in
Zid's standard envelope with HTTP 400. One specific failure comes back in the RFC 6749
shape. Handle both β€” branch on whether the body has a top-level error key.

Shape A β€” Zid envelope (most failures)#

HTTP 400, with code and name both null. The OAuth reason is in
message.description:
{
  "status": "error",
  "message": {
    "type": "error",
    "code": null,
    "name": null,
    "description": "Client authentication failed"
  }
}
Note on status codes: these are returned as 400 even where RFC 6749 specifies a
different status β€” invalid_client, for example, is 400 here rather than 401. Do not
dispatch on the HTTP status alone at this endpoint; read the description.
description containsOAuth errorCauseFix
"Client authentication failed"invalid_clientWrong client_id, wrong client_secret, or mixed environmentsRe-copy both from the Partner Dashboard. Check for whitespace or newlines from copy-paste, and that your secret store isn't truncating.
"authorization grant … is invalid, expired, revoked, does not match the redirection URI …, or was issued to another client"invalid_grantThe big one. See the breakdown below.See below
"The refresh token is invalid"β€”Refresh token expired (1 year), already used, or revoked by an uninstallIf the merchant still has your app installed, restart the install flow. If not, mark the installation inactive.
"The request is missing a required parameter …"invalid_requestA required form field was absent or emptyVerify all five fields are present in the body, not the query string
"The authorization grant type is not supported"unsupported_grant_typeTypo in grant_typeMust be exactly authorization_code or refresh_token
"The requested scope is invalid, unknown, or malformed"invalid_scopeRequested a scope your app does not holdOmit scope to receive your app's current grants, or request a subset of them
"The authorization server encountered an unexpected condition …"server_errorZid-side faultRetry with backoff; if it persists, open a ticket with the timestamp

invalid_grant β€” the five causes, in order of likelihood#

invalid_grant is one message for five different problems. Work through them in this order:
1.
The code expired. Authorization codes live 10 minutes. If you hand the callback
off to a queue or a background worker, you will hit this intermittently. Exchange the
code synchronously inside the callback request.
2.
The code was already used. Codes are single-use. If your token exchange failed and
you retried with the same code, the retry fails for a different reason than the original
β€” which hides the real error. Never retry a code; restart the flow.
3.
redirect_uri mismatch. The redirect_uri sent to /oauth/token must be
byte-identical to the one sent to /oauth/authorize, and both must match a registered
allowed redirection URL. https://x.com/cb and https://x.com/cb/ are different values.
4.
Issued to another client. The code was obtained with a different client_id than the
one exchanging it β€” classic staging/production mix-up.
5.
Refreshing with a stale refresh token. A successful refresh revokes the old refresh
token
and issues a new one. If your token write is not atomic, or two workers refresh
the same merchant concurrently, one of them holds a dead token. Serialise refreshes per
merchant (a per-store lock) and persist the whole new token set in one transaction.

Shape B β€” RFC 6749 shape (one specific failure)#

HTTP 400, with a top-level error key:
{
  "error": "invalid_request",
  "error_description": "App installation is not allowed"
}
Cause: your app has paid plans, but the store has no active plan subscription for it.
This happens after the credentials were accepted β€” the token was minted and then
immediately revoked, so any token value you may have captured from an earlier log line is
already dead.
Fix: the merchant must activate or renew the app subscription, then install again. This
is the token-endpoint sibling of EC4; seeing both for one merchant is
consistent, not contradictory.

3. API request errors#

https://api.zid.sa/v1/… with Authorization + X-Manager-Token.
All of these use Zid's standard envelope except 422.

401 β€” authentication#

message.codedescription (en)CauseWhat to do
ERROR_SESSION_MISSING"Please login first."Neither X-Manager-Token nor Access-Token was sentAdd the X-Manager-Token header with the access_token value from the token response
ERROR_SESSION_INVALID"Invalid authentication"The manager token could not be decrypted β€” it is corrupted, truncated, re-encoded, or from a different environmentSend the access_token value verbatim. Do not URL-encode, re-encrypt, trim, or pass it through a JSON round-trip that may mangle it. Check your column is wide enough β€” these tokens are long.
ERROR_SESSION_INVALID"Login session expired. Please login again."The manager token is past its expiry, or its underlying record no longer existsRefresh. If a successful refresh does not fix it, the merchant uninstalled your app β€” see the decision tree.
ERROR_SESSION_INVALID"The authentication token has been revoked"The Authorization token was explicitly revokedDo not retry. Treat as uninstalled until the merchant reinstalls.
null"Unauthenticated"The Authorization bearer token was rejected outright β€” missing, malformed, expired, or revokedCheck the header is Authorization: Bearer <authorization> (the authorization field, not access_token). Then refresh.
nulla message mentioning the required scopeYour token is missing a required scope.Do not refresh. See the warning below.
🚨 A missing scope returns 401, not 403.
This is the single most expensive trap in Zid's auth surface. The natural reaction to a
401 is "token expired β†’ refresh" β€” but the refreshed token carries the same scopes, so
the next call 401s again, and your client loops until it trips the rate limiter and starts
returning 429 instead.
How to tell it apart from a real expiry: a scope 401 has message.code: null and a
description naming a scope, and it does not go away after a successful refresh.
Defensive rule: never retry a 401 more than once per token. Refresh at most once, and
if the same endpoint 401s again with the new token, stop and treat it as a scope problem.
Fix: add the scope to your app in the Partner Dashboard. Existing merchants must then
re-approve it β€” see Scope and consent errors.

403 β€” authorized, but not allowed#

message.codedescription (en)CauseWhat to do
APP_PERMISSIONS_PENDING_APPROVAL"Permissions pending merchant approval."You added scopes; this merchant has not approved them yetOnly the merchant can clear it β€” by deactivating and reactivating your app. Their subscription is not affected. See below.
APP_PERMISSION_REMOVED"This app no longer has the permission this request needs."A scope was removed from your app after the token was issuedRestore the scope in the Partner Dashboard, or stop calling the endpoint. Note: enforcement of this is currently gated β€” today you are more likely to see the 401 scope error above.
ERROR_POPUP_OK"Subscription is not active."The merchant's own Zid store subscription has lapsed β€” nothing to do with your appNot fixable by you. The merchant must renew their Zid plan. Pause syncs for that store and retry later.
ERROR_POPUP_OK"This action is not allowed on demo store"You sent a non-GET request to a demo storeDemo stores are read-only. Only GET requests succeed.
ERROR_POPUP_BACK"Store mobile isn't verified"The merchant has not verified their mobile (or email, for non-KSA stores)The merchant must verify in their dashboard
ERROR_POPUP_BACK"Store readiness isn't completed"The store has not finished onboardingThe merchant must complete store setup
ERROR_POPUP_OK"Permission Denied"The merchant user your token belongs to lacks the role permission this endpoint requiresUsually means the token was issued to a sub-user, not the store owner. Have the owner authorize, or have them grant the permission.
The 403s split cleanly into two groups. APP_PERMISSIONS_PENDING_APPROVAL and
APP_PERMISSION_REMOVED are your problem (scope configuration). Everything else is
the merchant's account state and no amount of retrying or re-authorizing will change
it. Surface those to the merchant rather than to your error logs.

404, 422, 429, 500#

HTTPmessage.codeCauseWhat to do
404ERROR_SESSION_INVALID"Store not found." The user behind the token has no storeMark the installation broken and open a ticket with the store id β€” this should not normally happen
422ERROR_POPUP_OKValidation failure β€” uses the validation envelopeRead message.validations[] for per-field detail
429MSG_HIDDENRate limit exceededHonour the Retry-After response header. See below.
500ERROR_POPUP_OK"Something went wrong" β€” server-side faultRetry with exponential backoff. If persistent, open a ticket with the timestamp and X-Request-Id.

Scope and consent errors#

Zid enforces scopes per request, against the app's current configuration β€” not against
whatever was frozen into the token at issue time. Two consequences:

When you add a scope to a live app#

Every existing installation is flagged pending consent for the newly added scope.
Per request, Zid subtracts the un-approved scopes from the live token:
endpoints covered by already-approved scopes β†’ keep working normally;
endpoints needing the new scope β†’ 403 APP_PERMISSIONS_PENDING_APPROVAL.
Until the merchant re-approves, that specific capability is unavailable for that specific
merchant. Different merchants will be in different states at the same time β€” your code must
tolerate this per-store.

How the merchant re-approves#

The merchant deactivates your app, then activates it again, from their Zid dashboard.
There is no separate "approve new permissions" prompt β€” reactivation is the approval
step, because the merchant sees the full, current scope list on the way back in.
Tell the merchant this plainly, and reassure them about the two things they will worry
about:
Deactivating does not cancel your subscription. If the store has a paid subscription
to the app, deactivating leaves it untouched β€” the merchant can reactivate at any time
while the subscription is still valid, at no extra cost.
What you must handle: reactivation issues a fresh token pair carrying every current
scope, delivered the same way the original install was. Your app must accept and persist
new tokens for a store that is already installed β€” overwrite the stored
authorization, access_token and refresh_token for that store rather than rejecting
the callback as a duplicate install.
A reactivation is therefore not a new customer; it is the same installation with new
credentials. If your code keys on "first install only", the merchant will reactivate,
your app will discard the new tokens, and the 403s will continue β€” which looks to everyone
like the re-approval didn't work.
🚨 If your app uses a wildcard token, adding a scope takes the whole integration down
for that merchant.
A wildcard token cannot express "everything except these two", so
Zid withholds the app entirely β€” every endpoint returns
403 APP_PERMISSIONS_PENDING_APPROVAL β€” until the merchant re-approves.
Plan wildcard-token scope changes like a release: notify merchants first, expect a
re-consent window, and do not ship on a Friday.

When a scope is removed from your app#

Removed scopes are subtracted the same way, surfacing as 403 APP_PERMISSION_REMOVED with
the offending scopes listed in the response. Enforcement is currently behind a feature
flag and is off by default
, so today a removed scope more commonly shows up as the
401 scope error. Write your handler to treat both the same way.

Handling consent states well#

SignalMeaningRight response
403 APP_PERMISSIONS_PENDING_APPROVAL on some endpointsPartial consentDegrade that feature; keep the rest running. Ask the merchant to deactivate and reactivate the app.
403 APP_PERMISSIONS_PENDING_APPROVAL on every endpointWildcard token, consent pendingPause the integration for that store; ask the merchant to deactivate and reactivate the app
403 APP_PERMISSION_REMOVEDScope config driftFix your app config; alert your own team, not the merchant
Never auto-retry a consent 403 on a timer. It resolves only when a human merchant acts.
Message you can send the merchant, as-is:
To finish updating the app's permissions, please open your Zid dashboard, deactivate
the app and then activate it again. Your subscription is not affected β€” you can
reactivate at any time while it is valid, at no extra cost.

429 β€” rate limiting#

Zid rate-limits app requests per app, per store β€” the default is 60 requests per 60
seconds
for each store your app serves. Per-app and per-store limits can be configured by
Zid, so treat 60/60s as a floor, not a contract.
Response: HTTP 429, message.code = MSG_HIDDEN, plus a Retry-After header giving
the seconds until the window resets.
Handle it like this:
1.
Read Retry-After and wait that long. Do not use a fixed sleep.
2.
Back off per store, not globally β€” one busy merchant should not stall the others.
3.
Serialise bulk work per store rather than fanning out concurrently.
The most common cause of a 429 is not traffic volume. It is the
401 refresh loop: a missing scope returns 401, your client refreshes
and retries, the new token is missing the same scope, and the loop consumes the whole
window in seconds. If you see 429s with low real throughput, look for a scope error
underneath.
See also: Rate Limiting.

Your errors are in Arabic#

If you omit the Accept-Language header β€” or send anything other than ar or en β€”
Zid replies in Arabic.
Arabic is the default locale, not a fallback to English.
Partners commonly hit this when a staging environment sets the header and production does
not, producing error strings their log parsers have never seen.
The durable fix is not the header. Branch your logic on message.code, never on
message.description. Codes are stable identifiers; descriptions are localised editorial
text in two languages and can be reworded at any time.

Uninstalls, revocation, and propagation delay#

When a merchant uninstalls your app, Zid:
1.
sends you an app.market.application.uninstall webhook;
2.
deletes the manager-token records for your app on that store;
3.
revokes the Authorization tokens and their refresh tokens;
4.
clears the token caches.
Nothing you hold for that store still works, and refresh cannot recover it. The only
path back is the merchant installing your app again, which starts a fresh authorization
flow.
An uninstalled app and an expired session return the identical error β€” 401 with
ERROR_SESSION_INVALID and "Login session expired." The distinguishing test is whether a
refresh fixes it. See the decision tree.
Propagation delay. Token state is cached for up to 5 minutes. A token can keep
working for a short window after revocation, and conversely a freshly-fixed permission can
take a few minutes to take effect. When testing revocation behaviour, wait 5 minutes before
concluding anything.
Treat the uninstall webhook as authoritative and mark the installation inactive on
receipt, rather than discovering it later through 401s.
One webhook caveat: app.market.application.authorized is dispatched during the
authorize step, before the token exchange completes. An install can therefore fail
after you have already received authorized. Use app.market.application.install as
your signal that an installation is real and usable.

The decision tree#

A request failed. Where?
β”‚
β”œβ”€ The merchant clicked Install and landed on their dashboard
β”‚  └─ Read ?error_code= from the URL they paste β†’ Β§1 EC table
β”‚     β”œβ”€ no error_code at all β†’ merchant was not logged in
β”‚     └─ a Zid-branded error card instead β†’ read the card; it's accurate
β”‚
β”œβ”€ POST /oauth/token returned an error
β”‚  β”œβ”€ body has a top-level "error" key
β”‚  β”‚  └─ "App installation is not allowed" β†’ no active app subscription; merchant must
β”‚  β”‚     subscribe, then reinstall
β”‚  └─ body is Zid's envelope (code: null) β†’ read message.description
β”‚     β”œβ”€ "Client authentication failed"  β†’ client_id / client_secret / wrong environment
β”‚     β”œβ”€ "...grant...is invalid, expired..." β†’ work the 5 invalid_grant causes in order
β”‚     └─ "The refresh token is invalid"  β†’ refresh token dead (1y expiry, reused, or
β”‚                                           uninstalled) β†’ restart install flow
β”‚
└─ An API call to api.zid.sa failed
   β”‚
   β”œβ”€ 429 β†’ honour Retry-After, back off per store.
   β”‚        Low real traffic? Look for a 401 loop underneath.
   β”‚
   β”œβ”€ 403 β†’ is message.code APP_PERMISSIONS_PENDING_APPROVAL / APP_PERMISSION_REMOVED?
   β”‚        β”œβ”€ yes β†’ YOUR scope config. Do NOT retry on a timer.
   β”‚        β”‚        PENDING_APPROVAL β†’ merchant deactivates + reactivates the app
   β”‚        β”‚           (subscription unaffected). Be ready to store the NEW tokens.
   β”‚        β”‚        PERMISSION_REMOVED β†’ restore the scope in the Partner Dashboard.
   β”‚        └─ no  β†’ MERCHANT account state (subscription, verification, readiness,
   β”‚                 permissions, demo store). Not fixable by you β€” surface it to them.
   β”‚
   β”œβ”€ 422 β†’ validation envelope; read message.validations[]
   β”‚
   β”œβ”€ 404 ERROR_SESSION_INVALID β†’ store missing; open a ticket with the store id
   β”‚
   └─ 401 β†’ Refresh the token ONCE.
            β”‚
            β”œβ”€ Refresh succeeded, call now works
            β”‚     β†’ ordinary expiry. You are refreshing too late:
            β”‚       move to a scheduled refresh (~every 10 months).
            β”‚
            β”œβ”€ Refresh succeeded, call still 401s
            β”‚     β†’ MISSING SCOPE. Stop retrying immediately.
            β”‚       Add the scope; existing merchants must re-approve.
            β”‚
            └─ Refresh itself failed
                  β†’ the merchant uninstalled (or tokens were revoked).
                    Mark the installation inactive. Wait for a reinstall.

Preventive checklist#

Most authorization tickets Zid receives are one of these six things. Ship all six:
1.
Refresh on a schedule, not on a 401. Roughly every 10 months, per merchant, from a
cron job. The refresh token lasts 1 year while the access token lasts 3 β€” so a
reactive strategy never fires at all: no 401 arrives in year one, the refresh token
expires unnoticed, and two years later the integration dies with no way back except
asking every affected merchant to reinstall.
2.
Persist the full token set atomically on every refresh β€” new authorization, new
access_token, new refresh_token, new expiry. The old refresh token is revoked the
moment the new one is issued.
3.
Lock refreshes per store. Two concurrent refreshes for one merchant leave one worker
holding a revoked token.
4.
Cap 401 retries at one per token. This single rule prevents the scope-error refresh
loop and the 429s it causes.
5.
Branch on message.code, never on message.description. Descriptions are localised
(Arabic by default) and editorial.
6.
Act on the uninstall webhook immediately β€” mark the installation inactive rather than
discovering it through failed calls days later.
7.
Accept new tokens for a store you already know. A merchant reactivating your app
(to approve new scopes, or after deactivating it) sends you a fresh token pair for an
existing installation. Overwrite the stored tokens; do not discard the delivery as a
duplicate install.

Getting help#

When opening a ticket, include:
the store id and your app id;
the UTC timestamp of the failed request;
the X-Request-Id response header, if you captured it;
the full HTTP status and response body, including message.code;
for install failures, the complete redirect URL including error_code;
what you have already ruled out from this page.
Channels: Partner Help Center Β·

Related#

Authorization β€” the OAuth 2.0 flow itself
Responses β€” the general response envelope
Rate Limiting
Zid App Activation & OAuth Policy
Webhooks Overview
Modified atΒ 2026-10-19 14:47:41
Previous
Public Apps OAuth Requirements
Next
Zid Bridge Tool
Built with