API gateway gallery: Drop-in API policy management examples

01-21-2025: We updated this blog post with new information about cloud endpoints, internal endpoints, and links to new guides for using ngrok's composable API gateway.

While other API gateway providers seem to operate under the assumption that it’s impossible to achieve, ngrok starts with an API gateway that’s truly developer-defined.

Let’s take a closer look at the difference between ngrok’s API toolkit and the entrenched (aka expensive) coterie of deployed and cloud API gateways—but if you’re already onboard and just want to see what policy management magic you can get up to in a few minutes, feel free to skip down to the gallery.

Does your API gateway make policy management accessible to developers?

Unfortunately, if you’re using any of the most popular deployed or cloud API gateways, the answer ranges from a pained “not particularly” to a flat-out “that’s impossible.”

Most deployed API gateways come with missing signs, potholes, and guardrails that are a little too ambitious, not just keeping you on the road, but forcing you into a lane that keeps getting smaller. As a developer, trying to enact change on these API gateways is expensive, cumbersome, and slow, because they:

  • Force you to create more than one deployment to cover multiple regions, which means you’re actually maintaining two or more separate gateways to have a global presence.
  • Ask you to pay extra for policy plugins you consider essential, like advanced authentication, or request/response modification.
  • Require weeks or months of coordination with operation teams to spin up.
  • Often rely on tools and languages you’re not familiar with, like XML (yikes) and CSharpScript (double yikes), or force you to install entire ecosystems of tools (Make, Docker, Go, plus “special” images) just to write a basic custom policy for your API.

On the other hand, cloud-based API gateways are often easier to deploy and simpler to use than their deployed counterparts, but are often far more limited in features and lock you into specific environments. You can’t add all the policies you’d like or go multi-cloud without once again begging your operations peers for help that might take them days of work and weeks of waiting for sign-offs from networking and security stakeholders you barely know.

How ngrok lets you quickly add API policy and traffic management

A truly developer-defined API gateway allows you to flexibly deploy and configure in ways that best serve your API consumer. With ngrok’s API gateway, you can:

  1. Deploy the ngrok agent in whichever way best gets your API to your consumers quickly and reliably, including directly on a Linux/macOS/Windows system, within any Kubernetes cluster, or directly within your Go, JavaScript, Python or Rust app with one of our SDKs.
  2. Configure your API gateway either (or both) on the ngrok agent or with a cloud endpoint in the dashboard.
  3. Run all policy and traffic management workloads on the ngrok network, at a Point of Presence (PoP) closest to your API consumer, for a consistent and consistently fast global presence.

With ngrok, you enable ingress at the runtime level (and can even configure it there, too) but also decouple its operation. Your API is then portable across all possible environments, letting you freely test it locally, in a CI/CD environment, or on multiple cloud providers with identical behavior and results for your API consumer.

At the heart of this flexibility is our new Traffic Policy module, which provides a flexible, programmable, and uniform approach to managing API requests and responses across all the ways you use ngrok. This module lets you securely connect your APIs, whether they’re in local testing environments or production deployments, using a single configuration, with support for essential security and availability policies like JWT authentication and rate limiting.

Unlike both traditional deployed API gateways and their newer cloud alternatives, ngrok’s developer-defined option is feature-rich, works everywhere you do, and lets you self-serve your way to production without the operational headaches, red tape, or explosive costs.

Drop-in API policy management examples with ngrok

As mentioned earlier, you can configure your ngrok API gateway in multiple ways:

  1. On cloud endpoints with the ngrok dashboard. Create a cloud endpoint by navigating to Endpoints → + New and adding a reserved domain. You can then paste in any of the YAML shown below to apply a single set of policy rules across multiple upstream services from one place.

    Tip: If you're looking for a way to quickly test out ngrok's API gateway functionality with Traffic Policy but don't yet have an upstream service to route traffic to, you can test and debug using
    cloud endpoints and custom responses. Make sure you add a custom response to the end of any of these rule sets to terminate the request lifecycle.‍‍
  2. On agent endpoints with the ngrok agent. You can configure the agent itself—such as via the Go SDK, agent CLI, and beyond—to store your API gateway configurations as close to your business logic as possible. This lets you more tightly version-control your policies and makes your deployments declarative and repeatable. if you have an upstream service running on http://localhost:8080, for example, you can create a policy.yaml file containing any of the snippets below and attach it to a new agent endpoint.

    ngrok http 8080 --traffic-policy-file=/path/to/policy.yaml
       
  3. On both cloud and internal agent endpoints. You can use a single cloud endpoint to route traffic from one hostname to multiple upstream services, and compose multiple sets of traffic management rules to both. For an example of how that works, check out our blog post on creating a self-service platform for API development.

Template #1: Route to internal endpoints

Internal endpoints are secure, non-public endpoints that end with the .internal TLD suffix and receive traffic only from the forward-internal Traffic Policy action.

Let's say you want to host multiple separate upstream API services on a single hostname. You can create multiple internal endpoints that route traffic to the ports on which these upstream services run.

ngrok http 8080 --url https://api-foo.internal
ngrok http 8081 --url https://api-bar.internal


Our Traffic Policy engine then lets you route specific requests to the appropriate internal endpoint.

---
on_http_request:  
  - expressions:      
      - req.url.path.startsWith("/foo")    
    actions:     
      - type: forward-internal       
        config:         
          url: https://api-foo.internal
  - expressions:      
      - req.url.path.startsWith("/bar")    
    actions:     
      - type: forward-internal       
        config:         
          url: https://api-bar.internal


This architecture also applies to all the other templates below—don't be shy trying to combine these composable building blocks of ngrok's API gateway in different ways!

Template #2: Add JWT authentication and key-based rate limiting

This drop-in policy is the de facto standard of all API gateways. It rejects access to your API to those who haven't properly authenticated their machine-to-machine requests with JSON Web Tokens (JWTs) and restricts their usage to reasonable limits. This prevents an accidental distributed denial-of-service (DDoS) attack on your upstream service and helps control your costs.

For this policy to work, you must have defined your API with an identity provider like Auth0, which issues JWTs on your behalf for ngrok to validate with every subsequent request.

---
on_http_request:
  - name: Add JWT authentication and rate limiting
    actions:
      - type: rate-limit
        config:
          name: Only allow 30 requests per minute
          algorithm: sliding_window
          capacity: 30
          rate: 60s
          bucket_key:
            - req.Headers['x-api-key']
      - type: jwt-validation
        config:
          issuer:
            allow_list:
              - value: https://<YOUR-AUTH-PROVIDER>
          audience:
            allow_list:
              - value: {YOUR_NGROK_DOMAIN}
          http:
            tokens:
              - type: jwt
                method: header
                name: Authorization
                prefix: "Bearer "
          jws:
            allowed_algorithms:
              - RS256
            keys:
              sources:
                additional_jkus:
                  - https://<YOUR-AUTH-PROVIDER>/.well-known/jwks.json

Template #3: Rate limit API consumers based on authentication status

If you have a public API, you may want to let consumers try it out, albeit with strong restrictions, but also allow those who have signed up for your service and received their authentication token to access it more freely.

In the example below, ngrok applies two tiers of rate limiting: 10 requests/minute for unauthorized users and 100 requests/minute for users with a JWT token and the appropriate Authorization request header.

---
on_http_request:
  - expressions:
      - "!('Authorization' in req.Headers)"
    name: Unauthorized rate limiting tier
    actions:
      - type: rate-limit
        config:
          name: Allow 10 requests per minute
          algorithm: sliding_window
          capacity: 10
          rate: 60s
          bucket_key:
            - conn.ClientIP
  - expressions:
      - ('Authorization' in req.Headers)
    name: Authorized rate limiting tier
    actions:
      - type: rate-limit
        config:
          name: Allow 100 requests per minute
          algorithm: sliding_window
          capacity: 100
          rate: 60s
          bucket_key:
            - conn.ClientIP

Template #4: Rate limit API consumers based on pricing tiers

This policy enforces three tiers of rate limiting—free, bronze, silver, and gold—based on the headers present in API requests—or lack thereof. 

You would then need to instruct your API consumers to use the appropriate header based on their pricing tier, ideally through your developer documentation.

---
on_http_request:
  - expressions:
      - "!('Tier' in req.Headers)"
    name: Free rate limiting tier
    actions:
      - type: rate-limit
        config:
          name: Allow 10 requests per minute
          algorithm: sliding_window
          capacity: 10
          rate: 60s
          bucket_key:
            - conn.ClientIP
  - expressions:
      - getReqHeader('tier').exists(v, v.matches('(?i)bronze'))
    name: Bronze rate limiting tier
    actions:
      - type: rate-limit
        config:
          name: Allow 100 requests per minute
          algorithm: sliding_window
          capacity: 100
          rate: 60s
          bucket_key:
            - conn.ClientIP
  - expressions:
      - getReqHeader('tier').exists(v, v.matches('(?i)silver'))
    name: Bronze rate limiting tier
    actions:
      - type: rate-limit
        config:
          name: Allow 1000 requests per minute
          algorithm: sliding_window
          capacity: 1000
          rate: 60s
          bucket_key:
            - conn.ClientIP
  - expressions:
      - getReqHeader('tier').exists(v, v.matches('(?i)gold'))
    name: Gold rate limiting tier
    actions:
      - type: rate-limit
        config:
          name: Allow 10000 requests per minute
          algorithm: sliding_window
          capacity: 10000
          rate: 60s
          bucket_key:
            - conn.ClientIP


Looking for a quick way to test your new drop-in rate limiting policies? This loop prints out the response status code from curl, showing you exactly when good 200 status codes become 429, indicating Too Many Requests.

for i in `seq 1 20`; do \
  curl -s -o /dev/null \
    -w "\n%{http_code}" \
    -X GET https://{YOUR_NGROK_DOMAIN}/legend ; \
  done

Template #5: Block traffic from specific countries

Sometimes, you must refuse traffic from specific countries due to internal policy or sanctions applied by the country from which you operate. With the conn.Geo.CountryCode connection variable, ngrok's API gateway lets you send a custom response with a status code and content to deliver as much context as you want or are required to provide to the failed request.

Replace {COUNTRY_01} and {COUNTRY-02}, or add more countries with any of the standard ISO country codes.

---
on_http_request:
  - expressions:
      - conn.Geo.CountryCode in ['{COUNTRY_01}', '{COUNTRY_02}>']
    name: Block traffic from unwanted countries
    actions:
      - type: custom-response
        config:
          status_code: 401
          content: 'Unauthorized request due to country of origin'

Template #6: Maintain and deprecate API versions

As you continue improving your API, whether to add features or fix security flaws, you’ll eventually want to migrate consumers to newer versions. If your developer documentation instructs consumers to use an X-Api-Version header with their requests, you can quickly increment the supported version and deny requests to others.

This example also demonstrates how your custom responses can also be formatted in JSON.

---
on_http_request:
  - expressions:
      - "'2' in req.Headers['X-Api-Version']"
    name: Deprecate API v2
    actions:
      - type: custom-response
        config:
          status_code: 400
          content: >
            {
              "error": {
                "message": "Version 2 of the API is no longer supported. Use Version 3 instead."
              }
            }

Template #7: Manipulate headers on inbound requests

When you manipulate headers on requests, you can then provide your upstream service more context and detail to perform custom business logic. If your API returns prices on goods for sale, for example, your upstream service could localize prices using the API consumer’s country code.

Your headers can use arbitrary strings, like the is-ngrok header in the example before, or any request variable.

---
on_http_request:
  - name: Add headers to requests
    actions:
      - type: add-headers
        config:
          headers:
            is-ngrok: "1"
            country: ${.ngrok.geo.country_code}

Template #8: Add compression to your responses

If your upstream service can't compress responses or you would like ngrok to do the work, you can compress all responses using the gzip, deflate, br, or compress algorithms.

---
on_http_response:
  - expressions: []
    name: Add compression
    actions:
      - type: compress-response
        config:
          algorithms:
            - gzip
            - br
            - deflate
            - compress

Template #9: Enforce the TLS version of requests

ngrok’s API gateway lets you quickly add checks to requests to ensure they meet your internal security requirements and send an informative error message if not.

---
on_http_request:
  - expressions:
      - req.ClientTLS.Version > '1.3'
    name: Reject requests using old TLS versions
    actions:
      - type: custom-response
        config:
          status_code: 401
          content: "Unauthorized: bad TLS version"

Template #10: Log unsuccessful events to your observability platform

This API policy logs every unsuccessful request to ngrok's eventing system, by checking all responses with status codes of less than 200 or greater than or equal to 300, letting you observe the effectiveness of any API traffic policy in real time.

---
on_http_response:
  - expressions:
      - res.StatusCode < '200' && res.StatusCode >= '300'
    name: Log unsuccessful requests
    actions:
      - type: log
        config:
          metadata:
            message: Unsuccessful request
            edge_id: {YOUR_NGROK_DOMAIN}
            success: false

Template #11: Limit request (POST/PUT) size limits

If your API accepts new documents or updates to existing ones via user input, you could be at risk of excessively large requests—either accidental or malicious in origin—that create performance bottlenecks in your upstream server or excessive costs due to higher resource usage.

---
on_http_request:
  - expressions
      - req.Method == 'POST' || req.Method == 'PUT'
      - req.ContentLength >= 1000
    name: Block POST/PUT reqests of excessive length
    actions:
      - type: custom-response
        config:
          status_code: 400
          content: 'Error: content length'

Time to get composable with ngrok's API gateway

Get started with the ngrok API gateway by signing up for ngrok and checking out the Traffic Policy engine on your first Edge. Once your ngrok agent is running, you can use these drop-in API policy management examples and start shaping the security and availability of your endpoints in a few minutes.

Don’t be afraid to experiment with API policies! Feel free to mix and match the examples provided, add in additional actions we haven’t covered, and even try your hand at custom logic using the Common Expression Language (CEL) expressions at your disposal.

If you're looking for a more direct path toward using ngrok's composable API gateway, check out our end-to-end guides:

We’re also building a template gallery in our documentation for common-to-unconventional use cases for API policy management. If you extend one of the drop-in templates or create your own, we’d love to see a pull request in the ngrok-docs repository or a message on the ngrok community repo about what you've built.

Share this post
Joel Hans
Joel Hans is a Senior Developer Educator. Away from blog posts and demo apps, you might find him mountain biking, writing fiction, or digging holes in his yard.
API gateway
Traffic Policy
Gateways
Production