SDKs
Two official SDKs wrap the OpenAI client for TypeScript and Python,
pre-configured for
https://api.buildsable.com/v1, and add helpers for the Sable-specific
surface: sandbox runs, credit, delegated sub-keys, and receipt and attestation
verification. Everything the OpenAI client can do (chat, embeddings,
streaming) works unchanged, because Sable is the OpenAI client with a
different base URL.
Both SDKs are at version 0.3.0, which added two layers under the hand-written one: a generated client with a method for every operation in the gateway's OpenAPI document, and launchpad helpers that read the public coin endpoints and build unsigned transactions.
Two more client packages ship alongside them: a CLI and a framework middleware. All four are listed below.
None of the four is published to npm or PyPI yet. Install them from the
repository's sdk/ directories for now. Publishing is planned, not done,
and we'd rather say so than have you hunt for a package that isn't
there.
| Package | What it is | Path |
|---|---|---|
@sable-network/sdk | What it isThe TypeScript client (extends the OpenAI client) | Pathsdk/ts/ |
sable-network | What it isThe Python client (wraps openai) | Pathsdk/python/ |
@sable-network/cli | What it issable on the command line: chat, run code, check credit, verify a receipt | Pathsdk/cli/ |
@sable-network/middleware | What it isA zero-dependency fetch adapter that points any fetch-based framework at Sable and captures the receipt | Pathsdk/middleware/ |
TypeScript: @sable-network/sdk
Sable extends the OpenAI client, so the whole OpenAI surface is inherited.
import { Sable, SableError } from "@sable-network/sdk";
const client = new Sable({ apiKey: process.env.SABLE_API_KEY! });
// The OpenAI surface, unchanged:
const resp = await client.chat.completions.create({
model: "sable-llama-3.3-70b",
messages: [{ role: "user", content: "hi" }],
});
// Sable extensions:
const run = await client.runCode({ language: "python", code: "print(6*7)" });
const credit = await client.credit(); // this key's balance and runway| Method | What it does |
|---|---|
chat.completions.create(...) | What it doesOpenAI-compatible chat, inherited from the base client. See Chat completions. |
embeddings.create(...) | What it doesOpenAI-compatible embeddings, inherited. See Embeddings. |
runCode({...}) | What it doesRun code in a metered sandbox via POST /v1/sandboxes. See Sandbox compute. |
credit() | What it doesThe calling key's balance via GET /v1/credit, so an agent can see its runway over REST. |
delegateKey({...}) | What it doesMint a bounded child of the calling key. See Delegated sub-keys. |
chatWithReceipt({...}) | What it doesA non-streaming completion plus its signed receipt, captured from the response headers in one call. |
verifyReceipt(...) | What it doesExported helper: verify a signed receipt through the public POST /v1/receipts/verify. See Verifiable receipts. |
verifyReceiptLocally(...) | What it doesExported helper: verify a receipt offline against a pinned signer address (EIP-191 recovery via @noble, no network, no trusting the party being verified). |
verifyAttestation() | What it doesExported helper: pre-flight check of the live confidential attestation via GET /v1/attestation. See Privacy tiers. |
xPaymentHeader(...) / settleWith(...) | What it doesExported helpers: build the X-PAYMENT header for inline x402 settlement. See Paying with USDT. |
Requests carry sensible default timeouts (per-call timeoutMs override), so a
hung gateway call fails instead of blocking an agent forever.
Python: sable-network
The same shape: a wrapper around openai-python, plus the Sable methods in
snake_case.
from sable_network import Sable, SableError
client = Sable(api_key=SABLE_API_KEY)
# The OpenAI surface, unchanged:
resp = client.chat.completions.create(
model="sable-llama-3.3-70b",
messages=[{"role": "user", "content": "hi"}],
)
# Sable extensions:
run = client.run_code(language="python", code="print(6*7)")
credit = client.credit()| Method | What it does |
|---|---|
chat.completions.create(...) | What it doesOpenAI-compatible chat, inherited. |
embeddings.create(...) | What it doesOpenAI-compatible embeddings, inherited. |
run_code(...) | What it doesRun code in a metered sandbox. |
credit() | What it doesThe calling key's balance and runway. |
delegate_key(...) | What it doesMint a bounded child key. |
chat_with_receipt(...) | What it doesA non-streaming completion plus its signed receipt in one call. |
verify_receipt(...) | What it doesModule helper: verify a signed receipt through the public endpoint. |
verify_receipt_locally(...) | What it doesModule helper: verify a receipt offline against a pinned signer. Needs the optional extra: pip install "sable-network[verify]". |
verify_attestation() | What it doesPre-flight the live confidential attestation. |
x_payment_header(...) | What it doesModule helper: build the X-PAYMENT header for inline x402 settlement. |
Every other endpoint: the generated client
The methods above are hand-written for the routes agents use most. Beneath
them, both SDKs carry a generated client with one method for every operation
in the gateway's OpenAPI document, grouped by tag and reached as
client.api.<group>.<operation>().
const sable = new Sable({
apiKey: process.env.SABLE_API_KEY!,
sessionToken: process.env.SABLE_SESSION, // optional; see below
});
// GET /v1/memory/collections (API key)
const { collections } = await sable.api.memory.getMemoryCollections();
// GET /v1/runs/{run_id} (session token: an account-management route)
const run = await sable.api.runs.getRunsRunId({ run_id: "nightly-eval" });
// GET /v1/community/public/{slug}/candles (public: no credential is sent)
const candles = await sable.api.community.getCommunityPublicSlugCandles({ slug: "sabre", interval: "1h" });- Names are mechanical. The HTTP method plus the path segments, with
v1dropped:GET /v1/runs/{run_id}isgetRunsRunIdin TypeScript andget_runs_run_idin Python. - The right credential per route. API-key routes send the API key,
account-management routes send a session token (a
sess_…fromPOST /v1/auth/verify), and public routes send nothing. A call that needs a credential you did not supply fails withMissingCredentialErrorbefore any request is made. With no key at all,new SableApi()(SableApi()in Python) reaches every public route. - Same errors and deadlines. A non-2xx answer raises the same
SableError. Defaults are 15 s for GET and DELETE, 180 s for writes, and none for streams. - Python is synchronous only, like the rest of that package.
Response types are only as precise as the OpenAPI document, and no more.
The document describes 448 JSON responses. 165 of them are typed all the way
down, 55 partly, and 228 are described only in prose: those come back as a
free-form object (Record<string, unknown> in TypeScript, a plain dict in
Python) with the prose in the method's doc comment. The SDK does not invent
fields for them. Where a shape is declared, nothing in the gateway checks it
against the handler that produces the response, and Python validates nothing at
runtime, so read a declared type as documentation rather than a guarantee.
The client is generated from an OpenAPI document emitted from the
gateway's source, not fetched from a running deployment. So it covers
every operation the build knows about, including ones a given deployment has
switched off; such a call answers 404 or 501 there.
Launchpad helpers
Both SDKs can read the public coin endpoints and build transactions for a coin's bonding curve. The rule they are built on:
The SDK never holds a key, never signs and never sends a transaction. Every builder returns an unsigned
{ to, data, value, chainId }for your own wallet to complete, sign and send.
import { Launchpad, SableApi, planBuy, parseUnits, toRpcTransaction } from "@sable-network/sdk";
const lp = new Launchpad(new SableApi()); // public; or `sable.launchpad`
const coin = await lp.record("sabre"); // launch, curve, market
const { candles } = await lp.candles("sabre", { interval: "1h" });
// Plan a buy of 0.01 ETH. The reads go through YOUR RPC, not Sable's.
const plan = await planBuy({
ethCall: (call) => provider.request({ method: "eth_call", params: [call, "latest"] }),
curve: coin.curve!.curve_address!,
quoteToken: coin.curve!.quote_token, // null = paired with ETH
quoteIn: parseUnits("0.01", 18)!,
slippageBps: 100,
chainId: coin.curve!.chain_id!,
});
for (const step of plan.transactions) {
// an exact approval first when the coin is paired with an ERC-20, then the buy
await provider.request({ method: "eth_sendTransaction", params: [toRpcTransaction(step.tx, myAddress)] });
}| TypeScript | Python | |
|---|---|---|
Reads (Launchpad) | TypeScriptconfig, directory, record, market, curve, candles, holders, trades, quote | Pythonthe same names |
| Builders | TypeScriptbuildBuy, buildSell, buildApprove, buildCreate (and buildCreateApproval), buildGraduate, buildClaimCreatorFees | Pythonbuild_buy, build_sell, build_approve, build_create (and build_create_approval), build_graduate, build_claim_creator_fees |
| Planning | TypeScriptplanBuy, planSell | Pythonplan_buy, plan_sell |
| Helpers | TypeScriptapplySlippage, parseUnits, formatUnits, findLaunchCreated, toRpcTransaction | Pythonapply_slippage, parse_units, format_units, find_launch_created |
planBuy and planSell read the curve's own preview through your
eth_call, apply the slippage floor, and return the steps to send. Three rules
are built in:
- No minimum is ever zero. The buy and sell builders refuse a non-positive slippage floor.
- An approval is for exactly the amount, never unlimited.
- A figure the gateway could not read is
null(Nonein Python), never zero.
The calldata is pinned byte for byte to Foundry cast vectors. A preview is a
reading at the moment it was taken, not a price guarantee; the floor is what
protects you. Sable operates this curve and takes a fee on trades. The helpers
have been tested against those vectors and a local listener, not against a
live chain or a real wallet.
Error handling
Both SDKs raise a SableError that keeps the gateway's error information
intact. The case worth branching on is credit exhaustion: an out-of-credit
402 carries x402 payment terms, and the error exposes them so an agent can
settle and retry: isInsufficientCredit in TypeScript,
is_insufficient_credit in Python. Both are true only for
type: "insufficient_credit", which is deliberate — four unrelated conditions
answer 402, and the other three (a spend cap reached, a
circuit breaker tripped, type: "circuit_breaker_tripped", and a
plan that does not include what was asked for, type: "plan_required")
carry no terms and are not clearable by paying. Settling on those funds the
runaway the breaker exists to stop, so branch on the predicate, never on the
status code. See Paying with USDT for what the terms contain
and how settlement works. On a 429, the error also carries the Retry-After
value (retryAfterSecs / retry_after_secs), so a backoff loop doesn't
have to guess.
try {
await client.chat.completions.create({ /* ... */ });
} catch (err) {
if (err instanceof SableError && err.isInsufficientCredit) {
// err carries the 402's x402 "accepts" terms; settle, then retry.
}
throw err;
}If you'd rather not take a dependency at all, the plain OpenAI SDK with
base_url="https://api.buildsable.com/v1" covers chat and embeddings (see the
Quickstart), and every Sable extension is reachable as an
ordinary HTTP call.