prepare

View as MarkdownOpen in Claude

Validate a browser request and build the upstream call. This is the framework-agnostic core of the gateway, for serving it from something other than FastAPI. With FastAPI, use router() instead.

Everything the browser could use to widen its own access is either rejected or overwritten here: the method must be one of the four, the conversation comes from a signed handle, and config_url is the gateway’s.

Parameters

body
dict[str, Any]Required

The parsed request body, carrying method and, depending on the method, handle and message.

origin
str | NoneRequired

The request’s Origin header. Keyword-only. None is allowed, since a non-browser caller sends none and refusing those would break server-side use without stopping an attacker, who simply omits the header.

key
str | NoneRequired

The publishable key from Authorization: Bearer. Keyword-only.

Returns

tuple[str, dict[str, Any], str | None] — the upstream method name, its params, and a newly minted handle. The handle is set only on the call that created the conversation, so you can hand it back in the X-Chat-Handle header.

The upstream method is the service’s, not the browser’s: start becomes create_conversation, end becomes end_conversation, and log becomes chat_log.

Raises

GatewayRejection for a bad key (401), a disallowed origin (403), a bad or expired handle (403), an unknown method or missing message (400), or a cap hit (429).

Example

Serving the gateway from a framework of your own, forwarding with AIChatClient:

1from signalwire.ai_chat import AIChatClient, ChatGateway, GatewayRejection
2
3client = AIChatClient(space="your-space")
4gateway = ChatGateway(config_url="https://bayview-taxi.example.com/swml", client=client)
5
6
7async def handle_request(body, origin, key):
8 try:
9 method, params, minted = gateway.prepare(body, origin=origin, key=key)
10 except GatewayRejection as rejection:
11 return rejection.status, {"error": rejection.reason}, None
12
13 async with client.raw_post(method, params) as resp:
14 payload = await resp.read()
15
16 return 200, payload, minted