The REST transport of the shop API: a plug that the router forwards to,
once per audience.
forward "/api/admin/v1", PhoenixKit.API.REST, audience: :admin
forward "/api/shop/v1", PhoenixKit.API.REST, audience: :publicphoenix_kit_routes/0 emits both. The plug sees the path after the
forward, so the routes are:
POST /<tool>runs a tool; the body is the tool's input as one JSON object. A tool name has a dot in it (ecommerce.set_prices);GET /lists the tools the caller may use, with their schemas;GET /openapi.jsonis the generated OpenAPI 3.1 document of the tools the caller may use.
Any other method on those paths answers 405 with an allow header,
and any other path 404, both as application/problem+json.
Everything else is done by PhoenixKit.API.Auth (API switch, key) and
PhoenixKit.API.Executor (rights, limits, validation, the tool). The
request body is conn.body_params, which PhoenixKit.API.BodyGuard
filled in the endpoint; a body that never went through a parser reads as
%{}.
A tool name is matched as sent: the segments of the guarded prefix are
percent-decoded by BodyGuard, the tool segment is not (system%2Eget_identity
is an unknown tool).
Encoded prefixes need BodyGuard in the endpoint
Phoenix's forward strips its prefix from the raw path_info, so a request
that writes the prefix percent-encoded (/api/%61dmin/v1/x, which the router
still matches) raises a MatchError inside the forward. BodyGuard, in the
host's endpoint, decodes those segments first. A host that has not run
mix phoenix_kit.update since BodyGuard was added answers such a crafted
URL with a 500; the MCP forwards will have the same dependency.
CORS
The public audience answers cross-origin requests from any origin, so a
browser-based agent can call it: access-control-allow-origin: *, no
credentials, and a 204 for the OPTIONS preflight. The headers the rate
limit and the support flow rely on (retry-after, ratelimit-*,
x-request-id, idempotent-replayed) are listed in access-control-expose-headers, otherwise a
browser script cannot read them. The admin audience
sends no CORS headers: a browser must not call it from another site.