Authentication
API credentials, QR login and customer JWTs
Authentication
The Shop API has two authentication schemes.
| Scheme | Headers | Used for |
|---|---|---|
| API credentials | X-API-Key + X-API-Secret | Nearly everything: server-to-server and POS calls |
| JWT | Authorization: Bearer <token> | The few customer-scoped endpoints, such as GET /{locale}/shop/auth/me |
API credentials
Generate a key/secret pair in the Partners Portal →
API Credentials. Pick the environment when you create them: staging credentials only
work against staging-api.loyalty.lt, production ones only against api.loyalty.lt.
Both headers are required on every request — sending one without the other is a 401.
curl -X GET "https://api.loyalty.lt/lt/shop/loyalty-cards" \
-H "X-API-Key: $LOYALTY_API_KEY" \
-H "X-API-Secret: $LOYALTY_API_SECRET" \
-H "Content-Type: application/json"The secret is shown once and must stay on your server. Never ship it in frontend code or a mobile app bundle.
Check a pair without touching real data:
curl -X POST "https://api.loyalty.lt/lt/shop/validate-credentials" \
-H "X-API-Key: $LOYALTY_API_KEY" \
-H "X-API-Secret: $LOYALTY_API_SECRET"A credential can also be restricted to specific IP addresses. Calls from anywhere else
return 403.
QR login
QR login signs a customer in on a screen with no keyboard — a POS terminal or a checkout page. You show a QR code, they scan it in the Loyalty.lt app, and you receive their JWT.
Open a session
curl -X POST "https://api.loyalty.lt/lt/shop/auth/qr-login/generate" \
-H "X-API-Key: $LOYALTY_API_KEY" \
-H "X-API-Secret: $LOYALTY_API_SECRET" \
-H "Content-Type: application/json" \
-d '{"device_name": "Till 3", "shop_id": 1}'The response carries session_id, the qr_code payload to render, manual_code
and expires_at. Sessions last 5 minutes.
Show manual_code next to the QR. It is nine digits, formatted XXX-XXX-XXX, and
the app has an entry sheet for it — a customer whose camera will not focus, or who is
standing under a light that washes the screen out, types it instead of being turned
away.
Render the code and subscribe
The result arrives on the session's channel. qr-login.{session_id} is public — the
unguessable session ID is the secret — so there is nothing to authorize.
const { data: config } = await (await fetch(
'https://api.loyalty.lt/lt/shop/realtime/config',
{ headers: { 'X-API-Key': apiKey, 'X-API-Secret': apiSecret } },
)).json();
const pusher = new Pusher(config.key, {
wsHost: config.host,
wsPort: config.port,
wssPort: config.port,
forceTLS: config.scheme === 'https',
enabledTransports: ['ws', 'wss'],
cluster: '',
disableStats: true,
});
pusher.subscribe(`qr-login.${session_id}`).bind('status_update', (p) => {
if (p.status === 'authenticated') signIn(p.token, p.user);
if (p.status === 'expired') regenerate();
});Every status value and the full payload are in Realtime.
Use the token
On status: "authenticated" the payload carries the customer's token and user.
Send the token as Authorization: Bearer <token> on customer-scoped endpoints.
If the customer does not have the app yet, text them the download link rather than leaving them stuck at the till:
curl -X POST "https://api.loyalty.lt/lt/shop/auth/send-app-link" \
-H "X-API-Key: $LOYALTY_API_KEY" \
-H "X-API-Secret: $LOYALTY_API_SECRET" \
-H "Content-Type: application/json" \
-d '{"phone": "+37060000001", "shop_id": 1}'If you cannot open a WebSocket
Only then, poll. It costs a request per second per session while the customer waits.
curl -X POST "https://api.loyalty.lt/lt/shop/auth/qr-login/poll/{session_id}" \
-H "X-API-Key: $LOYALTY_API_KEY" \
-H "X-API-Secret: $LOYALTY_API_SECRET"Customer JWT
The token from QR login identifies one customer:
curl -X GET "https://api.loyalty.lt/lt/shop/auth/me" \
-H "Authorization: Bearer $TOKEN"It is also what signs a private realtime channel — see Realtime.
The Shop API has no password login, refresh or logout endpoint. A token comes from QR
login; when it expires, run QR login again. Partner-account sign-in for the Partners
Portal is a separate flow under /{locale}/partners/auth/... and is not part of the
Shop API.
Errors
| Status | Cause |
|---|---|
401 | A header missing, wrong key/secret, wrong environment, or an expired credential |
403 | IP not allowlisted, or the partner's plan does not include API access |
Both return the standard envelope with a request_id — see
API Overview.