Register a Domain
A domain is how an agent calls back into your application. When an agent
runs ctx.domain("company", "get", company_id="..."), Waxell routes it to an
HTTPS endpoint you control — POST {base_url}/domains/company/get — with auth,
per-end-user identity headers, and policy governance applied at the boundary.
For SDK agents (e.g. the Claude Agent SDK), each domain action is surfaced as a
tool named <domain>__<action> (e.g. company__get). The agent calls the tool;
Waxell dispatches the HTTP call.
Domains are the inbound→your-app counterpart to custom tools. A tool is code Waxell runs; a domain is an endpoint Waxell calls.
Register via the API
Register (or update) a domain with your own tenant API key — no Waxell
engineer or manage.py required:
curl -X POST https://api.waxell.dev/api/v1/tenant-domains/push/ \
-H "Authorization: Bearer $WAXELL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain_name": "company",
"set_default": true,
"spec": {
"name": "company",
"auth": { "type": "bearer",
"config": { "header": "Authorization", "prefix": "Bearer",
"token": "YOUR_SHARED_SECRET" } },
"endpoint": { "base_url": "https://app.example.com",
"path_template": "/domains/{domain}/{action}" },
"request": { "timeout_seconds": 30 },
"per_user_auth": true,
"actions": [
{ "name": "get",
"request_schema": {
"type": "object",
"properties": { "company_id": { "type": "string" } },
"required": ["company_id"] } },
{ "name": "set_enrichment",
"request_schema": {
"type": "object",
"properties": { "company_id": { "type": "string" },
"source": { "type": "string" },
"data": { "type": "object" } },
"required": ["company_id", "source", "data"] } }
]
}
}'
Re-pushing the same domain_name creates a new version (the registry is
versioned). The call returns {domain_name, version, is_default, action}.
Always declare request_schema per action
This is the single most common mistake. The request_schema becomes the tool's
input schema that the model sees. Omit it and the agent has no idea what fields
the action needs — it will leave out required parameters and your endpoint will
reject the call (422). Mirror your endpoint's request body exactly: list every
field under properties and the mandatory ones under required.
Auth and per-end-user identity
auth.type: "bearer"sendsAuthorization: Bearer <token>on every call, wheretokenisauth.config.token. Use this as a shared secret so only Waxell can reach your/domains/*endpoints (verify it on your side; fail closed).per_user_auth: trueadditionally sends signed end-user identity headers (X-Waxell-Sub-User-Id,X-Waxell-Sub-User-Email, and a short-lived Ed25519-signedX-Waxell-Sub-User-Tokenyou can verify against the tenant's JWKS) — so your endpoint knows which end user the agent is acting for, without the model ever seeing a credential.
Your endpoint
Implement one route per action at {base_url}/domains/{domain}/{action},
accepting the action's request body as JSON and returning JSON. Validate the
shared-secret bearer, and (if you opted into per-user auth) the sub-user token.
Verify
After pushing, run an agent that calls the domain and check the run trace: the
<domain>__<action> tool span should show your endpoint's response. If you see a
generic dispatch failure, check (1) every action has a request_schema, (2) your
endpoint accepts the shared-secret bearer, and (3) the request body matches the
schema.