TypeScript SDK
@mnd8t/sdk — a typed client for the mnd8t authority API. No runtime
dependencies; receipt and artifact verification run on WebCrypto, so they
work in Node, Deno, Bun, edge runtimes and the browser.
For the integration narrative, read the developer guide. This page is the method-by-method reference.
Install
pnpm add @mnd8t/sdk
import { MandateClient } from "@mnd8t/sdk";
const mandate = new MandateClient({
apiKey: process.env.MANDATE_API_KEY!,
});
Client options
| Option | Type | Default | Notes |
|---|---|---|---|
apiKey |
string |
— | Required. Throws if empty. |
baseUrl |
string |
http://localhost:4000 |
Your API host. Trailing slash is trimmed. |
timeoutMs |
number |
15000 |
Per-request timeout. |
maxRetries |
number |
2 |
Applies to safe requests only — see retries. |
fetch |
typeof fetch |
global | Inject your own for tests or proxies. |
Never put a live key in browser code. The SDK will happily run there for receipt verification, but an API key in a bundle is a key you have given away.
Decisions
The endpoint that matters. Call it before touching a wallet or provider.
decisions.authorize(request)
const decision = await mandate.decisions.authorize({
agent_external_id: "procurement_agent_12",
external_reference: "INV-2026-0042",
action_type: "PAYMENT",
amount: { asset: "USDC", asset_amount_minor: 84_000_000 },
valuation: {
policy_amount_minor: 50_000,
policy_currency: "GBP",
source: "CUSTOMER_SUPPLIED",
rate_reference: "provider_quote_123",
},
counterparty_external_id: "supplier_acme_cloud",
destination: "0x1111111111111111111111111111111111111111",
purpose: "cloud_infrastructure",
});
Returns AuthorizeResponse:
{
id: string;
mode: "SHADOW" | "ENFORCE";
decision: "APPROVE" | "REJECT" | "ESCALATE" | "OBSERVE";
effective_decision: same;
would_decide: "APPROVE" | "REJECT" | "ESCALATE";
reason_codes: string[];
mandate_id: string | null;
mandate_version: number | null;
approval: { id, status, expires_at, approval_url } | null;
authorisation_artifact: { id, status, expires_at, download_url } | null;
evidence_receipt_id: string | null;
}
An idempotency key is generated per call. Override it with
idempotencyKey to make a retry collide deliberately — derive it from
something stable in your domain:
await mandate.decisions.authorize({
...payload,
idempotencyKey: `invoice-${invoice.id}`,
});
Amounts are integer minor units. 50_000 is £500.00. The flat form
(amount_minor, policy_amount_minor, policy_currency, asset) is still
accepted but deprecated; prefer amount + valuation.
decisions.confirm(intentId, body)
Reports an execution that you performed. mnd8t does not execute.
await mandate.decisions.confirm(decision.id, {
provider: "MY_PROVIDER",
external_reference: txRef,
authorisation_artifact_id: artifact.id, // required in enforce mode
reported_by: "CUSTOMER_EXECUTOR",
verification: { method: "PROVIDER_LOOKUP", verified: true },
});
Consumes the budget reservation once. Repeating with the same
external_reference is idempotent and returns duplicate: true.
decisions.cancel(intentId, reason?)
Releases the reservation, expires any pending approval, cancels live artifacts.
decisions.get(intentId) · decisions.list(params?)
get returns the decision with its rule-by-rule evaluations trace,
approvals, executions, artifacts and receipts.
list accepts { status?, decision?, limit? }.
Authorisation artifacts
The signed, single-use token that lets your system execute an approved action.
artifacts.get(artifactId)
Returns the envelope, including the signed artifact object.
artifacts.verify(artifact, options?)
Verifies the Ed25519 signature locally. Do this before acting on an artifact — it is the entire point of the signature.
const envelope = await mandate.artifacts.get(artifact.id);
const { valid, reason } = await mandate.artifacts.verify(envelope.artifact);
if (!valid) throw new Error(`refusing to execute: ${reason}`);
The public key is fetched from the gateway's key discovery document and
matched on signature.key_id. Pin it instead with
{ publicKeyPem } if you would rather not fetch.
artifacts.claim(artifactId, executorReference)
Atomic. If two executors race, exactly one succeeds and the other throws a 409. Claim before executing.
await mandate.artifacts.claim(artifact.id, "executor-instance-1");
artifacts.fail(artifactId, reasonCode)
Marks the artifact FAILED after a provider rejection.
artifacts.reissue(intentId)
Issues a fresh artifact after failure or expiry. Re-checks live mandate state — a revoked or expired mandate refuses, as does a released reservation.
Mandates
mandates.create(request)
const m = await mandate.mandates.create({
agent_id: agent.id,
name: "Cloud procurement mandate",
mode: "SHADOW",
policy: {
currency: "GBP",
allowed_assets: ["USDC"],
per_transaction: { autonomous_limit: 75_000, absolute_limit: 250_000 },
budgets: [{ period: "MONTH", limit: 500_000 }],
approval: { new_counterparty_requires_approval: true },
allowed_purposes: ["cloud_infrastructure"],
effective_at: new Date().toISOString(),
expires_at: new Date(Date.now() + 30 * 86_400_000).toISOString(),
},
});
Full field semantics: policy model.
mandates.publish(mandateId, options)
Freezes the draft. Requires a delegator attestation — whoever publishes asserts they hold the authority being delegated:
await mandate.mandates.publish(m.id, {
delegation: {
delegatorRole: "Finance Director",
authoritySource: "CORPORATE_POLICY",
sourceReference: "DoA-2026-v4-section-8",
},
changeNote: "Initial cloud procurement authority",
});
mnd8t records the assertion in the evidence chain. It does not verify that the delegator legally held the authority.
mandates.simulate(mandateId, body)
Dry run — nothing persisted, no budget moved.
const { would_decide, reason_codes } = await mandate.mandates.simulate(m.id, {
amount_minor: 120_000,
asset: "USDC",
counterparty_external_id: "supplier_acme_cloud",
purpose: "software",
at: "2026-08-04T14:00:00Z", // test schedule rules
});
mandates.revoke(mandateId)
Immediate kill switch: new decisions reject, pending approvals expire, unconsumed reservations release, live artifacts are revoked.
mandates.get(id) · mandates.list()
Agents and counterparties
const agent = await mandate.agents.create({
name: "Procurement Agent",
external_id: "procurement_agent_12", // what you pass on every decision
purpose: "Purchase approved software",
principal_type: "ORGANISATION",
principal_reference: "acme_ltd",
});
await mandate.counterparties.create({
external_id: "supplier_acme_cloud",
display_name: "Acme Cloud",
authority_status: "CUSTOMER_APPROVED", // your assertion, not a screening result
destination: "0x1111111111111111111111111111111111111111",
});
Also: agents.list(), agents.get(id), counterparties.list().
external_id must be unique within your organisation and matches
[a-zA-Z0-9_.-]+.
Receipts
receipts.get(receiptId)
receipts.verify(receipt, options?)
Local Ed25519 verification — the one that matters, because it does not require trusting the API that issued the receipt.
const envelope = await mandate.receipts.get(receiptId);
const { valid } = await mandate.receipts.verify(envelope.receipt);
receipts.verifyRemote(receiptId)
Server-side check. Convenience only — it asks the issuer to vouch for itself.
Errors
Every non-2xx response throws a typed subclass of MandateError, so you can
branch on the class rather than parsing strings:
import {
MandateError,
MandateAuthenticationError,
MandateValidationError,
MandateIdempotencyConflictError,
MandateRateLimitError,
MandateNotFoundError,
} from "@mnd8t/sdk";
try {
await mandate.decisions.authorize(payload);
} catch (err) {
if (err instanceof MandateRateLimitError) return backOffAndRetry();
if (err instanceof MandateValidationError) return report(err.details);
if (err instanceof MandateError) log(err.status, err.code, err.message);
throw err;
}
| Class | Status |
|---|---|
MandateAuthenticationError |
401 |
MandateNotFoundError |
404 |
MandateIdempotencyConflictError |
409 (IDEMPOTENCY_CONFLICT) |
MandateValidationError |
422 |
MandateRateLimitError |
429 |
MandateError |
anything else |
Every instance carries status, code, message and details.
Retries and idempotency
GETs retry automatically. Writes retry only when an idempotency key
makes the replay safe — which decisions.authorize always sets, so it is
safe by default. Retries fire on network failures and 5xx, up to
maxRetries, and never on 4xx.
If you supply your own idempotencyKey, the guarantees are the API's:
identical payload replays the original response; a different payload with
the same key throws MandateIdempotencyConflictError. Keys are retained 7
days.
A complete flow
import { MandateClient } from "@mnd8t/sdk";
const mandate = new MandateClient({ apiKey: process.env.MANDATE_API_KEY! });
const decision = await mandate.decisions.authorize({
agent_external_id: "procurement_agent_12",
action_type: "PAYMENT",
amount: { asset: "USDC", asset_amount_minor: 84_000_000 },
valuation: { policy_amount_minor: 50_000, policy_currency: "GBP" },
counterparty_external_id: "supplier_acme_cloud",
purpose: "cloud_infrastructure",
});
if (decision.effective_decision === "ESCALATE") {
await notifyApprover(decision.approval!.approval_url);
return;
}
if (decision.effective_decision !== "APPROVE") return;
const artifact = decision.authorisation_artifact!;
const envelope = await mandate.artifacts.get(artifact.id);
const { valid } = await mandate.artifacts.verify(envelope.artifact);
if (!valid) throw new Error("artifact failed verification");
await mandate.artifacts.claim(artifact.id, "executor-1");
const txRef = await myWallet.pay(envelope.artifact); // your credentials
await mandate.decisions.confirm(decision.id, {
provider: "MY_PROVIDER",
external_reference: txRef,
authorisation_artifact_id: artifact.id,
reported_by: "CUSTOMER_EXECUTOR",
verification: { method: "PROVIDER_LOOKUP", verified: true },
});