The Trinity Beast – API Reference

Complete reference for the Listener Price Oracle (LPO) and Listener Reporting Service (LRS)

Region: us-east-2 (Ohio) LPO: https://api.cpmp-site.org LRS: https://lrs.cpmp-site.org UDP: udp.cpmp-site.org (2679/2680) Updated: October 2026 Endpoints: 50+

1. Authentication

All API requests require a Bearer token in the Authorization header.

Authorization Header Required on all requests
Authorization: Bearer YOUR_API_KEY

Your API key is provided after subscribing. Keep it secret — it identifies your account for billing and rate limiting.

⚠️ Security: Never expose your API key in client-side JavaScript or public repositories. Use environment variables or a backend proxy.

2. LPO – Listener Price Oracle

The LPO delivers real-time cryptocurrency prices with sub-2ms cached responses. Prices are sourced from 6 persistent WebSocket feeds (Coinbase, Bitstamp, Kraken, Gate.io, Crypto.com, OKX) — 357 prewarmed assets pushed in real-time with zero REST polling. REST fallback (Coinbase → Kraken → Bitstamp) activates only if all WebSocket feeds for an asset are stale.

2.1 GET /price

GET /price GET

Returns the current price for a cryptocurrency asset.

Query Parameters

ParameterTypeRequiredDescription
assetstringRequiredAsset symbol (e.g. btc, eth, sol). Case-insensitive.

Example Request

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.cpmp-site.org/price?asset=btc"

2.2 POST /prices NEW

POST /prices POST

Returns current prices for multiple cryptocurrency assets in a single request. Fetch up to 30 assets at once — perfect for portfolio dashboards, watchlists, and batch operations. The entire batch counts as 1 request against your rate limits and monthly quota, rewarding efficient API usage.

✅ Efficiency Incentive: A single /prices call with 30 assets counts as 1 request — not 30. Partners and developers who batch their requests are being good API citizens, and we reward that efficiency. Available on all tiers including Free.

Request Body

FieldTypeRequiredDescription
assetsstring[]RequiredArray of asset symbols (e.g. ["BTC", "ETH", "SOL"]). Case-insensitive. Maximum 30 assets per request. Duplicates are automatically deduplicated.

Example Request

curl -X POST -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"assets": ["BTC", "ETH", "SOL", "DOGE", "XRP"]}' \
  "https://api.cpmp-site.org/prices"

Success Response (200 OK)

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/prices] [200]",
  "status_code": 200,
  "endpoint": "/prices",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbts/v1",
  "timestamp": "2026-05-28T20:32:08Z",
  "data": {
    "prices": {
      "BTC": {
        "exchange": "coinbase-ws",
        "cached": true,
        "cache_age_seconds": 0.082,
        "timestamp": 1780000328,
        "price": "73551.36"
      },
      "ETH": {
        "exchange": "coinbase-ws",
        "cached": true,
        "cache_age_seconds": 0.081,
        "timestamp": 1780000328,
        "price": "2016.48"
      },
      "SOL": {
        "exchange": "coinbase-ws",
        "cached": true,
        "cache_age_seconds": 0.217,
        "timestamp": 1780000328,
        "price": "82.36"
      },
      "DOGE": {
        "exchange": "coinbase-ws",
        "cached": true,
        "cache_age_seconds": 4.501,
        "timestamp": 1780000324,
        "price": "0.09983"
      },
      "XRP": {
        "exchange": "coinbase-ws",
        "cached": true,
        "cache_age_seconds": 1.389,
        "timestamp": 1780000327,
        "price": "1.3207"
      }
    },
    "asset_count": 5,
    "resolved_count": 5,
    "failed": [],
    "latency_ms": 0,
    "duration_ms": 12,
    "monthly_usage": 4521,
    "monthly_limit": 100000,
    "usage_pct": 4.52,
    "usage_warning": "",
    "usage_status": "✅"
  },
  "error": ""
}

Response Fields (inside data)

FieldTypeDescription
pricesobjectMap of asset symbol → price data. Each entry contains exchange, cached, cache_age_seconds, timestamp, and price.
asset_countintNumber of unique assets requested (after deduplication)
resolved_countintNumber of assets successfully resolved
failedstring[]Array of asset symbols that could not be resolved (empty on full success)
latency_msintSum of exchange lookup latencies for cache misses (0 if all cached)
duration_msintTotal wall-clock processing time
monthly_usageintNumber of requests used this month
monthly_limitintMaximum requests allowed per month for your tier
usage_pctfloatCurrent usage as a percentage of monthly limit
usage_warningstringGraduated warning: "green" (≥85%), "yellow" (≥90%), "red" (≥95%), or ""
usage_statusstringVisual indicator: ✅, ✅🟡, ✅⚠️, or ✅🔴

Partial Success Response (some assets failed)

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/prices] [200]",
  ...
  "data": {
    "prices": {
      "BTC": { "exchange": "coinbase-ws", "cached": true, ... },
      "ETH": { "exchange": "coinbase-ws", "cached": true, ... }
    },
    "asset_count": 3,
    "resolved_count": 2,
    "failed": ["FAKECOIN"],
    ...
  }
}
🎯 Better than the major exchanges: Most exchange batch endpoints (including Kraken) fail the entire request if a single asset is delisted, mistyped, or temporarily unavailable. Trinity Beast returns HTTP 200 with every asset that resolved in prices, and every asset that didn't in failed. One round-trip, useful data on the first call, no client-side retry logic required. Partial failures don't break your batch.

Error Response — Too Many Assets (400)

{
  "status": "🛑 [LPO] [us-east-2] [BeastMain] [/prices] [400]",
  "status_code": 400,
  "endpoint": "/prices",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbts/v1",
  "timestamp": "2026-05-28T20:32:23Z",
  "data": null,
  "error": "🛑 [LPO] [us-east-2] [BeastMain] [/prices] [400] Maximum 30 assets per request"
}
Deduplication: If you request ["BTC", "btc", "Btc"], the response will contain a single "BTC" entry. Asset symbols are normalized to uppercase and deduplicated before processing.

2.3 LPO Response Examples

Success Response (200 OK) — Cached Hit

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/price] [200]",
  "status_code": 200,
  "endpoint": "/price",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbts/v1",
  "timestamp": "2026-04-12T14:23:01Z",
  "data": {
    "usage_status": "✅",
    "api_key_id": "key_abc123",
    "asset": "BTC",
    "price": "83421.57",
    "source": "coinbase-ws",
    "timestamp": 1744466581,
    "readable_timestamp": "2026-04-12T14:23:01Z",
    "latency_ms": 0,
    "duration_ms": 0,
    "cached": true,
    "cache_duration_seconds": 4.2,
    "monthly_usage": 42,
    "monthly_limit": 1000,
    "usage_pct": 4.2,
    "usage_warning": "",
    "ip_address": "203.0.113.42"
  },
  "error": ""
}

Success Response (200 OK) — Live Fetch

{
  "status": "✅ [LPO] [us-east-2] [BeastMirror] [/price] [200]",
  "status_code": 200,
  "endpoint": "/price",
  "cluster_node": "BeastMirror",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbts/v1",
  "timestamp": "2026-04-12T14:23:15Z",
  "data": {
    "usage_status": "✅",
    "api_key_id": "key_abc123",
    "asset": "ETH",
    "price": "3187.44",
    "source": "kraken",
    "timestamp": 1744466595,
    "readable_timestamp": "2026-04-12T14:23:15Z",
    "latency_ms": 38,
    "duration_ms": 38,
    "cached": false,
    "cache_duration_seconds": 0,
    "monthly_usage": 43,
    "monthly_limit": 1000,
    "usage_pct": 4.3,
    "usage_warning": "",
    "ip_address": "203.0.113.42"
  },
  "error": ""
}

Success Response — Graduated Usage Warnings

As monthly usage approaches the query limit, the data.usage_status field includes graduated warning indicators. Thresholds are runtime-tunable via application parameters.

🟡 Green Warning (≥85% of monthly limit)
{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/price] [200]",
  ...
  "data": {
    "usage_status": "✅🟡",
    ...
    "monthly_usage": 855,
    "monthly_limit": 1000,
    "usage_pct": 85.5,
    "usage_warning": "green",
    ...
  },
  "error": ""
}
⚠️ Yellow Warning (≥90% of monthly limit)
{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/price] [200]",
  ...
  "data": {
    "usage_status": "✅⚠️",
    ...
    "monthly_usage": 910,
    "monthly_limit": 1000,
    "usage_pct": 91.0,
    "usage_warning": "yellow",
    ...
  },
  "error": ""
}
🔴 Red Warning (≥95% of monthly limit)
{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/price] [200]",
  ...
  "data": {
    "usage_status": "✅🔴",
    ...
    "monthly_usage": 960,
    "monthly_limit": 1000,
    "usage_pct": 96.0,
    "usage_warning": "red",
    ...
  },
  "error": ""
}
⚠️ Graduated Usage Warnings: As data.monthly_usage approaches data.monthly_limit, the response includes graduated indicators at three configurable thresholds:
  • Green (85%) — ✅🟡 — Heads up: usage is climbing
  • Yellow (90%) — ✅⚠️ — Caution: approaching your limit
  • Red (95%) — ✅🔴 — Critical: near your monthly limit
The usage_warning field is an empty string ("") when usage is below the green threshold. The usage_pct field is always present. Both fields appear in every response — the JSON schema is always consistent. Thresholds are configurable via usage_warning_green_pct, usage_warning_yellow_pct, and usage_warning_red_pct application parameters.

Response Fields

The /price endpoint returns the standard 12-field UME envelope. Price data is nested inside the data field:

Envelope Fields (top level)
FieldTypeDescription
statusstringColor-coded bracket line: ✅ [LPO] [us-east-2] [BeastMain] [/price] [200]
status_codeintHTTP status code as integer
endpointstringThe URL path that handled the request
cluster_nodestringContainer that served the request (BeastMain, BeastMirror, BeastLRS)
regionstringAWS region — us-east-2
languagestringISO 639-1 language code for human-readable content
timestampstringRFC 3339 UTC timestamp of response generation
dataobjectPrice data payload (see below). null on error.
errorstringEmpty string on success. Full bracketed error message on failure.
Price Data Fields (inside data)
FieldTypeDescription
usage_statusstring✅ on success, ✅🟡 at green warning, ✅⚠️ at yellow warning, ✅🔴 at red warning
api_key_idstringYour API key
assetstringUppercase asset symbol
pricestringCurrent price in USD (formatted to 9 decimal places, trailing zeros trimmed)
sourcestringPrice source tag. WebSocket (real-time): coinbase-ws, bitstamp-ws, kraken-ws, gateio-ws, cryptocom-ws, okx-ws. REST fallback: coinbase, bitstamp, kraken, gateio, cryptocom, okx.
timestampint64Unix timestamp of the price
readable_timestampstringRFC3339 formatted timestamp
latency_msint64Time to fetch price from exchange (0 on cache hit)
duration_msint64Total request processing time including validation
cachedboolWhether the response was served from cache
cache_duration_secondsfloat64Age of cached value in seconds (0 if live fetch)
monthly_usageintNumber of queries used this month
monthly_limitintMaximum queries allowed per month for your tier
usage_pctfloat64Current usage as a percentage of monthly limit (always present)
usage_warningstringGraduated warning level: "green" (≥85%), "yellow" (≥90%), "red" (≥95%), or "" (empty string when below green threshold). Always present.
ip_addressstringClient IP address

3. LRS – Listener Reporting Service

The LRS provides access to your API usage history and aggregated statistics. Included with every subscription tier.

3.1 GET /reports/usage

GET /reports/usage GET

Returns paginated usage log entries for your API key.

Query Parameters

ParameterTypeRequiredDefaultDescription
api_key_idstringOptional—Filter by API key ID
assetstringOptional—Filter by asset symbol (e.g. BTC)
start_datestringOptional93 days agoStart date in YYYY-MM-DD format
end_datestringOptionalTodayEnd date in YYYY-MM-DD format
cachedboolOptional—Filter by cache status: true or false
pageintOptional1Page number
page_sizeintOptional100Records per page (max 1000)
formatstringOptionaljsonOutput format: json, csv, tsv, text

Example Requests

# JSON (default)
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://lrs.cpmp-site.org/reports/usage?asset=BTC&page=1&page_size=50"

# CSV download
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://lrs.cpmp-site.org/reports/usage?format=csv" -o usage.csv

# Tab-delimited
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://lrs.cpmp-site.org/reports/usage?format=tsv" -o usage.tsv

# Plain text
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://lrs.cpmp-site.org/reports/usage?format=text"

3.2 GET /reports/summary

GET /reports/summary GET

Returns aggregated statistics for a date range.

Query Parameters

ParameterTypeRequiredDefaultDescription
api_key_idstringOptional—Filter by API key ID
start_datestringOptional30 days agoStart date in YYYY-MM-DD format
end_datestringOptionalTodayEnd date in YYYY-MM-DD format
formatstringOptionaljsonOutput format: json, csv, tsv, text

3.3 Output Formats

✅ New: LRS endpoints now support four output formats to make it easy to import data into spreadsheets, databases, or other systems.
FormatContent-TypeUse Case
jsonapplication/jsonDefault. Best for programmatic consumption and APIs.
csvtext/csvComma-separated. Import into Excel, Google Sheets, or any CSV-compatible tool.
tsvtext/tab-separated-valuesTab-delimited. Ideal for databases, ETL pipelines, and tools that handle commas in data.
texttext/plainHuman-readable plain text. Good for quick terminal review or log files.

3.4 LRS Response Examples

Usage Report — JSON

{
  "logs": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "api_key_id": "key_abc123",
      "asset": "BTC",
      "price": 83421.57,
      "source": "coinbase",
      "cached": true,
      "latency_ms": 7,
      "duration_ms": 7,
      "ip_address": "203.0.113.42",
      "timestamp": 1744466581,
      "readable_timestamp": "2026-04-12T14:23:01Z",
      "cache_age_seconds": 4.2
    }
  ],
  "total_count": 1482,
  "page": 1,
  "page_size": 100,
  "filters": {
    "asset": "BTC"
  }
}

Usage Report — CSV

id,api_key_id,asset,price,source,cached,latency_ms,duration_ms,ip_address,timestamp,readable_timestamp,cache_age_seconds
a1b2c3d4-e5f6-7890-abcd-ef1234567890,key_abc123,BTC,83421.57000000,coinbase,true,7,7,203.0.113.42,1744466581,2026-04-12T14:23:01Z,4.200

Usage Report — Text

LRS Usage Report — Page 1 | Page Size 100 | Total Records 1482
================================================================================
[2026-04-12T14:23:01Z] a1b2c3d4... | Asset: BTC | Price: 83421.57000000 | Source: coinbase | Cached: true | Latency: 7ms | IP: 203.0.113.42

Summary Report — JSON

{
  "total_requests": 14820,
  "cache_hit_rate": 91.4,
  "avg_latency_ms": 9.3,
  "by_asset": {
    "BTC": 6210,
    "ETH": 4105,
    "SOL": 2380,
    "DOGE": 1240,
    "XRP": 885
  },
  "by_source": {
    "coinbase": 13540,
    "kraken": 1180,
    "bitstamp": 100
  },
  "date_range": {
    "start": "2026-03-13",
    "end": "2026-04-12"
  }
}

Summary Report — Text

LRS Summary Report — 2026-03-13 to 2026-04-12
============================================================
Total Requests  : 14820
Cache Hit Rate  : 91.40%
Avg Latency     : 9.30ms

By Asset:
  BTC        6210
  ETH        4105
  SOL        2380

By Source:
  coinbase     13540
  kraken       1180
  bitstamp     100

4. Supported Assets & Exchanges

The Trinity Beast LPO serves 357 prewarmed assets across 6 exchanges via persistent WebSocket connections. Per-exchange split reflects real liquidity ceilings: Bitstamp 48, Coinbase 66, Crypto.com 46, Gate.io 65, Kraken 65, OKX 67. Prices arrive in real-time (push-based) before any request is made.

Exchange Overview

ExchangeSource TagWebSocket EndpointPair SuffixAssets
Coinbasecoinbase-wswss://advanced-trade-ws.coinbase.comUSD66
Bitstampbitstamp-wswss://ws.bitstamp.netUSD48
Krakenkraken-wswss://ws.kraken.com/v2USD65
Gate.iogateio-wswss://api.gateio.ws/ws/v4/USDT65
Crypto.comcryptocom-wswss://stream.crypto.com/exchange/v1/marketUSD46
OKXokx-wswss://ws.okx.com:8443/ws/v5/publicUSDT67

Assets by Exchange

Coinbase (25 assets)

BTC · ETH · SOL · DOGE · XRP · LINK · DOT · LTC · AVAX · UNI · PEPE · XLM · ETC · ICP · RENDER · BONK · BCH · JASMY · AXS · CHZ · STORJ · ZEC · DASH · CGLD · NEAR

Bitstamp (29 assets)

AAVE · ATOM · ARB · CRV · GRT · FIL · SHIB · BAT · LRC · MASK · ANKR · SKL · API3 · QNT · HYPE · LPT · HNT · UMA · JTO · CTX · POL · PNUT · POPCAT · DRIFT · CUBE

Kraken (25 assets)

NANO · SC · LSK · KAVA · BICO · RARI · OCEAN · CFG · CQT · ALGO · FET · FLOW · XTZ · QTUM · ICX · ASTR · ENJ · EGLD · COTI · MINA · GLMR · MOVR · KSM · TEER · CRO

Gate.io (25 assets)

BNB · TRX · APT · SEI · INJ · OP · SUI · VET · HBAR · SAND · MANA · S · CHR · FLR · THETA · CELR · REEF · DENT · HOT · POND · IOTX · ALICE · SUPER · FLUX · ONE

Crypto.com (25 assets)

TON · WLD · APE · BLUR · IMX · ENS · LDO · SNX · COMP · 1INCH · SUSHI · GALA · MAGIC · ORDI · PIXEL · RUNE · ARKM · DYM · GMX · CRO · TAO · AGLD · AR · AUDIO · POL

OKX (25 assets)

TIA · JUP · STRK · PYTH · W · ZRO · PENDLE · ONDO · WIF · FLOKI · NOT · AEVO · ENA · ETHFI · TURBO · NEIRO · DOGS · HMSTR · EIGEN · BOME · KAITO · GRASS · BANANA · ACE · TNSR

REST Fallback Sources (used only when WebSocket feeds are stale)

PrioritySourceSource TagTimeoutWeight
1Coinbasecoinbase100ms0.50
2Krakenkraken80ms0.35
3Bitstampbitstamp200ms0.15

REST fallback is rarely triggered — with 6 WebSocket feeds pushing prices in real-time, the cache hit rate is 98.5%+. REST sources are only consulted when an asset's WebSocket price is older than the cache_ttl_seconds threshold (currently 300 seconds).

ℹ️ Asset symbols are case-insensitive in requests. Responses always return uppercase. Use the GET /asset-categories endpoint for a category-grouped listing of all available assets. The GET /exchanges endpoint returns the full exchange configuration including WebSocket endpoints and asset mappings.

5. Error Codes & Response Convention

Status Indicators

IndicatorMeaningWhen It Appears
✅SuccessEvery successful price response (usage below green threshold)
✅🟡Success with green warningMonthly usage is at or above 85% of your limit (configurable via usage_warning_green_pct)
✅⚠️Success with yellow warningMonthly usage is at or above 90% of your limit (configurable via usage_warning_yellow_pct)
✅🔴Success with red warningMonthly usage is at or above 95% of your limit (configurable via usage_warning_red_pct)
⚠️Rate limit warningMonthly query limit reached or QPS rate limit exceeded
🛑ErrorInvalid request, authentication failure, or server error

Error Response Format

All error responses use the unified response envelope with color-coded bracket convention. The status field shows the indicator and origin metadata. The error field contains the full bracketed message.

{
  "status": "🛑 [LPO] [us-east-2] [BeastMain] [/price] [401]",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbts/v1",
  "status_code": 401,
  "endpoint": "/price",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "timestamp": "2026-04-26T16:20:03Z",
  "error": "🛑 [LPO] [us-east-2] [BeastMain] [/price] [401] Missing required API key parameter"
}

Rate Limit Warning (429)

{
  "status": "🛑 [LPO] [us-east-2] [BeastMirror] [/price] [429]",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbts/v1",
  "error": "🛑 [LPO] [us-east-2] [BeastMirror] [429] ⚠️ Monthly query limit reached. You have used 1000 of 1000 allowed queries this month. Upgrade your plan for higher limits.",
  "code": 429,
  "region": "us-east-2",
  "cluster_node": "BeastMirror"
}

HTTP Status Codes

HTTP StatusMeaningCommon Cause
400Bad RequestMissing or invalid asset parameter.
401UnauthorizedMissing, invalid, or revoked API key.
429Too Many RequestsMonthly query limit reached or QPS rate limit exceeded. Message includes your current usage and limit.
500Internal Server ErrorAll price sources (WebSocket + REST) failed or database connectivity issue.
503Service UnavailableElastiCache health check failed.

6. Rate Limits by Tier

TierRequests/MonthLRS IncludedPrice
Free1,00010 reports/month$0/mo
Pro50,00010 reports/month$30/mo
Enterprise500,00010 reports/month$100/mo
UnlimitedUnlimitedUnlimited$300/mo
LifetimeUnlimited foreverUnlimited forever$3,000 one-time
AWS PartnerUnlimited — no rate limitingUnlimited$0 — always free
ℹ️ LRS Add-on: Existing subscribers can add unlimited LRS access for $20/month.
AWS Partner Tier: The exchanges we depend on share their data with us at no cost — we pass that forward. If your AWS application needs live crypto prices, partner keys provide unlimited access with no rate limiting, no monthly caps, and no billing. Partners connect via AWS PrivateLink (TCP) or VPC Peering (UDP) directly to containers. See the Partner Onboarding Guide for setup details.

7. Endpoint Summary

ServiceProtocolEndpointPort
LPO (Price API)HTTPShttps://api.cpmp-site.org443
LRS (Reports)HTTPShttps://lrs.cpmp-site.org443
LPO (UDP)UDPudp.cpmp-site.org2679
LRS (UDP)UDPudp.cpmp-site.org2680
PrivateLink (LPO)TCPVPC Endpoint → NLB8080
PrivateLink (LRS)TCPVPC Endpoint → NLB9090
ℹ️ PrivateLink: AWS Partners connect via PrivateLink (TCP) or VPC Peering (UDP) for private VPC-to-VPC connectivity without traversing the public internet. Partner keys have zero rate limiting and zero monthly caps. See the Partner Onboarding Guide.

Public Endpoints

MethodPathDescription
GET/exchangesPublic exchange asset list for demo dropdown
GET/asset-categoriesAsset categories for demo dropdown NEW
GET/map/pinsMission impact map pins
POST/translateReal-time text translation (AWS Translate, cached in ElastiCache)
GET/searchFull-text document search (Valkey-indexed, multi-lingual) NEW
POST/support/submitSubmit support ticket
GET/support/ticketsList tickets by email
GET/support/ticket/{ticket_number}View ticket detail
GET/support/resolveOne-click ticket resolve via email link. Query: ?token=.... Single-use, 30-day TTL. NEW
POST/demo/registerDemo lead registration NEW
POST/partner/applyPartner application submission
GET/partner/statusPartner application status check
POST/newsletter/subscribeNewsletter subscribe
GET/newsletter/unsubscribeNewsletter unsubscribe
GET/newsletter/resubscribeNewsletter resubscribe
GET/checkoutPublic checkout redirect (Stripe Payment Link) NEW
GET/public/statusPublic system status — nodes, feeds, uptime (no auth)
GET/public/clusterPublic ECS cluster stats — per-node metrics, cache hit rates (no auth) NEW

Admin Endpoints

MethodPathDescription
GET/admin/configView application parameters
GET/admin/setup-demoSetup demo API key
GET/admin/invalidate-keyInvalidate API key cache
GET/admin/demo-modeToggle demo/performance mode
GET/admin/system-modeSwitch system profile
GET/admin/reload-paramsForce parameter reload
GET/admin/profilesList system profiles NEW
GET/admin/stress-statsReal-time 24-counter metrics
GET/admin/stress-resetReset metrics counters
GET/admin/cluster-statsCluster-wide aggregated metrics NEW
POST/admin/build-search-indexRebuild full-text search index from all 32 docs NEW
GET/admin/feed-statusPrice feed status (all 6 exchanges)
GET/admin/partner-ws-statsPartner WebSocket connections — masked keys, subscribed assets, uptime NEW
GET/admin/lang/listList i18n language keys cached in Valkey NEW
POST/admin/lang/setPush an i18n language JSON blob to Valkey NEW
GET/admin/usage-analyticsUsage log analytics
GET/admin/adaptive-statsAdaptive governor stats
GET/admin/db-insightsPostgreSQL self-observability (cache hit, top queries, scan health) NEW
GET/admin/exchange-feedsList exchange configurations
POST/admin/exchange-feeds/saveCreate/update exchange
POST/admin/exchange-feeds/toggleEnable/disable exchange
DELETE/admin/exchange-feedsRemove exchange
GET/admin/exchange-assetsList asset mappings
POST/admin/exchange-assets/saveCreate/update asset mappings
POST/admin/exchange-assets/toggleFlip enabled/prewarm on single asset NEW
DELETE/admin/exchange-assetsRemove asset mappings
POST/admin/eam/syncSync EAM → application_parameters NEW
GET/admin/eam/auditStart 4-layer prewarm audit (async) NEW
GET/admin/eam/audit?poll=1Poll for audit results NEW
POST/admin/eam/discoverStart asset discovery for exchange (async) NEW
GET/admin/eam/discover?exchange=okx&poll=1Poll for discovery results NEW
GET/admin/partner-applicationsList partner applications
POST/admin/partner-applications/{id}/statusUpdate application status
POST/admin/sqlExecute SQL (read/write) NEW
POST/admin/sql-batchExecute SQL batch (DDL allowed) NEW
POST/admin/valkeyExecute ElastiCache commands NEW
GET/admin/payment-linksList payment links NEW
POST/admin/payment-links/saveCreate/update payment link NEW
POST/admin/payment-links/toggleEnable/disable payment link NEW
POST/admin/test-refund-emailSend test refund email (dummy data, any language) NEW
POST/admin/test-finalize-emailSend test translation completion email with per-language cost breakdown NEW
POST/admin/test-customer-emailSend test customer-facing translation email (any template, any language) NEW

Test email endpoints read live templates. All three test-*-email endpoints resolve their strings through the shared EmailLoader, which means they render exactly what a real send would produce — including any copy edited in Valkey since the last deploy. Use them to verify a template change before it reaches a customer.

Newsletter Admin Endpoints

MethodPathDescription
GET/admin/newsletter/templatesList templates
GET/admin/newsletter/templates/{id}Get template
POST/admin/newsletter/templatesSave template
DELETE/admin/newsletter/templates/{id}Delete template
GET/admin/newsletter/newslettersList newsletters
GET/admin/newsletter/newsletters/{id}Get newsletter
POST/admin/newsletter/newslettersSave newsletter
POST/admin/newsletter/newsletters/{id}/statusToggle status
POST/admin/newsletter/newsletters/{id}/sendSend newsletter
GET/admin/newsletter/subscribersList subscribers

Email Admin Endpoints

MethodPathDescription
GET/admin/email/templatesList email templates
GET/admin/email/templates/{id}Get template
POST/admin/email/templatesSave template
DELETE/admin/email/templates/{id}Delete template
GET/admin/email/sendersList senders
POST/admin/email/sendersSave sender
DELETE/admin/email/senders/{id}Delete sender
GET/admin/email/draftsList drafts
GET/admin/email/drafts/{id}Get draft
POST/admin/email/draftsSave draft
DELETE/admin/email/drafts/{id}Delete draft
POST/admin/email/drafts/{id}/sendSend email

Support Admin Endpoints (X-Admin-Key)

MethodPathDescription
GET/support/admin/statsTicket statistics — counts by status (total, new, open, in_progress, awaiting_customer, resolved, closed)
GET/support/admin/ticketsList all tickets. Filters: ?status=, ?category=, ?email=. Ordered by updated_at DESC
GET/support/admin/tickets/{id}Full ticket detail by UUID — includes all replies (internal notes visible), translations, preferred_lang
POST/support/admin/tickets/{id}/updateUpdate ticket fields. Body: {"status":"...", "category":"..."} (at least one required)
POST/support/admin/tickets/{id}/replyAdd reply. Body: {"author":"...", "message":"...", "is_internal": false}. Auto-translates + emails customer
GET/support/admin/ticket/{ticket_number}Full ticket by ticket number — includes AutoOps Bedrock analysis from Valkey

Dashboard Customer Support Endpoints (Bearer Token)

MethodPathDescription
GET/dashboard/api/support/ticketsList authenticated user's tickets. Scoped to session email. Ordered by most recent.
GET/dashboard/api/support/tickets/{ticket_number}Ticket detail with reply thread. Excludes internal admin notes. Shows translated replies in customer's language.
POST/dashboard/api/support/tickets/{ticket_number}/replyPost customer reply. Body: {"message":"..."}. Max 10,000 chars. Auto-translates to English. Notifies admin.
POST/dashboard/api/support/tickets/{ticket_number}/resolveMark own ticket as resolved. Notifies admin. Closed tickets cannot be resolved.

Dashboard Account Endpoints (Bearer Token)

MethodPathDescription
GET/dashboard/api/accountRetrieve full account profile: email, display name, roles, facets, preferred_lang, api_lang, response_format, member since, tier info.
POST/dashboard/api/profileUpdate account profile. Body fields (all optional): display_name, preferred_lang, api_lang, response_format. Updates ALL non-revoked api_keys rows for the user.
POST/dashboard/api/change-emailInitiate email change. Body: {"new_email":"..."}. Sends verification link to new email. 1 pending change at a time.

Dashboard Webhook Endpoints (Bearer Token)

Added 2026-08-07 through 2026-08-11 alongside the webhook Delivery Log, Delivery Reports, and dashboard-native activation features (see Trinity-Beast-Account-Dashboards.html §5.5).

MethodPathDescription
POST/dashboard/api/webhook/activateActivates a pending_verification subscription. Body: {"delivery_method":"...", "udp_host":"...", "udp_port":..., "https_url":"..."}. Requires assets already configured. Skips the public API's token-echo verification since a dashboard session already proves control.
GET/dashboard/api/webhook/logUp to 50 recent push events, grouped server-side by (sequence_number, delivery_method), each with a nested per-asset price array. Response shape changed 2026-08-09 — previously one flat row per asset per push; now one grouped row per push with a nested assets[] array. Any consumer reading a flat per-asset shape will break.
GET/dashboard/api/webhook/reports/dailyDaily delivery-rollup rows (from webhook_delivery_daily) for the caller's subscription. Date-range filterable, 4-format export (JSON/CSV/TSV/Text).
GET/dashboard/api/webhook/reports/summaryAggregate delivery summary (delivery rate, asset coverage, average latency) over the selected date range.
POST/dashboard/api/webhook/reports/refreshStarts an on-demand rollup refresh outside the 15-minute cron cadence — asynchronous, returns 202 immediately. 3-day scan window, 60-second per-subscription cooldown.
GET/dashboard/api/webhook/reports/refresh/statusPoll for the outcome of a refresh started above (running, done, error, or not_found).
POST/dashboard/api/add-lrsPurchases the $20/mo LRS Reports add-on for an eligible account (Free/Pro/Enterprise, or a Webhook/Partner account without a tier-included grant). Creates the Stripe subscription, links to the account's existing Stripe customer via stripe_customer_xref. Replaced the old standalone Stripe Payment Link, now disabled — this is the sole purchase path.

The public-API mirror of the three webhook-reports endpoints lives at GET /webhook/reports/daily, /webhook/reports/summary, and POST /webhook/reports/refresh — same behavior, authenticated by API key rather than dashboard session, and (deliberately) not gated by LRS entitlement.

Dashboard Partner Endpoints (Bearer Token)

Partner connection management. Available to accounts with a Partner-tier key. No asset cap — partner access is unlimited by policy; the watchlist is purely organizational.

MethodPathDescription
GET/dashboard/api/partner/configCurrent partner configuration: api_key_id, tier, asset_count, assets (array). Returns 404 if the session email has no non-revoked partner key.
POST/dashboard/api/partner/assetsUpdate the partner's asset watchlist. Body: {"assets":["BTC","ETH","SOL",...]}. Deduplicates, normalizes to uppercase. Empty array is valid (clears watchlist — access unaffected). No maximum. Audit-logged.
GET/dashboard/api/partner/available-assetsFull pickable catalog (same as GET /asset-categories?full=1). Powers the Partner asset watchlist chip-picker.

Dashboard Admin Support Endpoints (Bearer Token + Admin Role)

MethodPathDescription
GET/dashboard/api/admin/support/ticketsList all tickets system-wide. Filters: ?status=, ?category=. Limit 200. Ordered by created_at DESC.
GET/dashboard/api/admin/support/tickets/{id}Full ticket detail by UUID — includes all replies (internal notes visible), customer IP, email.
POST/dashboard/api/admin/support/tickets/{id}/replyPost admin reply. Body: {"message":"...", "is_internal": false}. Translates + emails customer. Includes one-click resolve link.
POST/dashboard/api/admin/support/tickets/{id}/statusUpdate ticket status. Body: {"status":"open"}. Valid: new, open, in_progress, awaiting_customer, resolved, closed. Audit-logged.

Dashboard Admin Analytics Endpoints (Bearer Token + Admin Role)

MethodPathDescription
GET/dashboard/api/admin/analyticsUsage analytics from Aurora. ?mode=detail (default, paginated rows) or ?mode=summary (aggregates via a single GROUPING SETS query with p50/p95/p99 latency percentiles). Date filters: ?start_date=, ?end_date=. CSV export via ?format=csv.
GET/dashboard/api/admin/daily-rollupPer-day usage rollup served from the mv_usage_daily materialized view (fast, never scans usage_logs). ?days= (default 30, max 93). Returns totals, latency percentiles, cache-hit rate, cardinality, and a last_refreshed timestamp. NEW

8. UDP Protocol Access

Both LPO and LRS support UDP for low-latency, connectionless access. UDP is ideal for high-frequency price polling where occasional packet loss is acceptable.

LPO UDP — Price Queries UDP 2679

Send a UDP datagram to udp.cpmp-site.org:2679 with your API key and asset.

# Python UDP example
import socket, json

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
request = json.dumps({"api_key": "YOUR_API_KEY", "asset": "btc"}).encode()
sock.sendto(request, ("udp.cpmp-site.org", 2679))
data, addr = sock.recvfrom(4096)
print(json.loads(data))

The reply is JSON carrying the same 12-field envelope as the HTTP API:

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/price] [200]",
  "status_code": 200,
  "endpoint": "/price",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "YOUR_API_KEY",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbi/v1",
  "timestamp": "2026-07-30T19:22:14Z",
  "data": {
    "asset": "BTC",
    "price": 64762.93,
    "source": "coinbase-ws",
    "cached": true,
    "monthly_usage": 37862,
    "monthly_limit": 999999999,
    "usage_pct": 0.0,
    "usage_warning": ""
  },
  "error": ""
}

Note that usage_warning is an empty string rather than absent, and error is an empty string rather than omitted — the always-present rule applies over UDP exactly as it does over HTTP.

LRS UDP — Report Queries UDP 2680

Send a UDP datagram to udp.cpmp-site.org:2680 for usage reports and summaries.

# Python UDP example
import socket, json

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
request = json.dumps({"api_key": "YOUR_API_KEY", "type": "summary"}).encode()
sock.sendto(request, ("udp.cpmp-site.org", 2680))
data, addr = sock.recvfrom(65535)
print(json.loads(data))

9. Network & Connectivity

Access Methods

MethodUse CaseLatency
HTTPS (ALB)Standard API access from any client~50ms
UDP (NLB)High-frequency polling, low-latency price feeds~10ms
PrivateLink (NLB)Private VPC-to-VPC access, no public internet~5ms

PrivateLink Configuration

Enterprise subscribers can connect via AWS PrivateLink. Both LPO (TCP 8080) and LRS (TCP 9090) are available through the same PrivateLink endpoint. Create a VPC endpoint in your AWS account pointing to our PrivateLink service.

Data Sync

Usage logs are written to Aurora Serverless v2 by the LPO in real-time. A nightly sync job (1 AM EST) copies usage data from Aurora to ElastiCache for fast LRS report generation. Reports reflect data up to the previous day's sync.

10. Unified Response Envelope

Every HTTP endpoint in The Trinity Beast returns a consistent JSON envelope. This makes responses self-reporting — each response tells you which endpoint handled the request, which ECS container answered, and which AWS region served it.

Coverage is now universal. Three endpoints previously returned bare payloads outside the envelope: /search, /ws/assets, and /admin/partner-ws-stats. All three now return the standard 12-field envelope with their payload nested under data. There are zero remaining code paths that bypass the envelope.

Consumer impact: if you were reading response.results from /search, read response.data.results instead. The unwrap pattern is the same for every TBI endpoint — check status_code, throw on error, read the payload from data.

Search Response — Before and After

// BEFORE — bare payload (no longer returned)
{ "query": "webhook", "results": [ ... ], "count": 12, "elapsed": "3.1ms" }

// AFTER — standard envelope, payload under data
{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/search] [200]",
  "status_code": 200,
  "endpoint": "/search",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbi/v1",
  "timestamp": "2026-07-29T20:14:00Z",
  "data": {
    "query": "webhook",
    "results": [
      { "title": "API Reference", "section": "Webhook Push", "url": "/docs/...", "snippet": "...", "score": 27 }
    ],
    "count": 12,
    "elapsed": "3.1ms"
  },
  "error": ""
}

Success Envelope

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/health] [200]",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbts/v1",
  "status_code": 200,
  "endpoint": "/health",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "timestamp": "2026-04-26T16:20:03Z",
  "data": { "message": "Health Check - OK" }
}

Error Envelope

{
  "status": "🛑 [LPO] [us-east-2] [BeastMain] [/price] [401]",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbts/v1",
  "status_code": 401,
  "endpoint": "/price",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "timestamp": "2026-04-26T16:20:03Z",
  "error": "🛑 [LPO] [us-east-2] [BeastMain] [/price] [401] Missing required API key parameter"
}

Color-Coded Status Indicators

Every response uses a color-coded emoji indicator followed by bracketed origin metadata. This convention matches the usage logs, application logs, and subscriber-facing error messages — consistent across all messaging.

IndicatorColorMeaningHTTP Codes
✅GreenSuccess200, 201
🛑RedError400, 401, 403, 404, 429, 500, 503
⚠️YellowWarning / Redirect3xx
✅🔴Green + RedSuccess with critical warning (usage ≥ 95%)200 (with usage_warning: "red")
✅⚠️Green + YellowSuccess with caution warning (usage ≥ 90%)200 (with usage_warning: "yellow")
✅🟡Green + GoldSuccess with green warning (usage ≥ 85%)200 (with usage_warning: "green")
ℹ️BlueInformationalUsed in logs
🟠AmberDebugUsed in logs
ℹ️ Plain-Text Response Format: By default, all responses use the emoji status indicators shown above (the Trinity Beast Convention format). If your system does not render emoji well, you can request plain-text responses by submitting a support ticket through our Support page. Once enabled, all status indicators will use text equivalents: OK, OK - NOTE: usage above 85%, OK - WARNING: approaching monthly query limit, OK - CRITICAL: near monthly query limit, and ERROR. This setting applies to both LPO and LRS responses for your API key.

Bracket Convention

The bracket format self-reports the origin of every message at a glance:

INDICATOR [SERVICE] [REGION] [NODE] [ENDPOINT] [CODE] message

Example: 🛑 [LPO] [us-east-2] [BeastMirror] [/admin/config] [403] Invalid admin key

This convention is used consistently across:

Envelope Fields

FieldTypeDescription
statusstringColor-coded bracket line: ✅ [LPO] [us-east-2] [BeastMain] [/health] [200]. The leading indicator (✅/🛑/⚠️) switches to text (OK/ERROR/WARN) when the API key's response_format is set to "plain".
languagestringISO 639-1 language code declaring the language of all human-readable content in this response (e.g., "en", "ja", "es"). Determined by the user's api_lang setting. Default: "en".
status_codeintHTTP status code as an integer for programmatic parsing
endpointstringThe URL path that handled the request (self-reporting)
cluster_nodestringWhich ECS container answered: BeastMain, BeastMirror, or BeastLRS
regionstringAWS region — us-east-2
api_key_idstringWhich API key made the call — who asked. Empty string on unauthenticated requests, never absent.
ip_addressstringThe client IP the request arrived from — from where. Empty string when the request was rejected before IP resolution (for example a UDP parse-level error), never absent.
agent_profile_arnstringThe addressable identity of the actor that produced this response — who answered. Format: arn:tbi:{region}:{account}:agent-profile/{name}/{version}. Never empty.
timestampstringRFC 3339 UTC timestamp of when the response was generated
dataobjectThe response payload. Always present. On errors it is null — never omitted.
errorstringFull bracketed error message with indicator. Always present. On success it is an empty string — never omitted.
⚠️ Every field is always present — do not code around missing fields. All 12 fields are serialized on every response, success or error. Zero values ("", 0, false, [], {}, null) are valid and are still emitted. A success response and an error response have exactly the same shape and can be parsed with the same struct or type definition.

This means you never need presence checks, optional chaining, or fallbacks like data.data || data. Check status_code, throw on a non-empty error, and read your payload from data. The shape is the contract.
⚠️ Full UME Compliance: As of May 2026, the /price endpoint uses the standard 12-field UME envelope. Price data is nested inside the data field. All endpoints — including /price — return the same envelope shape.
ℹ️ UDP Note: UDP responses are JSON and carry the same full 12-field envelope as HTTP — parse them exactly the same way. Both UDP listeners (2679 for prices, 2680 for reports) are UME-compliant, including the bracketed status line.

The UDP path is heavily optimized — responses are assembled into pooled, pre-serialized buffers to avoid per-request allocation — but that is an implementation detail of how the JSON is built, not a different wire format. There is no binary protocol.

Diagram 10.1 — Request Flow & Response Envelope

Diagram 10.1 — Request Flow & Response Envelope
flowchart LR
    A["Client Request"] --> B["⚖️ ALB / NLB"]
    B --> C{"ECS Container"}
    C --> D["BeastMain"]
    C --> E["BeastMirror"]
    C --> F["BeastLRS"]
    D --> G["WriteJSON / WriteError"]
    E --> G
    F --> G
    G --> H["Unified Envelope"]
    H --> I["status: ok | error"]
    H --> J["endpoint: /path"]
    H --> K["cluster_node: BeastX"]
    H --> L["region: us-east-2"]
    H --> M["timestamp: RFC3339"]
    H --> N["data / error payload"]

    style A fill:#334155,stroke:#475569,color:#cbd5e1
    style B fill:#334155,stroke:#475569,color:#cbd5e1
    style C fill:#2d4a5e,stroke:#475569,color:#cbd5e1
    style D fill:#2d4a5e,stroke:#475569,color:#cbd5e1
    style E fill:#2d4a5e,stroke:#475569,color:#cbd5e1
    style F fill:#2d4a5e,stroke:#475569,color:#cbd5e1
    style G fill:#3d3455,stroke:#475569,color:#cbd5e1
    style H fill:#FF9900,stroke:#cc7a00,color:#0f172a
    style I fill:#1e293b,stroke:#475569,color:#94a3b8
    style J fill:#1e293b,stroke:#475569,color:#94a3b8
    style K fill:#1e293b,stroke:#475569,color:#94a3b8
    style L fill:#1e293b,stroke:#475569,color:#94a3b8
    style M fill:#1e293b,stroke:#475569,color:#94a3b8
    style N fill:#1e293b,stroke:#475569,color:#94a3b8
        

11. Complete Endpoint Inventory

Comprehensive inventory of every registered route across The Trinity Beast services.

50+
Total Endpoints
21
Public
35+
Admin
TCP, UDP
Protocols
LPO, LRS
Services
MethodPathAuthServiceCategoryDescription
🏥 Health & Status
GET/healthNoneLPOHealthHealth check
GET/healthNoneLRSHealthHealth check
Price
GET/priceBearerLPOPriceFetch real-time crypto price
Reports
GET/reports/usageBearerLRSReportsUsage detail report
GET/reports/summaryBearerLRSReportsUsage summary report
GET/reports/report-usageBearerLRSReportsReport access detail
GET/reports/report-summaryBearerLRSReportsReport access summary
Public
GET/exchangesNoneLPOExchange MgmtExchange asset list
GET/asset-categoriesNoneLPOExchange MgmtAsset categories for demo dropdown
GET/map/pinsNoneLPOSystemMission impact map pins
POST/translateNoneLPOTranslationReal-time text translation via AWS Translate (ElastiCache cached)
GET/searchNoneLPOSearchFull-text document search — query + lang params, returns ranked results with snippets
GET/ws/assetsNoneLPOPartnersPartner WebSocket discovery — available assets, connection URL, subscribe protocol
POST/translate/signupNoneLPOTranslationTBTS self-service signup — creates translation key + Stripe customer, sends welcome email
GET/translate/languagesNoneLPOTranslationLive 127-language catalog, quote limits and alternate codes (see 15.12)
GET/translate/modelsNoneLPOTranslationThe six translation agents with our AI cost per agent (see 15.1)
POST/support/submitNoneLPOSupportSubmit support ticket
GET/support/ticketsNoneLPOSupportList tickets by email
GET/support/ticket/{ticket_number}NoneLPOSupportView ticket detail
POST/demo/registerNoneLPOSystemDemo lead registration
POST/partner/applyNoneLPOPartnersPartner application
GET/partner/statusNoneLPOPartnersPartner application status
POST/newsletter/subscribeNoneLPONewsletterNewsletter subscribe
GET/newsletter/unsubscribeNoneLPONewsletterNewsletter unsubscribe
GET/newsletter/resubscribeNoneLPONewsletterNewsletter resubscribe
FieldTypeDescription
Admin — System
GET/admin/configAdmin KeyLPOSystemView application parameters
GET/admin/setup-demoAdmin KeyLPOSystemSetup demo API key
GET/admin/invalidate-keyAdmin KeyBothSystemInvalidate API key cache
GET/admin/demo-modeAdmin KeyLPOSystemToggle demo/performance mode
GET/admin/system-modeAdmin KeyLPOSystemSwitch system profile
GET/admin/reload-paramsAdmin KeyLPOSystemForce parameter reload
GET/admin/profilesAdmin KeyLPOSystemList system profiles
MethodPathAuthServiceCategoryDescription
📈 Admin — Metrics & Monitoring
GET/admin/stress-statsAdmin KeyLPOMetricsReal-time 24-counter metrics
GET/admin/stress-resetAdmin KeyLPOMetricsReset metrics counters
GET/admin/cluster-statsAdmin KeyLPOMetricsCluster-wide aggregated metrics
GET/admin/feed-statusAdmin KeyLPOMetricsPrice feed status (all 6 exchanges)
GET/admin/usage-analyticsAdmin KeyLPOMetricsUsage log analytics
GET/admin/adaptive-statsAdmin KeyLPOMetricsAdaptive governor stats
GET/admin/db-insightsAdmin KeyLPOMetricsPostgreSQL self-observability — cache hit ratio, top queries, scan health
POST/admin/db-insights/resetAdmin KeyLPOMetricsReset pg_stat_statements baseline for a clean measurement window
MethodPathAuthServiceCategoryDescription
🔄 Admin — Exchange Management
GET/admin/exchange-feedsAdmin KeyLPOExchange MgmtList exchange configurations
POST/admin/exchange-feeds/saveAdmin KeyLPOExchange MgmtCreate/update exchange
POST/admin/exchange-feeds/toggleAdmin KeyLPOExchange MgmtEnable/disable exchange
DELETE/admin/exchange-feedsAdmin KeyLPOExchange MgmtRemove exchange
GET/admin/exchange-assetsAdmin KeyLPOExchange MgmtList asset mappings
POST/admin/exchange-assets/saveAdmin KeyLPOExchange MgmtCreate/update asset mappings
DELETE/admin/exchange-assetsAdmin KeyLPOExchange MgmtRemove asset mappings
MethodPathAuthServiceCategoryDescription
🎫 Admin — Support
GET/support/admin/statsAdmin KeyLPOSupportTicket statistics
GET/support/admin/ticketsAdmin KeyLPOSupportList all tickets
GET/support/admin/tickets/{id}Admin KeyLPOSupportView ticket detail
POST/support/admin/tickets/{id}Admin KeyLPOSupportUpdate ticket
POST/support/admin/tickets/{id}/replyAdmin KeyLPOSupportAdd reply
🤝 Admin — Partners
GET/admin/partner-applicationsAdmin KeyLPOPartnersList applications
POST/admin/partner-applications/{id}/statusAdmin KeyLPOPartnersUpdate status
📰 Admin — Newsletter
GET/admin/newsletter/templatesAdmin KeyLPONewsletterList templates
GET/admin/newsletter/templates/{id}Admin KeyLPONewsletterGet template
POST/admin/newsletter/templatesAdmin KeyLPONewsletterSave template
DELETE/admin/newsletter/templates/{id}Admin KeyLPONewsletterDelete template
GET/admin/newsletter/newslettersAdmin KeyLPONewsletterList newsletters
GET/admin/newsletter/newsletters/{id}Admin KeyLPONewsletterGet newsletter
POST/admin/newsletter/newslettersAdmin KeyLPONewsletterSave newsletter
POST/admin/newsletter/newsletters/{id}/statusAdmin KeyLPONewsletterToggle status
POST/admin/newsletter/newsletters/{id}/sendAdmin KeyLPONewsletterSend newsletter
GET/admin/newsletter/subscribersAdmin KeyLPONewsletterList subscribers
✉️ Admin — Email
GET/admin/email/templatesAdmin KeyLPOEmailList email templates
GET/admin/email/templates/{id}Admin KeyLPOEmailGet template
POST/admin/email/templatesAdmin KeyLPOEmailSave template
DELETE/admin/email/templates/{id}Admin KeyLPOEmailDelete template
GET/admin/email/sendersAdmin KeyLPOEmailList senders
POST/admin/email/sendersAdmin KeyLPOEmailSave sender
DELETE/admin/email/senders/{id}Admin KeyLPOEmailDelete sender
GET/admin/email/draftsAdmin KeyLPOEmailList drafts
GET/admin/email/drafts/{id}Admin KeyLPOEmailGet draft
POST/admin/email/draftsAdmin KeyLPOEmailSave draft
DELETE/admin/email/drafts/{id}Admin KeyLPOEmailDelete draft
POST/admin/email/drafts/{id}/sendAdmin KeyLPOEmailSend email
💳 Checkout & Payments
GET/checkoutNoneLPOCheckoutRedirect to Stripe Payment Link
GET/admin/payment-linksAdmin KeyLPOCheckoutList payment links
POST/admin/payment-links/saveAdmin KeyLPOCheckoutCreate/update payment link
POST/admin/payment-links/toggleAdmin KeyLPOCheckoutEnable/disable payment link
🗄️ Admin — Database & Cache
POST/admin/sqlAdmin KeyLPODatabaseExecute SQL (read/write)
POST/admin/sql-batchAdmin KeyLPODatabaseExecute SQL batch (DDL allowed)
POST/admin/valkeyAdmin KeyLPOCacheExecute ElastiCache commands
UDP Protocol
UDP:2679Bearer (in JSON body)LPOPricePrice queries
UDP:2680Bearer (in JSON body)LRSReportsReport queries

12. Public Infrastructure Endpoints

These endpoints expose operational metrics for the public Infrastructure Live page. No authentication required. Safe for public consumption — sensitive internals (DB pools, internal IPs, config) are stripped.

GET /public/status GET

Returns a curated system status snapshot — node health, feed connections, uptime, and cache hit rate. Designed for the public dashboard. Reads from ElastiCache for sub-millisecond response.

Cache: public, max-age=3

Response Fields

FieldTypeDescription
statusstring"operational" when all systems healthy
system.nodesintTotal expected nodes (4)
system.nodes_healthyintNodes currently reporting
system.node_names[]stringNames of healthy nodes (BeastMain, BeastMirror, BeastLRS, BeastWebhook)
system.uptimestringHuman-readable uptime (e.g., "4d 12h")
system.cache_hit_pctfloatCluster-wide cache hit percentage
feeds[]arrayPer-exchange WebSocket feed status
feeds[].exchangestringExchange name (e.g., "Coinbase")
feeds[].connectedboolWebSocket connection active
feeds[].assetsintNumber of assets cached from this feed

Example

curl https://api.cpmp-site.org/public/status
GET /public/cluster GET NEW

Returns per-node ECS cluster metrics — the same operational data as /admin/cluster-stats but stripped of sensitive internals. Reads from ElastiCache (each container publishes its stats every 3 seconds).

Cache: public, max-age=5

Response Fields

FieldTypeDescription
statusstring"ok"
nodes_foundintNodes currently reporting
nodes_expectedintExpected node count (4)
regionstringAWS region (us-east-2)
nodes[]arrayPer-node metrics
nodes[].namestringNode name (BeastMain, BeastMirror, BeastLRS, BeastWebhook)
nodes[].rolestring"LPO + LRS" or "Push Delivery"
nodes[].uptime_secondsfloatSeconds since container start
nodes[].tcp_requestsintTotal TCP requests served
nodes[].udp_requestsintTotal UDP requests served
nodes[].lrs_requestsintTotal LRS report requests
nodes[].syncmap_hit_pctfloatLocal sync.Map cache hit rate (%)
nodes[].cache_hit_pctfloatCombined cache hit rate (sync.Map + ElastiCache) (%)
nodes[].errors_5xxintServer errors since start
nodes[].errors_4xxintClient errors since start
nodes[].rate_limit_hitsintRate limit rejections since start
aggregatedobjectCluster-wide totals (same fields summed across all nodes)

Example

curl https://api.cpmp-site.org/public/cluster
{
  "status": "ok",
  "nodes_found": 4,
  "nodes_expected": 4,
  "region": "us-east-2",
  "nodes": [
    {
      "name": "BeastMain",
      "role": "LPO + LRS",
      "uptime_seconds": 86412.3,
      "tcp_requests": 1247,
      "udp_requests": 0,
      "lrs_requests": 83,
      "syncmap_hit_pct": 99.2,
      "cache_hit_pct": 99.8,
      "errors_5xx": 0,
      "errors_4xx": 12,
      "rate_limit_hits": 0
    }
  ],
  "aggregated": {
    "tcp_requests": 3891,
    "udp_requests": 0,
    "lrs_requests": 249,
    "syncmap_hit_pct": 99.1,
    "cache_hit_pct": 99.7,
    "rate_limit_hits": 0,
    "errors_5xx": 0,
    "errors_4xx": 34,
    "bg_work_dropped": 0
  }
}
GET /public/infrastructure GET

Returns infrastructure-wide operational metrics aggregated from CloudWatch, Valkey, AWS ECS, and other AWS service APIs. Powers the Infrastructure Live page. The aggregated payload is cached in Valkey for 60 seconds; the standard response envelope is rebuilt on every request, so cache hits and misses are identical in shape.

Cache: public, max-age=60 (Valkey payload: 60s)

Envelope: Like every TBI response, this endpoint returns the standard 12-field Unified Messaging Envelope (UME). The fields documented below are the contents of the data object. Because this is a public, no-auth endpoint, api_key_id is "" (empty string, always present — never omitted).

Response Fields (data object)

FieldTypeDescription
cloudfront.requests_24hintCloudFront requests in last 24h
cloudfront.bytes_24hintBytes downloaded in last 24h
cloudfront.bytes_humanstringHuman-readable bytes (e.g., "1.4 GB")
waf.alb_blocked_24hintALB WAF blocked requests (24h)
waf.alb_allowed_24hintALB WAF allowed requests (24h)
waf.cf_blocked_24hintCloudFront WAF blocked requests (24h)
sqs.visibleintMessages visible in usage log queue
sqs.in_flightintMessages currently being processed
lambda.receiptobjectReceipt Lambda stats (invocations_24h, errors_24h)
lambda.writerobjectQueued Writer Lambda stats
sync.last_runstringLast nightly sync timestamp
sync.statusstring"success" or "failed"
sync.durationstringSync execution time
sync.search_indexobjectSearch index stats (languages, sections)
guardduty.active_findingsintActive GuardDuty findings
alarms.totalintTotal CloudWatch alarms configured
alarms.alarmintAlarms currently in ALARM state
honeypot.total_hitsintTotal honeypot trap hits
honeypot.auto_blockedintIPs auto-blocked by honeypot
cache.enginestring"valkey"
cache.versionstringValkey version (e.g., "7.2.4")
cache.itemsintTotal keys in Valkey (DBSIZE)
cache.hit_ratefloatAll-time cache hit rate (%)
cache.memory_usedstringHuman-readable memory usage (e.g., "3.03G")
autoops.lambdasobjectPer-function invocation counts (24h)
autoops.step_functionsobjectStep Function execution counts (24h)
autoops.self_heal_countintSelf-heal actions this month
autoops.last_threatstringCurrent threat level (NONE, LOW, MEDIUM, HIGH, CRITICAL)
services.clusterstringECS cluster name
services.total_desiredintTotal desired task count across all 4 services
services.total_runningintTotal running task count across all 4 services
services.total_pendingintTotal pending task count (non-zero during deployments)
services.all_healthybooltrue when all services are ACTIVE with running == desired and pending == 0
services.services[]arrayPer-service breakdown — name, status, desired, running, pending, healthy, task_family
translation.mtd_spend_usdfloatMonth-to-date Bedrock spend (NEW — added alongside the daily figures, since the daily counters reset every 24h and previously gave no month-level view)
translation.mtd_infra_usdfloatMonth-to-date infra cost attributed to translation NEW
translation.mtd_cost_usdfloatMonth-to-date total cost (spend + infra) NEW
translation.mtd_input_tokensintMonth-to-date input tokens consumed NEW
translation.mtd_output_tokensintMonth-to-date output tokens produced NEW
translation.daily_spend_usd / daily_infra_usd / daily_cost_usdfloatToday's equivalents, reset every 24h against the $600/day cap
translation.daily_input_tokens / daily_output_tokensintToday's token counts, reset every 24h against the 50M/day cap
translation.active_jobs / queue_depthintCurrently running jobs and SQS queue depth
translation.cost_per_pair_usdfloatLive average cost per language pair
webhook_engine.delivery_activeboolWhether the outbound Webhook Push delivery engine is running NEW
webhook_engine.test_receiverobjectHealth of the inbound test receiver (added 2026-08-07) — enabled, plus per-transport https/udp objects each with host, port, target_group, healthy_hosts, healthy NEW

Example Request

curl https://api.cpmp-site.org/public/infrastructure

Example Response

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/public/infrastructure] [200]",
  "status_code": 200,
  "endpoint": "/public/infrastructure",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbi/v1",
  "timestamp": "2026-05-30T17:00:00Z",
  "data": {
    "cloudfront": { "requests_24h": 1095, "bytes_24h": 17114513, "bytes_human": "16 MB" },
    "waf": { "alb_blocked_24h": 1186, "alb_allowed_24h": 3302, "cf_blocked_24h": 0 },
    "sqs": { "visible": 0, "in_flight": 0 },
    "lambda": {
      "receipt": { "state": "Active", "last_deployed": "", "invocations_24h": 0, "errors_24h": 0 },
      "writer": { "state": "Active", "last_deployed": "", "invocations_24h": 141, "errors_24h": 0 }
    },
    "sync": {
      "last_run": "May 30 01:00AM",
      "status": "success",
      "duration": "783.351423ms",
      "new_logs": "154 new logs loaded",
      "search_index": { "languages": 12, "sections": 3150 }
    },
    "guardduty": { "active_findings": 0, "status": "active" },
    "alarms": { "total": 17, "ok": 16, "alarm": 1, "insufficient_data": 0 },
    "honeypot": { "active": true, "endpoints": 12, "total_hits": 22, "auto_blocked": 77, "pending_queue": 0 },
    "autoops": {
      "active": true,
      "lambdas": {
        "notify_invocations_24h": 14,
        "self_heal_invocations_24h": 61,
        "waf_action_invocations_24h": 3,
        "honeypot_processor_invocations_24h": 288,
        "bedrock_analyze_invocations_24h": 288,
        "rhema_support_invocations_24h": 11,
        "digest_invocations_24h": 1,
        "total_invocations_24h": 666,
        "total_errors_24h": 0
      },
      "step_functions": { "executions_24h": 62, "succeeded_24h": 62, "failed_24h": 0 },
      "eventbridge": {
        "rules": [
          { "name": "tbi-ops-alarm-trigger", "schedule": "", "pattern": "CloudWatch alarm → ALARM", "target": "Step Function: health-check-heal" },
          { "name": "tbi-ops-honeypot-queue-processor", "schedule": "rate(5 minutes)", "pattern": "", "target": "Lambda: honeypot-processor" }
        ],
        "total": 6
      },
      "waf_blocked_by_ops": 0,
      "self_heal_count": 0,
      "last_threat": "LOW"
    },
    "cache": {
      "engine": "valkey",
      "version": "7.2.6",
      "items": 38675,
      "hit_rate": 65.7,
      "memory_used": "1.42G",
      "memory_percent": 0.0,
      "cpu_percent": 3.0
    },
    "services": {
      "cluster": "trinity-beast-fargate-cluster",
      "total_desired": 4,
      "total_running": 4,
      "total_pending": 0,
      "all_healthy": true,
      "services": [
        { "name": "trinity-beast-main-service", "status": "ACTIVE", "desired": 1, "running": 1, "pending": 0, "healthy": true, "task_family": "trinity-beast-main-task" }
      ]
    },
    "translation": {
      "active": true,
      "daily_spend_usd": 0,
      "daily_limit_usd": 600,
      "active_jobs": 0,
      "queue_depth": 0,
      "cost_per_pair_usd": 1.65,
      "daily_input_tokens": 0,
      "daily_output_tokens": 0,
      "daily_token_limit": 50000000
    }
  },
  "error": ""
}

The services.services[] array contains one object per Fargate service (4 total — abbreviated to one above). Each eventbridge.rules[] entry always carries both schedule and pattern; the field that does not apply to a given rule is an empty string, never omitted — consistent with the UME "no omitempty" rule.

13. Admin Endpoints — Full Reference

All admin endpoints require the X-Admin-Key header. The admin key is provisioned in AWS Secrets Manager (trinity-beast-admin-secrets) and nowhere else. If no key is loaded, every admin endpoint refuses the request.

Admin Authentication Required on all admin requests
X-Admin-Key: YOUR_ADMIN_KEY

System Management

Endpoints for managing application configuration, demo mode, system profiles, and parameter reloading.

MethodPathDescription
GET/admin/configView all application parameters and their current values
GET/admin/setup-demoCreate or reset the demo API key for the live demo page
GET/admin/invalidate-keyInvalidate a cached API key in ElastiCache (forces re-fetch from Aurora)
GET/admin/demo-modeToggle between demo mode (synthetic prices) and performance mode (live feeds)
GET/admin/system-modeSwitch the active system profile (e.g., production, maintenance, demo)
GET/admin/reload-paramsForce an immediate reload of application parameters from the database
GET/admin/profilesList all available system profiles and their configurations

Metrics & Monitoring

Real-time observability into The Trinity Beast runtime performance, price feed health, and adaptive governor behavior.

MethodPathDescription
GET/admin/stress-stats24-counter real-time metrics: requests, errors, cache hits, latency percentiles, goroutines, memory
GET/admin/stress-resetReset all runtime metrics counters to zero
GET/admin/cluster-statsAggregated metrics across all ECS containers in the cluster
GET/admin/feed-statusWebSocket and REST feed status for all 6 exchanges (Coinbase, Bitstamp, Kraken, Gate.io, Crypto.com, OKX)
GET/admin/usage-analyticsAggregated usage log analytics with breakdowns by asset, source, and time period
GET/admin/adaptive-statsAdaptive governor statistics — current thresholds, adjustment history, and feed health scores
GET/admin/db-insightsPostgreSQL self-observability — buffer-cache hit ratio, top queries by total time and call count (from pg_stat_statements), and per-table sequential-vs-index scan health
POST/admin/db-insights/resetReset the pg_stat_statements baseline so a clean measurement window can begin (e.g., before/after an optimization A/B)

Exchange Management

Full CRUD for exchange feed configurations and asset symbol mappings. Changes take effect immediately — no restart required.

MethodPathDescription
GET/admin/exchange-feedsList all exchange configurations with connection status
POST/admin/exchange-feeds/saveCreate or update an exchange configuration
POST/admin/exchange-feeds/toggleEnable or disable an exchange without removing its configuration
DELETE/admin/exchange-feedsPermanently remove an exchange configuration
GET/admin/exchange-assetsList all asset symbol mappings across exchanges. Returns id, asset, exchange_name, exchange_symbol, enabled, prewarm, category, icon_url. Optional ?exchange=coinbase filter.
POST/admin/exchange-assets/saveCreate or update asset symbol mappings for an exchange. Accepts enabled, prewarm, and category per asset.
POST/admin/exchange-assets/toggleFlip enabled and/or prewarm on a single asset row. Only the fields provided are updated. Body: {"exchange_name":"okx","asset":"BANANA","prewarm":false}
DELETE/admin/exchange-assetsRemove asset symbol mappings. ?exchange=bitstamp&asset=APE removes one; ?exchange=bitstamp removes all for that exchange.
POST/admin/eam/syncSync EAM → application_parameters. Writes per-exchange CSV params and master prewarm_assets_list from all enabled=true, prewarm=true rows. Follow with /admin/force-deploy-params to activate.
GET/admin/eam/auditStart a 4-layer prewarm audit in the background (async). Returns 202 immediately. Layers: live exchange API prices, EAM ↔ param drift, master list parity, LPO smoke test. Results cached in Valkey 24h.
GET/admin/eam/audit?poll=1Poll for audit results. Returns {"ready":false} while running, {"ready":true,"result":{...}} when complete.
POST/admin/eam/discoverStart async discovery of uncovered assets for an exchange, ranked by 24h volume. Body: {"exchange":"okx","limit":18}. Returns 202 immediately. Results cached in Valkey 10 min.
GET/admin/eam/discover?exchange=okx&poll=1Poll for discovery results. Each candidate includes asset, exchange_symbol, price, volume_24h, category (auto-assigned via CoinGecko), and icon_url. Result also contains a grouped object (candidates keyed by category), covered_total, exchange_total, uncovered, and scored.

GET /asset-categories — Response Shape

Public endpoint, no auth. Returns all enabled assets grouped by category with icon URLs. Cache: 5 min.

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/asset-categories] [200]",
  "status_code": 200,
  "data": {
    "categories": [
      {
        "name": "Major Currencies",
        "description": "Top market cap assets",
        "display_order": 1,
        "assets": ["BTC", "ETH", "SOL", "XRP", "ADA"],
        "icons": {
          "BTC": "https://coin-images.coingecko.com/coins/images/1/large/bitcoin.png",
          "ETH": "https://coin-images.coingecko.com/coins/images/279/large/ethereum.png",
          "SOL": "https://coin-images.coingecko.com/coins/images/4128/large/solana.png"
        }
      },
      {
        "name": "DeFi",
        "description": "Decentralized finance protocols",
        "display_order": 2,
        "assets": ["UNI", "AAVE", "MKR", "CRV"],
        "icons": {
          "UNI": "https://coin-images.coingecko.com/coins/images/12504/large/uni.png",
          "AAVE": "https://coin-images.coingecko.com/coins/images/12645/large/aave.png"
        }
      }
    ],
    "total_assets": 357
  },
  "error": ""
}
Icons coverage: The icons map only contains entries for assets that have an icon_url in EAM (~78% coverage from CoinGecko top 750). Assets without an icon are still listed in the assets array — the frontend falls back to rotating geometric SVGs (gold/blue/purple).

GET /asset-categories?full=1 — Uncapped Mode

By default each category is trimmed to 9 assets (ranked by 30-day request volume from usage_logs, alphabetical tiebreak) to keep the demo dropdown compact. Passing full=1 skips the trim entirely and returns every enabled asset in every category — the mode the Webhook and Partner dashboard asset pickers use, since a Professional or Enterprise subscriber needs to see the whole pool, not just the top 9 per category. Cache is 15 minutes in this mode (vs 5 minutes capped) since the full response is larger and changes less often.

GET /asset-names — Response Shape

Public endpoint, no auth. Returns the human-readable display name for every asset symbol, sourced from exchange_asset_map.display_name (backfilled from CoinGecko). Used by the dashboard asset pickers to show a name on hover (e.g. hovering BTC shows "Bitcoin").

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/asset-names] [200]",
  "status_code": 200,
  "data": {
    "names": {
      "BTC": "Bitcoin",
      "ETH": "Ethereum",
      "SOL": "Solana"
    }
  },
  "error": ""
}

POST /admin/eam/discover — Response Shape (poll result)

{
  "ready": true,
  "result": {
    "exchange": "okx",
    "candidates": [
      {
        "asset": "MON",
        "exchange_symbol": "MON-USDT",
        "price": 0.0237,
        "volume_24h": 21862.50,
        "exchange": "okx",
        "category": "Meme Coins",
        "icon_url": "https://coin-images.coingecko.com/coins/images/..."
      }
    ],
    "grouped": {
      "Meme Coins": [{"asset": "MON", "...": "..."}],
      "DeFi": [{"asset": "SPELL", "...": "..."}],
      "Infrastructure": [{"asset": "FLUX", "...": "..."}]
    },
    "covered_total": 171,
    "exchange_total": 400,
    "uncovered": 229,
    "scored": 40
  }
}

Database & Cache

Direct database and cache access for administrative operations. These endpoints execute commands against Aurora Serverless v2 and ElastiCache.

MethodPathDescription
POST/admin/sqlExecute a single SQL statement (SELECT, INSERT, UPDATE, DELETE)
POST/admin/sql-batchExecute multiple SQL statements in sequence (DDL allowed — CREATE TABLE, ALTER, etc.)
POST/admin/valkeyExecute ElastiCache (Valkey) commands — GET, SET, DEL, KEYS, HGETALL, etc.
⚠️ Caution: The /admin/sql, /admin/sql-batch, and /admin/valkey endpoints execute commands directly against production databases. Use with care.
Parameterized queries on /admin/sql: The endpoint accepts an optional args array for safe parameter binding. When present, the query uses PostgreSQL placeholders ($1, $2, …) bound to the array values instead of string interpolation. The parameter is backward-compatible — omitting args preserves the exact prior behavior.
{
  "query": "SELECT COUNT(*) AS count FROM usage_logs WHERE asset = $1",
  "args": ["BTC"],
  "mode": "read"
}

Support Ticket Administration

MethodPathDescription
GET/support/admin/statsTicket statistics — open, closed, average response time
GET/support/admin/ticketsList all support tickets with filtering and pagination
GET/support/admin/tickets/{id}View full ticket detail including all replies
POST/support/admin/tickets/{id}Update ticket status, priority, or assignment
POST/support/admin/tickets/{id}/replyAdd an admin reply to a ticket (triggers email notification via SES)

Partner Administration

MethodPathDescription
GET/admin/partner-applicationsList all partner applications with status filtering
POST/admin/partner-applications/{id}/statusApprove, reject, or request more info on a partner application

Newsletter Administration

Full newsletter lifecycle management — templates, composition, subscriber management, and sending via SES.

MethodPathDescription
GET/admin/newsletter/templatesList all newsletter templates
GET/admin/newsletter/templates/{id}Get a specific template by ID
POST/admin/newsletter/templatesCreate or update a newsletter template
DELETE/admin/newsletter/templates/{id}Delete a newsletter template
GET/admin/newsletter/newslettersList all newsletters with status
GET/admin/newsletter/newsletters/{id}Get a specific newsletter by ID
POST/admin/newsletter/newslettersCreate or update a newsletter
POST/admin/newsletter/newsletters/{id}/statusToggle newsletter active/inactive status
POST/admin/newsletter/newsletters/{id}/sendSend newsletter to all active subscribers via SES
GET/admin/newsletter/subscribersList all newsletter subscribers with status

Email Administration

Direct email composition and sending — templates, verified senders, draft management, and delivery via SES.

MethodPathDescription
GET/admin/email/templatesList all email templates
GET/admin/email/templates/{id}Get a specific email template
POST/admin/email/templatesCreate or update an email template
DELETE/admin/email/templates/{id}Delete an email template
GET/admin/email/sendersList verified sender identities
POST/admin/email/sendersAdd or update a sender identity
DELETE/admin/email/senders/{id}Remove a sender identity
GET/admin/email/draftsList all email drafts
GET/admin/email/drafts/{id}Get a specific draft
POST/admin/email/draftsCreate or update an email draft
DELETE/admin/email/drafts/{id}Delete an email draft
POST/admin/email/drafts/{id}/sendSend an email draft via SES

Email Testing

Diagnostic endpoints for verifying email rendering without triggering real payment processing or translation jobs.

MethodPathDescription
POST/admin/test-refund-emailSend a test refund confirmation email with dummy data. Supports type: translation or subscription. Supports lang: en, es, pt, fr, de, ru, hi, ur, ar, ja, zh, it.
POST/admin/test-finalize-emailSend a test translation completion notification email with mock data. Verifies the per-language cost breakdown table (sqsLangDetails) renders correctly. Three scenarios: succeeded, partial, failed. No Bedrock calls, no translation jobs — SES only.

POST /admin/test-refund-email

Request Body
{
  "email": "recipient@example.com",
  "lang": "ur",
  "type": "translation"
}
Response
{
  "status": "sent",
  "email": "recipient@example.com",
  "lang": "ur",
  "type": "translation",
  "subject": "رقم کی واپسی کی تصدیق — Trinity-Beast-API-Reference.html"
}

POST /admin/test-finalize-email

Sends a mock translation completion email with realistic dummy data. The email includes the full stats bar (documents, languages, succeeded/failed counts, duration, cost/fee/total, state badge) and the Cost by Language table showing per-language Bedrock token usage and cost. Subject line is prefixed with [TEST] and a purple banner at the top makes it immediately clear this is not a real alert.

Request Body
{
  "email": "recipient@example.com",
  "scenario": "succeeded"
}

// scenario values:
// "succeeded" — 2 docs × 11 langs, all 22 pairs succeeded, full cost breakdown table
// "partial"   — 2 docs × 11 langs, 16/22 pairs succeeded, 3 failed pairs listed, partial cost table
// "failed"    — 1 doc × 3 langs, all failed, no cost table, CRITICAL badge
Response
{
  "status": "sent",
  "email": "recipient@example.com",
  "scenario": "partial",
  "subject": "[TEST] Translation Complete: 2 docs × 11 langs — 16/22 pairs PARTIAL"
}

13b. Translation Engine Admin Endpoints

Internal admin endpoints for the document translation pipeline. All require the X-Admin-Key header. Full documentation: TBI Translation Engine.

Job Submission

MethodPathDescription
POST/admin/translateSubmit a translation job. Accepts docs (array, max 42; more answers 400 translate_max_docs), langs ("all" for the internal 11, or an array of up to 12 codes, each resolved through the catalog and its alternate codes and de-duplicated; an unknown code answers 400 translate_langs_invalid), model, options (force, delta, skip_search_rebuild, skip_validation), protected_terms. Enforces $600/day dollar cap and 50M token/day cap. Returns job_id.

Monitoring

MethodPathDescription
GET/admin/translate/status/{job_id}Full job state — Valkey primary (live progress), Aurora fallback (completed jobs)
GET/admin/translate/queueAll active and queued jobs
GET/admin/translate/historyLast 50 completed jobs from Aurora
GET/admin/translate/healthSystem health — queue depth, active jobs, daily spend, daily token counts (input/output/limit), last completed job

Control

MethodPathDescription
POST/admin/translate/cancel/{job_id}Stop any running job. Each language finishes the section it is on and stops there, so every page already delivered stays live and nothing is left half-written. The same cancel path the customer uses (see 15.7)
POST/admin/translate/refund-approve/{quote_id}Issue the refund calculated for a customer-cancelled quote. Moves refund_status from pending_approval to approved, and claims the quote first so a second click can never refund twice (see 15.7)
POST/admin/translate/retry-failed/{job_id}Re-submit only the failed pairs from a previous job, on the same model as the original job. On a customer job it goes through the same one-free-retry claim as the customer (15.10), so it can never reopen a pair that was already refunded
POST/admin/translate/refund-sweepRun the failed-pair refund sweeper now instead of waiting for its 15-minute tick. Returns {opened, closed_none, due, issued, failed, errors}. 409 translate_refund_sweep_busy when another container is sweeping; 503 translate_refund_sweep_failed when the sweep couldn't run

Cost Protection — 429 Responses

The submit handler enforces two independent daily caps. Either can trigger a 429 on /admin/translate. For customer accepts (15.5, 15.6), the kill switch and both caps are also checked before the charge, and a tripped guard answers 503 tbts_service_paused with nothing charged, so no customer ever pays for a job a guard would stop:

CapValkey KeyLimitReset
Dollar capautoops:bedrock:spend:daily$600/day24h TTL auto-reset
Token capautoops:bedrock:tokens:input:daily + autoops:bedrock:tokens:output:daily50M combined tokens/day24h TTL auto-reset

Emergency kill switch: SET autoops:bedrock:kill 1 in Valkey — halts all translation operations immediately.

Language Catalog

Gates /translate/quote, accept and admin submit. The catalog holds 127 languages: the 75 rows of Amazon Translate's published list joined with the 119 languages Qwen 3 publishes. Input codes are 2–12 characters (a primary code, an optional script, an optional region), and 36 alternate codes such as nb, zh-Hant and ar-EG are converted silently. A code that has a valid shape but is not in the catalog is rejected before any document analysis, pricing or charge. See architecture.md for the full incident this closes.

MethodPathDescription
GET/admin/translate/languagesList all supported languages. Optional ?status=supported|blocked|unverified filter. Returns Aurora rows plus live Valkey values for drift visibility.
POST/admin/translate/languagesUpsert a language. Body: {"lang_code":"xx","status":"blocked","notes":"reason","updated_by":"cory"}; status is always required. Also accepts native_name, script, rtl, word_spaced, base_lang, tuning_lang, target_allowed and default_model. A new code requires english_name, native_name, script and tuning_lang (400 translate_language_fields_required; recorded with source='manual'). An invalid default_model answers 400 translate_language_model_invalid. The response includes rtl and word_spaced.
POST/admin/translate/languages/reloadFlush the catalog keys in Valkey, reload from Aurora in place right away, and return source (where the reload read from).

Valkey cache, two keys: tx:supported_langs (hash, lang_code → status, synced nightly by BeastReconciler) and tx:lang_catalog (the full catalog as JSON, TTL 27 hours). Every LPO container also refreshes its catalog in place every 15 minutes (timer txlangs_refresh), so a change reaches every container without a deploy. If Aurora and both Valkey keys are unreachable the catalog is empty and a quote answers 503 tbts_langs_unavailable — it fails closed, and nothing is ever charged for a language we can't confirm.

14. Dashboard Support Endpoints — Full Reference

The dashboard exposes 8 support endpoints split into two groups: Customer (authenticated via Bearer token, scoped to the user's own tickets) and Admin (Bearer token + admin role, system-wide access). All responses use the standard 12-field UME envelope.

Customer Endpoints (Bearer Token)

These endpoints are scoped to the authenticated user's email. Customers can only see and interact with their own tickets. Internal admin notes are never exposed.

GET /dashboard/api/support/tickets

List the authenticated user's support tickets, ordered by most recent. Limited to 50.

Authentication: Bearer token (session cookie)

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/dashboard/api/support/tickets] [200]",
  "status_code": 200,
  "endpoint": "/dashboard/api/support/tickets",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbi-dashboard/v1",
  "timestamp": "2026-05-17T19:00:00Z",
  "data": {
    "tickets": [
      {
        "ticket_number": "TKT-20260517-A3F2",
        "subject": "API key not working after renewal",
        "status": "open",
        "created_at": "2026-05-17T14:30:00Z"
      }
    ]
  },
  "error": ""
}

GET /dashboard/api/support/tickets/{ticket_number}

Full ticket detail with reply thread. Excludes internal admin notes. Shows translated replies in the customer's language.

Authentication: Bearer token (session cookie). Must own the ticket.

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/dashboard/api/support/tickets/TKT-20260517-A3F2] [200]",
  "status_code": 200,
  "endpoint": "/dashboard/api/support/tickets/TKT-20260517-A3F2",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbi-dashboard/v1",
  "timestamp": "2026-05-17T19:00:00Z",
  "data": {
    "ticket": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "ticket_number": "TKT-20260517-A3F2",
      "name": "Jane Developer",
      "category": "technical",
      "subject": "API key not working after renewal",
      "message": "I renewed my subscription yesterday but my API key returns 403...",
      "status": "open",
      "preferred_lang": "en",
      "created_at": "2026-05-17T14:30:00Z",
      "updated_at": "2026-05-17T15:45:00Z"
    },
    "replies": [
      {
        "id": "f1e2d3c4-b5a6-7890-1234-567890abcdef",
        "author_type": "admin",
        "message": "I've checked your key — it looks like the cache hadn't refreshed yet. Should be working now.",
        "created_at": "2026-05-17T15:45:00Z"
      }
    ]
  },
  "error": ""
}

POST /dashboard/api/support/tickets/{ticket_number}/reply

Post a customer reply. Max 10,000 characters. Auto-translates to English for admin readability. Notifies admin via email. Re-opens tickets in awaiting_customer or resolved state.

Authentication: Bearer token (session cookie). Must own the ticket.

Request body:

{
  "message": "Thanks, it's working now! One more question — can I add more assets to my plan?"
}

Response:

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/dashboard/api/support/tickets/TKT-20260517-A3F2/reply] [200]",
  "status_code": 200,
  "endpoint": "/dashboard/api/support/tickets/TKT-20260517-A3F2/reply",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbi-dashboard/v1",
  "timestamp": "2026-05-17T19:10:00Z",
  "data": {
    "reply_id": "c4d5e6f7-a8b9-0123-4567-890abcdef123",
    "message": "Reply posted"
  },
  "error": ""
}

Errors: 400 if message is empty or exceeds 10,000 chars. 400 if ticket is closed. 404 if ticket not found or not owned by user.

POST /dashboard/api/support/tickets/{ticket_number}/resolve

Mark own ticket as resolved. Notifies admin. Closed tickets cannot be resolved (already terminal). Idempotent — resolving an already-resolved ticket returns success.

Authentication: Bearer token (session cookie). Must own the ticket.

Request body: Empty {}

Response:

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/dashboard/api/support/tickets/TKT-20260517-A3F2/resolve] [200]",
  "status_code": 200,
  "endpoint": "/dashboard/api/support/tickets/TKT-20260517-A3F2/resolve",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbi-dashboard/v1",
  "timestamp": "2026-05-17T19:15:00Z",
  "data": {
    "message": "Ticket marked resolved",
    "status": "resolved"
  },
  "error": ""
}

Admin Endpoints (Bearer Token + Admin Role)

System-wide ticket management. Admin can see all tickets, all replies (including internal notes), customer IPs, and full audit trail. Requires admin role on the session.

GET /dashboard/api/admin/support/tickets

List all tickets system-wide. Supports filtering by status and category. Limited to 200 results, ordered by created_at DESC.

Authentication: Bearer token + admin role

Query parameters:

ParamTypeDescription
statusstringFilter by status: new, open, in_progress, awaiting_customer, resolved, closed
categorystringFilter by category: billing, technical, api-access, rate-limit, feature-request, bug-report, account, general
{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/dashboard/api/admin/support/tickets] [200]",
  "status_code": 200,
  "endpoint": "/dashboard/api/admin/support/tickets",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbi-dashboard/v1",
  "timestamp": "2026-05-17T19:00:00Z",
  "data": {
    "tickets": [
      {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "ticket_number": "TKT-20260517-A3F2",
        "name": "Jane Developer",
        "email": "jane@example.com",
        "category": "technical",
        "subject": "API key not working after renewal",
        "status": "open",
        "created_at": "2026-05-17T14:30:00Z",
        "updated_at": "2026-05-17T15:45:00Z"
      }
    ]
  },
  "error": ""
}

GET /dashboard/api/admin/support/tickets/{id}

Full ticket detail by UUID. Includes all replies (internal notes visible), customer IP, email, preferred language, and English translation of the original message.

Authentication: Bearer token + admin role

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/dashboard/api/admin/support/tickets/a1b2c3d4-e5f6-7890-abcd-ef1234567890] [200]",
  "status_code": 200,
  "endpoint": "/dashboard/api/admin/support/tickets/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbi-dashboard/v1",
  "timestamp": "2026-05-17T19:00:00Z",
  "data": {
    "ticket": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "ticket_number": "TKT-20260517-A3F2",
      "name": "Jane Developer",
      "email": "jane@example.com",
      "category": "technical",
      "subject": "API key not working after renewal",
      "message": "I renewed my subscription yesterday but my API key returns 403...",
      "message_en": "I renewed my subscription yesterday but my API key returns 403...",
      "preferred_lang": "en",
      "status": "open",
      "client_ip": "203.0.113.50",
      "created_at": "2026-05-17T14:30:00Z",
      "updated_at": "2026-05-17T15:45:00Z"
    },
    "replies": [
      {
        "id": "f1e2d3c4-b5a6-7890-1234-567890abcdef",
        "author": "CoryDeanKalani@CPMP-Site.org",
        "author_type": "admin",
        "message": "I've checked your key — it looks like the cache hadn't refreshed yet. Should be working now.",
        "is_internal": false,
        "created_at": "2026-05-17T15:45:00Z"
      },
      {
        "id": "d5e6f7a8-b9c0-1234-5678-90abcdef1234",
        "author": "CoryDeanKalani@CPMP-Site.org",
        "author_type": "admin",
        "message": "Note: key cache was stale due to recent deploy. Forced invalidation.",
        "is_internal": true,
        "created_at": "2026-05-17T15:44:00Z"
      }
    ]
  },
  "error": ""
}

POST /dashboard/api/admin/support/tickets/{id}/reply

Post an admin reply. Auto-translates to customer's language and emails them. Includes a one-click resolve link in the email (single-use, 30-day TTL). Internal notes (is_internal: true) are never shown to the customer or emailed. First reply auto-advances status from new to open.

Authentication: Bearer token + admin role

Request body:

{
  "message": "I've checked your key — it looks like the cache hadn't refreshed yet. Should be working now.",
  "is_internal": false
}

Response:

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/dashboard/api/admin/support/tickets/a1b2c3d4/reply] [200]",
  "status_code": 200,
  "endpoint": "/dashboard/api/admin/support/tickets/a1b2c3d4/reply",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbi-dashboard/v1",
  "timestamp": "2026-05-17T19:10:00Z",
  "data": {
    "reply_id": "f1e2d3c4-b5a6-7890-1234-567890abcdef",
    "message": "Reply posted"
  },
  "error": ""
}

Errors: 400 if message is empty. 404 if ticket not found.

POST /dashboard/api/admin/support/tickets/{id}/status

Update a ticket's status. Audit-logged with the admin's email. Valid statuses: new, open, in_progress, awaiting_customer, resolved, closed.

Authentication: Bearer token + admin role

Request body:

{
  "status": "in_progress"
}

Response:

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/dashboard/api/admin/support/tickets/a1b2c3d4/status] [200]",
  "status_code": 200,
  "endpoint": "/dashboard/api/admin/support/tickets/a1b2c3d4/status",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbi-dashboard/v1",
  "timestamp": "2026-05-17T19:12:00Z",
  "data": {
    "message": "Status updated",
    "status": "in_progress"
  },
  "error": ""
}

Errors: 400 if status is invalid. 404 if ticket not found.

Ticket Status Flow

Tickets follow a lifecycle with automatic transitions:

TransitionTrigger
new → openFirst admin reply
awaiting_customer → openCustomer replies
resolved → openCustomer replies (re-opens)
Any → resolvedCustomer clicks resolve (dashboard or email link)
Any → any valid statusAdmin sets status manually

Note: Closed tickets cannot be replied to by customers. They must open a new ticket.

GET /dashboard/api/admin/daily-rollup

Returns per-day usage aggregates served from the mv_usage_daily materialized view in Aurora. Because the data is precomputed, this endpoint is fast and never scans the full usage_logs table. The view is refreshed every 5 minutes by the pg_cron job tbi-mv-usage-daily-refresh running natively inside Aurora; the last_refreshed field tells consumers how current the data is.

Authentication: Bearer token + admin role

ParameterTypeDescription
daysintNumber of days to return. Default 30, maximum 93 (matches the usage-log retention window).

Response:

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/dashboard/api/admin/daily-rollup] [200]",
  "status_code": 200,
  "endpoint": "/dashboard/api/admin/daily-rollup",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbi-dashboard/v1",
  "timestamp": "2026-05-30T23:30:00Z",
  "data": {
    "days": [
      {
        "day": "2026-05-30",
        "total_requests": 4821,
        "avg_latency_ms": 12.4,
        "p50_latency_ms": 9,
        "p95_latency_ms": 38,
        "p99_latency_ms": 71,
        "cached_requests": 4603,
        "cache_hit_rate_pct": 95.48,
        "distinct_assets": 142,
        "distinct_api_keys": 6,
        "distinct_sources": 6,
        "distinct_nodes": 3
      }
    ],
    "count": 1,
    "last_refreshed": "2026-05-30T23:26:00Z"
  },
  "error": ""
}

Notes: days is always an array (empty [] when no data, never null). last_refreshed reflects the most recent materialized-view refresh, not the request time.

15. Translation Service API

AI-powered document translation with sentinel preprocessing and multi-layer validation. Translates technical documentation while preserving code blocks, Mermaid diagrams, brand terms and every technical identifier, into and out of any of 127 languages. Six AI agents are available: five at the same flat $3.00 per document-language pair, and Claude Opus 4.8 priced at its actual cost with a $3.00 floor. The engine is agent-agnostic, so any model reachable through Amazon Bedrock can serve as an agent without code changes.

The flow is quote → accept → follow the status → download. Along the way you can cancel any time before a job finishes, retry failed pairs once at no charge, and you are refunded automatically for anything that still can't be delivered. Every promise in that sentence is backed by an endpoint below, so an integration can do everything the dashboard can. The Translation Service document walks through it end to end.

Authentication: every /translate/* endpoint except GET /translate/models and GET /translate/languages (both public) needs a Translation API key, sent as the X-API-Key header, as Authorization: Bearer, or as the ?api_key= query parameter. A Prices-only key is refused with 403, so translation spend is always tied to a translation account.

The limits, from one place in the code: up to 12 languages and 42 documents per quote, each document up to 300 KB (307,200 bytes), up to 9 quotes per batch, and 72 hours to accept a quote. The quote, the accept, the admin submit and the dashboard all read these from the same constants, so a quote can never promise something the job would refuse. GET /translate/languages returns them live in limits.

15.1 GET /translate/models

Lists the six translation agents. Public — no authentication required. Use an id as the model in a quote; leave model out and you get Auto, the best agent per language: the agent named in default (Qwen 3 235B) for every language it supports, and Claude Sonnet 4.6 only for Amharic, Hausa, Mongolian, Pashto and Somali, at the same price. Naming an agent overrides Auto for every language, retries included.

Response (200 OK)

{
  "status_code": 200,
  "data": {
    "models": [
      { "id": "qwen3-235b",        "name": "Qwen 3 235B",       "tier": "Fast & Capable",   "input_rate_per_1m": 0.22,  "output_rate_per_1m": 0.88,  "estimated_cost_per_pair": 0.019294, "speed": "fast",     "quality": "good",        "description": "Fast and capable. Excellent with Chinese, Japanese, and Korean. The default choice." },
      { "id": "deepseek-v3",       "name": "DeepSeek V3",       "tier": "Fast & Capable",   "speed": "fast",     "quality": "good",        "description": "Fast and capable. Strong with European languages and technical content." },
      { "id": "mistral-large",     "name": "Mistral Large",     "tier": "Fast & Capable",   "speed": "fast",     "quality": "good",        "description": "Fast and capable. Native-quality Romance languages (French, Spanish, Portuguese, Italian)." },
      { "id": "claude-haiku-5.5",  "name": "Claude Haiku 5.5",  "tier": "Fast & Capable",   "speed": "fast",     "quality": "good",        "description": "Fast and capable. Versatile all-rounder, great for high-volume jobs." },
      { "id": "claude-sonnet-4.6", "name": "Claude Sonnet 4.6", "tier": "Deep Reasoning",   "speed": "moderate", "quality": "excellent",   "description": "Deep reasoning, nuanced output. Handles complex scripts (Arabic, Hindi, Japanese) and technical documents well." },
      { "id": "claude-opus-4.8",     "name": "Claude Opus 4.8",     "tier": "Maximum Fidelity", "input_rate_per_1m": 5.50, "output_rate_per_1m": 27.50, "estimated_cost_per_pair": 0.5731, "speed": "slow",     "quality": "exceptional", "description": "Highest quality, comprehensive translation. Best for critical documents, nuanced content, and languages with complex grammar." }
    ],
    "default": "qwen3-235b",
    "pricing_note": "Rates are per 1M tokens. estimated_cost_per_pair is for a typical 50KB document (one language). Actual cost depends on document size and content density."
  }
}

Every agent entry carries all nine fields; the first and the Claude Opus 4.8 entry are shown in full and the rest are abbreviated here for width. Opus 4.8's rates (5.50 / 27.50 USD per million tokens, refreshed nightly from the AWS Price List) put a typical 50 KB document at about $0.57 of our cost, so it is priced at the $3.00 floor until a document reaches about 200 KB. The earlier agent IDs claude-haiku-3.5 and claude-opus-4 are still accepted everywhere a model is taken, and are converted quietly to claude-haiku-5.5 and claude-opus-4.8, so an older integration never breaks.

FieldTypeDescription
input_rate_per_1mfloatOur Bedrock cost per 1 million input tokens (USD), read from tx:params, which the nightly sync job refreshes from the AWS Price List
output_rate_per_1mfloatOur Bedrock cost per 1 million output tokens (USD)
estimated_cost_per_pairfloatOur estimated Bedrock cost for one language on a typical 50 KB document

Our cost, not your price: the rates are published for transparency. Your price is the flat $3.00 per document-language pair on five of the six agents, and Claude Opus 4.8 is priced at its actual cost with a $3.00 floor, so a larger document can cost more per pair. The quote always shows the exact amount.

15.2 POST /translate/quote

Send document URLs and target languages and receive a fixed price. The engine fetches and analyzes each document (size, sections, code blocks, diagrams), resolves every language against the 127-language catalog (alternate codes such as nb or zh-Hant are converted quietly), works out each document's source language, and refuses a target that equals the source. Nothing is charged at this step.

Request Body

{
  "docs": ["https://your-site.com/docs/api-reference.html", "https://your-site.com/docs/faq.html"],
  "langs": ["es", "fr", "de", "ja", "zh-TW"],
  "source_lang": "en",
  "source_langs": { "https://your-site.com/docs/faq.html": "it" },
  "model": "qwen3-235b"
}
FieldTypeRequiredDescription
docsstring[]YesPublic http:// or https:// URLs of the HTML documents. Each must answer 200 and be 300 KB (307,200 bytes) or smaller. Up to 42 documents. A single doc_url string is also accepted. A document's name is the last part of its URL path, and two documents in one quote can't share a name.
langsstring[]YesTarget language codes from 15.12. Up to 12, counted after alternate codes are converted and duplicates removed, so ["nb","no"] is one language. English is a target like any other.
source_langstringNoThe language your documents are written in. Applies to every document without its own source_langs entry. "auto", or leaving it out, means "work it out for me". Any catalog code is accepted as a source.
source_langsobjectNoPer-document source language, keyed by the document URL exactly as you sent it in docs. Beats source_lang for that document.
supersedes_quote_idstringNoOne of your pending quotes that this quote replaces (for example after you add a source language). It is marked superseded and named in superseded_quote_id. Any other ID is ignored and the new quote still stands.
modelstringNoAgent ID from 15.1. Leave it out and the quote is auto-routed per language: qwen3-235b for almost every language, and claude-sonnet-4.6 for am, ha, mn, ps and so, at the same price. An agent you choose is honored for every language, in every attempt, including retries. The earlier IDs claude-haiku-3.5 and claude-opus-4 are accepted and converted quietly.
deliverystringNo"express", the only delivery mode and the default.

How the Source Language Is Decided

For each document the first of these that gives an answer wins, and source_provenance records which one it was: stated (your source_langs or source_lang), declared (the page's <html lang>, with region tags reduced, so en-US is English and ar-EG is Modern Standard Arabic), detected (only a definitive detection counts: English by word frequency, and Japanese, Korean or Thai by script), otherwise auto. A declared language that a definitive detection contradicts becomes auto rather than a guess. An unsure source never fails a quote: the document gets source_lang_confirm_needed: true and a source_hint in your api_lang, and the agent translates from the document's original language as written.

Response (200 OK)

{
  "status_code": 200,
  "data": {
    "quote_id": "qt-a1b2c3d4-e5f",
    "docs": [
      {
        "doc_url": "https://your-site.com/docs/api-reference.html",
        "doc_name": "api-reference.html",
        "analysis": { "size_bytes": 63488, "estimated_chunks": 4, "difficulty": "moderate", "code_blocks": 12, "diagrams": 2 },
        "total_price": 15.00
      },
      {
        "doc_url": "https://your-site.com/docs/faq.html",
        "doc_name": "faq.html",
        "analysis": { "size_bytes": 18944, "estimated_chunks": 2, "difficulty": "simple", "code_blocks": 0, "diagrams": 0 },
        "total_price": 15.00
      }
    ],
    "doc_count": 2,
    "languages": ["es", "fr", "de", "ja", "zh-TW"],
    "lang_count": 5,
    "total_pairs": 10,
    "model": "qwen3-235b",
    "auto_routed": false,
    "model_by_lang": {},
    "delivery": { "mode": "express", "label": "Express — real-time translation", "estimated_time": "~8 minutes", "qualified_reason": "10 translated documents (2 docs × 5 languages)" },
    "pricing": { "price_per_pair": 3.00, "total_pairs": 10, "total_price": 30.00, "currency": "USD" },
    "expires_at": "2026-10-13T15:00:00Z",
    "accept_url": "/translate/accept/qt-a1b2c3d4-e5f",
    "sources": [
      { "doc_url": "https://your-site.com/docs/api-reference.html", "doc_name": "api-reference.html", "source_lang": "en", "source_provenance": "stated", "source_lang_confirm_needed": false, "source_hint": "" },
      { "doc_url": "https://your-site.com/docs/faq.html", "doc_name": "faq.html", "source_lang": "it", "source_provenance": "stated", "source_lang_confirm_needed": false, "source_hint": "" }
    ],
    "superseded_quote_id": ""
  }
}

model_by_lang is {} when you chose an agent, and the per-language map when the quote was auto-routed (model: "per-language", auto_routed: true). Internal AI cost is never part of the response: you see the price and nothing else.

Error Responses

StatusKeyCondition
400tbts_langs_required / tbts_docs_requiredNo languages, or no documents, given
400tbts_invalid_lang_codeA language code has the wrong shape (2–12 characters: a primary code, an optional script, an optional region)
400tbts_unsupported_lang_codeA language code is well formed but not in the catalog (15.12)
400tbts_too_many_langs / tbts_too_many_docsMore than 12 languages, or more than 42 documents
400tbts_doc_url_invalid / tbts_doc_name_duplicate / tbts_doc_excludedA URL that isn't http(s), two documents with the same name, or a URL we don't translate
400tbts_doc_fetch_failedA document couldn't be fetched, including a URL that resolves to a private or internal network address
400tbts_doc_too_largeA document is larger than 300 KB. It is refused whole, never truncated, hashed or priced
400tbts_source_lang_invalid / tbts_source_lang_doc_unknownA stated source isn't a catalog code, or names a document that isn't in this quote
400tbts_target_is_sourceA target equals a document's source language. Variants such as es → es-MX or ar → arz are always allowed
400tbts_invalid_modelUnknown agent ID
403tbts_prices_only_keyThe key is a Prices-only key
503tbts_langs_unavailableThe language catalog couldn't be loaded. Nothing is priced or charged; try again in a moment

Quote expiry: a quote can be accepted for 72 hours. After that, request a new one.

15.3 GET /translate/quote/{quote_id}

Returns one quote with its current state: pending, accepted, expired, superseded (a newer quote replaced it; superseded_by names it, or is "" when the document now breaks a rule), refunded or partially_refunded.

Response (200 OK)

{
  "status_code": 200,
  "data": {
    "quote_id": "qt-a1b2c3d4-e5f",
    "doc_url": "https://your-site.com/docs/api-reference.html",
    "doc_name": "api-reference.html",
    "docs": [
      { "url": "https://your-site.com/docs/api-reference.html", "name": "api-reference.html" },
      { "url": "https://your-site.com/docs/faq.html", "name": "faq.html" }
    ],
    "doc_count": 2,
    "languages": ["es", "fr", "de", "ja", "zh-TW"],
    "total_pairs": 10,
    "model": "qwen3-235b",
    "analysis": { "size_bytes": 63488, "estimated_chunks": 4, "difficulty": "moderate", "code_blocks": 12, "diagrams": 2 },
    "pricing": { "price_per_pair": 3.00, "pairs": 10, "total_price": 30.00, "currency": "USD" },
    "sources": [
      { "doc_url": "https://your-site.com/docs/api-reference.html", "doc_name": "api-reference.html", "source_lang": "en", "source_provenance": "stated", "source_lang_confirm_needed": false, "source_hint": "" },
      { "doc_url": "https://your-site.com/docs/faq.html", "doc_name": "faq.html", "source_lang": "it", "source_provenance": "stated", "source_lang_confirm_needed": false, "source_hint": "" }
    ],
    "state": "accepted",
    "superseded_by": "",
    "expires_at": "2026-10-13T15:00:00Z",
    "accepted_at": "2026-10-10T15:04:12Z",
    "job_id": "019e4b8a37bc-1f2e3d4c5b6a7980"
  }
}

pricing.pairs is documents × languages, so a multi-document quote always shows its true pair count. doc_url, doc_name and analysis describe the first document and are kept for older integrations; docs lists them all.

15.4 GET /translate/quotes

Your 30 most recent quotes for this API key, newest first. data is the array itself. A quote replaced by a newer one appears with state: "superseded".

Response (200 OK)

{
  "status_code": 200,
  "data": [
    { "quote_id": "qt-a1b2c3d4-e5f", "doc_name": "api-reference.html",      "lang_count": 5, "total_price": 30.00, "state": "accepted", "expires_at": "2026-10-13T15:00:00Z", "job_id": "019e4b8a37bc-1f2e3d4c5b6a7980", "model": "qwen3-235b" },
    { "quote_id": "qt-f6e5d4c3-b2a", "doc_name": "architecture-guide.html", "lang_count": 3, "total_price": 9.00,  "state": "pending",  "expires_at": "2026-10-13T18:00:00Z", "job_id": "",                    "model": "claude-sonnet-4.6" }
  ]
}

15.5 POST /translate/accept/{quote_id}

Accepts a pending quote. No request body is needed. Before the card on file is touched, the engine claims the quote so a double-click can't charge twice, re-checks every limit, checks the service guards, and fetches every document again to compare it with what was priced. Only then is the quoted total charged and the job started.

Response (200 OK)

{
  "status_code": 200,
  "data": {
    "status": "accepted",
    "quote_id": "qt-a1b2c3d4-e5f",
    "job_id": "019e4b8a37bc-1f2e3d4c5b6a7980",
    "model": "qwen3-235b",
    "payment_id": "pi_3abc123def456",
    "amount_usd": 30.00,
    "status_url": "/translate/status/019e4b8a37bc-1f2e3d4c5b6a7980",
    "notice": "",
    "notice_key": "",
    "updated_docs": [],
    "key": "",
    "message": "",
    "refunded": false,
    "refund_amount": 0
  }
}

Every field is always present. When a document's readable content changed but its price and rule results did not, the job still starts at the quoted price on the current version: notice_key is tbts_quote_content_updated, notice says so in your api_lang, and updated_docs names the documents. On Claude Opus 4.8, a re-quoted document that changes again is charged at the lower of the two prices, with tbts_quote_price_held.

When the Charge Succeeds but the Start Doesn't

Statusdata.status / keyWhat it means
202pending / tbts_translation_start_pendingYou were charged and we are still confirming the job started. If it didn't, the full amount is refunded automatically. status_url is /translate/status/{quote_id}, so you can follow it either way.
502refunded / tbts_translation_start_refundedThe job couldn't start, and the full amount has already been refunded (refunded: true, refund_amount).
502refund_delayed / tbts_status_refund_delayedThe job couldn't start and the refund is taking longer than usual. It is retried automatically every 15 minutes and our team is alerted.

Error Responses

StatusKeyCondition
400tbts_doc_too_largeA document grew past 300 KB since the quote. Nothing is charged, the quote is marked superseded and no new quote is made
400tbts_target_is_sourceA document now declares the same language as one of your targets. Nothing is charged and no new quote is made
402tbts_payment_required / tbts_payment_failedNo payment method on file, or the payment was declined
404tbts_quote_not_foundQuote not found on this API key
409tbts_quote_conflictThe quote is no longer pending (the message names its state). For a superseded quote, data carries {state: "superseded", new_quote_id}
409tbts_quote_accept_in_progressAnother request is accepting this quote right now. Nothing is staged or charged; wait a minute and check the quote
409tbts_quote_requotedA document changed and its price or source result changed with it. Nothing is charged; data is {old_quote_id, new_quote_id, new_total_price, changed_docs, accept_url, quote}, ready to accept
409tbts_quote_needs_requoteThe quote no longer passes today's checks, or a document couldn't be fetched again. Request a new quote
410tbts_quote_expired_acceptQuote has expired (72 hours) — request a new one
500tbts_payment_unavailablePayment processing is temporarily unavailable. Nothing was charged
503tbts_service_pausedThe service's safety switch, daily spend cap or token cap is tripped. Checked before the charge, so nothing is charged or queued

Job tracking: status_url points at GET /translate/status/{job_id} (15.8), which reports every document-language pair live, along with the ETA and any refund. The Account Dashboard shows the same data, you receive one email when the job completes, and GET /translate/download/{job_id} returns the files.

Why the second look: a quote lives for 72 hours and a web page can change in that time. The engine fingerprints only the readable content (scripts, styles, comments and tracking markup are ignored), so a page that reloads slightly differently every time is never re-priced, and a page whose words really changed is either translated at the same price with a notice or re-quoted. You never pay a price computed on other content.

15.6 POST /translate/accept-batch

Accept multiple pending quotes in a single transaction. A single Stripe PaymentIntent is created for the batch total. Each quote becomes a separate translation job. Maximum 9 quotes per batch. Every quote is claimed, re-checked and re-fetched exactly as in 15.5 before the card is touched.

Request Body

{
  "quote_ids": [
    "qt-a1b2c3d4-e5f",
    "qt-b2c3d4e5-f6a",
    "qt-c3d4e5f6-a7b"
  ]
}

Parameters

FieldTypeRequiredDescription
quote_idsarrayYesArray of quote IDs to accept. Minimum 1, maximum 9.

Response (200 OK)

{
  "status_code": 200,
  "data": {
    "status": "accepted",
    "batch_size": 3,
    "quote_ids": ["qt-a1b2c3d4-e5f", "qt-b2c3d4e5-f6a", "qt-c3d4e5f6-a7b"],
    "task_id": "019e4b8a37bd-0a1b2c3d4e5f6a7b",
    "task_status_url": "/translate/task/019e4b8a37bd-0a1b2c3d4e5f6a7b",
    "jobs": [
      { "quote_id": "qt-a1b2c3d4-e5f", "doc_name": "api-reference.html", "job_id": "019e4b8a37bc-1f2e3d4c5b6a7980", "error": "", "key": "", "status_url": "/translate/status/019e4b8a37bc-1f2e3d4c5b6a7980", "refunded": false, "refund_amount": 0, "notice": "", "notice_key": "", "updated_docs": [] },
      { "quote_id": "qt-b2c3d4e5-f6a", "doc_name": "quick-start.html", "job_id": "019e4b8a37be-2c3d4e5f6a7b8c9d", "error": "", "key": "", "status_url": "/translate/status/019e4b8a37be-2c3d4e5f6a7b8c9d", "refunded": false, "refund_amount": 0, "notice": "", "notice_key": "", "updated_docs": [] },
      { "quote_id": "qt-c3d4e5f6-a7b", "doc_name": "webhooks.html", "job_id": "019e4b8a37bf-3d4e5f6a7b8c9d0e", "error": "", "key": "", "status_url": "/translate/status/019e4b8a37bf-3d4e5f6a7b8c9d0e", "refunded": false, "refund_amount": 0, "notice": "", "notice_key": "", "updated_docs": [] }
    ],
    "payment_id": "pi_3abc123def456",
    "amount_usd": 42.00,
    "eta": {
      "estimated_seconds": 540,
      "estimated_low_seconds": 420,
      "estimated_high_seconds": 660,
      "estimated_formatted": "~9 minutes",
      "estimated_range": "7-11 minutes",
      "bottleneck_lang": "ja",
      "elapsed_seconds": 0,
      "remaining_seconds": 540,
      "remaining_formatted": "~9 minutes",
      "percent_complete": 0
    },
    "key": "",
    "message": ""
  }
}

In this example the three quotes cover 5, 3 and 6 document-language pairs: 14 pairs at the flat $3.00 is one charge of 42.00. task_status_url follows the whole batch (15.9), and each job's status_url follows that job (15.8). The eta is the slowest job in the batch, so it tells you when everything will be done.

Each jobs[] entry reports its own start, so one quote that can't start never holds the others back. A quote that couldn't start has an empty job_id, key tbts_translation_start_refunded (or tbts_status_refund_delayed, or tbts_translation_start_pending while we confirm), refunded and refund_amount for what came back, and a status_url that uses the quote ID. When no quote in the batch could start, the response is 502 with status: "start_failed" and the same jobs list, and the task still exists so GET /translate/task/{task_id} reports the refunds.

Error Responses

StatusCondition
400tbts_batch_ids_required (no quote IDs), tbts_batch_max_9 (more than 9), or a body that isn't valid JSON
402Payment failed — no valid payment method on file, or the card was declined
409status: "validation_failed": one or more quotes can't be accepted, nothing is charged, and errors[] lists each one as {quote_id, error, key, new_quote_id, new_total_price}. key is tbts_quote_not_found, tbts_quote_already_processed, tbts_quote_expired, tbts_quote_accept_in_progress, tbts_quote_needs_requote, tbts_quote_requoted (with new_quote_id and new_total_price set), or a rule's own key such as tbts_doc_too_large
502status: "start_failed": the charge succeeded but no quote could start; every quote is refunded as described above
503tbts_service_paused: checked before the charge, so nothing is charged

Batch behavior: All quotes must belong to the same API key and be in pending state. If any quote in the batch fails validation, the entire batch is rejected with nothing charged (no partial acceptance); replace each tbts_quote_requoted ID with its new_quote_id and resubmit. Each accepted quote spawns an independent translation job, and you receive one completion email when the whole batch is done.

15.7 POST /translate/cancel/{quote_id}

Changed your mind after accepting a quote? You can stop the translation at any point before it finishes, and you pay only for what you received. Every document-language pair already delivered is yours to keep. Every pair that was not delivered is refunded at its full per-pair price. No request body is needed; authenticate with the same API key you use for the rest of /translate/*.

Request

curl -X POST -H "X-API-Key: your-api-key-here" \
     https://api.cpmp-site.org/translate/cancel/qt-a1b2c3d4-e5f

Response (200 OK)

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/translate/cancel/qt-a1b2c3d4-e5f] [200]",
  "status_code": 200,
  "data": {
    "quote_id": "qt-a1b2c3d4-e5f",
    "job_id": "019e4b8a37bc-1f2e3d4c5b6a7980",
    "state": "cancelled",
    "refund_status": "calculating",
    "message": "Translation cancelled. Pages already delivered are yours to keep. The refund for the rest is being calculated."
  },
  "error": ""
}

What Happens After You Cancel

The cancel takes effect the moment you send it, but the engine never cuts a language off mid-sentence. Each language finishes the section it is working on and stops at that boundary, so a page is either fully delivered or not delivered at all. You will never receive half a translation. On a large document one section can take up to about three minutes, and that short wait is what keeps every delivered page whole.

Once the last language has stopped, the refund is fixed by counting what was actually delivered. Your refund_status then moves through these states:

refund_statusWhat It Means For You
calculatingThe job is cancelled and the last language is finishing its current section. The refund is counted as soon as it stops.
pending_approvalYour refund amount is fixed and waiting for a person to approve it. Every refund is reviewed by a human before it is issued.
approvedThe refund has been issued through Stripe. It appears on your statement within 5-10 business days.
noneEvery pair had already been delivered before the cancel reached it, so there is nothing to refund. All of it is yours.

You get one email when the refund is issued. It tells you which languages were delivered, which were stopped, and the amount coming back to you. The Account Dashboard shows the same refund state live, and a cancelled job stays in your Translation History with the refunded amount under its price.

How the refund is counted: refund = total paid − (delivered pairs × per-pair price). The arithmetic runs in whole cents and any rounding goes to you, so what you were charged and what you get back always add up to exactly what you paid. If a language was in the middle of a section when you cancelled, the AI cost of that partial work is absorbed by us, never passed to you.

Error Responses

StatusCondition
401API key missing or invalid
404Quote not found on your account. A quote belonging to another account also returns 404, so its existence is never confirmed to anyone else.
409The translation has already been cancelled
409The translation has already finished, so there is nothing left to stop
409The quote was never accepted, so no job is running (the message includes the quote's current state)
409tbts_cancel_already_finished also covers a quote whose finished job is already in the failed-pair refund flow (15.10, 15.11): those pairs are refunded automatically, so there is nothing to cancel

Cancelling a Free Retry

When the job that is running is your free retry (15.10), cancel stops only the retry. The answer is 200 with tbts_cancel_retry_accepted ("The retry was cancelled. Anything it didn't finish will be refunded automatically."), refund_status stays "", and no approval is involved: the retry was free, so the pairs neither the job nor the retry delivered go straight to the automatic failed-pair refund.

Ownership follows you, not your key: a quote is matched to your account rather than to the single key that accepted it. If you rotate your API key while a job is running, the new key can still cancel it. Error messages arrive in your api_lang, like every other response.

15.8 GET /translate/status/{job_id}

Live status of one translation: every document-language pair, the ETA while it runs, the free retry, and any refund. The path accepts a job ID, a retry job ID, or a quote ID (the form a start-failure response points at). Authentication is the same as the rest of /translate/*, and ownership is by your account, so any of your keys can read any of your jobs. Pair progress is read from Valkey first, so polling stays fast, with Aurora as the fallback; money and refund state always come from Aurora.

Response (200 OK)

{
  "status_code": 200,
  "data": {
    "job_id": "019e4b8a37bc-1f2e3d4c5b6a7980", "quote_id": "qt-a1b2c3d4-e5f", "parent_job_id": "019e4b8a37bc-1f2e3d4c5b6a7980",
    "state": "partial", "job_state": "partial",
    "model": "per-language", "model_by_lang": { "es": "qwen3-235b", "zh-TW": "qwen3-235b" }, "model_display": "Qwen 3 235B",
    "submitted_at": "2026-10-10T15:04:13Z", "started_at": "2026-10-10T15:04:20Z", "completed_at": "2026-10-10T15:12:40Z",
    "docs": ["api-reference.html"], "langs": ["es", "zh-TW"], "total_pairs": 2,
    "counts": { "queued": 0, "running": 0, "succeeded": 1, "failed": 1, "cancelled": 0 },
    "pairs": [
      { "doc": "api-reference.html", "lang": "es",    "state": "succeeded", "job_id": "019e4b8a37bc-1f2e3d4c5b6a7980", "attempts": 1 },
      { "doc": "api-reference.html", "lang": "zh-TW", "state": "failed",    "job_id": "019e4c02d9f1-6b5a4c3d2e1f0a9b", "attempts": 2 }
    ],
    "eta": null,
    "retry": { "allowed": false, "state": "closed", "retry_job_id": "019e4c02d9f1-6b5a4c3d2e1f0a9b", "window_ends_at": "" },
    "cancel_refund": { "status": "", "amount_due": 0, "amount_refunded": 0 },
    "failed_pair_refund": { "status": "issued", "amount_due": 0, "amount_refunded": 3.00, "pairs": [{ "doc": "api-reference.html", "lang": "zh-TW" }], "window_ends_at": "", "refunded_at": "2026-10-10T15:30:02Z" },
    "refund_status": "issued", "refund_due": 0, "total_refunded": 3.00,
    "downloads_ready": true, "download_url": "/translate/download/019e4b8a37bc-1f2e3d4c5b6a7980",
    "message_key": "tbts_status_partial_refunded",
    "message": "1 of 2 translations couldn't be completed. We refunded 3.00 for them automatically."
  }
}
FieldMeaning
stateThe whole quote across every attempt: queued, running, succeeded, partial, failed or cancelled. job_state is the raw state of the job you asked about.
pairsOne entry per document and language, sorted by document then language. A pair delivered by any attempt counts as succeeded; job_id is the attempt that produced its state and attempts counts the tries.
model, model_by_lang, model_displayThe agent you were quoted for, so you can see your choice was honored. model_by_lang is {} for an agent you chose.
etaFilled while any job of the quote is active, null otherwise.
retryallowed is true when the free retry is available; state is open, running or closed; window_ends_at is when the retry window closes.
cancel_refund, failed_pair_refundThe two refund kinds, kept apart. failed_pair_refund.status is window_open, retrying, due, issuing, issued, none, error or "". refund_status and refund_due summarize whichever applies, so a simple client reads one field.
downloads_ready, download_urlTrue once at least one pair is delivered and no job is active, including partial and cancelled jobs.
message_key, messageWhere things stand, in your api_lang. Amounts have two decimals and no currency symbol, and dates read YYYY-MM-DD HH:MM UTC.

Every field is always present and every list is [] rather than null. The full list of message_key values and what each one means is in the Translation Service document.

Error Responses

StatusKeyCondition
401missing_api_key / invalid_api_keyAPI key missing or invalid
403tbts_prices_only_keyThe key is a Prices-only key
404tbts_job_not_foundNot found, not yours, never accepted, or a malformed ID. Always 404, never 403, so nobody can probe for another account's jobs
503tbts_status_unavailableThe ledger is briefly unreachable; try again in a moment

15.9 GET /translate/task/{task_id}

Live status of a whole batch from 15.6. Each job is reported with the same object as 15.8, built by the same code, so a batch and its jobs can never disagree.

{
  "status_code": 200,
  "data": {
    "task_id": "019e4b8a37bd-0a1b2c3d4e5f6a7b", "state": "running", "submitted_at": "2026-10-10T15:04:13Z",
    "total_jobs": 3,
    "counts": { "queued": 0, "running": 1, "succeeded": 1, "partial": 1, "failed": 0, "cancelled": 0 },
    "jobs": [ "<one 15.8 status object per job>" ],
    "total_pairs": 14, "succeeded_pairs": 11, "failed_pairs": 1,
    "total_refunded": 0, "eta": { "estimated_seconds": 180, "remaining_formatted": "~3 minutes" },
    "message": "2 of 3 jobs finished."
  }
}

state is running while any job is active, then succeeded, partial or cancelled. eta is the slowest active job. A quote in the batch that was refunded because it couldn't start is reported too, with its refund. Not found or not yours → 404 tbts_task_not_found.

15.10 POST /translate/retry/{quote_id}

Your one free retry per quote. It runs only the retryable failed pairs again, on the agent the quote was priced for, at no charge. It is always available, whether or not automatic failed-pair refunds are switched on. A pair skipped because the document was already in that language is never retried, only refunded. No request body is needed.

{
  "status_code": 200,
  "data": {
    "quote_id": "qt-a1b2c3d4-e5f",
    "retry_job_id": "019e4c02d9f1-6b5a4c3d2e1f0a9b",
    "state": "queued",
    "pairs": [{ "doc": "api-reference.html", "lang": "zh-TW" }],
    "status_url": "/translate/status/019e4c02d9f1-6b5a4c3d2e1f0a9b",
    "message": "Retrying 1 translations at no charge."
  }
}
StatusKeyCondition
404tbts_job_not_foundQuote not found on your account
409tbts_retry_not_availableNothing to retry, or a job is still running
409tbts_retry_in_progressThe retry is already running
409tbts_retry_closedThe failed pairs were already refunded, or the one free retry was used
409tbts_retry_window_expiredThe 72-hour retry window has ended, so the refund is being processed instead
502tbts_retry_start_failedThe retry couldn't start; nothing changed, so try again in a few minutes
503tbts_status_unavailableThe ledger is briefly unreachable

15.11 POST /translate/refund-failed/{quote_id}

"Refund instead": you decline the free retry and ask for the failed pairs to be refunded now. It moves the quote's failed_pair_refund.status from window_open to due and issues the refund straight away. Each failed pair is refunded at exactly its own price, in whole cents with any rounding going to you, and the refund can never be sent twice. No request body and no approval step.

{
  "status_code": 200,
  "data": {
    "quote_id": "qt-a1b2c3d4-e5f",
    "failed_pair_refund": { "status": "issued", "amount_due": 0, "amount_refunded": 3.00, "pairs": [{ "doc": "api-reference.html", "lang": "zh-TW" }], "window_ends_at": "", "refunded_at": "2026-10-10T15:20:41Z" },
    "message": "Refund requested for 1 translations. It's being processed now."
  }
}
StatusKeyCondition
404tbts_refund_failed_unavailableOnly while automatic refunds for incomplete translations are paused (they have been on since October 11, 2026). Checked before authentication, so this one answer is always in English. The free retry (15.10) still works
404tbts_job_not_foundQuote not found on your account
409tbts_refund_failed_not_availableThere is no refund to request right now: nothing failed, the retry is running, or the refund is already due or issued
503tbts_status_unavailableThe ledger is briefly unreachable

If you do nothing: 72 hours after the job finishes, anything still undelivered is refunded automatically, and if your free retry also fails, the refund happens as soon as it finishes. A 15-minute sweeper inside the LPO keeps those promises without anyone watching, and you get one email when the refund is issued.

15.12 GET /translate/languages — Supported Languages

127 languages, every one usable as a source or a target, English included, translated directly from one to the other with no pivot through a third language. The list below is generated from the same catalog the quote checks; a code that isn't on it is refused before anything is priced or charged.

GET /translate/languages returns it live. It is public (no key) and cacheable for five minutes (Cache-Control: public, max-age=300), so your integration never has to hard-code the list:

{
  "status_code": 200,
  "data": {
    "count": 127,
    "languages": [
      { "code": "af", "english_name": "Afrikaans", "native_name": "Afrikaans", "script": "Latn", "rtl": false, "word_spaced": true, "base_lang": "", "status": "supported" }
    ],
    "limits": { "max_langs_per_quote": 12, "max_docs_per_quote": 42, "max_doc_kb": 300, "max_quotes_per_batch": 9 },
    "aliases": { "nb": "no", "zh-Hant": "zh-TW", "ar-EG": "ar" },
    "source": "aurora"
  }
}

count is the number of languages you can order right now. languages lists every targetable row, and a row an operator has paused shows status: "blocked" rather than disappearing. source says where the catalog was read from (aurora, valkey or empty); an empty catalog answers 200 with count: 0, and a quote then answers 503 tbts_langs_unavailable rather than guessing.

CodeLanguageCodeLanguageCodeLanguage
afAfrikaanssqAlbanianamAmharic
arArabicarzArabic (Egyptian)apcArabic (Levantine)
acmArabic (Mesopotamian)aryArabic (Moroccan)arsArabic (Najdi)
acqArabic (Ta'izzi-Adeni)aebArabic (Tunisian)hyArmenian
asAssameseastAsturianawaAwadhi
azAzerbaijanibanBalinesebjnBanjar
baBashkireuBasquebeBelarusian
bnBengalibhoBhojpuribsBosnian
bgBulgarianmyBurmeseyueCantonese
caCatalancebCebuanohneChhattisgarhi
zhChinese (Simplified)zh-TWChinese (Traditional)hrCroatian
csCzechdaDanishfa-AFDari
nlDutchenEnglishetEstonian
foFaroesefiFinnishfrFrench
fr-CAFrench (Canada)furFriulianglGalician
kaGeorgiandeGermanelGreek
guGujaratihtHaitian CreolehaHausa
heHebrewhiHindihuHungarian
isIcelandiciloIlocano (Iloko)idIndonesian
gaIrishitItalianjaJapanese
jvJavanesekeaKabuverdianuknKannada
kkKazakhkmKhmerkoKorean
CodeLanguageCodeLanguageCodeLanguage
loLaolvLatvianlijLigurian
liLimburgishltLithuanianlmoLombard
lbLuxembourgishmkMacedonianmagMagahi
maiMaithilimsMalaymlMalayalam
mtMaltesemrMarathiminMinangkabau
mnMongolianneNepalinoNorwegian (Bokmål)
nnNorwegian (Nynorsk)ocOccitanorOdia (Oriya)
pagPangasinanpapPapiamentopsPashto
faPersianplPolishptPortuguese (Brazil)
pt-PTPortuguese (Portugal)paPunjabiroRomanian
ruRussianscSardiniansrSerbian
scnSicilianszlSilesiansdSindhi
siSinhalaskSlovakslSlovenian
soSomaliesSpanishes-MXSpanish (Mexico)
suSundaneseswSwahilisvSwedish
tlTagalog (Filipino)tgTajiktaTamil
ttTatarteTeluguthThai
tpiTok PisintrTurkishukUkrainian
urUrduuzUzbekvecVenetian
viVietnamesewarWaraycyWelsh
yiYiddish (Eastern)

15.13 POST /admin/translate/refund/{quote_id}

Issues a full or partial refund for a completed translation purchase. Admin-only — requires X-Admin-Key header. Refunds are processed via Stripe and the customer receives a confirmation email automatically.

Path Parameters

ParameterTypeDescription
quote_idstringThe quote ID from the original translation purchase (e.g., qt-a1b2c3d4-e5f)

Request Body (Optional)

POST /admin/translate/refund/qt-a1b2c3d4-e5f
X-Admin-Key: your-admin-key
Content-Type: application/json

{
  "amount": 6.00,
  "reason": "Customer unsatisfied with Japanese translation quality"
}
FieldTypeRequiredDescription
amountnumberNoPartial refund amount in USD. If omitted or equal to the original charge, a full refund is issued.
reasonstringNoReason for the refund. Included in the customer's refund confirmation email and stored as metadata on the Stripe refund.

Response (200 OK)

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/admin/translate/refund/qt-a1b2c3d4-e5f] [200]",
  "status_code": 200,
  "data": {
    "status": "refunded",
    "quote_id": "qt-a1b2c3d4-e5f",
    "refund_id": "re_1234567890",
    "refund_amount": 15.00,
    "original_amount": 15.00,
    "refunded_total": 15.00,
    "kind": "admin",
    "partial": false,
    "stripe_status": "succeeded"
  },
  "error": ""
}

Validation rules:

  • Quote must be in accepted or partially_refunded state (already paid)
  • Cannot refund a quote that is pending, expired, superseded, or already fully refunded
  • The amount is clamped to what is left: the quote's total_price minus refunded_total, and never more than remains on the Stripe PaymentIntent (which matters for a batch, where several quotes share one charge). So a refund can never exceed what was paid, whichever path issued the earlier ones
  • refunded_total is the running total refunded on this quote by every kind (admin, cancel, failed pairs); kind is admin for this endpoint
  • Refund is processed via Stripe SDK — appears on customer's statement within 5-10 business days

15.14 Brand Terms Protection

Customers can configure up to 150 brand terms that are automatically protected during every translation job. These terms are never translated or transliterated — they appear exactly as written in every target language. Terms are managed via the dashboard API and apply to all future translation jobs for the account.

GET /dashboard/api/protected-terms

Returns the customer's current list of protected brand terms. Requires dashboard Bearer token authentication.

Response (200 OK)

{
  "status_code": 200,
  "data": {
    "terms": ["Acme Corp", "DataForge", "CloudSync API", "John Smith"],
    "count": 4,
    "limit": 150
  },
  "error": ""
}

PUT /dashboard/api/protected-terms

Replaces the customer's entire brand terms list. Requires dashboard Bearer token authentication.

Request Body

PUT /dashboard/api/protected-terms
Authorization: Bearer your-session-token
Content-Type: application/json

{
  "terms": ["Acme Corp", "DataForge", "CloudSync API", "John Smith", "SyncEngine"]
}
FieldTypeRequiredDescription
termsarray of stringsYesThe complete list of brand terms to protect. Replaces any existing terms.

Response (200 OK)

{
  "status_code": 200,
  "data": {
    "message": "Saved 5 brand terms",
    "terms": ["Acme Corp", "DataForge", "CloudSync API", "John Smith", "SyncEngine"],
    "count": 5,
    "limit": 150
  },
  "error": ""
}

Limits & validation:

  • Maximum 150 terms per account
  • Maximum 100 characters per term
  • Duplicates are automatically removed
  • Empty strings are ignored
  • Terms apply to all future translation jobs — no per-request overrides
  • Terms are merged with the engine's internal protected terms (code blocks, Mermaid diagrams, technical identifiers are always protected regardless of this list)

15.15 Dashboard Translation Endpoints

Authenticated dashboard endpoints for translation spend tracking, refund history, retries, refunds, downloads and job completion notifications. All require Bearer token authentication via the dashboard session. Every one of them reads across all of your translation keys, revoked keys included, so rotating a key never hides your history.

GET /dashboard/api/translate/spend

Returns translation spend aggregated by month and model for the last 6 months. Customers see only their own spend; admins see all accounts.

Response (200 OK)

{
  "status_code": 200,
  "data": {
    "summary": {
      "total_revenue_usd": 247.50,
      "total_bedrock_cost_usd": 89.30,
      "total_jobs": 18,
      "total_pairs": 142
    },
    "months": [
      {
        "month": "2026-05",
        "revenue_usd": 132.00,
        "bedrock_cost_usd": 48.20,
        "jobs": 10,
        "pairs": 78,
        "by_model": {
          "claude-haiku-5.5": {"jobs": 3, "pairs": 24, "cost_usd": 9.60},
          "claude-sonnet-4.6": {"jobs": 6, "pairs": 48, "cost_usd": 79.20},
          "claude-opus-4.8": {"jobs": 1, "pairs": 6, "cost_usd": 43.20}
        }
      }
    ]
  },
  "error": ""
}

GET /dashboard/api/translate/refunds

Returns refund history for translation purchases. A customer sees their own refunds from every one of their translation keys; an admin sees every account. kind tells the three refund paths apart: failed_pairs (the automatic refund for incomplete translations), cancel and admin. refund_amount is the quote's running refunded_total.

Response (200 OK)

{
  "status_code": 200,
  "data": {
    "refunds": [
      {
        "quote_id": "qt-a1b2c3d4-e5f",
        "doc_name": "api-reference.html",
        "lang_count": 5,
        "model": "qwen3-235b",
        "original_price": "15.00",
        "refund_amount": "3.00",
        "refund_id": "re_1234567890",
        "state": "partially_refunded",
        "refunded_at": "2026-05-22T14:30:00Z",
        "accepted_at": "2026-05-22T13:58:10Z",
        "customer_email": "user@example.com",
        "kind": "failed_pairs"
      }
    ],
    "count": 1
  },
  "error": ""
}

POST /dashboard/api/translate/cancel/{job_id}

The endpoint behind the Cancel Translation button in your Account Dashboard. It follows the same policy and runs the same cancel path as POST /translate/cancel/{quote_id} (15.7), so the refund is identical whichever one you use. The only differences are that it takes the job_id your dashboard already holds and authenticates with your dashboard session instead of an API key. No request body is needed.

Response (200 OK)

{
  "status_code": 200,
  "data": {
    "job_id": "019e4b8a37bc-1f2e3d4c5b6a7980",
    "state": "cancelled",
    "refund_status": "calculating"
  },
  "error": ""
}

Refusals match 15.7: 404 when the job is not on your account, 409 when it has already been cancelled, has already finished, or was never accepted. Cancelling a running free retry stops only the retry, exactly as in 15.7.

The buttons know when to appear: GET /dashboard/api/translate/status/{job_id} always carries three cancel fields. can_cancel is true only while the job is queued or running, which is what shows or hides the button. For a cancelled job, refund_status and refund_due report the refund as it moves from calculating to pending_approval to approved, so the dashboard stays in step with your money without a page refresh. It is built by the same code as the public status (15.8), so it also carries quote_id, retry, failed_pair_refund, downloads_ready, model_display, model_by_lang and is_retry: the Retry button shows when retry.allowed is true, "Refund instead" when failed_pair_refund.status is window_open, and Download when downloads_ready is true.

POST /dashboard/api/translate/retry-failed/{job_id}

The Retry button: the one free retry per quote (15.10), started from the dashboard with the job ID it already holds. Always available.

{
  "status_code": 200,
  "data": {
    "job_id": "019e4c02d9f1-6b5a4c3d2e1f0a9b",
    "quote_id": "qt-a1b2c3d4-e5f",
    "state": "queued",
    "submitted_at": "2026-10-10T15:14:02Z",
    "retry_of": "019e4b8a37bc-1f2e3d4c5b6a7980",
    "docs": ["api-reference.html"],
    "langs": ["zh-TW"]
  },
  "error": ""
}

job_id here is the new retry job. Refusals use the dashboard error keys translate_retry_not_available, translate_retry_in_progress, translate_retry_closed and translate_retry_window_expired, with the same meanings as 15.10.

POST /dashboard/api/translate/refund-failed/{job_id}

The "Refund instead" button (15.11). Response {job_id, quote_id, pairs, failed_pair_refund}. If automatic failed-pair refunds are ever paused, it answers 404 translate_refund_failed_unavailable, and when there is nothing to refund, 409 translate_refund_failed_not_available.

GET /dashboard/api/translate/download/{job_id}

Signed download links for every delivered document and language, the same set as GET /translate/download/{job_id}. Allowed once no job of the quote is active and at least one pair was delivered, so partial and cancelled jobs can collect what they received.

GET /dashboard/api/translate/notifications

Returns unread notification count and recent completed job summaries across every translation API key of the authenticated user.

Response (200 OK)

{
  "status_code": 200,
  "data": {
    "unread_count": 2,
    "notifications": [
      {
        "job_id": "019e5bdc36a5-c87d32a56bc39bee",
        "completed_at": "2026-05-24T21:25:00Z",
        "state": "succeeded",
        "docs": ["Trinity-Beast-Translation-Service.html"],
        "langs": 11,
        "succeeded_pairs": 11,
        "failed_pairs": 0
      }
    ]
  },
  "error": ""
}

POST /dashboard/api/translate/notifications/seen

Marks all notifications as seen. Resets the badge count to 0. No request body required.

Response (200 OK)

{
  "status_code": 200,
  "data": {
    "message": "Notifications marked as seen"
  },
  "error": ""
}

Polling behavior: The dashboard SPA polls /dashboard/api/translate/notifications every 60 seconds. The seen state is stored per user in Valkey at dashboard:notifications:seen:user:{users.id} with a 30-day TTL, so it follows you across key rotations; the old per-key value (dashboard:notifications:seen:{api_key_id}) is read once as a fallback. Only translation job completions generate notifications — quotes, cancellations, and in-progress updates do not.

16. i18n Key Translation API

Automated multi-lingual JSON key translation for the website's internationalization system. Detects missing keys across all 11 target languages and fills them via Amazon Bedrock (Claude) or manual injection. The website serves language JSON from Valkey via GET /public/lang/{code} — this endpoint keeps all languages in sync with English.

16.1 Translate i18n Keys

Detects missing keys and translates them. Bedrock mode is async (fire-and-forget) — returns a job ID immediately and processes in the background. Manual mode and dry-run are synchronous.

PropertyValue
EndpointPOST /admin/translate-i18n-keys
AuthX-Admin-Key header
Content-Typeapplication/json

Request Body

FieldTypeDefaultDescription
modestring"bedrock""bedrock" (async, AI-powered) or "manual" (sync, pre-translated values)
namespacestring"*"Target namespace prefix (e.g., "translate", "docLibrary", "*" for all)
target_langsstring[]["all"]["all"] or specific codes like ["es","pt","fr"]
dry_runboolfalseIf true, report missing keys without writing
keysobjectnullManual mode only — pre-translated key/value pairs per language
sync_s3boolfalseReserved — sync updated JSON to S3 after translation

Example — Bedrock Mode (Async)

POST /admin/translate-i18n-keys
X-Admin-Key: {admin_key}
Content-Type: application/json

{
  "namespace": "translate",
  "target_langs": ["all"]
}

Response (202 Accepted)

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/admin/translate-i18n-keys] [202]",
  "status_code": 202,
  "data": {
    "status": "accepted",
    "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "mode": "bedrock",
    "namespace": "translate",
    "source_keys": 62,
    "target_langs": ["es","pt","fr","de","ru","hi","ur","it","ar","ja","zh"],
    "message": "Translation job started. Poll /admin/translate-i18n-keys/status/{job_id} for progress."
  }
}

Example — Dry Run (Sync)

POST /admin/translate-i18n-keys
X-Admin-Key: {admin_key}
Content-Type: application/json

{
  "namespace": "*",
  "dry_run": true,
  "target_langs": ["es","ja"]
}

Response (200 OK)

{
  "data": {
    "status": "dry_run",
    "namespace": "*",
    "source_keys": 1583,
    "languages": [
      {"lang": "es", "missing_keys": ["dailyReports.loadError", "..."], "status": "dry_run"},
      {"lang": "ja", "missing_keys": ["dailyReports.loadError", "..."], "status": "dry_run"}
    ]
  }
}

Example — Manual Mode (Sync)

POST /admin/translate-i18n-keys
X-Admin-Key: {admin_key}
Content-Type: application/json

{
  "mode": "manual",
  "namespace": "translate",
  "target_langs": ["all"],
  "keys": {
    "translate.hero.title": {
      "es": "Motor de Traducción",
      "pt": "Motor de Tradução",
      "fr": "Moteur de Traduction",
      "de": "Übersetzungsmaschine",
      "ru": "Движок перевода",
      "hi": "अनुवाद इंजन",
      "ur": "ترجمہ انجن",
      "it": "Motore di Traduzione",
      "ar": "محرك الترجمة",
      "ja": "翻訳エンジン",
      "zh": "翻译引擎"
    }
  }
}

16.2 Check Translation Job Status

Poll the status of an async Bedrock translation job. Jobs are stored in Valkey with a 24-hour TTL.

PropertyValue
EndpointGET /admin/translate-i18n-keys/status/{job_id}
AuthX-Admin-Key header

Response (200 OK)

{
  "data": {
    "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "completed",
    "mode": "bedrock",
    "namespace": "translate",
    "source_keys": 62,
    "target_langs": ["es","pt","fr","de","ru","hi","ur","it","ar","ja","zh"],
    "started_at": "2026-05-22T19:10:00Z",
    "completed_at": "2026-05-22T19:12:34Z",
    "total_translated": 682,
    "languages": [
      {"lang": "es", "translated": 62, "status": "ok"},
      {"lang": "pt", "translated": 62, "status": "ok"},
      {"lang": "fr", "translated": 62, "status": "ok"}
    ]
  }
}

16.3 Supported Languages

CodeLanguageCodeLanguage
esSpanisharArabic
ptPortuguesejaJapanese
frFrenchzhChinese (Simplified)
deGermanitItalian
ruRussianhiHindi
urUrdu

16.4 How It Works

  1. Reads lang:en from Valkey (source of truth for all i18n keys)
  2. Flattens nested JSON into dot-notation keys (e.g., translate.hero.title)
  3. For each target language, reads lang:{code} and identifies missing keys
  4. Bedrock mode: translates in batches of 15 keys per API call (reliable JSON output)
  5. Validates every key survives the round-trip (falls back to English if Bedrock drops one)
  6. Merges translations into the existing language JSON (preserves all other keys)
  7. Writes updated JSON back to Valkey — immediately available via GET /public/lang/{code}

16.5 Notes