Authentication#
All TetraFi API requests require authentication via Bearer token. This page covers API key management, environments, rate limits, error handling, and pagination.
30-second proof of life. Set your API key and hit the status endpoint. If you get 200 OK you're authenticated and ready to make real requests:
1export TETRAFI_API_KEY=tfk_test_...2curl -H "Authorization: Bearer $TETRAFI_API_KEY" https://sandbox.tetrafi.io/api/v1/health3# → { "ok": true, "environment": "sandbox", "version": "v1" }API Keys#
TetraFi uses two types of API keys:
| Type | Prefix | Environment | Real Funds |
|---|---|---|---|
| Sandbox | tfk_test_ | Testing & development | No |
| Production | tfk_live_ | Live trading | Yes |
Both key types work identically - sandbox never touches real funds and compliance checks are mocked. Generate keys from the TetraFi dashboard.
tfk_{env}_{key_id}_{random_hex} - e.g., tfk_live_a1b2c3_8f4e...
Never expose API keys in client-side code. Store them in environment variables and use server-side requests only. Key rotation: generate a new key, update your config, the old key expires in 24 hours.
Endpoint Auth Map#
Not every endpoint needs authentication. The table below shows the auth tier per endpoint:
| Endpoint | Auth Tier | Header Required |
|---|---|---|
GET /api/v1/health | Public | None |
POST /api/v1/quotes | Public | None |
GET /api/v1/quotes/{rfqId} | Public | None |
POST /api/v1/orders | Optional Auth | API key recommended (Direct path) or none (Compliant path) |
GET /api/v1/orders/{id} | Public | None |
POST /api/v1/auth/challenge | Public | None |
POST /api/v1/auth/verify | Public | None |
GET /api/v1/auth/me | Required | JWT (Bearer) |
X-TetraFi-Attestation is only required on compliance-sensitive operations (e.g., POST /orders when using the Compliant path). It is optional on public endpoints like POST /quotes.
Making Authenticated Requests#
Include your API key in the Authorization header. Optionally include your compliance attestation:
1const response = await fetch("https://api.tetrafi.io/api/v1/quotes", {2 method: "POST",3 headers: {4 "Authorization": "Bearer tfk_live_...",5 "Content-Type": "application/json",6 // Optional: compliance attestation hash7 "X-TetraFi-Attestation": "0x...",8 },9 body: JSON.stringify({10 pair: "USDC/USDT",11 side: "buy",12 amount: "1000000",13 }),14});1516const data = await response.json();Environments#
| Sandbox | Production | |
|---|---|---|
| Base URL | https://sandbox.tetrafi.io/api/v1 | https://api.tetrafi.io/api/v1 |
| WebSocket | wss://sandbox.tetrafi.io/api/v1/ws | wss://api.tetrafi.io/api/v1/ws |
| API Key Prefix | tfk_test_ | tfk_live_ |
| Real Funds | No | Yes |
| Rate Limits | Same | Same |
| Compliance | Mocked | Enforced |
| Settlement | Testnet (Sepolia) | Mainnet |
1# .env.local - sandbox2TETRAFI_API_KEY=tfk_test_...3TETRAFI_BASE_URL=https://sandbox.tetrafi.io/api/v145# .env.production6TETRAFI_API_KEY=tfk_live_...7TETRAFI_BASE_URL=https://api.tetrafi.io/api/v1Rate Limits#
Rate limits are enforced per API key. Default limits:
| Metric | Limit |
|---|---|
| Requests/min | 120 |
| Burst/sec | 20 |
| WebSocket connections | 5 |
Higher limits are available on request for high-volume integrations.
Rate Limit Headers#
Every response includes current rate limit information:
X-RateLimit-Limitnumber- Maximum requests allowed per window
X-RateLimit-Remainingnumber- Remaining requests in current window
X-RateLimit-Resettimestamp- UTC epoch when the window resets
Retry-Afternumber- Seconds to wait (on 429 responses only)
Handling Rate Limits#
1async function requestWithBackoff<T>(2 fn: () => Promise<T>,3 maxRetries = 54): Promise<T> {5 for (let attempt = 0; attempt <= maxRetries; attempt++) {6 try {7 return await fn();8 } catch (e) {9 if (e instanceof RateLimitError && attempt < maxRetries) {10 const wait = e.retryAfter ?? Math.min(30, 2 ** attempt);11 console.log(`Rate limited - waiting {wait}s (attempt {attempt + 1})`);12 await sleep(wait * 1000);13 } else {14 throw e;15 }16 }17 }18 throw new Error("Max retries exceeded");19}Error Handling#
All errors follow a consistent machine-readable format:
1{2 "error": {3 "type": "invalid_request",4 "code": "pair_not_supported",5 "message": "The trading pair DOGE/SHIB is not supported.",6 "param": "pair",7 "doc_url": "https://tetrafi.io/docs/errors#pair_not_supported"8 }9}HTTP Status Codes#
| Status | Type | Description |
|---|---|---|
400 | invalid_request | Malformed request, missing required param |
401 | authentication_error | Invalid or expired API key |
403 | compliance_error | Participant not compliant to trade |
404 | not_found | Resource (RFQ, quote, settlement) not found |
409 | conflict | Duplicate request (idempotency violation) |
422 | validation_error | Param values are valid types but fail business rules |
429 | rate_limit_error | Too many requests - use Retry-After header |
500 | api_error | Internal server error - retry with backoff |
503 | maintenance | Scheduled maintenance - check status page |
Common Error Codes#
| Code | Meaning |
|---|---|
pair_not_supported | The pair isn't available on the requested corridor |
amount_too_small | Below minimum trade size ($10,000 USD equivalent) |
amount_too_large | Exceeds maximum trade size for your tier |
attestation_expired | Compliance attestation needs renewal |
corridor_unavailable | No solvers covering the requested corridor |
rfq_expired | Quote window has passed - create a new RFQ |
Idempotency#
For POST requests that create resources, include an Idempotency-Key header to safely retry failed requests:
1const response = await fetch("https://api.tetrafi.io/api/v1/quotes", {2 method: "POST",3 headers: {4 "Authorization": "Bearer tfk_live_...",5 "Content-Type": "application/json",6 // Unique per request - same key = same response (for 24h)7 "Idempotency-Key": crypto.randomUUID(),8 },9 body: JSON.stringify({ pair: "USDC/USDT", side: "buy", amount: "1000000" }),10});Pagination#
Long lists (quotes, audit entries, settlements) use cursor-based pagination:
1// First page2const response = await fetch("https://api.tetrafi.io/api/v1/settlements?limit=25", {3 headers: { "Authorization": "Bearer tfk_live_..." },4});5const { data, has_more, next_cursor } = await response.json();67// Next page8if (has_more) {9 const next = await fetch(10 `https://api.tetrafi.io/api/v1/settlements?limit=25&after=${next_cursor}`,11 { headers: { "Authorization": "Bearer tfk_live_..." } }12 );13}| Parameter | Type | Description |
|---|---|---|
| limit | number | Number of results per page. Max 100, default 25. |
| after | string | Cursor from previous response's next_cursor. Fetches next page. |
| before | string | Fetch the previous page (reverse pagination). |
Response shape:
1{2 "data": [...],3 "has_more": true,4 "next_cursor": "cursor_abc123",5 "total_count": 12476}Common Auth Issues#
Quick Setup#
Authentication Setup
1import { TetraFi } from '@tetrafi/sdk';23const tetrafi = new TetraFi({4 apiKey: process.env.TETRAFI_API_KEY!,5 environment: 'sandbox',6});78// Verify connection9const status = await tetrafi.status();10console.log('Connected:', status.ok);