How to stop one user’s key from opening another user’s records?
Before your API returns or changes a record, check that this caller is allowed that particular record, not just that they are signed in.
A valid key proves who someone is. It doesn’t make every record theirs. Getting that check right on every route is the hard part. Here’s why, and the easy way to solve it.
Skip to the one-command wayChecking sign-in is one rule. Checking every record is many.
To make sure each user only reaches their own records, every route that takes an ID needs its own rule, and all of this around it:
Signed in?
One rule, the same on every route. The easy part.
Allowed this record? A different answer on every route that takes an ID:
POST/bookings
rule in placeAnyone signed in, and they become its owner
Record the owner every time, or later checks have nothing to go on.
GET/bookings/:id
rule in placeThe booker, a guest on it, or the restaurant’s manager
Access follows who is linked to the record, not just their role.
PATCH/bookings/:id
rule in placeThe booker or the manager, checked before the change
Once the change has happened, saying no is too late.
GET/invoices/:id
rule in placeThe person who paid, or the restaurant’s accountant
Another record type, another set of people to track.
DELETE/reviews/:id
open to anyone signed inNo check yet
Nobody wrote this one yet, and nothing tells you. The route you forget is the one that leaks.
Every check has to be fast
It runs on every call that touches a record, so a slow check slows your whole API.
Old records have no owner yet
Bookings made before the rules existed. Switch it on too hard and real users get locked out.
One edited URL, someone else’s booking
Ben booked reservation 41, then changes the URL to 42, which is Ana’s. This hole, broken object-level authorization, is number one on OWASP’s list of API security risks.
GET /restaurants/nino/reservations/4242 is Ana’s booking200 OK
{
"id": "42",
"diner_name": "Ana",
"diner_email": "ana@ninopizzas.com",
"party_size": 2
}Ana’s name and email, sent to Ben.
403 Forbidden
{
"error": "Forbidden",
"reason": "Not allowed for this user"
}Your API never saw the call.
Check every record with one command
APIblaze handles all of that, and counts the routes that still have no rule. Point it at your API:
Put APIblaze in front of your API
$ npx apiblaze@latest create --target https://ninopizzas.com/openapi.yamlPaste it in a terminal. No sign-up, no servers. It reads the spec and asks one question: should only the person who created a record be able to change it? The link is a sample restaurant API; swap in your own.
APIblaze is an API proxy: every request passes through it first
It sits between your users and your API. Every call goes to APIblaze, which checks who is asking for which record, and passes on only the calls that are allowed.
Callers
- Ben, signed inasks to open reservation 41
- Ben, signed inasks to open reservation 42
- Ben’s AI assistantasks to cancel reservation 42
APIblaze
The API proxy
Checks who is asking for which record against who owns it, was given it or manages it, before your API runs.
Your API
Only sees the calls APIblaze lets through.
Never reached your code.
That’s why it’s the right place for per-record checks. It sees the caller and the record on every call, and it records the owner the moment a record is created, so one rule covers every route:
Ana: POST /reservations
POST /reservations
201 {"id": "42"}
user:ana owner reservation:42
Ana now owns reservation 42
201 Created
- Later
Ben: GET /reservations/42
can ben view reservation:42? no
Checked before your API is asked
403 Forbidden
Per-record access, step by step
- 1
Start from your spec, or a sentence
Most APIs need one rule first: each record belongs to whoever created it, or an admin. For anything else, describe it in plain English and review the draft before anything is saved.
In the dashboard, under Authorization
Protect what people create
We read your spec and found 3 things people create through your API. Turn a rule on and each one belongs to whoever created it.
- restaurants
- reservations
- tables
or say it in your own wordsguests can view a reservation, and managers can cancel any reservation at their restaurantDraft itNothing is saved until you apply it.+1 typenew role · guest4 routes get a rulenothing breaksApplyOr from your terminal
Lock each record to whoever created it
$ npx apiblaze@latest rule myapiAny other rule, in plain English
$ npx apiblaze@latest rule myapi "guests can view a reservation, and managers can cancel any reservation at their restaurant"Design a bigger set of rules in a chat
$ npx apiblaze@latest agent authz myapi
Replace myapi with your project’s name. These need a login. Rules save in shadow first, and each plain-English sentence is billed as one AI turn.
- 2
Let access follow how people connect to a record
Roles say what kind of user someone is. Relationships say which records are theirs. Pick a person to see why they can, or can’t, open reservation 42.
Reservation 42 · Nino’s Pizza
Table for 2, Friday at 8 pm. Booked by Ana.
Mariamanager of Nino’s Pizzaholds Reservation 42She manages the restaurant it was booked at.
ViewChangeCancel→ 3 of 3 allowed
The rules behind itOpenFGAtype usertype restaurantrelationsdefine manager: [user]type reservationrelationsdefine restaurant: [restaurant]define owner: [user]define guest: [user]define can_view: owner or guest or manager from restaurantdefine can_change: owner or manager from restaurantdefine can_cancel: owner or manager from restaurantThe lit lines are why Maria gets in.
- 3
Watch first, then enforce
Rules start in shadow, so you can turn them on without locking anyone out. Records made before the rules have no owner yet, and the CLI warns you about them before you enforce.
EnforcementShadow: owners are recorded, nobody is blocked until you enforce.
- Ana opens her reservation 42200
- Ben opens reservation 42200
- Luca books a tableowner recorded201
Changed your mind? This turns enforcement off again:
$ npx apiblaze@latest config myapi authorization.enforce_authorization false
Common questions
- A front door
What is an API proxy?
A service that sits in front of your API. Every request goes through it first, so it can check who is asking for which record before your code runs. APIblaze is an API proxy.
- Routes with a rule
Is every route protected once I turn this on?
A route is checked only once it has a rule, so a new route stays open until it gets one. The dashboard counts the routes that have no rule, so none slip by unseen.
- Your API does
Does APIblaze filter list results?
Lists come back as your API returns them. Your API is told who is asking, in x-abz-user-id, so it can return only that person’s records. Add a list rule and it also receives the ids they may see, in X-Authorized-Objects.
- A 403
What does a refused caller get?
A 403 with {"error":"Forbidden","reason":"Not allowed for this user"}. Your API never sees the call. If the check itself cannot run, the call is refused too, never let through.
- Within 90 seconds
How fast does a change take effect?
Answers are cached briefly to keep calls fast, so a new rule, grant or removal can take up to about 90 seconds to apply everywhere.
- Through your API
Can users share a record with someone?
Give your API a share call. You can add a rule to it that lets only the owner share, and records who the record was shared with.
- Access stays
What happens when a record is deleted?
Deleting a record does not remove who could access it. That is harmless as long as your API never reuses an id.
- No
Does it work on an open API with no login?
A rule has to know who is calling: a signed-in user, an API key issued to that person, or your own server naming them in X-End-User-Id.
- Yes
Do AI agents using my MCP server get the same checks?
Each tool call reaches your API with the caller’s own credentials, so your rules apply. Ben’s assistant cannot read Ana’s booking either.
- Not yet
Can access depend on time or attributes, like office hours?
Rules are relationships: who owns a record, who was given it, who manages what it belongs to. Time and attribute conditions are not supported yet.
- OpenFGA
What is it built on?
OpenFGA, the open-source authorization engine modelled on Google’s Zanzibar. APIblaze runs it for you, so there is no policy server to host.
Prices are on the pricing page.
Keep each user’s records theirs
One command, no sign-up. Every call checked against who owns the record.
$ npx apiblaze@latest create --target https://ninopizzas.com/openapi.yaml