Integracja API
A practical, copy-paste guide to integrating Gibbond's GraphQL API — from a back-office service, a mobile or web app, or another sales channel. Read the API reference for the full schema.
Pro plan. Create an API key in the API keys page of the Gibbond admin, then keep it in a server-side environment variable. Everything below assumes GIBBOND_API_KEY holds a WRITE key.
How the pieces fit
There are two credentials, and choosing the right one is the whole game:
- API key (
glk_live_…) — identifies your backend. It can read and write every member, so it lives only on your server. Use it for server-to-server work and to mint customer tokens. - Customer token — a short-lived credential scoped to one customer. Your backend mints it with the API key and hands it to a client (browser or app). A client holding it can only see and act on that one customer, so it is safe to ship to the device.
Choose your pattern
| You're building… | Use | Jump to |
|---|---|---|
| A back-office job, ERP sync, or headless backend | API key directly | From your backend |
| A mobile or single-page app that shows a member their points | API key on your server → customer token on the device | Inside an app |
| A POS, marketplace, or headless checkout that should earn points | API key, awardPointsForOrder | From another channel |
A request helper
Every call is the same shape, so wrap it once. This Node example uses the built-in fetch (Node 18+):
// gibbond.js
const ENDPOINT = "https://app.gibbond.com/api/v1/graphql";
export async function gibbond(query, variables, token) {
const res = await fetch(ENDPOINT, {
method: "POST",
headers: {
"Authorization": "Bearer " + token,
"Content-Type": "application/json",
},
body: JSON.stringify({ query, variables }),
});
if (res.status === 429) {
const retryAfter = Number(res.headers.get("Retry-After") || 1);
const err = new Error("Rate limited");
err.retryAfter = retryAfter;
throw err;
}
const json = await res.json();
if (json.errors && json.errors.length) {
const code = json.errors[0].extensions && json.errors[0].extensions.code;
throw new Error(code + ": " + json.errors[0].message);
}
return json.data;
} Pattern 1 — From your backend (server-to-server)
With a WRITE key you can act on any member of your shop. Enroll a customer, read their state, and grant points:
import { gibbond } from "./gibbond.js";
const KEY = process.env.GIBBOND_API_KEY;
const id = "gid://shopify/Customer/123456789";
// Enroll a customer (idempotent — safe to call again).
await gibbond(
"mutation($id: ID!, $email: String) { enrollCustomer(customerId: $id, email: $email) { ok customer { enrolled } } }",
{ id, email: "ada@example.com" },
KEY,
);
// Read their balance and tier.
const { customer } = await gibbond(
"query($id: ID!) { customer(id: $id) { balance pending lifetime tier { name multiplier } } }",
{ id },
KEY,
);
// Grant points. idempotencyKey makes retries safe — the same key never double-applies.
await gibbond(
"mutation($id: ID!, $pts: Int!, $k: String) { awardPoints(customerId: $id, points: $pts, reason: \"Newsletter signup\", idempotencyKey: $k) { ok pointsApplied balance { balance } } }",
{ id, pts: 100, k: "newsletter-123456789" },
KEY,
); A negative awardPoints amount, and clawbackPoints, require an ADMIN key — keep those flows on a separate, tightly-held credential.
Pattern 2 — Inside a mobile or web app
Never put the API key in an app. Instead, expose one small endpoint on your own server that mints a customer token for the signed-in user, and let the app talk to Gibbond directly with that token.
Where does the customer GID come from?
A customer token is bound to whatever GID you pass to mintCustomerToken — so minting for the right customer is your responsibility. Gibbond authenticates your backend (via the API key); it does not authenticate the end shopper. Before you mint, your server must already know, and trust, which Shopify customer is signed in. There are three ways to establish that, in order of preference:
| How your app signs the shopper in | Where the GID comes from |
|---|---|
| Shopify Customer Account API (recommended) | Log the shopper in with Shopify's OAuth-based Customer Account API. Your backend exchanges the resulting token for the verified customer and reads their GID. The identity is signed by Shopify end-to-end. |
| A Shopify-native surface | If the experience runs inside Shopify (a theme app embed, or a customer-account UI extension), Shopify already provides the signed identity — the app-proxy's logged_in_customer_id, or a customer-account session token's sub. Read it server-side and use that GID. |
| Your own login | A channel with its own accounts (a custom app, a POS) maps its user to a Shopify customer GID from a mapping you stored when the customer was created. You are vouching for the link, so guard this path carefully. |
Recommended: the Customer Account API. For a headless storefront or a mobile app, authenticate shoppers with Shopify's Customer Account API and derive the GID from Shopify's verified token. That makes the whole chain — shopper → Shopify-verified identity → your backend → minted customer token — trustworthy by construction, with no GID mapping for you to secure. The requireAuth middleware below is exactly where this check belongs.
If your surface already runs inside Shopify's session (a theme/checkout/customer-account block), you usually don't need this API at all — the storefront app blocks and customer account integration verify the Shopify identity for you. Reach for customer tokens when you're outside that context.
On your server
Your own auth tells you which Shopify customer is signed in (their GID). Mint a token for them:
import express from "express";
import { gibbond } from "./gibbond.js";
const app = express();
// requireAuth is YOUR session middleware; it sets req.customerGid.
app.post("/loyalty/session", requireAuth, async (req, res) => {
const data = await gibbond(
"mutation($id: ID!) { mintCustomerToken(customerId: $id, ttlSeconds: 900) { token expiresAt } }",
{ id: req.customerGid },
process.env.GIBBOND_API_KEY, // WRITE key, server-side only
);
// Hand the app the token, when it expires, and the id it is bound to.
res.json({
token: data.mintCustomerToken.token,
expiresAt: data.mintCustomerToken.expiresAt,
customerId: req.customerGid,
});
}); In the app
Fetch a token from your server, then call Gibbond directly. The me query resolves to the token's customer — no id needed:
// 1. Get a short-lived token from YOUR backend.
const session = await fetch("/loyalty/session", { method: "POST" }).then(r => r.json());
// 2. Call Gibbond with the customer token.
async function loyalty(query, variables) {
const res = await fetch("https://app.gibbond.com/api/v1/graphql", {
method: "POST",
headers: {
"Authorization": "Bearer " + session.token,
"Content-Type": "application/json",
},
body: JSON.stringify({ query, variables }),
});
return (await res.json()).data;
}
// Show the member their points, tier, and reward codes.
const { me } = await loyalty("{ me { name balance lifetime tier { name multiplier } } }");
const { activeCodes } = await loyalty(
"query($id: ID!) { activeCodes(customerId: $id) { code rewardName status expiresAt } }",
{ id: session.customerId },
);
renderRewardsScreen(me, activeCodes); Redeeming from the app
A customer token can redeem a reward for itself — no merchant key on the device. The mutation returns a one-time discount code to apply at checkout:
const { redeemReward } = await loyalty(
"mutation($id: ID!, $reward: ID!) { redeemReward(customerId: $id, rewardId: $reward) { ok code { code rewardName pointsSpent expiresAt } } }",
{ id: session.customerId, reward: "gid_or_reward_id_from_rewards_query" },
);
if (redeemReward.ok) applyDiscountCode(redeemReward.code.code); Tokens default to a 15-minute life (set ttlSeconds up to 24h). When a call returns UNAUTHENTICATED, just hit /loyalty/session again for a fresh token and retry.
Pattern 3 — Earn points from another sales channel
When an order happens somewhere other than your Shopify checkout — a POS, a marketplace, a headless storefront — report it with awardPointsForOrder. Gibbond applies the member's tier multiplier, active boosts and milestones exactly as it would for a native order, and is idempotent on orderId, so retries are safe:
await gibbond(
`mutation($input: OrderInput!) {
awardPointsForOrder(input: $input) {
ok
pointsApplied
balance { balance lifetime }
}
}`,
{
input: {
orderId: "pos-2026-000123",
customerId: "gid://shopify/Customer/123456789",
customerEmail: "ada@example.com",
orderName: "POS #000123",
lineItems: [
{ productId: "gid://shopify/Product/55", collectionIds: ["gid://shopify/Collection/9"], lineTotal: 42.0 },
{ lineTotal: 8.5 },
],
},
},
process.env.GIBBOND_API_KEY,
); Each line item carries an optional productId, optional collectionIds, and a lineTotal — so per-product and per-collection boosts and exclusions apply just as they do for Shopify orders. To confirm points later (e.g. after fulfillment) call confirmPoints(customerId, orderId); to reverse them on a refund call clawbackPoints with an ADMIN key.
Errors, retries & idempotency
- Codes. Failures come back as a GraphQL
errorsarray; readerrors[0].extensions.code—UNAUTHENTICATED(401),FORBIDDEN(403, wrong scope),RATE_LIMITED(429),BAD_USER_INPUT(400),NOT_FOUND(404). - Rate limits. 600 requests/minute per credential. On
429, wait the seconds in theRetry-Afterheader, then retry. - Idempotency. Pass an
idempotencyKeytoawardPoints, and a stableorderIdtoawardPointsForOrder. Re-sending the same key/order never double-applies — so you can retry network failures freely. - Token expiry. Treat
UNAUTHENTICATEDon a customer token as "mint a new one and retry".
Security checklist
- Keep API keys in server-side secrets only — never in app bundles, browser code, or version control.
- Use a customer token for anything that runs on a device; never an API key.
- Give each integration the lowest scope it needs (a
READkey for dashboards,WRITEfor earning/redeeming,ADMINonly for refunds/corrections). - Rotate a key from the admin the moment it might have leaked — revoking takes effect immediately.
- Mint customer tokens with the shortest practical
ttlSeconds.
For the complete list of types, queries, mutations and fields, see the API reference.