Back to Streamneeds

API Documentation

Public reference for the Streamneeds REST API. Authentication is required per-request via an API key — generate one from your account's API Keys page.

Authentication

All API requests require authentication using an API key. Include your API key in the Authorization header:

Authorization: Bearer bh_sk_your_api_key_here

Base URL

https://streamneeds.com

API Scopes

read

Read general data and leaderboards

write

Write general data

points:read

Read point balances and transactions

points:write

Award, remove, set, purchase, or refund points

users:read

Read detailed user profiles (casinos, crypto, affiliate codes)

store:read

Read store catalog items

store:purchase

Record store purchases (can be used instead of points:write for checkout)

raffles:read

Read raffle catalogue and a viewer's ticket totals

raffles:purchase

Buy raffle tickets on a viewer's behalf (can be used instead of points:write)

identity:write

Link/merge a viewer's platform accounts from your own site + ingest their credentials

games:read

Read the slot database — the full game catalogue feed (GET /api/v1/games)

hunts:read

Read this account's bonus hunts, summaries and per-hunt entries (GET /api/v1/hunts)

slot-requests:read

Read this account's viewer slot requests (GET /api/v1/slot-requests)

admin

Legacy scope — no longer grants implicit access to anything. Keys holding only 'admin' fail every check; re-issue them with explicit scopes.

Endpoints

GET/api/v1/health

Zero-cost liveness probe. Returns 200 when the API is up — touches no database, requires no authentication, and can be polled as often as you like. Use THIS for availability pre-checks, never a data endpoint.

Required Scope:

public (no API key required)

Response:

{
  "ok": true,
  "ts": 1752690000000
}

Example cURL:

curl -X GET "https://streamneeds.com/api/v1/health" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
GET/api/public/users/{identifier}/points

Get a user's current point balance

Required Scope:

read or points:read

Parameters:

identifierpathrequired

Discord ID (17-20 digits), Kick username, or Viewer UUID

Response:

{
  "viewerId": "uuid-here",
  "points": 1500,
  "identifier": "123456789012345678"
}

Example cURL:

curl -X GET "https://streamneeds.com/api/public/users/123456789012345678/points" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
GET/api/public/users/{identifier}/transactions

Get a user's point transaction history. Supports a since filter for reconciliation — fetch every movement after a timestamp and compare it against your own ledger.

Required Scope:

read or points:read

Parameters:

identifierpathrequired

Discord ID, Kick username, or Viewer UUID

limitquery

Number of transactions to return (default: 50, max: 100)

offsetquery

Pagination offset (default: 0)

sincequery

ISO-8601 timestamp — only return movements at or after this instant (e.g. 2026-07-16T19:00:00Z)

Response:

{
  "identifier": "username",
  "transactions": [
    {
      "id": "tx-id",
      "amount": 100,
      "reason": "api_award",
      "platform": "discord",
      "createdAt": "2024-01-15T10:30:00Z"
    }
  ],
  "count": 1,
  "limit": 50,
  "offset": 0,
  "since": "2024-01-15T00:00:00Z"
}

Example cURL:

curl -X GET "https://streamneeds.com/api/public/users/123456789012345678/transactions" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
GET/api/public/users/{identifier}/profile

Get a user's complete profile: points, linked Discord/Kick accounts, casino credentials, crypto addresses, and affiliate codes scoped to the authenticated streamer.

Required Scope:

read or users:read

Parameters:

identifierpathrequired

Discord ID, Kick username, or Viewer UUID

Response:

{
  "viewerId": "uuid-here",
  "points": 1500,
  "createdAt": "2024-01-01T00:00:00Z",
  "discord": [
    {
      "discordId": "123456789012345678",
      "username": "user",
      "displayName": "Display Name",
      "avatarUrl": "https://...",
      "verified": true,
      "banned": false,
      "flagged": false
    }
  ],
  "kick": [
    {
      "username": "kickuser",
      "displayName": "Kick Name",
      "profilePicture": "https://...",
      "verified": false
    }
  ],
  "casinos": [
    {
      "id": "uuid",
      "platform": "stake",
      "username": "casinoUser123",
      "note": null,
      "verified": true,
      "createdAt": "2024-01-01T00:00:00Z",
      "updatedAt": "2024-01-01T00:00:00Z"
    },
    {
      "id": "uuid",
      "platform": "hypebet",
      "username": "hypeUser",
      "note": "VIP",
      "verified": false,
      "createdAt": "2024-01-01T00:00:00Z",
      "updatedAt": "2024-01-01T00:00:00Z"
    }
  ],
  "cryptoAddresses": [
    {
      "id": "uuid",
      "currency": "USDT",
      "address": "0x...",
      "network": "ethereum",
      "isPrimary": true,
      "verified": true,
      "createdAt": "2024-01-01T00:00:00Z",
      "updatedAt": "2024-01-01T00:00:00Z"
    }
  ],
  "affiliateCodes": [
    {
      "id": "uuid",
      "platform": "stake",
      "code": "STREAMER123",
      "verified": true,
      "verifiedAt": "2024-01-02T00:00:00Z",
      "createdAt": "2024-01-01T00:00:00Z",
      "updatedAt": "2024-01-02T00:00:00Z"
    }
  ]
}

Example cURL:

curl -X GET "https://streamneeds.com/api/public/users/123456789012345678/profile" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
GET/api/public/users/{identifier}/purchases

Get a user's store purchase history, scoped to the authenticated streamer's store. Supports filtering by purchase status and fulfillment status.

Required Scope:

read, users:read, or store:read

Parameters:

identifierpathrequired

Discord ID, Kick username, or Viewer UUID

limitquery

Number of purchases to return (default: 50, max: 100)

offsetquery

Pagination offset (default: 0)

statusquery

Filter by purchase status: completed, refunded, cancelled

fulfillmentStatusquery

Filter by fulfillment status: pending, processing, shipped, delivered, completed

Response:

{
  "identifier": "username",
  "viewerId": "uuid-here",
  "purchases": [
    {
      "id": "purchase-uuid",
      "item": {
        "id": "item-uuid",
        "name": "$10 Stake Bonus",
        "slug": "stake-bonus-10",
        "description": "Credited to your Stake account",
        "imageUrl": "https://...",
        "instructions": "Provide your Stake username at checkout"
      },
      "quantity": 1,
      "pointsSpent": 750,
      "pricePerItem": 750,
      "status": "completed",
      "fulfillmentStatus": "pending",
      "paidStatus": "unpaid",
      "fulfilledAt": null,
      "refundedAt": null,
      "refundReason": null,
      "trackingNumber": null,
      "notes": null,
      "purchasedFrom": "api",
      "createdAt": "2024-01-15T10:30:00Z",
      "updatedAt": "2024-01-15T10:30:00Z"
    }
  ],
  "count": 1,
  "total": 1,
  "limit": 50,
  "offset": 0
}

Example cURL:

curl -X GET "https://streamneeds.com/api/public/users/123456789012345678/purchases" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
GET/api/public/leaderboard

Get top users by points

Required Scope:

read or points:read

Parameters:

limitquery

Number of users to return (default: 10, max: 100)

offsetquery

Pagination offset (default: 0)

Response:

{
  "leaderboard": [
    {
      "rank": 1,
      "viewerId": "uuid-here",
      "points": 5000,
      "username": "TopUser",
      "avatar": "https://...",
      "verified": true,
      "platforms": {
        "discord": {
          "discordId": "123456789012345678",
          "username": "user#1234"
        },
        "kick": {
          "username": "kickuser"
        }
      }
    }
  ],
  "count": 1,
  "limit": 10,
  "offset": 0
}

Example cURL:

curl -X GET "https://streamneeds.com/api/public/leaderboard" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
POST/api/public/points/award

Award points to a user. Supports idempotency keys: send a unique key per logical operation and retries with the same key replay the original result instead of awarding twice (replayed responses carry an Idempotency-Replay: true header).

Required Scope:

points:write

Parameters:

identifierbodyrequired

Discord ID, Kick username, or Viewer UUID

amountbodyrequired

Number of points to award (must be positive)

reasonbody

Reason for awarding points (default: 'api_award')

idempotencyKeybody

Unique key (e.g. UUID) per logical operation — same key never awards twice. Can also be sent as an Idempotency-Key header. Keys expire after 7 days. Reusing a key with a different body returns 422; a concurrent duplicate returns 409 (retry shortly).

Request Body:

{
  "identifier": "123456789012345678",
  "amount": 100,
  "reason": "tournament_win",
  "idempotencyKey": "9f8e7d6c-5b4a-3210-fedc-ba9876543210"
}

Response:

{
  "success": true,
  "viewerId": "uuid-here",
  "identifier": "123456789012345678",
  "awarded": 100,
  "newBalance": 1600,
  "reason": "tournament_win"
}

Example cURL:

curl -X POST "https://streamneeds.com/api/public/points/award" \
  -H "Authorization: Bearer bh_sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"identifier":"123456789012345678","amount":100,"reason":"tournament_win","idempotencyKey":"9f8e7d6c-5b4a-3210-fedc-ba9876543210"}'
POST/api/public/points/remove

Remove points from a user. Supports idempotency keys: send a unique key per logical operation and retries with the same key replay the original result instead of deducting twice (replayed responses carry an Idempotency-Replay: true header).

Required Scope:

points:write

Parameters:

identifierbodyrequired

Discord ID, Kick username, or Viewer UUID

amountbodyrequired

Number of points to remove (must be positive)

reasonbody

Reason for removing points (default: 'api_remove')

idempotencyKeybody

Unique key (e.g. UUID) per logical operation — same key never deducts twice. Can also be sent as an Idempotency-Key header. Keys expire after 7 days. Reusing a key with a different body returns 422; a concurrent duplicate returns 409 (retry shortly).

Request Body:

{
  "identifier": "kickusername",
  "amount": 50,
  "reason": "rule_violation",
  "idempotencyKey": "1a2b3c4d-5e6f-7890-abcd-ef0123456789"
}

Response:

{
  "success": true,
  "viewerId": "uuid-here",
  "identifier": "kickusername",
  "removed": 50,
  "newBalance": 1550,
  "reason": "rule_violation"
}

Example cURL:

curl -X POST "https://streamneeds.com/api/public/points/remove" \
  -H "Authorization: Bearer bh_sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"identifier":"kickusername","amount":50,"reason":"rule_violation","idempotencyKey":"1a2b3c4d-5e6f-7890-abcd-ef0123456789"}'
POST/api/public/points/set

Set a user's points to a specific amount

Required Scope:

points:write

Parameters:

identifierbodyrequired

Discord ID, Kick username, or Viewer UUID

amountbodyrequired

New point balance (must be non-negative)

reasonbody

Reason for setting points (default: 'api_set')

Request Body:

{
  "identifier": "uuid-viewer-id",
  "amount": 2000,
  "reason": "season_reset"
}

Response:

{
  "success": true,
  "viewerId": "uuid-viewer-id",
  "identifier": "uuid-viewer-id",
  "previousBalance": 1550,
  "newBalance": 2000,
  "reason": "season_reset"
}

Example cURL:

curl -X POST "https://streamneeds.com/api/public/points/set" \
  -H "Authorization: Bearer bh_sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"identifier":"uuid-viewer-id","amount":2000,"reason":"season_reset"}'
GET/api/public/banned-games/{userId}

List the games a streamer has banned from their bonus hunts. Public endpoint — no authentication.

Required Scope:

public (no API key required)

Parameters:

userIdpathrequired

Streamer's user ID (their Streamneeds User UUID)

Response:

{
  "user": {
    "id": "streamer-uuid",
    "name": "Streamer Name"
  },
  "bannedGames": [
    {
      "id": "uuid",
      "gameName": "Example Slot",
      "gameSlug": "example-slot",
      "gameImage": "https://...",
      "reason": "Low RTP / avoid",
      "bannedBy": "streamer-uuid",
      "createdAt": "2024-01-15T10:30:00Z"
    }
  ],
  "total": 1
}

Example cURL:

curl -X GET "https://streamneeds.com/api/public/banned-games/{userId}" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
GET/api/public/store/items

List the streamer's point-shop items (white-label store catalog). Calculates currentPrice / onSale from active sales.

Required Scope:

read or store:read

Parameters:

userIdqueryrequired

Must match the API key's owner (self-scoped)

includeDisabledquery

Include disabled items in the response (default: false)

Response:

{
  "items": [
    {
      "id": "uuid",
      "name": "$10 Stake Bonus",
      "slug": "stake-bonus-10",
      "description": "Credited to your Stake account",
      "imageUrl": "https://...",
      "price": 1000,
      "salePrice": 750,
      "saleEndsAt": "2024-02-01T00:00:00Z",
      "stock": 50,
      "sold": 12,
      "enabled": true,
      "maxPerUser": 1,
      "instructions": "Provide your Stake username at checkout",
      "currentPrice": 750,
      "onSale": true,
      "inStock": true,
      "stockRemaining": 50,
      "createdAt": "2024-01-01T00:00:00Z",
      "updatedAt": "2024-01-20T00:00:00Z"
    }
  ],
  "total": 1
}

Example cURL:

curl -X GET "https://streamneeds.com/api/public/store/items" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
POST/api/public/store/purchase

Record a purchase and deduct points. Supports Streamneeds store items (itemId) or external white-label items (amount + itemName). Optional webhookUrl fires a purchase.completed event.

Required Scope:

points:write or store:purchase

Parameters:

identifierbodyrequired

Discord ID, Kick username, or Viewer UUID

itemIdbody

UUID of a Streamneeds store item. Mutually exclusive with amount + itemName.

quantitybody

Units to buy when using itemId (default: 1)

amountbody

Point cost for an external (non-Streamneeds) item

itemNamebody

Display name for an external item

itemDescriptionbody

Description for the external item

metadatabody

Arbitrary JSON metadata passed through to the webhook

storeIdbody

External store identifier

storeNamebody

External store display name

externalTxIdbody

Your external transaction ID for reconciliation

webhookUrlbody

URL that receives a fire-and-forget purchase.completed POST on success

Request Body:

{
  "identifier": "discord:123456789012345678",
  "itemId": "uuid-of-store-item",
  "quantity": 1,
  "webhookUrl": "https://your-site.com/webhooks/purchase"
}

Response:

{
  "success": true,
  "transaction": {
    "purchaseId": "uuid",
    "viewerId": "uuid-viewer-id",
    "previousBalance": 2000,
    "amountDeducted": 750,
    "newBalance": 1250,
    "itemName": "$10 Stake Bonus",
    "itemDescription": "Credited to your Stake account",
    "quantity": 1,
    "instructions": "Provide your Stake username at checkout",
    "timestamp": "2024-01-20T12:30:00Z"
  }
}

Example cURL:

curl -X POST "https://streamneeds.com/api/public/store/purchase" \
  -H "Authorization: Bearer bh_sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"identifier":"discord:123456789012345678","itemId":"uuid-of-store-item","quantity":1,"webhookUrl":"https://your-site.com/webhooks/purchase"}'
GET/api/public/raffles

List the streamer's raffles with ticket price, draw date, totals, and winner info. Use this to render a raffle catalogue on your site.

Required Scope:

read or raffles:read

Parameters:

statusquery

Filter by status: active | drawn | cancelled | all (default: active)

includeDisabledquery

Include raffles with enabled=false (default: false)

Response:

{
  "raffles": [
    {
      "id": "uuid",
      "slug": "weekly-cashout",
      "name": "Weekly $100 Cashout",
      "description": "Cash prize drawn every Friday",
      "imageUrl": "https://...",
      "ticketPrice": 50,
      "status": "drawn",
      "enabled": true,
      "drawDate": "2024-02-02T20:00:00Z",
      "drawnAt": "2024-02-02T20:05:00Z",
      "isOpen": false,
      "stats": {
        "totalTickets": 240,
        "totalEntries": 47,
        "entryRecords": 62
      },
      "winner": {
        "viewerId": "uuid-viewer-id",
        "username": "LuckyDuck",
        "winningTicketNumber": 137,
        "ticketsEntered": 18,
        "drawnAt": "2024-02-02T20:05:00Z"
      },
      "createdAt": "2024-01-20T00:00:00Z",
      "updatedAt": "2024-01-20T00:00:00Z"
    }
  ],
  "total": 1
}

Example cURL:

curl -X GET "https://streamneeds.com/api/public/raffles" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
GET/api/public/raffles/{id}

Get a single raffle by UUID or slug. Pass an optional identifier to get that viewer's ticket totals for this raffle. Pass includeTicketNumbers=true alongside identifier to include the viewer's individual ticket numbers.

Required Scope:

read or raffles:read

Parameters:

idpathrequired

Raffle UUID or slug

identifierquery

Discord ID / Kick username / Viewer UUID — returns that viewer's ticketCount + pointsSpent + isWinner under `viewer`

includeTicketNumbersquery

Set to 'true' alongside `identifier` to include that viewer's individual ticket numbers in `viewer.ticketNumbers`. Default: false.

Response:

{
  "id": "uuid",
  "slug": "weekly-cashout",
  "name": "Weekly $100 Cashout",
  "description": "Cash prize drawn every Friday",
  "imageUrl": "https://...",
  "ticketPrice": 50,
  "status": "drawn",
  "enabled": true,
  "drawDate": "2024-02-02T20:00:00Z",
  "drawnAt": "2024-02-02T20:05:00Z",
  "isOpen": false,
  "stats": {
    "totalTickets": 240,
    "totalEntries": 47,
    "entryRecords": 62
  },
  "winner": {
    "viewerId": "uuid-viewer-id",
    "username": "LuckyDuck",
    "winningTicketNumber": 137,
    "ticketsEntered": 18,
    "drawnAt": "2024-02-02T20:05:00Z"
  },
  "viewer": {
    "viewerId": "uuid-viewer-id",
    "ticketCount": 12,
    "pointsSpent": 600,
    "isWinner": false,
    "ticketNumbers": [
      3,
      4,
      5,
      88,
      89,
      90,
      91,
      92,
      150,
      151,
      152,
      153
    ]
  },
  "createdAt": "2024-01-20T00:00:00Z",
  "updatedAt": "2024-01-20T00:00:00Z"
}

Example cURL:

curl -X GET "https://streamneeds.com/api/public/raffles/{id}" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
POST/api/public/raffles/purchase

Buy raffle tickets on behalf of a viewer. Deducts points atomically and increments the raffle's totals. ticketCount is capped at 10,000 per call.

Required Scope:

points:write or raffles:purchase

Parameters:

identifierbodyrequired

Discord ID, Kick username, or Viewer UUID

prizeIdbodyrequired

Raffle UUID or slug

ticketCountbody

1-10000 (default: 1)

purchasedFrombody

Source label stored on the entry (default: 'api')

Request Body:

{
  "identifier": "discord:123456789012345678",
  "prizeId": "weekly-cashout",
  "ticketCount": 5
}

Response:

{
  "success": true,
  "transaction": {
    "entryId": "uuid",
    "viewerId": "uuid-viewer-id",
    "prizeId": "uuid",
    "prizeName": "Weekly $100 Cashout",
    "ticketCount": 5,
    "pricePerTicket": 50,
    "pointsSpent": 250,
    "previousBalance": 2000,
    "newBalance": 1750,
    "ticketNumbers": {
      "first": 241,
      "last": 245
    },
    "viewerTotalsForRaffle": {
      "ticketCount": 12,
      "pointsSpent": 600
    },
    "timestamp": "2024-01-25T12:30:00Z"
  }
}

Example cURL:

curl -X POST "https://streamneeds.com/api/public/raffles/purchase" \
  -H "Authorization: Bearer bh_sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"identifier":"discord:123456789012345678","prizeId":"weekly-cashout","ticketCount":5}'
GET/api/public/users/{identifier}/raffles

List a viewer's raffle tickets, grouped by prize. Each group includes the individual ticket numbers they hold (for a 'my tickets' page or post-draw reveal), the total tickets they bought, and whether they won the prize.

Required Scope:

read, users:read, or raffles:read

Parameters:

identifierpathrequired

Discord ID, Kick username, or Viewer UUID

prizeIdquery

Filter to a single raffle

statusquery

Filter prizes by status: active, drawn, cancelled

includeTicketNumbersquery

Set to "false" to omit the per-ticket number list (useful for large raffles). Default: true

Response:

{
  "identifier": "username",
  "viewerId": "uuid-viewer-id",
  "raffles": [
    {
      "prize": {
        "id": "uuid",
        "name": "Weekly $100 Cashout",
        "slug": "weekly-cashout",
        "imageUrl": "https://...",
        "ticketPrice": 50,
        "drawDate": "2024-02-02T20:00:00Z",
        "status": "active",
        "totalTickets": 240
      },
      "ticketCount": 5,
      "pointsSpent": 250,
      "ticketNumbers": [
        241,
        242,
        243,
        244,
        245
      ],
      "isWinner": false,
      "winningTicketNumber": null
    }
  ],
  "totalRaffles": 1,
  "totalTickets": 5
}

Example cURL:

curl -X GET "https://streamneeds.com/api/public/users/123456789012345678/raffles" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
POST/api/public/store/refund

Credit points back to a viewer (use when a purchase fails, an item is out of stock, or you need to reverse a transaction). Logged to the streamer's activity log.

Required Scope:

points:write

Parameters:

identifierbodyrequired

Discord ID, Kick username, or Viewer UUID

amountbodyrequired

Points to credit back (must be positive)

reasonbodyrequired

Human-readable reason for the refund

externalTxIdbody

Your external transaction ID for the refund

originalTxIdbody

The original purchase transaction you're reversing

Request Body:

{
  "identifier": "discord:123456789012345678",
  "amount": 500,
  "reason": "Purchase failed - item out of stock",
  "externalTxId": "tx_refund_abc123",
  "originalTxId": "tx_original_purchase"
}

Response:

{
  "success": true,
  "viewerId": "uuid-viewer-id",
  "amountRefunded": 500,
  "newBalance": 1750,
  "reason": "Purchase failed - item out of stock"
}

Example cURL:

curl -X POST "https://streamneeds.com/api/public/store/refund" \
  -H "Authorization: Bearer bh_sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"identifier":"discord:123456789012345678","amount":500,"reason":"Purchase failed - item out of stock","externalTxId":"tx_refund_abc123","originalTxId":"tx_original_purchase"}'
POST/api/v1/identity/link

External identity-link / merge request. For streamers running their OWN site where viewers log in and link their platforms. Send all the linked ids in one call and we merge them into a single unified viewer, ingesting casino/crypto/affiliate credentials. An account that hasn't chatted yet is pre-seeded and auto-attaches to the same viewer on its first appearance in chat — no follow-up call needed. Idempotent: re-send the same body to force a re-merge immediately (also honours an Idempotency-Key header). At least one of discordId / kickUserId is required.

Required Scope:

identity:write

Parameters:

externalIdbodyrequired

Your stable id for the user (e.g. Google sub or your own DB id). One link record per externalId; re-POST to update.

discordIdbody

Discord snowflake (from Discord OAuth). Required unless kickUserId is given.

kickUserIdbody

Kick's NUMERIC user id (from Kick OAuth) — not the username. Non-numeric values are rejected with 400. Required unless discordId is given.

autoVerifybody

Mark the linked Discord account + ingested credentials verified (the site login is the vouch). Default: true.

credentialsbody

Optional { casino: [{platform, username}], crypto: [{currency, address, network}], affiliate: [{platform, code}] } — all upserted onto the viewer.

idempotencyKeybody

Unique key per logical operation (or Idempotency-Key header) — safe retries. The whole operation is idempotent regardless.

Request Body:

{
  "externalId": "google-oauth-sub-123",
  "discordId": "123456789012345678",
  "kickUserId": "987654",
  "autoVerify": true,
  "credentials": {
    "casino": [
      {
        "platform": "hypebet",
        "username": "bigwinner"
      }
    ],
    "affiliate": [
      {
        "platform": "hypebet",
        "code": "HADDZY"
      }
    ]
  }
}

Response:

{
  "success": true,
  "viewerId": "uuid-viewer-id",
  "createdViewer": false,
  "mergedViewerCount": 1,
  "discordLinked": true,
  "kickLinked": false,
  "discordPending": false,
  "kickPending": true,
  "credentialsApplied": {
    "casino": 1,
    "crypto": 0,
    "affiliate": 1
  },
  "fullyApplied": false
}

Example cURL:

curl -X POST "https://streamneeds.com/api/v1/identity/link" \
  -H "Authorization: Bearer bh_sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"externalId":"google-oauth-sub-123","discordId":"123456789012345678","kickUserId":"987654","autoVerify":true,"credentials":{"casino":[{"platform":"hypebet","username":"bigwinner"}],"affiliate":[{"platform":"hypebet","code":"HADDZY"}]}}'
GET/api/v1/identity/link

Status check for a link you already pushed — confirm the merge fully landed and see which side (if any) is still pending because that account hasn't appeared in chat yet. Returns 404 with { found: false } if no link exists for that externalId under this streamer.

Required Scope:

identity:write

Parameters:

externalIdqueryrequired

The externalId you sent when creating the link.

Response:

{
  "found": true,
  "externalId": "google-oauth-sub-123",
  "viewerId": "uuid-viewer-id",
  "fullyApplied": false,
  "discord": {
    "supplied": true,
    "linked": true,
    "pending": false
  },
  "kick": {
    "supplied": true,
    "linked": false,
    "pending": true
  },
  "autoVerify": true,
  "appliedAt": null,
  "updatedAt": "2026-07-17T12:00:00Z"
}

Example cURL:

curl -X GET "https://streamneeds.com/api/v1/identity/link" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
GET/api/v1/games

The slot database — the full game catalogue. Ordered by (updatedAt, id) ascending, so a since watermark plus cursor walks it deterministically. This catalogue is shared platform-wide, not per-account: every key with games:read sees the same rows. Store the highest updatedAt you have seen and pass it as since on the next run; do not re-drain from scratch on a schedule.

Required Scope:

games:read

Parameters:

sincequery

ISO-8601 timestamp — only games with updatedAt at or after this (incremental sync)

cursorquery

Opaque keyset cursor from the previous page's nextCursor

limitquery

Page size (default: 500, max: 1000)

Response:

{
  "games": [
    {
      "slug": "sweet-bonanza",
      "name": "Sweet Bonanza",
      "provider": "Pragmatic Play",
      "imageUrl": "https://.../sweet-bonanza.png",
      "rtp": 96.51,
      "volatility": "High",
      "maxWin": "21,100x",
      "betRange": "0.20 - 125",
      "releaseDate": "2019-06-27T00:00:00Z",
      "releaseStatus": "released",
      "reels": 6,
      "rows": 5,
      "paylines": "Pays Anywhere",
      "hitFreq": "24.5%",
      "features": [
        "Free Spins",
        "Tumble",
        "Multiplier"
      ],
      "timesUsedInHunts": 412,
      "rating": 8.5,
      "updatedAt": "2026-08-01T09:12:00Z"
    }
  ],
  "nextCursor": "MjAyNi0wOC0wMVQwOToxMjowMFp8...",
  "count": 1
}

Example cURL:

curl -X GET "https://streamneeds.com/api/v1/games" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
GET/api/v1/hunts

Bonus hunt summaries, newest first (no entries — use the detail endpoint for those). Scoped to the key owner's own hunts; admin keys read across all streamers. Add anonymize=1 to replace every streamer/viewer identity with a stable pseudonym.

Required Scope:

hunts:read

Parameters:

sincequery

ISO-8601 timestamp — only hunts created at or after this

cursorquery

Opaque keyset cursor from the previous page's nextCursor

limitquery

Page size (default: 50, max: 200)

statusquery

preparing | live | completed

anonymizequery

Set to 1 to replace names with a stable 12-hex pseudonym (anonId)

streamerquery

Admin keys only — restrict to one streamer's User.id. Ignored on non-admin keys.

Response:

{
  "hunts": [
    {
      "id": "uuid-hunt-id",
      "title": "Sunday Hunt",
      "casino": "Hype.bet",
      "currency": "USD",
      "status": "completed",
      "startBalance": 5000,
      "endBalance": 8200,
      "totalCost": 4000,
      "totalWon": 8200,
      "profit": 4200,
      "entryCount": 20,
      "isPublic": false,
      "shareSlug": "8kz0jhh1",
      "startedAt": "2026-08-01T19:00:00Z",
      "completedAt": "2026-08-01T22:30:00Z",
      "createdAt": "2026-08-01T18:45:00Z",
      "streamer": {
        "userId": "uuid-user-id",
        "name": "Streamer",
        "username": "streamer",
        "image": null,
        "kickUsername": "streamer"
      }
    }
  ],
  "nextCursor": "MjAyNi0wOC0wMVQxODo0NTowMFp8...",
  "count": 1
}

Example cURL:

curl -X GET "https://streamneeds.com/api/v1/hunts" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
GET/api/v1/hunts/{id}

Full detail for one bonus hunt — every summary field plus each entry in play order. A hunt belonging to another account returns 404 (not 403), so the endpoint never confirms a foreign hunt exists.

Required Scope:

hunts:read

Parameters:

idpathrequired

The hunt's UUID, from the /api/v1/hunts list

anonymizequery

Set to 1 to replace names with a stable 12-hex pseudonym (anonId)

Response:

{
  "hunt": {
    "id": "uuid-hunt-id",
    "title": "Sunday Hunt",
    "status": "completed",
    "startBalance": 5000,
    "totalCost": 4000,
    "totalWon": 8200,
    "profit": 4200,
    "entries": [
      {
        "id": "uuid-entry-id",
        "gameName": "Sweet Bonanza",
        "gameSlug": "sweet-bonanza",
        "gameProvider": "Pragmatic Play",
        "betSize": 2,
        "cost": 200,
        "result": 1450,
        "multiplier": 725,
        "position": 1,
        "superBonus": false,
        "status": "completed"
      }
    ]
  }
}

Example cURL:

curl -X GET "https://streamneeds.com/api/v1/hunts/{id}" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"
GET/api/v1/slot-requests

Viewer slot requests, most-recently-updated first. Granted separately from hunts:read so a hunt consumer does not automatically gain viewer request data. Scoped to the key owner's own queue; admin keys read across all streamers. since filters on updatedAt, which makes this feed genuinely incremental.

Required Scope:

slot-requests:read

Parameters:

sincequery

ISO-8601 timestamp — only requests updated at or after this

cursorquery

Opaque keyset cursor from the previous page's nextCursor

limitquery

Page size (default: 100, max: 500)

statusquery

pending | played | skipped | archived

anonymizequery

Set to 1 to replace requester identities with a stable pseudonym

streamerquery

Admin keys only — restrict to one streamer's User.id. Ignored on non-admin keys.

Response:

{
  "slotRequests": [
    {
      "id": "uuid-request-id",
      "streamerUserId": "uuid-user-id",
      "gameName": "Sweet Bonanza",
      "gameSlug": "sweet-bonanza",
      "status": "played",
      "isSuper": false,
      "voteCount": 12,
      "huntId": "uuid-hunt-id",
      "betSize": 2,
      "winAmount": 1450,
      "multiplier": 725,
      "pointsAwarded": 500,
      "requester": {
        "platform": "kick",
        "username": "viewer",
        "displayName": "Viewer",
        "avatar": null
      },
      "playedAt": "2026-08-01T20:14:00Z",
      "createdAt": "2026-08-01T19:02:00Z",
      "updatedAt": "2026-08-01T20:14:00Z"
    }
  ],
  "nextCursor": "MjAyNi0wOC0wMVQyMDoxNDowMFp8...",
  "count": 1
}

Example cURL:

curl -X GET "https://streamneeds.com/api/v1/slot-requests" \
  -H "Authorization: Bearer bh_sk_your_api_key_here"

Error Responses

401Invalid or missing API key
{
  "error": "Invalid or missing API key"
}
403Insufficient permissions
{
  "error": "API key does not have 'points:write' scope"
}
404Resource not found
{
  "error": "User not found"
}
400Bad request
{
  "error": "amount must be a positive number"
}
429Rate limit exceeded — honour the Retry-After header
{
  "error": "Rate limit exceeded",
  "retryAfter": 2
}
500Internal server error
{
  "error": "Internal server error"
}
503Temporarily overloaded — honour the Retry-After header and retry (safe with idempotency keys)
{
  "error": "Service temporarily overloaded, retry shortly",
  "retryAfter": 2
}

Integration Best Practices

Health checks go to /api/v1/health

If your integration pre-checks availability before sending a transaction, probe /api/v1/health — it costs nothing on our side. Never use a data endpoint (like the points balance) as a liveness probe: those run real database queries, and probing them at volume degrades the service for everyone, including you.

Retry with exponential backoff

On a 500, 429, or timeout, wait before retrying — 2s, then 4s, then 8s (with a retry cap). Instant re-sends amplify load exactly when the service is under pressure and make recovery slower for everyone.

Use idempotency keys on point mutations

A timed-out points/award or points/remove call may have succeeded server-side, so a blind re-send risks double-applying it. Send a unique idempotencyKey (body field or Idempotency-Key header) with every mutation — then retries are always safe: if the first attempt landed, you get the original result back instead of a second application. Without a key, verify the balance before re-sending.

Respect rate limits

Requests are rate-limited per API key. The /api/v1 read feeds (games, hunts, slot-requests) allow 120 requests per minute per key. A 429 response includes Retry-After (seconds) plus X-RateLimit-Limit, -Remaining and -Reset — honour them and back off rather than retrying hot.

Those feeds are keyset-paginated and support a since watermark, so a normal incremental poll costs one or two requests. If you are approaching the limit you are almost certainly re-draining history instead of using since — store the highest updatedAt you have seen and resume from it.

User Identifiers

The API supports three types of user identifiers. The system will automatically detect the type:

Discord ID (Snowflake)

17-20 digit numeric ID

123456789012345678

Kick Username

String username from Kick platform

kickusername

Viewer UUID

Unified viewer identifier (internal)

a1b2c3d4-e5f6-7890-abcd-ef1234567890
Public API Documentation — Streamneeds REST API · Streamneeds