Skip to main content
Waygate

Control how your API behaves at runtime. Roll out changes, enforce limits, and manage endpoints without redeploying.

PyPI Python versions License



What is Waygate

Waygate is an API behavior control layer for Python web applications.

It lets you control how your API responds — per request, per user, and per route — at runtime, without modifying business logic or redeploying.

Most teams handle API control in the wrong place: inside business logic with conditionals, in infrastructure with limited flexibility, or through redeploys for simple changes. Waygate gives you a dedicated runtime layer for that instead.


Solve real production problems

  • Roll out a new endpoint to a subset of users safely
  • Disable a broken route instantly without a hotfix
  • Enforce rate limits per user or plan with no code changes
  • Restrict features by environment or segment
  • Schedule maintenance without touching code
  • Manage all of the above from one dashboard, CLI, or REST API

Key features

Core (waygate.core)

These features are framework-agnostic and available to any adapter.

Feature Description
🚩 Feature flags Boolean, string, integer, float, and JSON flags with targeting rules, user segments, percentage rollouts, prerequisites, and a live evaluation stream. Built on the OpenFeature standard
🚦 Rate limiting Per-IP, per-user, per-API-key, or global counters with tiered limits, burst allowance, and runtime mutation
⏰ Scheduled windows asyncio-native scheduler, maintenance windows activate and deactivate automatically
🔔 Webhooks Fire HTTP POST on every state change. Built-in Slack formatter and custom formatters supported
📋 Audit log Every state change is recorded: who, when, what route, old status to new status
🖥️ Admin dashboard HTMX-powered UI with live SSE updates, no JS framework required
🖱️ REST API + CLI Full programmatic control from the terminal or CI pipelines, works over HTTPS remotely
🏗️ Waygate Server Centralised control plane for multi-service architectures. SDK clients sync state via SSE with zero per-request latency
🌐 Multi-service CLI WAYGATE_SERVICE env var scopes every command; waygate services lists connected services
⚡ Zero-restart control State changes take effect immediately, no redeployment or server restart needed
🔄 Sync & async Full support for both async def and plain def route handlers. Use await engine.* or engine.sync.*
🛡️ Fail-open by default If the backend is unreachable, requests pass through. Waygate never takes down your API
🔌 Pluggable backends In-memory (default), file-based JSON, or Redis for multi-instance deployments
🧪 Test mode Disable lifecycle checks or rate limits per engine instance, via env var, or scoped to a test block with waygate.testing.bypass

Framework adapters

FastAPI (waygate.fastapi) ✅ supported

Feature Description
🎨 Decorator-first DX @maintenance, @disabled, @env_only, @force_active, @deprecated, @rate_limit. State lives next to the route
📄 OpenAPI integration Disabled / env-gated routes hidden from /docs; deprecated routes flagged; live maintenance banners in the Swagger UI
🧩 Dependency injection All decorators work as Depends(), enforcing route state per-handler without middleware
🎨 Custom responses Return HTML, redirects, or any response shape for blocked routes. Set per-route or as an app-wide default on the middleware
🔀 WaygateRouter Drop-in APIRouter replacement that auto-registers route metadata with the engine at startup
Waygate OpenAPI integration Waygate maintenance banner in Swagger UI

Disabled and env-gated routes hidden from /docs. Maintenance banners injected live.


Install

uv add "waygate[all]"
# or: pip install "waygate[all]"

Quickstart

We currently support FastAPI. More framework adapters are on the way.

from fastapi import FastAPI
from waygate import make_engine
from waygate.fastapi import (
    WaygateMiddleware, WaygateAdmin, apply_waygate_to_openapi,
    maintenance, env_only, disabled, force_active, deprecated,
)

engine = make_engine()

app = FastAPI()
app.add_middleware(WaygateMiddleware, engine=engine)

@app.get("/payments")
@maintenance(reason="DB migration - back at 04:00 UTC")
async def get_payments():
    return {"payments": []}

@app.get("/health")
@force_active
async def health():
    return {"status": "ok"}

apply_waygate_to_openapi(app, engine)
app.mount("/waygate", WaygateAdmin(engine=engine, auth=("admin", "secret")))
GET /payments  → 503  {"error": {"code": "MAINTENANCE_MODE", ...}}
GET /health    → 200  always

Manage routes from the CLI with no code changes or restarts:

waygate config set-url http://localhost:8000/waygate
waygate login admin
waygate status
waygate enable GET:/payments
waygate global enable --reason "Deploying v2" --exempt /health
Waygate admin dashboard

Admin dashboard — route states, audit log, rate limits, and feature flags. No JS framework required.

Decorators

Decorator Effect Status
@maintenance(reason, start, end) Temporarily unavailable 503
@disabled(reason) Permanently off 503
@env_only("dev", "staging") Restricted to named environments 403 elsewhere
@deprecated(sunset, use_instead) Still works, injects deprecation headers 200
@force_active Bypasses all waygate checks Always 200
@rate_limit("100/minute") Cap requests per IP, user, API key, or globally 429

Custom responses (FastAPI)

By default, blocked routes return a structured JSON error body. You can replace it with HTML, a redirect, plain text, or custom JSON in two ways:

Per-route: pass response= directly on the decorator:

from starlette.requests import Request
from starlette.responses import HTMLResponse, RedirectResponse
from waygate.fastapi import maintenance, disabled

def maintenance_page(request: Request, exc: Exception) -> HTMLResponse:
    return HTMLResponse(
        f"<h1>Down for maintenance</h1><p>{exc.reason}</p>", status_code=503
    )

@router.get("/payments")
@maintenance(reason="DB migration", response=maintenance_page)
async def payments():
    return {"payments": []}

@router.get("/orders")
@maintenance(reason="Upgrade in progress", response=lambda *_: RedirectResponse("/status"))
async def orders():
    return {"orders": []}

Global default: set once on WaygateMiddleware, applies to every route without a per-route factory:

app.add_middleware(
    WaygateMiddleware,
    engine=engine,
    responses={
        "maintenance": maintenance_page,
        "disabled": lambda req, exc: HTMLResponse(
            f"<h1>Gone</h1><p>{exc.reason}</p>", status_code=503
        ),
    },
)

Resolution order: per-route response= then global responses[...] then built-in JSON. The factory can be sync or async and receives the live Request and the WaygateException that triggered the block.

Rate limiting

from waygate.fastapi import rate_limit

@router.get("/public/posts")
@rate_limit("10/minute")               # 10 req/min per IP
async def list_posts():
    return {"posts": [...]}

@router.get("/users/me")
@rate_limit("100/minute", key="user")  # per authenticated user
async def get_current_user():
    ...

@router.get("/reports")
@rate_limit(                           # tiered limits per plan
    {"free": "10/minute", "pro": "100/minute", "enterprise": "unlimited"},
    key="user",
)
async def get_reports():
    ...

Update limits at runtime with no redeploy:

waygate rl set GET:/public/posts 20/minute   # raise the limit live
waygate rl reset GET:/public/posts           # clear counters
waygate rl hits                              # blocked requests log

Requires waygate[rate-limit]. Powered by limits.


Feature flags

waygate ships a full feature flag system built on the OpenFeature standard. All five flag types, multi-condition targeting rules, user segments, percentage rollouts, and a live evaluation stream. Managed from the dashboard or CLI with no code changes.

from waygate import (
    FeatureFlag, FlagType, FlagVariation, RolloutVariation,
    TargetingRule, RuleClause, Operator, EvaluationContext,
)

engine.use_openfeature()

await engine.save_flag(
    FeatureFlag(
        key="new-checkout",
        name="New Checkout Flow",
        type=FlagType.BOOLEAN,
        variations=[
            FlagVariation(name="on",  value=True),
            FlagVariation(name="off", value=False),
        ],
        off_variation="off",
        fallthrough=[
            RolloutVariation(variation="on",  weight=20_000),  # 20%
            RolloutVariation(variation="off", weight=80_000),  # 80%
        ],
        targets={"on": ["beta_tester_1"]},
        rules=[
            TargetingRule(
                description="Enterprise users always get the new flow",
                clauses=[RuleClause(attribute="plan", operator=Operator.IS, values=["enterprise"])],
                variation="on",
            )
        ],
    )
)

# Evaluate in an async route handler
ctx = EvaluationContext(key=user_id, attributes={"plan": user.plan})
enabled = await engine.flag_client.get_boolean_value("new-checkout", False, ctx)

# Evaluate in a sync def handler (thread-safe)
enabled = engine.sync.flag_client.get_boolean_value("new-checkout", False, {"targeting_key": user_id})

Manage flags and segments from the CLI:

waygate flags list
waygate flags eval new-checkout --user user_123
waygate flags disable new-checkout          # kill-switch
waygate flags enable new-checkout
waygate flags stream                        # live evaluation events

waygate segments create beta_users --name "Beta Users"
waygate segments include beta_users --context-key user_123,user_456
waygate segments add-rule beta_users --attribute plan --operator in --values pro,enterprise

Requires waygate[flags].


Framework support

waygate's core is completely framework-agnostic with zero framework imports. Adapters plug into the engine and expose framework-native patterns like decorators, middleware, and routers.

Framework Status Adapter
FastAPI ✅ Supported waygate.fastapi
More coming 🔜 On the way

Want your framework supported? Open an issue.


Backends

Embedded mode (single service)

Backend Persistence Multi-instance Best for
MemoryBackend No No Development, tests
FileBackend Yes No (single process) Simple single-instance prod
RedisBackend Yes Yes Load-balanced / multi-worker prod

For rate limiting in multi-worker deployments, use RedisBackend. Counters are atomic and shared across all processes.

Waygate Server mode (multi-service)

Run a dedicated WaygateServer process and connect each service via WaygateSDK. State is managed centrally; enforcement happens locally with zero per-request network overhead.

# Waygate Server (centralised, runs once)
from waygate.server import WaygateServer
waygate_app = WaygateServer(backend=MemoryBackend(), auth=("admin", "secret"))

# Each service (connects to the Waygate Server)
from waygate.sdk import WaygateSDK
sdk = WaygateSDK(server_url="http://waygate-server:9000", app_id="payments-service")
sdk.attach(app)
Scenario Waygate Server backend SDK rate_limit_backend
Multi-service, single replica each MemoryBackend or FileBackend not needed
Multi-service, multiple replicas RedisBackend RedisBackend (shared counters)

Documentation

Full documentation at attakay78.github.io/waygate

Tutorial Get started in 5 minutes
Decorators reference All decorator options
Rate limiting Per-IP, per-user, tiered limits
Feature flags Targeting rules, segments, rollouts, live events
WaygateEngine reference Programmatic control
Backends Memory, File, Redis, Waygate Server, custom
Admin dashboard Mounting WaygateAdmin
CLI reference All CLI commands
Waygate Server guide Multi-service centralized control
Distributed deployments Multi-instance backend guide
Production guide Monitoring & deployment automation

License

MIT

Metadata

Release files for waygate 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for waygate 0.2.0
File Size Uploaded
waygate-0.2.0.tar.gz 1.5 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for waygate 0.2.0
File Interpreter ABI Platform
waygate-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.8 MB

Release files / waygate-0.2.0.tar.gz

Download URL waygate-0.2.0.tar.gz
Size 1.5 MB
Tags Source
SHA-256 checksum
How to use checksums
8796850681f5cc22deeb937ad86d3b92c27a5af212a0c2cf77f687e303dbcca5
BLAKE2b-256 checksum
How to use checksums
1bd44e760598b9c44028b837c385d2145944922830fae0abcbe24487b2fef923
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.

Transparency log

Release files / waygate-0.2.0-py3-none-any.whl

Download URL waygate-0.2.0-py3-none-any.whl
Size 304.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9d4cc08ea5243015008fc9fc718130893331395da31412de024d83d31f59f2b7
BLAKE2b-256 checksum
How to use checksums
5ae71b5aa933023ed7cfe89a3dcf84a690773b608bf67897b8abb788f293dafd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page