Seller authorization
Before you can read or write a seller's data, they must grant your app scoped access. The flow is OAuth-style: you send them to a consent page, they authenticate, and you exchange the resulting code for tokens.
Signing the auth endpoints. /open/v1/auth/token,
/token/refresh and /revoke are signed exactly like any other call — HMAC v3 with a nonce.
Send X-MallPlus-Signature-Version and X-MallPlus-Nonce with those signed requests.
They are app-authenticated, so they carry no seller token. Signature version 2 is rejected on these
endpoints.
{timestamp}:{clientId}:GET:/open/v1/auth/authorize:{queryCanonical}:{sha256("")}:{nonce}
The flow
Sandbox consent sequence
- seller-verify
Validate the sandbox seller login on
POST /open/v1/auth/seller-verify. Sandbox consent uses the fixed OTP111111; no SMS is sent. - seller-verify-otp
Verify the OTP on
POST /open/v1/auth/seller-verify-otp. - seller-consent
Record consent on
POST /open/v1/auth/seller-consent, then continue to the redirect callback.
- Build the authorization URLGET/open/v1/auth/authorizeHMAC-signed, no seller token
Sign the request as usual and redirect the seller to it. The platform validates your registered
redirect_uri— a mismatch returnsREDIRECT_URL_MISMATCH. - The seller consents
They authenticate and approve the scopes your app requests. On sandbox, identity is a sandbox shop (Shop ID plus password). If they decline, the platform calls
POST /open/v1/auth/denyand returns them to your registered redirect URL. - Receive the authorization code
The seller lands back on your
redirect_uriwith acodeparameter.const res = await fetch(authorizeUrl, { redirect: 'manual' })The code expires in 10 minutes and is single-use. Exchange it immediately. A second exchange returns
AUTH_CODE_USED. - Exchange it for tokensPOST/open/v1/auth/token
The request body is strict. Send
code,client_id, andseller_id. Standard OAuth fields such asgrant_type,redirect_uri, andclient_secretare unsupported and rejected.{ "code": "auth_code_...", "client_id": "mp_...", "seller_id": "seller_..." }{ "success": true, "data": { "access_token": "…", "refresh_token": "…", // single-use; rotates on every refresh "expires_in": 14400, // seconds — 4 hours "expires_at": "2026-08-15T13:30:00.000Z", "seller_id": "seller_nike_ph", "seller_name": "Nike Philippines", // may be null "scopes": ["catalog:read", "orders:read", "orders:write"] } }Store
seller_id— it is the value you send asX-MallPlus-Seller-Id.
Checkexpires_atis ISO-8601 UTC. Example token expiry values include"expires_at": "2026-07-16T10:30:00.000Z"on exchange and"expires_at": "2026-07-16T14:30:00.000Z"after refresh.scopesagainst what you requested: a seller can consent to fewer scopes than you asked for, and calls outside the granted set return403 FORBIDDEN. - Call seller-scoped endpoints
Send
X-MallPlus-Access-TokenandX-MallPlus-Seller-Idalongside your five HMAC headers. Remember these two are not signed. - Refresh before expiryPOST/open/v1/auth/token/refresh
Single-flight your refreshes. Refresh tokens rotate on use, and reuse of a rotated-out token is treated as compromise: the entire token chain is revoked and you get
REFRESH_TOKEN_REUSED. Two concurrent refreshes will lock you out and require the seller to re-authorize. Serialise refresh through a mutex or a single worker.
Token lifetimes
| Token | Lifetime | Notes |
|---|---|---|
| Authorization code | 10 minutes | Single-use |
| Access token | 4 hours | SHA-256 hashed at rest |
| Refresh token | 30 days | Rotates on every use; reuse revokes the chain |
Subscribe to authorization.expiring to prompt re-consent before a seller's grant lapses, and to
authorization.revoked to stop calling immediately when they disconnect.
Authorization expiry event
AUTHORIZATION.EXPIRING / authorization.expiring fires 7 days before the 365-day grant expires.
{
"event_type": "authorization.expiring",
"event_id": "evt_01HK...",
"authorization_id": "auth_01HK...",
"expires_at": 1798675200
}
expires_at in the event payload is Unix epoch seconds.
Listing your authorizations
Lists the sellers who have authorized your app — the source of the seller_id values used by
seller-scoped calls and by revocation. Defaults to status=active; pass status=all (or
revoked / expired) for the full history. Standard page/limit pagination.
{
"success": true,
"data": [{
"seller_id": "sel_mWeoDRkTMksY",
"seller_name": "Nike Philippines", // may be null
"scopes": ["catalog:read", "orders:read"],
"status": "active", // active | revoked | expired
"granted_at": "2026-08-15T09:30:00.000Z",
"expires_at": "2027-08-15T09:30:00.000Z" // 365-day wall — re-consent after this
}],
"meta": { "page": 1, "limit": 20, "total": 1 }
}
Revocation
Either side may revoke. The call is HMAC-signed and app-authenticated (no seller token). The body is
strict and requires both ids; client_id must match the authenticated app:
{
"client_id": "mp_...",
"seller_id": "sel_..."
}
The response is { "revoked": true } — false means there was no active grant for
that seller. After revocation every seller-scoped read returns 404 NOT_FOUND rather
than 403 — the platform does not confirm the existence of resources you can no longer see.
Authorization errors
| Code | Meaning | Resolution |
|---|---|---|
| REDIRECT_URL_MISMATCH | Redirect URI is not registered for this app | Register it in the console; exact match including scheme and trailing slash |
| INVALID_AUTHORIZATION_CODE | Code not recognised | Restart the consent flow |
| AUTH_CODE_EXPIRED | Older than 10 minutes | Exchange immediately on receipt |
| AUTH_CODE_USED | Already exchanged | Codes are single-use; store the resulting tokens |
| INVALID_REFRESH_TOKEN | Not recognised or already rotated | Re-authorize the seller |
| REFRESH_TOKEN_EXPIRED | Older than 30 days | Re-authorize the seller |
| REFRESH_TOKEN_REUSED | A rotated-out token was replayed — chain revoked | Single-flight your refresh logic, then re-authorize |
| AUTHORIZATION_REVOKED | The seller disconnected your app | Stop calling; prompt re-consent |
| RE_AUTHORIZATION_REQUIRED | Scopes changed or grant invalidated | Send the seller through consent again |