GiftingStack
Sign in Get started
Docs menu

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.

Checked against the API and the live OpenAPI on 2026-10-21.

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"}
FieldMeaning
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_secondsLifetime, 60 to 86,400. Default 3,600 (one hour).
roomsFor 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).
nameDisplay 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 (with icon_url and animation_url).
  • Read its own balance: GET /v1/client/me, which answers {"user":"ava","balance":500,"livemode":false}. livemode is false for a token made with a test key.
  • Send a gift as its own user: POST /v1/client/gift-transactions (needs an Idempotency-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 rooms claim.

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 token or user.
  • A revocation reaches every edge location and the gift room service within 30 seconds.
  • Revoking by user also 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) is 422 token_not_revocable: revoke by user.
  • 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.