TypeSafe
TypeSafe's Jev model answers typed
questions instead of returning text. It has one credential, TYPESAFE_API_KEY,
sent as Authorization: Bearer to one endpoint,
POST https://api.typesafe.ai/v1/systemone. Both SDKs read the key from the
environment by default and take it as a constructor argument when you want to
override that.
Start here if seekrit is new: three
commands put the keys in
an environment, mint a token bound to it, and export SEEKRIT_TOKEN. A token
reads everything in its
environment, so
there is nothing per-key to set up first.
1. Wrap the process
seekrit secrets set TYPESAFE_API_KEY ts-… --app storefront --env development
seekrit run -- python classify.py
seekrit run -- node classify.mjs
Nothing changes in the app. TypeSafeClient() with no arguments picks up the
injected TYPESAFE_API_KEY.
The same applies to the SDKs' other environment variables — TYPESAFE_BASE_URL,
TYPESAFE_DEFAULT_MODEL, TYPESAFE_LOG_LEVEL. None of those is a secret, so
keep them in your compose file or Procfile rather than in seekrit.
For containers and CI, seekrit-run does the same thing as a
static binary with no Node dependency.
2. Resolve in code
Use this where there is no process to wrap: a serverless handler, a Worker, a notebook.
import seekrit
from typesafe_sdk import TypeSafeClient
secrets = seekrit.Client().resolve()
client = TypeSafeClient(api_key=secrets["TYPESAFE_API_KEY"])
import { TypeSafeClient } from "@typesafe-ai/sdk";
import { Seekrit } from "@seekrit/sdk";
const secrets = await new Seekrit().resolve();
const client = new TypeSafeClient({ apiKey: secrets.TYPESAFE_API_KEY });
Resolve once per handler, not once per question. resolve() is a network round
trip and a decrypt.
3. Hold a placeholder, not a key
Your code holds {{seekrit:TYPESAFE_API_KEY}}; the value is substituted into the
outbound request and exists nowhere else. Both SDKs have a seam for this, so
neither needs the proxy running.
TypeScript
TypeSafeClientConfig takes a fetch, which is all the substitution needs:
import { TypeSafeClient, noul } from "@typesafe-ai/sdk";
import { seekritFetch } from "@seekrit/sdk/fetch";
const client = new TypeSafeClient({
apiKey: "{{seekrit:TYPESAFE_API_KEY}}",
fetch: seekritFetch({
allow: { "api.typesafe.ai": ["TYPESAFE_API_KEY"] },
}),
});
const response = await client.systemOne({
state: { document: ticket.body },
questions: { urgency: noul("Does this express urgency?") },
});
Python
The TypeSafe Python SDK is built on
httpx2, not httpx, so it takes
seekrit.transport_httpx2 rather than seekrit.transport:
pip install 'seekrit[httpx2]'
from seekrit.transport_httpx2 import SeekritTransport
from typesafe_sdk import Noul, TypeSafeClient
client = TypeSafeClient(
api_key="{{seekrit:TYPESAFE_API_KEY}}",
transport=SeekritTransport(allow={"api.typesafe.ai": ["TYPESAFE_API_KEY"]}),
)
response = client.system_one(
state=ticket.body,
questions={"urgency": Noul(instructions="Does this express urgency?")},
)
Use AsyncSeekritTransport with AsyncTypeSafeClient. The arguments are
identical to the httpx transport's, and a test in the SDK fails if the two
drift apart, so everything in
in-process injection applies unchanged:
the allowlist is default-deny, a denied or unresolved name is refused with a 403
and never sent, and scope narrows per request.
Both shims run in your process, which makes them a weaker boundary than the egress proxy. That page sets out the trade-off.
Gotchas
-
The Python SDK will not accept
seekrit.transport.httpx2is a separate distribution, not a newer release ofhttpx, sohttpx2.BaseTransportandhttpx.BaseTransportare unrelated classes. Passing the wrong one raises a type error fromTypeSafeClient, naming neither package usefully. Useseekrit.transport_httpx2here andseekrit.transportfor clients built onhttpx; both can be imported in the same process. -
Do not call
httpx2.alias_httpx()to paper over that. It rebindsimport httpxfor the whole process, and httpx2's own docs say libraries must never do it. Pick the module that matches the client you are configuring. -
stateis the request body, and the shim does not read it for you. Substitution gates placeholders; it has no opinion about content. A credential that some earlier step put into the document you are classifying is sent to TypeSafe like any other text. That is true of every inference API, and it is the case for keeping credentials out of the process at all — see the ladder. -
dangerouslyAllowBrowserdoes not make a service token safe to ship. The JS SDK exposes the option; askt_token in a client bundle is a published credential. Resolve server-side. -
One host, one path. If you constrain the allowlist beyond the host,
POST /v1/systemoneis the whole surface the SDKs use. That makes this a narrower rule than most:from seekrit.transport_httpx2 import AllowRule, SeekritTransport SeekritTransport(rules=[AllowRule( host="api.typesafe.ai", methods=("POST",), paths=("/v1/systemone",), allow=("TYPESAFE_API_KEY",), )])