Your first receipt in five minutes
Install the SDK, record one action, and open your public verify URL. That's the whole loop — signed in your process, verifiable by anyone. Verification is free forever.
1 · Install
bun add @oreoasis/sdk # or: npm i @oreoasis/sdk
2 · Get an API key
Create a free account — we email you a one-time sign-in link (no password). In your dashboard, name your organization and mint a key. The key is shown once, so copy it right away and set it as OREOASIS_API_KEY. The free tier is live; upgrading is a click when checkout opens.
3 · Record an action
The receipt is signed locally with your agent's key. Inputs and outputs are hashed on your machine — raw content never leaves it.
import {
OreoasisClient, generateAgentKey, agentKeyFromSecret, exportAgentSecret,
} from "@oreoasis/sdk";
import { readFile, writeFile } from "node:fs/promises";
// An agent signing identity — PERSIST IT. A key minted on every boot is a NEW
// agent every restart (see "Your agent's key", below).
const agent = await loadOrCreateAgentKey("my-bot");
const client = new OreoasisClient({ apiKey: process.env.OREOASIS_API_KEY!, agent });
const receipt = await client.record({
action: { type: "tool.call", tool: "search_flights", summary: "Booked SEA→LHR" },
inputs: [{ name: "query", content: "SEA to LHR, 2 pax" }], // hashed locally
outputs: [{ name: "result", content: "PNR ABC123" }],
});
console.log(receipt.verifyUrl);
// → https://oreoasis.com/verify/receipt/rcpt_8xKQ2m
async function loadOrCreateAgentKey(name) {
const path = process.env.AGENT_KEY_PATH ?? "./.agent-key";
try {
return await agentKeyFromSecret(name, await readFile(path, "utf8"));
} catch {
const agent = await generateAgentKey(name);
await writeFile(path, exportAgentSecret(agent), { mode: 0o600 });
return agent;
}
}Your agent's key — persist it
generateAgentKey() mints a new identity every call. Minting on every boot means a different agent after every restart. Your receipts stay verifiable either way — the public key travels inside the envelope, so nothing you already issued is affected — but the continuity is gone: nobody can say “these forty thousand receipts are all the same agent.” Persist the key; ephemeral is the exception, for a script or a test. Treat the file as a credential: anyone holding it can sign as your agent.
What goes in a receipt — and what must never
inputs and outputs are hashed on your machine: raw content never leaves it. meta is the exception. It is free-form and ships verbatim into the signed payload, onto the public verify page, and into a hash chain whose tip is published to a public transparency log.
Transparency logs cannot be erased — not by you, not by us. That permanence is the whole point of anchoring, and it applies to mistakes exactly as faithfully as to evidence. So: no names, no emails, no card or account numbers, no free text a customer wrote. Use meta for boring annotations — a workflow id, an attempt number. It is capped at 8 KiB; over that the ingest refuses the receipt outright, deliberately loudly, because the alternative is discovering it after it is anchored.
Two hashing notes worth knowing early: sha256("") is a valid digest but a meaningless one — it is identical for every empty input, so omit a ref rather than passing empty content. And for anything large (say over 10 MB) or already hashed upstream, pass a precomputed sha256 instead of content.
4 · Open your verify URL
Click it. The page re-checks the signature in your browser and shows the verdict — Oreoasis is never contacted. Send the link to a customer, an auditor, or a stranger; they can check it the same way.
It will say “anchoring pending” at first, and that's normal: free-tier receipts anchor in batches, typically within a few hours (paid plans anchor each receipt immediately). Your receipt is signed and chained the moment it's accepted — anchoring adds the public existence-proof on top of that.
The page shows three times, labelled: claimed by agent (from your signed payload — it proves what was signed, not when), received by Oreoasis, and anchored. Only the last two are times Oreoasis attests to. A timestamp more than 24 hours in the future is refused outright, so a broken clock fails loudly instead of quietly producing evidence nobody should trust.
One line, every action
Prefer a middleware? wrap records a receipt around any function — completed or failed — and passes the result straight through:
const search = client.wrap("tool.call", searchFlights, {
summary: (q) => `search: ${q}`,
inputs: (q) => [{ name: "query", content: q }],
});
await search("SEA→LHR"); // runs searchFlights AND records a signed receiptTyped errors (OreoasisRateLimitError, OreoasisPlanLimitError, …) and the full API live in the docs. Full interface: specs/product-contract.md.