Request routing is the core capability of an API Gateway, enabling it to direct incoming traffic to the appropriate backend service. By defining routing rules, you can control how requests are handled, which services they reach, and under what conditions. Proper request routing ensures: 🚀 Optimized performance by directing traffic efficiently.
🔄 Seamless service rollouts by routing requests based on versioning, headers, or user location.
🛡 Enhanced reliability with failover and fallback mechanisms.

🔍 What are the Benefits of Request Routing?

Modern applications often consist of multiple microservices that serve different functions. A well-configured API Gateway can intelligently route traffic based on:
  • Path-based rules (/api/v1/orders → Order Service, /api/v1/users → User Service).
  • Header-based conditions (User-Agent: Mobile → Mobile Backend, User-Agent: Desktop → Web Backend).
  • Query parameters & cookies (A/B testing, canary deployments).
Key Benefits:
  • Efficiently Direct Requests: Ensure each request reaches the correct service, region, or version.
  • Enable Canary & Blue-Green Deployments: Route a subset of traffic to new versions before full rollout.
  • Improve Scalability: Load balance requests across multiple backend instances.
  • Enhance Security & Compliance: Route traffic based on user identity, location, or authentication level.
  • Optimize Multi-Region Deployments: Direct users to the nearest geographically distributed service.

Using CloudEndpoints with AgentEndpoints

AgentEndpoint resources allow you to configure an upstream that should receive the traffic sent to the AgentEndpoint, but you can only specify a single upstream. CloudEndpoint resources do not have configuration for an upstream, but you can use traffic policy configuration with the forward-internal action to forward to other endpoints. The most common pattern is to create an internal AgentEndpoint for your upstream services. An internal endpoint is used so that your upstream service is not directly accessible to the public internet, but other ngrok endpoints can route to it using the forward-internal action. Then, a CloudEndpoint resource is created for the hostname that you would like to use. The CloudEndpoint is then given traffic policy configuration to forward to your AgentEndpoints. This makes it simple to have a central resource for a given hostname that defines traffic policy actions for things like rate-limiting and authentication that you may like to enforce for the entire hostname while having all of the routing rules for the hostname located in the same location. The following example showcases how you can create two different internal AgentEndpoint resources for two different upstream services, and then use a CloudEndpoint to accept traffic for an example hostname and route to the desired AgentEndpoint.
apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: AgentEndpoint
metadata:
  name: foo-service
  namespace: default
spec:
  url: http://foo-service.internal:80
  upstream:
    url: http://foo-service.example-namespace:8080
  bindings:
  - internal
apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: AgentEndpoint
metadata:
  name: bar-service
  namespace: default
spec:
  url: http://bar-service.internal:80
  upstream:
    url: http://bar-service.example-namespace:8080
  bindings:
  - internal
apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: CloudEndpoint
metadata:
  name: example-cloud-endpoint
spec:
  url: https://example-hostname.ngrok.io
  bindings:
  - public
  trafficPolicy:
    policy:
      on_http_request:
      - expressions:
        - "req.url.path.startsWith('/foo-service')"
        actions:
        - type: forward-internal
          config:
            url: http://foo-service.internal:80
      - expressions:
        - "req.url.path.startsWith('/bar-service')"
        actions:
        - type: forward-internal
          config:
            url: http://bar-service.internal:80
CloudEndpoint and AgentEndpoint resources offer a lot of flexibility in determining when to run traffic policy actions. Check out the traffic policy expressions page for more information about the expressions you can write.
You can also include traffic policy configuration on your internal AgentEndpoint resources that will run after the CloudEndpoint forwards the request to them

Exact Path Matching

The following examples showcase how you can match on an exact path. The request will not be routed unless it has the exact path /example, so /example/foo will not be routed.

1. Create an AgentEndpoint for your upstream Service

apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: AgentEndpoint
metadata:
name: example-service
namespace: default
spec:
url: http://example-service.internal:80
upstream:
  url: http://example-service.example-namespace:8080
bindings:
- internal

2. Create a CloudEndpoint that routes to the AgentEndpoint

apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: CloudEndpoint
metadata:
name: example-cloud-endpoint
spec:
url: https://example-hostname.ngrok.io
bindings:
- public
trafficPolicy:
  policy:
    on_http_request:
    - expressions:
      - "req.url.path == '/example'"
      actions:
      - type: forward-internal
        config:
          url: http://example-service.internal:80

Path Prefix Matching

The following examples showcase how you can match using a path prefix. Any request that has a path starting with /example will be routed, so /example/foo will be routed as well.

1. Create an AgentEndpoint for your upstream Service

apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: AgentEndpoint
metadata:
name: example-service
namespace: default
spec:
url: http://example-service.internal:80
upstream:
  url: http://example-service.example-namespace:8080
bindings:
- internal

2. Create a CloudEndpoint that routes to the AgentEndpoint

apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: CloudEndpoint
metadata:
name: example-cloud-endpoint
spec:
url: https://example-hostname.ngrok.io
bindings:
- public
trafficPolicy:
  policy:
    on_http_request:
    - expressions:
      - "req.url.path.startsWith('/example')"
      actions:
      - type: forward-internal
        config:
          url: http://example-service.internal:80

Request Method Matching

The following examples showcase how to create an endpoint that only routes requests with the POST method.

1. Create an AgentEndpoint for your upstream Service

apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: AgentEndpoint
metadata:
name: example-service
namespace: default
spec:
url: http://example-service.internal:80
upstream:
  url: http://example-service.example-namespace:8080
bindings:
- internal

2. Create a CloudEndpoint that routes to the AgentEndpoint

apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: CloudEndpoint
metadata:
name: example-cloud-endpoint
spec:
url: https://example-hostname.ngrok.io
bindings:
- public
trafficPolicy:
  policy:
    on_http_request:
    - expressions:
      - "req.method == 'POST'"
      actions:
      - type: forward-internal
        config:
          url: http://example-service.internal:80

Exact Header Matching

The following examples showcase how to create an endpoint that only routes requests if the header x-request-header is present with value foo.

1. Create an AgentEndpoint for your upstream Service

apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: AgentEndpoint
metadata:
name: example-service
namespace: default
spec:
url: http://example-service.internal:80
upstream:
  url: http://example-service.example-namespace:8080
bindings:
- internal

2. Create a CloudEndpoint that routes to the AgentEndpoint

apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: CloudEndpoint
metadata:
name: example-cloud-endpoint
spec:
url: https://example-hostname.ngrok.io
bindings:
- public
trafficPolicy:
  policy:
    on_http_request:
    - expressions:
      - "req.url.path.startsWith('/')"

      - "req.headers.exists_one(x, x == 'x-request-header') && req.headers['x-request-header'].join(',') == 'foo'"
      actions:
      - type: forward-internal
        config:
          url: http://example-service.internal:80

Regex Header Matching

The following examples showcase how to create an endpoint that only routes requests if the header x-request-header is present and the value matches the regex x-value-.*

1. Create an AgentEndpoint for your upstream Service

apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: AgentEndpoint
metadata:
name: example-service
namespace: default
spec:
url: http://example-service.internal:80
upstream:
  url: http://example-service.example-namespace:8080
bindings:
- internal

2. Create a CloudEndpoint that routes to the AgentEndpoint

apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: CloudEndpoint
metadata:
name: example-cloud-endpoint
spec:
url: https://example-hostname.ngrok.io
bindings:
- public
trafficPolicy:
  policy:
    on_http_request:
    - expressions:
      - "req.headers.exists_one(x, x == 'x-request-header') && req.headers['x-request-header'].join(',').matches('x-value-.*')"
      actions:
      - type: forward-internal
        config:
          url: http://example-service.internal:80

Exact Query Parameter Matching

The following examples showcase how to create an endpoint that only routes requests if the query parameter my-query-param is present with value foo.

1. Create an AgentEndpoint for your upstream Service

apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: AgentEndpoint
metadata:
name: example-service
namespace: default
spec:
url: http://example-service.internal:80
upstream:
  url: http://example-service.example-namespace:8080
bindings:
- internal

2. Create a CloudEndpoint that routes to the AgentEndpoint

apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: CloudEndpoint
metadata:
name: example-cloud-endpoint
spec:
url: https://example-hostname.ngrok.io
bindings:
- public
trafficPolicy:
  policy:
    on_http_request:
    - expressions:
      - "req.url.query_params.exists_one(x, x == 'my-query-param') && req.url.query_params['my-query-param'].join(',') == 'foo'"
      actions:
      - type: forward-internal
        config:
          url: http://example-service.internal:80

Regex Query Parameter Matching

The following examples showcase how to create an endpoint that only routes requests if the query parameter my-query-param is present and its value matches the regex value-.*.

1. Create an AgentEndpoint for your upstream Service

apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: AgentEndpoint
metadata:
name: example-service
namespace: default
spec:
url: http://example-service.internal:80
upstream:
  url: http://example-service.example-namespace:8080
bindings:
- internal

2. Create a CloudEndpoint that routes to the AgentEndpoint

apiVersion: ngrok.k8s.ngrok.com/v1alpha1
kind: CloudEndpoint
metadata:
name: example-cloud-endpoint
spec:
url: https://example-hostname.ngrok.io
bindings:
- public
trafficPolicy:
  policy:
    on_http_request:
    - expressions:
      - "req.url.query_params.exists_one(x, x == 'my-query-param') && req.url.query_params['my-query-param'].join(',').matches('value-.*')"
      actions:
      - type: forward-internal
        config:
          url: http://example-service.internal:80