New in reflex-enterprise v0.7.1.
Event Handler API Plugin
rxe.EventHandlerAPIPlugin exposes every registered event handler on your
Reflex state as an HTTP POST endpoint 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 LLM agents, 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.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()):
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 a Bearer token in the Authorization header. The
token is a random UUID that identifies a client session:
All calls using the same token share state — the token plays the same role as the per-tab session cookie the browser uses. Generate one with any UUID library:
Reuse $TOKEN across calls if you want subsequent requests to see the
effects of earlier ones (e.g. create a ticket, then list tickets). Pick a
new UUID to get a fresh, independent session.
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.
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:
For one-shot clients that just want the final state, simply consume the stream to completion and then (optionally) 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.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 full 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 and a token in
$TOKEN:
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), 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.- Generate a session token (
uuid4) to use as the 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>
api-catalog automatically, point them directly at /_reflex/events/openapi.yaml. A single URL is enough context for most tool-using models to take it from there.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 just a session id, not an auth credential. Anyone who can reach the backend and generate UUIDs can drive the app. Put the API behind your normal auth layer (reverse-proxy, OIDC, VPN, etc.) before exposing it outside trusted networks.
- Every event handler on every state is exposed by default. If you have privileged handlers, either split them onto a state class you don't want to expose, or run the plugin only in environments where API access is appropriate.
- 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.