Organizations
Users
Personas
Describe what you want to teach the persona
Knowledge modules
Persona playbooks
Active trials
Subscriptions
Usage — last 30 days
Send invitations
Each invitee receives an email with a link that signs them in and attaches them to the selected organization. Email domains do not need to match. Up to 100 emails per request.
Recent invitations
Conversations
Login activity
Third-party API keys
External APIs
How a persona calls an external API — read this first
What you are configuring. One row in persona_tools gives one persona one extra tool. When the model decides to call it, the engine POSTs a signed envelope to your HTTPS endpoint and feeds the reply back into the conversation as untrusted data. Three things have to be true before a single call can leave: the tool row exists and is active, the endpoint's hostname is allowlisted for that world, and an HMAC key sits at the secret_ref you name.
Consider sql instead. If the data is already in our own database, a sql-backend tool needs no endpoint, no secret and no allowlist. The external path is for data we do not hold.
What your endpoint receives
POST <your url>
Content-Type: application/json
X-Gen2-Signature: <hex HMAC-SHA256 of the RAW body, key = the secret at secret_ref>
X-Gen2-Timestamp: <unix seconds>
{
"tool": "lookup_claim_status",
"args": { ...only the parameters you declare below... },
"principal": {
"person_id": "...", "world_id": "...", "role_key": "__NONE__",
"conversation_id": "...", "persona_name": "Atlis"
},
"issued_at": 1754000000
}
What your endpoint must do
- Verify the signature first. Recompute HMAC-SHA256(raw_body, secret) and compare in constant time — over the raw bytes, before parsing, not after re-serializing.
- Reject stale timestamps. ±5 minutes is typical. This is what bounds replay.
- Take identity from principal, never from args. This is the one that matters. principal is built from the authenticated conversation and signed; args is whatever the model produced. An endpoint that reads a user id out of args can be talked into acting as another person — which is why the form below refuses to let you declare one as a parameter.
- Answer with JSON, within the timeout and size cap. A non-2xx shows up in the transcript as (persona tool X endpoint returned HTTP N).
- Return no instructions. Your reply is fenced as untrusted before it re-enters the prompt, but write it as though the model will try to obey it.
What the engine enforces for you
- Per-world host allowlist — exact hostname, no wildcards, no ports, no paths. An empty allowlist means nothing can leave, whatever tools exist.
- https only, and private addresses refused (loopback, RFC1918, CGNAT, link-local including the cloud metadata IP).
- Connection pinning to a pre-validated address, closing the DNS-rebinding window; redirects refused, so a 302 cannot escape the allowlisted host.
- Timeout, response-size and per-conversation call caps.
- Untrusted-data fencing of the reply before it re-enters the prompt.
Why there is no “Save” button
secret_ref names a secret the chat Lambda reads, so whoever writes it can make that Lambda read any secret its role can reach; and input_schema goes straight to the model. Both want review. So the form below validates everything the engine will check and hands you the exact SQL — you run it through the migration Lambda. Full detail: docs/external-api-tools.md.