@zindexzero/sdk
v0.5.2
Published
Official SDK for Zero — Z-index Zero's metered, tenant-isolated AI API.
Readme
@zindexzero/sdk
The official SDK for building on Zero — Z-index Zero's metered, tenant-isolated AI API. Add Zero to your own app: chat, your own private knowledge base (RAG), and key/usage management.
Status: shipped & stable — full surface.
@zindexzero/sdkcovers: the server data-plane (chat,docs.*), the control-plane (signIn→ manage keys, caps, usage, style profile, and per-key persona), the browser/mobile client (ZizHosted+ publishable-keyZiz), theuseZeroReact/React-Native hook (@zindexzero/sdk/react), and CDN delivery (esm.sh / jsDelivr). Zero deps, typed, works in Node and browsers. It's a thin typed wrapper over a plain HTTP API, so a hand-rolledfetchworks too. Design/architecture:../SDK-SCOPE.md.
Get a key (required — there is no anonymous access)
The SDK does nothing without a key, and keys come only from us:
- Sign up at https://zindexzero.org/start and submit your project.
- Once your account is approved, your keys are issued in the portal.
- Use a secret key (
ziz_live_…) on your server, or a publishable key (ziz_pub_…) in a browser/mobile app (chat-only, origin-locked).
New keys start disabled and are enabled from your account. No key, no calls —
the API returns 403 and the SDK routes you to the signup page.
Quick start (server)
import { Ziz } from "@zindexzero/sdk";
const ziz = new Ziz({ apiKey: process.env.ZIZ_KEY! }); // ziz_live_… — server only
// Chat — returns { text, modelId, escalated, usage, retrieved? }
const { text } = await ziz.chat("Summarize this contract");
// With media (vision keys) + prior turns
await ziz.chat(
{ text: "What's in this?", attachments: [{ type: "image", format: "png", data: b64 }] },
[{ role: "user", text: "earlier message" }],
);
// Private knowledge base (RAG-enabled secret keys)
await ziz.docs.add({ text: "Internal policy …", title: "Policy" });
const { documents } = await ziz.docs.list();
await ziz.docs.delete(documents[0].docId);No key, or a disabled one? The constructor throws ZizNoKeyError and 401/403 throw
ZizApiError, both carrying .url → Ziz.signupUrl() so your app can route the
user to sign up. usage is token counts, never cost (see below).
First-party (our own apps): pass a ziz_app_ key and the SDK auto-routes to the
full-Zero /v1/app tier — same chat/docs methods. Frontend-only first-party
apps (no server to hold a secret) go through a proxy; see
../SDK-SCOPE.md §10.
Prefer no dependency? It's a plain POST with an Authorization: Bearer header —
hand-rolled fetch against /v1/client/chat works identically.
Browser / mobile (no secret key)
An untrusted app must never hold a ziz_live_ secret. Two browser-safe options:
1. Publishable key (chat only) — origin-locked, quota-fenced:
import { Ziz } from "@zindexzero/sdk";
const ziz = new Ziz({ apiKey: "ziz_pub_…" }); // safe to ship; locked to your origins
const { text } = await ziz.chat("Hello");2. Hosted proxy (full surface — chat + RAG docs) — ZiZ holds your secret server-side;
your app calls the proxy. Register once with scripts/register-hosted-client.mjs:
import { ZizHosted } from "@zindexzero/sdk";
// Browser: just the client slug — the browser's Origin is the gate.
const zh = new ZizHosted({ client: "acme" });
// Native / React Native (no enforced Origin): add the app-token from registration.
// const zh = new ZizHosted({ client: "acme", appToken: "hat_…" });
const { text } = await zh.chat("Summarize my docs");
await zh.docs.add({ text: "Internal note…", title: "note" });
const { documents } = await zh.docs.list();The app-token ships in the bundle — a low-value surface gate, not a secret; the
real spend cap is the upstream key's monthly quota. No ziz_live_ key ever reaches
the client.
React / React Native
useZero manages chat state for any client (Ziz, ZizHosted, …). react is a
peer dependency.
import { useMemo } from "react";
import { ZizHosted } from "@zindexzero/sdk";
import { useZero } from "@zindexzero/sdk/react";
function Chat() {
const client = useMemo(() => new ZizHosted({ client: "acme" }), []);
const { messages, send, status } = useZero(client);
// render messages; call send(text) on submit; status is "idle" | "sending"
}From a CDN (<script type="module">)
The package is zero-deps pure ESM, so the public npm CDNs serve it browser-ready — no build step, nothing to self-host:
<script type="module">
import { ZizHosted } from "https://esm.sh/@zindexzero/sdk";
const zh = new ZizHosted({ client: "acme" });
console.log(await zh.chat("hi"));
</script>(https://cdn.jsdelivr.net/npm/@zindexzero/sdk/+esm works too.) A self-hosted
classic-<script> global build is deferred until a non-module consumer needs it.
Manage your account (control-plane, server-only)
Sign in with your ZiZ account (same email/password as zindexzero.org/start) to manage keys, request caps, read usage, and edit your style profile. Server-only — it holds account credentials; never ship it in a browser/mobile bundle.
import { signIn } from "@zindexzero/sdk";
const account = await signIn({ email: process.env.ZIZ_EMAIL!, password: process.env.ZIZ_PW! });
// Keys — first list() may include `provisioned` (starter keys, raw, shown once)
const { keys } = await account.keys.list();
const { key } = await account.keys.create({ kind: "secret", label: "prod server" }); // raw key, once
await account.keys.setFeature(key /* or keyId */, { smart: true, vision: true });
await account.keys.disable(keyId);
// Public widget key locked to origins
await account.keys.create({ kind: "public", origins: ["https://acme.com"], label: "site widget" });
// Usage (already +20% marked up) · cap request · style profile
const { totalCostUsd } = await account.usage();
await account.caps.request(keyId, 50_000, "launch traffic");
await account.profile.set("Friendly, concise, no emoji.");
// Persona — the assistant ROLE/VOICE for this key (both server + widget keys). "" clears it.
await account.keys.setPersona(keyId, "You are Acme's friendly support agent. Be warm and brief.");A persona shapes role/tone only — it's injected as fenced data, never the system prompt, and can't override Zero's isolation/safety rules or make it speak for ZiZ.
Sessions auto-refresh while the SDK lives; an expired/invalid session throws
ZizApiError (401) carrying .url to re-auth.
Cost & the +20% rule
chat().usagegives token counts only — never a dollar figure.- Your cost (already including our +20% service fee) comes from
GET /v1/portal/usage. The SDK reads that number; it never computes cost and ships no pricing table.
Security — your data is yours, and only yours
These guarantees are enforced server-side (IAM credential fences + the
authorizer), so they hold no matter what the SDK — or a hand-rolled curl — does.
Verified against the live handlers:
- You can only ever reach your own tenant. Your tenant is resolved server-side
from your key (data plane) or your validated account token (control plane) — there
is no field in any request that names a tenant, so you cannot ask for someone
else's. (
zero-client/authorizer.mjs,zero-portalresolveTenant.) - You cannot read Z-index Zero's data or any other client's data. The role
behind the chat endpoint can write only your own usage rows and read/query
nothing — no other client, no ZiZ projects/finances/keys, no first-party app
data. (
zero-client-iam-policy.json.) - Your knowledge base is credential-isolated. RAG runs in a separate service that pins each request to your one private index via a per-request scoped session — a bug passing another index's id fails closed, it does not leak.
- Our own apps are a different, walled tier. A client key on the first-party
endpoints is rejected (
403), and this SDK has no type or method that can even name them. - The assistant itself knows nothing about us. Zero's client persona has no knowledge of or access to Z-index Zero's operations or other clients, and refuses to answer about them. Retrieved docs and style hints are injected as data, never as instructions.
In short: the SDK is a pure consumer. It holds no privileged credentials and has no code path that bypasses the server-side fences — security never depends on the SDK behaving.
Prove your isolation (ziz proof)
Don't take "tenant-isolated" on faith — run the leak test. Given two RAG-enabled keys on different tenants, it plants a unique secret in each tenant's private KB, then proves each tenant retrieves only its own and neither can reach the other's:
ZIZ_PROOF_KEY_A=ziz_live_… ZIZ_PROOF_KEY_B=ziz_live_… npx @zindexzero/sdk proofExit 0 = fenced, 1 = leak detected, 2 = setup error — so it drops straight into CI.
The tenant is resolved server-side from the key, so a request has no field to name another
tenant; a foreign secret can't appear in a reply it was never given. Check just the
detector logic (no network, no keys) with:
npx @zindexzero/sdk proof --selfcheckScope, honestly: this exercises the API fence (B never sees A's data). The deeper
"fails closed at the credential layer" guarantee — a bug passing another tenant's index
ARN returns AccessDenied rather than leaking — is the per-request literal-ARN STS session,
verified by our IAM policy-sim isolation gate, which the public API can't reach.
Design & internals
All phases (0–5) are shipped. The phased build-out, the security model (the
secret-vs-publishable key fence, the hosted proxy, the persona injection fence), and
the design rationale live in ../SDK-SCOPE.md.
