Errors
Software branches on error.code. error.message is English for a person reading a log, and it can change. error.request_id matches the Terminal-Request-Id header.
Every error body also has notice and terms. See Notice and terms.
The shape
A failed request looks like this. The field path uses the request's names. A header uses the header name.
- Field:
error.code. Meaning: The code in the table below - Field:
error.message. Meaning: A sentence for a person - Field:
error.request_id. Meaning: Id for this request - Field: extra keys. Meaning: Depend on the code
- Field:
notice. Meaning: The notice string - Field:
terms.version. Meaning:2026-10-draft - Field:
terms.url. Meaning: Site origin plus/api-terms
Codes
- Code:
invalid_key. HTTP: 401. Extra keys: None. Also sendsWWW-Authenticate: Bearer realm="terminal", error="invalid_token". - Code:
scope_missing. HTTP: 403. Extra keys:required_scope. A key scope, orsessionwhen only the signed-in app may make the change. - Code:
terms_required. HTTP: 403. Extra keys:version. The current Terms version. A write from an account that has not agreed to it. GET and HEAD are not refused. - Code:
credits_required. HTTP: 402. Extra keys:price_cents,balance_cents, andbuy_urlwhile credit packs are on sale.buy_urlis the site origin plus/app/settings/billing. While credit packs are not on sale, there is nobuy_url. - Code:
quota_exceeded. HTTP: 402. Extra keys:limit,max,used. A plan limit also includesplan. A spend cap also includesresets_atand does not includeplan. - Code:
payment_required. HTTP: 402. Extra keys:reason. Used by pay-per-call routes, not by/v1. - Code:
rate_limited. HTTP: 429. Extra keys:reason:key,account,long_polls,server_busy,previews,tests,ip,wallet, and on the exchange's routessubscriptions_limit(20 new subscriptions a day) andreports_limit(3 reports a month). SendsRetry-After, except for those two daily and monthly limits. - Code:
not_found. HTTP: 404. Extra keys: None. Unknown route or method, or an id that is missing or belongs to another account. - Code:
conflict. HTTP: 409. Extra keys:reason:duplicate_rule(withrule_id),idempotency_key_reused,in_progress,destination_off. A rule the engine cannot change as asked names the engine's reason:rule_off,rule_muted,preset_not_allowedorplan_required. A rule tied to an exchange service can also useuse_subscription, orrule_backs_servicewithservice_id. - Code:
validation_failed. HTTP: 400. Extra keys:fields, a map of path to message. - Code:
not_available. HTTP: 503. Extra keys:reason:channel_closed,beta,feature_off,note_unavailable,no_history,exchange_closed(the exchange's routes),payment_unavailable(pay-per-call routes). - Code:
server_error. HTTP: 500. Extra keys: None. Retry. The same Idempotency-Key is safe.
quota_exceeded.limit is one of key_daily_spend, account_monthly_spend, rules, coins, keys, webhook_destinations, destinations. destinations is the account cap of 20 of your own destinations, of any kind. Channels you post alerts to have their own limit.
A 402 for the call's own block or preview charge, and a 500, are removed from the monthly call count. See Quotas.
Idempotency
A POST that writes or charges requires Idempotency-Key: 1 to 255 visible ASCII characters with no spaces (0x21 to 0x7E), such as a UUID. The same key and the same body replay the stored success, byte for byte, with the header Idempotent-Replayed: true, for 24 hours. The same key and a different body return 409 conflict with reason idempotency_key_reused. A 4xx or 5xx deletes the claim, so you can send the same key again after you fix the request. A claim still running returns reason in_progress.
cooldown_seconds on a rule is one of 900, 1800, 3600, 14400, 43200, 86400, or 604800.
Versioning
/v1 and terminal.alert.v1 change by adding: new routes, new optional request fields, new response fields, new enum values, and new error reason values. Ignore fields you do not know. Treat an enum value you do not know as other. Removing a field, renaming one, changing a type or a unit, or changing what a field means, needs /v2 and terminal.alert.v2. Both versions are served for at least 6 months.
A missing Idempotency-Key
Example. These are not live prices.
POST /v1/rules HTTP/1.1
Authorization: Bearer $TERMINAL_API_KEY
Content-Type: application/json
{
"kind": "percent_change",
"scope": "symbol",
"symbol": "ETH",
"name": "ETH moves 5% in 1 hour",
"cooldown_seconds": 3600,
"condition": { "threshold_pct": 5, "window": "1h", "direction": "either" }
}
HTTP/1.1 400 Bad Request
{
"error": {
"code": "validation_failed",
"message": "Check the fields listed.",
"fields": {
"Idempotency-Key": "Send an Idempotency-Key header with this request: 1 to 255 printable characters, such as a UUID."
}
}
}
A field the route does not have
Example. These are not live prices.
POST /v1/rules HTTP/1.1
Authorization: Bearer $TERMINAL_API_KEY
Content-Type: application/json
Idempotency-Key: 8c2d1f44-0e6b-4a91-b3d7-5f0a6c8e1d29
{
"kind": "percent_change",
"scope": "symbol",
"symbol": "ETH",
"name": "ETH moves 5% in 1 hour",
"cooldown_seconds": 3600,
"condition": { "threshold_pct": 5, "window": "1h", "direction": "either" },
"bonus": true
}
HTTP/1.1 400 Bad Request
{
"error": {
"code": "validation_failed",
"fields": { "bonus": "Not a field of this request." }
}
}
An unknown path
Example. These are not live prices.
GET /v1/not-a-route HTTP/1.1
Authorization: Bearer $TERMINAL_API_KEY
HTTP/1.1 404 Not Found
{
"error": {
"code": "not_found",
"message": "We could not find that. Check the id and the path."
}
}
An id that is not on the account
Example. These are not live prices.
GET /v1/alerts/2147483646 HTTP/1.1
Authorization: Bearer $TERMINAL_API_KEY
HTTP/1.1 404 Not Found
{
"error": { "code": "not_found" }
}
A cursor that is not valid
Example. These are not live prices.
GET /v1/events?cursor=not-a-cursor&wait=0 HTTP/1.1
Authorization: Bearer $TERMINAL_API_KEY
HTTP/1.1 400 Bad Request
{
"error": {
"code": "validation_failed",
"fields": {
"cursor": "This cursor is not valid. Use next_cursor from an earlier answer, or leave it out."
}
}
}
Create a rule
The condition uses the public names. threshold_pct is the size, without a sign. direction is up, down, or either.
Example. These are not live prices.
POST /v1/rules HTTP/1.1
Authorization: Bearer $TERMINAL_API_KEY
Content-Type: application/json
Idempotency-Key: a1b2c3d4-e5f6-4789-a012-3456789abcde
{
"kind": "percent_change",
"scope": "symbol",
"symbol": "ETH",
"name": "ETH moves 5% in 1 hour",
"cooldown_seconds": 3600,
"condition": { "threshold_pct": 5, "window": "1h", "direction": "either" }
}
HTTP/1.1 201 Created
{
"rule": {
"name": "ETH moves 5% in 1 hour",
"kind": "percent_change",
"scope": "symbol",
"status": "active",
"cooldown_seconds": 3600,
"note_mode": "auto",
"asset": { "symbol": "ETH", "name": "Ethereum" },
"condition": { "threshold_pct": 5, "window": "1h", "direction": "either" }
}
}
The same Idempotency-Key on a different body
Example. These are not live prices.
POST /v1/rules HTTP/1.1
Authorization: Bearer $TERMINAL_API_KEY
Content-Type: application/json
Idempotency-Key: a1b2c3d4-e5f6-4789-a012-3456789abcde
{
"kind": "percent_change",
"scope": "symbol",
"symbol": "ETH",
"name": "ETH moves 5% in 1 hour again",
"cooldown_seconds": 3600,
"condition": { "threshold_pct": 5, "window": "1h", "direction": "either" }
}
HTTP/1.1 409 Conflict
{
"error": {
"code": "conflict",
"reason": "idempotency_key_reused",
"message": "This Idempotency-Key was used for a different request. Use a new key for a new request."
}
}
The same condition again
A new Idempotency-Key with the same coin, kind, and condition returns the existing rule's id.
Example. These are not live prices.
POST /v1/rules HTTP/1.1
Authorization: Bearer $TERMINAL_API_KEY
Content-Type: application/json
Idempotency-Key: b2c3d4e5-f6a7-4890-b123-456789abcdef
{
"kind": "percent_change",
"scope": "symbol",
"symbol": "ETH",
"name": "ETH moves 5% in 1 hour",
"cooldown_seconds": 3600,
"condition": { "threshold_pct": 5, "window": "1h", "direction": "either" }
}
HTTP/1.1 409 Conflict
{
"error": {
"code": "conflict",
"reason": "duplicate_rule",
"rule_id": "…",
"message": "You already have this alert. Its id is in rule_id."
}
}
A plan with no webhooks
Example. These are not live prices.
POST /v1/destinations HTTP/1.1
Authorization: Bearer $TERMINAL_FREE_KEY
Content-Type: application/json
Idempotency-Key: 1b9e4c70-2a55-4d18-8f06-3c7a91e0d4b2
{
"kind": "webhook",
"format": "terminal",
"url": "https://hooks.example.com/free",
"label": "Free webhook"
}
HTTP/1.1 402 Payment Required
{
"error": {
"code": "quota_exceeded",
"message": "Webhooks for your software are part of Pro.",
"limit": "webhook_destinations",
"max": 0,
"used": 0,
"plan": "free"
}
}
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.