Send your key in the Authorization header with every request. A session cookie, query parameter or overlay token does not grant access to this API.
Authorization: Bearer dl_live_YOUR_SECRET_KEY
Choose a validity of 30, 90 or 365 days. You can have up to 10 active keys and create one key per minute. DonoLink stores only a SHA-256 hash; the full key is visible only immediately after creation.
Use a separate key per integration. To rotate, create a new key, update your app, then revoke the old one. A revoked or expired key stops working on the next request.
Every key has the fixed activity:read scope and is bound to your account. There are no write, payment or administration permissions. OAuth for multi-customer apps is not available yet.
Endpoints
GET/api/v1/me
Check which streamer your key belongs to and when it expires. Only the public profile, scope and key metadata are returned.
Read the combined feed of DonoLink donations and stored platform events. The account is determined exclusively from the API key.
Parameter
Description
limit
Events per page: 1 to 100, default 50.
order
desc (default) for newest first, asc for oldest first and continuous polling. Sorted by recorded_at, then internal record ID and source.
type
Optional: donation, follow, subscription, giftsub, cheer or raid. One type per request.
platform
Optional: donolink, twitch, kick, youtube, tiktok or an import source: tipeeestream, streamlabs, streamelements, kofi, csv. One source per request.
since
Optional, inclusive. UTC ISO 8601, for example 2026-09-07T00:00:00Z. Filters recorded_at, the time of storage.
until
Optional, exclusive. Same date format as since. Keep this boundary fixed during a historical export.
cursor
Pass pagination.next_cursor unchanged. The cursor is signed and bound to your account, filters and order. limit may change.
Unknown or repeated parameters return HTTP 400. Do not put an account ID, key or PocketBase filter in the URL.
Event format
Each event has a stable id prefixed with don_ or evt_. Use this id to prevent duplicate processing after a retry.
type
unit
Description
donation
cents
DonoLink donation or a stored platform donation, such as YouTube Super Chat.
follow
none
New follower. value is 0.
subscription
months
Subscription. value contains the stored number of months.
giftsub
subscriptions
Gifted subscriptions. value is the count.
cheer
bits / diamonds
Bits; for TikTok the unit is diamonds. This is not donation revenue.
raid
viewers
Raid. value is the stored viewer count.
actor.name is the stored display name or null. message is text or null. occurred_at is the donation date; for platform events only the storage time is known. recorded_at always contains the storage time and determines pagination. All timestamps are UTC ISO 8601.
For donation, value is the original amount in integer cents and currency is the currency. refunded_amount_cents contains the refunded amount for DonoLink, otherwise null. Donations with status paid are shown, including imported history with the import source as platform. Fully refunded and disputed donations are excluded. This is an activity feed, not a financial ledger.
Platform events are available to the extent DonoLink received and stored them through an active connection. History from before the connection is not fetched from the platform. There is no guaranteed retention period; deleted records cannot be retrieved. Test alerts are not included.
History & live feed
For an export, use order=asc with fixed since and until boundaries. Process data and request the next page using next_cursor while has_more is true. There is no limit on historical page count, but request limits apply.
An empty page preserves your existing cursor. With no previous cursor and no events, next_cursor is null. Cursors contain no API key, but treat them as opaque values. After changing filters, start without a cursor.
For your IRL app, use order=asc, a fixed since and no until. Save the cursor only after processing events. Poll about every 5 seconds; when has_more is true, continue directly to the next page. v1 has no WebSocket or webhook endpoint.
// Node.js: run on your server. Keep the key out of frontend bundles.
const base = new URL('https://donolink.nl/api/v1/activity');
base.searchParams.set('order', 'asc');
base.searchParams.set('limit', '100');
// Persist both this fixed starting point and the returned cursor.
base.searchParams.set('since', '2026-09-07T00:00:00Z');
let cursor = await loadCheckpoint();
for (;;) {
const url = new URL(base);
if (cursor) url.searchParams.set('cursor', cursor);
const response = await fetch(url, {
headers: { Authorization: 'Bearer ' + process.env.DONOLINK_API_KEY },
signal: AbortSignal.timeout(15000)
}).catch(() => null);
if (!response || response.status === 429 || response.status >= 500) {
const seconds = Number(response?.headers.get('Retry-After') || 60);
await new Promise(r => setTimeout(r, seconds * 1000));
continue;
}
if (!response.ok) throw new Error('DonoLink API: ' + response.status);
const page = await response.json();
// Implement these storage functions in your app.
// Process idempotently by event.id, then save the checkpoint.
for (const event of page.data) await processOnce(event.id, event);
if (page.pagination.next_cursor) {
cursor = page.pagination.next_cursor;
await saveCheckpoint(cursor);
}
if (!page.pagination.has_more)
await new Promise(r => setTimeout(r, 5000));
}
The feed reads current stored records and is not an immutable snapshot or a guaranteed exactly-once event bus. Payment status changes can remove previously visible records. Use idempotent processing, retries and, when needed, overlapping reads for recovery.
Limits & errors
Maximum 120 requests per 60 seconds per account, shared by all keys. There is also a limit of 300 requests per 60 seconds per IP address. HTTP 429 and 503 include Retry-After: 60. Wait that long and retry with the same cursor.
HTTP
code
Description
400
invalid_parameter / invalid_cursor
Correct your parameters or restart without the invalid cursor.
401
invalid_api_key
Key is missing, invalid, expired or revoked.
403
account_unavailable
The account is unavailable or not verified.
429
rate_limit_exceeded
Too many requests. Respect Retry-After.
503
temporarily_unavailable
Temporary outage. Access is also denied if the shared limiter is unavailable.
{
"error": {
"code": "invalid_api_key",
"message": "API key is invalid, expired or revoked.",
"request_id": "request-uuid"
}
}
Every response has X-Request-Id; error responses also include error.request_id. Include this ID and the time when contacting support. Never share your key, Authorization header or full request logs. Use GET for data. HEAD and OPTIONS are available for HTTP checks; write methods return 405.
Secure integration
Use HTTPS. Never put keys in URLs, Git, browser code, localStorage, analytics or log files. API responses use Cache-Control: private, no-store. The API does not provide cross-origin browser access.
In a personal native app, enter your own key and store it in Keychain or Android Keystore. For apps serving others, use a backend with isolated secrets per user. Never embed a shared key in the app binary.
The API returns only display names, messages, event values and limited profile details. Email addresses, payment methods, Stripe IDs, platform tokens and administration settings are not shared. Treat names and messages as personal data and store only what your integration needs.
If a key leaks, revoke it immediately under Settings → Developer and create a replacement. Revoked and expired keys, blocked accounts and deleted accounts cannot access the API.
Names and messages are untrusted input. Render them as text, never as HTML or executable code. Do not automate payment actions based on this feed.