Skip to content

API keys and merchant access (ADSR)

ADSR apps do not run the OAuth flow. A mall reporting feed is a nightly batch job pulling a hundred tenants, often from a system that has no browser anywhere near it, and the thing the tenant actually cares about is which stores you can see. So ADSR uses a static credential plus explicit, store-level merchant approval:

  1. An API key identifies your app. You create it in the console; it never expires.
  2. An access request asks a specific merchant for a specific set of stores. Their account owner approves it in their back office.

Neither half works alone. A key with no approved request reads nothing; an approved request with no key has nothing to authenticate.

Create keys under Apps → your app → API keys. Give the key a name you will recognise in six months — mall-nightly-prod, not key 2.

Shown once — copy it now
name mall-nightly-prod
env PRODUCTION
key lp_ak_pk_7f3c91b2e84a05d6c7b1a394f8e02d5c6a7b9013
PropertyDetail
Formatlp_ak_pk_… for production, lp_ak_sb_… for sandbox
VisibilityDisplayed once at creation. Only a hash is stored — there is no endpoint that reads it back
LifetimeNo expiry. It works until you revoke it
LimitFive active keys per app, per environment
ProductionOnly issued once the app is APPROVED. Sandbox keys are available immediately

Rotate by creating the replacement key first, deploying it, and revoking the old one once nothing is using it — two keys can be active at the same time, so rotation needs no downtime. Revocation is immediate at the source and takes effect on live traffic within five minutes, as verification caches expire.

A request names the merchant and the stores you need.

  1. Every LithosPOS merchant has a numeric company id. The tenant gives it to you — usually alongside the store list the landlord agreed to.

  2. From Apps → your app → Access:

    FieldRequiredNotes
    companyIdyesThe tenant’s LithosPOS company id
    storeIdsnoThe stores you need. Omit to request every store in the company
    notenoOne line the merchant will read — say which mall and which lease this is for

    Access requests are a production mechanism: the company must exist, be active, and be a live merchant. Sandbox tenants need no request — see Sandbox below.

  3. The company’s account owner sees the request in Settings → Connected apps in their back office, with your app name, your organization, the stores you asked for and your note. They can approve it as filed, approve it with fewer stores than you asked for, or deny it. LithosPOS emails them when the request arrives.

    You cannot widen what they granted. There is one live connection per merchant: filing a second request while one is pending, or while a grant is active, is refused with developer.access_request_exists. Changing the store list — a tenant opening a second unit in the mall — means the merchant removing the connection and approving a fresh request.

  4. The grant exists the moment they approve, and live traffic picks it up within five minutes as verification caches expire.

A request that is neither approved nor denied expires after 30 days. Cancel a pending request yourself from the same screen if the deal changes — a stale request that a merchant approves six months later is worse than no request at all.

Send the key in X-API-Key and name the merchant with a companyId query parameter.

Terminal window
curl -s "https://api.lithospos.com/v1/reports/daily-sales?companyId=1033&storeId=2&from=2026-07-01&to=2026-07-31" \
-H 'X-API-Key: lp_ak_pk_7f3c91b2e84a05d6c7b1a394f8e02d5c6a7b9013'
200 OK
{
"data": [
{
"date": "2026-07-01",
"grossSales": 12345.67,
"taxAmount": 617.28,
"discountAmount": 120.00,
"refundAmount": 89.50,
"netSales": 11518.89,
"transactionCount": 214
}
]
}

Authorization: Bearer lp_ak_pk_… is accepted as an alternative to the header if your HTTP client makes bearer auth easier to configure. Everything else — the envelope, the error codes, the rate limit, pagination — is exactly as documented for token-authenticated calls.

The key inherits your app’s ADSR scope bundle — adsr.read, reports.read, stores.read — which resolves to six routes:

RoutePurpose
GET /v1/reports/daily-salesThe canonical daily row
GET /v1/storesThe tenant’s stores, so you can map ids to names
GET /v1/reports/business-summaryTotals for reconciliation
GET /v1/reports/salesThe sales list for the window
GET /v1/reports/itemsItem-level sales
GET /v1/reports/pay-typesSplit by payment type

Anything outside the bundle returns 403 partner.scope_denied. That narrowness is the point: a tenant approving a landlord’s reporting provider can see exactly what it will read.

If the merchant granted specific stores, that restriction applies whether or not you send storeId:

  • No storeId — every store in the grant, and nothing else.
  • storeId inside the grant — that store.
  • storeId outside the grant — no rows, not an error. Reconcile against GET /v1/stores rather than guessing why a store is empty.

Sandbox keys need no access request. Your organization’s own sandbox merchants are granted to every app in the organization automatically, so a lp_ak_sb_… key works the moment you create it.

Terminal window
curl -s "https://api.lithospos.com/v1/reports/daily-sales?companyId=104271&from=2026-07-01&to=2026-07-07" \
-H 'X-API-Key: lp_ak_sb_2b8d40f19c7e35a6d0f8b241e93c5a70d6491fb2'

companyId is still required — the parameter is a property of key authentication, not of the environment. A sandbox key against a live company, or a production key against a demo one, fails the environment check the same way credentials always have.

Failures on the call itself use the partner API envelope, with a stable code:

CodeStatusMeaningFix
partner.api_key_invalid401Unknown, revoked or malformed keyCheck the secret you deployed; create a new key if it was revoked
partner.company_param_required400companyId missing from a key-authenticated requestAdd it
partner.company_not_granted403That merchant has not approved your app, or the approval was withdrawnFile an access request, or stop scheduling the tenant
partner.api_key_product403The key belongs to an app whose product is not ADSRKeys are an ADSR mechanism; other products use OAuth
partner.api_key_unavailable503Key verification is temporarily unavailableRetry with backoff. Requests fail closed rather than being let through unverified
gateway.company_required400companyId missing, rejected at the edgeAdd it
gateway.company_unknown404No merchant with that idConfirm the id with the tenant

Console-side failures while managing keys and requests use the developer API envelope:

CodeStatusMeaning
developer.api_keys_adsr_only400Only ADSR apps can hold API keys
developer.api_key_limit400Five active keys already exist for that environment
developer.app_not_approved409Production key or production access request before review approval
developer.access_requests_adsr_only400Only ADSR apps file access requests
developer.access_request_exists409A pending request, or a live grant, already covers that merchant
developer.merchant_not_found404No company with that id
developer.merchant_inactive403The company exists but is not active
developer.env_mismatch403An access request naming a demo company — sandbox tenants are granted automatically

The operational side of a mall feed — scheduling, trading days, backfill, reconciliation and what to do when a tenant disconnects — is covered in mall integration. Two things change when you authenticate with a key rather than a token:

  • There is no token to cache. Send the key on every request. The 300-requests-per-minute app budget still applies, so keep staggering tenants.
  • A withdrawn approval surfaces as partner.company_not_granted rather than a mint failure. Treat it as “stop, permanently” for that tenant and alert your operations team.