Loyalty.lt
API Basics

Authentication

API credentials, QR login and customer JWTs

Authentication

The Shop API has two authentication schemes.

SchemeHeadersUsed for
API credentialsX-API-Key + X-API-SecretNearly everything: server-to-server and POS calls
JWTAuthorization: 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

StatusCause
401A header missing, wrong key/secret, wrong environment, or an expired credential
403IP not allowlisted, or the partner's plan does not include API access

Both return the standard envelope with a request_id — see API Overview.

Next

Channels, events and payloads — where QR results actually arrive

Test credentials and demo data

On this page