---
title: "How to Route API Requests to Different Backends"
description: "Learn how to implement Stripe-style environment-based routing using API key metadata to direct requests to sandbox or production backends through a single API endpoint."
canonicalUrl: "https://zuplo.com/blog/2026/02/02/route-api-requests-to-different-backends"
pageType: "blog"
date: "2026-02-02"
authors: "martyn"
tags: "API Gateway"
image: "https://zuplo.com/og?text=Route%20API%20Requests%20to%20Different%20Backends"
---
If you've ever used Stripe's API, you've experienced a clever pattern: test keys
and live keys both hit the same API endpoint, but they route to completely
different backends. Your sandbox requests never touch production data, and you
don't need to remember separate URLs for each environment.

It's called environment-based routing, and in this post we're going to show you
how to implement it using TypeScript and the awesome programmable aspects of the
Zuplo API Gateway.

<CalloutAudience
  variant="useIf"
  items={[
    `Building an API with separate sandbox
and production environments`,
    `Need to route customers to isolated backends for
compliance or data residency`,
    `Want a single API endpoint that serves both
shared and dedicated infrastructure`,
    `Looking to implement Stripe-style
test/live key patterns`,
  ]}
/>

## What is Environment-Based Routing?

Environment-based routing lets you direct API requests to different backend
systems based on metadata attached to the caller's identity. Instead of exposing
separate endpoints for sandbox vs. production (or different customer
environments), you expose a single API URL. The gateway reads the caller's API
key metadata or JWT claims and routes the request to the appropriate backend.

This creates a cleaner developer experience. Your API consumers don't need to
manage multiple base URLs or remember which environment they're targeting. The
key itself carries that context.

## Why Use Environment-Based Routing?

There are three common scenarios where this pattern makes sense to use:

**Sandbox/Production Separation**

The Stripe model. Give developers a test key for development and a live key for
production. Both keys work against your single API endpoint, but test keys route
to sandbox infrastructure with mock data, while live keys route to production
systems with real data. Developers can safely test integrations without worrying
about affecting production.

**Customer Isolation**

For B2B APIs with compliance or data residency requirements, you might need to
route each customer to their own isolated backend. A healthcare API might route
Customer A's requests to their dedicated HIPAA-compliant infrastructure while
Customer B routes to a separate isolated environment. The API surface looks
identical; only the underlying backend differs.

**Hybrid Multi-Tenant Architecture**

Maybe most of your customers share a multi-tenant backend, but your enterprise
customers pay for dedicated infrastructure. Environment-based routing lets you
handle both cases through the same API endpoint. Standard customers route to
shared infrastructure; premium customers route to their dedicated environment.

## How Zuplo Implements This

Zuplo's programmable gateway makes this straightforward. When a request is
authenticated (via API key or JWT), user information becomes available on
`request.user`. This includes metadata you've attached to the API key or custom
claims from the JWT.

A custom inbound policy reads this metadata and sets the backend URL
dynamically:

```typescript
import { ZuploContext, ZuploRequest, environment } from "@zuplo/runtime";

export default async function policy(
  request: ZuploRequest,
  context: ZuploContext,
) {
  const userEnvironment = request.user?.data?.environment;

  if (userEnvironment === "sandbox") {
    context.custom.downstreamUrl = environment.SANDBOX_BACKEND_URL;
  } else if (userEnvironment === "production") {
    context.custom.downstreamUrl = environment.PRODUCTION_BACKEND_URL;
  } else {
    throw new Error("Unknown environment in user data");
  }

  return request;
}
```

<CalloutDoc
  title="Custom Code Inbound Policy"
  description={`Write custom TypeScript policies to intercept and modify requests before they reach your backend.`}
  href="https://zuplo.com/docs/policies/custom-code-inbound"
/>

The URL Rewrite handler then uses `context.custom.downstreamUrl` to forward the
request to the correct backend. The caller never sees which backend handled
their request; they just get the appropriate response.

<CalloutDoc
  title="URL Rewrite Handler"
  description={`Proxy and rewrite requests to different APIs using dynamic URL patterns with context variables.`}
  href="https://zuplo.com/docs/handlers/url-rewrite"
/>

To set this up, you attach metadata to your API keys when creating them. You can
do this in the Zuplo portal for testing, or programmatically via the Zuplo API
when provisioning keys for your users:

```json
{
  "environment": "sandbox"
}
```

Or for production keys:

```json
{
  "environment": "production"
}
```

<CalloutDoc
  title="API Key Authentication"
  description={`Zuplo's API Key Authentication policy validates keys and makes user metadata available on request.user for downstream policies.`}
  href="https://zuplo.com/docs/policies/api-key-inbound"
  features={[
    `Zero-code
setup`,
    `Custom metadata per key`,
    `Built-in key management`,
  ]}
/>

The same pattern works with JWT authentication. Just include the environment (or
customer ID, or tenant info) as a custom claim, and your routing policy reads it
from `request.user.data`.

## Benefits

This approach gives you a single entry point for all your customers.
Documentation, SDKs, and client implementations stay simple because there's one
URL to remember.

Policy enforcement happens uniformly at the gateway. Authentication, rate
limiting, and other policies apply consistently before requests reach any
backend. This ensures security and compliance across all environments without
duplicating configuration.

And because Zuplo policies are just TypeScript, you can implement whatever
routing logic you need: geographic routing, A/B testing, failover handling, or
combinations of multiple factors.

## Try It Yourself

We've published a complete working example that implements Stripe-style
environment routing. The example includes mock backends, pre-configured
policies, and step-by-step instructions for creating API keys with environment
metadata.

<CalloutSample
  title="Environment-Based Routing Example"
  description="A complete working example that implements Stripe-style environment routing with API key metadata. Run locally or deploy directly to your Zuplo account."
  deployUrl="https://zuplo.com/examples/environment-routing"
  localCommand="npx create-zuplo-api --template environment-routing"
  repoUrl="https://github.com/zuplo/zuplo/tree/main/examples/environment-routing"
/>

For the full implementation guide covering additional patterns like
customer-specific routing and hybrid multi-tenant architectures, see our
documentation.

<CalloutDoc
  title="Route to Backends Based on User Identity"
  description={`Learn how to route requests to different backends based on API key metadata, JWT claims, or other user identity information.`}
  href="https://zuplo.com/docs/guides/user-based-backend-routing"
  features={[
    `Customer isolation`,
    `Environment separation`,
    `Multi-tenant
routing`,
  ]}
/>