Fine-grained, relationship-based authorization for your API

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 way
1 Why it’s hard

Checking 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:

  1. POST/bookings

    Anyone signed in, and they become its owner

    Record the owner every time, or later checks have nothing to go on.

    rule in place
  2. GET/bookings/:id

    The booker, a guest on it, or the restaurant’s manager

    Access follows who is linked to the record, not just their role.

    rule in place
  3. PATCH/bookings/:id

    The booker or the manager, checked before the change

    Once the change has happened, saying no is too late.

    rule in place
  4. GET/invoices/:id

    The person who paid, or the restaurant’s accountant

    Another record type, another set of people to track.

    rule in place
  5. DELETE/reviews/:id

    No check yet

    Nobody wrote this one yet, and nothing tells you. The route you forget is the one that leaks.

    open to anyone signed in
  • 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.

Ben, signed inGET /restaurants/nino/reservations/4242 is Ana’s booking
An API that trusts the URL

200 OK

{
  "id": "42",
  "diner_name": "Ana",
  "diner_email": "ana@ninopizzas.com",
  "party_size": 2
}

Ana’s name and email, sent to Ben.

The same API, behind APIblaze

403 Forbidden

{
  "error": "Forbidden",
  "reason": "Not allowed for this user"
}

Your API never saw the call.

There’s a shorter way.
2 The shortcut

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.yaml

Paste 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.

How can it know whose record it is?
3 Why it works

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.

Same rules as Ben

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:

The callerAPIblazeYour API
  1. Ana: POST /reservations

  2. POST /reservations

  3. 201 {"id": "42"}

  4. user:ana owner reservation:42

    Ana now owns reservation 42

  5. 201 Created

  6. Later
  7. Ben: GET /reservations/42

  8. can ben view reservation:42? no

    Checked before your API is asked

  9. 403 Forbidden

never called
Here is what setting it up looks like.
4 How to do it

Per-record access, step by step

  1. 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
    Turn on 3 rulesNo AI involved.
    or say it in your own words
    guests can view a reservation, and managers can cancel any reservation at their restaurant
    Draft itNothing is saved until you apply it.
    +1 typenew role · guest4 routes get a rulenothing breaksApply

    Or from your terminal

    1. Lock each record to whoever created it

      $ npx apiblaze@latest rule myapi
    2. Any other rule, in plain English

      $ npx apiblaze@latest rule myapi "guests can view a reservation, and managers can cancel any reservation at their restaurant"
    3. 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. 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 42

    She manages the restaurant it was booked at.

    View
    Change
    Cancel

    → 3 of 3 allowed

    The rules behind itOpenFGA
    type user
    type restaurant
    relations
    define manager: [user]
    type reservation
    relations
    define restaurant: [restaurant]
    define owner: [user]
    define guest: [user]
    define can_view: owner or guest or manager from restaurant
    define can_change: owner or manager from restaurant
    define can_cancel: owner or manager from restaurant

    The lit lines are why Maria gets in.

  3. 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.

    Enforcement

    Shadow: 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
Using an AI coding assistant? Paste this into Claude Code or Codex:Make my API check who can access which record using npx apiblaze@latest skills