Skip to content

Free spins API reference

Self-serve routes for issuing, listing, and cancelling free-spin grants for your own players — signed the same way as every other Operator API call. See Free spins for the guide. Campaign templates (reusable presets an admin configures) are managed by the platform admin team, not through this API — you reference one by ID if your integration contact has set one up for you, or supply the grant's fields directly.

POST /v1/operator/free-spins/grants

Request body:

ts
type IssueGrantRequestBody = {
  playerRef: string
  campaignId?: string // if set, gameId/spins/betAmountMinor/currency below
                        // are IGNORED and read from the campaign instead
  gameId?: string          // required if campaignId is omitted
  spins?: number            // required (> 0) if campaignId is omitted
  betAmountMinor?: number  // optional; omit/0 to use your wallet's default stake
  currency?: string         // required if campaignId is omitted
  idempotencyKey: string   // required — see Idempotency below
  expiresAt?: string        // optional RFC3339 timestamp; informational only, not enforced by the platform today
}

currency must be one the target game's provider supports, and the game must be visible to your operator tenant — the same checks launch applies.

Response: the created (or, on a repeat idempotency key, the original) grant:

ts
type GrantStatus = 'pending' | 'active' | 'cancelled' | 'failed'
// 'expired' and 'completed' are reserved for a future phase that ingests
// provider-reported completion — nothing issues them today.

type GrantResponseBody = {
  id: string
  campaignId?: string
  operatorId: string
  providerId: string
  gameId: string
  playerRef: string
  spins: number
  betAmountMinor: number
  currency: string
  externalRef?: string // the provider's own identifier for this batch — empty until the provider call succeeds
  status: GrantStatus
  idempotencyKey: string
  expiresAt?: string
  lastError?: string    // populated when status is 'failed'
  createdAt: string
  updatedAt: string
}

Idempotency

idempotencyKey is required and scoped to (your operatorId, idempotencyKey): a repeat call with the same key returns the original call's grant unchanged, rather than issuing a second batch. This is a synchronous call to the provider, not a fire-and-forget queue — a provider-side failure marks the grant failed immediately (see lastError) and permanently consumes that idempotency key: retry with a fresh key, not the same one, if you want another attempt.

Error responses: 400 (missing/invalid fields, spins not positive, currency not supported, or — for a campaign-based grant — the campaign isn't active or is outside its validity window), 403 (the game isn't visible to your operator tenant), 404 (no such campaign, or no such game), 502 (the provider call itself failed — see lastError on the resulting failed grant), 401/429 (shared with every Operator API endpoint — see Errors & retry).

GET /v1/operator/free-spins/grants?playerRef=<ref>

Lists grants under your operator tenant. playerRef is optional — omit it to list every grant your tenant has issued.

Response: GrantResponseBody[].

GET /v1/operator/free-spins/grants/{id}

Returns a single grant by ID, scoped to your operator tenant (a grant ID belonging to a different operator behaves like it doesn't exist).

Error responses: 404 (no such grant under your tenant), 400 (id is not a valid identifier).

POST /v1/operator/free-spins/grants/{id}/cancel

Voids a grant's remaining, unused spins. Only an active grant can be cancelled — calling this on a grant that's pending, already cancelled, or failed returns 400 (ErrGrantNotCancellable). Like issuance, this is a synchronous call to the provider: if the provider's cancel call itself fails, your local grant is not marked cancelled (the spins may still be live on the provider's side), so a 502 here means retry the cancel rather than assuming it took effect.

Response: the updated GrantResponseBody.

Error responses: 404 (no such grant under your tenant), 400 (not cancellable — see above), 502 (the provider's cancel call failed), 401/429 (shared, see Errors & retry).