Skip to content
$AEMUpump.fun ↗
Aemulus
Open protocol

Build on Aemulus

Run skills, read their proof, and verify receipts programmatically. One API key, REST over HTTPS, verifiable by anyone.

POST/api/v1/runs
GET/api/v1/runs/:id
GET/api/v1/skills
GET/api/verify/:runId
GET/api/batch/:id/bundle
Quickstart

Run a skill in one request

Run a skill
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" }
Poll the run + read extracted output
curl https://aemulusai.com/api/v1/runs/run_… \
  -H "Authorization: Bearer aem_live_…"
# → { "status":"completed", "output":{"total":"$42.00"}, "receiptHash":"…" }
Verify the receipt - no key, anyone can
curl https://aemulusai.com/api/verify/run_…
# → { "matches": true, "batch": { "proofValid": true, "root": "…" } }
TypeScript SDK

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 ↗
1

Get an API key

Connect your wallet on this page and create a key under Authentication. The key authenticates as your wallet, so your skills, quota, and earnings all apply. Keep it server-side.
2

Install the package

terminal
npm install aemulus
3

Create a client

index.ts
import { Aemulus } from "aemulus";

const aemulus = new Aemulus({ apiKey: process.env.AEMULUS_KEY! });
4

Run a skill and read its output

Browse the marketplace for a skill id, then run it on your own inputs. runAndWait polls until the run reaches a terminal state.
run a skill
const run = await aemulus.runAndWait("skl_…", {
  vendor: "Acme",
  amount: "1499",
});

console.log(run.status);   // "completed"
console.log(run.output);   // { total: "$42.00" }
5

Verify the receipt

Every completed run is sealed. Anyone can check it, with no API key.
verify
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
6

Prove one field, and nothing else

Show a counterparty a single value from a run - a total, a status - provable against the run's on-chain anchored root, without handing over the rest of the run. They check it themselves, with no API key and no account.
selective disclosure
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.
7

Watch a page, and do something when it moves

A watch is a schedule plus the rule that reads its output, created together. The cadence is checked against your tier before the watch exists - an unaffordable one is refused with the list you can sustain, rather than accepted and then silently skipped. A rule can do more than “changed”, and when it fires it can run another of your skills rather than only messaging you.
watches
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);
8

Check a webhook really came from us

Deliveries are signed. Verify against the RAW body - a framework that hands you a parsed object has already destroyed the bytes that were signed.
webhooks
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();

Full method reference on npm, source in the repo.

AgenC interop

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:

The four elements
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.
Recompute it yourself
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.

MCP server

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.

MCP client config
{
  "mcpServers": {
    "aemulus": {
      "url": "https://aemulusai.com/api/mcp",
      "headers": { "Authorization": "Bearer aem_live_…" }
    }
  }
}
Authentication

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.

Nothing here is shown until you connect your wallet.
Webhooks

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.

Verify the signature (Node)
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.

Nothing here is shown until you connect your wallet.
Reference

Webhook events

run.completedA run finished successfully
run.needs_reviewA step needs human input
run.failedA run errored out
run.outputA run captured extracted data (output destination)

Status events: { event, runId, skillId, status, receiptHash, at }. The opt-in run.output event carries the extracted data: { event, runId, skillId, output, at }.

Status codes

200OK
400Invalid request body
401Missing or invalid API key
403Insufficient $AEMU balance or missing key scope
404Skill / run / batch not found
409Idempotency-Key already in progress
429Rate limit or daily quota reached