Release quarantine for mobile and web apps

Block a broken app release from your API in seconds

When an app release ships with a bug that costs money, your API refuses that release on the endpoints that cause harm.

ios 4.7.2 · 1031 09:14 · version live rule off
Serve Pull to block
  1. api-1
  2. api-2
  3. api-3
  4. api-4
  5. api-5
  6. api-6
0 of 6 instances enforcing
  • Other versions keep working
  • Users on the bad version see an update screen
  • No app store review and no redeploy

For teams whose API serves iOS, Android and web apps

Why now

AI tools mean more releases, and a mobile release cannot be rolled back

Small teams now ship app versions every week, and each release gets less human attention. Your backend can roll back in a minute. The app on a customer's phone cannot.

  • Your serverRolls back in a minuteRedeploy the previous build and every request uses it at once.
  • Your mobile appNo rollback existsApple and Google cannot remove a version from phones. It keeps sending requests until each user updates.
  • With UpforcerBlocked in secondsYour API refuses that version for the endpoints that cause harm, and keeps serving everything else.
Five broken releases

Five ways a bad release costs money, and the same fix for each

The companies are invented. The bugs are the kind that reach production every week.

android 4.7.2Food delivery

Pepperline charges dinner twice

A timed-out payment is retried without an idempotency key, so the card is charged again.

block payOrder · android 4.7.2

6 of 6 instances in 3.1 s. Menus and tracking keep working.

ios 4.7.2Ticketing

Gatefold sells the same seat twice

The app skips the seat hold, so two buyers complete the same seat in the same second.

block createOrder · ios 4.7.2

6 of 6 instances in 2.7 s. Browsing and web checkout keep working.

android 4.7.2Ride-hailing

Kerbside gives every ride away cheap

The app decides promo eligibility on its own and applies the first-ride discount to every trip.

block applyPromo · android 4.7.2

6 of 6 instances in 3.4 s. Riders still request, ride and pay.

android 4.7.2Mobile game

Emberhold grants gems for ads nobody saw

Ads that fail to load are reported as watched, and each report grants premium gems.

block claimReward · android 4.7.2

6 of 6 instances in 2.8 s. Matches and purchases keep working.

ios 4.7.2Wallet

Northfold sends withdrawals nowhere

A production build ships with test addresses, so withdrawals go where nobody holds the key.

block createWithdrawal · ios 4.7.2

6 of 6 instances in 2.9 s. Balances and deposits keep working.

An evening with Upforcer

Support spots it, the on-call engineer blocks it, everyone goes back to dinner

Your team finds the bug. Upforcer blocks the release from the same thread, shows what the rule will match, and confirms when every instance is in sync. Nobody redeploys.

# incidentsPepperline · invented
  1. MS

    Maya Support · 18:55

    Forty "charged twice" tickets in fifteen minutes. All from Android.

  2. TR

    Tomás On-call · 18:57

    Found it. 4.7.2 retries payOrder on timeout without an idempotency key. Blocking it until the fix ships.

    /upforcer block payOrder android 4.7.2

  3. uf

    upforcer APP 18:57

    Draft rule r_42: block payOrder for Android 4.7.2 in prod. Preview: about 310 requests an hour match, all from Android 4.7.2. It is under the blast-radius limit, so no second approver is needed.

    EnforceCancel

  4. uf

    upforcer APP 18:58

    Enforced by Tomás. All 6 instances in sync in 3.1 s. People on Android 4.7.2 now see "Update to 4.7.3 to order."

  5. MS

    Maya Support · 19:04

    Tickets stopped. Refunding the forty now.

  6. PK

    Priya Payments · 21:30

    4.7.3 is live with the idempotency key. Keep the block on. People will reopen 4.7.2 for months.

  7. uf

    upforcer APP 21:30

    Rule r_42 stays on. Anyone who opens 4.7.2 later, even months from now, gets the update screen instead of a double charge.

How it blocks

Your API makes the decision itself, from rules it already holds

Each request names its release in a header. The middleware checks it against signed rules held in memory and answers with a 403 only for the blocked release and endpoint. A new rule reaches every instance in seconds.

2.9s
from Enforce to every instance confirming
3µs
to decide, in your process, no call to us
1
version and 1 endpoint refused, nothing else
0
inbound connections to your infrastructure
Under the hood
Every request names its release

One RFC 9651 structured header, set by the client SDK, with OpenTelemetry attribute names so the same values appear in your traces.

Client-Release: service.name="wallet",
  service.version="4.7.2",
  release.platform=ios,
  release.build="1031",
  deployment.environment.name=prod
Install

Create a project, add two libraries, done

Every request then names its release, and your dashboard lists the live versions before you write a single rule.

  1. 1

    Create a project

    Sign in, name the project and copy its key into your API's environment.

    UPFORCER_PROJECT=wallet
    UPFORCER_KEY=uf_sk_…
  2. 2

    Add the middleware to your API

    It reads the release header and decides in your process. Name the endpoints you may want to block.

    // npm i @upforcer/hono
    app.use(upforcer())
    app.post("/orders/:id/pay",
      upforcer.operation("payOrder"),
      payOrder)

    Also Express, Fastify, Next.js, NestJS, Go and Python.

  3. 3

    Add the SDK to your app

    It names the release on every request and shows the update screen on a refusal.

    // Swift Package Manager
    Upforcer.configure(
      service: "wallet")

    Also web and Kotlin.

That is the whole setup. Your live versions appear in the dashboard, and you block one when you need to.

SDKs

One SDK in your API, one in your app

Both are Apache 2.0 at github.com/upforcer. The server side decides, the app side announces itself and reacts.

In your API

Server SDKs

Read the release header, decide in-process, refuse with a 403 and the decision header.

TypeScript
ExpressFastifyHonoNext.jsNestJS
Go
net/httpchiechogin
Python
FastAPIDjango
Cloudflare
Worker template

Coming soon

RustJVM (Java, Kotlin).NETPHPRuby
In your app

Client SDKs

Send the release header on every request, and turn a refusal into an update screen or a typed error.

WebSwiftKotlinFlutterReact Native
Runs on
VercelFlyRenderRailwayCloudflareAWS Lambda
Panel schedule · what each circuit protectsupforcer · prod
  1. 01Per-version rulesMatch one app version, platform and environment.
  2. 02Per-endpoint blockRefuse only the operations that cause harm.
  3. 03Instant previewSee what a draft would hit before you enforce.
  4. 04Signed rule pullInstances fetch and verify. Nothing connects in.
  5. 05Seconds to every instancePer-instance status shows who runs which rule.
  6. 06RFC 9457 update screenThe 403 carries what the app should show.
  7. 07Live version listEvery version in production, within minutes of install.
  8. 08AttestationApp Attest and Play Integrity on money-moving calls.
  9. 09Dry runLog what a rule would block without blocking it.
  10. 10Audit logWho drafted, approved and retired every rule.
  11. 11Alerts and approvalsA second pair of eyes before enforce, if you want one.
  12. 12Unlimited requestsThe decision runs in your process. Volume is never metered.
Pricing

Unlimited requests on every plan

Plans are priced on what the control plane does for you, never on traffic.

Free$0
  • Core rules and preview
  • 1 project, unlimited requests
  • Per-instance rule status
  • 7-day audit log
Start free
Pro$49/month
  • Unlimited projects and environments
  • Hosted attestation
  • Approvals and alerts
  • 90-day audit log
Start Pro
Business$199/month
  • Everything in Pro
  • SSO and SIEM export
  • 1-year audit log
  • Self-serve, no sales call
Start Business
EnterpriseCustom
  • Dedicated control plane
  • Bring your own signing keys
  • SLA
Talk to us
Before the next release

Install it before the release that needs it

Once a broken release is on your users' phones, every fix waits for app store review and for each user to update. The middleware goes into your API in minutes.

$ npm i @upforcer/hono

Also go get upforcer.dev/go, and SDKs for Python, web, Swift and Kotlin.

Upforcer is release quarantine for mobile and web apps. A broken release stays away from your money until the fix is in the store.