# Multi-Tenant APIs

> One set of APIs for all your customers, each with a database of its own. Keep tenants in a table, name the tenant on every request, and give each tenant's users tokens that work for their tenant only.

Source: https://docs.apimaker.dev/v4/docs/features/multi-tenant.html

| | |
|---|---|
| One instance | Serves every tenant with the same APIs, schemas, hooks and permissions. |
| One database per tenant | Of the type of the instance ; the connection string lives in a row of a tenants table. |
| Name the tenant | `crm::acme` in the path, or the header `x-am-tenant-username: acme`. |
| Tokens | A person of a tenant gets a token which works for that tenant only. |

- A multi-tenant instance serves many customers (tenants) with one set of APIs. Every tenant has its own database, of the same type as the instance.
- The APIs, schemas, hooks, settings and permissions of the instance are defined once and serve every tenant. Customers share the servers and the APIs, not the databases.
- The instance keeps its own connection string. That database is the structure database: a request without a tenant works on it.
- Supported for MongoDB, MySQL, MariaDB, SQL Server, PostgreSQL and Oracle instances.

> Diagram : Multi-tenant : one instance, one database per tenant, a pool per tenant
>
> A request names its tenant, crm::acme in the path or the header x-am-tenant-username. On the first request of the tenant, API Maker reads its row in the tenants table, opens a connection pool for it, and the request runs on the database of the tenant. The next requests reuse the pool. A request without a tenant runs on the structure database of the instance.

## Example: a CRM with two customers

- A CRM is sold to two companies, Acme and Globex. Each one gets its own PostgreSQL database: `crm_acme` and `crm_globex`.
- One instance named `crm` serves both. Its own connection string points to `crm`, the structure database, which holds the tables every tenant has: `customers`, `orders`, `users`.
- The tenants live in a table `tenants` of another instance, `catalog`, one row per customer.

## 1. Keep your tenants in a table

- Use a table of any instance, with one row per tenant: a username column and a connection string column.
- The table needs an active schema in API Maker.
- To keep connection strings encrypted, add `conversions.encryption` to that column in its schema, and save the rows through the APIs of the table. API Maker decrypts it with the default secret when it connects.
- For Oracle tenants, add columns for the username and the password of each tenant, and if a tenant connects with a privilege, a column for it: `SYSDBA`, `SYSOPER`, `SYSASM`, `SYSBACKUP`, `SYSDG`, `SYSKM` or `SYSRAC`. A tenant without a privilege connects as a plain user of its database.

```json
[
    { "username": "acme",   "connection_string": "postgresql://crm:secret@db-1:5432/crm_acme",   "active": true },
    { "username": "globex", "connection_string": "postgresql://crm:secret@db-2:5432/crm_globex", "active": true }
]
```

- Tenants can use different database servers: here Acme is on `db-1` and Globex on `db-2`.

## 2. Point the default secret to that table

```typescript
import * as T from 'types';

let Secret: T.ISecretType | any = {
    // ... common and your other keys
    multiTenant: {
        crm: <T.IMultiTenantSecretObj>{
            instanceName: 'catalog',                 // instance of the tenants table
            databaseName: 'public',
            collectionName: 'tenants',
            usernameColumn: 'username',
            connectionStringColumn: 'connection_string',
            find: { active: true },                  // optional, added to the tenant lookup

            // Oracle only
            // oracleDBUsernameColumn: 'db_username',
            // oracleDBPasswordColumn: 'db_password',
            // oracleDBPrivilegeColumn: 'db_privilege',   // optional : SYSDBA, SYSOPER... empty for a plain user
        },
    },
};
module.exports = Secret;
```

- `instanceName`, `databaseName`, `collectionName`, `usernameColumn` and `connectionStringColumn` are required. A request of the instance fails with an error naming the missing keys otherwise.
- A tenant must match its username **and** the `find`. With `find: { active: true }`, setting `active` to `false` suspends a tenant. One table can also keep the tenants of several instances apart, for example with `find: { db_kind: 'pg' }`.
- Each multi-tenant instance chooses its own entry, so two instances can use two tenants tables.
- A request never names the entry: the instance knows it. The tenant is all a request names.

## 3. Mark the instance

- Open the instance and check **Is Multi Tenant Structure Instance**.
- In **Connection String Multi Tenant**, choose the entry of the secret, for example `multiTenant.crm`.

## 4. Name the tenant in every request

- In the path, after the instance name, with two colons.

```text
GET /api/gen/admin/crm::acme/crm/customers      # the customers of Acme, from crm_acme
GET /api/gen/admin/crm::globex/crm/customers    # the customers of Globex, from crm_globex
GET /api/gen/admin/crm/crm/customers            # no tenant: the structure database
```

- Or with the [x-am-tenant-username](https://docs.apimaker.dev/v4/docs/apis-all/header/requestHeader.html#x-am-tenant-username) header.

```text
GET /api/gen/admin/crm/crm/customers
x-am-tenant-username: acme
```

- The rules:
    - The instance looks the tenant up in its own tenants table, the entry chosen as its **Connection String Multi Tenant**. One header therefore serves every multi-tenant instance, even instances with different tenants tables.
    - An empty header names no tenant: the structure database.
    - When the path and the header both name a tenant, the tenant of the path is used.
    - A tenant the table does not have, or which the `find` leaves out: 400, `Unable to find tenant with username 'acme'.`
    - `crm::` or `::acme`: 400, `Please provide valid multi tenant instance name.`
    - A tenant named for an instance which is not multi-tenant changes nothing: that instance has one database for every tenant.

## Custom APIs

- Call a custom API with the tenant header. It reads the tenant in `g.req.params`, and the APIs it calls with `g.sys.db` and `g.sys.system` work on the database of that tenant: the header is passed on to them. Each instance they call finds the tenant in its own tenants table.

```text
GET /api/custom-api/admin/customers-report
x-am-tenant-username: acme
```

**TypeScript**

**The tenant of the request, and its data**

```typescript
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const tenant = g.req.params.tenantUsername; // 'acme', undefined without the header

    // The database of acme : no need to name the tenant.
    const customers = await g.sys.db.getAll({ instance: 'crm', database: 'crm', collection: 'customers' });
    const orders = await g.sys.db.gen.queryGen({
        instance: 'crm', database: 'crm', collection: 'orders',
        find: { status: 'paid' },
    });

    return { tenant, customers, orders };
}
module.exports = main;
```

**Python**

**The tenant of the request, and its data**

```python
from am import G


async def main(g: G):
    tenant = g.req.params.tenantUsername  # 'acme', None without the header

    # The database of acme : no need to name the tenant.
    customers = await g.sys.db.get_all('crm', 'crm', 'customers')
    orders = await g.sys.db.gen.query_gen('crm', 'crm', 'orders', find={'status': 'paid'})

    return {'tenant': tenant, 'customers': customers, 'orders': orders}
```

**PHP**

**The tenant of the request, and its data**

```php
<?php
function main(AmG $g)
{
    $tenant = $g->req->params->tenantUsername; // 'acme', null without the header

    // The database of acme : no need to name the tenant.
    $customers = $g->sys->db->getAll('crm', 'crm', 'customers');
    $orders = $g->sys->db->gen->queryGen('crm', 'crm', 'orders', find: ['status' => 'paid']);

    return ['tenant' => $tenant, 'customers' => $customers, 'orders' => $orders];
}
```

**C#**

**The tenant of the request, and its data**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var tenant = g.Req.Params.GetString("tenantUsername"); // "acme", null without the header

        // The database of acme : no need to name the tenant.
        var customers = await g.Sys.Db.GetAll(new GetAll("crm", "crm", "customers"));
        var orders = await g.Sys.Db.Gen.QueryGen(new Query("crm", "crm", "orders") { Find = new JsonObject { ["status"] = "paid" } });

        return new { tenant, customers, orders };
    }
}
```

**Go**

**The tenant of the request, and its data**

```go
package code

import "apimaker/am"

func Main(g *am.G) (any, error) {
	tenant := g.Req.Params().String("tenantUsername") // "acme", "" without the header

	// The database of acme : no need to name the tenant.
	customers, err := g.Sys.Db.GetAll(am.GetAll{Instance: "crm", Database: "crm", Collection: "customers"})
	if err != nil {
		return nil, err
	}
	orders, err := g.Sys.Db.Gen.QueryGen(am.Query{Instance: "crm", Database: "crm", Collection: "orders", Find: am.M{"status": "paid"}})
	if err != nil {
		return nil, err
	}

	return am.M{"tenant": tenant, "customers": customers, "orders": orders}, nil
}
```

**Rust**

**The tenant of the request, and its data**

```rust
use am::{Errors, G, GetAll, Query, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let tenant = g.req.params()["tenantUsername"].clone(); // "acme", null without the header

    // The database of acme : no need to name the tenant.
    let customers = g.sys.db.get_all(GetAll::new("crm", "crm", "customers"))?;
    let orders = g.sys.db.query_gen(Query::new("crm", "crm", "orders").find(json!({ "status": "paid" })))?;

    Ok(json!({ "tenant": tenant, "customers": customers, "orders": orders }))
}
```

**Java**

**The tenant of the request, and its data**

```java
import am.G;
import am.GetAll;
import am.JsonObject;
import am.Query;
import java.util.List;
import java.util.Map;

class Api {
    static Object main(G g) {
        String tenant = g.req.getParams().getString("tenantUsername"); // "acme", null without the header

        // The database of acme : no need to name the tenant.
        List<JsonObject> customers = g.sys.db.getAll(new GetAll("crm", "crm", "customers"));
        List<JsonObject> orders = g.sys.db.gen.queryGen(new Query("crm", "crm", "orders").find(Map.of("status", "paid")));

        return JsonObject.of("tenant", tenant, "customers", customers, "orders", orders);
    }
}
```

**Kotlin**

**The tenant of the request, and its data**

```kotlin
import am.G
import am.GetAll
import am.Query

fun main(g: G): Any? {
    val tenant = g.req.params.getString("tenantUsername") // "acme", null without the header

    // The database of acme : no need to name the tenant.
    val customers = g.sys.db.getAll(GetAll("crm", "crm", "customers"))
    val orders = g.sys.db.gen.queryGen(Query("crm", "crm", "orders").find(mapOf("status" to "paid")))

    return mapOf("tenant" to tenant, "customers" to customers, "orders" to orders)
}
```

- Another tenant, or the structure database, on purpose:

**TypeScript**

**Name the tenant of one call**

```typescript
// In the instance name : the tenant of the name wins over the tenant of the request.
const customersOfGlobex = await g.sys.db.getAll({ instance: 'crm::globex', database: 'crm', collection: 'customers' });

// With the header of the call.
const ordersOfGlobex = await g.sys.db.count({
    instance: 'crm', database: 'crm', collection: 'orders',
    headers: { 'x-am-tenant-username': 'globex' },
});

// An empty header : the structure database.
const templates = await g.sys.db.getAll({
    instance: 'crm', database: 'crm', collection: 'templates',
    headers: { 'x-am-tenant-username': '' },
});
```

**Python**

**Name the tenant of one call**

```python
# In the instance name : the tenant of the name wins over the tenant of the request.
customers_of_globex = await g.sys.db.get_all('crm::globex', 'crm', 'customers')

# With the header of the call.
orders_of_globex = await g.sys.db.count(
    'crm', 'crm', 'orders',
    headers={'x-am-tenant-username': 'globex'},
)

# An empty header : the structure database.
templates = await g.sys.db.get_all(
    'crm', 'crm', 'templates',
    headers={'x-am-tenant-username': ''},
)
```

**PHP**

**Name the tenant of one call**

```php
// In the instance name : the tenant of the name wins over the tenant of the request.
$customersOfGlobex = $g->sys->db->getAll('crm::globex', 'crm', 'customers');

// With the header of the call.
$ordersOfGlobex = $g->sys->db->count(
    'crm', 'crm', 'orders',
    headers: ['x-am-tenant-username' => 'globex'],
);

// An empty header : the structure database.
$templates = $g->sys->db->getAll(
    'crm', 'crm', 'templates',
    headers: ['x-am-tenant-username' => ''],
);
```

**C#**

**Name the tenant of one call**

```csharp
// In the instance name : the tenant of the name wins over the tenant of the request.
var customersOfGlobex = await g.Sys.Db.GetAll(new GetAll("crm::globex", "crm", "customers"));

// With the header of the call.
var ordersOfGlobex = await g.Sys.Db.Count(new Count("crm", "crm", "orders")
{
    Headers = new JsonObject { ["x-am-tenant-username"] = "globex" },
});

// An empty header : the structure database.
var templates = await g.Sys.Db.GetAll(new GetAll("crm", "crm", "templates")
{
    Headers = new JsonObject { ["x-am-tenant-username"] = "" },
});
```

**Go**

**Name the tenant of one call**

```go
// In the instance name : the tenant of the name wins over the tenant of the request.
customersOfGlobex, err := g.Sys.Db.GetAll(am.GetAll{Instance: "crm::globex", Database: "crm", Collection: "customers"})
if err != nil {
	return nil, err
}

// With the header of the call.
ordersOfGlobex, err := g.Sys.Db.Count(am.Count{
	Instance: "crm", Database: "crm", Collection: "orders",
	Headers: am.M{"x-am-tenant-username": "globex"},
})
if err != nil {
	return nil, err
}

// An empty header : the structure database.
templates, err := g.Sys.Db.GetAll(am.GetAll{
	Instance: "crm", Database: "crm", Collection: "templates",
	Headers: am.M{"x-am-tenant-username": ""},
})
if err != nil {
	return nil, err
}
```

**Rust**

**Name the tenant of one call**

```rust
// In the instance name : the tenant of the name wins over the tenant of the request.
let customers_of_globex = g.sys.db.get_all(GetAll::new("crm::globex", "crm", "customers"))?;

// With the header of the call.
let orders_of_globex = g.sys.db.count(
    Count::new("crm", "crm", "orders")
        .headers(json!({ "x-am-tenant-username": "globex" })),
)?;

// An empty header : the structure database.
let templates = g.sys.db.get_all(
    GetAll::new("crm", "crm", "templates")
        .headers(json!({ "x-am-tenant-username": "" })),
)?;
```

**Java**

**Name the tenant of one call**

```java
// In the instance name : the tenant of the name wins over the tenant of the request.
var customersOfGlobex = g.sys.db.getAll(new GetAll("crm::globex", "crm", "customers"));

// With the header of the call.
var ordersOfGlobex = g.sys.db.count(new Count("crm", "crm", "orders")
    .headers(Map.of("x-am-tenant-username", "globex")));

// An empty header : the structure database.
var templates = g.sys.db.getAll(new GetAll("crm", "crm", "templates")
    .headers(Map.of("x-am-tenant-username", "")));
```

**Kotlin**

**Name the tenant of one call**

```kotlin
// In the instance name : the tenant of the name wins over the tenant of the request.
val customersOfGlobex = g.sys.db.getAll(GetAll("crm::globex", "crm", "customers"))

// With the header of the call.
val ordersOfGlobex = g.sys.db.count(
    Count("crm", "crm", "orders")
        .headers(mapOf("x-am-tenant-username" to "globex")),
)

// An empty header : the structure database.
val templates = g.sys.db.getAll(
    GetAll("crm", "crm", "templates")
        .headers(mapOf("x-am-tenant-username" to "")),
)
```

- Queries and table meta run in the database of the tenant too:

**TypeScript**

**SQL of the tenant of the request**

```typescript
const rows = await g.sys.system.executeQuery({
    instance: 'crm', database: 'crm',
    query: 'select status, count(*) as orders from orders group by status',
});
const columns = await g.sys.system.getTableMeta({ instance: 'crm', database: 'crm', table: 'orders' });

// Another tenant : name it in the instance.
const rowsOfGlobex = await g.sys.system.executeQuery({ instance: 'crm::globex', database: 'crm', query: 'select count(*) from orders' });
```

**Python**

**SQL of the tenant of the request**

```python
rows = await g.sys.system.execute_query(
    'crm',
    'select status, count(*) as orders from orders group by status',
    database='crm',
)
columns = await g.sys.system.get_table_meta('crm', 'crm', table='orders')

# Another tenant : name it in the instance.
rows_of_globex = await g.sys.system.execute_query('crm::globex', 'select count(*) from orders', database='crm')
```

**PHP**

**SQL of the tenant of the request**

```php
$rows = $g->sys->system->executeQuery(
    'crm',
    'select status, count(*) as orders from orders group by status',
    database: 'crm',
);
$columns = $g->sys->system->getTableMeta('crm', 'crm', table: 'orders');

// Another tenant : name it in the instance.
$rowsOfGlobex = $g->sys->system->executeQuery('crm::globex', 'select count(*) from orders', database: 'crm');
```

**C#**

**SQL of the tenant of the request**

```csharp
var rows = await g.Sys.System.ExecuteQuery(new ExecuteQuery("crm", "select status, count(*) as orders from orders group by status")
{
    Database = "crm",
});
var columns = await g.Sys.System.GetTableMeta(new GetTableMeta { Instance = "crm", Database = "crm", Table = "orders" });

// Another tenant : name it in the instance.
var rowsOfGlobex = await g.Sys.System.ExecuteQuery(new ExecuteQuery("crm::globex", "select count(*) from orders") { Database = "crm" });
```

**Go**

**SQL of the tenant of the request**

```go
rows, err := g.Sys.System.ExecuteQuery(am.ExecuteQuery{
	Instance: "crm", Database: "crm",
	Query: "select status, count(*) as orders from orders group by status",
})
if err != nil {
	return nil, err
}
columns, err := g.Sys.System.GetTableMeta(am.GetTableMeta{Instance: "crm", Database: "crm", Table: "orders"})
if err != nil {
	return nil, err
}

// Another tenant : name it in the instance.
rowsOfGlobex, err := g.Sys.System.ExecuteQuery(am.ExecuteQuery{Instance: "crm::globex", Database: "crm", Query: "select count(*) from orders"})
if err != nil {
	return nil, err
}
```

**Rust**

**SQL of the tenant of the request**

```rust
let rows = g.sys.system.execute_query(
    ExecuteQuery::new("crm", "select status, count(*) as orders from orders group by status")
        .database("crm"),
)?;
let columns = g.sys.system.get_table_meta(GetTableMeta::default().instance("crm").database("crm").table("orders"))?;

// Another tenant : name it in the instance.
let rows_of_globex = g.sys.system.execute_query(ExecuteQuery::new("crm::globex", "select count(*) from orders").database("crm"))?;
```

**Java**

**SQL of the tenant of the request**

```java
var rows = g.sys.system.executeQuery(new ExecuteQuery("crm", "select status, count(*) as orders from orders group by status")
    .database("crm"));
var columns = g.sys.system.getTableMeta(new GetTableMeta().instance("crm").database("crm").table("orders"));

// Another tenant : name it in the instance.
var rowsOfGlobex = g.sys.system.executeQuery(new ExecuteQuery("crm::globex", "select count(*) from orders").database("crm"));
```

**Kotlin**

**SQL of the tenant of the request**

```kotlin
val rows = g.sys.system.executeQuery(
    ExecuteQuery("crm", "select status, count(*) as orders from orders group by status")
        .database("crm"),
)
val columns = g.sys.system.getTableMeta(GetTableMeta().instance("crm").database("crm").table("orders"))

// Another tenant : name it in the instance.
val rowsOfGlobex = g.sys.system.executeQuery(ExecuteQuery("crm::globex", "select count(*) from orders").database("crm"))
```

- A custom API with `enableCaching` keeps its answers per tenant. Reset them for one tenant:

**TypeScript**

**Reset the cache of a custom API for one tenant**

```typescript
await g.sys.cache.resetCacheCustomApis({ name: 'customers-report', tenantUsername: 'acme' }); // without tenantUsername : every tenant
```

**Python**

**Reset the cache of a custom API for one tenant**

```python
await g.sys.cache.reset_cache_custom_apis_of_tenant('customers-report', 'acme')  # without tenantUsername : every tenant
```

**PHP**

**Reset the cache of a custom API for one tenant**

```php
$g->sys->cache->resetCacheCustomApisOfTenant('customers-report', 'acme'); // without tenantUsername : every tenant
```

**C#**

**Reset the cache of a custom API for one tenant**

```csharp
await g.Sys.Cache.ResetCacheCustomApisOfTenant("customers-report", "acme"); // without tenantUsername : every tenant
```

**Go**

**Reset the cache of a custom API for one tenant**

```go
if _, err := g.Sys.Cache.ResetCacheCustomApisOfTenant("customers-report", "acme"); err != nil { // without tenantUsername : every tenant
	return nil, err
}
```

**Rust**

**Reset the cache of a custom API for one tenant**

```rust
g.sys.cache.reset_cache_custom_apis_of_tenant("customers-report", "acme")?; // without tenantUsername : every tenant
```

**Java**

**Reset the cache of a custom API for one tenant**

```java
g.sys.cache.resetCacheCustomApisOfTenant("customers-report", "acme"); // without tenantUsername : every tenant
```

**Kotlin**

**Reset the cache of a custom API for one tenant**

```kotlin
g.sys.cache.resetCacheCustomApisOfTenant("customers-report", "acme") // without tenantUsername : every tenant
```

- A custom API whose `authProviders` name a token generator on a users table of the instance needs a token of the tenant of the request, see [Users of a tenant](https://docs.apimaker.dev/v4/docs/features/multi-tenant.html#users-of-a-tenant).

## Hooks

- The pre and post hooks of a request of a tenant read it in `g.req.params.tenantUsername`.
- The APIs they call work on the database of that tenant, whether the request named the tenant in the path or with the header.

**TypeScript**

**Post hook : an audit row in the database of the tenant**

```typescript
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    await g.sys.db.saveSingleOrMultiple({
        instance: 'crm', database: 'crm', collection: 'audit_logs',
        saveData: { action: 'customer saved', tenant: g.req.params.tenantUsername },
    });
}
module.exports = main;
```

**Python**

**Post hook : an audit row in the database of the tenant**

```python
from am import G


async def main(g: G):
    await g.sys.db.save_single_or_multiple(
        'crm', 'crm', 'audit_logs',
        {'action': 'customer saved', 'tenant': g.req.params.tenantUsername},
    )
```

**PHP**

**Post hook : an audit row in the database of the tenant**

```php
<?php
function main(AmG $g)
{
    $g->sys->db->saveSingleOrMultiple('crm', 'crm', 'audit_logs', ['action' => 'customer saved', 'tenant' => $g->req->params->tenantUsername]);
    return null;
}
```

**C#**

**Post hook : an audit row in the database of the tenant**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        await g.Sys.Db.SaveSingleOrMultiple(new Save("crm", "crm", "audit_logs", new { action = "customer saved", tenant = g.Req.Params.GetString("tenantUsername") }));
        return null;
    }
}
```

**Go**

**Post hook : an audit row in the database of the tenant**

```go
package code

import "apimaker/am"

func Main(g *am.G) (any, error) {
	_, err := g.Sys.Db.SaveSingleOrMultiple(am.Save{
		Instance: "crm", Database: "crm", Collection: "audit_logs",
		SaveData: am.M{"action": "customer saved", "tenant": g.Req.Params().Get("tenantUsername")},
	})
	return nil, err
}
```

**Rust**

**Post hook : an audit row in the database of the tenant**

```rust
use am::{Errors, G, Save, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let tenant = g.req.params()["tenantUsername"].clone();
    g.sys.db.save_single_or_multiple(Save::new("crm", "crm", "audit_logs", json!({ "action": "customer saved", "tenant": tenant })))?;
    Ok(Value::Null)
}
```

**Java**

**Post hook : an audit row in the database of the tenant**

```java
import am.G;
import am.JsonObject;
import am.Save;

class Api {
    static Object main(G g) {
        g.sys.db.saveSingleOrMultiple(new Save("crm", "crm", "audit_logs",
            JsonObject.of("action", "customer saved", "tenant", g.req.getParams().get("tenantUsername"))));
        return null;
    }
}
```

**Kotlin**

**Post hook : an audit row in the database of the tenant**

```kotlin
import am.G
import am.Save

fun main(g: G): Any? {
    g.sys.db.saveSingleOrMultiple(Save("crm", "crm", "audit_logs", mapOf("action" to "customer saved", "tenant" to g.req.params["tenantUsername"])))
    return null
}
```

## What API Maker does

- Reads the row of the tenant from the tenants table, decrypts its connection string if needed, and opens a connection pool for that tenant (for PostgreSQL, one per tenant and database). The next requests of the tenant reuse the pool.
- A name the tenants table does not know opens no pool.
- A new tenant needs no restart and no deployment: its first request reads its row. Onboarding a customer is creating its database and adding one row to the tenants table.
- Logs record the tenant of each call.
- The databases panel and the API testing page let you pick a tenant to work with its data.

## Caching

- The cached answers of a multi-tenant instance are kept per tenant: a tenant never gets the cached answer of another tenant, nor of the structure database.
- A request naming its tenant in the path and one naming it with the header share their cached answers.
- A write for one tenant drops the cached answers of that tenant. A write to the structure database drops those of every tenant.
- The tables of an instance which is not multi-tenant are read by every tenant: a write made for any tenant drops the cached answers of all of them.
- Custom APIs with caching keep their answers per tenant as well. A write to the database of a tenant drops the cached answers of the custom APIs reading that table, for that tenant.
- To reset the cache of a table by hand:

**TypeScript**

**Reset the cache of a table**

```typescript
await g.sys.cache.resetCacheDB({ instance: 'crm::acme', database: 'crm', collection: 'customers' }); // acme only
await g.sys.cache.resetCacheDB({ instance: 'crm', database: 'crm', collection: 'customers' });       // the structure database and every tenant
```

**Python**

**Reset the cache of a table**

```python
await g.sys.cache.reset_cache_db('crm::acme', 'crm', 'customers')  # acme only
await g.sys.cache.reset_cache_db('crm', 'crm', 'customers')  # the structure database and every tenant
```

**PHP**

**Reset the cache of a table**

```php
$g->sys->cache->resetCacheDB('crm::acme', 'crm', 'customers'); // acme only
$g->sys->cache->resetCacheDB('crm', 'crm', 'customers'); // the structure database and every tenant
```

**C#**

**Reset the cache of a table**

```csharp
await g.Sys.Cache.ResetCacheDB(new ResetCacheDB("crm::acme", "crm", "customers")); // acme only
await g.Sys.Cache.ResetCacheDB(new ResetCacheDB("crm", "crm", "customers")); // the structure database and every tenant
```

**Go**

**Reset the cache of a table**

```go
if _, err := g.Sys.Cache.ResetCacheDB(am.ResetCacheDB{Instance: "crm::acme", Database: "crm", Collection: "customers"}); err != nil { // acme only
	return nil, err
}
if _, err := g.Sys.Cache.ResetCacheDB(am.ResetCacheDB{Instance: "crm", Database: "crm", Collection: "customers"}); err != nil { // the structure database and every tenant
	return nil, err
}
```

**Rust**

**Reset the cache of a table**

```rust
g.sys.cache.reset_cache_db(ResetCacheDB::new("crm::acme", "crm", "customers"))?; // acme only
g.sys.cache.reset_cache_db(ResetCacheDB::new("crm", "crm", "customers"))?; // the structure database and every tenant
```

**Java**

**Reset the cache of a table**

```java
g.sys.cache.resetCacheDB(new ResetCacheDB("crm::acme", "crm", "customers")); // acme only
g.sys.cache.resetCacheDB(new ResetCacheDB("crm", "crm", "customers")); // the structure database and every tenant
```

**Kotlin**

**Reset the cache of a table**

```kotlin
g.sys.cache.resetCacheDB(ResetCacheDB("crm::acme", "crm", "customers")) // acme only
g.sys.cache.resetCacheDB(ResetCacheDB("crm", "crm", "customers")) // the structure database and every tenant
```

## Users of a tenant

- Users can live in a users table inside each tenant database, like `crm.users` above.
- Add a DB token generator on that table of the multi-tenant instance, on the **Token Generators** page, for example `crm_users_token`.
- A user gets a token with the [token API](https://docs.apimaker.dev/v4/docs/apis-all/system-apis/system-generated-token-api.html) and the tenant header:

```text
POST /api/system-api/admin/token
x-am-tenant-username: acme

{ "name": "crm_users_token", "u": "alice@acme.com", "p": "PASSWORD" }
```

- API Maker reads Alice from the users table of `crm_acme`, and the token it returns is made for `acme`:
    - Requests of `acme` accept it, as long as Alice is in the users table of `crm_acme`.
    - A request for `globex` is refused with 401: `Tenant from x-am-user-authorization and x-am-tenant-username should be same.` Even when `globex` has a user with the same username and password.
    - A request without a tenant, on the structure database, is refused with 401.
    - A refreshed token keeps the tenant, whatever tenant the refresh call names.
- A token made without a tenant, or from a users table in another instance, is not tied to a tenant: it is checked against the users table of the tenant each request names. To keep such a caller to its own tenant, compare `g.req.params.tenantUsername` with the tenant of the caller in a pre hook.

## After a tenant moves

- When the connection string of a tenant changes, call the **Multi tenant instance updated** system API. Every server drops the pools of that tenant, for PostgreSQL the pools of all its databases. The cached answers of the instance and of the tenants table are cleared, so the next request of the tenant reads its row again and connects to the new database.
- Until then, the servers keep using the pools they have.

**TypeScript**

```typescript
await g.sys.system.multiTenantInstanceUpdated({ instanceName: 'crm', username: 'acme' });
```

**Python**

```python
await g.sys.system.multi_tenant_instance_updated('crm', 'acme')
```

**PHP**

```php
$g->sys->system->multiTenantInstanceUpdated('crm', 'acme');
```

**C#**

```csharp
await g.Sys.System.MultiTenantInstanceUpdated(new MultiTenantInstanceUpdated("crm", "acme"));
```

**Go**

```go
if _, err := g.Sys.System.MultiTenantInstanceUpdated(am.MultiTenantInstanceUpdated{InstanceName: "crm", Username: "acme"}); err != nil {
	return nil, err
}
```

**Rust**

```rust
g.sys.system.multi_tenant_instance_updated(MultiTenantInstanceUpdated::new("crm", "acme"))?;
```

**Java**

```java
g.sys.system.multiTenantInstanceUpdated(new MultiTenantInstanceUpdated("crm", "acme"));
```

**Kotlin**

```kotlin
g.sys.system.multiTenantInstanceUpdated(MultiTenantInstanceUpdated("crm", "acme"))
```

- It also accepts an array of `{ instanceName, username }`, and answers each item: `{ success: true, data: true }`, or `success: false` with the error: an unknown instance (404) or an instance which is not multi-tenant (400).

```text
POST /api/system-api/admin/multi-tenant-instance-updated

[{ "instanceName": "crm", "username": "acme" }, { "instanceName": "billing", "username": "acme" }]
```

## Good to know

- A schema change must run on every tenant database: plan migrations for all of them.
- Each API Maker process keeps one pool per active tenant. Size the connection limits of your database servers for it.
