Docs
Client tokens
A client token is what your app holds. Your server mints it for the signed-in user. It can send gifts as that user and read what the user may see, and nothing else.
Mint a token (your server)
Call this from your backend with the secret key, once per signed-in user:
curl -X POST https://api.giftingstack.com/v1/client-tokens \
-H "Authorization: Bearer $GS_KEY" \
-H "Content-Type: application/json" \
-d '{"user": "ava", "ttl_seconds": 3600}'
# 201
# {"token":"gs_ct_…","user":"ava","expires_at":"2026-10-20T13:00:00.000Z"}
| Field | Meaning |
|---|---|
user (required) | Your own id for the end user, 1 to 128 characters of letters, digits and _ . : -. The token acts only for this user. |
ttl_seconds | Lifetime, 60 to 86,400. Default 3,600 (one hour). |
rooms | For gift rooms: up to 8 room ids this token may join and send into. An entry may end with * to match a prefix (match_*). Without it the token has no room access at all (room_not_allowed). |
name | Display name shown to room viewers with this user's gifts, 1 to 32 characters, with no control, invisible (zero-width, direction-changing) or other format characters. Optional. |
Mint a token per signed-in user and per session. Never share one token between users, and never
hand a secret key to the app instead. The call needs no Idempotency-Key.
What a token can do
All on /v1/client/*:
- Read the gifts your app can show:
GET /v1/client/gifts(withicon_urlandanimation_url). - Read its own balance:
GET /v1/client/me, which answers{"user":"ava","balance":500,"livemode":false}.livemodeisfalsefor a token made with a test key. - Send a gift as its own user:
POST /v1/client/gift-transactions(needs anIdempotency-Key; the sender is always the token's user). - Read the gifts any user has received, gift walls, leaderboards, and the "while you were away" summary of its own user.
- Join gift rooms listed in its
roomsclaim.
It cannot credit or debit, act for another user, or call any /v1/* endpoint. A
suspended live project serves no client-token traffic, reads included.
Receiver ids are untrusted. A gift can name a receiver that does not exist yet,
and that creates the user. So that one end user cannot flood your project with made-up ids, a
token creates at most 200 new receiver ids an hour (more answers 429 rate_limited
with details.limit: "new_receivers_per_hour"; sending to a receiver that already
exists does not count). Leaderboards and rooms show receiver ids, so treat them as text a user
chose: pick opaque ids, show your own display names, and validate the receiver on your server
if only real people may receive gifts.
Use it from a browser
/v1/client/* allows cross-origin requests from any origin (GET,
POST, PUT with the Authorization, Content-Type,
Idempotency-Key and X-GiftingStack-Platform headers), because the token
is a bearer credential with no cookies. A web app can call it directly:
const API = "https://api.giftingstack.com";
async function gs(path, token, init = {}) {
const res = await fetch(API + path, {
...init,
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
"X-GiftingStack-Platform": "web",
...init.headers,
},
});
const body = await res.json();
if (!res.ok) throw Object.assign(new Error(body.error.message), body.error);
return body;
}
// token comes from YOUR backend (the endpoint that calls POST /v1/client-tokens)
const me = await gs("/v1/client/me", token); // { user: "ava", balance: 500, livemode: false }
const sent = await gs("/v1/client/gift-transactions", token, {
method: "POST",
headers: { "Idempotency-Key": crypto.randomUUID() },
body: JSON.stringify({ receiver: "ben", gift_id: "pink-tulip", quantity: 1 }),
});
console.log(sent.sender_balance_after);
Use a gift id from your own catalog. pink-tulip is the first gift of the Starter set;
read the others from GET /v1/client/gifts. The SDKs do all of this, including the
refresh below, for you: SDKs.
X-GiftingStack-Platform says where your app runs: web,
ios, android, flutter or react-native. Your
dashboard's Home page shows the gifts sent from each platform. It is optional and only
informational: a missing or other value counts as "not reported" and never changes or refuses a
request. All five SDKs set it for you.
Apps built with Flutter or React Native also send X-GiftingStack-OS:
ios or android, the phone the app runs on. It is optional and
informational in the same way. The React Native, Flutter, Android and iOS SDKs set it for you.
Refresh
A token is valid until expires_at. When a call answers
401 invalid_client_token, ask your backend for a new token and repeat the request
once. For a gift send, repeat it with the same Idempotency-Key, so a send that did go
through is not made twice.
Revoke
From your backend, with the secret key (a client token cannot):
# every token of one user (minted up to now)
curl -X POST https://api.giftingstack.com/v1/client-tokens/revoke \
-H "Authorization: Bearer $GS_KEY" -H "Content-Type: application/json" \
-d '{"user": "ava"}'
# or one token
curl -X POST https://api.giftingstack.com/v1/client-tokens/revoke \
-H "Authorization: Bearer $GS_KEY" -H "Content-Type: application/json" \
-d '{"token": "gs_ct_…"}'
# {"revoked":"user","user":"ava","effective_within_seconds":30}
- Send exactly one of
tokenoruser. - A revocation reaches every edge location and the gift room service within 30 seconds.
- Revoking by
useralso kills a token minted in the same second; mint the next one a second later. - A token from another project or environment is
404. A token without an id (minted before revocation existed) is422 token_not_revocable: revoke byuser. - Revoking the secret key that minted a token ends the token too. An expired token is accepted and nothing happens.
Questions or something unclear? support@giftingstack.com.