Guides
Large Payload Lane
Send files above the 4.5 MB direct limit: client-side AES-256-GCM encryption and one-time private staging.
Preview
The Large Payload Lane ships as a preview and becomes generally available in v1.1.
Tokentide accepts request bodies up to 4.5 MB directly (a limit of the hosting platform; streamed responses and binary media can be any size, non-streaming JSON responses up to 64 MB, see Rate limits). Venice accepts files up to 25 MB. For inputs between the two, use one of these options, in order of preference:
- Public URL inputs. For
/v1/image/edit,/v1/image/multi-edit,/v1/image/background-removeandstyle_references, pass a publichttps://URL instead of the bytes. Venice fetches it directly (no redirects, at most 25 MB), so the bytes never transit Tokentide. - Encrypted ephemeral staging (this page): for
/v1/audio/transcriptionsand image files that have no URL. You encrypt the file on your side, upload the ciphertext to a one-time private slot, and reference it in the request. - Compression. Most transcription inputs fit under 4.5 MB when compressed: Opus at 24 kbps is about 3 minutes of speech per MB.
A body above the direct limit answers 413 payload_too_large, and the message points here.
How it works
1.Your client: Encrypt the file with a random key K (AES-256-GCM)
2.Your client → Tokentide API: POST /v1/uploads with purpose, bytes and sha256
3.Tokentide API → Your client: upload_id, upload_url, headers, expires_at
4.Your client → Private storage: PUT the ciphertext to upload_url
5.Your client → Tokentide API: Inference call with X-Tokentide-Upload: upload_id; key=K
6.Tokentide API → Private storage: Read the ciphertext
7.Tokentide API: Verify sha256, decrypt in memory
8.Tokentide API → Venice: Forward the plaintext as the multipart file part
9.Venice → Tokentide API: Result
10.Tokentide API → Private storage: Delete the ciphertext
11.Tokentide API → Your client: Result
- The ciphertext goes straight from your client to private storage. It never passes through Tokentide’s functions on the way up.
- The key
Kreaches Tokentide only in the request header, over TLS. It is used in memory and never stored or logged, so ciphertext left behind is useless. - The object is deleted right after Venice responds. A sweeper deletes anything older than 15 minutes.
- An upload slot is single use, valid for 10 minutes, and usable only by the account that created it. A request that fails with it (wrong key, checksum mismatch) uses it up too.
- Each account can request 20 slots per 10 minutes, and only while it has a positive balance.
Encryption format
The format is fixed and shared by the TypeScript and Python SDK helpers:
The nonce travels inside the framed ciphertext, so the header only carries K.
offset 0 12 n − 16 n
├──nonce───┼──────────── ciphertext ─────────────┼──── tag ─────┤Step by step
Encrypt locally
TypeScript (Web Crypto) const key = crypto.getRandomValues(new Uint8Array(32)); const nonce = crypto.getRandomValues(new Uint8Array(12)); const aesKey = await crypto.subtle.importKey('raw', key, 'AES-GCM', false, ['encrypt']); // Web Crypto returns ciphertext ‖ tag; prepend the nonce to get the framed object. const sealed = new Uint8Array( await crypto.subtle.encrypt({ name: 'AES-GCM', iv: nonce, tagLength: 128 }, aesKey, plaintext), ); const framed = new Uint8Array(12 + sealed.length); framed.set(nonce, 0); framed.set(sealed, 12);Request an upload slot
bytesandsha256describe the ciphertext you will upload, never the plaintext: a hash of the plaintext would be a fingerprint of your file.curl curl https://api.tokentide.app/v1/uploads \ -H "Authorization: Bearer $TOKENTIDE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "purpose": "audio_transcription", "bytes": 10000028, "sha256": "3f7a…(64 lowercase hex characters)…", "filename": "meeting.m4a", "content_type": "audio/mp4" }'200 OK { "object": "upload", "upload_id": "upl_01JDV8K3M4N5P6Q7R8S9T0V1W2", "upload_url": "https://…", "method": "PUT", "headers": { "content-type": "application/octet-stream" }, "expires_at": "2026-10-31T18:10:00.000Z", "max_bytes": 10000028, "framing": "nonce(12) || ciphertext || tag(16), AES-256-GCM" }Upload the ciphertext
PUTthe framed bytes toupload_urlwith the returnedheaders, beforeexpires_at:curl curl -X PUT "$UPLOAD_URL" -H "content-type: application/octet-stream" --data-binary @meeting.m4a.encCall the endpoint with the upload reference
Send the request as usual, but leave out the file and add
X-Tokentide-Upload:Request header X-Tokentide-Upload: upl_01JDV8K3M4N5P6Q7R8S9T0V1W2; key=q3Zk…(44 base64 characters)…=curl curl https://api.tokentide.app/v1/audio/transcriptions \ -H "Authorization: Bearer $TOKENTIDE_API_KEY" \ -H "X-Tokentide-Upload: $UPLOAD_ID; key=$KEY_BASE64" \ -F model=openai/whisper-large-v3 \ -F response_format=jsonTokentide reads the ciphertext, checks it against the declared
sha256, decrypts it in memory, forwards it to Venice as the multipart file part, and deletes it as soon as Venice responds. To parse the header yourself, split on the first;, then on the first=afterkey(base64 can end with=).
With the SDK helpers
The SDKs do steps 1–3 for you and return the header to send. The other request fields can be sent as JSON: Tokentide turns them into multipart fields and adds the decrypted file part.
import { readFile } from 'node:fs/promises';
import { TokentideClient, largePayload } from '@tokentide/sdk';
const client = new TokentideClient({ apiKey: process.env.TOKENTIDE_API_KEY });
const upload = await largePayload(client, await readFile('meeting.m4a'), {
purpose: 'audio_transcription',
filename: 'meeting.m4a',
contentType: 'audio/mp4',
});
const res = await client.inference<{ text: string }>(
'/audio/transcriptions',
{ model: 'openai/whisper-large-v3', response_format: 'json' },
{ upload: upload.headerValue },
);
console.log(res.data.text, res.meta.chargeUsd);The Python helper needs the optional cryptography package: pip install "tokentide[crypto]". Multipart works too
(send every field except the file part, plus upload.headers), for example with curl as in step 4. See
SDK helpers for the full API.
Errors
Request a new slot after any failure; slots are never reused.