Skip to main content

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" sends Authorization: Bearer <token> on every call, where token is auth.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: true additionally sends signed end-user identity headers (X-Waxell-Sub-User-Id, X-Waxell-Sub-User-Email, and a short-lived Ed25519-signed X-Waxell-Sub-User-Token you 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.