Alerts
An account's alert rules, and the Telegram chats their matches are sent to.
Each rule runs on Traktor's servers as the data it reads arrives, so it keeps matching while your code is offline, and what it matches lands in the account's notifications.
Credentials
Alerts belong to the account, not to a key, so every credential the account routes take reads and writes the same rules. The routes here that take a credential, the notification routes and the two on /v1/browser-sessions/current share a per-minute budget per account, apart from the plan's: the Requests limit on each block. Past it they answer 429 rate_limited with Retry-After.
Rules
The grammar is closed: GET /v1/alert-capabilities publishes it, and a rule naming anything it does not list is refused. A rule as the API stores it:
{
"v": 1,
"source": "flow",
"windowMinutes": 60,
"scope": { "chains": ["bnb"], "platforms": [], "tokens": [], "wallets": [], "side": "all" },
"match": { "op": "all", "conditions": [{ "metric": "usdAmount", "op": "gte", "value": 1000 }] },
"trigger": { "mode": "crossing", "cooldownSeconds": 300, "hysteresisPct": 5 }
}sourceis what the rule tests:floweach trade as Traktor stores it,token_windowa token's figures overwindowMinutes, andsignaleach signal the feed detects. A signal rule takes no window, and never matches a signal for a token that no launchpad we know made, as no board lists one, a verified stock aside, nor for a major such as a stablecoin or a wrapped or bridged coin.scopenarrows the source, and an empty list means every member. Atoken_windoworsignalrule cannot name wallets or a side.matchholds the conditions, all of them or any (op). A condition compares a metric withvalue, or withminandmaxunderbetween; a signal rule's condition names asignaland aminTierinstead, and since each event is one signal, itsallcan't name two different ones. A figure Traktor did not measure satisfies no condition, a ceiling included.triggersays when a match notifies, subject by subject: a wallet in a token on aflowrule, a token on atoken_windowrule.crossingnotifies when the subject starts to match, and again only after it has left the match by more thanhysteresisPctpercent of the bound; a reading whose measured figures can't settle the match doesn't count as leaving it.repeatnotifies on every match.oncenotifies on the first and pauses the alert. None notifies a subject again withincooldownSecondsof its last. A signal notifies once, whatever the mode.A
flowrule sees only the trades Traktor stores as they arrive: a row it recovers later, withoriginsweeporbackfill, never fires one.
A rule error is a 400 whose details.pointer names the part refused, as a JSON pointer such as /match/conditions/0/metric.
GET /v1/alert-capabilities
What an alert rule can name: sources, metrics, signals, windows, trigger modes and limits, beside each tracked chain and app's freshness.
- Credential
- None
Errors
curl -s "https://api.traktor.fun/v1/alert-capabilities"const response = await fetch("https://api.traktor.fun/v1/alert-capabilities");
const body = await response.json();import requests
response = requests.get(
"https://api.traktor.fun/v1/alert-capabilities",
)
print(response.json()){
"data": {
"sources": [
"flow",
"token_window",
"signal"
],
"triggerModes": [
"crossing",
"repeat",
"once"
],
"limits": {
"alertsPerAccount": 100,
"conditions": 12,
"addressesPerScope": 50,
"evaluationsPerAccountMinute": 10000,
"trackedSubjectsPerAlert": 1000
},
"coverage": {
"available": true,
"asOf": "2026-10-03T12:00:04.000Z",
"thresholds": {
"staleAfterSeconds": 600,
"downAfterSeconds": 3600
},
"pairs": [
{
"chain": "bnb",
"platform": "fomo",
"state": "live",
"lastIngestedAt": "2026-10-03T12:00:01.870Z",
"lastBlockTime": "2026-10-03T12:00:00.000Z",
"lastCursorAt": "2026-10-03T12:00:03.000Z",
"cursorBlock": 63012346,
"sourceHead": 63012348,
"sourceLagSeconds": 1.8,
"lagSeconds": 2.1,
"rowsLastHour": 9410
}
]
}
},
"meta": {
"requestId": "req_01m40t5ek8b3x7r2mh5q",
"generatedAt": "2026-10-03T12:00:04.210Z",
"asOf": null,
"caveats": []
}
}The full body has more keys.
Beside the grammar, the answer carries coverage: under pairs, the freshness row of each tracked chain and app, its state judged by the thresholds beside it, or available: false and no rows when they could not be read. A row keeps the newest trade apart from the capture's health, so a quiet market is not taken for a stopped capture. lastIngestedAt is when the newest row was stored, and lagSeconds how long before coverage.asOf that was. The route answers with Cache-Control: public, max-age=300, so a copy can be up to five minutes old: read a row's age against coverage.asOf, not against when you asked. lastCursorAt, cursorBlock, sourceHead and sourceLagSeconds say when the capture last read and how far behind the chain it is. rowsLastHour counts the trades of the last hour by block time. A Coinbase Wallet row, on Base, marks its coverage partial and adds sources, holding its one source, swap-tag: the tagger that reads Coinbase's trades from the swap feed, whose state the row takes. Bankr's rows mark their coverage partial too, with no sources; each coverageNote says what Bankr's own record leaves out.
A chain that has stopped is a state inside a 200.
Writing rules
GET /v1/alerts lists the account's alerts newest first, unpaged. A create and a duplicate both count toward the Alerts limit on the create block, and past it answer 409 alert_limit_reached.
A PATCH names at least one of name, rule or enabled beside its revision, and every change raises the revision by one. A stale revision is 409 alert_revision_conflict, with the current one in details.currentRevision: read the alert again, then resend. A changed rule starts over, its subjects' trigger state cleared.
A
oncealert that has notified is paused, withoneShotCompletedAtset. Enabling it again with its rule unchanged is409 alert_once_completed: change the rule, or duplicate the alert.A rule stored before alerts dropped a window, field or match it uses can still be read, but enabling it without sending a rule is
400 alert_rule_unsupported: send the rule edited.A signal rule naming a signal the feed does not emit is
400 unknown_signal, and one whoseallnames two different signals is400 invalid_match.404 alert_not_foundreads the same for an alert on another account.DELETEleaves the notifications an alert wrote in place.
GET /v1/alerts
Every alert on the account.
- Credential
- API key, browser session or Traktor sign-in
Limits
- Requests120 a minute, per account
- Session checks120 a minute, per IP
- Invalid keys30 a minute, per IP
Errors
missing_credentials, invalid_key, key_revoked, subscription_inactive, session_expired, sign_in_required, method_not_allowed, rate_limited, rate_limit_unavailable, internal_error
curl -s "https://api.traktor.fun/v1/alerts" \
-H "Authorization: Bearer $TRAKTOR_API_KEY"const response = await fetch("https://api.traktor.fun/v1/alerts", {
headers: { Authorization: `Bearer ${process.env.TRAKTOR_API_KEY}` },
});
const body = await response.json();import os
import requests
response = requests.get(
"https://api.traktor.fun/v1/alerts",
headers={"Authorization": f"Bearer {os.environ['TRAKTOR_API_KEY']}"},
)
print(response.json()){
"data": {
"alerts": [
{
"id": "8f14e45f-ceea-467a-9575-2b0f3e8d1c2a",
"name": "Large BNB buys",
"enabled": true,
"revision": 1,
"triggerCount": 0,
"lastTriggeredAt": null,
"oneShotCompletedAt": null,
"rule": {
"v": 1,
"source": "flow",
"windowMinutes": 60,
"scope": {
"chains": [
"bnb"
],
"platforms": [],
"tokens": [],
"wallets": [],
"side": "all"
},
"match": {
"op": "all",
"conditions": [
{
"metric": "usdAmount",
"op": "gte",
"value": 1000
}
]
},
"trigger": {
"mode": "crossing",
"cooldownSeconds": 300,
"hysteresisPct": 5
}
},
"createdAt": "2026-10-03T12:00:00.000Z",
"updatedAt": "2026-10-03T12:00:00.000Z"
}
]
},
"meta": {
"requestId": "req_01m40t5ek8b3x7r2mh5q",
"generatedAt": "2026-10-03T12:00:04.210Z",
"asOf": null,
"caveats": []
}
}POST /v1/alerts
Creates an alert from a name and a rule; it is enabled unless enabled says otherwise.
- Credential
- API key, browser session or Traktor sign-in
namestringrequiredbodyWhat the alert is called, 1 to 80 characters.
ruleobjectrequiredbodyThe rule, as
GET /v1/alert-capabilitiesdescribes it.enabledbooleanbodyWhether it runs from the start.
Default
true
Limits
- Requests120 a minute, per account
- Session checks120 a minute, per IP
- Invalid keys30 a minute, per IP
- Alerts100 kept, per account
Errors
invalid_alert_name, invalid_alert_enabled, alert_limit_reached, invalid_rule, invalid_scope, invalid_condition, unknown_metric, unknown_signal, metric_source_mismatch, invalid_trigger, invalid_source, invalid_window, invalid_match, alert_version_unsupported, too_many_conditions, too_many_addresses, invalid_json, invalid_body, unknown_field, body_too_large, missing_credentials, invalid_key, key_revoked, subscription_inactive, session_expired, sign_in_required, method_not_allowed, rate_limited, rate_limit_unavailable, internal_error
curl -s -X POST "https://api.traktor.fun/v1/alerts" \
-H "Authorization: Bearer $TRAKTOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Large BNB buys","rule":{"v":1,"source":"flow","scope":{"chains":["bnb"]},"match":{"op":"all","conditions":[{"metric":"usdAmount","op":"gte","value":1000}]}}}'const response = await fetch("https://api.traktor.fun/v1/alerts", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TRAKTOR_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({"name":"Large BNB buys","rule":{"v":1,"source":"flow","scope":{"chains":["bnb"]},"match":{"op":"all","conditions":[{"metric":"usdAmount","op":"gte","value":1000}]}}}),
});
const body = await response.json();import os
import requests
response = requests.post(
"https://api.traktor.fun/v1/alerts",
headers={"Authorization": f"Bearer {os.environ['TRAKTOR_API_KEY']}"},
json={"name": "Large BNB buys", "rule": {"v": 1, "source": "flow", "scope": {"chains": ["bnb"]}, "match": {"op": "all", "conditions": [{"metric": "usdAmount", "op": "gte", "value": 1000}]}}},
)
print(response.json()){
"data": {
"alert": {
"id": "8f14e45f-ceea-467a-9575-2b0f3e8d1c2a",
"name": "Large BNB buys",
"enabled": true,
"revision": 1,
"triggerCount": 0,
"lastTriggeredAt": null,
"oneShotCompletedAt": null,
"rule": {
"v": 1,
"source": "flow",
"windowMinutes": 60,
"scope": {
"chains": [
"bnb"
],
"platforms": [],
"tokens": [],
"wallets": [],
"side": "all"
},
"match": {
"op": "all",
"conditions": [
{
"metric": "usdAmount",
"op": "gte",
"value": 1000
}
]
},
"trigger": {
"mode": "crossing",
"cooldownSeconds": 300,
"hysteresisPct": 5
}
},
"createdAt": "2026-10-03T12:00:00.000Z",
"updatedAt": "2026-10-03T12:00:00.000Z"
}
},
"meta": {
"requestId": "req_01m40t5ek8b3x7r2mh5q",
"generatedAt": "2026-10-03T12:00:04.210Z",
"asOf": null,
"caveats": []
}
}GET /v1/alerts/{id}
One alert by its id.
- Credential
- API key, browser session or Traktor sign-in
iduuidrequiredpathThe alert's id.
Limits
- Requests120 a minute, per account
- Session checks120 a minute, per IP
- Invalid keys30 a minute, per IP
Errors
alert_not_found, route_not_found, missing_credentials, invalid_key, key_revoked, subscription_inactive, session_expired, sign_in_required, method_not_allowed, rate_limited, rate_limit_unavailable, internal_error
curl -s "https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a" \
-H "Authorization: Bearer $TRAKTOR_API_KEY"const response = await fetch("https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a", {
headers: { Authorization: `Bearer ${process.env.TRAKTOR_API_KEY}` },
});
const body = await response.json();import os
import requests
response = requests.get(
"https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a",
headers={"Authorization": f"Bearer {os.environ['TRAKTOR_API_KEY']}"},
)
print(response.json()){
"data": {
"alert": {
"id": "8f14e45f-ceea-467a-9575-2b0f3e8d1c2a",
"name": "Large BNB buys",
"enabled": true,
"revision": 1,
"triggerCount": 0,
"lastTriggeredAt": null,
"oneShotCompletedAt": null,
"rule": {
"v": 1,
"source": "flow",
"windowMinutes": 60,
"scope": {
"chains": [
"bnb"
],
"platforms": [],
"tokens": [],
"wallets": [],
"side": "all"
},
"match": {
"op": "all",
"conditions": [
{
"metric": "usdAmount",
"op": "gte",
"value": 1000
}
]
},
"trigger": {
"mode": "crossing",
"cooldownSeconds": 300,
"hysteresisPct": 5
}
},
"createdAt": "2026-10-03T12:00:00.000Z",
"updatedAt": "2026-10-03T12:00:00.000Z"
}
},
"meta": {
"requestId": "req_01m40t5ek8b3x7r2mh5q",
"generatedAt": "2026-10-03T12:00:04.210Z",
"asOf": null,
"caveats": []
}
}PATCH /v1/alerts/{id}
Changes an alert's name, rule or whether it runs, against the revision you last read.
- Credential
- API key, browser session or Traktor sign-in
iduuidrequiredpathThe alert's id.
revisionintegerrequiredbodyThe revision you last read; a newer one answers 409.
from 1
namestringbodyA new name, 1 to 80 characters.
ruleobjectbodyA new rule.
enabledbooleanbodyWhether it runs.
Limits
- Requests120 a minute, per account
- Session checks120 a minute, per IP
- Invalid keys30 a minute, per IP
Errors
alert_not_found, route_not_found, invalid_alert_patch, invalid_alert_revision, invalid_alert_name, invalid_alert_enabled, alert_revision_conflict, alert_once_completed, alert_rule_unsupported, invalid_rule, invalid_scope, invalid_condition, unknown_metric, unknown_signal, metric_source_mismatch, invalid_trigger, invalid_source, invalid_window, invalid_match, alert_version_unsupported, too_many_conditions, too_many_addresses, invalid_json, invalid_body, unknown_field, body_too_large, missing_credentials, invalid_key, key_revoked, subscription_inactive, session_expired, sign_in_required, method_not_allowed, rate_limited, rate_limit_unavailable, internal_error
curl -s -X PATCH "https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a" \
-H "Authorization: Bearer $TRAKTOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"revision":1,"enabled":false}'const response = await fetch("https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a", {
method: "PATCH",
headers: { Authorization: `Bearer ${process.env.TRAKTOR_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({"revision":1,"enabled":false}),
});
const body = await response.json();import os
import requests
response = requests.patch(
"https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a",
headers={"Authorization": f"Bearer {os.environ['TRAKTOR_API_KEY']}"},
json={"revision": 1, "enabled": False},
)
print(response.json()){
"data": {
"alert": {
"id": "8f14e45f-ceea-467a-9575-2b0f3e8d1c2a",
"name": "Large BNB buys",
"enabled": false,
"revision": 2,
"triggerCount": 0,
"lastTriggeredAt": null,
"oneShotCompletedAt": null,
"rule": {
"v": 1,
"source": "flow",
"windowMinutes": 60,
"scope": {
"chains": [
"bnb"
],
"platforms": [],
"tokens": [],
"wallets": [],
"side": "all"
},
"match": {
"op": "all",
"conditions": [
{
"metric": "usdAmount",
"op": "gte",
"value": 1000
}
]
},
"trigger": {
"mode": "crossing",
"cooldownSeconds": 300,
"hysteresisPct": 5
}
},
"createdAt": "2026-10-03T12:00:00.000Z",
"updatedAt": "2026-10-03T12:00:00.000Z"
}
},
"meta": {
"requestId": "req_01m40t5ek8b3x7r2mh5q",
"generatedAt": "2026-10-03T12:00:04.210Z",
"asOf": null,
"caveats": []
}
}DELETE /v1/alerts/{id}
Deletes an alert.
- Credential
- API key, browser session or Traktor sign-in
iduuidrequiredpathThe alert's id.
Limits
- Requests120 a minute, per account
- Session checks120 a minute, per IP
- Invalid keys30 a minute, per IP
Errors
alert_not_found, route_not_found, missing_credentials, invalid_key, key_revoked, subscription_inactive, session_expired, sign_in_required, method_not_allowed, rate_limited, rate_limit_unavailable, internal_error
curl -s -X DELETE "https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a" \
-H "Authorization: Bearer $TRAKTOR_API_KEY"const response = await fetch("https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a", {
method: "DELETE",
headers: { Authorization: `Bearer ${process.env.TRAKTOR_API_KEY}` },
});
console.log(response.status);import os
import requests
response = requests.delete(
"https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a",
headers={"Authorization": f"Bearer {os.environ['TRAKTOR_API_KEY']}"},
)
print(response.status_code)Response 204: no body.
POST /v1/alerts/{id}/duplicate
Copies an alert as a new one, created paused.
- Credential
- API key, browser session or Traktor sign-in
iduuidrequiredpathThe alert's id.
Limits
- Requests120 a minute, per account
- Session checks120 a minute, per IP
- Invalid keys30 a minute, per IP
Errors
alert_not_found, alert_limit_reached, route_not_found, missing_credentials, invalid_key, key_revoked, subscription_inactive, session_expired, sign_in_required, method_not_allowed, rate_limited, rate_limit_unavailable, internal_error
curl -s -X POST "https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a/duplicate" \
-H "Authorization: Bearer $TRAKTOR_API_KEY"const response = await fetch("https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a/duplicate", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TRAKTOR_API_KEY}` },
});
const body = await response.json();import os
import requests
response = requests.post(
"https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a/duplicate",
headers={"Authorization": f"Bearer {os.environ['TRAKTOR_API_KEY']}"},
)
print(response.json()){
"data": {
"alert": {
"id": "2c9a7e10-5b3d-4f6e-a1c2-d3e4f5a6b7c8",
"name": "Large BNB buys",
"enabled": false,
"revision": 1,
"triggerCount": 0,
"lastTriggeredAt": null,
"oneShotCompletedAt": null,
"rule": {
"v": 1,
"source": "flow",
"windowMinutes": 60,
"scope": {
"chains": [
"bnb"
],
"platforms": [],
"tokens": [],
"wallets": [],
"side": "all"
},
"match": {
"op": "all",
"conditions": [
{
"metric": "usdAmount",
"op": "gte",
"value": 1000
}
]
},
"trigger": {
"mode": "crossing",
"cooldownSeconds": 300,
"hysteresisPct": 5
}
},
"createdAt": "2026-10-03T12:00:00.000Z",
"updatedAt": "2026-10-03T12:00:00.000Z"
}
},
"meta": {
"requestId": "req_01m40t5ek8b3x7r2mh5q",
"generatedAt": "2026-10-03T12:00:04.210Z",
"asOf": null,
"caveats": []
}
}Testing a rule
A test runs the alert's stored rule over a bounded sample and leaves the rule's trigger state as it was, so save a change before you test it. A null matches comes with truncated: true: the sample was cut short, so no match in it is not a miss. subjects lists matching subjects up to a fixed number, so its length is not a count, and dataAvailable is false when the sample held nothing to test. Past the Alert tests limit on the block, a test answers 429 preview_rate_limited.
POST /v1/alerts/{id}/test
Runs an alert's rule over recent data without notifying; matches is true when a subject matched, null when none did and the read was cut short (truncated), and false otherwise.
- Credential
- API key, browser session or Traktor sign-in
iduuidrequiredpathThe alert's id.
Limits
- Requests120 a minute, per account
- Session checks120 a minute, per IP
- Invalid keys30 a minute, per IP
- Alert tests6 a minute, a burst of 3, per account
Errors
alert_not_found, route_not_found, preview_rate_limited, missing_credentials, invalid_key, key_revoked, subscription_inactive, session_expired, sign_in_required, method_not_allowed, rate_limited, rate_limit_unavailable, internal_error
curl -s -X POST "https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a/test" \
-H "Authorization: Bearer $TRAKTOR_API_KEY"const response = await fetch("https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a/test", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TRAKTOR_API_KEY}` },
});
const body = await response.json();import os
import requests
response = requests.post(
"https://api.traktor.fun/v1/alerts/8f14e45f-ceea-467a-9575-2b0f3e8d1c2a/test",
headers={"Authorization": f"Bearer {os.environ['TRAKTOR_API_KEY']}"},
)
print(response.json()){
"data": {
"matches": true,
"dataAvailable": true,
"truncated": false,
"evaluatedAt": "2026-10-03T12:00:03.000Z",
"subjects": [
{
"subjectKey": "bnb:0x2222222222222222222222222222222222222222:0x1111111111111111111111111111111111111111",
"observation": {
"source": "flow",
"chain": "bnb",
"platform": "fomo",
"tokenAddress": "0x2222222222222222222222222222222222222222",
"wallet": "0x1111111111111111111111111111111111111111",
"side": "buy",
"metrics": {
"usdAmount": 1204.5,
"tokenAmount": 365000,
"priceUsd": 0.0033,
"lagSeconds": 2.1
}
}
}
]
},
"meta": {
"requestId": "req_01m40t5ek8b3x7r2mh5q",
"generatedAt": "2026-10-03T12:00:04.210Z",
"asOf": null,
"caveats": []
}
}A rule not saved yet is tested the same way from its body, under the same Alert tests limit, and the tokens it matched come back named, each once in match order with ticker null where none is stored or the names could not be read. capped is true when the matches reached the fixed number, so more may match than came back, as they may whenever truncated is true. A rule the service would refuse answers as creating it would, before the test is counted.
POST /v1/alerts/preview
Runs a rule that is not saved over recent data without notifying, as testing a saved alert does, and names the tokens it matched: each once, in match order, with ticker null where none is stored or the names could not be read. capped is true when the matches reached the preview's limit, so more may match, as they may whenever truncated is true.
- Credential
- API key, browser session or Traktor sign-in
ruleobjectrequiredbodyThe rule, as
GET /v1/alert-capabilitiesdescribes it.
Limits
- Requests120 a minute, per account
- Session checks120 a minute, per IP
- Invalid keys30 a minute, per IP
- Alert tests6 a minute, a burst of 3, shared with testing a saved alert, per account
Errors
preview_rate_limited, invalid_rule, invalid_scope, invalid_condition, unknown_metric, unknown_signal, metric_source_mismatch, invalid_trigger, invalid_source, invalid_window, invalid_match, alert_version_unsupported, too_many_conditions, too_many_addresses, invalid_json, invalid_body, unknown_field, body_too_large, missing_credentials, invalid_key, key_revoked, subscription_inactive, session_expired, sign_in_required, method_not_allowed, rate_limited, rate_limit_unavailable, internal_error
curl -s -X POST "https://api.traktor.fun/v1/alerts/preview" \
-H "Authorization: Bearer $TRAKTOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"rule":{"v":1,"source":"flow","windowMinutes":60,"scope":{"chains":["bnb"],"platforms":[],"tokens":[],"wallets":[],"side":"all"},"match":{"op":"all","conditions":[{"metric":"usdAmount","op":"gte","value":1000}]},"trigger":{"mode":"crossing","cooldownSeconds":300,"hysteresisPct":5}}}'const response = await fetch("https://api.traktor.fun/v1/alerts/preview", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TRAKTOR_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({"rule":{"v":1,"source":"flow","windowMinutes":60,"scope":{"chains":["bnb"],"platforms":[],"tokens":[],"wallets":[],"side":"all"},"match":{"op":"all","conditions":[{"metric":"usdAmount","op":"gte","value":1000}]},"trigger":{"mode":"crossing","cooldownSeconds":300,"hysteresisPct":5}}}),
});
const body = await response.json();import os
import requests
response = requests.post(
"https://api.traktor.fun/v1/alerts/preview",
headers={"Authorization": f"Bearer {os.environ['TRAKTOR_API_KEY']}"},
json={"rule": {"v": 1, "source": "flow", "windowMinutes": 60, "scope": {"chains": ["bnb"], "platforms": [], "tokens": [], "wallets": [], "side": "all"}, "match": {"op": "all", "conditions": [{"metric": "usdAmount", "op": "gte", "value": 1000}]}, "trigger": {"mode": "crossing", "cooldownSeconds": 300, "hysteresisPct": 5}}},
)
print(response.json()){
"data": {
"matches": true,
"dataAvailable": true,
"truncated": false,
"evaluatedAt": "2026-10-03T12:00:03.000Z",
"subjects": [
{
"subjectKey": "bnb:0x2222222222222222222222222222222222222222:0x1111111111111111111111111111111111111111",
"observation": {
"source": "flow",
"chain": "bnb",
"platform": "fomo",
"tokenAddress": "0x2222222222222222222222222222222222222222",
"wallet": "0x1111111111111111111111111111111111111111",
"side": "buy",
"metrics": {
"usdAmount": 1204.5,
"tokenAmount": 365000,
"priceUsd": 0.0033,
"lagSeconds": 2.1
}
}
}
],
"tokens": [
{
"chain": "bnb",
"address": "0x2222222222222222222222222222222222222222",
"ticker": "CAKE"
}
],
"capped": false
},
"meta": {
"requestId": "req_01m40t5ek8b3x7r2mh5q",
"generatedAt": "2026-10-03T12:00:04.210Z",
"asOf": null,
"caveats": []
}
}Telegram
A link code's url has the form https://t.me/<bot>?startgroup=link_<code>. A group's admin whose Telegram is connected to the same account opens it and picks the group to add the bot to, or sends /start link_<code> or /link <code> in a group the bot is already in, and the bot sends the account's alerts and trade outcomes there from then on. In a group the bot reads commands from admins alone; it refuses a code in a private chat and spends a code once, and a new code replaces the account's last.
Only Traktor's own sign-in mints a code, from Create a link code in Delivery on traktor.fun. An API key, a browser session or a Telegram Mini App session answers
403 web_sign_in_required: a linked group keeps the account's alerts and trades after the key is revoked or the session ends.While the service has no bot, a code is
503 telegram_not_configured. The other two routes never answer it, and the list givesbotasnull.The list's
identityisnulluntil a Telegram account is connected to Traktor, and itsstartedstaysfalseuntil that person presses Start in the bot, which can message them only then.Unlinking the connected Telegram account's own chat disconnects that account too.
404 telegram_link_not_foundreads the same for another account's chat.A link's
kindis Telegram's chat type:private,group,supergrouporchannel. A channel takes no code: adding the bot to it as an admin that may post, from the Telegram connected to the account, links it, and it carries the account's alerts alone.
Connecting a person's own Telegram takes Traktor's own sign-in on the web, and is not part of this API.
POST /v1/telegram/link-codes
A one-time code and its link, which a group's admin whose Telegram is connected to the account opens to send that group the account's alerts.
- Credential
- Traktor sign-in only
Limits
- Requests120 a minute, per account
- Session checks120 a minute, per IP
- Invalid keys30 a minute, per IP
- Code lifetime10 minutes, per request
- Live codes1 at once, per account
Errors
web_sign_in_required, telegram_not_configured, invalid_json, invalid_body, unknown_field, body_too_large, missing_credentials, invalid_key, key_revoked, subscription_inactive, session_expired, sign_in_required, method_not_allowed, rate_limited, rate_limit_unavailable, internal_error
curl -s -X POST "https://api.traktor.fun/v1/telegram/link-codes"const response = await fetch("https://api.traktor.fun/v1/telegram/link-codes", {
method: "POST",
});
const body = await response.json();import requests
response = requests.post(
"https://api.traktor.fun/v1/telegram/link-codes",
)
print(response.json()){
"data": {
"code": "q8Zr2Lw0yT5nB7vC1xK4mH9dF3sJ6gP0aE2uR8iO5lW",
"url": "https://t.me/traktor_bot?startgroup=link_q8Zr2Lw0yT5nB7vC1xK4mH9dF3sJ6gP0aE2uR8iO5lW",
"bot": "traktor_bot",
"expiresAt": "2026-10-03T12:10:00.000Z"
},
"meta": {
"requestId": "req_01m40t5ek8b3x7r2mh5q",
"generatedAt": "2026-10-03T12:00:04.210Z",
"asOf": null,
"caveats": []
}
}GET /v1/telegram/links
The chats linked to the account, the Telegram account connected to it, and the bot's name.
- Credential
- API key, browser session or Traktor sign-in
Limits
- Requests120 a minute, per account
- Session checks120 a minute, per IP
- Invalid keys30 a minute, per IP
Errors
missing_credentials, invalid_key, key_revoked, subscription_inactive, session_expired, sign_in_required, method_not_allowed, rate_limited, rate_limit_unavailable, internal_error
curl -s "https://api.traktor.fun/v1/telegram/links" \
-H "Authorization: Bearer $TRAKTOR_API_KEY"const response = await fetch("https://api.traktor.fun/v1/telegram/links", {
headers: { Authorization: `Bearer ${process.env.TRAKTOR_API_KEY}` },
});
const body = await response.json();import os
import requests
response = requests.get(
"https://api.traktor.fun/v1/telegram/links",
headers={"Authorization": f"Bearer {os.environ['TRAKTOR_API_KEY']}"},
)
print(response.json()){
"data": {
"links": [
{
"chatId": "-1001234567890",
"kind": "supergroup",
"title": "Desk",
"linkedAt": "2026-10-01T09:00:00.000Z"
}
],
"identity": {
"telegramUserId": "123456789",
"username": "example",
"displayName": "Example",
"linkedAt": "2026-09-30T09:00:00.000Z",
"started": true
},
"bot": "traktor_bot"
},
"meta": {
"requestId": "req_01m40t5ek8b3x7r2mh5q",
"generatedAt": "2026-10-03T12:00:04.210Z",
"asOf": null,
"caveats": []
}
}DELETE /v1/telegram/links/{chatId}
Stops sending the account's alerts to a chat.
- Credential
- API key, browser session or Traktor sign-in
chatIdstringrequiredpathThe chat's Telegram id.
Limits
- Requests120 a minute, per account
- Session checks120 a minute, per IP
- Invalid keys30 a minute, per IP
Errors
telegram_link_not_found, route_not_found, missing_credentials, invalid_key, key_revoked, subscription_inactive, session_expired, sign_in_required, method_not_allowed, rate_limited, rate_limit_unavailable, internal_error
curl -s -X DELETE "https://api.traktor.fun/v1/telegram/links/-1001234567890" \
-H "Authorization: Bearer $TRAKTOR_API_KEY"const response = await fetch("https://api.traktor.fun/v1/telegram/links/-1001234567890", {
method: "DELETE",
headers: { Authorization: `Bearer ${process.env.TRAKTOR_API_KEY}` },
});
console.log(response.status);import os
import requests
response = requests.delete(
"https://api.traktor.fun/v1/telegram/links/-1001234567890",
headers={"Authorization": f"Bearer {os.environ['TRAKTOR_API_KEY']}"},
)
print(response.status_code)Response 204: no body.