Skip to content

Authentication

The partner API is authenticated with an OAuth2 client credentials grant. Your app holds a client id and secret, exchanges them for a 900-second access token, and sends that token as a bearer credential. There is no user login, no redirect and no refresh token.

POST https://api-v2.lithospos.com/v1/token

Everything you call as a partner — this endpoint included — lives on api-v2.lithospos.com. The gateway proxies token requests through to the developer token service, so the request and response bodies are exactly as documented below.

This endpoint accepts JSON or application/x-www-form-urlencoded. The form encoding exists for OAuth2 tooling that insists on grant_type; the value is ignored beyond validation.

Terminal window
curl -s https://api-v2.lithospos.com/v1/token \
-H 'Content-Type: application/json' \
-d '{
"client_id": "lp_app_sbK2m9Qx7Rt4",
"client_secret": "lp_sk_sb_5ZkQ5vJ2rN8pXw3TfM6bD1yH4sC7aE0g",
"merchant_id": 104271
}'
FieldTypeRequiredNotes
client_idstringyeslp_app_ prefix. sb marker for sandbox credentials, pk for production
client_secretstringyeslp_sk_<env>_…. Shown once at creation or rotation
merchant_idnumberconditionalThe LithosPOS company id. Optional only when the credential is sandbox and the organization has exactly one active sandbox merchant
grant_typestringnoAccepted as client_credentials for OAuth2 clients; other values are rejected
200 OK
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjIwMjYtMDQifQ…",
"token_type": "Bearer",
"expires_in": 900,
"scope": "stores.read items.read items.write categories.read categories.write …",
"api_base_url": "https://api-v2.lithospos.com/v1/"
}
FieldMeaning
access_tokenRS256 JWT. Send as Authorization: Bearer <token>
token_typeAlways Bearer
expires_inLifetime in seconds. Always 900
scopeSpace-delimited resolved scopes for this app’s product
api_base_urlBase URL for every partner request, with a trailing slash. Currently https://api-v2.lithospos.com/v1/

LithosPOS runs separate clusters for the UK, US, India and UAE, and a merchant’s data lives in exactly one of them. You do not need to know which. Every partner request goes to the same host:

https://api-v2.lithospos.com/v1/…

The gateway reads the region claim from your token, routes the request to that merchant’s origin, and returns the response. Routing happens at the edge, so there is no redirect to follow and no extra round trip.

const endpoint = new URL('items', api_base_url);
// → https://api-v2.lithospos.com/v1/items

The access token is a signed JWT. Treat it as opaque for authorisation decisions — the partner API verifies it against the LithosPOS JWKS on every request — but you may read exp for cache scheduling.

ClaimExampleMeaning
audlithospos-appAudience. App tokens are rejected on merchant-user endpoints and vice versa
sub"412"Your app id
orgId88Owning organization
companyId104271The merchant this token may touch — one token, one merchant
regioninCluster holding this merchant’s data. The gateway routes on it; you never send it yourself
productFULLFULL, ONLINE_ORDER or ADSR
scopes["items.read", …]Resolved scope bundle for the product
envSANDBOXSANDBOX or PRODUCTION
allowedIps["203.0.113.7"]Present only when the credential has an allowlist
jti / iat / expToken id and 900-second window

Mint one token per merchant and reuse it. A fresh mint on every request will hit the token endpoint throttle and adds latency for no benefit.

const cache = new Map(); // merchantId → { token, apiBaseUrl, expiresAt }
async function getToken(merchantId) {
const hit = cache.get(merchantId);
// Renew 60s early so an in-flight request never expires mid-call.
if (hit && hit.expiresAt - 60_000 > Date.now()) return hit;
const minted = await mintToken(merchantId);
const entry = {
token: minted.access_token,
apiBaseUrl: minted.api_base_url,
expiresAt: Date.now() + minted.expires_in * 1000,
};
cache.set(merchantId, entry);
return entry;
}

Guard the mint with a single-flight lock so a burst of concurrent requests produces one token, not fifty. Tokens are not revoked when you mint a new one — the old one stays valid until it expires.

Every credential belongs to one environment and the environment is enforced on both ends.

SandboxProduction
Client id markerlp_app_sb…lp_app_pk…
Secret prefixlp_sk_sb_…lp_sk_pk_…
IssuedWith the app, immediatelyBy LithosPOS when app review is approved
Merchants reachableYour organization’s sandbox merchants onlyMerchants that have an active grant for your app
Merchant flagDemo companies onlyLive companies only

A sandbox token presented for a live company, or a production token for a demo company, fails with developer.env_mismatch at mint time and partner.env_mismatch if it somehow reaches the partner API. Production tokens additionally require the app to be APPROVED (developer.app_not_approved) and a grant row linking app to merchant (developer.grant_missing).

Rotate from Apps → Credentials → Rotate. The new secret is displayed once and works immediately. The previous secret keeps working for 24 hours so you can deploy without a window of downtime, then it is revoked automatically.

A credential can carry an allowlist of source addresses. When set, the addresses are copied into the token as allowedIps and enforced on every partner request; calls from anywhere else return 403 partner.ip_denied.

Use it when your integration runs from fixed egress IPs. Leave it empty when it does not — an allowlist that drifts out of date is an outage, not a control.

Failures use the developer API error envelope: { "error": { "code", "message" } }.

CodeStatusCause
developer.invalid_client401Unknown client id, wrong secret, or a revoked credential. Deliberately indistinguishable
developer.merchant_required400merchant_id omitted and it could not be inferred
developer.app_archived403The app has been archived in the console
developer.app_not_approved403Production credential used before app review approval
developer.grant_missing403No active grant links this app to that merchant
developer.env_mismatch403Sandbox credential against a live merchant, or the reverse

See Errors for the complete code table and the partner API envelope.