API Maker

The backend engine for the AI era

Your code

Custom APIs

Write a function in TypeScript, Python, PHP, C#, Go, Rust, Java or Kotlin, save it, and the API is live.

For logic that goes beyond the generated APIs, write a main function in the editor of API Maker, in the language you pick above it. It gets the request in g.req and every database, cache and system API in g.sys. Save it and it answers at /api/custom-api/<your path>, with tokens, validation, hooks and logs like every other API.

See it live

Step through it, slow it down, or open the full canvas.

api-maker/features/custom-apiLive
  • Request
  • Your code
  • Data
  • Error
Deploy steps
0after save
Calls
0received
Sandbox runs
0of the code
Last status
–HTTP

Your logic is one function. In TypeScript, as here, or in Python, PHP, C#, Go, Rust, Java or Kotlin: main(g) gets the request in g.req and API Maker in g.sys: databases, cache, system APIs, events. Return what the API answers.

How it works

  1. Your logic is one function.

    In TypeScript, as here, or in Python, PHP, C#, Go, Rust, Java or Kotlin: main(g) gets the request in g.req and API Maker in g.sys: databases, cache, system APIs, events. Return what the API answers.

  2. Save it and the API is live.

    The code is compiled when you save it and POST /api/custom-api/admin/order-total answers at once. No build, no deployment.

  3. Every call is checked first.

    The token is verified, the pre hooks run and the body is validated against the schema of the API, before your code.

  4. Your code runs in the sandbox.

    In a Docker container, apart from API Maker, it queries MongoDB through g.sys.db like any API, and computes the total.

  5. The answer has the usual shape.

    After the post hooks, the returned object becomes { success, statusCode, data }. The call is logged with its execution time.

  6. Errors are yours to shape.

    No orders for customer 99: g.res.errors sets the message and the status code, and the API answers 404.

  7. Keep versions, run the active one.

    Save a new version next to the old one, each with its own pre and post hooks. Activate it and the next call runs it; activate the old one to roll back.

What you get

All your data through g.sys

g.sys.db calls every generated and schema API of every instance, g.sys.cache works with Redis, and g.sys.system encrypts, hashes, emits events and calls external APIs.

Uploads and downloads

Accept files with size and extension rules per field, and return a file or a zip of folders for the client to download.

Your packages

Add packages of npm, PyPI, Packagist, NuGet, Go modules, crates.io or Maven Central in the sandbox settings and import them. A heavy API can get a sandbox group of its own, with its own packages and Dockerfile.

An example

Checkout

A checkout API reads the cart, checks stock in one database, saves the order in another, calls the payment provider with a key from your secret and emits an event for the receipt email, all in one function.

POST /api/custom-api/admin/order-totalorder-total.ts
import * as T from 'types';async function main(g: T.IAMGlobal) {    const orders = await g.sys.db.query<{ total: number }>({        instance: 'mongodb', database: 'shop', collection: 'orders',        find: { customer_id: g.req.body.customerId, status: 'paid' },        select: { total: 1 },    });    if (!orders.length) {        g.res.errors = [{ code: 404, message: 'No paid orders for this customer.' }];        return;    }    return { orders: orders.length, total: orders.reduce((sum, o) => sum + o.total, 0) };}module.exports = main;

The returned object becomes { success, statusCode, data }. g.sys.db calls throw on an error, or return the full response when you pass true as second argument.

The same g in Pythoncustomers.py
from am import Gasync def main(g: G):    table = ('mysql_8', 'inventory', 'customers')    rows = await g.sys.db.get_all(*table, limit=10, sort='-customer_id')    one = await g.sys.db.get_by_id(*table, 2)    count = await g.sys.db.count(*table, find={'country': 'IN'})    return {'first': rows[0] if rows else None, 'two': one, 'in_india': count}

Every language gets the same g, written its own way: g.sys.db.get_all in Python and Rust, g.Sys.Db.GetAll in C# and Go, getAll in TypeScript, PHP, Java and Kotlin.

Good to know

  • runOnNativeProcess skips the sandbox: the code runs on the server itself, in one runner per runtime shared by every account. It is faster and not isolated, and the runtime must be installed on the server. Use it only for code you trust.
  • The code of an API and all its hooks are written in one language and run on one runtime.
  • A call that passes its time limit is answered with a timeout error. Raise customApiTimeoutInSeconds for long work, or move it to a scheduler.

Questions

Can a custom API call other custom APIs?

Yes, over HTTP with g.sys.system.callExternalApi, also one written in another language. To share code without a call, import your utility classes of the same language.

Which language should I pick?

The one your team knows. Every language gets the same g and the same APIs, and each custom API keeps its own language, so one account can mix them.

How do I test it?

Run it from the API testing page of API Maker with any body and headers, write test cases with mocks for its logic, and read its console output in the logs.