How it works
MarkdownUsing a blob
Your app opens a session on a blob and gets a local URL and a stand-in key. The enclave adds the real key on the way out.
What your app needs#
OPENAI_API_BLOB.SEAL_ENDPOINTSEAL_TENANTacme is enough for sessions.The SDK reads these itself. You only pass it the blob.
HTTP APIs#
The session gives you a local URL and a stand-in key. Use them as your client's base URL and API key.
import os, seal
from openai import OpenAI
with seal.session(os.environ["OPENAI_API_BLOB"], kind="http") as s:
client = OpenAI(base_url=s.url, api_key=s.key)
client.responses.create(model="gpt-5", input="Hello")import { Client } from "@sealzone/sdk";
import OpenAI from "openai";
const seal = await Client.fromEnv();
await using s = await seal.session(process.env.OPENAI_API_BLOB!, { kind: "http" });
const openai = new OpenAI({ baseURL: s.url, apiKey: s.key });
await openai.responses.create({ model: "gpt-5", input: "Hello" });client, err := seal.FromEnv(ctx)
if err != nil {
return err
}
s, err := client.Session(ctx, os.Getenv("OPENAI_API_BLOB"), seal.WithKind(seal.KindHTTP))
if err != nil {
return err
}
defer s.Close(ctx)
// Send requests to s.URL() with "Authorization: Bearer " + s.Key()let seal = seal::Client::from_env().await?;
let blob = std::env::var("OPENAI_API_BLOB")?;
let s = seal.session(&blob).http().await?;
// Send requests to s.url() with s.key() as the bearer token
s.close().await?;The URL already includes the sealed path, like /v1. kind is optional and catches a database blob passed by mistake. await using needs Node 24 or TypeScript 5.2; otherwise call await s.close() in a finally.
Databases#
For Postgres and Redis, the session gives you a connection string for your driver.
import os, seal, psycopg
with seal.session(os.environ["DATABASE_BLOB"], kind="database") as db:
conn = psycopg.connect(db.dsn)import { Client } from "@sealzone/sdk";
import pg from "pg";
const seal = await Client.fromEnv();
await using db = await seal.session(process.env.DATABASE_BLOB!, { kind: "database" });
const conn = new pg.Client({ connectionString: db.dsn });
await conn.connect();// import _ "github.com/jackc/pgx/v5/stdlib"
s, err := client.Session(ctx, os.Getenv("DATABASE_BLOB"), seal.WithKind(seal.KindDatabase))
if err != nil {
return err
}
defer s.Close(ctx)
conn, err := sql.Open("pgx", s.DSN())let blob = std::env::var("DATABASE_BLOB")?;
let db = seal.session(&blob).database().await?;
let (conn, task) = tokio_postgres::connect(db.dsn(), tokio_postgres::NoTls).await?;
tokio::spawn(task);The string points at 127.0.0.1 with sslmode=disable, because that hop never leaves your machine. The hop to the enclave is TLS. The enclave logs in as the user and database in the blob.
What happens on each request#
Your server
POST http://127.0.0.1:40213/responses
authorization: Bearer sl_9f…stand-in
On open, the SDK checks the enclave's attestation and sends the blob with a proof of identity. The enclave checks the identity's grant, network rules, the blob's expiry and key version, suspension and the artifact check.
The enclave sends the secret only to the blob's destination. Other paths, denied paths and disallowed methods get a 403, and the upstream never sees them. WebSockets, compressed responses and range requests don't pass through. See APIs and databases.
Keeping a session#
- Open one per blob at startup and keep it. Opening one per request is slow.
- It renews itself every five minutes with the same checks. Open connections carry on.
- Settings changes apply live. Remove a grant and its sessions stop within about 30 seconds, open connections included.
- Closing ends it on every replica. Leave the
withblock,await usingscope ordefer, or callclose(). Ifclose()fails, call it again. - Failover is built in. With several replicas in
SEAL_ENDPOINT, the SDK moves on when one stops answering. After two minutes with none, it raisesUnavailable. - The first call after 15 idle minutes takes about a minute while Seal's hosted replica starts. Your own replicas never stop on their own.
- Forked workers like gunicorn or Celery must open sessions after the fork. In gunicorn, use
post_forkor the worker's first request. - New blobs reach a running process through
reload_withandon_resealin Python, Node and Rust. See Rotating and revoking.
Errors#
The SDK raises typed errors, each with a retryable flag. Errors lists them all and what to do.