Skip to content

SDK helpers (TS & Python)

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:

HelperTypeScriptPython
Tokentide-native endpointsTokentideClientTokentideClient
Response metadataparseTokentideMeta(headers) → TokentideMetaparse_meta(headers) → TokentideMeta
Request headerstokentideHeaders({...})tokentide_headers(...)
Market helperslowTideWindow, waitForPrice, tableMultiplierAt, firstTimeAtOrBelowlow_tide_window, wait_for_price, table_multiplier_at, first_time_at_or_below
Large Payload LanelargePayload(client, data, { purpose })upload_large_payload(client, data, purpose=...)
Batch schedulingnew TideRunner(client, options).run(jobs)TideRunner(client, **options).run(jobs)

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 reset
Option (TS / Python)DefaultMeaning
apiKey / api_key—tt_live_… or tt_test_…
baseUrl / base_urlproductionhttps://api.tokentide.app/v1, or any base URL including /v1, e.g. a staging host or http://localhost:3000/v1
headers—Added to every request (e.g. x-vercel-protection-bypass for protected previews)
timeoutMs / timeout120 sPer-request timeout
fetch / transportglobal / urllibCustom HTTP layer (tests, proxies)

TokentideClient.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):

MethodEndpoint
market()GET /v1/market (no credentials sent)
status()GET /v1/status (no credentials sent)
models()GET /v1/models with the tokentide pricing extension
account()GET /v1/account
usage(query), usageRecords(query), usageCsv()GET /v1/account/usage (metadata only), one page, every page, or CSV
quote(lane)POST /v1/market/quote
createUpload(body)POST /v1/uploads (prefer largePayload)
inference(path, body, opts), chatCompletions()Non-streaming JSON inference with typed Tokentide headers; returns { data, meta }
request(), raw()Any route; raw() returns the response without throwing (streams, binary bodies)

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.

Field (TS / Python)Header
requestId / request_idX-Request-Id
clientRequestId / client_request_idX-Client-Request-Id (echoed back, except on CDN-cached public answers)
multiplier, multiplierPpmX-Tokentide-Multiplier (string and ppm)
laneX-Tokentide-Lane
epoch, crossEpochX-Tokentide-Epoch, X-Tokentide-Cross-Epoch
listCostUsd, chargeUsdX-Tokentide-List-Cost-USD, X-Tokentide-Charge-USD (non-streaming)
modelPrivacyX-Tokentide-Model-Privacy
modelFallback / model_fallbackX-Tokentide-Model-Fallback
lowtideOpensAtX-Tokentide-Lowtide-Opens-At
retryAfterSecondsRetry-After (seconds or HTTP-date)
rateLimit.{limit, remaining, resetSeconds}X-RateLimit-*-Requests (your Tokentide limits)
debugX-Tokentide-Debug-* (only with X-Tokentide-Debug: 1)

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-Id with every request unless you set one (tokentideHeaders({ clientRequestId }), tokentide_headers(client_request_id=...), or the clientRequestId / client_request_id option of inference()). Turn it off with clientRequestIds: false / client_request_ids=False.
  • TypeScript. The client never logs. Pass onRequestComplete to receive one metadata record per request (method, path, status, requestId, clientRequestId, durationMs, failure), never headers, bodies or credentials, for example onRequestComplete: (r) => console.debug('[tokentide]', r).
  • Python. One DEBUG line per request goes to the tokentide logger (method, path, status, request ids, duration). Nothing is printed unless you configure logging, or set TOKENTIDE_LOG=debug to send the lines to stderr.
  • Debug headers. debug: true / debug=True sends X-Tokentide-Debug: 1; the parsed values are in meta.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

TypeScript
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.
  • lowTideWindow reports 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.
  • waitForPrice polls GET /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 throws TokentideTimeoutError at the deadline.

Large Payload Lane

TypeScript
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); header X-Tokentide-Upload: <upload_id>; key=<base64 of the 32-byte key>.
  • The slot request’s bytes and sha256 describe the ciphertext, never the plaintext.
  • headerValue contains 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);
Option (TS / Python)DefaultMeaning
deadlinerequiredJobs not started by then fail with deadline_exceeded
maxMultiplier / max_multiplierrequiredCap for Low Tide, sent as X-Tokentide-Max-Multiplier
fallbackToStandard / fallback_to_standardfalseMove the remaining jobs to Standard before the deadline
fallbackMaxMultiplier / fallback_max_multipliernoneOptional cap for the Standard fallback
fallbackLeadMs / fallback_lead (s)10 minMinimum lead; the runner starts earlier when the remaining jobs need more time at the observed pace
standardWithinCap / standard_within_capfalseAlso use Standard whenever its live price is within maxMultiplier
concurrency4Parallel requests (new accounts allow 4 in flight)
maxAttempts / max_attempts4Attempts for transient errors (500, 502, a gateway 503, network, timeout)
retryUpstreamTimeouts / retry_upstream_timeoutsfalseRetry 504 upstream_timeout (may be billed twice)
marketPollMs / market_poll (s)30 sMarket re-check interval while waiting
signal / cancel (threading.Event)—Stop submitting; in Python, running requests finish

How it behaves:

  • Every job must be non-streaming (Low Tide refuses stream: true). Results come back in input order with status, lane, attempts, the parsed response (data), its meta, or an error and errorCode.
  • 409 lowtide_closed, 409 lowtide_cap_reached, 409 price_above_limit, 429 rate_limited and 503 answers are honored: the runner waits for Retry-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 a 500/502/503 failure (internal_error, upstream_unavailable, or a gateway 502/503 without a Tokentide code) reuses the same key, so a request that was already charged is rejected as 409 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-in 504 upstream_timeout retry 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.chargeUsd and result.listCostUsd sum the X-Tokentide-Charge-USD and X-Tokentide-List-Cost-USD headers of the successful jobs.