# VoteTracker Giveaways API Guide

Create and manage giveaways programmatically, and let your own bot hand out prizes automatically.
Request and response schemas for each endpoint live in the [API Reference](/docs).

**Official SDK:** [`@votetracker/sdk`](https://www.npmjs.com/package/@votetracker/sdk) (TypeScript/JavaScript) wraps every endpoint in this guide with typed requests, retries and idempotency support. Source at [votetracker-bot/sdks](https://github.com/votetracker-bot/sdks).

## Overview

The giveaways API mirrors what the dashboard can do:

- `GET /api/v1/integrations/{integrationId}/events` lists giveaways (scope `events:read`)
- `GET /api/v1/integrations/{integrationId}/events/{eventId}` returns one giveaway with winners (scope `events:read`)
- `POST /api/v1/integrations/{integrationId}/events/giveaways` creates a giveaway (scope `events:write`)
- `POST .../events/{eventId}/activate` activates a draft
- `PUT .../events/{eventId}` updates a draft
- `POST .../events/{eventId}/cancel` cancels
- `PUT .../events/{eventId}/repeating` sets recurrence
- `POST .../events/{eventId}/winners/{userPlatformId}/reroll` rerolls one winner
- `POST .../events/{eventId}/winners/reroll` rerolls all winners

Authentication uses the same API keys as the rest of the public API. Writes require the
`events:write` scope on the key.

## Guild Authorization

Every giveaway belongs to a Discord server. Writes are authorized with the same rule the dashboard
uses: the Vote Tracker bot must be in the target server AND the integration's linked owner must
have Manage Server (or Admin) there. No setup needed. Requests failing the check return `403`,
and `503` when the check itself cannot be answered.

## Creating a Giveaway

All Discord ids (servers, channels, roles) are strings. Bare JSON numbers are tolerated on input, but strings avoid precision loss in JavaScript and are the documented form.

```
POST /api/v1/integrations/{integrationId}/events/giveaways
Authorization: Bearer vtk_live_...
Idempotency-Key: create-2026-08-04-weekly
```

```json
{
  "guild_id": "610464532967456778",
  "starts_at": "2026-08-05T18:00:00Z",
  "ends_at": "2026-08-06T18:00:00Z",
  "winner_count": 3,
  "prize_text": "500 credits",
  "announcement_channel_id": "763124809637953577",
  "external_reward": { "kind": "currency", "amount": 500 },
  "prize_image_url": "https://cdn.example.com/prizes/credits.png",
  "repeating": true,
  "activate": true
}
```

Notes:

- `activate: true` skips the draft step. Without it, follow up with the activate endpoint.
- The optional `Idempotency-Key` header (1 to 128 characters) makes retries safe. A replay returns
  the originally created giveaway with the `Idempotent-Replay: true` response header, and reusing
  a key with a different payload fails with `409`. Without the header every request creates a new
  giveaway, so send one whenever you retry automatically.
- When activation fails (for example the bot cannot post in the announcement channel), the giveaway
  stays as a draft and the response is a `409` naming the draft, so you can fix the channel and
  retry activation.
- `require_guild_membership` defaults to `true`: voters must be members of the server to enter.
  Members who join later still earn entries for their active votes.
- `message` and `completed_message` are optional. Without them the bot posts its default
  announcements, including the prize image when `prize_image_url` is set.

## Premium

The API itself is free. Premium features cost the same through the API as in the dashboard:

- `external_reward`, role gating, entry multipliers and `repeating: true` require premium
- Free integrations run one basic giveaway at a time

Requests using premium features without premium fail with `402` and a problem response naming the
feature.

## Prize Payload (`external_reward`)

`external_reward` is an opaque JSON object or array, at most 2048 bytes. VoteTracker stores and
echoes it verbatim in `EVENT_CREATED`, `EVENT_WINNERS_SELECTED` and `EVENT_WINNER_REROLLED`
webhooks and in API reads. It never appears in Discord messages. Use it to tell your own bot what
to hand out, the schema is yours.

For different prizes per placement, key the payload by rank. Every winner carries a `rank`, and a
reroll replaces the winner at the same rank, so the mapping stays valid across rerolls:

```json
{
  "kind": "card_tiers",
  "ranks": {
    "1": { "card_id": "holo_pikachu" },
    "2": { "card_id": "rare_eevee" },
    "3": { "card_id": "booster_pack" }
  }
}
```

## Receiving Winners

Subscribe to `EVENT_WINNERS_SELECTED` in your
[forwarding configuration](/docs/forwarding). Each winner carries `user_platform_id`, `rank`,
`entries_at_draw`, `discord_id` and `user_name`. `discord_id` is optional and exists for actions on Discord: for `BOT`/`SERVER`
projects it equals `user_platform_id`, for `GAME` projects it differs (the winner's linked
Discord account) and is null when the voter never linked one. Dedupe grants on `event_id` plus
`user_platform_id`, the same delivery can arrive more than once.

If a delivery ends in `DEAD_LETTER`, reconcile through the list and detail endpoints, both
include winners and `external_reward`.

## Rerolls

Replace one winner or all current winners with freshly drawn eligible participants. A reason is
required, kept in the audit trail and never shown in the Discord channel. Constraints:

- Only completed giveaways, within 7 days of the draw
- Capped at the giveaway's winner count in total
- Replaced users can never win the same giveaway again
- Rerolling all winners either fully succeeds or changes nothing

Each reroll emits `EVENT_WINNER_REROLLED` with the replaced and new winners, revoke and grant
accordingly. The Discord announcement is updated automatically.

## Rate Limits

Per API key, giveaway reads share 120 requests per minute and giveaway writes share 30 per
minute. Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`
headers, and `429` responses include `Retry-After`.

We're here to help. Drop by the **[support server](https://votetracker.bot/support)** if you need anything.
