Zuplo
API Gateway

Migrating from Apigee to Zuplo: Policy Mapping, Architecture Translation, and Step-by-Step Guide

Nate TottenNate Totten
March 20, 2026
15 min read

A complete guide to migrating from Apigee Edge to Zuplo — covering policy mapping, architecture differences, developer portal migration, and zero-downtime cutover strategies.

Apigee Edge is winding down. The final Private Cloud (OPDK) version, 4.53.01, reaches end of life on February 26, 2027. The Classic UI shut down on November 2, 2025. Public Cloud customers are getting increasingly urgent migration notices. If you’re running APIs on Apigee Edge, the question isn’t whether to migrate — it’s where.

Google’s recommended path is Apigee X, but that migration is notoriously complex. Apigee X is not an in-place upgrade — it’s a different platform with different infrastructure, APIs, and deployment models. SADA, a Google Cloud partner, describes the Edge-to-X migration as “a daunting task that requires lots of careful planning, scripting, testing, and coordination.”

This guide walks you through migrating from Apigee to Zuplo — mapping every policy, translating architecture concepts, and cutting over with zero downtime. For the business case behind this move, see our companion post on Apigee Edge reaching end of life.

What This Guide Covers

  1. Architecture Comparison
  2. Policy-to-Policy Mapping
  3. Complete Policy Reference
  4. Developer Portal Migration
  5. API Product and App Migration
  6. Step-by-Step Migration Process
  7. Handling Custom Apigee Policies
  8. Analytics and Monitoring Migration
  9. Common Migration Pitfalls
  10. Post-Migration Optimization

Architecture Comparison

Before diving into policy mapping, it helps to understand how Apigee and Zuplo differ architecturally. The two platforms share the same goal — managing API traffic — but take fundamentally different approaches.

Apigee’s Architecture

Apigee uses a proxy + target model. You define API proxies that intercept client requests, apply policies (written in XML), and forward traffic to target endpoints. Configuration is split across proxy bundles, shared flows, target servers, environment configurations, and key-value maps. Deployment is region-bound — tied to Google Cloud Platform in the case of Apigee X, or to your own infrastructure for Private Cloud.

Custom logic lives in JavaScript callouts (running on a proprietary runtime), Java callouts (compiled JAR files deployed with your proxy bundle), or Python scripts (limited support). Apigee’s Trireme-based Node.js runtime already reached end of life, further limiting options.

Zuplo’s Architecture

Zuplo is an edge-native, programmable API gateway. Your API configuration is a standard OpenAPI document (routes.oas.json) extended with Zuplo-specific annotations. Policies are defined declaratively in policies.json and executed in a pipeline — inbound policies run before the request reaches your backend, outbound policies run before the response reaches the client.

Custom logic is written in TypeScript and runs on a Web Standards-based runtime (standard Request, Response, and fetch APIs). Everything lives in Git — routes, policies, custom code, environment variables, and developer portal configuration. Push to main and your gateway deploys globally across 300+ edge locations in under 20 seconds.

Key Architectural Differences

  • Configuration format: Apigee uses XML policy bundles. Zuplo uses JSON (OpenAPI + policies) and TypeScript.
  • Deployment model: Apigee is region-bound (GCP for Apigee X). Zuplo deploys to 300+ global edge locations on any cloud.
  • Custom logic runtime: Apigee uses proprietary JavaScript/Java callouts. Zuplo uses standard TypeScript with Web APIs.
  • Configuration management: Apigee requires custom CI/CD tooling. Zuplo is GitOps-native — push to Git and it deploys.
  • Developer portal: Apigee uses a separate Drupal-based portal. Zuplo auto-generates a portal from your OpenAPI spec.

Policy-to-Policy Mapping

This is the core of any Apigee migration. Every Apigee policy has a Zuplo equivalent — either a built-in policy, a configuration option, or a short TypeScript module.

Traffic Management Policies

SpikeArrest → Rate Limiting

Apigee’s SpikeArrest policy smooths traffic spikes by limiting the peak request rate. In Zuplo, the rate-limit-inbound policy provides equivalent functionality with more flexibility — you can limit by IP, authenticated user, API key, or a custom function.

JSONjson
{
  "name": "rate-limit",
  "policyType": "rate-limit-inbound",
  "handler": {
    "export": "RateLimitInboundPolicy",
    "module": "$import(@zuplo/runtime)",
    "options": {
      "rateLimitBy": "ip",
      "requestsAllowed": 100,
      "timeWindowMinutes": 1
    }
  }
}

Quota → Rate Limiting (per-user)

Apigee’s Quota policy enforces long-term usage limits per developer or app. In Zuplo, you use the same rate-limit-inbound policy with rateLimitBy set to "user" or "function" for per-consumer tier limits.

Authentication Policies

VerifyAPIKey → API Key Authentication

Apigee’s VerifyAPIKey policy validates API keys sent as query parameters or headers. Zuplo’s api-key-inbound policy does the same, backed by a globally distributed API key service.

JSONjson
{
  "name": "api-key-auth",
  "policyType": "api-key-inbound",
  "handler": {
    "export": "ApiKeyInboundPolicy",
    "module": "$import(@zuplo/runtime)",
    "options": {}
  }
}

OAuthV2 (VerifyAccessToken) → JWT Authentication

Apigee’s OAuthV2 policy with the VerifyAccessToken operation validates OAuth 2.0 bearer tokens. In Zuplo, the open-id-jwt-auth-inbound policy validates JWTs from any OpenID-compliant provider. Zuplo also offers provider-specific policies for Auth0, Okta, Clerk, Supabase, AWS Cognito, and Firebase.

BasicAuthentication → Basic Auth

Apigee’s BasicAuthentication policy decodes Base64-encoded credentials. Zuplo’s basic-auth-inbound policy provides the same functionality.

Message Transformation Policies

AssignMessage → Header / Body / Query Param Policies

Apigee’s AssignMessage is a multipurpose policy for setting headers, query parameters, form parameters, and the request body. In Zuplo, these capabilities are split into focused policies:

XMLToJSON / JSONToXML → XML to JSON Outbound

Apigee’s format conversion policies have a direct equivalent in Zuplo’s xml-to-json-outbound policy, which is especially useful for modernizing legacy SOAP backends.

ExtractVariables → Custom Code

Apigee’s ExtractVariables policy parses variables from request/response payloads using JSON paths, XML paths, or regex. In Zuplo, you handle this with a custom-code-inbound policy — a TypeScript function that extracts values and stores them on context.custom for downstream policies to use.

Mediation Policies

RaiseFault → Custom Code (return Response)

Apigee’s RaiseFault policy generates custom error responses. In Zuplo, any inbound policy can short-circuit the pipeline by returning a Response object:

TypeScripttypescript
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";

export default async function (request: ZuploRequest, context: ZuploContext) {
  if (!request.headers.get("x-required-header")) {
    return new Response(JSON.stringify({ error: "Missing required header" }), {
      status: 400,
      headers: { "content-type": "application/json" },
    });
  }
  return request;
}

ServiceCallout → fetch() in Custom Code

Apigee’s ServiceCallout policy makes HTTP requests to external services during the proxy flow. In Zuplo, you use the standard fetch() API inside a custom code policy:

TypeScripttypescript
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";

export default async function (request: ZuploRequest, context: ZuploContext) {
  const authResponse = await fetch(
    "https://auth-service.example.com/validate",
    {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ token: request.headers.get("authorization") }),
    },
  );

  if (!authResponse.ok) {
    return new Response("Unauthorized", { status: 401 });
  }

  const authData = await authResponse.json();
  context.custom.userId = authData.userId;

  return request;
}

Routing and Target Policies

RouteRules / TargetEndpoint → URL Forward or URL Rewrite Handler

Apigee’s proxy-to-target routing is handled by Zuplo’s URL Forward Handler (appends the incoming path to a base URL) or URL Rewrite Handler (rewrites URLs using template interpolation).

Security Policies

AccessControl → ACL Policy

Apigee’s AccessControl policy restricts access by IP address. Zuplo’s acl-policy-inbound policy provides similar IP-based access control.

MessageValidation → Request Validation

Apigee’s message validation policies (SOAPMessageValidation, JSONThreatProtection, XMLThreatProtection) map to Zuplo’s request-validation-inbound policy, which validates requests against your OpenAPI schema definitions — including body, query parameters, path parameters, and headers.

Complete Policy Reference

Here is a quick-reference mapping of the most common Apigee policies to their Zuplo equivalents:

  • SpikeArrestrate-limit-inbound
  • Quotarate-limit-inbound (with rateLimitBy: "user" or "function")
  • VerifyAPIKeyapi-key-inbound
  • OAuthV2 (VerifyAccessToken)open-id-jwt-auth-inbound (or provider-specific JWT policies)
  • BasicAuthenticationbasic-auth-inbound
  • AssignMessage (headers)set-headers-inbound / remove-headers-inbound
  • AssignMessage (query params)set-query-params-inbound
  • AssignMessage (body)set-body-inbound
  • XMLToJSONxml-to-json-outbound
  • ExtractVariablescustom-code-inbound (TypeScript)
  • JavaScript / JavaCalloutcustom-code-inbound / custom-code-outbound (TypeScript)
  • ServiceCalloutcustom-code-inbound with fetch()
  • RaiseFault → Return a Response from any custom policy
  • AccessControlacl-policy-inbound
  • JSONThreatProtectionrequest-validation-inbound
  • ResponseCache → Handled at the edge automatically, or via custom code
  • KeyValueMapOperations → Environment variables + context.custom
  • FlowCallout (Shared Flows) → Composite policies (composite-inbound / composite-outbound)

Developer Portal Migration

Apigee’s developer portal is typically Drupal-based — requiring separate hosting, CMS management, theme customization, and manual content updates. Many teams have invested significant effort in customizing their Drupal portal with custom themes, documentation pages, and interactive features.

Zuplo takes a fundamentally different approach. Your developer portal is auto-generated directly from your OpenAPI spec. Every route, parameter, and schema you define in routes.oas.json automatically appears in your portal with:

  • Interactive API documentation — auto-generated from your OpenAPI spec, always in sync with your gateway configuration
  • API Playground — developers can test live endpoints directly from the portal
  • Self-serve API key management — developers sign up, create keys, and manage their own access without admin intervention
  • Custom pages — add rich documentation with MDX (Markdown + JSX components)
  • Custom themes — full visual customization with Tailwind CSS and shadcn themes
  • Custom domains — host your portal on docs.yourcompany.com

Migration Steps for Your Developer Portal

  1. Export your OpenAPI specs from Apigee — use the Apigee management API or download from the Apigee UI
  2. Import into Zuplo — your specs become both your gateway configuration and your portal documentation
  3. Add custom documentation — migrate any guides, tutorials, or reference pages from Drupal to MDX custom pages in Zuplo
  4. Configure authentication — set up developer sign-in using Auth0, Clerk, Supabase, or any OAuth provider
  5. Update DNS — point your developer portal domain to Zuplo

The biggest win here is eliminating the Drupal dependency entirely. No more CMS upgrades, no more theme compatibility issues, no more manually syncing API documentation with your actual API behavior.

API Product and App Migration

Apigee uses a hierarchy of API Products, Developer Apps, and API Keys to control access. Here’s how these concepts translate to Zuplo:

Apigee API Products → Zuplo Routes + Policies

In Apigee, an API Product bundles specific API proxy paths with quota limits and OAuth scopes. In Zuplo, you achieve this by:

  • Defining routes in your OpenAPI spec (routes.oas.json)
  • Attaching policies (rate limiting, authentication) per route
  • Using consumer metadata to control per-consumer access levels

Apigee Developer Apps → Zuplo Consumers

In Apigee, a Developer App represents an application registered by a developer, with one or more API keys. In Zuplo, consumers serve the same purpose:

  • Each consumer represents a person, organization, or service
  • Consumers can have one or more API keys
  • Consumers carry metadata (plan tier, organization name, custom attributes) accessible at runtime via request.user.data
  • Consumers can be tagged for grouping and management

Migrating API Keys

If you need to migrate existing API keys from Apigee (to avoid breaking existing integrations during the transition), you can use Zuplo’s Developer API to programmatically create consumers and import keys. A typical migration script would:

  1. Export developer apps and keys from Apigee’s management API
  2. Create corresponding consumers in Zuplo with matching metadata
  3. Import the API keys so existing client integrations continue working
  4. Run both gateways in parallel during the transition period

Step-by-Step Migration Process

Phase 1: Pre-Migration Audit

Before touching any configuration, inventory everything your Apigee setup does.

Routes and proxies:

  • List every API proxy and its base path
  • Document all route rules and target endpoint configurations
  • Identify conditional flows and their conditions
  • Note any shared flows referenced across proxies

Policies:

  • Catalog every policy by type (security, mediation, traffic management)
  • Document policy execution order in PreFlow, conditional flows, and PostFlow
  • Note any custom JavaScript or Java callouts
  • Identify policies with environment-specific configurations

External dependencies:

  • List all target servers and their configurations
  • Document key-value map entries and their usage
  • Identify service callout endpoints
  • Note any encrypted KVM entries (secrets)

Developer ecosystem:

  • Count active developer apps and API keys
  • Document API product configurations and quota limits
  • Export developer portal customizations and custom pages
  • List custom attributes on developers and apps

Phase 2: Set Up Zuplo

  1. Create your Zuplo project — sign up at portal.zuplo.com and create a new project
  2. Connect to Git — link your project to a GitHub repository for GitOps-based deployments
  3. Import your OpenAPI specs — use the OpenAPI import to bootstrap your route configuration
  4. Configure policies — add authentication, rate limiting, and transformation policies to each route using the policy mapping table above
  5. Add custom logic — rewrite any JavaScript or Java callouts as TypeScript modules in the modules/ directory
  6. Set environment variables — migrate Apigee KVM entries and target server URLs to Zuplo environment variables

Phase 3: Parallel Running

Run Zuplo alongside Apigee to validate behavior before cutting over:

  1. Deploy to a preview environment — every Git branch gets its own isolated Zuplo environment with a unique URL
  2. Run your test suite against Zuplo — point integration tests at the preview URL
  3. Compare responses — verify that Zuplo returns identical responses for the same requests
  4. Load test — confirm that Zuplo handles your peak traffic patterns

Phase 4: Traffic Cutover

Use one of these strategies for a zero-downtime cutover:

DNS-based blue-green deployment:

  1. Lower your API domain’s DNS TTL to 60 seconds (24 hours before cutover)
  2. Switch DNS to point at Zuplo’s edge network
  3. Monitor error rates and latency
  4. Roll back by reverting DNS if anything goes wrong

Canary routing (percentage-based):

  1. Route 5% of traffic to Zuplo while 95% continues through Apigee
  2. Monitor for errors and latency differences
  3. Gradually increase to 25%, then 50%, then 100%
  4. This approach gives you fine-grained control and easy rollback

For more detail on zero-downtime strategies, see our guide to migrating from self-hosted to managed API gateways.

Phase 5: Validation and Cleanup

After full cutover:

  • Verify all routes return expected response codes
  • Confirm authentication works for all methods
  • Validate rate limiting triggers at correct thresholds
  • Check that logs flow to your monitoring system
  • Decommission Apigee proxies and infrastructure

Handling Custom Apigee Policies

Custom logic is where most Apigee migrations stall. If you’ve invested in JavaScript callouts, Java callouts, or Python scripts, you’ll need to rewrite them. The good news: TypeScript is a significant upgrade in every dimension.

JavaScript Callouts → TypeScript Policies

Apigee JavaScript callouts use a proprietary runtime with Apigee-specific objects (context, proxyRequest, proxyResponse). Zuplo uses standard Web APIs — Request, Response, Headers, fetch — that any TypeScript developer already knows.

Apigee JavaScript callout:

Javascriptjavascript
// Apigee JavaScript policy
var apiKey = context.getVariable("request.header.x-api-key");
var tier = context.getVariable("apiproduct.developer.quota.limit");

if (tier === "premium") {
  context.setVariable("target.url", "https://premium-backend.example.com");
} else {
  context.setVariable("target.url", "https://standard-backend.example.com");
}

Zuplo TypeScript equivalent:

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

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

  if (tier === "premium") {
    context.custom.backendUrl = environment.PREMIUM_BACKEND_URL;
  } else {
    context.custom.backendUrl = environment.STANDARD_BACKEND_URL;
  }

  return request;
}

Java Callouts → TypeScript Modules

Java callouts are the heaviest lift in an Apigee migration. Apigee lets you deploy compiled JAR files that run complex business logic — encryption, third-party SDK integrations, data transformations.

In Zuplo, you replace Java callouts with TypeScript modules. For most use cases this is straightforward — TypeScript has excellent ecosystem support for cryptography (crypto Web API), HTTP clients (fetch), and data transformation. For cases where you need a specific Java library, consider:

  • npm equivalents — most Java libraries have TypeScript/JavaScript alternatives on npm
  • External microservice — extract complex Java logic into a standalone service and call it via fetch() from your Zuplo policy
  • WebAssembly — for compute-intensive operations, compile your Java code (or rewrite in Rust/Go) to Wasm

Shared Flows → Composite Policies

Apigee’s shared flows let you define reusable policy sequences that can be called from multiple API proxies via FlowCallout. Zuplo’s equivalent is composite policies — group multiple policies into a single named policy that can be attached to any route:

JSONjson
{
  "name": "standard-security",
  "policyType": "composite-inbound",
  "handler": {
    "export": "CompositeInboundPolicy",
    "module": "$import(@zuplo/runtime)",
    "options": {
      "policies": ["api-key-auth", "rate-limit", "request-validation"]
    }
  }
}

This lets you define your “standard security stack” once and apply it consistently across all routes, just like shared flows in Apigee.

Analytics and Monitoring Migration

Apigee includes built-in analytics with dashboards for traffic, error rates, latency, and developer engagement. Migrating your observability setup requires understanding Zuplo’s analytics model and integrating with your existing monitoring stack.

Built-in Analytics

Zuplo provides built-in analytics dashboards covering request volumes, error rates, latency percentiles, and per-API-key usage. For most teams, this provides immediate visibility without additional configuration.

Integrating with Your Existing Monitoring Stack

If you’re using third-party monitoring tools with Apigee, Zuplo supports direct integrations through logging and metrics plugins:

  • Datadog — stream logs and metrics directly from Zuplo
  • New Relic — real-time log and metrics forwarding
  • Google Cloud Logging — if you’re keeping some GCP infrastructure, Zuplo integrates directly
  • Splunk — log streaming for enterprise SIEM workflows
  • Dynatrace — full observability integration
  • OpenTelemetry — export traces and logs to any OTLP-compatible backend (Honeycomb, Jaeger, and more)

These integrations are configured in your zuplo.runtime.ts file — a TypeScript module that runs at startup:

TypeScripttypescript
import { RuntimeExtensions, environment } from "@zuplo/runtime";
import { DataDogLoggingPlugin } from "@zuplo/runtime";

export function runtimeInit(runtime: RuntimeExtensions) {
  runtime.addPlugin(
    new DataDogLoggingPlugin({
      apiKey: environment.DATADOG_API_KEY,
      url: "https://http-intake.logs.datadoghq.com/api/v2/logs",
    }),
  );
}

Custom Logging

Every request in Zuplo is logged with structured data including the API key, route, response code, latency, and request ID. You can add custom attributes using context.log in any policy or handler:

TypeScripttypescript
context.log.info("Order processed", {
  orderId: order.id,
  customerId: request.user?.sub,
  amount: order.total,
});

Common Migration Pitfalls

Forgetting About Shared Flows

The mistake: You migrate individual API proxies but forget that they depend on shared flows for authentication, logging, or error handling.

The fix: Before migrating any proxy, trace its full execution path including all FlowCallout references. Create composite policies in Zuplo that replicate the shared flow behavior, then attach them to the appropriate routes.

Overlooking Environment-Specific Configuration

The mistake: Apigee’s environment configurations (target servers, KVM entries, resource files) vary between dev, staging, and production. You migrate the production config but forget the environment-specific differences.

The fix: Zuplo handles this with environment variables scoped per environment type. Map each Apigee KVM entry and target server URL to a Zuplo environment variable, and set different values for production vs. preview environments.

Ignoring Conditional Flow Logic

The mistake: Apigee’s conditional flows execute different policy sequences based on request attributes (path suffix, HTTP verb, headers). You migrate the policies but not the conditions.

The fix: In Zuplo, conditional logic is handled either by defining separate routes (each with its own policy set) or by implementing conditions inside a custom code policy. Most Apigee conditional flows map naturally to separate route definitions in your OpenAPI spec.

Big-Bang Migration

The mistake: Migrating all API proxies at once on a single cutover date.

The fix: Migrate proxy by proxy, starting with low-traffic, low-risk endpoints. Use canary routing to validate each batch before moving to the next. This limits blast radius and gives you confidence with each step.

Not Testing Under Real Load

The mistake: Your integration tests pass, so you assume production will work.

The fix: Use shadow traffic testing or replay production access logs against Zuplo. Synthetic tests don’t catch the edge cases that real traffic exposes — unusual header combinations, oversized payloads, concurrent requests from the same client.

Post-Migration Optimization

Once you’ve completed the migration, take advantage of Zuplo features that Apigee didn’t offer:

Edge Deployment

Your APIs now run at 300+ edge locations globally. Requests are automatically routed to the nearest point of presence, reducing latency for users everywhere — not just in the GCP regions where Apigee was deployed.

GitOps Workflow

Every change to your API gateway is a Git commit. Open a pull request and get an isolated preview environment automatically. Review policy changes in code review, just like application code. Roll back by reverting a commit. This is a fundamentally better workflow than Apigee’s UI-driven or script-based deployment model.

OpenAPI-First Development

Your routes.oas.json file is both your gateway configuration and your API documentation source. Changes to routes automatically update your developer portal. No more manually syncing documentation with behavior.

API Monetization

If you’re migrating monetized APIs, Zuplo offers built-in monetization with usage-based billing integrations. Instead of building custom metering on top of Apigee, you get native support for tracking usage, enforcing plan limits, and integrating with billing platforms like Stripe.

TypeScript Ecosystem

With Zuplo, your gateway logic is standard TypeScript. You can use any npm package, share code between your API gateway and your application, and leverage the full TypeScript ecosystem for testing, linting, and type checking.

Next Steps

You now have a complete map from Apigee to Zuplo: every major policy has a direct equivalent, the developer portal migration eliminates your Drupal dependency, and the phased cutover approach keeps production traffic safe throughout. The most common blockers — shared flows, environment-specific KVM entries, and Java callouts — each have a clear TypeScript-based solution.

Migrating from Apigee is a significant project, but it pays dividends in reduced complexity, better developer experience, and lower costs. The key is to approach it methodically — inventory everything, map policies, migrate incrementally, and validate thoroughly.

For a high-level comparison of Apigee and Zuplo features, see the Apigee alternative comparison page. For guidance on migration strategies and zero-downtime patterns, see our guide to migrating from self-hosted to managed API gateways.

Ready to migrate? Start a free Zuplo project and import your first API proxy in minutes — no credit card required.