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.

Before you start​

  • The agent must declare the domain. An agent can only reach domains it lists by name in its manifest: domains: [company] (a list of domain names).
  • Your tenant needs the Runtime product, and the API key you push with must carry the domains:push permission. Listing and reading domains needs domains:read.

Register with the CLI​

Write the domain contract as a YAML or JSON file (or a folder containing DOMAIN.yaml / DOMAIN.json) and push it:

# company.yaml
name: company
per_user_auth: true # top level; defaults to false
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
actions:
- name: get
data_classification: pii_basic
human_in_the_loop: false
rate_limit_per_minute: 60
request_schema:
type: object
properties: { company_id: { type: string } }
required: [company_id]
wax push-domain company.yaml                     # creates a new version and makes it the default
wax push-domain company.yaml --no-default # push without promoting
wax domains list # every domain on the tenant
wax domains show company # full detail for one domain

The wax domains group also covers set-version, activate / deactivate, and enable-per-user-auth / disable-per-user-auth.

Each action's contract can declare data_classification, human_in_the_loop, and rate_limit_per_minute. Mark write or sensitive actions human_in_the_loop: true; to have a person approve each call before it proceeds, attach an approval policy.

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 (at the top level of the domain spec; defaults to false) 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.