Play · Developers and platform teams
Prove which agent called your API
The call sayswhich agent, which run,and for whom.
A domain is how an agent calls back into your application, and every domain call arrives with a short-lived Ed25519-signed identity: the agent, its pushed version, the run, the person it acts for, and the parent that delegated it. You verify it against your workspace's public keys; no shared secret leaves Waxell.
30 minutes. No commitment.
SOC 2 Type II attested · ISO/IEC 27001:2022 certified · HIPAA with a BAA · aligned to the EU AI Act and NIST AI RMF

Who
A platform team exposing refund and lookup endpoints to agents, where the only caller identity today is a shared bearer token.
What happened
A refund went out and the service log said 'the agent did it'. Which agent, which version, which run, and for which support rep took a day to reconstruct.
What's at stake
Endpoints that cannot tell one agent from another, actions that cannot be tied to a person, and a shared secret that proves the call came from somewhere, not who made it.
- 01Run
Register a domain. The agent gets an interface, not your database.
When an agent runs ctx.domain('company', 'get', company_id=...), Waxell routes it to POST {base_url}/domains/company/get on an HTTPS endpoint you control; SDK agents see each action as a tool named company__get. Write the contract as company.yaml: a bearer auth block whose token is a shared secret so only Waxell can reach your /domains/* routes, the endpoint's base_url (public HTTPS, so a tunnel during local development), and one action per route with its request_schema, which becomes the input schema the model sees. Omit request_schema and the agent has no idea what fields the action needs; your endpoint rejects the call with a 422. Each action can also declare data_classification, human_in_the_loop and rate_limit_per_minute. wax push-domain company.yaml creates a new version and makes it the default, and the agent reaches it only if its manifest lists domains: [company].
ctx.domain('company', 'get', ...) · POST {base_url}/domains/company/get · company__get · request_schema per action · wax push-domain company.yaml · domains: [company]
company.yaml · domain contractname: companyper_user_auth: trueauth: type: bearer config: header: Authorization prefix: Bearer token: <shared secret>endpoint: base_url: https://api.acme.com path_template: /domains/{domain}/{action}actions: - name: get request_schema: type: object properties: company_id: {type: string} required: [company_id] data_classification: confidential human_in_the_loop: false rate_limit_per_minute: 120$ wax push-domain company.yaml - 02Enforce
Verify the AXID on every call.
Every run, on any framework and any execution tier, is minted an Agent Execution Identity when it starts, and every domain call it makes carries X-Waxell-AXID: a 60-second JWT with alg EdDSA and typ axid+jwt, which you refuse if either differs. The claims name the agent (name, version and a hash of its manifest), the run (id and workflow), scope as domain:<name>.* for each domain the agent declared, the call (id, domain, action), and act: the person the run acts for, with assurance tenant_asserted or idp_verified, or null for a system-triggered run. Fetch the key by kid from your workspace's JWKS at /api/v1/tenants/<workspace-id>/.well-known/axid-jwks.json, which wax axid show prints; check exp, tenant_id, that aud contains domain:<your domain>, and that call.action is the action being invoked; then authorize on agent.name, agent.version, act and scope as your policy requires. Keys live 90 days and rotate with an overlap, so cache the JWKS for up to 10 minutes and refetch on an unknown kid.
X-Waxell-AXID · alg EdDSA · typ axid+jwt · 60 s · agent.name / agent.version / run.id / act / scope / call.action · wax axid show · wax axid verify
identity · overview
- 03Enforce
Verify the person too. Reject if the two disagree.
When the domain sets per_user_auth: true and the signal that started the run carried _sub_user_identity, the same call also carries X-Waxell-Sub-User-Token, minted fresh for each domain call. Its format is wax_su_v2_<kid>.<payload>.<signature>; the Ed25519 signature covers the base64url payload string exactly as received, and it is not a JWT, so a generic JWT library will not verify it. The claims give you sub (the sub_user_id you sent), email, roles, perms and groups, aud equal to the domain name, exp at iat plus 300 seconds, and a jti to reject reused tokens. Authorize on the verified token, never on the plain X-Waxell-Sub-User-Id and X-Waxell-Sub-User-Email headers. Read the two tokens together: the end-user token's axid claim must equal the AXID's sub, and the AXID's act.sub must equal the token's sub_user_id. Reject the request if either pair disagrees.
per_user_auth: true · _sub_user_identity · wax_su_v2_<kid>.<payload>.<signature> · exp = iat + 300 · jti · waxell-sub-user-jwks.json · wax sub-user-auth rotate
POST /domains/company/get · headersAuthorization: Bearer <shared secret>X-Waxell-AXID: <agent identity token>X-Waxell-Sub-User-Token: wax_su_v2_<kid>.<payload>.<signature># Ed25519 over the payload string as received. Not a JWT.sub: alice # the sub_user_id you sentaud: company # the domain nameexp: iat + 300jti: reject replaysaxid: must equal the AXID's sub$ wax sub-user-auth rotate - 04Prove
Store the sub. Trace it back later.
The AXID's sub, axid:<hex>, is durable: the same value appears on the run's spans, its lineage edges, its domain call records and the person's activity log, so store it and call.id on any row the call changes. From any sub you stored, wax axid trace prints the person, the agent and version, the run, every domain call that presented the identity with the call.id you logged, the lineage edges, the person's activity entries, and any child identities a spawned run minted; GET /api/v1/axid/<sub>/ returns the same data. A run spawned by another run carries parent_axid, a chain_depth one higher, and a chain_hash that commits to the actor and the parent so a chain cannot be spliced; a verifier that trusts only depth-1 identities can refuse chain_depth above 1.
store sub and call.id · wax axid trace axid:5f1a9c… · GET /api/v1/axid/<sub>/ · parent_axid · chain_depth · chain_hash
observe · run detail
- company.refund · order 1042 · $140refunds · v1.2.0 · run 4471 · for u_42 · AXID verified · act.sub u_42 · perms orders:refundallow
- company.refund · token with aud billingrefunds · v1.2.0 · run 4471 · for u_42 · wrong audience · endpoint serves companyblock
- company.export_customersrefunds · v1.1.0 · run 4466 · system · act null · system-triggered run · action requires a personblock
- company.get · chain_depth 2researcher · run 4480 · parent run 4471 · parent_axid run 4471 · chain_hash verifiedallow
Illustrative entries in the shape the product writes them.
Products in this play
The domain's bearer token is a shared secret that proves a call came from Waxell at all, and you should verify it and fail closed. Who made the call is proved by the AXID and the end-user token, both signed with Ed25519 and verified against your workspace's public JWKS. There is nothing to rotate on your side; keys live 90 days and rotate with an overlap.
Run this play this week.
Free for your first agents and devices, live in about thirty seconds. Or walk through it with someone who has run it before.
30 minutes. No commitment.
SOC 2 Type II attested · ISO/IEC 27001:2022 certified · HIPAA with a BAA · aligned to the EU AI Act and NIST AI RMF