How your tools run
Once you have defined a tool and pinned it into a project version, it runs during a conversation in one of two ways: the model calls it while it answers a turn when it needs data it does not have, or, if you marked it to run at the start of the session, the platform runs it once before the model’s first reply. This page walks through what happens either way, so you know exactly what your backend receives and how to trust it.
Every call is signed
Section titled “Every call is signed”Every tool call the platform makes to your endpoint comes with a short-lived signed assertion, a JSON Web Token sent as a standard Authorization: Bearer token, that your backend should verify before doing any work. This assertion also lets your endpoint tell portal-originated traffic apart from other sources.
The assertion carries:
iss, the issuer: your organization’s URL on the platform. Your backend fetches that organization’s public keys from{iss}/.well-known/jwks.jsonand verifies the signature against them. This is the same key set that signs your session tokens.aud, the audience: the name of the one tool being called. Dispatch on this, not on any name in the request body, so an assertion minted for one tool can never be pointed at another.sub, the subject: the same opaque user identifier your backend asserted when it minted the session token, so you can map it back to a person and apply your own authorization.anon, whether this is an anonymous session, so you can apply anonymous policies, if any.turn, a random identifier for the model turn this call belongs to, generated at the start of each turn. Every tool call the model makes within that turn carries the same value, so your backend can group the calls that came from one turn and tie a call back to a point in a conversation in your own logs for support and debugging. It is not a counter and does not represent order.exp, the expiry: thirty seconds after the call is minted. A leaked assertion is worthless almost immediately, and it is scoped to one tool and one turn besides.
Your backend verifies the signature, checks the audience is the tool it expected, maps the subject to a person, and applies the authorization it already applies to its own app. The platform never learns your roles or your policy. All it does is present a signed assertion scoped to a specific tool, specific person, specific turn, but it is your endpoint that decides what is returned to the model.
For example, if the subject is not allowed access to a particular entity, your backend can return a 403 like it normally would. See What comes back to the model.
The subject is filled for you
Section titled “The subject is filled for you”If the tool is per-user, its request carries a subject parameter: the identity of the person the turn is for. This is the reserved parameter placed into the request the platform builds, separate from the JWT’s sub claim above, which every call already carries. The platform fills the subject from the session token, never from the model, and the model never even sees that the parameter exists. So a message crafted to say “fetch someone else’s orders” cannot change whose orders are fetched. The one value that decides whose data a tool reads is the one value the model can never touch.
Tools that run at the start of a conversation
Section titled “Tools that run at the start of a conversation”Not every tool waits for the model to ask. A tool you mark to run at session start runs once, automatically, before the model’s first reply in a conversation, so the model begins already knowing something about the person instead of having to fetch it first. Good candidates are facts that shape the whole conversation and rarely change within it, like a loyalty tier, a subscription plan, or a dietary restriction.
Because the model has not spoken yet when one of these runs, it cannot supply any parameters. A session-start tool therefore carries only the subject, filled by the platform as always. The call is signed with the same assertion as any other tool call, and it follows the same entitlements: when the person’s token does not carry a tool’s required entitlement, the platform skips that tool rather than running it.
Its result reaches the model differently from a tool the model calls itself. Rather than being returned as the answer to a call the model made, it is placed in the context the model starts the turn with, labeled with the tool’s name and the description you wrote for it, and marked as authoritative. The model can see what the data is and where it came from, and use it from its very first reply. That context is remembered for the rest of the conversation, so later turns still have it without the tool running again. If a session-start tool does not succeed, its place in that context carries the same small summary of what went wrong described below, never your error body.
During a turn
Section titled “During a turn”A turn starts when your app sends a person’s message. The platform assembles the context, calls the model, and the model decides how to respond. If answering well needs data it does not have, such as this person’s orders or their loyalty tier, the model calls one of your tools instead of guessing.
When that happens, the platform builds the HTTP request from the tool’s definition: the method and URL you set, and each parameter placed where you told it to go, in the query string, the URL path, or the JSON body. The values for those parameters come from the model, drawn from the conversation. The platform then calls your endpoint, waits for the result, and hands it back to the model, which continues the turn with the new information in hand. A single turn can call several tools before the model has what it needs.
The model can also ask for more than one tool at once. When it requests several calls in a single step, the platform runs them in parallel rather than one after another. It fires all of them together, waits for the last one to come back, and hands the whole set to the model to continue. So two calls that each take a second cost about a second together, not two. A turn can still take several such steps in sequence, the model asking for tools, seeing the results, then asking for more, so calls made in different steps run one step after another while calls made in the same step overlap.
Tools follow the person’s entitlements
Section titled “Tools follow the person’s entitlements”You can require an entitlement on a tool: a claim the person’s session token must carry for the tool to be available. When a turn’s token does not carry that claim, the platform never offers the tool to the model, so the model cannot call it and cannot mention it. A tool with no required entitlement is available to everyone.
This filtering is about the experience, not about security. Your backend still authorizes every call from its own fresh check, because a token’s claims can be up to thirty minutes old. Think of the entitlement as the platform’s way of not tempting the model with a tool this person cannot use, while your backend remains the real gate.
What comes back to the model
Section titled “What comes back to the model”When your endpoint answers successfully, the platform passes the response as-is back to the model to continue the turn. Successful responses should stay below 16 KB (16,384 characters). Over that limit, the response is treated as a failure. Tools should respond within ten seconds: a call that has not answered by then is aborted and treated as a timeout, so a slow tool fails rather than stalling the turn.
When a call does not succeed, the model receives a small, fixed object to let it know that the call failed: a coarse kind for why, and the HTTP status code when your endpoint answered with one, or absent on timeouts, transport errors, and tool-name hallucinations.
{ "status": "error", "kind": "refused", "httpCode": 404 }| What happened | kind | httpCode |
|---|---|---|
| Your endpoint answered with a non-2xx status and a body | refused | The status |
| Your endpoint answered with a non-2xx status and an empty body | returned-nothing | The status |
| Your endpoint answered 2xx, but the body was larger than the 16 KB cap | response-too-big | The 2xx status |
| Your endpoint did not answer within ten seconds | timed-out | None |
| The platform could not reach your endpoint at all | transport-error | None |
| The model named a tool that does not exist, so nothing was called | unknown-tool | None |
An empty response with a 2xx status is not a failure: it is a valid empty result, so the model just sees nothing there. Your endpoint’s own response body is never forwarded on a failure, so nothing you put in an error, such as a stack trace or an internal message, will reach the model. The coarse reason should be enough for the model to choose a sensible next step, like narrowing a request that came back too large or moving on from a record that does not exist.
Shaping a successful response into exactly what the model sees, mapping each outcome to your own wording, choosing which fields of an error response the model may see, and grounding what the model renders against the records a tool returned are all on the way. Today a successful response is passed through as it is, and an unsuccessful one is reduced to the small summary above.