Routes
A route is the core entity of the Otoroshi proxy engine. It defines a unique routing rule: which incoming requests to match (the frontend), where to forward them (the backend), and what processing to apply along the way (the plugin chain).
Routes are the building blocks of your API gateway configuration. Every HTTP request that reaches Otoroshi is matched against the set of enabled routes. When a match is found, the request flows through the route's plugin pipeline and is ultimately forwarded to one of the backend targets.
How a route works
A route is made of three main parts:
┌─────────────────────────────────────┐
Incoming request ───► │ Frontend (match the request) │
│ Plugin chain (process the request) │
│ Backend (forward the request) │
└──────────────────┬──────────────────┘
│
▼
Backend targets
- Frontend — defines the matching criteria: domain names, paths, HTTP methods, headers, query parameters, and cookies. If an incoming request satisfies all the criteria, the route is selected.
- Backend — defines where matched requests are forwarded: a list of target servers with load balancing, health checks, timeouts, retries, and TLS settings.
- Plugins — an ordered chain of plugins that process the request and response as they flow through the route. Plugins handle cross-cutting concerns like authentication, rate limiting, header manipulation, CORS, caching, etc.
Request lifecycle
When a request hits Otoroshi, it goes through the following steps:
- Route matching — the router compares the request against all enabled routes. It matches on domain, path, method, headers, query params, and cookies. The router is optimized to handle thousands of routes without performance impact.
- Pre-route plugins — plugins that run before the request is routed (e.g., early redirections, pre-routing logic).
- Access validation plugins — plugins that decide whether the request is allowed to proceed (e.g., API key validation, IP filtering, OAuth token verification).
- Request transformation plugins — plugins that modify the request before it is sent to the backend (e.g., add/remove headers, rewrite the body).
- Backend call — Otoroshi selects a target using the configured load balancing strategy and forwards the (possibly transformed) request.
- Response transformation plugins — plugins that modify the response before sending it back to the client (e.g., add security headers, transform the body).
- Client response — the final response is streamed back to the caller.
Each step is recorded in an execution report (when debug_flow or export_reporting is enabled), making it easy to understand exactly what happened during a request. See engine docs for details.
Frontend matching in depth
The frontend controls which requests a route captures. All specified criteria must match (AND logic).
Domain and path patterns
Domains and paths are combined in the domains field (e.g., api.example.com/v1/users). Several patterns are supported:
| Pattern | Example | Matches |
|---|---|---|
| Exact domain | api.example.com | only api.example.com |
| Wildcard subdomain | *.example.com | api.example.com, admin.example.com, etc. |
| Wildcard segment | api.*.com | api.example.com, api.test.com, etc. |
| Plain path | api.example.com/users | /users, /users/123 (prefix mode) or just /users (exact mode) |
| Wildcard path | api.example.com/users/*/bills | /users/42/bills, /users/abc/bills |
| Named path param | api.example.com/users/:id/bills | /users/42/bills (captures id=42) |
| Regex path param | api.example.com/users/$id<[0-9]+>/bills | /users/42/bills but not /users/abc/bills |
Path matching modes
- Prefix mode (
exact: false, the default) —/api/usersmatches/api/users,/api/users/123,/api/users/123/orders, etc. - Exact mode (
exact: true) —/api/usersmatches only/api/usersand nothing else.
Strip path
When strip_path is true (the default), the matched path prefix is removed before forwarding to the backend. For example:
| Frontend path | Incoming request | strip_path: true | strip_path: false |
|---|---|---|---|
/api/users | /api/users/123 | backend receives /123 | backend receives /api/users/123 |
/v1 | /v1/orders | backend receives /orders | backend receives /v1/orders |
Combined with the backend root path, this gives full control over URL rewriting. If root is /legacy-api and strip_path is true, then a request to /api/users/123 on a route matching /api is forwarded to /legacy-api/users/123.
Named path params and URL rewriting
When using named path parameters (:id or $id<regex>), captured values are available for URL rewriting on the backend side. For example:
- Frontend:
api.example.com/users/$id<[0-9]+>/bills - Backend rewrite target:
/apis/v1/basic_users/${req.pathparams.id}/all_bills
This allows completely reshaping URLs between the client-facing API and the actual backend.
Header, query, and cookie matching
Headers, query parameters, and cookies support several matching modes:
| Syntax | Meaning |
|---|---|
"exact_value" | The value must match exactly |
"Regex(pattern)" | The value must match the regex pattern |
"Wildcard(val*)" | The value must match the wildcard pattern |
"Exists()" or "IsDefined()" | The header/param/cookie must be present (any value) |
"NotDefined()" | The header/param/cookie must not be present |
If the map is empty, any value is accepted (no constraint).
Plugin pipeline
Plugins are the primary extension mechanism. Each plugin in the chain can inspect, modify, or short-circuit the request at a specific phase of the lifecycle. A route's plugin list is an ordered array of plugin slots, each with its own configuration.
Key concepts:
- Ordering: plugins execute in array order by default. Use
plugin_indexto set explicit positions per phase (e.g.,{ "pre_route": 0, "validate_access": 1 }). - Scoping: each plugin can have
include/excludepath patterns so it only applies to certain sub-paths of the route. - Per-plugin debug: enable
debug: trueon a single plugin slot to get detailed execution info for that plugin only, without turning ondebug_flowfor the entire route. - Listener binding: a plugin can be restricted to specific HTTP listeners via
bound_listeners. - Enable/disable: toggle a plugin with
enabled: true/falsewithout removing it from the configuration.
For the full list of available plugins, see built-in plugins.
UI page
You can find all routes here
Properties
| Property | Type | Default | Description |
|---|---|---|---|
id | string | Unique identifier of the route | |
name | string | Display name of the route | |
description | string | Description of the route | |
tags | array of string | [] | Tags for categorization and API automation |
metadata | object | {} | Key/value metadata. Some keys are reserved |
enabled | boolean | true | Whether the route is active. Disabled routes are ignored by the router |
debug_flow | boolean | false | Enable debug flow. Execution reports will contain all input/output values. See engine docs |
capture | boolean | false | Enable request/response capture. Generates events with full request content. Use with caution! See engine docs |
export_reporting | boolean | false | Export execution reports for each request via data exporters. See engine docs |
groups | array of string | ["default"] | Service groups this route belongs to. Used for API key authorization |
bound_listeners | array of string | [] | List of HTTP listener IDs this route is bound to. When a listener is exclusive, only bound routes are served on its port |
frontend | object | Frontend configuration (how the router matches this route). See below | |
backend | object | Backend configuration (where to forward requests). See backends | |
backend_ref | string | null | Reference to a global stored backend by ID. If set, takes precedence over inline backend |
plugins | object | Plugin chain configuration. See below |
Reserved metadata
Some metadata keys are reserved for Otoroshi internal use:
| Key | Description |
|---|---|
otoroshi-core-user-facing | Is this a user-facing app for the Snow Monkey |
otoroshi-core-use-akka-http-client | Use the pure Akka HTTP client |
otoroshi-core-use-netty-http-client | Use the pure Netty HTTP client |
otoroshi-core-use-akka-http-ws-client | Use the modern WebSocket client |
otoroshi-core-issue-lets-encrypt-certificate | Enable Let's Encrypt certificate issuance for this route (true/false) |
otoroshi-core-issue-certificate | Enable certificate issuance for this route (true/false) |
otoroshi-core-issue-certificate-ca | ID of the CA cert to generate the certificate |
otoroshi-core-openapi-url | OpenAPI spec URL for this route |
otoroshi-core-env | Environment for this route (legacy) |
otoroshi-deployment-providers | Relay routing: infrastructure providers |
otoroshi-deployment-regions | Relay routing: network regions |
otoroshi-deployment-zones | Relay routing: network zones |
otoroshi-deployment-dcs | Relay routing: datacenters |
otoroshi-deployment-racks | Relay routing: racks |
Frontend configuration
The frontend defines how the Otoroshi router matches incoming requests to this route.
| Property | Type | Default | Description |
|---|---|---|---|
domains | array of string | [] | Matched domains and paths. Supports wildcards in domain and path, and named path params (e.g., api.oto.tools/users/$id<[0-9]+>) |
strip_path | boolean | true | Strip the matched path from the forwarded request |
exact | boolean | false | Perform exact path matching. If false, matches on /path* |
headers | object | {} | Required HTTP headers to match. If empty, any headers match |
query | object | {} | Required query parameters to match. If empty, any query params match |
cookies | object | {} | Required cookies to match. If empty, any cookies match |
methods | array of string | [] | Allowed HTTP methods. If empty, any method matches |
For more information about routing, check the engine documentation.
Frontend JSON example
{
"domains": [
"api.oto.tools/users"
],
"strip_path": true,
"exact": false,
"headers": {},
"query": {},
"cookies": {},
"methods": ["GET", "POST"]
}
Backend configuration
The backend field defines the target servers to forward requests to. For detailed documentation, see backends.
Alternatively, use backend_ref to reference a global stored backend by ID, making the backend reusable across multiple routes.
Plugins
The plugins field contains the list of plugins applied on this route. Each plugin definition has the following structure:
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Whether the plugin is active |
debug | boolean | false | Enable debug output for this specific plugin |
plugin | string | The plugin identifier (e.g., cp:otoroshi.next.plugins.Redirection) | |
include | array of string | [] | Path patterns to include. If empty, all paths are included |
exclude | array of string | [] | Path patterns to exclude. If empty, no paths are excluded |
config | object | {} | Plugin-specific configuration |
plugin_index | object | null | Explicit plugin execution order. If not provided, array order is used |
bound_listeners | array of string | [] | Listener-specific plugin binding |
Plugin JSON example
{
"enabled": true,
"debug": false,
"plugin": "cp:otoroshi.next.plugins.Redirection",
"include": [],
"exclude": [],
"config": {
"code": 303,
"to": "https://www.otoroshi.io"
},
"plugin_index": {
"pre_route": 0
}
}
For the full list of available plugins, see built-in plugins.
Complete route JSON example
{
"id": "route_users_api",
"name": "Users API",
"description": "Route for the users microservice",
"tags": ["users", "api"],
"metadata": {},
"enabled": true,
"debug_flow": false,
"capture": false,
"export_reporting": false,
"groups": ["default"],
"bound_listeners": [],
"frontend": {
"domains": ["api.oto.tools/users"],
"strip_path": true,
"exact": false,
"headers": {},
"query": {},
"cookies": {},
"methods": []
},
"backend": {
"targets": [
{
"id": "target_1",
"hostname": "users-service.internal",
"port": 8080,
"tls": false,
"weight": 1,
"protocol": "HTTP/1.1"
}
],
"root": "/",
"rewrite": false,
"load_balancing": { "type": "RoundRobin" }
},
"backend_ref": null,
"plugins": {
"slots": [
{
"plugin": "cp:otoroshi.next.plugins.OverrideHost",
"enabled": true,
"include": [],
"exclude": [],
"config": {}
}
]
}
}
Admin API
GET /api/routes # List all routes
POST /api/routes # Create a route
GET /api/routes/:id # Get a route
PUT /api/routes/:id # Update a route
DELETE /api/routes/:id # Delete a route
PATCH /api/routes/:id # Partially update a route
Related entities
- Backends - Reusable backend configurations
- APIs - Group multiple routes into a managed API
- HTTP Listeners - Custom listeners that routes can be bound to
- Route Templates - Reusable route blueprints
- Service Groups - Group routes for API key authorization