BlackLab docs

BlackLab runs open AI models on hardware enclaves. You sign in with a crypto wallet, pay with USDT on BNB Chain, and your prompts and the replies are never saved to our database or logs.

PieceWhat you get
Web appapp.blacklab.chat: chat and agent mode in the browser, 8 free messages a day, chats kept encrypted on your device.
APIhttps://api.blacklab.chat/v1 speaks the OpenAI chat format, so existing SDKs and tools work. It spends the same balance as the app.
ModelsEach one runs in a trusted execution environment (TEE) operated by Phala, NEAR AI, Chutes or Tinfoil. BlackLab reaches them through RedPill, whose gateway is also a TEE.
PaymentsUSDT on BNB Chain (chain id 56). Network fees are paid in BNB.

Each section describes the code as it runs now. Parts that aren't finished are listed at the end, under Still to come.

Try it free

No wallet is needed to start. Visitors get 8 messages a day on Qwen3.8 27B Uncensored, and each reply is capped at 1,536 tokens.

Instead of a sign-up form or a CAPTCHA, your browser does a little proof of work: it searches for a SHA-256 hash with 18 leading zero bits, which takes a second or two. The API then hands out a token beginning with free_ that stays in your browser.

On our side there's a hash of that token and a count of today's messages, nothing more. Neither your wallet nor your IP address is stored with it. The record is dropped 30 days after the token was made. Conversations started this way aren't saved.

All free use draws on one shared spending budget per UTC day. When it's spent, free messages pause until 00:00 UTC.

Once you connect a wallet, the same 8 daily messages on the same model and cap come first, before any credits are touched. Agent steps and calls made with an API key are always paid.

Wallet sign-in

There are no email or password accounts. A wallet address is the account, and nothing else is asked for.

The app lists the wallets your browser announces (EIP-6963). The one you pick is asked to sign an EIP-4361 (Sign-In with Ethereum) message that names app.blacklab.chat and a one-time nonce valid for 10 minutes. Signing is free and grants no access to funds.

After checking the signature, the API opens a 7-day session in a cookie that page scripts can't read. The database holds a hash of the session token, never the token.

Wallets popular on BNB Chain are shown first: Binance Wallet, Trust Wallet, MetaMask, OKX Wallet, Rabby and Coinbase Wallet. On a phone, most of them open BlackLab inside the wallet's own browser. Smart-contract wallets are verified on chain and can sign in as well; for their saved chats, see Saved chats.

Models

All models run inside hardware enclaves. This table comes from the live API when the page is built. Prices are credits per million tokens, and 100 credits cost 1 USDT.

The current list is at https://api.blacklab.chat/v1/models.

  • Privacy column. Enclave: the model runs in a TEE, and our API hands it your text in memory. Enclave · Encrypted: on top of that, the app can encrypt your messages so only enclaves can open them (see End-to-end encryption).
  • Uncensored models answer lawful questions without refusing or lecturing. The safety limits still apply to them.
  • 18+ content is off until you confirm your age under Settings → Chats. Even then it only works on Qwen3.8 27B Uncensored, the one model whose licence permits it.
  • Tool calling works on every model except DeepSeek V4 Flash, so all the others can run agent mode.

Credits and prices

  • 100 credits per USDT, so one credit is worth a cent. Balances and prices are shown in credits; payments and refunds are in USDT.
  • A reply costs its input tokens at the model's input price plus its output tokens at the output price. You'll find both in the table above and in the model menu of the app.
  • When a request starts, the API reserves the most it could cost: an estimate of the prompt plus the longest permitted reply. When the reply ends, you pay for the token counts the model reported and the unused part of the reservation returns to your balance. A balance too small for the reservation gets a 402.
  • If the model provider fails, the reservation is released and you aren't charged.
  • Credits have no expiry date. They're usable only in BlackLab, can't be passed to another wallet and can't be cashed out. The one exception is the refund of unused credit.

Buying credits

  1. Choose Buy credits (under Settings → Billing, or wherever the app says your balance is empty) and set an amount, from 1 USDT up. The sheet shows the rate, the seller, how many credits you'll get and how much USDT your wallet holds.
  2. The pay button reads Start now. Pressing it is your request for the credits to begin at once, and the line above it says what follows from that: credit you've used can't be withdrawn from the purchase. EU consumer law asks for that explicit step, and the API records it.
  3. Your wallet then asks you to approve a USDT transfer (BEP-20, 18 decimals) from your signed-in address to our treasury address. If it's on a different network, the app first asks it to switch to BNB Chain. The network fee is a little BNB.
  4. After 20 block confirmations, roughly 9 seconds on BNB Chain, the credits land in your balance. A purchase confirmation for each payment can be downloaded as a text file from Settings → Billing.

The API learns about a payment two ways. The app reports the transaction hash straight after sending, and a watcher also scans the chain every 15 seconds. A transfer from a wallet that has never signed in is attached to it at its first sign-in.

Some payments wait for a manual check instead of being credited: one made without the step in point 2, one sent from an address on a sanctions list, and, if a limit is configured, one that takes a wallet past its 30-day cap. Screening uses the EVM addresses on the US OFAC SDN list, downloaded daily, plus a list we keep by hand. The lookup happens on our server; addresses aren't sent anywhere for it.

No payment processor sits in between. Our server knows the treasury's address but holds no key able to spend from it.

Refunds

For 14 days after a payment you can ask for unused credit back, under Settings → Billing → Refund of unused credit. The amount is whichever is smaller: your current balance, or what you paid in that window minus earlier refunds.

The credits leave your balance as soon as you ask. We then send the USDT back by hand, and only ever to the wallet that made the payment. Addresses on a sanctions list can't receive refunds. Outside this rule, credit is never turned back into crypto or money.

Path of a message

  1. The app, or your own code, sends the request over HTTPS to the BlackLab API.
  2. For any request that isn't end-to-end encrypted, the API runs the safety check in memory. Then it reserves credits.
  3. The API builds a fresh request out of a fixed set of fields: messages, sampling and stop settings, seed, response format, tools and reasoning options. Everything else is left behind, including the user, metadata and store fields that some OpenAI clients add by themselves. It goes to RedPill under one API key used for all BlackLab traffic, so the provider sees a single customer rather than individual accounts.
  4. RedPill's gateway runs inside a TEE. It confirms the model's enclave is genuine before it forwards anything, and produces a signed receipt once the reply is done.
  5. The reply flows back through our API unchanged. While that happens, prompt and reply exist only in memory; no code path writes either to the database, a log file or a cache.
Limit to be aware of. The BlackLab API itself is an ordinary cloud service on Railway, not an enclave. For models without end-to-end encryption your text sits in that server's memory as it passes, so the promise we can back up is that it's never stored, not that only an enclave could have read it. With encryption on, the API carries ciphertext it has no key for.

What the server stores

DataReasonKept for
Your wallet addressIt identifies the accountThe life of the account
Balance and the 18+ switchRunning the accountThe life of the account
A usage row per request: type, model, token counts, cost, timeBilling and the charts under Settings → Account. No message textThe life of the account
Payments: transaction, sender, amount, time of your go-ahead, screening time, country if one was givenConsumer law, tax and accountingKept as accounting records
Refund requests: wallet, amount, payout transactionPaying refunds and accountingKept as accounting records
Synced chats as ciphertext, with random id, size and time of change (sync only)Opening them on your other devicesUntil you erase them or turn sync off
Hashes of session tokens and API keysRecognising your requestsSessions 7 days; keys until revoked
Hash of a free_ token and its count for todayThe daily free limit30 days
Today's free-message count for a walletThe daily free limitCleared the next day
Pages, searches and market lookups made for the agentNot stored: fetched, handed to the model, discardedNot stored

The API writes one log line per request: method, path, status and how long it took. Query strings, request bodies and IP addresses stay out of it. Rate limits use a keyed hash of your IP that lives only in memory. Railway (the API) and Vercel (the app) see IP addresses in order to route traffic, as any host does, and Railway's edge keeps its own request logs for a while.

The app loads no trackers, analytics or advertising, and no scripts from other sites. In encrypted chats your browser also contacts RedPill directly, to fetch the enclave's key report.

Saved chats

Signed-in wallets can keep their chats, and they're encrypted in the browser before being stored anywhere. After the first sign-in the app asks where they should live; the choice can be changed later under Settings → Chats.

  • This device only. The browser creates a random AES key that can't be exported and keeps the encrypted chats in IndexedDB. Our server receives nothing. Clearing this site's data in the browser erases them.
  • Sync across devices. The encrypted chats stay in the browser and our server receives a copy, so a second device signed in with the same wallet can open them. Here the key comes from a wallet signature.

Where the sync key comes from:

  1. The app has your wallet sign a fixed message with personal_sign (EIP-191). It starts with "Chat key", says what the signature is for, and ends with "Key version: 1". The wording never changes, because different wording would produce a different key.
  2. HKDF-SHA256 turns the signature into a 256-bit AES-GCM key (salt saved-chats, info aes-256-gcm v1). The browser marks it non-exportable, and it never leaves your device.
  3. Each save runs the whole chat (title, messages, model, times) through AES-256-GCM with a new random 12-byte IV and the chat's id as associated data, and stored as 0x01 ‖ IV ‖ ciphertext. The server turns away anything not in that shape, which would catch a bug trying to upload plain text.

The first time, you sign twice so the app can confirm your wallet returns the same signature each time. Passkey and smart-contract wallets don't, so their chats can only stay on the device.

Worth knowing: if the wallet is lost, nobody can rebuild the key or the synced chats. Any site that gets you to sign that exact message could derive the same key, so sign it only on app.blacklab.chat. Our server can count your chats and see their sizes, ids and change times, but not titles, messages or models.

Erase deletes a chat from the browser and from our database straight away. Switching from sync to this device only downloads every chat first, then deletes the server copies. Limits per wallet: 2 MiB per chat, 50 MiB in total, 5,000 chats.

End-to-end encryption

Models marked Encrypted accept messages encrypted on your device to a key that lives inside an enclave. Our API then forwards ciphertext. It can see the model, the size of the request, the token counts and the time, but not the words.

The key belongs to RedPill's gateway enclave. It decrypts the request and hands it to the enclave running the model, so the text is readable in those two enclaves and nowhere else. The scheme is Phala's ACI E2EE v2: X25519 for the key exchange, then HKDF-SHA256, then AES-256-GCM, with every encrypted field tied to the model, a random nonce and a timestamp.

From your own client:

  1. Make a random 32-byte nonce and fetch the gateway's attestation from RedPill itself: GET https://api.redpill.ai/v1/aci/attestation?nonce={64 hex}. Check that the TDX quote commits to your nonce and the key set, then take its X25519 key.
  2. Encrypt each message's content to that key and call POST https://api.blacklab.chat/v1/chat/completions with five headers: X-E2EE-Version: 2, X-Client-Pub-Key, X-Model-Pub-Key, X-E2EE-Nonce, X-E2EE-Timestamp. The model name bound into each field is the model's e2eeModel value from /v1/models, not our id.
  3. The reply comes back encrypted to your client key, with X-E2EE-Applied: true. Token usage stays readable; billing relies on it.

RedPill rejects a timestamp more than 300 seconds off and a nonce it has seen before. Sending encryption headers for a model without encryption returns 400 e2ee_unsupported.

In the app all of this happens automatically when you're signed in, in chat mode, on an Encrypted model. The browser fetches the key report from RedPill itself, so our API can't substitute its own key. It then verifies the nonce binding, encrypts every message on the device (the default instruction included) and decrypts the reply as it streams. A reply that comes back unencrypted is stopped. The model button reads Encrypted. Agent mode doesn't use encryption.

Not checked in the browser yet: Intel's signature on the TDX quote. Until it is, the app claims only that messages are encrypted on your device and that our servers relay ciphertext.

Why the uncensored models aren't encrypted: the safety check must read a message to enforce the hard limits, those models won't refuse on their own, and today the check runs in our API outside any enclave. Models trained to refuse such requests themselves can skip our check, which is why they can be encrypted. Kimi K3 and Qwen3.5 397B run at Chutes, whose encryption scheme isn't connected yet.

Proofs you can check

The enclave claims on this page can be verified without trusting us:

  • The model's enclave. GET https://api.blacklab.chat/v1/enclave?model={id}&nonce={64 hex} returns that model's attestation report, with the Intel TDX quote and NVIDIA GPU evidence. No account required. Leave out the nonce and you may get a copy up to 10 minutes old; include one for a fresh report.
  • The gateway. Ask RedPill directly: GET https://api.redpill.ai/v1/aci/attestation?nonce={64 hex}. Its TDX quote commits to the gateway's keys together with the nonce you chose, and the report names the public source repository and commit the gateway was built from.
  • Every reply. Replies come with an X-Receipt-Id header. GET https://api.blacklab.chat/v1/proofs/{id}, called with any of your credentials, returns a receipt the gateway signed with ed25519 inside its enclave. The receipt states which model answered and that the gateway checked that model's enclave first, and it holds SHA-256 hashes of the request and of the exact reply bytes. Because our API doesn't alter replies, that hash matches what you received.

A receipt can take a second to appear after the reply ends; a 404 just means ask again. The random id is the only thing that unlocks it, and our server keeps no record of who fetched which receipt. To check a TDX quote's signature chain, use Intel's DCAP tooling, such as Phala's open-source dcap-qvl.

Safety limits

BlackLab answers lawful requests without moralising. Three things are refused no matter who asks or which model is used: sexual material that involves minors, sexual or degrading material about a real person who can be identified, and real help toward building chemical, biological, radiological or nuclear arms.

For every request that isn't end-to-end encrypted:

  1. A quick keyword pass reads your latest message and any system instructions.
  2. Next, gpt-oss-120b acts as a guard model. It runs in an enclave, is reached through RedPill, and reads the recent conversation: up to 12,000 characters, with system messages first (at most 4,000) and then the newest turns from every role, tool-call arguments included. Its only answers are safe or unsafe.
  3. On unsafe, the same guard writes a two-to-four-sentence reply saying what it won't help with and, where one exists, a lawful alternative. If the guard can't be reached or gives an unclear answer, the request fails with a 503 that's safe to retry.

18+ content is a separate switch: it's refused unless you've confirmed your age and the model's licence allows it. The guard can't read encrypted requests, so only models with their own safety training accept them. Blocked text is never logged.

Agent mode

In the app, pick Agent in the sidebar and describe a task. The model may call tools along the way; each call appears above the answer and opens to show what the tool returned.

  • Web search via Brave Search, when the API has it switched on. 1 credit per search, given back if the search fails. Brave receives only the search terms, from our server. A daily cap applies across all users; once it's reached, the tool says so and the agent goes on without it until the next UTC day.
  • Crypto markets: trending tokens and the newest pools on a chain from GeckoTerminal, and token lookups by name, symbol or contract address from DexScreener. Free.
  • Read a web page. Our server fetches it (public addresses only, up to 3 redirects, 10-second timeout) and returns the text, so the website sees our server's address instead of yours.
  • Wikipedia search in English.
  • Calculator and date and time, both run in your browser.

Every step is its own model request, charged like a message; free messages don't cover them. A task ends after 8 steps. Agent mode needs a wallet and a model with tool calling. Tool results aren't stored.

Your own agent: point any OpenAI-compatible framework at https://api.blacklab.chat/v1 with an sk-lab- key. Send tools and tool_choice, run the tools yourself, and return their output as tool messages. Tool-call arguments go through the guard too. The server-side tools are open to wallet credentials too (not free_ tokens), at 20 calls a minute each; see the API reference.

A compact summary for agents:

base_url   https://api.blacklab.chat/v1   (OpenAI chat format)
auth       Authorization: Bearer sk-lab-…
models     GET /v1/models   ids, credit prices per 1M tokens, privacy, tools
chat       POST /v1/chat/completions   (streaming, tools, response_format)
receipt    X-Receipt-Id header, then GET /v1/proofs/{id}
limits     60 requests a minute; on 402, buy credits at app.blacklab.chat

API reference

The base URL is https://api.blacklab.chat/v1, in OpenAI's chat format. Model, proof and agent paths also answer without the /v1 prefix; account, payment and chat-storage paths live only at the root. Make a key under Settings → API keys. It begins with sk-lab-, is displayed once, and only its hash is kept. Keys spend the same credits as the app but can't manage the account; those routes need a wallet session.

curl https://api.blacklab.chat/v1/chat/completions \
  -H "Authorization: Bearer sk-lab-…" \
  -H "Content-Type: application/json" \
  -d '{"model": "glm-5.3", "messages": [{"role": "user", "content": "Write a haiku about block times."}]}'

Any OpenAI SDK works once the base URL points here:

import sys
from openai import OpenAI

client = OpenAI(base_url="https://api.blacklab.chat/v1", api_key="sk-lab-…")
answer = client.chat.completions.create(
    model="qwen3.8-27b-uncensored",
    messages=[{"role": "user", "content": "What does a TEE protect against?"}],
    stream=True,
)
for part in answer:
    if part.choices:  # the final chunk only reports token usage
        sys.stdout.write(part.choices[0].delta.content or "")

Replies are the provider's bytes, untouched, so the model field of a non-streamed reply holds the upstream name (for example phala/qwen3.8-27b-uncensored).

Models, chat and proofs

Method and pathPurpose
GET /v1/modelsPublic list: id, label, price per 1M tokens in credits, context length, privacy (tee or e2ee), tools, and e2eeModel where encryption is offered.
POST /v1/chat/completionsChat, streamed with "stream": true or in one piece. Tools, response formats and reasoning options are forwarded. Takes a session, an sk-lab- key or a free_ token.
GET /v1/proofs/{id}The signed receipt for one reply.
GET /v1/enclave?model=&nonce=Attestation report for the model's enclave. Public, 30 calls a minute per IP.

Agent tools

Method and pathPurpose
GET /v1/agentWhich server tools are on right now, and the price of a search.
POST /v1/agent/read{"url": "…"} returns a public page's title and text.
POST /v1/agent/search{"query": "…"} runs a Brave web search for 1 credit.
POST /v1/agent/market{"kind": "trending" | "new", "chain": "bsc"} or {"kind": "token", "query": "…"} returns market data. Free.

Free messages

Method and pathPurpose
GET /free/challengeA signed puzzle and the number of zero bits required.
POST /free/start{"challenge", "solution"} returns a free_ token for /v1/chat/completions.
GET /free/statusMessages left today for that token.

Account (wallet session unless noted)

Method and pathPurpose
GET /login/nonce, POST /login, POST /logoutSign-In with Ethereum: fetch a nonce, send the signed message, end the session.
GET /meWallet, balance, 18+ setting and free messages left. API keys allowed.
GET /me/usage, GET /me/stats?days=The last 100 requests, and totals per day and per model. API keys allowed.
POST /me/settingsTurn 18+ content on or off.
GET/POST /me/keys, DELETE /me/keys/{id}List, create and revoke API keys.

Payments and refunds

Method and pathPurpose
GET /topup/infoPublic: network, token contract, decimals, treasury, confirmations, rate, seller and refund window.
POST /topup/startRecords your request to start at once, before you pay.
POST /topup/claim{"chainId", "txHash"} settles a payment without waiting for the watcher. 202 means not enough confirmations yet.
GET /topup/history, GET /topup/limitsYour payments with their status, and what's left under a 30-day cap if one is set.
GET /topup/receipt/{chainId}/{txHash}Purchase confirmation; add ?format=text to download it.
GET /topup/wallet-balanceYour wallet's USDT balance, read by our server so the RPC sees us rather than you.
GET/POST /refundsRefundable credit and past requests; ask for a refund.

Saved chats (wallet session only)

Method and pathPurpose
GET /vaultIds, revisions, sizes and change times, plus usage against the limits.
GET/PUT/DELETE /vault/{id}Read, save ({"blob", "baseRev"}) or delete one encrypted chat. A 409 means another device saved first.
DELETE /vaultDelete every chat stored for the wallet.

Errors

Statuserror.typeWhen
400blockedA safety limit applied; error.code is minors, real_people, weapons, adult_off or guard.
400invalid_request_errorMalformed body, or the conversation doesn't fit the model's context.
400e2ee_unsupportedEncryption headers sent for a model without encryption.
401unauthorizedNo credential, or one the API doesn't know.
402insufficient_creditsThe balance can't cover the reservation.
402free_limitToday's free messages are used up.
403free_modelA free_ token asked for a model outside the free offer.
404invalid_request_errorNo model with that id.
429rate_limitedOver 60 requests a minute per account (20 for each agent tool).
502upstream_errorThe model provider failed or refused the request. Nothing was charged.
503blocked, code guard_downThe safety check couldn't run. Try again shortly.
503free_unavailableToday's shared free budget is spent.

Still to come

  • Running the BlackLab API in a confidential VM, so text for the uncensored models is readable only inside enclaves on its way through.
  • In the app: verifying Intel's signature on enclave reports, and showing each reply's receipt.
  • Encrypting messages end to end for the uncensored models, which depends on running the safety check in an enclave, and for the models hosted at Chutes.
  • Image and video generation.

Updated 8 October 2026 · Terms · Privacy