Build on Aemulus
Run skills, read their proof, and verify receipts programmatically. One API key, REST over HTTPS, verifiable by anyone.
Run a skill in one request
curl -X POST https://aemulusai.com/api/v1/runs \
-H "Authorization: Bearer aem_live_…" \
-H "Content-Type: application/json" \
-d '{"skillId":"skl_…","input":{"vendor":"Acme","amount":"1499"}}'
# → { "id": "run_…", "status": "running" }curl https://aemulusai.com/api/v1/runs/run_… \
-H "Authorization: Bearer aem_live_…"
# → { "status":"completed", "output":{"total":"$42.00"}, "receiptHash":"…" }curl https://aemulusai.com/api/verify/run_…
# → { "matches": true, "batch": { "proofValid": true, "root": "…" } }Install the SDK
A tiny, dependency-free client for the Aemulus protocol. Works anywhere fetch does - Node 18+, browsers, Deno, and edge runtimes.
npm install aemulusview on npm ↗Get an API key
Install the package
npm install aemulus
Create a client
import { Aemulus } from "aemulus";
const aemulus = new Aemulus({ apiKey: process.env.AEMULUS_KEY! });Run a skill and read its output
const run = await aemulus.runAndWait("skl_…", {
vendor: "Acme",
amount: "1499",
});
console.log(run.status); // "completed"
console.log(run.output); // { total: "$42.00" }Verify the receipt
const proof = await aemulus.verify(run.id); console.log(proof.matches); // true console.log(proof.batch?.proofValid); // true console.log(proof.sandbox); // the isolation policy it ran under console.log(proof.repairedSteps); // steps the agent had to finish
Prove one field, and nothing else
const d = await aemulus.disclose(run.id, "output.total");
// send d to anyone - it reveals only this field
const { valid, bound } = await aemulus.verifyDisclosure(d);
// valid: the proof holds. bound: it belongs to that run.
// Accept only when both are true.Watch a page, and do something when it moves
const w = await aemulus.createWatch({
skillId: "skl_…",
cadence: "every30m",
rule: { key: "dev_holding", op: "below", value: "5" },
// Optional: run a skill at that moment, handed the value that fired it.
// Metered against your daily run quota like any other run.
action: { kind: "run_skill", skillId: "skl_exit" },
});
await aemulus.listWatches(); // value, last checked, its action
await aemulus.setWatchActive(w.id, false); // pause, keeps its history
await aemulus.clearWatchAction(w.id); // stop it running the skill
await aemulus.deleteWatch(w.id);Check a webhook really came from us
import { verifyWebhook } from "aemulus";
const ok = await verifyWebhook({
secret, // from your webhook settings
signature: req.headers["x-aemulus-signature"],
body: rawBody, // not JSON.parse'd
});
if (!ok) return res.status(400).end();Every run carries an AgenC constraint hash
Computed with AgenC's own SDK (@tetsuo-ai/sdk, pinned to 1.4.0), folded into the run's receipt so it is sealed rather than stored in a column that could be edited, and shown on the public verify page for any run that has one.
Their circuit takes exactly four field elements, so the layout is fixed and ordered. Each is a domain-separated sha256 reduced into the BN254 scalar field:
0 run sha256("aemulus:run:" + runId)
1 skill sha256("aemulus:skill:" + skillId + "@" + version)
2 outputs sha256("aemulus:outputs:" + canonicalJson(outputs))
3 outcome sha256("aemulus:outcome:" + status + "/" + outcomeVerdict)
// canonicalJson sorts keys, so two encoders agree.
// Each element is taken modulo the BN254 scalar field.import { computeConstraintHash } from "@tetsuo-ai/sdk";
const hash = computeConstraintHash(vector).toString(16);
// matches the constraint hash shown on /verify/<runId>Element 2 digests the run's outputs, and those are private. So the hash commits to a result without publishing it: whoever holds the run can recompute this number and see it match, and everyone else learns nothing from it. RISC Zero proofs verified on-chain by AgenC's router are the next step, and land when their prover is live.
Give your agent verifiable hands
Aemulus is a Model Context Protocol server - point any MCP client (Claude, your agent) at it and the marketplace becomes callable tools: list_skills, run_skill, get_run, verify_receipt. The agent runs real browser tasks and gets back proof.
{
"mcpServers": {
"aemulus": {
"url": "https://aemulusai.com/api/mcp",
"headers": { "Authorization": "Bearer aem_live_…" }
}
}
}API keys
Keys authenticate as your wallet - your skills, quota, and earnings all apply. Send as a Bearer token.
Connect your wallet to create an API key. Keys belong to your wallet - only you can ever see them, and they're hidden the moment you sign out.
Get pinged when a run finishes
Subscribe a URL to run.completed, run.needs_review, run.failed, or run.output (extracted results, as a data destination) - each HMAC-signed so you can trust it.
import { createHmac, timingSafeEqual } from "node:crypto";
// header: "t=<unix>,sha256=<hex>" - signed payload is `${t}.${rawBody}`
const [tPart, sigPart] = req.headers["x-aemulus-signature"].split(",");
const t = tPart.slice(2), sig = sigPart.slice(7);
// reject stale/replayed deliveries (5-min tolerance)
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) throw new Error("stale");
const mac = createHmac("sha256", WHSEC).update(t + "." + rawBody).digest("hex");
const ok = timingSafeEqual(Buffer.from(sig), Buffer.from(mac));
// status events → { event, runId, skillId, status, receiptHash, at }
// run.output → { event, runId, skillId, output, at }Connect your wallet to register webhooks - they belong to your wallet.
Webhook events
Status events: { event, runId, skillId, status, receiptHash, at }. The opt-in run.output event carries the extracted data: { event, runId, skillId, output, at }.