Getting started
From zero to the first purchase that earns points, and from there to redemption, with the exact calls for each step.
This page is the complete integration, end to end. If you are here to implement and want to read a single page before writing code, this is the one.
There are two paths and they are not exclusive: almost every integration starts with the first and adds the second when they want the member to see their own points.
| Path | What it solves | Credential |
|---|---|---|
| From your server | Making purchases earn points and codes get redeemed at the sale | API key |
| From your frontend | Letting the member see their points, buy rewards and show their code | End-user token |
Before you start
You need two things, and we give you both: write to us at hola@loybox.com.ar.
LOYBOX_API_KEYsecretrequiredThe commerce API key. It goes on your server only.
commerce_idpublicrequiredYour commerce id. This is what travels in the X-Commerce-Id header and it
can live in the frontend.
From here on, the examples use the
reference's conventions: $LOYBOX_API_KEY for the
API key, $ACCESS_TOKEN for the end-user token and 87 as the commerce_id.
Path 1: earning points from your server
This is the minimum integration of a loyalty program: a single call, at the point where your system confirms a sale.
Record the purchase
At an online checkout you already have the email, so by email is the short path: it works even if the member does not have a Loybox account yet.
curl -X POST https://loybox-public-api-752998171300.southamerica-west1.run.app/v1/consumptions/email \
-H "Authorization: Bearer $LOYBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"client_email": "ana@example.com",
"amount": 1750
}'At a physical point of sale, where the member gives their number, it is the same
call by code with client_code.
The amount is an integer
amount is whole units of the commerce's currency. A purchase of 1,750.50 is
sent as 1750.
How many points it earns is not decided by your call: it is decided by the commerce's configuration. You send the amount and Loybox applies the money-per-point rule, double points if they are running, and the tier bonus, in that order (the formula).
Show them the balance
The consumption's response does not say how many points it added. The balance is looked up separately:
curl https://loybox-public-api-752998171300.southamerica-west1.run.app/v1/clients/12345 \
-H "Authorization: Bearer $LOYBOX_API_KEY"With this you already have a working program: purchases earn and the member has a balance.
Look up the code the member brings
When a member shows up with a redemption code (their client_benefit_code), the
first thing is to see what it is. Use
v2, which brings the discount's
value at the root:
curl https://loybox-public-api-752998171300.southamerica-west1.run.app/v2/benefits/preview/887766 \
-H "Authorization: Bearer $LOYBOX_API_KEY"With the response's type and value you apply the discount in your sale:
type | What to do |
|---|---|
percentage_discount | Apply the percentage in value. |
absolute_discount | Subtract the amount in value. |
free_product | Add the product in product. |
A 400 here means the code was already used: do not apply anything.
Redeem it once the sale closed
Redemption burns the code and there is no going back:
curl -X POST https://loybox-public-api-752998171300.southamerica-west1.run.app/v1/benefits/redeem \
-H "Authorization: Bearer $LOYBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"client_benefit_code": 887766
}'Order matters
Looking up is harmless and can be repeated; redeeming is final. If you redeem before closing the sale and the sale falls through, the member lost the reward and there is no way to give it back over the API.
Path 2: the club in your frontend
Here Loybox works as the loyalty engine under your product: the user signs in with a code emailed to them and from there sees their points, buys benefits and shows their codes. All of this can run in the browser.
The API key has no place on this path
No call on this path carries the API key: it grants access to the data of all your
members. What travels is the end-user token, which only sees their own data, plus
the X-Commerce-Id, which is not a secret.
Ask for the code
curl -X POST https://loybox-public-api-752998171300.southamerica-west1.run.app/v1/auth/otp/request \
-H "X-Commerce-Id: 87" \
-H "Content-Type: application/json" \
-d '{
"email": "ana@example.com"
}'It always answers 200, even if that email has no account. In the UI you always
show the same message ("we sent a code to your email"), because you cannot know
whether the account existed.
Verify it and store the session
curl -X POST https://loybox-public-api-752998171300.southamerica-west1.run.app/v1/auth/otp/verify \
-H "X-Commerce-Id: 87" \
-H "Content-Type: application/json" \
-d '{
"email": "ana@example.com",
"otp": "418302"
}'It returns access and refresh. Store both: the access expires in
expires_in seconds and the refresh is what
renews it without asking the user
for another code. If the email had no account, one is created, and either way the
user ends up subscribed to your program.
Paint the screen with a single call
GET /v1/me brings the points, the
commerce's branding and the points about to expire, all at once:
const res = await fetch(
'https://loybox-public-api-752998171300.southamerica-west1.run.app/v1/me',
{
headers: {
Authorization: `Bearer ${accessToken}`,
'X-Commerce-Id': '87',
},
},
);
const me = await res.json();
// me.points -> the big balance on the screen
// me.commerce -> logo, name and color for the program's branding
// me.subscribed -> if it comes back false, show the call to joinA 401 here means the access expired:
renew it and retry the call.
Show the catalog and buy
The catalog comes back
unfiltered by balance: you compare each benefit's cost with the user's points
and decide what to show as reachable and what as "you need N more points".
curl -X POST https://loybox-public-api-752998171300.southamerica-west1.run.app/v1/me/benefits/exchange \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "X-Commerce-Id: 87" \
-H "Idempotency-Key: 8f14e45f-ea0f-4d1c-9a1b-2c3d4e5f6a7b" \
-H "Content-Type: application/json" \
-d '{
"benefit_id": "b_9f2a"
}'Buying and redeeming are not the same
Buying trades points for a benefit, and the user does it from your frontend. Redeeming uses that benefit in the sale, and your server does it with the API key (step 4 of path 1).
Show them the code
The purchase returns a client_benefit_code. That number is both the redemption
code and the coupon code: in an online store it is what the user pastes at
checkout, and in a physical store it is what they show at the counter.
Show it large, with a copy button and the due_date next to it. The full list of
the ones they hold is in
my benefits.
The full circuit
The two paths close like this, and it is the mental model worth having before writing code:
purchase ─▶ POST /v1/consumptions/email (your server, API key)
│
▼
points to the member
│
▼
POST /v1/me/benefits/exchange (your frontend, end-user token)
│
▼
client_benefit_code
│
▼
GET /v2/benefits/preview/{code} (your server, API key)
│
▼
POST /v1/benefits/redeem (your server, API key)Before going to production
- The API key lives on your server only. If it leaked, write to us and we rotate it.
- Never retry a
400blindly: it is the already-used benefit, the expired one or points that fall short. Read themessageand stop. - Buying a benefit carries an
Idempotency-Key. It is what prevents charging the points twice when the network drops. - A
401in My account is the signal to renew the token, not to sign the user out. - The
404from my tier is the normal case for a new member: hide the tiers section, do not show an error. points_expirationdoes not come backnullwhen points do not expire: it comes withmode: "none". Check themodebefore showing the notice.
If you are implementing with an agent
The API is published in machine format:
/openapi.json: the complete OpenAPI 3.1 specification, with all 25 endpoints, the schemas and the two credentials. Good for generating a typed client. Its descriptions are in Spanish; the names and schemas are not./llms.txtand/llms-full.txt: the whole docs as text, meant to be handed over as context. These two are per language, so the links above are the English ones.- Any page of the docs as raw Markdown by appending
.mdto the URL, for example/api-reference/credenciales.md.