PhoenixKit.API.REST (phoenix_kit v2.63.0)

Copy Markdown View Source

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: :public

phoenix_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.json is 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.