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=sk_test_...2curl -H "Authorization: Bearer $TETRAFI_API_KEY" https://sandbox.tetrafi.io/v1/status3# → { "ok": true, "environment": "sandbox", "version": "v1" }TetraFi uses two types of API keys:
| Type | Prefix | Environment | Real Funds |
|---|---|---|---|
| Sandbox | sk_test_ | Testing & development | No |
| Production | sk_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.
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.
Include your API key in the Authorization header. Optionally include your compliance attestation:
1const response = await fetch("https://api.tetrafi.io/v1/rfq", {2 method: "POST",3 headers: {4 "Authorization": "Bearer sk_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();| Sandbox | Production | |
|---|---|---|
| Base URL | https://sandbox.tetrafi.io/v1 | https://api.tetrafi.io/v1 |
| WebSocket | wss://sandbox.tetrafi.io/v1/ws | wss://api.tetrafi.io/v1/ws |
| API Key Prefix | sk_test_ | sk_live_ |
| Real Funds | No | Yes |
| Rate Limits | Same | Same |
| Compliance | Mocked | Real |
| Settlement | Simulated | On-chain |
1# .env.local - sandbox2TETRAFI_API_KEY=sk_test_...3TETRAFI_BASE_URL=https://sandbox.tetrafi.io/v145# .env.production6TETRAFI_API_KEY=sk_live_...7TETRAFI_BASE_URL=https://api.tetrafi.io/v1Rate limits are enforced per API key across all endpoints:
| Tier | Requests/min | Burst/sec | WebSocket Connections |
|---|---|---|---|
| Free | 60 | 10 | 1 |
| Growth | 600 | 50 | 5 |
| Enterprise | 6,000 | 200 | 25 |
Every response includes current rate limit information:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed per window |
X-RateLimit-Remaining | Remaining requests in current window |
X-RateLimit-Reset | UTC epoch when the window resets |
Retry-After | Seconds to wait (on 429 responses only) |
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}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}| 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 |
| 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 |
For POST requests that create resources, include an Idempotency-Key header to safely retry failed requests:
1const response = await fetch("https://api.tetrafi.io/v1/rfq", {2 method: "POST",3 headers: {4 "Authorization": "Bearer sk_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});Long lists (quotes, audit entries, settlements) use cursor-based pagination:
1// First page2const response = await fetch("https://api.tetrafi.io/v1/settlements?limit=25", {3 headers: { "Authorization": "Bearer sk_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/v1/settlements?limit=25&after=${next_cursor}`,11 { headers: { "Authorization": "Bearer sk_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}