ServicesPromo Code

Entitlement Locks

A redemption is instant and irreversible. When a code funds a multi-step flow - a trade, a checkout with a payment step, an entitlement transfer - you want to reserve the code first, do the risky work, and only then commit it. That is the lock flow: a two-phase claim with a TTL, so an abandoned flow releases the code automatically.

Locks apply to single-use, non-stored-value codes only; multi-use and stored-value codes reject lock with 400 invalid_request.

The flow

snug promo-code lock --code SPRING-7K9Q2M4P8X-VIP --ttl-seconds 300
{
  "lock_id": "plock_mPFnMKnnYHQH",
  "commit_token": "ctok_pMtZAdXpAyBS",
  "expires_at": "2026-08-28T12:51:04.065857Z",
  "status": "intent_locked"
}

Hold the commit_token - it is the proof required to finalize, and this response is the only place it appears. Then either:

snug promo-code commit --lock-id plock_mPFnMKnnYHQH --commit-token ctok_pMtZAdXpAyBS
snug promo-code release --lock-id plock_mPFnMKnnYHQH   # on failure/cancel

Commit performs the actual redemption: it re-checks batch status, window, and caps, writes the redemption record, and reveals the reward payload. Release clears the reservation without a record, and the code is immediately redeemable again (verified).

Contention and safety

  • While a code is locked, both a second lock and a direct redeem fail 409 already_locked - the reservation really does exclude everyone, including the holder redeeming outside the lock.
  • Commit is idempotent: repeating it with the same lock id and token returns the same redemption_id.
  • A wrong commit token fails 400 invalid_request (commit token mismatch); a different user presenting even the correct token fails 403 insufficient_permissions - locks are bound to the claimant who created them, and release enforces the same ownership.
  • A commit can still lose to a cap: if the global or per-user cap was exhausted between lock and commit, commit fails 409 cap_reached.

TTL and the sweeper

ttl_seconds is capped by PROMO_CODE_MAX_LOCK_TTL_SECONDS (900 by default; longer requests fail 400 lock_ttl_too_long). Expiry is enforced inline: the moment a lock's TTL passes, the code redeems and locks normally again (verified with a 2-second lock), so a crashed flow ties a code up for at most the TTL. A background sweeper additionally reclaims leftover lock state every PROMO_CODE_LOCK_SWEEP_INTERVAL_SECONDS (30s default). Size the TTL to your flow's realistic worst case rather than defaulting to the maximum.

The exact request and response shapes are in the Promo Code API reference.

On this page