SDKs
SDKs
Zero-dependency clients for JavaScript and Python. One call asks the gate, waits for a person if it must, runs your code only on allow, and settles the outcome.
Everything the SDKs do is plain HTTP, documented in the API reference, so any language can call Immiscible directly. The SDKs exist to make the safe path the short one: guard() wraps the whole lifecycle so an agent cannot forget to wait or to settle.
| JavaScript and TypeScript | Python | |
|---|---|---|
| Package | @immiscible/sdk, source in packages/immiscible-js | immiscible, source in packages/immiscible-py |
| Runtime | Node 18 and later, Deno, Bun, browsers and edge runtimes; ESM and CommonJS | Python 3.9 and later, standard library only |
| Dependencies | none | none, including the Ed25519 receipt verifier |
| Types | bundled .d.ts | type hints |
#The lifecycle in one call
import { Immiscible, ImmiscibleDeniedError } from '@immiscible/sdk';
const gate = new Immiscible(); // reads IMMISCIBLE_AGENT_KEY and IMMISCIBLE_URL
try {
await gate.pay(
{ amount: 4200, currency: 'GBP', merchant: 'grocer.example', provenance: [{ source: 'user' }] },
(decision) => checkout(cart, decision.receipt), // runs only on allow
);
} catch (err) {
if (err instanceof ImmiscibleDeniedError) console.log(err.reasons, err.signals);
else throw err;
}from immiscible import Immiscible, ImmiscibleDeniedError
gate = Immiscible() # IMMISCIBLE_AGENT_KEY and IMMISCIBLE_URL from the environment
action = Immiscible.payment_action(4200, "GBP", "grocer.example", provenance=[{"source": "user"}])
try:
with gate.guard(action) as decision: # waits for a person if it must
checkout(cart, receipt=decision.receipt)
except ImmiscibleDeniedError as e:
print(e.reasons, e.signals)That one call:
- asks the gate whether this agent may pay (with an idempotency key, so a retry never authorises twice);
- if a person must decide, waits, polling politely, and gives up with the approval link if nobody answers in time;
- runs your code only on
allow; - settles the action
completed, orfailedif your code threw.
If Immiscible cannot be reached, the call fails and your code never runs.
#Where it connects, and what it throws
Both SDKs talk to the hosted service, https://immiscible.fly.dev, unless you set IMMISCIBLE_URL (or pass baseUrl / base_url) to your own server; the CLI uses the same default.
HTTP failures arrive as a class you can catch by name, each still an ImmiscibleError carrying the server’s x-request-id:
| Class | When | Carries |
|---|---|---|
ImmiscibleAuthenticationError | 401: the key is missing, unknown or revoked | |
ImmiscibleInvalidRequestError | 400 or 422 | errors, one entry per field |
ImmiscibleRateLimitError | 429 | retryAfter (retry_after), in seconds |
ImmiscibleIdempotencyConflictError | 409: the same idempotency key with a different body | |
ImmiscibleConnectionError | the server could not be reached or did not answer in time |
#Verifying receipts
Both SDKs include an offline receipt verifier that checks the Ed25519 signature against a JWKS you fetched and kept, with no network call. It is how a merchant or a downstream service checks that an action was allowed. See verifying offline for what it checks.
#Without a server
Every example in the JavaScript package runs against a built-in fake, so you can write and test an integration before connecting anything:
cd packages/immiscible-js
node examples/shopping-agent.mjs --demo
node examples/merchant-checkout.mjs --demoThe Claude Code hook is its own package, @immiscible/claude-code-hook, in packages/immiscible-claude-code.