本文へ移動
キャンペーン無料アイテム

Free Items

Learn how to let players claim free items without a code through the Tokenz Free Items API.

The Free Items API helps you run giveaways where players receive a reward without entering a code and without paying. Your server checks a campaign's status, claims on behalf of a player, and then handles a redemption.completed webhook for fulfillment.

Overview

The claim flow includes:

  • Status: Read a campaign's reward preview, remaining claims, and next reset time
  • Claim: Record a successful claim for a specific player and create a redemption record
  • Webhook fulfillment: Receive redemption.completed and grant the reward in your game backend

Free items reuse the campaign model and the redemption.completed webhook that redemption codes use. The difference is that no code exists, your server supplies campaignId and playerId instead of a redemption code.

The full request and response schemas for both endpoints are in the Free Items API reference.

Authentication

Call these endpoints from your server using a secret API key. Enable the FreeItemStatus scope to read status and FreeItemClaim to claim. Keep the key on your backend and derive playerId from the authenticated player in your game.

Player identifiers

playerId is your own identifier for a player. Tokenz treats it as an opaque string and does not look it up in your game, so the value you send is the only thing that identifies the player.

Tokenz applies these rules, and nothing else:

  • Leading and trailing whitespace is stripped
  • The value cannot be blank
  • The value can be at most 100 characters. A longer value is rejected with request.decoding-failed, never silently shortened
  • The value is matched exactly, including letter case

Player IDs are case-sensitive. Player_123 and player_123 are distinct identifiers and hold separate claim allowances. Use the same stable identifier, including capitalization, on every request, from every code path that calls these endpoints. A player's own account ID from your game is usually the right choice.

Whitespace trimming is the only normalization Tokenz performs. Do not lowercase or otherwise change the case of an identifier before sending it, because that would merge two distinct players into one.

The same identifier is the same player everywhere. A player claiming from a different device, session or storefront is still the same player, and shares one allowance.

Campaign setup and management

Free item campaigns are created and managed in the Tokenz Dashboard. If you do not see free item campaigns in your dashboard yet, contact Tokenz support to confirm availability for your account.

Merchants use the Tokenz Dashboard to:

  • Create campaigns with active periods and reward configuration
  • Set a total claim limit and a per-player claim limit
  • Choose a refresh cadence, so the per-player limit resets on a schedule
  • Pause, resume, or end campaign operations

Campaign statuses

Free item campaigns use the same statuses as code campaigns:

  • draft: Work in progress. Claims are rejected. All fields are editable.
  • scheduled: Published and queued. Becomes claimable at the configured startAt time.
  • active: Campaign is live and claims can succeed
  • paused: Manually paused. Claims are rejected. Can be resumed to active
  • ended: Campaign is finished. Claims are rejected. This state is irreversible

An active campaign can accept claims. A scheduled campaign can also accept claims once startAt is reached, even before its stored status changes to active. Claims are rejected at or after endAt. Both are compared as absolute instants, so a campaign ending at midnight UTC stops accepting claims at that instant regardless of where the player is.

Claim limits

Two independent limits apply, and both are checked on every claim:

  • Total claim limit: the maximum number of claims across all players for the whole campaign. Once reached, every further claim is rejected with campaign.limit-reached.
  • Per-player claim limit: how many times one player can claim. Once reached, that player is rejected with campaign.player-limit-reached for a one-time campaign, or campaign.player-period-limit-reached for a recurring campaign. Other players are unaffected.

Per-player limits are enforced per playerId. The same player claiming from a different device or session is still the same player.

A rejected claim consumes nothing. If a player has already reached their own limit, the campaign's total claim count is not affected.

Recurring campaigns

A campaign can refresh the per-player limit on a schedule, so the same player can claim again in each new period. The cadence is chosen when the campaign is created:

必要に応じて横にスクロール
CadencePeriod starts
None (one-time)Never resets. The per-player limit applies for the life of the campaign
DailyLocal midnight
WeeklyMonday at local midnight
MonthlyThe first day of the month, at local midnight

Periods are calculated in the campaign's own timezone, not the player's. Weekly periods always begin on Monday.

That timezone is not returned by the API, so do not try to work out a period boundary yourself. Use nextResetAt, which is already the correct absolute instant, and refresh status once it passes.

Only the per-player limit resets. The campaign's total claim limit is never restored by a period rollover.

nextResetAt in the claim and status responses is the exact instant the current period ends and the next one begins. Use it to render a countdown. It is absent for one-time campaigns. A claim made at or after nextResetAt belongs to the new period, provided the campaign is still available. A reset does not extend the campaign end time or replenish its total claim limit.

Claim lifecycle

  1. Your server calls GET /v2/free-items/{campaignId} to show the reward and the player's remaining claims
  2. Player chooses to claim in your UI
  3. Your server calls POST /v2/free-items/claim
  4. Tokenz returns a successful claim response
  5. Tokenz sends a redemption.completed webhook
  6. Your backend fulfills the reward and should use redemptionId to guard against duplicate fulfillment
100%
スクロールで移動・拡大して詳細を確認

Get free item status

Status returns the campaign preview and reward preview, plus how many claims remain. The campaign must be active, or scheduled with its start time reached, and must not have expired. Exhausted limits return 0 rather than a limit error. A status check does not reserve a claim; availability can change before the claim request. Pass playerId to also receive that player's remaining claims for the current period.

bash
curl --request GET \
  --url 'https://api.tokenz.one/v2/free-items/campaign_1p4LPTRKB5Z_t?playerId=player_98765' \
  --header 'Authorization: Bearer secret_test_YOUR_KEY_HERE'

Request fields

  • campaignId (required, path): The free item campaign to read
  • playerId (optional, query): Merchant player identifier. If provided, the response includes that player's remaining claims. Whitespace is stripped, the value cannot be blank, it is limited to 100 characters, and it is case-sensitive. See Player identifiers

Success response (200)

json
{
  "campaign": {
    "id": "campaign_1p4LPTRKB5Z_t",
    "name": "Weekly Free Pack"
  },
  "rewardPreview": {
    "name": "Marathon Energy Gift",
    "imageUrl": "https://images.example.com/rewards/energy.png",
    "quantity": 1
  },
  "campaignRemaining": 842,
  "remainingForPlayer": 1,
  "nextResetAt": "2026-09-14T00:00:00Z"
}

Response fields

  • campaignRemaining: Claims left for the whole campaign. Absent if the campaign has no total claim limit
  • remainingForPlayer: Claims left for this player in the current period. Absent if playerId was not provided, or the campaign has no per-player limit. A player with no claims left receives 0 rather than an error
  • nextResetAt: When the per-player limit next resets. Absent for one-time campaigns

Claim a free item

Claim creates a redemption record for a player. Your backend grants the reward when it processes the webhook. A successful claim requires the campaign to be claimable and both limits to have room.

bash
curl --request POST \
  --url https://api.tokenz.one/v2/free-items/claim \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer secret_test_YOUR_KEY_HERE' \
  --data '{
    "campaignId": "campaign_1p4LPTRKB5Z_t",
    "playerId": "player_98765"
  }'

Request fields

  • campaignId (required): The free item campaign to claim from
  • playerId (required): Merchant player identifier. Whitespace is stripped, the value cannot be blank, it is limited to 100 characters, and it is case-sensitive. See Player identifiers

Success response (201)

json
{
  "redemption": {
    "redemptionId": "redemption_1p4LPTRKB5Z_t",
    "playerId": "player_98765",
    "campaign": {
      "id": "campaign_1p4LPTRKB5Z_t",
      "name": "Weekly Free Pack"
    },
    "reward": {
      "skuRedemptionReward": {
        "type": "EXTERNAL_SKU",
        "sku": "ITEM_ENERGY_GIFT",
        "name": "Marathon Energy Gift",
        "quantity": 1
      }
    },
    "redeemedAt": "2026-09-10T08:10:00Z",
    "source": "FREE_ITEM_CLAIM"
  },
  "remainingForPlayer": 0,
  "nextResetAt": "2026-09-14T00:00:00Z"
}

remainingForPlayer is the number of claims left after this claim, and is omitted when no per-player limit is configured. nextResetAt is omitted for one-time campaigns.

The redemption object has no code field, because no code was used. See Claim webhook.

Retries and duplicate fulfillment

The claim request has no idempotency-key field. Repeating a successful request can create another redemption if limits allow it.

If a claim response is lost, do not blindly retry: a second request can consume a second claim and draw the campaign's total down again.

A redemption.completed webhook for that campaign and player shows that a claim succeeded, and carries the redemptionId your lost response would have returned. Tying that webhook to one specific timed-out request is not always unambiguous, because the payload identifies the campaign and the player rather than your request. Correlate it against your own request log, and take extra care on campaigns where a player can hold more than one claim, such as recurring campaigns.

If neither the response nor a webhook is available, the public API currently provides no way to read a claim back, so the outcome cannot be confirmed from your side. Contact Tokenz support rather than retrying.

Deduplicate fulfillment by redemptionId. Deduplicating webhook deliveries does not prevent a second claim request.

Claim webhook

After a successful claim, Tokenz sends redemption.completed to webhook endpoints subscribed to this event. This is the same event that code redemptions use.

Verify the signature before you grant anything. Every delivery carries a Tokenz-Signature header, and a handler that skips verification will grant free items to anyone who finds or guesses its URL. Verify over the raw request body, then parse. See Secure your webhook endpoint for the steps. Treat the fields below as trustworthy only once the signature checks out.

json
{
  "id": "f75ff6f0-bdf9-4b25-bb96-968f8733a7e3",
  "object": "redemption.completed",
  "createdAt": "2026-09-10T08:10:00Z",
  "test": true,
  "eventData": {
    "type": "redemption",
    "version": "v2",
    "data": {
      "redemption": {
        "redemptionId": "redemption_1p4LPTRKB5Z_t",
        "campaign": {
          "id": "campaign_1p4LPTRKB5Z_t",
          "name": "Weekly Free Pack"
        },
        "playerId": "player_98765",
        "reward": {
          "skuRedemptionReward": {
            "type": "EXTERNAL_SKU",
            "sku": "ITEM_ENERGY_GIFT",
            "name": "Marathon Energy Gift",
            "quantity": 1
          }
        },
        "redeemedAt": "2026-09-10T08:10:00Z",
        "source": "FREE_ITEM_CLAIM"
      }
    }
  }
}

Telling free item claims apart from code redemptions

Free item claims and code redemptions both arrive on redemption.completed.

Fulfillment usually does not need to tell them apart. redemptionId, playerId and reward are present for both, and they are everything you need to grant the reward once to the right player. Prefer a single fulfillment path that reads those three fields.

When you do need to distinguish the two, for example for reporting:

  • source is FREE_ITEM_CLAIM for a free item claim, and CODE_REDEMPTION for a redemption code. Those are the two values sent today
  • code carries the redeemed code for a code redemption, and is absent for a free item claim. Do not assume it is present

If source is missing or holds a value you do not recognise, do not guess. Fulfill from redemptionId, playerId and reward as usual, and flag the event for reconciliation.

If you already handle redemption.completed for redemption codes, review that handler before running your first free item campaign. A handler that reads code unconditionally will fail on the first claim.

Use the top-level event id for webhook delivery tracing, and use redemptionId to guard against duplicate fulfillment in your backend.

Delivery and retries

If your endpoint does not accept a delivery, Tokenz retries automatically, with increasing gaps and some randomization. Retries are scheduled within a window of up to 72 hours from the first attempt.

That window governs when retries are scheduled. It is not a promise that delivery succeeds inside it, and a scheduled attempt can still run later than planned. Do not depend on an exact number of attempts, or on exact retry times.

Two consequences for your handler:

  • A delivery can arrive days after the claim. Keep your record of fulfilled redemptionId values in durable storage, not in a cache with a short expiry. A record that expires before the last retry will let the same reward be granted twice
  • The same redemptionId can arrive more than once. Make fulfillment idempotent on it

Error handling

Free item endpoints return standard HTTP status codes with structured error objects.

Common errors

  • 400 Bad Request (request.decoding-failed): Invalid campaign id, a blank or over-length playerId, or a malformed request
  • 401 Unauthorized: Invalid or missing API key
  • 403 Forbidden: API key does not have the required scope
  • 404 Not Found (entity.not-found): Campaign not found, or it belongs to another merchant, or its mode does not match your API key
  • 422 Unprocessable Entity (campaign.kind-invalid): The campaign is not a free item campaign
  • 422 Unprocessable Entity (campaign.status-invalid): The campaign is draft, paused, ended, or scheduled before its start time
  • 422 Unprocessable Entity (campaign.expired): The campaign has reached its end time (an ended status can instead return campaign.status-invalid)
  • 422 Unprocessable Entity (campaign.limit-reached): The campaign reached its total claim limit
  • 422 Unprocessable Entity (campaign.player-limit-reached): This player reached the per-player claim limit
  • 422 Unprocessable Entity (campaign.player-period-limit-reached): This player reached the claim limit for the current period. Retry after nextResetAt
  • 429 Too Many Requests: Rate limit exceeded. Both endpoints are rate limited per API key and, when supplied, per player
  • 500 Server Error: Unexpected server error

Testing

Use test mode to develop and test your free item integration:

  1. Use test API keys (prefixed with secret_test_) to interact with test campaigns
  2. Create test campaigns in the Tokenz Dashboard using test mode
  3. Test claims are fully isolated from live data. A test API key can only claim from test campaigns, and a live API key can only claim from live campaigns
  4. Test entity IDs include a _t suffix (e.g., redemption_1p4LPTRKB5Z_t)
  5. Test webhooks are delivered only to your test webhook endpoints, with "test": true in the payload

Test a successful claim and webhook, an exhausted player limit, an exhausted total limit, a period reset, paused and expired campaigns, and duplicate webhook delivery. Confirm that the reward is granted once per redemptionId.