# Pre Hooks

> Run your code (TypeScript, Python, PHP, C#, Go, Rust, Java or Kotlin) before any API of API Maker - at the level of an instance, a database, a table, one API, a custom or system API, or a group - to check, change, narrow or answer a request, in the order API Maker runs them.

Source: https://docs.apimaker.dev/v4/docs/apis-all/hooks/preHook-api.html

A pre hook is a function of yours, in any of the [languages](https://docs.apimaker.dev/v4/docs/languages/index.html) of API Maker, which runs **before** an API. It sees the request, can change it, can answer instead of the API, and can refuse it. Attach it to a whole instance, a database, a table, one API, a custom API or a system API, and change it any time without a restart.

> Diagram : Hooks around an API : pre hooks from the instance down to the API, the API, then post hooks from the API up to the instance
>
> Order of the hooks around a database API : the pre hooks of the instance, then of the database, then of the table, then of the API run before it ; the API runs ; the post hooks run in the opposite order, API, table, database, instance. A pre hook which returns a value answers the caller and stops everything after it.

## Where hooks live

| Level | Runs for | Where |
|---|---|---|
| Instance | every API of every table of the instance | Instances page › the instance › hooks |
| Database | every API of every table of the database | the database › hooks |
| Table (collection) | every API of the table | the table › hooks |
| API | one API of one table, `SCHEMA_GET_ALL` for example | the API › hooks |
| Custom or system API | that API | its hooks tab |
| Group | the hooks above, only for API users of the groups named on the hook | `groupNames` on the hook |

- Pre hooks run from the widest level to the narrowest : instance, database, table, API, each level in the order of its list. Post hooks run the other way round. See [Post hooks](https://docs.apimaker.dev/v4/docs/apis-all/hooks/postHook-api.html).
- In API Testing, the **Pre Hooks** tab of an API of a table lists every pre hook which runs before it, in that order : the hooks of its instance, its database and its table (marked **Instance**, **Database**, **Table**), then its own. The ones of the other levels are changed in their categories of API Testing (**Instance Hooks**, **Database Hooks**, **Collection Hooks**).
- In Git, every hook is a [folder of its own](https://docs.apimaker.dev/v4/docs/Git/git.html#hooks), named by the hook.
- A hook with `groupNames` runs only when the API user of the call belongs to one of those groups. That is how one table gets a row scoping hook for the application users and none for the back office.
- **A hook is written in the language of its API.** The code of an API and all its hooks are [one language on one runtime](https://docs.apimaker.dev/v4/docs/languages/index.html#one-language) : the request is handed to one sandbox, which runs the hooks and the API one after the other. The form of a hook shows that language with a lock. For an API of a table, the hooks of its instance, its database, its table and the API itself are one language.

## The code

**TypeScript**

```typescript
import * as T from 'types';
import * as db from 'db-interfaces';

async function main(g: T.IAMGlobal) {
    if (!g.req.isApiRequestFromUser) return; // calls made by your own code : leave them alone

    // g.req.query, g.req.body, g.req.params, g.req.headers : the request, changeable
    // g.req.auth.authAMUser, g.req.auth.authAMDB… : who calls
    // g.res.shared : values for the API and the post hooks of the same request
}
module.exports = main;
```

**Python**

```python
from am import G


async def main(g: G):
    if not g.req.is_api_request_from_user:
        return  # calls made by your own code : leave them alone

    # g.req.query, g.req.body, g.req.params, g.req.headers : the request, changeable
    # g.req.auth.authAMUser, g.req.auth.authAMDB… : who calls
    # g.res.shared : values for the API and the post hooks of the same request
```

**PHP**

```php
<?php
function main(AmG $g)
{
    if (!$g->req->isApiRequestFromUser) {
        return null; // calls made by your own code : leave them alone
    }

    // $g->req->query, $g->req->body, $g->req->params, $g->req->headers : the request, changeable
    // $g->req->auth->authAMUser, $g->req->auth->authAMDB… : who calls
    // $g->res->shared : values for the API and the post hooks of the same request
    return null;
}
```

**C#**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        if (!g.Req.IsApiRequestFromUser) return null; // calls made by your own code : leave them alone

        // g.Req.Query, g.Req.Body, g.Req.Params, g.Req.Headers : the request, changeable
        // g.Req.Auth["authAMUser"], g.Req.Auth["authAMDB"]… : who calls
        // g.Res.Shared : values for the API and the post hooks of the same request
        return null;
    }
}
```

**Go**

```go
package code

import "apimaker/am"

func Main(g *am.G) (any, error) {
	if !g.Req.IsApiRequestFromUser() {
		return nil, nil // calls made by your own code : leave them alone
	}

	// g.Req.Query(), g.Req.Body(), g.Req.Params(), g.Req.Headers() : the request, changeable
	// g.Req.Auth().Map("authAMUser"), g.Req.Auth().Map("authAMDB")… : who calls
	// g.Res.Shared() : values for the API and the post hooks of the same request
	return nil, nil
}
```

**Rust**

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

pub fn main(g: &mut G) -> Result<Value, Errors> {
    if !g.req.is_api_request_from_user() {
        return Ok(Value::Null); // calls made by your own code : leave them alone
    }

    // g.req.query_mut(), g.req.body_mut(), g.req.params_mut(), g.req.headers_mut() : the request, changeable
    // g.req.auth()["authAMUser"], g.req.auth()["authAMDB"]… : who calls
    // g.res.shared_mut() : values for the API and the post hooks of the same request
    Ok(Value::Null)
}
```

**Java**

```java
import am.G;

class Api {
    static Object main(G g) {
        if (!g.req.isApiRequestFromUser()) return null; // calls made by your own code : leave them alone

        // g.req.getQuery(), g.req.getBody(), g.req.getParams(), g.req.getHeaders() : the request, changeable
        // g.req.getAuth().getObject("authAMUser"), g.req.getAuth().getObject("authAMDB")… : who calls
        // g.res.getShared() : values for the API and the post hooks of the same request
        return null;
    }
}
```

**Kotlin**

```kotlin
import am.G

fun main(g: G): Any? {
    if (!g.req.isApiRequestFromUser) return null // calls made by your own code : leave them alone

    // g.req.query, g.req.body, g.req.params, g.req.headers : the request, changeable
    // g.req.auth.getObject("authAMUser"), g.req.auth.getObject("authAMDB")… : who calls
    // g.res.shared : values for the API and the post hooks of the same request
    return null
}
```

What the return value does :

| The hook | Effect |
|---|---|
| returns nothing | The next hook, then the API, run. |
| returns a value | That value is the answer of the request : the remaining hooks and the API **do not run**. The response is `200` with the value in `data`. |
| sets `g.res.output` and returns nothing | The API runs ; its output replaces `g.res.output` unless a post hook changes it again. |
| throws | The request stops with the error : an exception gives `500` and its message ; the errors of API Maker give their own status (`throw [{ code: 400, message: '…' }]` in TypeScript, `raise Errors('…', code=400)` in Python, `AmErrors` in PHP, C#, Java and Kotlin, `am.Fail(400, '…')` in Go, `am::fail(400, '…')` in Rust). |

- A hook in any language other than TypeScript runs in the sandbox of its language. A hook in TypeScript runs [inside API Maker](https://docs.apimaker.dev/v4/docs/languages/index.html#in-the-server) when it needs nothing of a sandbox, else in its sandbox of Node.js or Bun ; the answer is the same. (`runOnNativeProcess` runs a hook on the machine of the server.) Every hook runs within the sandbox timeout of the call.
- When the answer of an API comes from the [cache](https://docs.apimaker.dev/v4/docs/features/automatic-caching.html), neither pre nor post hooks run.
- Calls from your own code (`g.sys.db.getAll` in a custom API) skip the hooks by default. Pass `skipHookRunning: false` to run them.

## Narrow a request to the rows of the caller

A person must reach only their own rows. This is the hook the [APIs Security Report](https://docs.apimaker.dev/v4/docs/apis-security/api-security-report.html) installs with **Add a row scoping pre-hook**, here on `orders`, whose owner is in `customer_id` :

- every read, update and delete gets the owner added to its filter, with `$and`, so a caller can not widen it ;
- every write gets the owner stamped into its rows, so a caller can not hand a row to someone else ;
- aggregate and distinct can not be scoped this way, and any API the hook does not know is refused : it fails closed.

> Diagram : The row gate : the same call, without and with the row scoping pre hook
>
> Without a row scoping pre hook, Diya calling get all on orders gets every order of the table, hers and Kabir's. With the hook, API Maker adds her customer id to the filter with $and before the query runs, and she gets only her two orders.

**TypeScript**

**Collection level pre hook on orders**

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

/**
 * Row scoping pre-hook for orders, as the API Security Report writes it.
 * Every request is limited to the rows whose "customer_id" equals the id of the person behind it
 * (g.req.auth.authAMDB.customer_id). Edit freely : the report reads the code again on every scan.
 */
const OWNER_COLUMN = 'customer_id';
const IDENTITY_FIELD = 'customer_id';

// Where each generated API keeps its filter. API Maker ANDs query.find onto the primary key for the *-by-id APIs.
const QUERY_FIND_APIS = new Set<string>([
    'SCHEMA_GET_ALL', 'SCHEMA_GET_ALL_STREAM', 'SCHEMA_GET_BY_ID', 'SCHEMA_PUT_UPDATE_BY_ID',
    'SCHEMA_PUT_REPLACE_BY_ID', 'SCHEMA_DEL_DELETE_BY_ID', 'GEN_GET_ALL', 'GEN_GET_ALL_STREAM', 'GEN_GET_BY_ID',
    'GEN_PUT_UPDATE_BY_ID', 'GEN_PUT_REPLACE_BY_ID', 'GEN_DEL_DELETE_BY_ID',
]);
const BODY_FIND_APIS = new Set<string>([
    'SCHEMA_POST_QUERY', 'SCHEMA_POST_QUERY_STREAM', 'SCHEMA_POST_QUERY_DELETE', 'SCHEMA_UPDATE_MANY',
    'SCHEMA_POST_DISTINCT_QUERY', 'SCHEMA_ARRAY_OPERATIONS', 'SCHEMA_POST_COUNT', 'GEN_POST_QUERY',
    'GEN_POST_QUERY_STREAM', 'GEN_POST_QUERY_DELETE', 'GEN_UPDATE_MANY', 'GEN_POST_DISTINCT_QUERY',
    'GEN_ARRAY_OPERATIONS', 'GEN_POST_COUNT',
]);
const STAMP_OWNER_APIS = new Set<string>([
    'SCHEMA_POST_BULK_INSERT', 'SCHEMA_MASTER_SAVE', 'GEN_POST_BULK_INSERT', 'GEN_MASTER_SAVE',
]);

async function main(g: T.IAMGlobal) {
    // Custom APIs, schedulers and events calling g.sys.db run under their own rules, only real callers are scoped.
    if (!g.req.isApiRequestFromUser) return;

    const person = g.req.auth?.authAMDB;
    const me = person ? person[IDENTITY_FIELD] : undefined;
    if (me === undefined || me === null || me === '') {
        g.res.statusCode = T.EStatusCode.UNAUTHORIZED;
        throw new Error('A person token is required for this API.');
    }

    const scope = { [OWNER_COLUMN]: me };
    // MERGE, never replace : $and keeps what the caller asked for and adds the owner on top, so a caller
    // can never widen the rows by sending its own owner filter.
    const merge = (existing: any) => (existing && typeof existing === 'object' && Object.keys(existing).length
        ? { $and: [existing, scope] }
        : { ...scope });
    const stamp = (row: any) => { if (row && typeof row === 'object') row[OWNER_COLUMN] = me; };

    const apiId = g.req.reqInfo?.apiInfo?.id;
    if (QUERY_FIND_APIS.has(apiId)) {
        g.req.query.find = merge(g.req.query.find);
        stamp(g.req.body); // update-by-id and replace-by-id : the owner of a row can not be handed over
        return;
    }
    if (BODY_FIND_APIS.has(apiId)) {
        g.req.body = g.req.body || {};
        g.req.body.find = merge(g.req.body.find);
        if (g.req.body.updateData) stamp(g.req.body.updateData); // update-many
        return;
    }
    if (STAMP_OWNER_APIS.has(apiId)) {
        const rows = Array.isArray(g.req.body) ? g.req.body : [g.req.body];
        for (const row of rows) stamp(row); // never trust the owner sent by the caller
        return;
    }

    // Fail closed : aggregate and distinct can not be scoped generically, so they are refused for row scoped callers.
    g.res.statusCode = T.EStatusCode.FORBIDDEN;
    throw new Error(`API ${apiId} is not available for row scoped users.`);
}
module.exports = main;
```

**Python**

**Collection level pre hook on orders**

```python
from am import Errors, G, Obj

# Row scoping pre-hook for orders, as the API Security Report writes it.
# Every request is limited to the rows whose "customer_id" equals the id of the person behind it
# (g.req.auth.authAMDB.customer_id). Edit freely : the report reads the code again on every scan.
OWNER_COLUMN = 'customer_id'
IDENTITY_FIELD = 'customer_id'

# Where each generated API keeps its filter. API Maker ANDs query.find onto the primary key for the *-by-id APIs.
QUERY_FIND_APIS = {
    'SCHEMA_GET_ALL', 'SCHEMA_GET_ALL_STREAM', 'SCHEMA_GET_BY_ID', 'SCHEMA_PUT_UPDATE_BY_ID',
    'SCHEMA_PUT_REPLACE_BY_ID', 'SCHEMA_DEL_DELETE_BY_ID', 'GEN_GET_ALL', 'GEN_GET_ALL_STREAM', 'GEN_GET_BY_ID',
    'GEN_PUT_UPDATE_BY_ID', 'GEN_PUT_REPLACE_BY_ID', 'GEN_DEL_DELETE_BY_ID',
}
BODY_FIND_APIS = {
    'SCHEMA_POST_QUERY', 'SCHEMA_POST_QUERY_STREAM', 'SCHEMA_POST_QUERY_DELETE', 'SCHEMA_UPDATE_MANY',
    'SCHEMA_POST_DISTINCT_QUERY', 'SCHEMA_ARRAY_OPERATIONS', 'SCHEMA_POST_COUNT', 'GEN_POST_QUERY',
    'GEN_POST_QUERY_STREAM', 'GEN_POST_QUERY_DELETE', 'GEN_UPDATE_MANY', 'GEN_POST_DISTINCT_QUERY',
    'GEN_ARRAY_OPERATIONS', 'GEN_POST_COUNT',
}
STAMP_OWNER_APIS = {
    'SCHEMA_POST_BULK_INSERT', 'SCHEMA_MASTER_SAVE', 'GEN_POST_BULK_INSERT', 'GEN_MASTER_SAVE',
}


async def main(g: G):
    # Custom APIs, schedulers and events calling g.sys.db run under their own rules, only real callers are scoped.
    if not g.req.is_api_request_from_user:
        return

    person = g.req.auth.authAMDB
    me = person.get(IDENTITY_FIELD) if isinstance(person, dict) else None
    if me is None or me == '':
        raise Errors('A person token is required for this API.', code=401)

    scope = {OWNER_COLUMN: me}
    # MERGE, never replace : $and keeps what the caller asked for and adds the owner on top, so a caller
    # can never widen the rows by sending its own owner filter.
    def merge(existing):
        return {'$and': [existing, scope]} if isinstance(existing, dict) and existing else dict(scope)

    api_id = g.req.req_info.apiInfo.id
    if api_id in QUERY_FIND_APIS:
        g.req.query['find'] = merge(g.req.query.get('find'))
        if isinstance(g.req.body, dict):
            g.req.body[OWNER_COLUMN] = me  # update-by-id and replace-by-id : the owner of a row can not be handed over
        return
    if api_id in BODY_FIND_APIS:
        if not isinstance(g.req.body, dict):
            g.req.body = Obj()
        g.req.body['find'] = merge(g.req.body.get('find'))
        if isinstance(g.req.body.get('updateData'), dict):
            g.req.body['updateData'][OWNER_COLUMN] = me  # update-many
        return
    if api_id in STAMP_OWNER_APIS:
        rows = g.req.body if isinstance(g.req.body, list) else [g.req.body]
        for row in rows:
            if isinstance(row, dict):
                row[OWNER_COLUMN] = me  # never trust the owner sent by the caller
        return

    # Fail closed : aggregate and distinct can not be scoped generically, so they are refused for row scoped callers.
    raise Errors(f'API {api_id} is not available for row scoped users.', code=403)
```

**PHP**

**Collection level pre hook on orders**

```php
<?php
// Row scoping pre-hook for orders, as the API Security Report writes it.
// Every request is limited to the rows whose "customer_id" equals the id of the person behind it
// (g.req.auth.authAMDB.customer_id). Edit freely : the report reads the code again on every scan.
const OWNER_COLUMN = 'customer_id';
const IDENTITY_FIELD = 'customer_id';

// Where each generated API keeps its filter. API Maker ANDs query.find onto the primary key for the *-by-id APIs.
const QUERY_FIND_APIS = [
    'SCHEMA_GET_ALL', 'SCHEMA_GET_ALL_STREAM', 'SCHEMA_GET_BY_ID', 'SCHEMA_PUT_UPDATE_BY_ID',
    'SCHEMA_PUT_REPLACE_BY_ID', 'SCHEMA_DEL_DELETE_BY_ID', 'GEN_GET_ALL', 'GEN_GET_ALL_STREAM', 'GEN_GET_BY_ID',
    'GEN_PUT_UPDATE_BY_ID', 'GEN_PUT_REPLACE_BY_ID', 'GEN_DEL_DELETE_BY_ID',
];
const BODY_FIND_APIS = [
    'SCHEMA_POST_QUERY', 'SCHEMA_POST_QUERY_STREAM', 'SCHEMA_POST_QUERY_DELETE', 'SCHEMA_UPDATE_MANY',
    'SCHEMA_POST_DISTINCT_QUERY', 'SCHEMA_ARRAY_OPERATIONS', 'SCHEMA_POST_COUNT', 'GEN_POST_QUERY',
    'GEN_POST_QUERY_STREAM', 'GEN_POST_QUERY_DELETE', 'GEN_UPDATE_MANY', 'GEN_POST_DISTINCT_QUERY',
    'GEN_ARRAY_OPERATIONS', 'GEN_POST_COUNT',
];
const STAMP_OWNER_APIS = [
    'SCHEMA_POST_BULK_INSERT', 'SCHEMA_MASTER_SAVE', 'GEN_POST_BULK_INSERT', 'GEN_MASTER_SAVE',
];

function main(AmG $g)
{
    // Custom APIs, schedulers and events calling g.sys.db run under their own rules, only real callers are scoped.
    if (!$g->req->isApiRequestFromUser) {
        return null;
    }

    $person = $g->req->auth->authAMDB;
    $me = $person instanceof AmObject ? $person->get(IDENTITY_FIELD) : null;
    if ($me === null || $me === '') {
        throw new AmErrors('A person token is required for this API.', 401);
    }

    $scope = [OWNER_COLUMN => $me];
    // MERGE, never replace : $and keeps what the caller asked for and adds the owner on top, so a caller
    // can never widen the rows by sending its own owner filter.
    $merge = fn ($existing) => ($existing instanceof AmObject || is_array($existing)) && count($existing) > 0
        ? ['$and' => [$existing, $scope]]
        : $scope;

    $apiId = $g->req->reqInfo->apiInfo->id;
    if (in_array($apiId, QUERY_FIND_APIS, true)) {
        $g->req->query->find = $merge($g->req->query->find);
        if ($g->req->body instanceof AmObject) {
            $g->req->body[OWNER_COLUMN] = $me; // update-by-id and replace-by-id : the owner of a row can not be handed over
        }
        return null;
    }
    if (in_array($apiId, BODY_FIND_APIS, true)) {
        if (!($g->req->body instanceof AmObject)) {
            $g->req->body = new AmObject([]);
        }
        $g->req->body->find = $merge($g->req->body->find);
        if ($g->req->body->updateData instanceof AmObject) {
            $g->req->body->updateData[OWNER_COLUMN] = $me; // update-many
        }
        return null;
    }
    if (in_array($apiId, STAMP_OWNER_APIS, true)) {
        $rows = is_array($g->req->body) ? $g->req->body : [$g->req->body];
        foreach ($rows as $row) {
            if ($row instanceof AmObject) {
                $row[OWNER_COLUMN] = $me; // never trust the owner sent by the caller
            }
        }
        return null;
    }

    // Fail closed : aggregate and distinct can not be scoped generically, so they are refused for row scoped callers.
    throw new AmErrors("API $apiId is not available for row scoped users.", 403);
}
```

**C#**

**Collection level pre hook on orders**

```csharp
// Row scoping pre-hook for orders, as the API Security Report writes it.
// Every request is limited to the rows whose "customer_id" equals the id of the person behind it
// (g.req.auth.authAMDB.customer_id). Edit freely : the report reads the code again on every scan.
file static class Api
{
    const string OwnerColumn = "customer_id";
    const string IdentityField = "customer_id";

    // Where each generated API keeps its filter. API Maker ANDs query.find onto the primary key for the *-by-id APIs.
    static readonly HashSet<string> QueryFindApis =
    [
        "SCHEMA_GET_ALL", "SCHEMA_GET_ALL_STREAM", "SCHEMA_GET_BY_ID", "SCHEMA_PUT_UPDATE_BY_ID",
        "SCHEMA_PUT_REPLACE_BY_ID", "SCHEMA_DEL_DELETE_BY_ID", "GEN_GET_ALL", "GEN_GET_ALL_STREAM", "GEN_GET_BY_ID",
        "GEN_PUT_UPDATE_BY_ID", "GEN_PUT_REPLACE_BY_ID", "GEN_DEL_DELETE_BY_ID",
    ];
    static readonly HashSet<string> BodyFindApis =
    [
        "SCHEMA_POST_QUERY", "SCHEMA_POST_QUERY_STREAM", "SCHEMA_POST_QUERY_DELETE", "SCHEMA_UPDATE_MANY",
        "SCHEMA_POST_DISTINCT_QUERY", "SCHEMA_ARRAY_OPERATIONS", "SCHEMA_POST_COUNT", "GEN_POST_QUERY",
        "GEN_POST_QUERY_STREAM", "GEN_POST_QUERY_DELETE", "GEN_UPDATE_MANY", "GEN_POST_DISTINCT_QUERY",
        "GEN_ARRAY_OPERATIONS", "GEN_POST_COUNT",
    ];
    static readonly HashSet<string> StampOwnerApis =
    [
        "SCHEMA_POST_BULK_INSERT", "SCHEMA_MASTER_SAVE", "GEN_POST_BULK_INSERT", "GEN_MASTER_SAVE",
    ];

    public static async Task<object?> Main(G g)
    {
        // Custom APIs, schedulers and events calling g.sys.db run under their own rules, only real callers are scoped.
        if (!g.Req.IsApiRequestFromUser) return null;

        var me = g.Req.Auth.GetObject("authAMDB")[IdentityField];
        if (me is null || me.ToString() == "")
            throw new AmErrors("A person token is required for this API.", 401);

        JsonObject Scope() => new() { [OwnerColumn] = me.DeepClone() };
        // MERGE, never replace : $and keeps what the caller asked for and adds the owner on top, so a caller
        // can never widen the rows by sending its own owner filter.
        JsonNode Merge(JsonNode? existing) => existing is JsonObject { Count: > 0 }
            ? new JsonObject { ["$and"] = new JsonArray(existing.DeepClone(), Scope()) }
            : Scope();

        var apiId = g.Req.ReqInfo.GetObject("apiInfo").GetString("id") ?? "";
        if (QueryFindApis.Contains(apiId))
        {
            g.Req.Query["find"] = Merge(g.Req.Query["find"]);
            var body = g.Req["body"];
            if (body is JsonObject) body[OwnerColumn] = me.DeepClone(); // update-by-id and replace-by-id : the owner of a row can not be handed over
            return null;
        }
        if (BodyFindApis.Contains(apiId))
        {
            g.Req.Body["find"] = Merge(g.Req.Body["find"]);
            var updateData = g.Req.Body["updateData"];
            if (updateData is JsonObject) updateData[OwnerColumn] = me.DeepClone(); // update-many
            return null;
        }
        if (StampOwnerApis.Contains(apiId))
        {
            var body = g.Req["body"];
            if (body is JsonArray rows)
            {
                foreach (var row in rows)
                {
                    if (row is JsonObject) row[OwnerColumn] = me.DeepClone(); // never trust the owner sent by the caller
                }
            }
            else if (body is JsonObject) body[OwnerColumn] = me.DeepClone();
            return null;
        }

        // Fail closed : aggregate and distinct can not be scoped generically, so they are refused for row scoped callers.
        throw new AmErrors($"API {apiId} is not available for row scoped users.", 403);
    }
}
```

**Go**

**Collection level pre hook on orders**

```go
package code

import (
	"fmt"

	"apimaker/am"
)

// Row scoping pre-hook for orders, as the API Security Report writes it.
// Every request is limited to the rows whose "customer_id" equals the id of the person behind it
// (g.req.auth.authAMDB.customer_id). Edit freely : the report reads the code again on every scan.
const ownerColumn = "customer_id"
const identityField = "customer_id"

// Where each generated API keeps its filter. API Maker ANDs query.find onto the primary key for the *-by-id APIs.
var queryFindApis = map[string]bool{
	"SCHEMA_GET_ALL": true, "SCHEMA_GET_ALL_STREAM": true, "SCHEMA_GET_BY_ID": true, "SCHEMA_PUT_UPDATE_BY_ID": true,
	"SCHEMA_PUT_REPLACE_BY_ID": true, "SCHEMA_DEL_DELETE_BY_ID": true, "GEN_GET_ALL": true, "GEN_GET_ALL_STREAM": true,
	"GEN_GET_BY_ID": true, "GEN_PUT_UPDATE_BY_ID": true, "GEN_PUT_REPLACE_BY_ID": true, "GEN_DEL_DELETE_BY_ID": true,
}
var bodyFindApis = map[string]bool{
	"SCHEMA_POST_QUERY": true, "SCHEMA_POST_QUERY_STREAM": true, "SCHEMA_POST_QUERY_DELETE": true,
	"SCHEMA_UPDATE_MANY": true, "SCHEMA_POST_DISTINCT_QUERY": true, "SCHEMA_ARRAY_OPERATIONS": true,
	"SCHEMA_POST_COUNT": true, "GEN_POST_QUERY": true, "GEN_POST_QUERY_STREAM": true, "GEN_POST_QUERY_DELETE": true,
	"GEN_UPDATE_MANY": true, "GEN_POST_DISTINCT_QUERY": true, "GEN_ARRAY_OPERATIONS": true, "GEN_POST_COUNT": true,
}
var stampOwnerApis = map[string]bool{
	"SCHEMA_POST_BULK_INSERT": true, "SCHEMA_MASTER_SAVE": true, "GEN_POST_BULK_INSERT": true, "GEN_MASTER_SAVE": true,
}

func Main(g *am.G) (any, error) {
	// Custom APIs, schedulers and events calling g.sys.db run under their own rules, only real callers are scoped.
	if !g.Req.IsApiRequestFromUser() {
		return nil, nil
	}

	me := g.Req.Auth().Map("authAMDB").Get(identityField)
	if me == nil || me == "" {
		return nil, am.Fail(401, "A person token is required for this API.")
	}

	// MERGE, never replace : $and keeps what the caller asked for and adds the owner on top, so a caller
	// can never widen the rows by sending its own owner filter.
	merge := func(existing am.M) am.M {
		if len(existing) > 0 {
			return am.M{"$and": []any{existing, am.M{ownerColumn: me}}}
		}
		return am.M{ownerColumn: me}
	}

	apiID := g.Req.ReqInfo().Map("apiInfo").String("id")
	switch {
	case queryFindApis[apiID]:
		query := g.Req.Query()
		query["find"] = merge(query.Map("find"))
		if body := g.Req.Map("body"); body != nil {
			body[ownerColumn] = me // update-by-id and replace-by-id : the owner of a row can not be handed over
		}
		return nil, nil
	case bodyFindApis[apiID]:
		body := g.Req.Body()
		body["find"] = merge(body.Map("find"))
		if updateData := body.Map("updateData"); updateData != nil {
			updateData[ownerColumn] = me // update-many
		}
		return nil, nil
	case stampOwnerApis[apiID]:
		rows := g.Req.List("body")
		if rows == nil {
			rows = []any{g.Req.Get("body")}
		}
		for _, row := range rows {
			if fields := am.AsM(row); row != nil {
				fields[ownerColumn] = me // never trust the owner sent by the caller
			}
		}
		return nil, nil
	}

	// Fail closed : aggregate and distinct can not be scoped generically, so they are refused for row scoped callers.
	return nil, am.Fail(403, fmt.Sprintf("API %s is not available for row scoped users.", apiID))
}
```

**Rust**

**Collection level pre hook on orders**

```rust
// Row scoping pre-hook for orders, as the API Security Report writes it.
// Every request is limited to the rows whose "customer_id" equals the id of the person behind it
// (g.req.auth.authAMDB.customer_id). Edit freely : the report reads the code again on every scan.
use am::{Errors, G, Value, json};

const OWNER_COLUMN: &str = "customer_id";
const IDENTITY_FIELD: &str = "customer_id";

// Where each generated API keeps its filter. API Maker ANDs query.find onto the primary key for the *-by-id APIs.
const QUERY_FIND_APIS: [&str; 12] = [
    "SCHEMA_GET_ALL", "SCHEMA_GET_ALL_STREAM", "SCHEMA_GET_BY_ID", "SCHEMA_PUT_UPDATE_BY_ID",
    "SCHEMA_PUT_REPLACE_BY_ID", "SCHEMA_DEL_DELETE_BY_ID", "GEN_GET_ALL", "GEN_GET_ALL_STREAM", "GEN_GET_BY_ID",
    "GEN_PUT_UPDATE_BY_ID", "GEN_PUT_REPLACE_BY_ID", "GEN_DEL_DELETE_BY_ID",
];
const BODY_FIND_APIS: [&str; 14] = [
    "SCHEMA_POST_QUERY", "SCHEMA_POST_QUERY_STREAM", "SCHEMA_POST_QUERY_DELETE", "SCHEMA_UPDATE_MANY",
    "SCHEMA_POST_DISTINCT_QUERY", "SCHEMA_ARRAY_OPERATIONS", "SCHEMA_POST_COUNT", "GEN_POST_QUERY",
    "GEN_POST_QUERY_STREAM", "GEN_POST_QUERY_DELETE", "GEN_UPDATE_MANY", "GEN_POST_DISTINCT_QUERY",
    "GEN_ARRAY_OPERATIONS", "GEN_POST_COUNT",
];
const STAMP_OWNER_APIS: [&str; 4] = [
    "SCHEMA_POST_BULK_INSERT", "SCHEMA_MASTER_SAVE", "GEN_POST_BULK_INSERT", "GEN_MASTER_SAVE",
];

pub fn main(g: &mut G) -> Result<Value, Errors> {
    // Custom APIs, schedulers and events calling g.sys.db run under their own rules, only real callers are scoped.
    if !g.req.is_api_request_from_user() {
        return Ok(Value::Null);
    }

    let me = g.req.auth()["authAMDB"][IDENTITY_FIELD].clone();
    if me.is_null() || me == "" {
        return Err(am::fail(401, "A person token is required for this API."));
    }

    let scope = json!({ OWNER_COLUMN: me.clone() });
    // MERGE, never replace : $and keeps what the caller asked for and adds the owner on top, so a caller
    // can never widen the rows by sending its own owner filter.
    let merge = |existing: &Value| match existing {
        Value::Object(find) if !find.is_empty() => json!({ "$and": [existing, scope] }),
        _ => scope.clone(),
    };

    let api_id = g.req.req_info()["apiInfo"]["id"].as_str().unwrap_or_default().to_owned();
    if QUERY_FIND_APIS.contains(&api_id.as_str()) {
        let find = merge(&g.req.query()["find"]);
        g.req.query_mut()["find"] = find;
        let body = g.req.body_mut();
        if body.is_object() {
            body[OWNER_COLUMN] = me.clone(); // update-by-id and replace-by-id : the owner of a row can not be handed over
        }
        return Ok(Value::Null);
    }
    if BODY_FIND_APIS.contains(&api_id.as_str()) {
        let find = merge(&g.req.body()["find"]);
        g.req.body_mut()["find"] = find;
        if let Some(update_data) = g.req.body_mut().get_mut("updateData").filter(|update_data| update_data.is_object()) {
            update_data[OWNER_COLUMN] = me.clone(); // update-many
        }
        return Ok(Value::Null);
    }
    if STAMP_OWNER_APIS.contains(&api_id.as_str()) {
        let rows: Vec<&mut Value> = match g.req.body_mut() {
            Value::Array(rows) => rows.iter_mut().collect(),
            row => vec![row],
        };
        for row in rows {
            if row.is_object() {
                row[OWNER_COLUMN] = me.clone(); // never trust the owner sent by the caller
            }
        }
        return Ok(Value::Null);
    }

    // Fail closed : aggregate and distinct can not be scoped generically, so they are refused for row scoped callers.
    Err(am::fail(403, format!("API {api_id} is not available for row scoped users.")))
}
```

**Java**

**Collection level pre hook on orders**

```java
// Row scoping pre-hook for orders, as the API Security Report writes it.
// Every request is limited to the rows whose "customer_id" equals the id of the person behind it
// (g.req.auth.authAMDB.customer_id). Edit freely : the report reads the code again on every scan.
import am.AmErrors;
import am.G;
import am.JsonObject;
import java.util.List;
import java.util.Map;
import java.util.Set;

class Api {
    static final String OWNER_COLUMN = "customer_id";
    static final String IDENTITY_FIELD = "customer_id";

    // Where each generated API keeps its filter. API Maker ANDs query.find onto the primary key for the *-by-id APIs.
    static final Set<String> QUERY_FIND_APIS = Set.of(
        "SCHEMA_GET_ALL", "SCHEMA_GET_ALL_STREAM", "SCHEMA_GET_BY_ID", "SCHEMA_PUT_UPDATE_BY_ID",
        "SCHEMA_PUT_REPLACE_BY_ID", "SCHEMA_DEL_DELETE_BY_ID", "GEN_GET_ALL", "GEN_GET_ALL_STREAM", "GEN_GET_BY_ID",
        "GEN_PUT_UPDATE_BY_ID", "GEN_PUT_REPLACE_BY_ID", "GEN_DEL_DELETE_BY_ID");
    static final Set<String> BODY_FIND_APIS = Set.of(
        "SCHEMA_POST_QUERY", "SCHEMA_POST_QUERY_STREAM", "SCHEMA_POST_QUERY_DELETE", "SCHEMA_UPDATE_MANY",
        "SCHEMA_POST_DISTINCT_QUERY", "SCHEMA_ARRAY_OPERATIONS", "SCHEMA_POST_COUNT", "GEN_POST_QUERY",
        "GEN_POST_QUERY_STREAM", "GEN_POST_QUERY_DELETE", "GEN_UPDATE_MANY", "GEN_POST_DISTINCT_QUERY",
        "GEN_ARRAY_OPERATIONS", "GEN_POST_COUNT");
    static final Set<String> STAMP_OWNER_APIS = Set.of(
        "SCHEMA_POST_BULK_INSERT", "SCHEMA_MASTER_SAVE", "GEN_POST_BULK_INSERT", "GEN_MASTER_SAVE");

    // MERGE, never replace : $and keeps what the caller asked for and adds the owner on top, so a caller
    // can never widen the rows by sending its own owner filter.
    static Object merge(Object existing, Map<String, Object> scope) {
        return existing instanceof Map<?, ?> find && !find.isEmpty() ? JsonObject.of("$and", List.of(existing, scope)) : new JsonObject(scope);
    }

    @SuppressWarnings("unchecked")
    static Object main(G g) {
        // Custom APIs, schedulers and events calling g.sys.db run under their own rules, only real callers are scoped.
        if (!g.req.isApiRequestFromUser()) return null;

        Object me = g.req.getAuth().getObject("authAMDB").get(IDENTITY_FIELD);
        if (me == null || "".equals(me)) throw new AmErrors("A person token is required for this API.", 401);
        JsonObject scope = JsonObject.of(OWNER_COLUMN, me);

        String apiId = g.req.getReqInfo().getObject("apiInfo").getString("id", "");
        if (QUERY_FIND_APIS.contains(apiId)) {
            JsonObject query = g.req.objectAt("query");
            query.put("find", merge(query.get("find"), scope));
            Object body = g.req.get("body");
            if (body instanceof Map<?, ?>) ((Map<String, Object>) body).put(OWNER_COLUMN, me); // update-by-id and replace-by-id : the owner of a row can not be handed over
            return null;
        }
        if (BODY_FIND_APIS.contains(apiId)) {
            JsonObject body = g.req.objectAt("body");
            body.put("find", merge(body.get("find"), scope));
            Object updateData = body.get("updateData");
            if (updateData instanceof Map<?, ?>) ((Map<String, Object>) updateData).put(OWNER_COLUMN, me); // update-many
            return null;
        }
        if (STAMP_OWNER_APIS.contains(apiId)) {
            List<?> rows = g.req.get("body") instanceof List<?> list ? list : List.of(g.req.objectAt("body"));
            for (Object row : rows) {
                if (row instanceof Map<?, ?>) ((Map<String, Object>) row).put(OWNER_COLUMN, me); // never trust the owner sent by the caller
            }
            return null;
        }

        // Fail closed : aggregate and distinct can not be scoped generically, so they are refused for row scoped callers.
        throw new AmErrors("API " + apiId + " is not available for row scoped users.", 403);
    }
}
```

**Kotlin**

**Collection level pre hook on orders**

```kotlin
// Row scoping pre-hook for orders, as the API Security Report writes it.
// Every request is limited to the rows whose "customer_id" equals the id of the person behind it
// (g.req.auth.authAMDB.customer_id). Edit freely : the report reads the code again on every scan.
import am.AmErrors
import am.G

const val OWNER_COLUMN = "customer_id"
const val IDENTITY_FIELD = "customer_id"

// Where each generated API keeps its filter. API Maker ANDs query.find onto the primary key for the *-by-id APIs.
val QUERY_FIND_APIS = setOf(
    "SCHEMA_GET_ALL", "SCHEMA_GET_ALL_STREAM", "SCHEMA_GET_BY_ID", "SCHEMA_PUT_UPDATE_BY_ID",
    "SCHEMA_PUT_REPLACE_BY_ID", "SCHEMA_DEL_DELETE_BY_ID", "GEN_GET_ALL", "GEN_GET_ALL_STREAM", "GEN_GET_BY_ID",
    "GEN_PUT_UPDATE_BY_ID", "GEN_PUT_REPLACE_BY_ID", "GEN_DEL_DELETE_BY_ID",
)
val BODY_FIND_APIS = setOf(
    "SCHEMA_POST_QUERY", "SCHEMA_POST_QUERY_STREAM", "SCHEMA_POST_QUERY_DELETE", "SCHEMA_UPDATE_MANY",
    "SCHEMA_POST_DISTINCT_QUERY", "SCHEMA_ARRAY_OPERATIONS", "SCHEMA_POST_COUNT", "GEN_POST_QUERY",
    "GEN_POST_QUERY_STREAM", "GEN_POST_QUERY_DELETE", "GEN_UPDATE_MANY", "GEN_POST_DISTINCT_QUERY",
    "GEN_ARRAY_OPERATIONS", "GEN_POST_COUNT",
)
val STAMP_OWNER_APIS = setOf(
    "SCHEMA_POST_BULK_INSERT", "SCHEMA_MASTER_SAVE", "GEN_POST_BULK_INSERT", "GEN_MASTER_SAVE",
)

@Suppress("UNCHECKED_CAST")
fun main(g: G): Any? {
    // Custom APIs, schedulers and events calling g.sys.db run under their own rules, only real callers are scoped.
    if (!g.req.isApiRequestFromUser) return null

    val me = g.req.auth.getObject("authAMDB")[IDENTITY_FIELD]
    if (me == null || me == "") throw AmErrors("A person token is required for this API.", 401)

    val scope = mapOf(OWNER_COLUMN to me)
    // MERGE, never replace : $and keeps what the caller asked for and adds the owner on top, so a caller
    // can never widen the rows by sending its own owner filter.
    fun merge(existing: Any?): Any = if (existing is Map<*, *> && existing.isNotEmpty()) mapOf("\$and" to listOf(existing, scope)) else scope.toMutableMap()

    val apiId = g.req.reqInfo.getObject("apiInfo").getString("id", "")
    if (apiId in QUERY_FIND_APIS) {
        val query = g.req.objectAt("query")
        query["find"] = merge(query["find"])
        val body = g.req["body"]
        if (body is MutableMap<*, *>) (body as MutableMap<String, Any?>)[OWNER_COLUMN] = me // update-by-id and replace-by-id : the owner of a row can not be handed over
        return null
    }
    if (apiId in BODY_FIND_APIS) {
        val body = g.req.objectAt("body")
        body["find"] = merge(body["find"])
        val updateData = body["updateData"]
        if (updateData is MutableMap<*, *>) (updateData as MutableMap<String, Any?>)[OWNER_COLUMN] = me // update-many
        return null
    }
    if (apiId in STAMP_OWNER_APIS) {
        val rows = g.req["body"] as? List<*> ?: listOf(g.req.objectAt("body"))
        for (row in rows) {
            if (row is MutableMap<*, *>) (row as MutableMap<String, Any?>)[OWNER_COLUMN] = me // never trust the owner sent by the caller
        }
        return null
    }

    // Fail closed : aggregate and distinct can not be scoped generically, so they are refused for row scoped callers.
    throw AmErrors("API $apiId is not available for row scoped users.", 403)
}
```

The report writes this hook for you, for the tables which need it, and reads it again on every scan. [Handle role based permissions](https://docs.apimaker.dev/v4/docs/authorization/handle-role-based-permissions.html) builds the same pattern for several tables at once, with a group which reads every row.

## Answer without the API

**TypeScript**

**A pre hook which answers from the cache**

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

async function main(g: T.IAMGlobal) {
    const cached = await g.sys.cache.getKey('countries');
    if (cached) return JSON.parse(cached);   // the API does not run
}
module.exports = main;
```

**Python**

**A pre hook which answers from the cache**

```python
import json

from am import G


async def main(g: G):
    cached = await g.sys.cache.get_key('countries')
    if cached:
        return json.loads(cached)  # the API does not run
```

**PHP**

**A pre hook which answers from the cache**

```php
<?php
function main(AmG $g)
{
    $cached = $g->sys->cache->getKey('countries');
    if ($cached) {
        return json_decode($cached); // the API does not run
    }
    return null;
}
```

**C#**

**A pre hook which answers from the cache**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var cached = await g.Sys.Cache.GetKey("countries");
        if (cached is not null) return JsonNode.Parse(cached); // the API does not run
        return null;
    }
}
```

**Go**

**A pre hook which answers from the cache**

```go
package code

import (
	"encoding/json"

	"apimaker/am"
)

func Main(g *am.G) (any, error) {
	cached, err := g.Sys.Cache.GetKey("countries")
	if err != nil || cached == "" {
		return nil, err
	}
	var countries any
	err = json.Unmarshal([]byte(cached), &countries)
	return countries, err // the API does not run
}
```

**Rust**

**A pre hook which answers from the cache**

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

pub fn main(g: &mut G) -> Result<Value, Errors> {
    match g.sys.cache.get_key("countries")? {
        Some(cached) => Ok(serde_json::from_str(&cached)?), // the API does not run
        None => Ok(Value::Null),
    }
}
```

**Java**

**A pre hook which answers from the cache**

```java
import am.G;
import am.Json;

class Api {
    static Object main(G g) {
        String cached = g.sys.cache.getKey("countries");
        if (cached != null) return Json.parse(cached); // the API does not run
        return null;
    }
}
```

**Kotlin**

**A pre hook which answers from the cache**

```kotlin
import am.G
import am.Json

fun main(g: G): Any? {
    val cached = g.sys.cache.getKey("countries")
    return cached?.let { Json.parse(it) } // the API does not run
}
```

## Validate and change

**TypeScript**

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

async function main(g: T.IAMGlobal) {
    if (!g.req.isApiRequestFromUser) return;
    if (g.req.body?.price_cents < 0) throw [{ code: T.EStatusCode.BAD_REQUEST, message: 'A price can not be negative.' }];
    g.req.body.updated_by = g.req.auth.authAMDB?.email;
    g.req.headers['x-am-response-case'] = 'camelCase';   // headers can change too
    g.res.shared.startedAt = Date.now();                 // for the post hook of this request
}
module.exports = main;
```

**Python**

```python
import time

from am import Errors, G


async def main(g: G):
    if not g.req.is_api_request_from_user:
        return
    if (g.req.body.price_cents or 0) < 0:
        raise Errors('A price can not be negative.', code=400)
    g.req.body.updated_by = (g.req.auth.authAMDB or {}).get('email')
    g.req.headers['x-am-response-case'] = 'camelCase'   # headers can change too
    g.res.shared.startedAt = int(time.time() * 1000)    # for the post hook of this request
```

**PHP**

```php
<?php
function main(AmG $g)
{
    if (!$g->req->isApiRequestFromUser) {
        return null;
    }
    if (($g->req->body->price_cents ?? 0) < 0) {
        throw new AmErrors('A price can not be negative.', 400);
    }
    $g->req->body->updated_by = $g->req->auth->authAMDB?->email;
    $g->req->headers['x-am-response-case'] = 'camelCase';            // headers can change too
    $g->res->shared->startedAt = (int) (microtime(true) * 1000);    // for the post hook of this request
    return null;
}
```

**C#**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        if (!g.Req.IsApiRequestFromUser) return null;
        if (g.Req.Body.GetDouble("price_cents") < 0) throw new AmErrors("A price can not be negative.", 400);
        g.Req.Body["updated_by"] = g.Req.Auth.GetObject("authAMDB").GetString("email");
        g.Req.Headers["x-am-response-case"] = "camelCase";                                  // headers can change too
        g.Res.Shared["startedAt"] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();         // for the post hook of this request
        return null;
    }
}
```

**Go**

```go
package code

import (
	"time"

	"apimaker/am"
)

func Main(g *am.G) (any, error) {
	if !g.Req.IsApiRequestFromUser() {
		return nil, nil
	}
	if g.Req.Body().Float("price_cents") < 0 {
		return nil, am.Fail(400, "A price can not be negative.")
	}
	g.Req.Body()["updated_by"] = g.Req.Auth().Map("authAMDB").Get("email")
	g.Req.Headers()["x-am-response-case"] = "camelCase"     // headers can change too
	g.Res.Shared()["startedAt"] = time.Now().UnixMilli() // for the post hook of this request
	return nil, nil
}
```

**Rust**

```rust
use std::time::{SystemTime, UNIX_EPOCH};

use am::{Errors, G, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    if !g.req.is_api_request_from_user() {
        return Ok(Value::Null);
    }
    if g.req.body()["price_cents"].as_f64().unwrap_or(0.0) < 0.0 {
        return Err(am::fail(400, "A price can not be negative."));
    }
    let email = g.req.auth()["authAMDB"]["email"].clone();
    g.req.body_mut()["updated_by"] = email;
    g.req.headers_mut()["x-am-response-case"] = json!("camelCase"); // headers can change too
    let now = SystemTime::now().duration_since(UNIX_EPOCH).map(|since| since.as_millis()).unwrap_or(0);
    g.res.shared_mut()["startedAt"] = json!(now as u64); // for the post hook of this request
    Ok(Value::Null)
}
```

**Java**

```java
import am.AmErrors;
import am.G;

class Api {
    static Object main(G g) {
        if (!g.req.isApiRequestFromUser()) return null;
        if (g.req.getBody().getDouble("price_cents") < 0) throw new AmErrors("A price can not be negative.", 400);
        g.req.getBody().put("updated_by", g.req.getAuth().getObject("authAMDB").get("email"));
        g.req.getHeaders().put("x-am-response-case", "camelCase");           // headers can change too
        g.res.getShared().put("startedAt", System.currentTimeMillis());      // for the post hook of this request
        return null;
    }
}
```

**Kotlin**

```kotlin
import am.AmErrors
import am.G

fun main(g: G): Any? {
    if (!g.req.isApiRequestFromUser) return null
    if (g.req.body.getDouble("price_cents") < 0) throw AmErrors("A price can not be negative.", 400)
    g.req.body["updated_by"] = g.req.auth.getObject("authAMDB")["email"]
    g.req.headers["x-am-response-case"] = "camelCase"            // headers can change too
    g.res.shared["startedAt"] = System.currentTimeMillis()       // for the post hook of this request
    return null
}
```

## Utility classes in a hook

A hook calls your [utility classes](https://docs.apimaker.dev/v4/docs/utility-class/utility-class.html) like any other code of its language : a check shared by many hooks lives in one.

**TypeScript**

**utils/Guards : a utility class**

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

// The person of the request has the role, or the request is refused.
export function requireRole(g: T.IAMGlobal, role: string) {
    const roles: string[] = g.req.auth.authAMDB?.roles || [];
    if (!roles.includes(role)) {
        throw [{ code: T.EStatusCode.FORBIDDEN, message: `The role ${role} is needed.` }];
    }
}
```

**A pre hook which uses it**

```typescript
import * as T from 'types';
import * as Guards from 'utils/Guards';

async function main(g: T.IAMGlobal) {
    Guards.requireRole(g, 'PURCHASE_INVOICE_VIEW');
}
module.exports = main;
```

**Python**

**The utility class Guards**

```python
from am import Errors


# The person of the request has the role, or the request is refused.
def require_role(g, role):
    roles = (g.req.auth.authAMDB or {}).get('roles') or []
    if role not in roles:
        raise Errors(f'The role {role} is needed.', code=403)
```

**A pre hook which uses it**

```python
from Guards import require_role


async def main(g):
    require_role(g, 'PURCHASE_INVOICE_VIEW')
```

**PHP**

**The utility class Guards**

```php
<?php
// The person of the request has the role, or the request is refused.
class Guards
{
    public static function requireRole(AmG $g, string $role): void
    {
        $roles = $g->req->auth->authAMDB?->roles ?? [];
        if (!in_array($role, (array) $roles, true)) {
            throw new AmErrors("The role {$role} is needed.", 403);
        }
    }
}
```

**A pre hook which uses it**

```php
<?php
function main(AmG $g)
{
    Guards::requireRole($g, 'PURCHASE_INVOICE_VIEW');
    return null;
}
```

**C#**

**The utility class Guards**

```csharp
// The person of the request has the role, or the request is refused.
public static class Guards
{
    public static void RequireRole(G g, string role)
    {
        if (!g.Req.Auth.GetObject("authAMDB").GetStrings("roles").Contains(role))
            throw new AmErrors($"The role {role} is needed.", 403);
    }
}
```

**A pre hook which uses it**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        Guards.RequireRole(g, "PURCHASE_INVOICE_VIEW");
        return null;
    }
}
```

**Go**

**The utility class Guards, in the folder utils**

```go
package guards

import "apimaker/am"

// RequireRole : the person of the request has the role, or the request is refused.
func RequireRole(g *am.G, role string) error {
	for _, held := range g.Req.Auth().Map("authAMDB").List("roles") {
		if held == role {
			return nil
		}
	}
	return am.Fail(403, "The role "+role+" is needed.")
}
```

**A pre hook which uses it**

```go
package code

import (
	"apimaker/am"
	guards "apimaker/src/utility_classes/utils---Guards"
)

func Main(g *am.G) (any, error) {
	return nil, guards.RequireRole(g, "PURCHASE_INVOICE_VIEW")
}
```

**Rust**

**The utility class Guards**

```rust
use am::{Errors, G};

/// The person of the request has the role, or the request is refused.
pub fn require_role(g: &G, role: &str) -> Result<(), Errors> {
    let roles = g.req.auth()["authAMDB"]["roles"].as_array().cloned().unwrap_or_default();
    if roles.iter().any(|held| held == role) {
        Ok(())
    } else {
        Err(am::fail(403, &format!("The role {role} is needed.")))
    }
}
```

**A pre hook which uses it**

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

use crate::Guards;

pub fn main(g: &mut G) -> Result<Value, Errors> {
    Guards::require_role(g, "PURCHASE_INVOICE_VIEW")?;
    Ok(Value::Null)
}
```

**Java**

**The utility class Guards**

```java
import am.AmErrors;
import am.G;

/** The person of the request has the role, or the request is refused. */
public class Guards {
    public static void requireRole(G g, String role) {
        if (!g.req.getAuth().getObject("authAMDB").getStrings("roles").contains(role)) {
            throw new AmErrors("The role " + role + " is needed.", 403);
        }
    }
}
```

**A pre hook which uses it**

```java
import am.G;

class Api {
    static Object main(G g) {
        Guards.requireRole(g, "PURCHASE_INVOICE_VIEW");
        return null;
    }
}
```

**Kotlin**

**The utility class Guards**

```kotlin
import am.AmErrors
import am.G

/** The person of the request has the role, or the request is refused. */
object Guards {
    fun requireRole(g: G, role: String) {
        if (role !in g.req.auth.getObject("authAMDB").getStrings("roles")) throw AmErrors("The role $role is needed.", 403)
    }
}
```

**A pre hook which uses it**

```kotlin
import am.G

fun main(g: G): Any? {
    Guards.requireRole(g, "PURCHASE_INVOICE_VIEW")
    return null
}
```

- Press Ctrl+Space in the editor for the list of your [utility classes](https://docs.apimaker.dev/v4/docs/utility-class/utility-class.html).

## Testing a hook

- The API testing page runs an API with its hooks. While you edit a hook there, the **Test** button of the hook runs only that hook, so you see its effect alone.
- Every hook has versions : keep the old one active until the new one is ready.
- The log profile records each hook run with the request ; the log explorer shows them next to the API call.

## Good to know

- Streams : pre hooks run before the stream starts ; post hooks can not change what was streamed.
- A hook which calls the same API it is attached to creates a cycle : API Maker detects it and stops the request with an error.
- Hooks are files in Git, under the folder of their instance, database, table or API, and deploy with a Git pull.

- [Post hooks →](https://docs.apimaker.dev/v4/docs/apis-all/hooks/postHook-api.html)
- [The global object g](https://docs.apimaker.dev/v4/docs/pre-defined-terms/global-object-g.html)
