Quick start
An API key lets your software read the alerts you set. Create the key in Settings. Send it on every request. Store the cursor from each events page, and send that cursor on the next page.
The API base is https://terminal.tokensmarketcap.com/v1. These pages describe that API: authentication, quotas, events, webhooks, the alert, errors, rate limits, and the notice and terms. The OpenAPI description of every route is at /developers/openapi.json.
Create a key
Open API keys while you are signed in. While the API is open, Create a free account on these pages opens API keys after the confirmation link. Your email must be confirmed before the key is created. Choose a name and the scopes the key needs. The secret is shown once. Copy it into the environment variable TERMINAL_API_KEY.
A key outside production starts with tmk_test_. A production key starts with tmk_live_. Send it only in the Authorization header. The header is Bearer and the secret.
Send the first request
Each program below reads the key from TERMINAL_API_KEY. Node and Python also read TERMINAL_API_ORIGIN. When that variable is unset, they call https://terminal.tokensmarketcap.com. They keep the feed's cursor in the file named by TERMINAL_CURSOR_FILE, or terminal-cursor.txt in the current folder. The cursor is your saved place in the feed.
curl
curl -sS "https://terminal.tokensmarketcap.com/v1/account" \
-H "Authorization: Bearer $TERMINAL_API_KEY"
curl -sS "https://terminal.tokensmarketcap.com/v1/events?start=earliest&wait=0" \
-H "Authorization: Bearer $TERMINAL_API_KEY"
Node
Node 20 has fetch. Save the program as quickstart.mjs, because it uses await at the top level, then run node quickstart.mjs. The loop prints each new event, writes next_cursor to the cursor file, and sends it on the next page. wait is a whole number of seconds from 0 to 25: with nothing new, the request stays open that long. The loop stops when has_more is false. On a new account with no alerts yet, it prints your plan, waits 25 seconds, then prints the cursor. Run it again and it reads on from the stored cursor. A reader that runs all the time keeps looping instead of stopping.
import { readFileSync, writeFileSync } from "node:fs"
const key = process.env.TERMINAL_API_KEY
if (!key) {
console.error("Set TERMINAL_API_KEY")
process.exit(1)
}
const origin = (process.env.TERMINAL_API_ORIGIN || "https://terminal.tokensmarketcap.com").replace(/\/$/, "")
async function getJson(path) {
const response = await fetch(origin + path, {
headers: { authorization: "Bearer " + key },
})
const body = await response.json()
if (!response.ok) {
console.error(response.status, JSON.stringify(body))
process.exit(1)
}
return body
}
const account = await getJson("/v1/account")
console.log(account.account.plan.code)
const cursorFile = process.env.TERMINAL_CURSOR_FILE || "terminal-cursor.txt"
let storedCursor = ""
try {
storedCursor = readFileSync(cursorFile, "utf8").trim()
} catch {
storedCursor = ""
}
const seen = new Set()
for (let page = 0; page < 5; page += 1) {
const params = new URLSearchParams()
if (storedCursor) params.set("cursor", storedCursor)
else params.set("start", "earliest")
params.set("wait", "25")
params.set("limit", "50")
const body = await getJson("/v1/events?" + params.toString())
for (const event of body.events) {
if (seen.has(event.id)) continue
seen.add(event.id)
console.log(event.id, event.headline)
}
storedCursor = body.next_cursor
writeFileSync(cursorFile, storedCursor + "\n")
if (!body.has_more) break
}
console.log(storedCursor)
Python
Python 3 uses urllib. The loop prints each new event and stores next_cursor in the cursor file the same way.
import json
import os
import urllib.parse
import urllib.request
key = os.environ["TERMINAL_API_KEY"]
origin = os.environ.get("TERMINAL_API_ORIGIN", "https://terminal.tokensmarketcap.com").rstrip("/")
cursor_file = os.environ.get("TERMINAL_CURSOR_FILE", "terminal-cursor.txt")
def get_json(path):
request = urllib.request.Request(
origin + path,
headers={"Authorization": "Bearer " + key},
)
with urllib.request.urlopen(request) as response:
return json.load(response)
account = get_json("/v1/account")
print(account["account"]["plan"]["code"])
try:
with open(cursor_file, encoding="utf-8") as handle:
stored_cursor = handle.read().strip()
except FileNotFoundError:
stored_cursor = ""
seen = set()
for _page in range(5):
if stored_cursor:
query = urllib.parse.urlencode({
"cursor": stored_cursor,
"wait": "25",
"limit": "50",
})
else:
query = urllib.parse.urlencode({
"start": "earliest",
"wait": "25",
"limit": "50",
})
body = get_json("/v1/events?" + query)
for event in body["events"]:
if event["id"] in seen:
continue
seen.add(event["id"])
print(event["id"], event["headline"])
stored_cursor = body["next_cursor"]
with open(cursor_file, "w", encoding="utf-8") as handle:
handle.write(stored_cursor + "\n")
if not body["has_more"]:
break
print(stored_cursor)
What the account request returns
GET /v1/account returns the plan, the limits, the credit balance, and the key that called. It does not return your email, your name, or a payment method.
Example. These are not live prices.
GET /v1/account HTTP/1.1
Authorization: Bearer $TERMINAL_API_KEY
HTTP/1.1 200 OK
{
"account": {
"plan": { "code": "pro", "name": "Pro" },
"credits": { "balance_cents": 0 }
},
"key": {
"name": "Reader",
"daily_cap_cents": 500
},
"notice": "Terminal reports market events for conditions you set. It does not tell you to buy, sell, or hold, and it does not place trades.",
"terms": { "version": "2026-10-draft", "url": "…" }
}
What one events page returns
start=earliest reads from the oldest alert the feed still has. wait=0 returns the page that is ready now. Store next_cursor. The numbers in the alert are an example payload, the same figures as on the alert schema page. They are not a live quote.
Example. These are not live prices.
GET /v1/events?start=earliest&wait=0 HTTP/1.1
Authorization: Bearer $TERMINAL_API_KEY
HTTP/1.1 200 OK
{
"events": [
{
"schema": "terminal.alert.v1",
"type": "alert.fired",
"sequence": null,
"headline": "Solana (SOL) fell 5.0% in 1 hour.",
"asset": { "symbol": "SOL", "name": "Solana" },
"price": { "value": 142.5, "currency": "USD", "source": "coinbase" },
"rule": {
"name": "SOL falls 5% in 1 hour",
"kind": "percent_change",
"scope": "symbol",
"cooldown_seconds": 3600,
"condition": { "threshold_pct": 5, "window": "1h", "direction": "down" }
},
"note": { "status": "none" },
"service": null
}
],
"next_cursor": "…",
"has_more": false,
"gap": false
}
Create your first rule
An empty events page is normal until a rule fires.
Creating a rule needs the rules:write scope, shown as Create and change alerts on API keys. A key's scopes cannot change after it is made, so a key without it must be revoked and made again.
Example. These are not live prices.
curl -sS -X POST "https://terminal.tokensmarketcap.com/v1/rules" \
-H "Authorization: Bearer $TERMINAL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: first-rule-1" \
-d '{"kind":"price_above","symbol":"BTC","condition":{"level":100000}}'
Idempotency-Key makes a retry safe, and a new rule needs a new key.
The rule starts at once, and its alerts arrive on the events feed when the condition is met. Read Events and Alert schema.
All developer pages
Terminal reports market events for conditions you set. It does not tell you to buy, sell, or hold, and it does not place trades.