# Languages and Runtimes

> Write custom APIs, hooks, schedulers and event listeners of API Maker in TypeScript (Node.js or Bun), Python, PHP, C#, Go, Rust, Java or Kotlin. How code runs, which runtime and version it gets, its packages and how to choose a language.

Source: https://docs.apimaker.dev/v4/docs/languages/index.html

The code of an account (custom APIs, pre and post hooks, schedulers and event listeners) can be written in any of these languages. Pick the language at the top of the code editor; the code of each item keeps its own language. An API is [one language](https://docs.apimaker.dev/v4/docs/languages/index.html#one-language) : its code and all its hooks are written in the same language and run on the same runtime.

| Language | Runtimes | Image (default version) | Packages from | `main` |
|---|---|---|---|---|
| [TypeScript / JavaScript](https://docs.apimaker.dev/v4/docs/languages/typescript.html) | Node.js, Bun | `node:24-slim`, `oven/bun:1-slim` | npm | `async function main(g)` |
| [Python](https://docs.apimaker.dev/v4/docs/languages/python.html) | Python | `python:3.13-slim` | PyPI | `def main(g)` or `async def main(g)` |
| [PHP](https://docs.apimaker.dev/v4/docs/languages/php.html) | PHP 8.3+ | `php:8.4-cli` | Packagist (Composer) | `function main($g)` |
| [C#](https://docs.apimaker.dev/v4/docs/languages/csharp.html) | .NET | `mcr.microsoft.com/dotnet/sdk:10.0` | NuGet | `public static async Task<object?> Main(G g)` |
| [Go](https://docs.apimaker.dev/v4/docs/languages/go.html) | Go | `golang:1.27` | Go modules | `func Main(g *am.G) (any, error)` |
| [Rust](https://docs.apimaker.dev/v4/docs/languages/rust.html) | Rust | `rust:1-slim` | crates.io | `pub fn main(g: &mut G) -> Result<Value, Errors>` |
| [Java](https://docs.apimaker.dev/v4/docs/languages/java.html) | JDK 20+ | `eclipse-temurin:25-jdk` | Maven Central | `static Object main(G g)` in a class |
| [Kotlin](https://docs.apimaker.dev/v4/docs/languages/kotlin.html) | Kotlin on the JDK | `eclipse-temurin:25-jdk`, with the compiler of Kotlin `2.4.20` | Maven Central | `fun main(g: G): Any?` |

Every language gets the same [`g`](https://docs.apimaker.dev/v4/docs/pre-defined-terms/global-object-g.html) : the request (`g.req`), the answer (`g.res`), the APIs of API Maker (`g.sys.db`, `g.sys.system`, `g.sys.cache`), the logger and the shared values, written the way the language writes things (`g.sys.db.get_all` in Python, `g.Sys.Db.GetAll` in C# and Go).

## The same API in every language

The API reads `name` from the body, counts the rows of a table and answers both :

**TypeScript**

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

async function main(g: T.IAMGlobal) {
    const count = await g.sys.db.count({ instance: 'mysql_8', database: 'inventory', collection: 'customers' });
    return { hello: g.req.body.name || 'world', customers: count };
}
module.exports = main;
```

**Python**

```python
from am import G


async def main(g: G):
    count = await g.sys.db.count('mysql_8', 'inventory', 'customers')
    return {'hello': g.req.body.name or 'world', 'customers': count}
```

**PHP**

```php
<?php
function main(AmG $g)
{
    $count = $g->sys->db->count('mysql_8', 'inventory', 'customers');
    return ['hello' => $g->req->body->name ?? 'world', 'customers' => $count];
}
```

**C#**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var count = await g.Sys.Db.Count(new Count("mysql_8", "inventory", "customers"));
        return new { hello = g.Req.Body.GetString("name", "world"), customers = count };
    }
}
```

**Go**

```go
package code

import "apimaker/am"

func Main(g *am.G) (any, error) {
	count, err := g.Sys.Db.Count(am.Count{Instance: "mysql_8", Database: "inventory", Collection: "customers"})
	if err != nil {
		return nil, err
	}
	name := g.Req.Body().String("name")
	if name == "" {
		name = "world"
	}
	return am.M{"hello": name, "customers": count}, nil
}
```

**Rust**

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

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let count = g.sys.db.count(Count::new("mysql_8", "inventory", "customers"))?;
    let name = g.req.body()["name"].as_str().unwrap_or("world");
    Ok(json!({ "hello": name, "customers": count }))
}
```

**Java**

```java
import am.Count;
import am.G;
import java.util.Map;

class Api {
    static Object main(G g) {
        long count = g.sys.db.count(new Count("mysql_8", "inventory", "customers"));
        String name = g.req.getBody().getString("name", "world");
        return Map.of("hello", name, "customers", count);
    }
}
```

**Kotlin**

```kotlin
import am.Count
import am.G

fun main(g: G): Any? {
    val count = g.sys.db.count(Count("mysql_8", "inventory", "customers"))
    return mapOf("hello" to g.req.body.getString("name", "world"), "customers" to count)
}
```

## How code runs

```mermaid
flowchart LR
    R[Request] --> S[API Maker server]
    S -->|TypeScript which needs no sandbox| Q[JavaScript engine<br/>inside the server]
    S -->|your code and its request, once| P[(Sandbox of the account<br/>one per language)]
    P -->|what the code made of it, once| S
    P -->|every g.sys call of your code| D[(Databases, Redis)]
    S -->|the API itself| D
```

- **One sandbox per account and language.** A sandbox is a Docker container of the image of the language (its runtime, its version, the packages you added and your [Dockerfile](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html#dockerfile)). The code of an account never shares a process with another account.
- **The server answers the request, the sandbox runs your code.**{ #where-code-runs } A request for a custom API, or for an API with hooks, is answered by the server like every other request. Your code goes to the sandbox of its language once, with the request as `g.req`, and what it left comes back once : the parts of the request it changed, and `g.res`. The hooks before the API, the API and the hooks after it each take their turn this way.
- **A `g.sys…` call is answered inside the sandbox.**{ #g-sys-in-the-sandbox } Every sandbox loads API Maker itself, through the native package of its language (Node-API for Node.js and Bun ; PyO3 for Python ; JNI for Java and Kotlin ; functions of C for C#, Go, Rust and PHP). `g.sys.db.getAll(…)` in your code is a function call into it : the sandbox reads the database itself, with the groups of the API user of the request, and the server takes no part in the call. This holds for every language ; nothing has to be set, and no setting turns it off. Code on the [native process](https://docs.apimaker.dev/v4/docs/apis-all/custom-apis/user-created-custom-api.html#native-process) (`runOnNativeProcess: true`) works the same way : the runner of its language on the machine of the server loads API Maker too, one for every account, and answers what the code of each account asks with that account's data. The server answers such a call only where the sandbox can not : in a sandbox for [debugging](https://docs.apimaker.dev/v4/docs/features/developer-tools.html#debugger), for a run of [test cases](https://docs.apimaker.dev/v4/docs/test-cases/test-cases.html) (their mocks and their code coverage are with the server), and for a database the sandbox does not reach (a client library or a certificate which is on the machine of the server).
- **TypeScript which needs no sandbox runs inside API Maker.**{ #in-the-server } A hook or a custom API in TypeScript which only works on what it is handed (it reads `g.req`, changes it, returns a value, reads through `g.sys.db`, `g.sys.system.getSecret` or `g.sys.cache.getKey`) is run by the server itself, with the JavaScript engine it carries (QuickJS) : no sandbox is asked, none has to start. Code which uses anything else (an npm package, a utility class, `try` / `catch`, dates, timers, `g.shared`, a call which writes) runs in its sandbox of Node.js or Bun, as code in every other language runs in the sandbox of its language. You do not choose : API Maker reads the code when it is saved, and the answer is the same either way. `sandbox.codeInServer: false` in the [settings of the installation](https://docs.apimaker.dev/v4/docs/am-resources/api-maker-configurations.html#sandboxes) sends all code to its sandbox.
- **No network between the server and its sandboxes.** On Linux, where the server runs on the machine of its containers (what the install script sets up), the server reaches each sandbox through a Unix socket in a folder of its own ; everywhere else through the address or the port of the container. Nothing has to be set.
- **Many requests at once.** Every runner serves many requests at the same time : coroutines in Python, fibers in PHP, tasks in C#, goroutines in Go, threads in Rust, Java and Kotlin, the event loop in JavaScript. While all runners of a sandbox are busy, the sandbox opens another one, and while all sandboxes are busy a new sandbox starts, up to the [sandbox count](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html#sandbox-count).
- **Compiled once.** TypeScript is compiled to JavaScript when you save. Go, Rust, C#, Java and Kotlin code is compiled the first time it runs; since saving starts that compilation in the background, the first request does not wait for the compiler, and code which does not compile is reported in the console of the account a few seconds after the save. Python and PHP code is ready at once.
- **Timeouts.** A run which takes longer than the timeout of its request takes its sandbox with it : the next request gets a new sandbox. Code which loops forever can not block the others.
- **Images are shared.** An image is named after what it is made of, so every account with the same runtime, version, packages and Dockerfile uses the same image, built once per server. Saving the settings builds the images which changed on every server, in the background.

## The same API, measured in every language

One custom API which reads the ten rows of a table (`g.sys.db.getAll`), written in every language. Ten connections, 20 seconds each after a rest, the sandboxes in Docker on a laptop (2026-10-09) ; no request failed.

| Language | Requests a second |
|---|---|
| TypeScript on Node.js | 11,648 |
| TypeScript on Bun | 11,268 |
| Rust | 11,260 |
| Kotlin | 11,230 |
| Java | 11,042 |
| PHP | 10,507 |
| C# | 10,338 |
| Go | 9,957 |
| Python | 9,415 |

The numbers of your machine differ ; what holds is that the language is a small part of what a request costs. Most of it is API Maker and the database, which every language shares.

## Choosing the language and the runtime of a piece of code

1. Open the custom API, hook, scheduler, event listener, WebSocket event, migration script, process initializer, token generator (its functions), utility class or test case. [Settings](https://docs.apimaker.dev/v4/docs/languages/index.html#basic-info) have no language to pick : they are TypeScript.
2. Pick the **language** in the title bar of the editor. A language with several runtimes (TypeScript : Node.js, Bun) shows a second list : pick the **runtime**, or leave **Default** to run on the one [Sandbox settings](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html#runtime) names for the language.
3. Untouched code becomes the template of the new language. Code you wrote is kept unless you ask for the template : rewrite it in the new language.
4. Save. The code is saved as it is written : `codeLanguage` remembers the language and `codeRuntime` the runtime (absent for the default one), and its versions keep them too.

- **One account, many languages.** Every item has its own language : one custom API in TypeScript, the next in Python, a scheduler in Go, an event listener in Kotlin. A custom API can call another custom API written in another language ; each runs in the sandbox of its language.
- **One API, one language**{ #one-language } : the code of an API and all its hooks (before and after it, of every version) are written in one language and run on one runtime : a request has one kind of code, in one place. For the APIs of tables, the hooks of the instance, of the database, of the table and of the API run for the same request, so they are one language too.
    - The form of a hook shows the language of its API, with a lock : there is nothing to choose. The first hook of an API which has no code of its own (an API of a table, a system API) chooses, and the next ones follow it.
    - A custom API which has hooks keeps its language : rewrite or remove the hooks first. The admin panel says which hooks stand in the way.
    - A save which would mix two languages is refused in words, wherever it comes from (the admin panel, a pull of git, the local client).
- **Logos.** The lists show the language of every piece of code with its logo, and the runtime too where a language has several (TypeScript on Node.js or Bun) : the grid and the tree of the custom APIs, the hooks of an API, schedulers, event listeners, process initializers, migration scripts, token generators, test cases, and the Code tab of API Testing.
- **Two runtimes side by side.** One custom API on Node.js and another on Bun run in the same account, each in the image of its runtime with the packages of JavaScript.
- **Settings are always TypeScript**{ #basic-info } : code which describes something, and runs once when you save it, is written in TypeScript, whatever language the code next to it is written in. API Maker [runs it itself](https://docs.apimaker.dev/v4/docs/languages/index.html#settings-run) at that moment, in no sandbox and on no runtime : what settings export is JSON for the database.
    - What is settings : the Basic Info of a custom API (its [settings](https://docs.apimaker.dev/v4/docs/settings/customApiSettings.html)) and of a token generator, the settings of the APIs of a [table](https://docs.apimaker.dev/v4/docs/settings/apiSettings.html), of a [database](https://docs.apimaker.dev/v4/docs/settings/databaseSettings.html) and of a [system API](https://docs.apimaker.dev/v4/docs/settings/systemApiSettings.html), a [schema](https://docs.apimaker.dev/v4/docs/schema/schema.html), the texts of [internationalization](https://docs.apimaker.dev/v4/docs/i18/i18.html) and a [secret](https://docs.apimaker.dev/v4/docs/secrets/secrets.html).
    - Their editors have no language to pick. The Basic Info tab, which stands next to code of any language, shows TypeScript with a lock : there is nothing to choose, and no runtime.
    - In git they are `.ts` files, next to the code where there is code (`orders.config.ts` next to `orders.py`).
    - What the code exports is what API Maker keeps : a request reads it, and never runs settings again.
    - Settings import `types` and your own utility classes written in TypeScript, not packages : see [where settings run](https://docs.apimaker.dev/v4/docs/languages/index.html#settings-run).
- **API Testing follows the language.** The code editor of the API Testing page shows a custom API, a scheduler, a listener or a hook in the language it is written in, and **SEND** tries the code on screen in that language, on the runtime it names.
- A language or a runtime API Maker does not run, and a runtime of another language (`codeLanguage: 'python'` with `codeRuntime: 'node'`), are refused when the code is saved, in words.

## Where settings run

Settings describe an item : what they export is kept as JSON, and nothing else happens. So API Maker runs them itself, inside the server, the moment you save, with the JavaScript engine it carries (QuickJS). No sandbox starts for them and no image is built : a save answers in milliseconds, also for an account whose code is all Python, also while the images of its sandboxes are still being built.

| Settings can use | |
|---|---|
| TypeScript itself | Everything of the language : objects, functions, classes, `async`, `Date`, `JSON`, `Math`, regular expressions, `BigInt`. |
| `types` | `import * as T from 'types'` : the enums and the interfaces of API Maker. |
| Your utility classes | The ones written in TypeScript : `import { ROLES } from 'utils/Roles'`. For the lists, the names and the small functions several settings share. A class which itself imports a package can not be used by settings. |
| A few things of Node.js | `console` (it prints into the console of the account), `Buffer`, the timers, `crypto` (`randomUUID`, `randomBytes`, `randomInt`, `createHash`, `createHmac`), `util.format`. `process.env` is there, and empty : settings see nothing of the machine. |

| Settings can not use | |
|---|---|
| Packages | The packages of npm are in the sandboxes, for code which runs. `import * as _ from 'lodash'` in settings is refused when you save : `Cannot find module 'lodash' : settings are run by API Maker itself when they are saved, not in a sandbox…` |
| The rest of Node.js | No files and no network : no `fs`, `path`, `http`, `fetch`. Nor what Node.js adds to the language : `URL`, `structuredClone`, `atob` / `btoa`, `Intl` (texts are compared letter by letter). |
| `g.sys` | Settings call no API. They describe ; a [custom API](https://docs.apimaker.dev/v4/docs/apis-all/custom-apis/user-created-custom-api.html) or a [hook](https://docs.apimaker.dev/v4/docs/apis-all/hooks/preHook-api.html) does. |

**utils/Roles : a utility class in TypeScript, shared by settings**

```typescript
export const ROLES: string[] = ['admin', 'editor', 'reader'];
```

**A schema which uses it**

```typescript
import { ISchemaType, EType } from 'types';
import { ROLES } from 'utils/Roles';

let schema: ISchemaType = {
    name: { __type: EType.string, validations: { required: true } },
    role: { __type: EType.string, validations: { enum: ROLES } },
};
module.exports = { schema };
```

- **The editor of settings knows what settings have.** The Basic Info tab of a custom API and of a token generator, and the editors of API settings, schemas, internationalization and secrets offer what the table above lists, and nothing else of Node.js : after `crypto.` the six functions which are there, after `process.` `env` and no `argv`, in `import … from '` the modules `crypto`, `buffer`, `util` and your utility classes. A package of npm, `fs`, `fetch` or `URL` is underlined in red while you type, and the editor says why : `Settings are run by API Maker itself when they are saved, not in a sandbox…` The Code tab next to it keeps Node.js and the packages of your sandbox : that code runs there. One name is offered which settings do not have : `Intl`, which belongs to the language itself for the editor.
- **Limits** : settings have 10 seconds and 64 MB. Code which does not end is stopped, and the save says so : `The schema did not finish in 10 seconds…`
- **Errors** : what settings throw is the error of the save, with the line of your TypeScript (`at fn (MainSandbox.ts:7:24)`).
- **Functions inside settings** : the functions of a [schema](https://docs.apimaker.dev/v4/docs/schema/schema.html) (`validatorFun`, `conversionFun`, `defaultFun`) are kept as their text when the schema is saved. They run later, with the requests, [where the request is answered](https://docs.apimaker.dev/v4/docs/schema/schema.html#where-the-functions-run), and there they have the packages of that sandbox : call `require('dayjs')` inside the function, not at the top of the schema.
- **Dates** are counted in the time zone of the server. Write a moment with its zone (`'2026-01-02T00:00:00+05:30'`) or with `Date.UTC(…)`.

## The code editor in every language

The code editor completes code in every language, not only in TypeScript. It knows `g` as the language writes it, with the types of every call : what a call takes, what it answers, your instances, databases and tables, and the columns of a table on the rows a call answers.

- **`g` and its parts.** After `g.` (`$g->` in PHP) the editor offers `req`, `res`, `sys`, `logger` and `shared`, then the calls of each part of `g.sys` under the names of the language : `get_all` in Python and Rust, `GetAll` in C# and Go, `getAll` in PHP, Java and Kotlin. Each shows its signature with the types of the language and says what it does ; the hint which opens at `(` shows what it takes, and so does pointing at a name. `full` (after `g.sys.db.`, `g.sys.system.`, `g.sys.cache.`) has the same calls with their whole answers.
- **The request and the answer.** After `g.req.` and `g.res.` the editor offers a member for each of their keys, as the language reads one : a property (`g.req.body` in Python, PHP and Kotlin, `g.Req.Body` in C#), a method (`g.Req.Body()` in Go, `g.req.body()` in Rust), a getter and a setter (`g.req.getBody()`, `g.res.setStatusCode(201)` in Java). After `auth` it offers the opened tokens.
- **What a call takes.** Each language makes it its own way, and the editor follows :

| Language | What a get all takes | The editor offers |
|---|---|---|
| TypeScript | `{ instance: 'mysql_8', …, queryParams: { limit: 10 } }` | the keys of the object |
| Python | `get_all('mysql_8', 'inventory', 'customers', limit=10)` | the named arguments : `limit=`, `find=`, `sort=`… |
| PHP | `getAll('mysql_8', 'inventory', 'customers', limit: 10)` | the named arguments : `limit: `, `find: `, `sort: `… |
| C# | `new GetAll("mysql_8", "inventory", "customers") { Limit = 10 }` | the properties between the braces : `Limit = `, `Find = `… |
| Go | `am.GetAll{Instance: "mysql_8", …, Limit: 10}` | the fields between the braces : `Limit: `, `Find: `… |
| Rust | `GetAll::new("mysql_8", "inventory", "customers").limit(10)` | `new` after `GetAll::`, then the methods which set a key |
| Java | `new GetAll("mysql_8", "inventory", "customers").limit(10)` | the methods which set a key, one after the other |
| Kotlin | `GetAll("mysql_8", "inventory", "customers").limit(10)` | the methods which set a key, one after the other |

- Each key says what it is for, in the words of TypeScript. Where a key names an instance, a database or a table, the editor offers the ones of your account : the instances, then the databases of the instance, then the tables of the database. Where a key names columns (`find`, `select`, `sort`, `saveData`, `updateData`, the column of a `distinct`), it offers the columns of the table.
- **What a call answers has its type**, and the editor offers its members : the columns of the table on a row of `getAll` and of `query`, on the row of `getById`, `updateById`, `replaceById` and `removeById`, on what `saveSingleOrMultiple` saved (`saved.row`, `saved.rows`), on the rows a stream hands to its function and on the deleted rows of `removeByQuery` ; the keys of what an API describes (`updatedRowsCount` of an `updateMany`, `token` of a `getToken`…) ; `success`, `data`, `errors` on the whole answer of a call. A column which is an object offers its own columns. Each column shows its type, and whether it is required or the primary key.

| Language | The columns are offered after |
|---|---|
| TypeScript | `rows[0].` |
| Python | `rows[0].`, `row['`, `row.get('` |
| PHP | `$rows[0]->`, `$row['`, `$row->get('` |
| C# | `rows[0].` (written `rows[0]["first_name"]`), `row["`, `row.GetString("` (and `GetInt64`, `GetDouble`, `GetObject`…) |
| Go | `rows[0].` (written `rows[0]["first_name"]`), `row["`, `row.String("` (and `Get`, `Map`, `List`, `Float`, `Int`, `Bool`) |
| Rust | `rows[0].` (written `rows[0]["first_name"]`), `row["`, `row.get("` |
| Java | `rows.get(0).` (written `.get("first_name")`), `row.getString("` (and `getLong`, `getDouble`, `getObject`…) |
| Kotlin | `rows[0].` (written `rows[0]["first_name"]`), `row["`, `row.getString("` |

- **Where the table is read from.** From the names written as text : in the call itself (what it is given first, a named argument, a method, a field), in a variable what the call takes is kept in, or in a function of the code which makes it. A loop over the rows, a row kept in a variable and a function given to a stream keep the table.
- **What is not known.** Names made while the code runs, and a table without a [schema](https://docs.apimaker.dev/v4/docs/schema/schema.html), offer no columns : the editor then suggests the words of the code, as it did.
- The editor asks the server for your tables each time it opens ; **↺ Types** in its corner reads them again after a schema changed.

## Utility classes in every language

A [utility class](https://docs.apimaker.dev/v4/docs/utility-class/utility-class.html) is code the other code of its language shares. Pick its language in the title bar of its editor, as for any other code. Each language names what it imports its own way, and it is the same way in a sandbox and in the [Git repository](https://docs.apimaker.dev/v4/docs/Git/git.html) of the account :

| Language | A class `Tax` in the folder `utils/` is | The code names it |
|---|---|---|
| TypeScript | the module `utils/Tax` | `import * as tax from 'utils/Tax';` |
| Python | the module `Tax` | `from Tax import add` |
| PHP | what its file declares : classes, functions, constants | `Tax::add(100)`, `add_tax(100)`, `TAX_RATE` |
| C# | what its file declares, compiled with the code | `Tax.Add(100)` |
| Go | a package, imported by its folder in the repository | `import tax "apimaker/src/utility_classes/utils---Tax"` |
| Rust | the module `Tax` of the crate of the code | `use crate::Tax::add;` |
| Java | the class `Tax`, compiled with the other classes of Java of the account | `Tax.add(100)` |
| Kotlin | what its file declares (it starts as an `object Tax`), compiled with the other classes of Kotlin | `Tax.add(100.0)` |

- **A class is used by the code of its own language.** Python code imports the classes written in Python, Go code the ones written in Go, and Java code does not see a class written in Kotlin. Code of another language reaches what a class does through a custom API.
- **Saved is live.** The next request runs with the class as it was saved : nothing restarts. Go, Rust, C#, Java and Kotlin code which uses the class is compiled again on its next run.
- **Classes use each other**, and the packages of their language.
- **Names.** In Python and Rust the name of the class is the name of its module : letters of a to z, digits and `_`, no word of the language (`lambda`, `type`), and one class of that name per language. In Go the folder is what is imported : letters of a to z, digits, `.`, `-` and `_`. In Java and Kotlin it is the name of a class or of an object : letters of a to z, digits and `_`, no digit first, no word of the language (`record`, `object`), not `am`, and one class of that name per language. A folder and a name belong to one class, whatever its language. What does not fit is refused when the class is saved, in words.
- **What a class keeps.** A module of Python, a file of PHP, a package of Go and the classes of Java or of Kotlin are loaded once per sandbox : what they keep (a counter, a connection) is shared by all code of the account there. A class of C# or of Rust is compiled into every piece of code which runs : what it keeps in a static is kept per piece of code.
- **PHP** : a utility class has no `namespace` of its own, and the code names what it declares without a leading `\`. API Maker loads every class in a namespace it chooses, so a class which changed is loaded next to the one it was.
- **The repository resolves the same names** : `pyproject.toml` lists the folders of the Python classes for Pyright / Pylance, pytest and mypy, `composer.json` loads the files of the PHP classes, `am/rust/project.rs` declares the modules of Rust, and the projects of Gradle (`am/java/build.gradle.kts`, `am/kotlin/build.gradle.kts`) compile the classes of Java and of Kotlin together, then every piece of code with them. Go and C# need nothing : a folder is a package, and one project compiles every file.

## Test cases in every language

A [test case](https://docs.apimaker.dev/v4/docs/test-cases/test-cases.html#other-languages) is written in any of the languages too. Its tests are named the way the test tools of the language name them (`test_…` functions in Python and PHP, `Test…` methods in C#, a list `Tests` in Go, `am::tests!` in Rust, `test…` methods in Java and functions in Kotlin), each gets `g`, and `g.sys.test` runs a custom API of any language with the mocks of the test.

> **What stays TypeScript**
>
> - [Settings](https://docs.apimaker.dev/v4/docs/languages/index.html#basic-info) : the Basic Info of a custom API and of a token generator, the settings of the APIs of a table, of a database and of a system API, schemas, the texts of internationalization and secrets. TypeScript, whatever language the code is written in, [run by API Maker itself](https://docs.apimaker.dev/v4/docs/languages/index.html#settings-run).
> - The functions of schemas (`validatorFun`, `conversionFun`, `defaultFun`), and mocks which are functions. A request answered in the sandbox of another language runs the functions of schemas in that sandbox too, in the JavaScript engine API Maker has there : see [where the functions run](https://docs.apimaker.dev/v4/docs/schema/schema.html#where-the-functions-run).
> - In a run of test cases, the lines of utility classes are counted for the classes written in TypeScript.

## Versions

API Maker runs the versions of a runtime released after January 2023. Where the runner of a language needs a newer one, that one is the oldest : Go 1.21 and Rust 1.88.

| Runtime | Default version | Oldest version |
|---|---|---|
| Node.js | `24` | `20` |
| Bun | `1` | `1.0` |
| Python | `3.13` | `3.12` |
| PHP | `8.4` | `8.3` |
| .NET (C#) | `10.0` | `8.0` |
| Go | `1.27` | `1.21` |
| Rust | `1` (the latest stable) | `1.88` |
| JDK (Java and Kotlin) | `25` | `20` |

- The version of a runtime is picked in [Sandbox settings](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html#runtime), from the list of the versions API Maker runs.
- An older version is refused when the settings are saved, in words : `Python 3.10 is too old : API Maker runs Python 3.12 and later.`
- Code on the [native process](https://docs.apimaker.dev/v4/docs/apis-all/custom-apis/user-created-custom-api.html#native-process) runs with the runtime of the machine of the server, at least the oldest version above : [API Maker Launchpad](https://docs.apimaker.dev/v4/docs/getting-started/local-run.html#languages-for-native-apis) installs one, on your computer and on your servers.

## Packages

Each language installs its packages from its own registry, in its own image : nothing is installed unless you add it in [Sandbox Settings](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html#packages). The code imports them the usual way :

| Language | Add in settings | In the code |
|---|---|---|
| TypeScript | `dayjs` `1.11.13` | `import * as dayjs from 'dayjs';` |
| Python | `requests` `2.32.3`, with extras `uvicorn[standard]` | `import requests` |
| PHP | `guzzlehttp/guzzle` `^7.9` | `use GuzzleHttp\Client;` |
| C# | `Humanizer.Core` `2.14.1` | `using Humanizer;` |
| Go | `github.com/google/uuid` `v1.6.0` | `import "github.com/google/uuid"` |
| Rust | `serde[derive]` `1`, `uuid[v4]` `1` | `use uuid::Uuid;` |
| Java | `com.google.code.gson:gson` `2.11.0` | `import com.google.gson.Gson;` |
| Kotlin | `com.google.code.gson:gson` `2.11.0` | `import com.google.gson.Gson` |

- A version is optional : the latest one is installed when the image is built.
- While code runs nothing is downloaded : Bun's automatic install and Go's module proxy are switched off. A package which is not installed is said in the error : `the package 'github.com/google/uuid' is not installed : add it to the packages of Go in the settings of the sandbox`.

## Errors in every language

What the code raises becomes the errors of the answer, with the lines of your code :

**A Python ValueError, as the API answers it**

```json
{
    "success": false,
    "statusCode": 500,
    "errors": [{
        "code": 500,
        "message": "ValueError: quantity must be positive",
        "stack": [
            "ValueError: quantity must be positive",
            "  File \"orders.py\", line 6, in check",
            "    raise ValueError('quantity must be positive')"
        ]
    }]
}
```

To answer with your own errors and status, use the error type of the language : `throw [{ code: 409, message }]` in TypeScript, `raise g.Errors('…', code=409)` in Python, `throw new AmErrors('…', 409)` in PHP, C# and Java, `return nil, am.Fail(409, "…")` in Go, `Err(am::fail(409, "…"))` in Rust, `throw AmErrors("…", 409)` in Kotlin.

## Bytes as the answer in every language

A file, a picture, a PDF : code which returns bytes (or leaves them in the output of `g.res`) answers with those bytes as they are, under the content type it names (`image/png`, `application/pdf`…), with the headers it sets (`Content-Disposition`, `Cache-Control`). Each language has its own type of bytes :

| Language | Bytes as the answer |
|---|---|
| TypeScript / JavaScript | a `Buffer`, or any `Uint8Array` |
| Python | `bytes`, `bytearray`, `memoryview` |
| PHP | `new AmBytes($bytes)` : a text of PHP holds bytes as well as letters |
| C# | a `byte[]` or a `Memory<byte>` returned by `Main` ; `g.Res.Output = Json.Bytes(bytes)` |
| Go | a `[]byte` |
| Rust | `am::bytes(bytes)` : a value of JSON has nothing which stands for bytes |
| Java | a `byte[]` |
| Kotlin | a `ByteArray` |

The page of each language has an example, and [the content types](https://docs.apimaker.dev/v4/examples/res/res.html#octet-stream) one in every language.

## Related

- [Sandbox settings](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html) · [The global object g](https://docs.apimaker.dev/v4/docs/pre-defined-terms/global-object-g.html) · [Custom APIs](https://docs.apimaker.dev/v4/docs/apis-all/custom-apis/user-created-custom-api.html) · [Separate sandboxes of a custom API](https://docs.apimaker.dev/v4/docs/settings/customApiSettings.html#separate-sandbox)
