L Loyalino Developers
Get API key

API reference

The Loyalino REST API lets you read and write loyalty data from your own backend. It is organized around predictable resources, uses standard HTTP verbs, and returns JSON for every response.

Introduction

The base URL for all endpoints is:

text
https://app.loyalino.io/api/v1

Send and receive JSON. Every request must be made over HTTPS and authenticated with an API key. Resource ids, amounts, and timestamps are returned as stable, snake_case fields.

Authentication

Authenticate every request by passing your secret key as a bearer token in the Authorization header. Create and manage keys in your Loyalino admin under Settings, API keys. The secret is shown only once when you create it, so store it somewhere safe.

bash
curl https://app.loyalino.io/api/v1/program \
  -H "Authorization: Bearer lzk_live_your_secret_key"

Keys come in two scopes. A read and write key can do everything. A read only key can call GET endpoints but is rejected with a 403 on any write. Never expose a secret key in client-side code or a public repository. If a key leaks, revoke it from the admin and create a new one.

Keep it secret. Treat your API key like a password. All API calls are server to server.

Errors

Loyalino uses conventional HTTP status codes. Errors return a JSON body with a stable type, a machine readable code, and a human readable message.

json
{
  "error": {
    "type": "authentication_error",
    "code": "invalid_api_key",
    "message": "The API key is invalid or has been revoked."
  }
}
StatusMeaning
200OK. The request succeeded.
201Created. A new resource was created.
400Bad request, for example invalid JSON.
401Missing, invalid, revoked, or expired API key.
403The key does not have permission, for example a read only key on a write.
404The resource does not exist.
422Validation failed, for example a missing required field.
429Too many requests. See rate limits.
500Something went wrong on our side.

Pagination

List endpoints return a list object and accept limit (default 20, maximum 100) and page (1 based) query parameters. The envelope includes the total count and whether more pages remain.

json
{
  "object": "list",
  "data": [ /* ... */ ],
  "page": 1,
  "limit": 20,
  "total_count": 134,
  "has_more": true
}

Rate limits

Each API key may make up to 120 requests per minute. If you exceed the limit you receive a 429 response with a retry_after value in seconds. Back off and retry after that interval.

Customers

A customer is a loyalty member of your shop, with a points balance, lifetime points, and an optional VIP tier.

GET/v1/customers

List your loyalty customers, most recent first. Filter with email for an exact match or search for a name or email substring.

FieldTypeDescription
limitintegerPage size, 1 to 100. Default 20.
pageintegerPage number, 1 based.
emailstringReturn the customer with this exact email.
searchstringMatch name or email substring.
bash
curl https://app.loyalino.io/api/v1/customers?limit=1 \
  -H "Authorization: Bearer lzk_live_xxx"
json
{
  "object": "list",
  "data": [
    {
      "id": "cl9x2a1b0000",
      "object": "customer",
      "email": "ada@example.com",
      "first_name": "Ada",
      "last_name": "Lovelace",
      "points_balance": 320,
      "lifetime_points": 1200,
      "pending_points": 0,
      "vip_tier": "Gold",
      "total_spent": 540,
      "created_at": "2026-05-01T10:00:00.000Z"
    }
  ],
  "page": 1,
  "limit": 1,
  "total_count": 134,
  "has_more": true
}
GET/v1/customers/:id

Retrieve a single customer by Loyalino id, Shopify customer id, or email address (pass the email directly in the path).

bash
curl https://app.loyalino.io/api/v1/customers/ada@example.com \
  -H "Authorization: Bearer lzk_live_xxx"
json
{
  "id": "cl9x2a1b0000",
  "object": "customer",
  "email": "ada@example.com",
  "first_name": "Ada",
  "last_name": "Lovelace",
  "points_balance": 320,
  "lifetime_points": 1200,
  "vip_tier": "Gold",
  "total_spent": 540,
  "created_at": "2026-05-01T10:00:00.000Z"
}
POST/v1/customers

Create a customer, or update the existing one with the same email. Requires a read and write key.

FieldTypeDescription
emailstringRequired. The customer email.
first_namestringOptional first name.
last_namestringOptional last name.
birthdaystringOptional, format YYYY-MM-DD.
shopify_customer_idstringOptional Shopify customer id to link.
bash
curl https://app.loyalino.io/api/v1/customers \
  -H "Authorization: Bearer lzk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"email":"grace@example.com","first_name":"Grace"}'
json
{
  "id": "cl9x9z0000",
  "object": "customer",
  "email": "grace@example.com",
  "first_name": "Grace",
  "points_balance": 0,
  "lifetime_points": 0,
  "vip_tier": null,
  "created_at": "2026-06-07T12:00:00.000Z"
}
PATCH/v1/customers/:id

Update a customer profile. Only the fields you send are changed. Requires a read and write key.

FieldTypeDescription
first_namestringNew first name.
last_namestringNew last name.
birthdaystringNew birthday, YYYY-MM-DD.
bash
curl -X PATCH https://app.loyalino.io/api/v1/customers/cl9x9z0000 \
  -H "Authorization: Bearer lzk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"last_name":"Hopper"}'
json
{
  "id": "cl9x9z0000",
  "object": "customer",
  "email": "grace@example.com",
  "first_name": "Grace",
  "last_name": "Hopper",
  "points_balance": 0
}

Points transactions

A points transaction changes a customer balance. Award points with a positive value and deduct with a negative value. This is the primary way to manage balances over the API.

POST/v1/customers/:id/points_transactions

Create a points transaction for a customer. points must be a non-zero integer, and a deduction cannot drop the balance below zero. Requires a read and write key.

FieldTypeDescription
pointsintegerRequired. Positive to award, negative to deduct.
typestringOptional label, for example earned or adjusted.
descriptionstringOptional note shown in history.
bash
curl https://app.loyalino.io/api/v1/customers/cl9x2a1b0000/points_transactions \
  -H "Authorization: Bearer lzk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"points":100,"type":"earned","description":"Birthday bonus"}'
json
{
  "id": "tx_71a2",
  "object": "points_transaction",
  "customer_id": "cl9x2a1b0000",
  "points": 100,
  "type": "earned",
  "description": "Birthday bonus",
  "created_at": "2026-06-07T12:05:00.000Z"
}
GET/v1/customers/:id/points_transactions

List a single customer transactions, most recent first.

bash
curl https://app.loyalino.io/api/v1/customers/cl9x2a1b0000/points_transactions \
  -H "Authorization: Bearer lzk_live_xxx"
json
{
  "object": "list",
  "data": [
    {
      "id": "tx_71a2",
      "object": "points_transaction",
      "customer_id": "cl9x2a1b0000",
      "points": 100,
      "type": "earned",
      "created_at": "2026-06-07T12:05:00.000Z"
    }
  ],
  "page": 1,
  "limit": 20,
  "total_count": 1,
  "has_more": false
}
GET/v1/points_transactions

List transactions across all customers. Filter with customer_id or type.

FieldTypeDescription
customer_idstringLimit to one customer (id or email).
typestringLimit to a transaction type.
limitintegerPage size, 1 to 100.
pageintegerPage number.
bash
curl "https://app.loyalino.io/api/v1/points_transactions?type=earned" \
  -H "Authorization: Bearer lzk_live_xxx"
json
{
  "object": "list",
  "data": [ /* points_transaction objects */ ],
  "page": 1,
  "limit": 20,
  "total_count": 58,
  "has_more": true
}
GET/v1/points_transactions/:id

Retrieve a single points transaction by id.

bash
curl https://app.loyalino.io/api/v1/points_transactions/tx_71a2 \
  -H "Authorization: Bearer lzk_live_xxx"
json
{
  "id": "tx_71a2",
  "object": "points_transaction",
  "customer_id": "cl9x2a1b0000",
  "points": 100,
  "type": "earned",
  "created_at": "2026-06-07T12:05:00.000Z"
}

Program

Read-only endpoints that describe how your program is configured: the rewards customers can redeem, the ways they earn, your VIP tiers, and your points settings.

GET/v1/rewards

List redemption rewards. Add ?enabled=true to return only active rewards.

bash
curl https://app.loyalino.io/api/v1/rewards?enabled=true \
  -H "Authorization: Bearer lzk_live_xxx"
json
{
  "object": "list",
  "data": [
    {
      "id": "rw_5",
      "object": "reward",
      "name": "5 off",
      "type": "amount_discount",
      "points_cost": 100,
      "discount_value": 5,
      "enabled": true,
      "variable": null
    }
  ],
  "page": 1,
  "limit": 20,
  "total_count": 6,
  "has_more": false
}
GET/v1/earn_rules

List the ways customers earn points. Add ?enabled=true for active rules only.

bash
curl https://app.loyalino.io/api/v1/earn_rules \
  -H "Authorization: Bearer lzk_live_xxx"
json
{
  "object": "list",
  "data": [
    {
      "id": "er_1",
      "object": "earn_rule",
      "name": "Place an order",
      "type": "place_order",
      "points": 1,
      "enabled": true
    }
  ],
  "page": 1,
  "limit": 20,
  "total_count": 5,
  "has_more": false
}
GET/v1/vip_tiers

List your VIP tiers, lowest threshold first.

bash
curl https://app.loyalino.io/api/v1/vip_tiers \
  -H "Authorization: Bearer lzk_live_xxx"
json
{
  "object": "list",
  "data": [
    {
      "id": "vt_2",
      "object": "vip_tier",
      "name": "Gold",
      "points_required": 1000,
      "multiplier": 1.5,
      "discount_type": "percentage",
      "discount_value": 10,
      "free_shipping": true
    }
  ],
  "page": 1,
  "limit": 20,
  "total_count": 3,
  "has_more": false
}
GET/v1/program

Retrieve your program settings and status: whether it is live, your plan, the earn rate, and the currency.

bash
curl https://app.loyalino.io/api/v1/program \
  -H "Authorization: Bearer lzk_live_xxx"
json
{
  "object": "program",
  "shop_domain": "your-store.myshopify.com",
  "live": true,
  "plan": "growth",
  "points_per_unit": 1,
  "currency": "USD",
  "points_delay_days": 0,
  "points_expiry_days": 365
}

Activities and redemptions

Award points for any custom action, or redeem a reward end to end.

POST/v1/activities

Record a customer activity and award points. Provide explicit points, or omit them to use the matching enabled earn rule for type. Requires a read and write key.

FieldTypeDescription
customer_idstringRequired. Internal id, Shopify id, or email.
typestringRequired. The activity name (also matched to an earn rule).
pointsintegerOptional. Overrides the matching rule's points.
bash
curl https://app.loyalino.io/api/v1/activities \
  -H "Authorization: Bearer lzk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"customer_id":"ada@example.com","type":"newsletter_signup","points":50}'
json
{
  "object": "activity",
  "type": "newsletter_signup",
  "customer_id": "cl9x2a1b0000",
  "points_awarded": 50,
  "transaction": {
    "id": "tx_a1",
    "object": "points_transaction",
    "points": 50,
    "type": "earned"
  }
}
POST/v1/customers/:id/redemptions

Redeem a reward for a customer. Uses the same pipeline as the storefront: atomic points deduction, Shopify discount minting (or native store credit), and rollback on failure. Pass points_to_redeem only for variable rewards. Requires a read and write key.

FieldTypeDescription
reward_idstringRequired. The reward to redeem.
points_to_redeemintegerRequired only for variable-redemption rewards.
bash
curl https://app.loyalino.io/api/v1/customers/cl9x2a1b0000/redemptions \
  -H "Authorization: Bearer lzk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"reward_id":"rw_5"}'
json
{
  "id": "LOYALTY7F3A2B",
  "object": "redemption",
  "customer_id": "cl9x2a1b0000",
  "reward_id": "rw_5",
  "points_spent": 100,
  "remaining_balance": 220,
  "discount_code": "LOYALTY7F3A2B",
  "apply_mode": "code"
}

Webhooks

Register HTTPS endpoints to receive events the moment they happen. Manage them here or in your admin under Settings, Webhooks. This is a capability most loyalty apps, including Smile, do not offer merchants.

EventWhen it fires
points.createdPoints are awarded (order, API, or activity).
customer.createdA new loyalty customer is created.
customer.updatedA customer profile changes.
redemption.createdA reward is redeemed.
tier.changedA customer moves VIP tier.

Each delivery is a signed POST with this JSON body:

json
{
  "id": "evt_3a9f1c",
  "object": "event",
  "type": "points.created",
  "created_at": "2026-06-07T12:00:00.000Z",
  "data": { "customer_id": "cl9x2a1b0000", "points": 100 }
}

Verify signatures

Every request carries a Loyalino-Signature: t=<unix>,v1=<hmac> header. Recompute the HMAC-SHA256 of the timestamp, a dot, and the raw request body using your endpoint secret, then compare in constant time. The Node SDK exports verifyWebhook for this.

javascript
import crypto from "node:crypto";

function verify(secret, header, rawBody) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto
    .createHmac("sha256", secret)
    .update(parts.t + "." + rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(parts.v1, "hex"),
    Buffer.from(expected, "hex"),
  );
}
GET/v1/webhook_endpoints

List your webhook endpoints. The signing secret is only ever returned once, at creation.

bash
curl https://app.loyalino.io/api/v1/webhook_endpoints \
  -H "Authorization: Bearer lzk_live_xxx"
json
{
  "object": "list",
  "data": [
    {
      "id": "we_1",
      "object": "webhook_endpoint",
      "url": "https://you.example.com/hook",
      "events": ["*"],
      "enabled": true,
      "last_status": 200
    }
  ],
  "page": 1,
  "limit": 20,
  "total_count": 1,
  "has_more": false
}
POST/v1/webhook_endpoints

Register an endpoint. The response includes the signing secret once. Subscribe to specific events, or omit events for all. Requires a read and write key.

FieldTypeDescription
urlstringRequired. A public https URL.
eventsarrayOptional. Event names to subscribe to, or omit for all.
descriptionstringOptional label.
bash
curl https://app.loyalino.io/api/v1/webhook_endpoints \
  -H "Authorization: Bearer lzk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://you.example.com/hook","events":["points.created","redemption.created"]}'
json
{
  "id": "we_1",
  "object": "webhook_endpoint",
  "url": "https://you.example.com/hook",
  "events": ["points.created", "redemption.created"],
  "enabled": true,
  "secret": "whsec_9f2a7c..."
}
DELETE/v1/webhook_endpoints/:id

Delete a webhook endpoint. You can also PATCH it to change the url, events, or enabled state.

bash
curl -X DELETE https://app.loyalino.io/api/v1/webhook_endpoints/we_1 \
  -H "Authorization: Bearer lzk_live_xxx"
json
{
  "id": "we_1",
  "object": "webhook_endpoint",
  "deleted": true
}