How to make your backend API multi-tenant?
Work out which customer every call belongs to, from their login or API key, and pass that to your code so it only ever touches that customer’s data.
Filtering by customer is easy. Knowing for sure which customer sent each call is the hard part. Here’s why, and the easy way to solve it.
Skip to the one-command wayFiltering by customer is easy. Knowing the customer isn’t.
Every query your code runs has one blank in it: which customer is this call from? Filling it in safely takes all of this, on every request:
// the easy part: one line in every query
SELECT * FROM bookingsWHERE customer =?
The hard part: what goes in that blank, on every call?
Their login
Each customer may sign in with their own Google, Okta or Auth0. Every one of those tokens has to be checked.
checked
Their API key
Tie every key to one customer, and look it up on every call, fast, wherever your API runs.
looked up
An ID the caller sends
A header, the URL or a field in the body. Whoever is calling can type anything there.
never trust it
And around it, on every call
Before any of your code runs
And from something the caller can’t change.
Handed to your code safely
So it can trust the customer without checking the token again.
Each customer runs their own people
Their users, admins and keys, instead of emailing you.
So most teams let the caller say who they are, and trust it
A tenant ID in a header or the URL is quick to build. But the caller controls it, so any customer can ask for any other customer’s data.
A backend that trusts the caller
One edited word. Another customer’s data.
- 1Gino and Nino both run restaurants on your API
- 2Your backend reads the customer from a header the caller sends
- 3Gino changes “gino” to “nino” in that header
- 4Back come all of Nino’s bookings
- 5Nothing in your logs looks wrong
Every customer is one edit away from every other customer.
1The call Gino’s app normally makes
GET /bookings
X-Tenant-Id: gino
200 OK · Gino’s own 2 bookings
[ "Table for 2", "Table for 6" ]
2The same call, one word changed
GET /bookings
X-Tenant-Id: gino nino
edited by Gino
200 OK · Nino’s 42 bookings, sent to Gino
[ "Chiara, table for 4", "Marco, table for 2", … 40 more of Nino’s bookings ]
Why it worked: your backend never checks that “nino” is who is really calling. It just looks up whichever customer the header names.
Make your API multi-tenant with one command
APIblaze fills in that blank on every call, and does everything around it. Point it at your API, then add your customers as tenants:
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. 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 customers and your backend. Every call goes to APIblaze, which checks the login or key and only then passes the call on, with the customer attached.
Callers
- Chiara, with Nino’s loginasks to list bookings
- Gino’s app, claiming Nino’sasks to list bookings
- A caller with no login or keyasks to list bookings
APIblaze
The API proxy
Reads the login or key on every call, works out the customer, and tells your backend in a header nobody can fake.
Your API
Only sees the calls APIblaze lets through.
Never reached your code.
That’s why it’s the right place to decide the customer. It verifies the credential itself, so the answer comes from the login or key, never from anything the caller typed. Here it is for two restaurants:
Chiara
signs in with Nino’s login
client_id: nino-web
Gino’s app
sends a key made on ginopizzas.com
X-API-Key: sk_prod_…
Gino’s appfake
claims to be Nino’s
X-API-Key: sk_prod_…
X-Abz-Tenant-Id: ninodropped
APIblaze
Reads the login or key on every call
Token issued for Nino’s
tenant: ninoKey belongs to Gino’s
tenant: ginoFake header dropped, key belongs to Gino’s
tenant: ginoYour backendrequest log
GET /bookings
X-Abz-Tenant-Id: ninoGET /bookings
X-Abz-Tenant-Id: ginoGET /bookings
X-Abz-Tenant-Id: ginoChiara
signs in with Nino’s login
client_id: nino-web
Token issued for Nino’s
tenant: ninoGET /bookings
X-Abz-Tenant-Id: ninoGino’s app
sends a key made on ginopizzas.com
X-API-Key: sk_prod_…
Key belongs to Gino’s
tenant: ginoGET /bookings
X-Abz-Tenant-Id: ginoGino’s appfake
claims to be Nino’s
X-API-Key: sk_prod_…
X-Abz-Tenant-Id: ninodropped
Fake header dropped, key belongs to Gino’s
tenant: ginoGET /bookings
X-Abz-Tenant-Id: ginoYour API, multi-tenant, step by step
- 1
Add each customer as a tenant
Every step is one command, so your signup code can add a new customer the moment they pay.
Create their tenant. Gino’s gets a tenant, and a portal at gino.portal.apiblaze.com.
$ npx apiblaze@latest tenant create --name ginoLet them use your API. They get a sign-in on their portal straight away. Swap in their own login provider any time.
$ npx apiblaze@latest tenant attach myapi --tenant ginoMake their manager the admin. From then on, Gino adds his own staff and makes his own keys.
$ npx apiblaze@latest admins add gino@ginopizzas.com --tenant ginoOr send a first key yourself. Every call made with it reaches you as X-Abz-Tenant-Id: gino.
$ npx apiblaze@latest apikeys mint --tenant gino - 2
Each customer gets their own way in
Everything that decides who they are, and who works there, is theirs alone.
Their own sign-in
Their Google, Microsoft, Okta, Auth0 or any OAuth client. Signing in there means signing in as them.
Their own API keys
Made on their site with the API-key widget, or by you. Revoke one without touching anyone else.
Their own users and groups
Managers, waiters, guests. Your rules check them on every call.
Their own admins
Their manager adds people and makes keys, so you aren’t the help desk.
Their own developer portal
Docs and keys at nino.portal.apiblaze.com, or on their own domain.
Their own login page
Under their own name, like login.ninopizzas.com.
- 3
Your backend reads one header
No token checks or key lookups in your code. Read the header and filter by it.
What arrives at your backendGET /bookings
- X-Abz-Tenant-Idnino
Which customer. Taken from the login or key.
- X-End-User-Idchiara@ninopizzas.com
Which person at that customer.
- X-Abz-IdentityeyJhbGciOi…
Both of the above, signed, if you want proof.
server.jsapp.get('/bookings', (req, res) => { // set by APIblaze from the login or key const tenant = req.get('x-abz-tenant-id'); res.json(db.bookings.where({ tenant })); });A caller can’t fake the customer. APIblaze removes any X-Abz header a caller sends and adds its own.
- 4
Every customer calls the same address
The login or key already names the customer, so nothing in the address says who is calling, and there is nothing in it to change or forge.
https://myapi.abz.run
Chiara, signed in with Nino’s login→ X-Abz-Tenant-Id: nino
Gino’s app, with its key→ X-Abz-Tenant-Id: gino
Common questions
What is a multi-tenant backend?
One backend that serves many customers, each kept to their own users, keys and data. Each customer is a tenant.
What is an API proxy?
A service that sits in front of your API. Every request goes through it first, so it can check the caller’s login or key and work out which customer they belong to before your code runs. APIblaze is an API proxy.
How does APIblaze know which customer is calling?
From the credential. A login token was issued for one customer, and an API key belongs to one customer, so every call names its own tenant. APIblaze sends it to your backend as X-Abz-Tenant-Id.
Do I need the customer in the URL?
No. Every customer calls the same address, like myapi.abz.run. The tenant comes from the login or key, never from the address.
Can a caller pretend to be another customer?
No. APIblaze removes any X-Abz-Tenant-Id a caller sends and sets it from the verified login or key.
Can each customer use their own login provider?
Yes. Each tenant has its own sign-in: their Google, Microsoft, Okta, Auth0 or any OAuth client. Tokens from it are tagged with that tenant.
Can one big customer get their own backend?
Yes. Give them a project of their own, pointed at their server, with its own limits and spend cap.
How am I billed?
Per request, from one wallet for your team, with no subscription.
Prices are on the pricing page.
Make your backend API multi-tenant today
One command, no sign-up. Every call tells your backend which customer it is for.
$ npx apiblaze@latest create --target https://ninopizzas.com/openapi.yaml