New in reflex-enterprise v0.7.1.
Event Handler API Plugin
rxe.EventHandlerAPIPlugin exposes your app's registered event handlers as
HTTP POST endpoints and auto-generates an OpenAPI 3 specification for them.
This turns any Reflex app into a machine-driveable API without writing a single
route by hand — great for CLI scripts, end-to-end tests, or external
integrations that need to drive the same logic the frontend uses.
reflex >= 0.9.0 and reflex-enterprise. The plugin only works with rxe.App.For driving the app from an LLM agent, Auto MCP publishes the same handlers over the Model Context Protocol instead of REST. The two plugins share one implementation — the same handler filtering, naming, authentication, and rate limits — so they can be enabled together and will agree.
Endpoints
When the plugin is enabled, the following routes are added to the backend:
Handler argument names and type annotations are introspected to build each
requestBody schema, and the docstring's first line becomes the endpoint
summary. Handlers registered as page on_load triggers are listed in the
description field of the spec so API consumers can tell which endpoint is
invoked when a given page is "visited".
Configuration
Add the plugin to the plugins list of rxe.Config in rxconfig.py:
Your app must use rxe.App() (not rx.App()):
Setting a rate limit to 0 disables it, which is not recommended in
production.
http://localhost:8000 in dev, or the deploy_url in production). If you're running production with --single-port, the API is instead reachable on the frontend port (default http://localhost:3000).Authentication
Every endpoint requires an app-issued bearer token in the Authorization
header:
The token identifies a client session, and that session is generated by the server — a caller can neither pick nor see the underlying Reflex client token, so a credential can only ever address its own session, never a browser session or another client's.
Anonymous session tokens
POST /_reflex/auth/token mints one, bound to a fresh, empty session:
Reuse the same token across calls if you want subsequent requests to see the effects of earlier ones (e.g. create a ticket, then list tickets). Request a new one to get a fresh, independent session — anonymous tokens have no refresh, so an expired token simply means a new session.
The endpoint is rate limited per client IP (token_rate_limit, default 10 per
minute), because every grant seeds a server-side session that consumes memory.
Set anonymous_sessions=False so this plugin does not wire it.
Authenticated tokens
An anonymous session carries no user identity, so with an
configured only auth=False
handlers and vars are reachable through one.
To act as a signed-in user, add and
complete its OAuth 2.1 flow. The access
token it issues works on these REST endpoints as well, and the session it is
bound to gets the full enforcement stack: the per-event gate, callable auth=
checks, delta filtering, and AuthUserState.current().
Auth checks can tell the surfaces apart — ctx.surface is "event_api" for a
request that arrived here — and inspect the token's granted scopes through
ctx.token_scopes. See
surface-aware auth checks.
Rate limiting
Every call is counted against the presenting session token: call_rate_limit
per call_rate_window (default 60 per minute), tracked per process. Exceeding
it returns 429 with a Retry-After header. Browser (websocket) events are
never rate limited by this mechanism.
Individual handlers can override their own budget:
An overridden handler is counted in its own per-token bucket; everything else shares the token's default bucket.
What is exposed
Only your application's own event handlers get endpoints. Framework and auth
handlers — every OIDC provider (including your own OIDCAuthState
subclasses), the login/logout/callback dispatchers, the page guard,
AuthUserState, and other reflex / reflex_enterprise internals — are
withheld, along with generated set-var handlers. API clients authenticate
through the token endpoint or the OAuth flow instead of by posting to the login
handlers.
The same goes for the Pages section of the generated spec: it lists your
app's pages, not the auth machinery's (/login, /callback, /logout,
/forbidden, the OIDC popup pages, and the MCP consent page are all omitted,
along with their dynamic route variables).
Discovering the API
The plugin publishes the OpenAPI spec at a well-known location per RFC 9727. Any compliant client can discover it from the catalog:
Response (RFC 9264 Linkset):
Fetch the spec directly:
Browse it with any OpenAPI viewer (Swagger UI, Redoc, Scalar, the JetBrains
HTTP client, etc.) pointed at that URL. Its info.description documents the
token endpoint, a document-level BearerToken security requirement covers every
operation, and each operation documents its 401 and 429 responses.
Response shape
Event handler endpoints return the state deltas produced by the handler as
newline-delimited JSON (application/x-ndjson). Each line is one
delta; the stream ends when the handler finishes:
Var names come back clean: the framework's internal _rx_state_ field-marker
suffix is stripped from the streamed deltas, from /_reflex/retrieve_state, and
from the Auto MCP surfaces alike.
For one-shot clients that just want the final state, consume the stream to completion and then fetch the full state:
hydrate event, /_reflex/retrieve_state does not reset client-storage vars (rx.Cookie, rx.LocalStorage, rx.SessionStorage). Use it whenever you want to read state without modifying it.State reads redact the session's server-side client_token / session_id from
the returned router var, so the session token never reaches the client, and
framework/auth states are dropped from the returned dict.
The tickets demo app
The reflex-enterprise repository includes a ready-to-run IT-ticketing demo
under demos/tickets/ that exercises every feature of the plugin. Its
rxconfig.py is the minimal reference setup:
The state class exposes typical CRUD handlers — create_ticket,
update_ticket, set_status, delete_ticket, seed, clear_all, plus
list/filter/sort/pagination helpers and a load_tickets on-load handler.
Here's a trimmed excerpt:
ExpandCollapse
Because the state's full name is tickets___tickets____ticket_state, the
generated handler routes live at:
The state name is built from the Python module path (dot separators become
___) followed by the class name — inspect the generated openapi.yaml if you
are unsure of the exact path for a given handler.
curl examples
Assume a dev server running on http://localhost:8000:
Discover the API.
Retrieve the full state dict.
Seed some sample tickets.
Load the first page of tickets into the session. This mirrors the
on_load handler the frontend runs when a browser hits /:
Create a ticket. title is required; description, priority, and
assignee are optional (the server applies the same defaults as in the
Python signature):
Change a ticket's status.
Partial update. update_ticket treats empty strings as "leave
unchanged":
Delete a ticket.
Clear the board.
Example OpenAPI excerpt
The generated spec groups handlers under an OpenAPI tag matching the
state class name. Here's the entry for create_ticket:
ExpandCollapse
Driving the app from an LLM
Because the OpenAPI spec is self-describing (summaries, parameter types, defaults, on-load references, and how to obtain a token), most LLM agents with HTTP tool access can drive a Reflex app end-to-end without any extra glue code. Give them the spec URL and a natural-language task:
Use the API exposed at http://localhost:8000/_reflex/events/openapi.yaml to drive the application.
Create a new ticket assigned to Masen for investigating RegistrationContext issues in reflex CI.
A well-equipped agent will:
GET /_reflex/events/openapi.yamland parse the operations.POST /_reflex/auth/tokenfor a session bearer credential.- Call
POST /_reflex/event/.../create_ticketwith a body like{"title": "Investigate RegistrationContext issues in Reflex CI", "assignee": "Masen", "priority": "medium"}. - Optionally call
/_reflex/retrieve_stateto confirm the ticket landed.
Other prompts that work well with the tickets demo:
Using the Reflex API at http://localhost:8000, seed the database, then close every ticket currently assigned to bob.
Via http://localhost:8000/_reflex/events/openapi.yaml, page through every ticket and summarize which assignees have the largest open backlog.
Using the Reflex API at http://localhost:8000, create three high-priority tickets for the following issues, then show me the resulting state: <list of issues>
Dynamic route variables
If any of your pages use dynamic route segments (e.g. /tickets/[ticket_id]),
the plugin surfaces those as optional query parameters on every
endpoint so the state can read them via self.router:
They appear under components.parameters.route_<name> in the OpenAPI spec
and are referenced from every operation's parameters list.
Security considerations
- The bearer token is a session credential, not a user identity. An anonymous token means "some client, its own session" — nothing more. Anyone who can reach the token endpoint can obtain one, so put the API behind your normal perimeter (reverse proxy, VPN, IP allowlist) before exposing it outside trusted networks, and serve it over HTTPS so tokens aren't readable in transit.
- Every application event handler is exposed by default. Framework and auth
handlers are withheld, but yours are not. Gate privileged handlers with
checks —
which can also require an OAuth scope or reject the
"event_api"surface outright — or run the plugin only where API access is appropriate. - Rate limits are per process. In a multi-worker deployment each worker
enforces its own budget; size
call_rate_limitandtoken_rate_limitaccordingly. - Configure Redis for multi-worker deployments. The default in-process token store does not survive a restart and is not shared across workers, so clients would have to re-authenticate whenever they land on a different one.
- Handlers that return
rx.redirect(...)work over the API, but the redirect is emitted as a state delta rather than an HTTP 3xx — the client sees the URL change, not a browser redirect. This is usually what you want for programmatic clients.
Related
- Auto MCP: the same handler surface over the Model Context Protocol.
- MCP authentication: the OAuth 2.1 flow that issues authenticated tokens for both surfaces.
- Authentication overview: the
AuthPluginand its secure-by-default enforcement.