API reference
The BitView backend exposes a single REST API in four scopes. Every endpoint speaks JSON, every response is a stable schema documented both here and via OpenAPI.
| Scope | Purpose |
|---|---|
/twitch-api | Twitch channel listening + read-only chat presence |
/bitview-api | Viewer wallet linking, profile lookup, accrual reads |
/distributions-api | Streamer / admin distribution lifecycle |
/claims-api | Claim summaries and merkle-proof proxy |
Live OpenAPI
When the backend is running, full schemas are available at:
- Swagger UI —
https://api.bitview.so/swagger-ui/ - RapiDoc —
https://api.bitview.so/rapidoc - Raw JSON —
https://api.bitview.so/api-docs/openapi.json
This page gives you the conceptual model, examples, and the contract guarantees that aren't expressible in OpenAPI.
Authentication
| Scheme | Header | Used by |
|---|---|---|
twitch_oauth | Authorization: Bearer <twitch user-access token> | POST /bitview-api/user/link |
admin_api_key | x-admin-api-key: <ADMIN_API_KEY env value> | distribution register / finalize |
| (none) | — | All public read endpoints |
Twitch tokens are validated against https://api.twitch.tv/helix/users
on every request. Tokens that fail Helix verification are rejected
with 401.
The admin API key is a simple shared secret used until Phase 4 introduces per-streamer signed-action authorization. It is rotated quarterly.
Base URL
Production: https://api.bitview.so
Local dev (default): http://localhost:4477
Common response shapes
All errors follow:
{
"error": "human-readable message"
}
HTTP status codes map to:
| Status | Meaning |
|---|---|
| 200 | OK |
| 201 | Created (registration endpoints) |
| 400 | Validation error (invalid pubkey, missing param, etc.) |
| 401 | Authentication missing or invalid |
| 403 | Authenticated but not authorized for this resource |
| 404 | Resource not found |
| 409 | Conflict (e.g., duplicate link) |
| 429 | Rate-limited (see rate limits) |
| 500 | Internal server error (please report — see security disclosure) |
| 502 | Upstream gateway error (Solana RPC, distributor proof API) |
Rate limits
| Endpoint class | Anonymous | Authenticated | Admin |
|---|---|---|---|
| Read endpoints (GET) | 60 req/min per IP | 300 req/min per token | 1200 req/min |
| Write endpoints (POST) | 10 req/min per IP | 60 req/min per token | 600 req/min |
Auth endpoints (/user/link) | 5 req/min per IP | n/a | n/a |
| Proof endpoints | 30 req/min per IP | 300 req/min per token | n/a |
Limits are intentionally generous for legitimate use and tightened algorithmically against patterns matching abuse signatures. Contact us via security@bitview.so if your integration needs a higher allowance.
Idempotency
All POST endpoints are idempotent on their primary key:
/bitview-api/user/link— keyed on Twitchuser_id. Re-posting with the same identity updates the link./distributions-api/register— keyed ondistributor_pda. Re-posting the same PDA is rejected with 409./distributions-api/{id}/finalize— keyed on distribution id. Re-posting aftersnapshottingis a no-op.
Because the on-chain state is the source of truth for distributions, the backend reconciles automatically if a transaction succeeds on-chain but the API call fails — the next read will reflect chain state.
Endpoints
GET /health
Public. Returns process health status.
$ curl https://api.bitview.so/health
{"status": "ok"}
Twitch scope
GET /twitch-api/channel/list
List channels the bot is currently listening to.
$ curl https://api.bitview.so/twitch-api/channel/list
["pewpiediepie", "lirik", "shroud", "gamesdonequick"]
GET /twitch-api/channel/user_list/{channel_login}
List logins currently present in chat for channel_login.
$ curl https://api.bitview.so/twitch-api/channel/user_list/pewpiediepie
["alice42", "bob_streams", "charlie", "..."]
Note: this endpoint reflects the in-memory presence map, not historical chat. It updates in real time as JOIN/PART events arrive.
GET /twitch-api/channel/connect/{channel_login}
Idempotently connect (or no-op) to a channel's chat.
$ curl https://api.bitview.so/twitch-api/channel/connect/pewpiediepie
{"channel": "pewpiediepie", "newly_started": true}
newly_started: false means the listener was already running.
BitView scope (viewer)
POST /bitview-api/user/link
Link a Twitch identity to a Solana wallet. Requires both auth headers described in authentication — wait, only the Twitch bearer; the wallet signature is in the request body and is verified server-side.
The signed message must contain the Twitch user_id (returned by
Helix) to bind the signature to the identity. This prevents replay
across different users.
$ TWITCH_TOKEN="oauth:xxx"
$ WALLET="Hxxx..."
$ MSG="BitView wallet link. Twitch user_id=12345, timestamp=2026-05-10T15:00:00Z"
$ SIG="$(...your wallet signs $MSG with ed25519, encodes base58...)"
$ curl -X POST https://api.bitview.so/bitview-api/user/link \
-H "Authorization: Bearer $TWITCH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"solana_wallet": "'"$WALLET"'",
"signature": "'"$SIG"'",
"signed_message": "'"$MSG"'"
}'
{
"twitch_login": "alice42",
"solana_wallet": "Hxxx..."
}
Possible errors:
400— invalid wallet pubkey, signed_message missing user_id401— Twitch token invalid or signature verification failed403— wallet sanctions screen hit409— Twitch user_id already linked to a different wallet (use the relink flow with cooling-off period)
GET /bitview-api/user/by-login/{twitch_login}
Fetch a linked viewer by Twitch login. Public read.
$ curl https://api.bitview.so/bitview-api/user/by-login/alice42
{
"twitch_login": "alice42",
"twitch_user_id": "12345",
"twitch_display_name": "Alice42",
"solana_wallet": "Hxxx...",
"created_at": "2026-05-10T15:00:00Z",
"last_update": "2026-05-12T18:30:00Z"
}
404 if not found.
GET /bitview-api/user/by-wallet/{wallet}
Same shape as above, looked up by Solana wallet pubkey. Public read.
GET /bitview-api/user/list
List linked users (capped at 200; the production limit will be
paginated via ?cursor=). Public.
GET /bitview-api/viewer/{wallet}/accruals
List all accruals for a wallet across all distributions. Public.
$ curl https://api.bitview.so/bitview-api/viewer/Hxxx.../accruals
[
{
"distribution_id": "Dxxx...",
"twitch_login": "alice42",
"solana_wallet": "Hxxx...",
"amount": 4523,
"tick_count": 42
},
{
"distribution_id": "Dyyy...",
"twitch_login": "alice42",
"solana_wallet": "Hxxx...",
"amount": 800,
"tick_count": 18
}
]
amount is in the smallest token unit of the distribution's mint.
Distributions scope (streamer / admin)
POST /distributions-api/register
Register an on-chain distribution with the backend. Requires
x-admin-api-key during Phase 1–3; replaced by streamer-signed
authorization in Phase 4+.
$ curl -X POST https://api.bitview.so/distributions-api/register \
-H "x-admin-api-key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"distributor_pda": "Dxxx...",
"channel_login": "pewpiediepie",
"streamer_wallet": "Sxxx...",
"mint": "EFsATdhojBVLTbQtrAWncbmFHng4U4jyEiekCptKTtuE",
"title": "Charity Stream Reward Pool",
"total_token": 200000000000,
"periodicity_seconds": 60,
"duration_seconds": 14400,
"max_per_viewer": 5000000000,
"start_at": "2026-05-15T20:00:00Z"
}'
Returns 201 Created with the full distribution document on success.
total_token, max_per_viewer, and accrual amounts are all in the
mint's smallest unit (e.g., 200000000000 for 200K tokens at 6 decimals).
GET /distributions-api/by-channel/{channel}
List distributions for a channel, newest first. Public read.
GET /distributions-api/{id}
Get a single distribution by distributor PDA. Public read.
$ curl https://api.bitview.so/distributions-api/Dxxx...
{
"id": "Dxxx...",
"channel_login": "pewpiediepie",
"streamer_wallet": "Sxxx...",
"mint": "EFsATdho...",
"title": "Charity Stream Reward Pool",
"total_token": 200000000000,
"periodicity_seconds": 60,
"duration_seconds": 14400,
"max_per_viewer": 5000000000,
"status": "active",
"merkle_root": null,
"start_at": "2026-05-15T20:00:00Z",
"end_at": "2026-05-16T00:00:00Z",
"created_at": "2026-05-15T19:55:00Z",
"last_update": "2026-05-15T22:30:00Z"
}
GET /distributions-api/active
List all currently-accruing distributions across all channels.
POST /distributions-api/{id}/finalize
Move a distribution into snapshotting status. Admin-gated.
$ curl -X POST https://api.bitview.so/distributions-api/Dxxx.../finalize \
-H "x-admin-api-key: $ADMIN_KEY"
{"distribution_id": "Dxxx...", "status": "snapshotting"}
After finalize, an offline tree-builder job constructs the merkle
tree from MongoDB accruals, then an on-chain set_enable_slot call
publishes the root and flips status to claimable.
Claims scope (viewer)
GET /claims-api/proof/{wallet}
Proxy for the distributor merkle-proof API. Returns the proof a viewer
needs to call new_claim on-chain.
$ curl https://api.bitview.so/claims-api/proof/Hxxx...
{
"merkle_tree": "Mxxx...",
"amount": 4523000000,
"proof": [
"QXJiaXRyYXJ5SGFzaDM=",
"QXJpdGFyeUhhc2g0",
"..."
]
}
merkle_tree is the distributor PDA (which holds the on-chain root).
proof is an array of base58-encoded 32-byte hashes.
GET /claims-api/summary/{wallet}
Combined view of every distribution where this wallet has accruals, with claimability status. Drives the rewards page.
$ curl https://api.bitview.so/claims-api/summary/Hxxx...
{
"wallet": "Hxxx...",
"claimable": [
{
"distribution_id": "Dxxx...",
"mint": "EFsATdho...",
"amount": 4523000000,
"status": "claimable"
},
{
"distribution_id": "Dyyy...",
"mint": "EFsATdho...",
"amount": 800000000,
"status": "active"
}
]
}
status: "active" entries are not yet claimable (distribution still
running). status: "claimable" is what the frontend lights up.
TypeScript example — complete claim flow
import { Connection, PublicKey, Transaction } from '@solana/web3.js';
import { AnchorProvider, Program, BN } from '@coral-xyz/anchor';
import idl from './merkle_distributor.json';
const BACKEND = 'https://api.bitview.so';
const RPC = 'https://api.mainnet-beta.solana.com';
const PROGRAM_ID = new PublicKey('4ffj6hEnx6cqp4ToMALExqk6QwPNSbZyr8ro9yW1Qvok');
async function claimAll(wallet: any) {
const summary = await fetch(
`${BACKEND}/claims-api/summary/${wallet.publicKey}`
).then((r) => r.json());
const claimable = summary.claimable.filter((c: any) => c.status === 'claimable');
const conn = new Connection(RPC);
const provider = new AnchorProvider(conn, wallet, {});
const program = new Program(idl, PROGRAM_ID, provider);
for (const entry of claimable) {
const proof = await fetch(
`${BACKEND}/claims-api/proof/${wallet.publicKey}`
).then((r) => r.json());
const tx = await program.methods
.newClaim(
new BN(proof.amount),
new BN(0),
proof.proof.map((p: string) => Array.from(Buffer.from(p, 'base64')))
)
.accounts({
distributor: new PublicKey(proof.merkle_tree),
// ... other accounts derived per IDL
})
.transaction();
await provider.sendAndConfirm(tx);
}
}
What's not in the API today
These are deliberately out of the current API surface:
- Streamer-signed authorization —
x-admin-api-keyis a Phase 1 shortcut; we move to per-streamer signed actions in Phase 4. - Webhooks — Pro/Plus subscription tier feature in Phase 3+.
- Cursor-based pagination — current limit is 200; cursor pagination ships when listing endpoints exceed practical limits.
- GraphQL — REST suffices for the surface area today.
- NFT drops API — ships in Phase 3 with the NFT drops feature.
- Sponsorship marketplace API — ships in Phase 4.
- Swap router API — ships in Phase 2 (with BTV launch).
These are tracked on the roadmap.
Versioning
The current API is unversioned during MVP. When we hit Phase 2 we
introduce Accept: application/vnd.bitview.v1+json versioning with
backward-compat for 12 months on any breaking change. Notable schema
changes are publicly announced.
Related
- Architecture — what's behind the API
- Operations — running the backend locally
- Security disclosure — reporting API security issues
- Glossary — specialized terms used in API responses