Reference
SDK helpers
TypeScript and Python helpers for Tokentide-native endpoints, response metadata, the Large Payload Lane and the Tide Runner.
Packages
TypeScript: @tokentide/sdk. Python: tokentide. Both are thin, dependency-free helpers; publication to npm and PyPI
is planned.
You do not need an SDK to use Tokentide. Inference works with the OpenAI SDKs, the Vercel AI SDK, LangChain and the Venice SDKs: change the base URL and the key (Quickstart). The helpers cover what those SDKs do not know about:
Neither SDK logs anything. API keys are validated locally (prefix and checksum) and are only ever sent to the configured Tokentide host; the Large Payload Lane’s storage upload carries no Tokentide credentials.
TokentideClient
import { TokentideClient } from '@tokentide/sdk';
const tt = new TokentideClient({ apiKey: process.env.TOKENTIDE_API_KEY });
const market = await tt.market(); // public, CDN-cached for 5 s
const account = await tt.account(); // balance, tier, limits, Low Tide allowance
for await (const r of tt.usageRecords({ from: '2026-10-01T00:00:00Z' }))
console.log(r.request_id, r.charge_micros);
const quote = await tt.quote('lowtide'); // multiplier locked for 60 s, never across the resetTokentideClient.baseUrlFor(domain?) (Python: api_base_url(domain="tokentide.app")) returns https://api.<domain>/v1,
the production base URL by default.
Methods (TypeScript names; Python uses snake_case, and iter_usage for usageRecords):
For streaming, use the OpenAI SDK with the client’s baseUrl.
Response metadata
parseTokentideMeta(headers) (TS) and parse_meta(headers) (Python) turn the response headers into a typed object.
Every field is null/None when the header is absent or malformed; parsing never throws.
With the OpenAI SDKs: client.chat.completions.create(...).withResponse() in TypeScript, or
client.chat.completions.with_raw_response.create(...) in Python, then pass the headers to the parser.
Errors
Non-2xx responses raise TokentideApiError (TS) / TokentideAPIError (Python) with status, code, type,
param, requestId, meta, retryAfterSeconds and retryable. code is a Tokentide code
(Errors) or the upstream code for passed-through Venice validation errors (for example
context_length_exceeded). retryable is true for Low Tide closures, price and rate limits, capacity and transient
server errors; upstream_timeout is deliberately not retryable, because the upstream may already have billed the
attempt. Network failures raise TokentideConnectionError; client-side timeouts raise TokentideTimeoutError.
Every error also carries clientRequestId / client_request_id, and printing an error shows the ids support needs:
String(err) (TS) and str(e) (Python) give
Rate limit reached. (status 429, code rate_limited, request_id req_…, client_request_id …), while message stays
the server’s message. For timeouts and network failures, where no X-Request-Id exists, quote the client request id.
Diagnostics
- Client request ids. Both clients send a random
X-Client-Request-Idwith every request unless you set one (tokentideHeaders({ clientRequestId }),tokentide_headers(client_request_id=...), or theclientRequestId/client_request_idoption ofinference()). Turn it off withclientRequestIds: false/client_request_ids=False. - TypeScript. The client never logs. Pass
onRequestCompleteto receive one metadata record per request (method,path,status,requestId,clientRequestId,durationMs,failure), never headers, bodies or credentials, for exampleonRequestComplete: (r) => console.debug('[tokentide]', r). - Python. One DEBUG line per request goes to the
tokentidelogger (method, path, status, request ids, duration). Nothing is printed unless you configure logging, or setTOKENTIDE_LOG=debugto send the lines to stderr. - Debug headers.
debug: true/debug=TruesendsX-Tokentide-Debug: 1; the parsed values are inmeta.debug. See Debugging a request. - Credentials stay out of error reports. The TypeScript client keeps them in a private field, so inspecting,
logging or serializing it never prints them. In Python,
repr()of the client and of its requests masks them, and connection errors drop the standard library's frames, which hold the request headers, so error trackers that capture local variables never store your key.
Market helpers
import { firstTimeAtOrBelow, lowTideWindow, waitForPrice } from '@tokentide/sdk';
const w = lowTideWindow(await tt.market()); // { open, multiplier, opensAt, reason, closesBy, forecastMultiplierPpm, ... }
const when = firstTimeAtOrBelow((await tt.market()).tide_table.today, 0.6); // Date | null
const snap = await waitForPrice(tt, {
maxMultiplier: 0.45,
lane: 'lowtide',
deadline: Date.now() + 8 * 3_600_000,
});- The published table is a lower bound for the Standard price: surges only push the live price up. The helpers
interpolate the 15-minute curve points of
tide_table.today.curve. lowTideWindowreports whether Low Tide is open, its forecast opening (opens_at) and the multiplier the table implies at that time (max(floor, λ · m_std)). The lane always closes 2 minutes before the 00:00 UTC reset.waitForPricepollsGET /v1/market(at most every 15 s by default) and sleeps towards the table’s forecast crossing, or Low Tide’s forecast opening, when that is further away, so an 8-hour wait costs about a hundred requests. It retries transient errors and throwsTokentideTimeoutErrorat the deadline.
Large Payload Lane
const upload = await largePayload(tt, fileBytes, {
purpose: 'audio_transcription',
filename: 'call.mp3',
contentType: 'audio/mpeg',
});
const res = await tt.inference(
'/audio/transcriptions',
{ model: 'openai/whisper-large-v3' },
{ upload: upload.headerValue },
);Returns { uploadId, headerValue, headers, expiresAt, bytes, sha256 } (Python: upload_id, header_value,
headers, expires_at, bytes, sha256). Send the inference request with the same client.
- Wire format: AES-256-GCM with a random 256-bit key and 96-bit nonce, no AAD; framing
nonce(12) ‖ ciphertext ‖ tag(16); headerX-Tokentide-Upload: <upload_id>; key=<base64 of the 32-byte key>. - The slot request’s
bytesandsha256describe the ciphertext, never the plaintext. headerValuecontains the key. Never log it.- Python needs
pip install "tokentide[crypto]"for the encryption; everything else in the Python SDK is standard library only.
Details and the raw protocol: Large Payload Lane.
Tide Runner
The Tide Runner is Tokentide’s batch mode, run on your side. Give it requests, a deadline and a maximum multiplier. It
submits them to the Low Tide lane whenever the lane is open at or below your cap and, if you allow it, falls back to
Standard early enough to finish before the deadline. Nothing is stored by Tokentide: the queue and the results live in
your process. Because of zero data retention, a lost response can never be replayed, so keep the process running
until run() returns.
import { TideRunner, TokentideClient } from '@tokentide/sdk';
const tt = new TokentideClient({ apiKey: process.env.TOKENTIDE_API_KEY });
const runner = new TideRunner(tt, {
deadline: Date.now() + 8 * 3_600_000,
maxMultiplier: 0.45,
fallbackToStandard: true,
fallbackMaxMultiplier: 0.7,
concurrency: 4,
onEvent: (e) => console.log(e.type), // metadata only
});
const result = await runner.run(
docs.map((text, i) => ({
id: `doc-${i}`,
body: { model: 'deepseek-v4-1-flash', messages: [{ role: 'user', content: text }] },
})),
);
console.log(result.succeeded, result.lanes, result.chargeUsd);How it behaves:
- Every job must be non-streaming (Low Tide refuses
stream: true). Results come back in input order withstatus,lane,attempts, the parsed response (data), itsmeta, or anerroranderrorCode. 409 lowtide_closed,409 lowtide_cap_reached,409 price_above_limit,429 rate_limitedand503answers are honored: the runner waits forRetry-After/X-Tokentide-Lowtide-Opens-At, then resubmits. Rejected requests are never charged.- Every attempt sends an
Idempotency-Key(<runId>-<jobId>-<n>). A retry after a network error, a timeout or a500/502/503failure (internal_error,upstream_unavailable, or a gateway502/503without a Tokentide code) reuses the same key, so a request that was already charged is rejected as409 duplicate_request(the job fails with that code) instead of being charged twice. After a definitive rejection (the answers above, never charged) the next attempt gets a new key. An opted-in504 upstream_timeoutretry also gets a new key, so it may be billed twice. - Account-level errors (
insufficient_balance,spend_limit_reached,invalid_api_key, …) stop the run. result.chargeUsdandresult.listCostUsdsum theX-Tokentide-Charge-USDandX-Tokentide-List-Cost-USDheaders of the successful jobs.