How to Use the Zuplo API?

Hands-on Zuplo API tutorial: create a project, define routes, add API key auth and rate limiting, write a TypeScript policy, deploy to the edge, and test with Apidog.

Ashley Innocent

Ashley Innocent

27 April 2026

How to Use the Zuplo API?

Apidog for Enterprise

On-Premises Deploy

SSO & RBAC

SOC 2 Compliant

Explore Apidog Enterprise

If you have read about Zuplo and want to ship something real with it, this is the post for you. The platform is fast to learn, but the documentation is spread across portal flows, CLI commands, and learning-center articles. This guide stitches the pieces together into one tutorial: create a project, expose a route, add API key auth and rate limiting, write a custom TypeScript policy, deploy to the edge, and test the whole thing with Apidog.

button

By the end you will have a working API gateway running in front of your origin, with authentication, rate limiting, an auto-generated developer portal, and a CI-friendly Git workflow. The whole walkthrough takes about thirty minutes.

If you are still deciding whether Zuplo is the right tool, start with our companion post: What is the Zuplo API gateway. For everything else, the Zuplo documentation covers edge cases this guide skips.

TL;DR

Prerequisites

You need three things before you start:

For local development you also need a code editor. VS Code with the TypeScript extension is the path of least resistance, and you can pair it with the Apidog VS Code extension to fire requests without leaving your editor.

Step 1: Create your Zuplo project

You have two ways to start: the web portal or the CLI. Most teams begin in the portal because it is faster to demo, then migrate to the CLI once they want CI/CD.

Option A: Portal-first

  1. Sign in at portal.zuplo.com.
  2. Click “New Project” and pick a name like acme-gateway.
  3. Choose “Empty Project” so nothing is auto-created.
  4. The Code tab opens to a starter file tree.

The portal links the project to a managed Git repo by default. You can connect your own GitHub, GitLab, Bitbucket, or Azure DevOps repo from Settings later.

Option B: CLI-first

The CLI scaffolds the same project layout locally so you can edit in your IDE and use Git from day one.

npm create zuplo@latest -- --name acme-gateway
cd acme-gateway
npm install
npm run dev

The dev server starts on port 9000 and prints a link to the local Route Designer at http://localhost:9100. Any change you make in the editor or in the designer hot-reloads immediately.

To link the local project to your Zuplo account once you are ready to deploy:

npx zuplo link

Pick the account and environment when prompted. From here, npx zuplo deploy ships the current Git branch.

Step 2: Define your first route

Open config/routes.oas.json. This is an OpenAPI 3 document with Zuplo extensions for handlers and policies. Add a route that forwards GET /v1/products to your origin:

{
  "openapi": "3.1.0",
  "info": { "title": "Acme Gateway", "version": "1.0.0" },
  "paths": {
    "/v1/products": {
      "get": {
        "summary": "List products",
        "operationId": "list-products",
        "x-zuplo-route": {
          "corsPolicy": "anything-goes",
          "handler": {
            "export": "urlForwardHandler",
            "module": "$import(@zuplo/runtime)",
            "options": {
              "baseUrl": "${env.ORIGIN_URL}"
            }
          },
          "policies": { "inbound": [] }
        },
        "responses": {
          "200": { "description": "Success" }
        }
      }
    }
  }
}

A few details worth noticing. The x-zuplo-route extension is where Zuplo lives inside an otherwise-vanilla OpenAPI file. The handler describes what happens when the route matches; urlForwardHandler is the built-in proxy. The ${env.ORIGIN_URL} reference pulls from environment variables so you can target different backends per environment.

Set ORIGIN_URL from Settings > Environment Variables in the portal, or by editing config/.env locally. Use https://echo.zuplo.io if you do not have a real origin yet.

Save and the local dev server reloads. Hit http://localhost:9000/v1/products and you should see the echoed request. Deployed gateways will respond from the closest edge data center instead.

Step 3: Add API key authentication

Public APIs need credentials. Zuplo ships a managed API key service so you do not have to build a key store yourself.

Edit the route to add the inbound policy:

"policies": {
  "inbound": ["api-key-auth"]
}

Then add the policy definition to config/policies.json (Zuplo creates this file the first time you add a policy):

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

Now create a consumer (the entity that owns one or more API keys):

  1. Go to Services > API Key Service in the portal.
  2. Click “Create Consumer”.
  3. Set the subject to a stable identifier like acme-customer-1.
  4. Add the email of whoever should manage the key.
  5. Copy the generated API key.

Test with curl. Without the header, you should see a 401:

curl -i https://YOUR-PROJECT.zuplo.app/v1/products
# HTTP/2 401

With the header, you should see the original 200 response:

curl -i https://YOUR-PROJECT.zuplo.app/v1/products \
  -H "Authorization: Bearer YOUR_API_KEY"
# HTTP/2 200

If you prefer driving this from a real client, import the gateway’s OpenAPI spec into Apidog, set a global header for Authorization: Bearer {{api_key}}, and bind api_key to an environment variable. You get a clean test surface for every route in seconds.

Step 4: Rate limit the route

Never ship a public API without rate limits. The default Zuplo rate limit policy gives you per-IP, per-key, or per-custom-attribute throttling.

Add it to the inbound list, after auth:

"policies": {
  "inbound": ["api-key-auth", "rate-limit-by-key"]
}

Define it in config/policies.json:

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

rateLimitBy: "sub" keys the bucket on the authenticated subject from the API key policy, so each customer gets their own 60-per-minute budget. Replace with "ip" if you want to throttle anonymous traffic.

The 61st request inside any sixty-second window returns 429 with retry headers attached. Test it by firing 70 requests in a loop and watching the response codes flip.

for i in {1..70}; do
  curl -s -o /dev/null -w "%{http_code}\n" \
    https://YOUR-PROJECT.zuplo.app/v1/products \
    -H "Authorization: Bearer YOUR_API_KEY"
done | sort | uniq -c

You should see 60 lines reading 200 and 10 reading 429.

Step 5: Validate request payloads

If you have a POST route that takes a JSON body, the request validation policy catches malformed payloads at the gateway instead of at your origin. It uses the JSON Schema embedded in your OpenAPI operation, so you get this for free if your spec is accurate.

Add a route with a request body:

"/v1/products": {
  "post": {
    "summary": "Create product",
    "operationId": "create-product",
    "requestBody": {
      "required": true,
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "required": ["name", "priceCents"],
            "properties": {
              "name": { "type": "string", "minLength": 1 },
              "priceCents": { "type": "integer", "minimum": 1 },
              "category": { "type": "string", "enum": ["food", "drink"] }
            }
          }
        }
      }
    },
    "x-zuplo-route": {
      "handler": { /* same as above */ },
      "policies": {
        "inbound": [
          "api-key-auth",
          "rate-limit-by-key",
          "validate-request"
        ]
      }
    }
  }
}

Add the policy:

{
  "name": "validate-request",
  "policyType": "open-api-request-validation-inbound",
  "handler": {
    "export": "OpenApiRequestValidationInboundPolicy",
    "module": "$import(@zuplo/runtime)",
    "options": {
      "validateBody": "reject"
    }
  }
}

Now a POST with a missing field is rejected with a 400 before it reaches your origin. Test it with Apidog by saving a “happy path” request, a “missing required field” request, and a “wrong enum value” request as separate examples in the same request group. You can run all three with one click.

Step 6: Write a custom TypeScript policy

Pre-built policies cover most of what teams need. The point of Zuplo, though, is the moment you need something custom. Here is an outbound policy that adds a Cache-Control header for paid customers and no-store for free ones.

Create modules/tiered-cache.ts:

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

interface PolicyOptions {
  paidPlanHeader: string;
  paidMaxAge: number;
}

export default async function (
  response: Response,
  request: ZuploRequest,
  context: ZuploContext,
  options: PolicyOptions,
): Promise<Response> {
  const plan = request.user?.data?.plan ?? "free";

  if (plan === "free") {
    response.headers.set("Cache-Control", "no-store");
  } else {
    response.headers.set(
      "Cache-Control",
      `public, max-age=${options.paidMaxAge}`,
    );
  }

  context.log.info(`Cache header set for plan=${plan}`);
  return response;
}

Wire it into config/policies.json:

{
  "name": "tiered-cache",
  "policyType": "custom-code-outbound",
  "handler": {
    "export": "default",
    "module": "$import(./modules/tiered-cache)",
    "options": {
      "paidPlanHeader": "x-plan",
      "paidMaxAge": 300
    }
  }
}

And reference it from the route:

"policies": {
  "inbound": ["api-key-auth", "rate-limit-by-key"],
  "outbound": ["tiered-cache"]
}

The custom policy is just a function. You can unit test it with Vitest or Jest by passing in a synthetic Response and ZuploRequest and asserting on the headers, no integration harness required.

Step 7: Deploy to the edge

Deployment is a Git push.

git add .
git commit -m "Add products gateway with auth, rate limit, and tiered cache"
git push origin feature/products-gateway

Zuplo builds a preview environment for every branch and prints the URL in the build log. The preview gets its own subdomain like https://acme-gateway-feature-products-gateway-abc123.zuplo.app, with all your policies active and pointing to whatever ORIGIN_URL is set for that environment.

Test the preview URL with Apidog by setting it as a new environment in your project. Run your full test suite against it. If everything passes, merge the branch.

git checkout main
git merge feature/products-gateway
git push origin main

The merge triggers the production deploy. Within sixty seconds the new version is live in 300+ edge locations. Promote and rollback are both git push operations; there is no separate UI.

Step 8: Generate the developer portal

The portal is hosted at https://YOUR-PROJECT.developers.zuplo.com and rebuilds on every deploy. It includes:

If your OpenAPI spec has good descriptions and examples, the portal looks finished without further work. If your spec is thin, this is the moment you find out.

To customize, the portal source ships as a separate Next.js app you can fork from the Zuplo developer portal repo on GitHub. Most teams stay on the hosted version.

Step 9: Test everything with Apidog

Once your gateway is live, the discipline that prevents production incidents is testing every route, every policy, and every error path. Apidog makes this fast.

The workflow that works well:

  1. Import the gateway’s OpenAPI spec from https://YOUR-PROJECT.zuplo.app/openapi. Apidog turns each operation into a request you can fire.
  2. Create environments for local, preview, and production, each with its own base_url and api_key.
  3. Save at minimum three requests per route: happy path, auth failure, and rate-limit trigger. Run them as a group before every deploy.
  4. Use Apidog’s automated test scenarios to chain calls together (create a product, list it, delete it) and assert on response shapes.
  5. Generate code samples in your team’s primary language and paste them into your runbooks.

If you are migrating from Postman, the API testing without Postman guide walks through the import. Download Apidog if you have not already.

Common questions about using Zuplo

How do I switch a route between environments without changing the spec?

Use environment variables. Define ORIGIN_URL per environment in the portal Settings or in config/.env locally, and reference it as ${env.ORIGIN_URL} inside the handler options. The route stays identical; only the variable changes.

Can I run Zuplo offline?

Yes. npm run dev starts a local gateway on port 9000 with the local Route Designer on 9100. Custom policies, validation, and rate limiting all work locally; the only thing that requires an internet connection is the managed API key service, and you can run npx zuplo link to use the cloud service from your local instance.

How do I roll back a bad deploy?

git revert the merge commit and push. Zuplo redeploys the previous state. There is no separate “rollback” button because the Git history is the source of truth.

What happens to in-flight requests during a deploy?

Deployments are atomic at the edge; in-flight requests finish on the old version and new requests hit the new version. There is no downtime window.

Can I use Zuplo with gRPC or WebSockets?

Yes. The urlForwardHandler proxies WebSocket upgrades transparently, and gRPC is supported through the gRPC handler. REST and GraphQL are first-class and the most common case.

How do I expose my Zuplo API to AI agents?

Add the MCP Server Handler to a route, point it at your OpenAPI spec, and pick the operations to expose. The same auth and rate-limit policies apply to MCP requests. The Zuplo MCP Server documentation covers the setup.

How much does the gateway cost in production?

The free tier covers 100K requests per month. The Builder plan adds 1M requests for $25 per month, and additional requests cost $100 per 100K. Enterprise pricing starts at $1,000 per month on an annual contract. Full breakdown on the Zuplo pricing page.

Conclusion

You now have a working Zuplo gateway with API key auth, per-key rate limiting, request validation, a custom TypeScript outbound policy, and a developer portal, all deploying through Git to the global edge. The same project handles preview environments, production rollouts, and AI agent access through MCP.

The piece that keeps it stable is the test loop. Use Apidog against every preview before it merges, and you will catch the broken auth headers, the missing schema fields, and the rate limits that were accidentally too generous before they ship. Download Apidog and wire it into your gateway today.

button

Explore more

How to Test the Sakana Fugu API in Apidog?

How to Test the Sakana Fugu API in Apidog?

Test the Sakana Fugu API in Apidog: build OpenAI-compatible requests, inspect SSE streaming, read usage, and compare balanced vs Ultra latency.

22 June 2026

How to Use the Sakana Fugu API?

How to Use the Sakana Fugu API?

Get started with the Sakana Fugu API: create a key at console.sakana.ai, point your OpenAI client at the endpoint, and send chat completions in Python or JS.

22 June 2026

How to Get Access to Sakana Fugu ?

How to Get Access to Sakana Fugu ?

How to access Sakana Fugu: sign in at console.sakana.ai, find keys and pricing, check beta vs GA status, and get the honest answer on whether it is free.

22 June 2026

Practice API Design-first in Apidog

Discover an easier way to build and use APIs

How to Use the Zuplo API?