Skip to content

Draw winners

POST
/v1/game-instances/{campaignId}/winner-selection/trigger
curl --request POST \
--url https://api.keliplay.com/v1/game-instances/example/winner-selection/trigger \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "idempotencyKey": "draw-2026-10-31-final", "maxWinners": 3 }'

Draws winners from the campaign’s eligible entries now. Every draw is recorded and can be verified.

Only campaigns whose winnerSelectionTrigger is manual can be drawn on demand (409 otherwise). The campaign must be active.

idempotencyKey is required. Repeating a request with the same key returns the first draw with replayed: true and status 200 rather than drawing again, so retries are safe.

If the campaign’s prizes are given by draw, winners receive their coupons as part of the draw. grants lists which winners received one. Read the codes with GET .../reward-grants.

Permission: draws.run. Rate limit: 30 requests per minute.

campaignId
required
string

The campaign’s id.

Media typeapplication/json
object
idempotencyKey
required

Your own unique key for this draw.

string
>= 8 characters <= 200 characters
maxWinners

How many winners to draw. Defaults to the campaign’s winnerSelectionConfig.maxWinners.

integer
>= 1 <= 1000
Example
{
"idempotencyKey": "draw-2026-10-31-final",
"maxWinners": 3
}

A repeat of an earlier request with the same idempotencyKey. Nothing new was drawn.

Media typeapplication/json
object
run
required

One draw. Every draw is recorded and can be verified.

object
id
required
string
gameInstanceId
required
string
triggeredBy
required

When winners are drawn.

  • manual: when you call the draw operation.
  • auto_on_session_end: after each play ends.
  • auto_on_schedule: once, at scheduleAt.
string
default: manual
Allowed values: manual auto_on_session_end auto_on_schedule
triggeredByApiKeyId

The API key that triggered it, if one did.

string | null
idempotencyKey
string | null
algorithm
required

The algorithm used, as in winnerSelectionAlgorithm.

string
algorithmConfigSnapshot
required
object
maxWinners
integer
>= 1 <= 1000
tieBreak

How equal scores are ordered, for score_highest.

string
Allowed values: earliest_entry random
windowStart

Only entries made at or after this time count.

string format: date-time
windowEnd

Only entries made before this time count.

string format: date-time
entriesSnapshotCount
required

How many eligible entries the draw picked from.

integer
status
required
string
Allowed values: pending completed failed
failureReason
string | null
executedAt
string | null format: date-time
createdAt
required
string format: date-time
winners
required
Array<object>
object
id
required
string
runId
required
string
gameInstanceId
required
string
entryId
required
string
participantRef
required
string
rank
required
integer
>= 1
prize
One of:

What the campaign is giving away, as shown to winners.

object
label
required
string
>= 1 characters
description
string
externalRef

Your own reference for the prize, such as a SKU.

string
notifiedAt
string | null format: date-time
createdAt
required
string format: date-time
replayed
required

True when this repeats an earlier draw with the same idempotencyKey.

boolean
grants
required

Winners who received a coupon in this draw. Codes are not included.

Array<object>
object
winnerId
required
string
grantId
required
string
Example
{
"run": {
"triggeredBy": "manual",
"algorithmConfigSnapshot": {
"tieBreak": "earliest_entry"
},
"status": "pending"
}
}
X-RateLimit-Limit
integer

Requests allowed in the current window.

X-RateLimit-Remaining
integer

Requests left in the current window.

A new draw.

Media typeapplication/json
object
run
required

One draw. Every draw is recorded and can be verified.

object
id
required
string
gameInstanceId
required
string
triggeredBy
required

When winners are drawn.

  • manual: when you call the draw operation.
  • auto_on_session_end: after each play ends.
  • auto_on_schedule: once, at scheduleAt.
string
default: manual
Allowed values: manual auto_on_session_end auto_on_schedule
triggeredByApiKeyId

The API key that triggered it, if one did.

string | null
idempotencyKey
string | null
algorithm
required

The algorithm used, as in winnerSelectionAlgorithm.

string
algorithmConfigSnapshot
required
object
maxWinners
integer
>= 1 <= 1000
tieBreak

How equal scores are ordered, for score_highest.

string
Allowed values: earliest_entry random
windowStart

Only entries made at or after this time count.

string format: date-time
windowEnd

Only entries made before this time count.

string format: date-time
entriesSnapshotCount
required

How many eligible entries the draw picked from.

integer
status
required
string
Allowed values: pending completed failed
failureReason
string | null
executedAt
string | null format: date-time
createdAt
required
string format: date-time
winners
required
Array<object>
object
id
required
string
runId
required
string
gameInstanceId
required
string
entryId
required
string
participantRef
required
string
rank
required
integer
>= 1
prize
One of:

What the campaign is giving away, as shown to winners.

object
label
required
string
>= 1 characters
description
string
externalRef

Your own reference for the prize, such as a SKU.

string
notifiedAt
string | null format: date-time
createdAt
required
string format: date-time
replayed
required

True when this repeats an earlier draw with the same idempotencyKey.

boolean
grants
required

Winners who received a coupon in this draw. Codes are not included.

Array<object>
object
winnerId
required
string
grantId
required
string
Examples
Exampledrawn

A new draw

{
"run": {
"id": "0192f3a1-0000-7000-8000-0000000d0001",
"gameInstanceId": "0192f3a1-0000-7000-8000-0000000ca001",
"triggeredBy": "manual",
"triggeredByApiKeyId": "0192f3a1-0000-7000-8000-0000000ce001",
"idempotencyKey": "draw-2026-10-31-final",
"algorithm": "random_uniform",
"algorithmConfigSnapshot": {
"maxWinners": 1
},
"entriesSnapshotCount": 412,
"status": "completed",
"failureReason": null,
"executedAt": "2026-10-31T18:00:01.000Z",
"createdAt": "2026-10-31T18:00:00.000Z"
},
"winners": [
{
"id": "0192f3a1-0000-7000-8000-0000000e0001",
"runId": "0192f3a1-0000-7000-8000-0000000d0001",
"gameInstanceId": "0192f3a1-0000-7000-8000-0000000ca001",
"entryId": "0192f3a1-0000-7000-8000-0000000f0001",
"participantRef": "user_48213",
"rank": 1,
"prize": {
"label": "Free coffee"
},
"notifiedAt": null,
"createdAt": "2026-10-31T18:00:01.000Z"
}
],
"replayed": false,
"grants": [
{
"winnerId": "0192f3a1-0000-7000-8000-0000000e0001",
"grantId": "0192f3a1-0000-7000-8000-000000073003"
}
]
}
X-RateLimit-Limit
integer

Requests allowed in the current window.

X-RateLimit-Remaining
integer

Requests left in the current window.

The request is not valid. details lists each field that failed, where there is one.

Media typeapplication/json
object
error
required
object
code
required

Stable and safe to branch on.

  • invalid_request (400): the request is not valid.
  • unauthorized (401): missing, malformed or revoked API key.
  • upgrade_required (402): not in your plan, or a plan limit is reached.
  • forbidden (403): the key lacks a permission, or the action is not allowed.
  • not_found (404): nothing with that id in your account.
  • conflict (409): clashes with the current state.
  • confirmation_required (409): repeat with confirm=true. Folder deletion only.
  • rate_limited (429): wait for Retry-After seconds.
  • internal_error (500) and service_unavailable (503): try again later.
string
Allowed values: invalid_request unauthorized upgrade_required forbidden not_found conflict confirmation_required rate_limited internal_error service_unavailable
message
required

Written for a person. May change; do not parse it.

string
details
One of:
Array<object>
object
path
required

The failing field, dot separated, such as prize.label.

string
message
required
string
Example
{
"error": {
"code": "invalid_request",
"message": "Invalid request json.",
"details": [
{
"path": "name",
"message": "Too small, expected string to have >=1 characters"
}
]
}
}

The API key is missing, malformed or revoked.

Media typeapplication/json
object
error
required
object
code
required

Stable and safe to branch on.

  • invalid_request (400): the request is not valid.
  • unauthorized (401): missing, malformed or revoked API key.
  • upgrade_required (402): not in your plan, or a plan limit is reached.
  • forbidden (403): the key lacks a permission, or the action is not allowed.
  • not_found (404): nothing with that id in your account.
  • conflict (409): clashes with the current state.
  • confirmation_required (409): repeat with confirm=true. Folder deletion only.
  • rate_limited (429): wait for Retry-After seconds.
  • internal_error (500) and service_unavailable (503): try again later.
string
Allowed values: invalid_request unauthorized upgrade_required forbidden not_found conflict confirmation_required rate_limited internal_error service_unavailable
message
required

Written for a person. May change; do not parse it.

string
details
One of:
Array<object>
object
path
required

The failing field, dot separated, such as prize.label.

string
message
required
string
Example
{
"error": {
"code": "unauthorized",
"message": "Invalid or revoked API key."
}
}

The API key does not carry the permission this needs, or the request is not allowed.

Media typeapplication/json
object
error
required
object
code
required

Stable and safe to branch on.

  • invalid_request (400): the request is not valid.
  • unauthorized (401): missing, malformed or revoked API key.
  • upgrade_required (402): not in your plan, or a plan limit is reached.
  • forbidden (403): the key lacks a permission, or the action is not allowed.
  • not_found (404): nothing with that id in your account.
  • conflict (409): clashes with the current state.
  • confirmation_required (409): repeat with confirm=true. Folder deletion only.
  • rate_limited (429): wait for Retry-After seconds.
  • internal_error (500) and service_unavailable (503): try again later.
string
Allowed values: invalid_request unauthorized upgrade_required forbidden not_found conflict confirmation_required rate_limited internal_error service_unavailable
message
required

Written for a person. May change; do not parse it.

string
details
One of:
Array<object>
object
path
required

The failing field, dot separated, such as prize.label.

string
message
required
string
Example
{
"error": {
"code": "forbidden",
"message": "Your role does not allow this (draws.run)."
}
}

Nothing with that id exists in your account.

Media typeapplication/json
object
error
required
object
code
required

Stable and safe to branch on.

  • invalid_request (400): the request is not valid.
  • unauthorized (401): missing, malformed or revoked API key.
  • upgrade_required (402): not in your plan, or a plan limit is reached.
  • forbidden (403): the key lacks a permission, or the action is not allowed.
  • not_found (404): nothing with that id in your account.
  • conflict (409): clashes with the current state.
  • confirmation_required (409): repeat with confirm=true. Folder deletion only.
  • rate_limited (429): wait for Retry-After seconds.
  • internal_error (500) and service_unavailable (503): try again later.
string
Allowed values: invalid_request unauthorized upgrade_required forbidden not_found conflict confirmation_required rate_limited internal_error service_unavailable
message
required

Written for a person. May change; do not parse it.

string
details
One of:
Array<object>
object
path
required

The failing field, dot separated, such as prize.label.

string
message
required
string
Example
{
"error": {
"code": "not_found",
"message": "Channel 0192f3a1-0000-7000-8000-00000000c009 not found."
}
}

The request clashes with the current state. The message says what to change.

Media typeapplication/json
object
error
required
object
code
required

Stable and safe to branch on.

  • invalid_request (400): the request is not valid.
  • unauthorized (401): missing, malformed or revoked API key.
  • upgrade_required (402): not in your plan, or a plan limit is reached.
  • forbidden (403): the key lacks a permission, or the action is not allowed.
  • not_found (404): nothing with that id in your account.
  • conflict (409): clashes with the current state.
  • confirmation_required (409): repeat with confirm=true. Folder deletion only.
  • rate_limited (429): wait for Retry-After seconds.
  • internal_error (500) and service_unavailable (503): try again later.
string
Allowed values: invalid_request unauthorized upgrade_required forbidden not_found conflict confirmation_required rate_limited internal_error service_unavailable
message
required

Written for a person. May change; do not parse it.

string
details
One of:
Array<object>
object
path
required

The failing field, dot separated, such as prize.label.

string
message
required
string
Example
{
"error": {
"code": "conflict",
"message": "An archived campaign cannot be edited. Restore it first, then make the change."
}
}

Rate limit exceeded. Wait for Retry-After seconds.

Media typeapplication/json
object
error
required
object
code
required

Stable and safe to branch on.

  • invalid_request (400): the request is not valid.
  • unauthorized (401): missing, malformed or revoked API key.
  • upgrade_required (402): not in your plan, or a plan limit is reached.
  • forbidden (403): the key lacks a permission, or the action is not allowed.
  • not_found (404): nothing with that id in your account.
  • conflict (409): clashes with the current state.
  • confirmation_required (409): repeat with confirm=true. Folder deletion only.
  • rate_limited (429): wait for Retry-After seconds.
  • internal_error (500) and service_unavailable (503): try again later.
string
Allowed values: invalid_request unauthorized upgrade_required forbidden not_found conflict confirmation_required rate_limited internal_error service_unavailable
message
required

Written for a person. May change; do not parse it.

string
details
One of:
Array<object>
object
path
required

The failing field, dot separated, such as prize.label.

string
message
required
string
Example
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded: 30 requests per 60s."
}
}
Retry-After
integer

Seconds to wait before trying again.