# API Maker Documentation (full text) > Every page of https://docs.apimaker.dev in one file, in the order of the navigation. Each page starts with its title, its description and its URL. --- # API Maker Documentation > Learn API Maker step by step - install it, connect a database, get REST APIs for every table, add your own TypeScript logic, secure it with tokens and groups, and deploy with Git. Source: https://docs.apimaker.dev/index.html API Maker v3.3.0 · The framework for AI era

Connect a database. Get every API. Add your logic.

API Maker connects to your databases and gives every table secure REST APIs at once : 17 per MongoDB collection, 14 per SQL table. You add your own TypeScript in custom APIs, hooks, schedulers and events. Caching, access control, logging, monitoring and Git deployment are built in, and it all runs on your own servers.

Your first API in 10 minutes Install on a server Run on your computer How it works ## Start here
  1. Install API Maker On a blank Ubuntu server, one command installs everything : Install on a server. On your laptop, the API Maker Local Run desktop app runs API Maker and its databases in Docker.
    curl -fsSL https://apimaker.dev/v1/install.sh > install.sh && bash install.sh --default --version=latest --license_key=YOUR_KEY
  2. Connect a database Put the connection string in the default secret and add an instance. Every table of it gets its APIs right away. A new account even comes with a sample shop : instance, schemas, custom APIs and hooks to read.
  3. Call an API Get a token of the API user with the token API, send it in x-am-authorization, and read your data : Your first API walks through it with curl.
  4. Add your logic Write custom APIs, hooks, schedulers and events in TypeScript, with the global object g to reach every API of API Maker from your code.
  5. Secure and ship Groups decide which APIs, tables and fields each application may use, the security report finds the gaps, and a Git pull is the deployment.
## Find your way ⟲ How API Maker works The life of a request, the API families, the two tokens and where your code runs. ⚡ All APIs at a glance Every generated, schema, custom and system API with its method and URL, on one page. { } Table schema Types, validations, conversions, defaults, ids, encryption, relations, concurrency control. g. The global object g Request, response, database, system and cache APIs, logger and shared space in your code. </> Code examples A working snippet for every method of g.sys.db, g.sys.system and g.sys.cache. H Request headers Tokens, response case and format, encryption, caching, sandbox, tenant, language. ⛨ Security Groups, API users, auth providers, single sign-on, encrypted payloads and the security report. ⚙ Operations Caching, logs, dashboards, deployment of API Maker itself and its configuration. ☰ Cheat sheet URLs, headers, query params and the g object on one page, to keep next to your editor. ✦ Docs for AI assistants llms.txt, the Markdown twin of every page, and how to point an agent at this documentation. new ↑ Release notes What changed in every version, from v1.0.0 to v3.3.0. ↗ Benchmarks Requests per second on Linode servers from 1 to 8 vCPU, with and without caching. ## What you get with every table | | Schema APIs `/api/schema` | Generated APIs `/api/gen` | |---|---|---| | Read | get all, get all by stream, get by id, query, query by stream, count, distinct, distinct with query, aggregate (MongoDB) | the same | | Write | save single or multiple, master save, update by id, update many, replace by id (MongoDB), array operations (MongoDB), remove by id, remove by query | the same | | Applies the [schema](/v1/docs/schema/schema.html) | types, validations, conversions, defaults, ids, encryption, concurrency control | no : data passes as it is | | Filters | [find](/v1/docs/apis-all/query-params/find.html) with `$in`, `$gt`, `$like`, `$regex`, `$and`, `$or`… and [find and join](/v1/docs/apis-all/query-params/find.html#find-and-join-fields-of-related-tables) across tables | the same | | Related data | [deep populate](/v1/docs/apis-all/query-params/deep.html) across tables, databases and instances | the same | | Output | JSON, XML, YAML, text, any key case, flat objects, encrypted : [headers](/v1/docs/apis-all/header/requestHeader.html) | the same | Every one of them can run [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html), be [cached](/v1/docs/features/automatic-caching.html), be [logged](/v1/docs/logs/log-profile.html), send [WebSocket notifications](/v1/docs/pages/web-socket-event-page.html) and [emit events](/v1/docs/apis-all/events/user-created-events-api.html). ## Supported databases MongoDB · MySQL · MariaDB · PostgreSQL · Microsoft SQL Server · Oracle Database · TiDB · Percona XtraDB, in one account at the same time, with queries and [deep populate](/v1/docs/apis-all/query-params/deep.html) across them. The [connection string](/v1/docs/Database-connection-string/mongodb-connection-strings.html) pages show the format for each one. ## Watch API Maker features quick walkthrough API Maker features quick walkthrough More on the [Learn API Maker](https://www.youtube.com/playlist?list=PLfJ8NnUMaDUYNNZUX5M_KcIzn9qGCNj43) playlist. ## Stay in touch [Twitter](https://twitter.com/api_maker) · [LinkedIn](https://www.linkedin.com/company/api-maker) · [YouTube](https://www.youtube.com/@api_maker) · report a problem at [API Maker Planning](https://github.com/APIMaker-dev/API-Maker-Planning/issues) · write to [contact@apimaker.dev](mailto:contact@apimaker.dev). --- # How API Maker Works > The moving parts of API Maker in one page - the life of a request, the API families on one base URL, the two tokens and the two gates, where your TypeScript code runs, and what the servers look like. Source: https://docs.apimaker.dev/v1/docs/getting-started/how-it-works.html API Maker is a server you install, not a library you embed. Your apps call it over HTTP, it talks to your databases, and your own TypeScript runs inside it in a sandbox. This page shows the pieces once, so every other page makes sense. ## The life of a request > Diagram : The life of a request : app → Caddy → token and groups → pre hooks → the API → post hooks → reply 1. **Your app** calls a URL such as `GET /api/schema/admin/shop/main/customers?limit=10` with the token of its API user in `x-am-authorization`. 2. **Caddy** (installed by the install script) ends HTTPS and forwards to API Maker on port `38246`. WebSockets go to `38245`. 3. **Token and groups** : API Maker checks the token, finds the API user and its [groups](/v1/docs/apis-security/api-group-permission.html), and refuses the call with `401` (no valid token) or `403` (no group grants this API of this table). When the table asks for a person token too, that token is checked the same way. 4. **Pre hooks** you wrote for the instance, the database, the table or this API run in order. They can change the request, add a filter such as "only the rows of this person", answer directly, or throw. 5. **The API** runs : a generated or schema API talks to the database, a custom API runs your function in the sandbox, a system API does its job. With caching on, the answer comes from Redis when it is there. 6. **Post hooks** run with the result and can reshape it, notify, or throw. 7. **The reply** goes back in the [envelope](/v1/docs/apis-all/response-format.html) `{ success, statusCode, data | errors }`, as JSON, XML, YAML or text, with the key case you asked for in the [headers](/v1/docs/apis-all/header/requestHeader.html). WebSocket subscribers of that API or table get their notification. Every step is logged when a [log profile](/v1/docs/logs/log-profile.html) selects the API, and counted in the analytics dashboard. ## One base URL, several families > Diagram : The API families : one base URL, one token, five HTTP families and the code API Maker runs for you | Family | URL | What it is | Where it is described | |---|---|---|---| | Schema APIs | `/api/schema/////…` | 17 operations per table which apply the [schema](/v1/docs/schema/schema.html) of the table | [All APIs](/v1/docs/apis-all/overview.html) | | Generated APIs | `/api/gen////
/…` | The same 17 operations, schemaless | [All APIs](/v1/docs/apis-all/overview.html) | | Custom APIs | `/api/custom-api//` | Your TypeScript function behind the path and method you choose | [Custom APIs](/v1/docs/apis-all/custom-apis/user-created-custom-api.html) | | System APIs | `/api/system-api//` | Ready made APIs : tokens, encryption, hashing, secrets, Redis keys, cache resets, events, indexes, validations | [System APIs](/v1/docs/apis-all/system-apis/system-generated-token-api.html) | | Third party APIs | `/api/third-party////` | Bundles installed from the API Maker store (deprecated, removed in v4) | [Third party APIs](/v1/docs/apis-all/thirdParty-apis/installed-third-party-api.html) | | Not over HTTP | | [Events](/v1/docs/apis-all/events/user-created-events-api.html), [schedulers](/v1/docs/apis-all/schedulers/user-created-schedulers-api.html), [WebSocket events](/v1/docs/pages/web-socket-event-page.html), [process initializers](/v1/docs/features/process-initializers.html), [migration scripts](/v1/docs/features/database-migration.html), [hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [test cases](/v1/docs/test-cases/test-cases.html) : code API Maker runs for you | | `` is the API path of the admin or developer account which owns the item : `admin` for the first admin account. Each [developer account](/v1/docs/dev-accounts/dev-accounts.html) has its own path, its own instances, secrets and code, on the same server. ## The two tokens and the two gates { #the-two-gates } > Diagram : Two tokens, two gates : the API user (which application) and the person (which rows) - The **API user** is an application : your web app, your mobile app, a partner. It is created on the [API user permissions](/v1/docs/apis-security/api-user-permission.html) page, gets a token from the [token API](/v1/docs/apis-all/system-apis/system-generated-token-api.html) with its username and password, and sends it in `x-am-authorization`. Its [groups](/v1/docs/apis-security/api-group-permission.html) are the **API gate** : which APIs, tables and fields it may read and write. - The **person** is a row of your own users table, or a Google, Azure AD, AWS Cognito or custom identity. An [auth provider](/v1/docs/authorization/AMDB.html) turns it into a token sent in `x-am-user-authorization` (or the header of the provider). Your code reads it in `g.req.auth`, and a pre hook is the **row gate** : it narrows every request to the rows of that person. - A new account starts with an API user named `default` (password `12345`, kept in the default secret under `common.apiUserPasswords.default`) and a group `Default` which allows everything. Replace both before you go live : one small group per screen is the pattern described in [Handle role based permissions](/v1/docs/authorization/handle-role-based-permissions.html). - Public APIs (`apiAccessType: IS_PUBLIC` in the [settings](/v1/docs/settings/apiSettings.html)) skip both tokens. ## Where your code runs - **The sandbox.** Custom APIs, hooks, events, schedulers, migrations, utility classes and test cases run in Docker containers apart from the API Maker process, with a time limit (`13000` ms by default, the header `x-am-sandbox-timeout` changes it per call) and their own npm packages, installed from the [sandbox settings](/v1/docs/settings/sandboxSettings.html). Every admin account gets its own sandbox containers. - **The native process.** A custom API with `runOnNativeProcess: true` runs inside API Maker itself, for the few cases that need it : native modules, a browser with [Playwright](/v1/docs/guides/browser-automation.html), or the lowest possible latency. It is faster and less isolated. - **The global object `g`.** Your code gets one object with the request (`g.req`), the response (`g.res`), every API of API Maker (`g.sys.db`, `g.sys.system`, `g.sys.cache`), a logger and a shared space : [Global object g](/v1/docs/pre-defined-terms/global-object-g.html). - **TypeScript in the browser.** You write the code in the admin panel, with the types of API Maker (`import * as T from 'types'`) and the interfaces generated from your schemas (`import * as db from 'db-interfaces'`). Or in your own editor, with the [local client](/v1/docs/features/developer-tools.html#your-own-editor) syncing the files both ways. ## The servers > Diagram : One server after the install script : Caddy → PM2 (API Maker backend, WebSocket, admin panel) → Docker (MongoDB, Redis, sandboxes) → your databases - **API Maker** is a Node.js 22 process run by PM2. `cpuCount` in its [configuration](/v1/docs/am-resources/api-maker-configurations.html) starts one worker per CPU core. Several servers behind Caddy form a cluster : schedulers run once per cluster, WebSocket notifications reach every server through Redis. - **MongoDB** (a replica set) holds the data of API Maker itself : accounts, schemas, code, settings, logs. Your data stays in your databases. - **Redis** holds the cache, the auto increment counters and what the servers of a cluster share. - **The admin panel** is a static site served on port `4626` (or the domain you gave Caddy). It talks to the same API. - **Sandboxes** are Docker containers on the same server. - **Your databases** can be on the same server or anywhere the server can reach. The [connection strings](/v1/docs/Database-connection-string/mongodb-connection-strings.html) live in the default [secret](/v1/docs/secrets/secrets.html). The [install page](/v1/docs/getting-started/install-on-server.html) shows what the install script sets up ; the [deployment architectures](https://apimaker.dev/architectures) on the website go from one server to a global fleet. ## What happens on a save > Diagram : The stages of a write through a schema API, in order, from the request to 201 or 400 A write through a schema API is converted and checked before it reaches the database : keys not in the schema are refused, values get their types, strings are trimmed and cased, your `conversionFun` runs, fields are encrypted or hashed, ids and defaults are filled in, then the rules and your `validatorFun` decide. Everything wrong is answered at once, with `400` and one entry per problem, and nothing is saved. [Table schema](/v1/docs/schema/schema.html) describes every option. ## Everything is in Git Schemas, settings, custom APIs, hooks, events, schedulers, utility classes, migrations, test cases and the security report are files in a Git repository. The admin panel commits and pushes them ; a Git pull on another server is the deployment. Secrets and notes never go to Git. See [Git integration](/v1/docs/Git/git.html) and [Deploy API Maker](/v1/docs/features/deploy-api-maker.html) for the upgrade of API Maker itself. Next : your first API → All APIs at a glance Install on a server --- # Install API Maker on a Server > Put API Maker on your own servers - deploy a whole architecture from your computer with API Maker Local Run, or set up one Ubuntu server with the install script. The reference architectures, the downloads, the installer flags, a second server and the manual setup. Source: https://docs.apimaker.dev/v1/docs/getting-started/install-on-server.html API Maker runs on your own servers, at any provider : a VPS, a dedicated machine or a virtual machine in your data centre. There are two ways to set it up, and both produce the same installation : API Maker run by PM2, its MongoDB and Redis in Docker, and a reverse proxy with HTTPS in front. Recommended⌘API Maker Local RunThe desktop app for macOS, Windows and Linux. Design the architecture on a diagram and deploy it from your computer over SSH, from one server to a load balanced cluster with replicated databases. The same app runs API Maker on your computer. $The install scriptOne command in the terminal of a fresh Ubuntu server installs everything on that server and prints the admin panel URL. Run it again on more servers to build a cluster. ## Deploy with API Maker Local Run **API Maker Local Run** is the one application for every place API Maker runs. Its **Local** mode runs API Maker and its databases on your computer for development ([Run on your computer](/v1/docs/getting-started/local-run.html)). Its **Cloud** mode takes servers you rent anywhere and turns them into a production environment : add a server with its SSH sign-in, choose a layout, and deploy. Everything happens over SSH from your computer, with no agent on the servers and no third party service in between. | | | |---|---| | Sets up | API Maker run by PM2, its MongoDB and Redis, a reverse proxy (Caddy, Ferron or Pingap) with Let's Encrypt certificates renewed on their own, and the firewall. | | Your databases | MongoDB replica sets, PostgreSQL with streaming replicas, MySQL and Percona with replicas, MariaDB replicas or a Galera cluster, SQL Server, Oracle and TiDB. Passwords are rotated and databases move between servers with their data. | | Environments | Production, staging, development… each one with its own servers, version and license key, in one project. | | Day two | New versions deployed with rolling updates behind the load balancer, live logs and health of every server, a full uninstall. | | Servers | Ubuntu 24.04 LTS, x86-64 or ARM64 ; 1 core, 900 MB of memory and 10 GB of disk at least (4 GB and 20 GB recommended). Sign in as root or a sudo user, with a password, a key or your SSH agent. | | Security | SSH host keys are pinned on first connection ; credentials are stored encrypted on your computer, with the key in the keychain of the operating system. | ### Download ⌘macOS 13 Ventura or later · Apple silicon and Intel UniversalDisk image .dmgZip .zip Open the .dmg and drag the app to Applications. ⊞Windows Windows 10 and 11 x64Installer .exePortable .zip ARM64Installer .exePortable .zip Installs for your user, no administrator rights needed. >_Linux Ubuntu 22.04+, Debian 12+, Fedora 39+ x64Debian, Ubuntu .debAny distro .tar.gz ARM64Debian, Ubuntu .debAny distro .tar.gz Needs GTK 3 and WebKitGTK 4.1 ; the .deb installs them. Version 1.0.0 · the SHA-256 checksum of every file and the install steps are on apimaker.dev/download Open the app, switch to the **Cloud** tab, add your servers, create a project and an environment, then press **Deploy**. Local mode needs Docker on your computer ; Cloud mode only needs SSH access to your servers. ## Choose an architecture { #architectures } Start on one server and grow without changing your APIs : API Maker keeps its state in MongoDB and Redis, so adding servers is a matter of configuration, not code. The website describes [13 reference architectures](https://apimaker.dev/architectures), each with a live diagram, the size of every server and the configuration to copy. API Maker Local Run deploys the everyday ones straight from its diagram : | Architecture | What it is | In API Maker Local Run | |---|---|---| | [All-in-one server](https://apimaker.dev/architectures/single-server) | API Maker, its MongoDB, Redis and your database on a single VPS. | The **All in one** layout. | | [Separate database server](https://apimaker.dev/architectures/separate-database) | API Maker on one VPS, your databases on servers of their own. | The **App + data** layout. | | [Dedicated data tier](https://apimaker.dev/architectures/dedicated-data-tier) | A stateless API server, with the MongoDB and Redis of API Maker on servers of their own. | The **Dedicated databases** layout. | | [Environments with Git](https://apimaker.dev/architectures/environments) | DEV, QA, UAT and PROD, each with its own servers and data. | One environment per stage, in one project. | | [Load-balanced API servers](https://apimaker.dev/architectures/load-balanced) | Several API Maker servers behind a load balancer, sharing one data tier. | The **Load balanced** layout ; add API Maker servers at any time. | | [Real-time events across servers](https://apimaker.dev/architectures/realtime-websockets) | WebSocket clients on any server, changes made on any server. | Any layout with several API Maker servers : they share Redis. | | [Clustered Redis and databases](https://apimaker.dev/architectures/clustered-data) | No single data node to lose. | API Maker's MongoDB as a replica set, its Redis as a cluster, your databases replicated. | | [High availability](https://apimaker.dev/architectures/high-availability) | A spare for every tier. | Several gateways, API Maker servers and database replicas ; the floating IP is set at your provider. | [Many projects on one platform](https://apimaker.dev/architectures/multi-project) and [a database per customer](https://apimaker.dev/architectures/multi-tenant) are features of API Maker itself and work on any of these layouts. [Multi-cloud](https://apimaker.dev/architectures/multi-cloud), [geo-routing](https://apimaker.dev/architectures/geo-routing) and the [global enterprise platform](https://apimaker.dev/architectures/global-enterprise) combine the same pieces across providers and regions : their pages show how. ## Install with one command On a fresh Ubuntu server, one command installs everything and starts API Maker. The installer prints what it does at every step, and ends with the URLs, the default users and the commands to start and stop. > Diagram : One server after the install script : Caddy → PM2 (API Maker backend, WebSocket, admin panel) → Docker (MongoDB, Redis, sandboxes) → your databases | | | |---|---| | Server | Ubuntu 22.04, 24.04 or 26.04 (20.04 works too), root user, 20 GB of disk or more, a CPU with AVX (MongoDB needs it). 1 CPU and 1 GB of RAM run it ; the installer adds swap. | | Command | `curl -fsSL https://apimaker.dev/v1/install.sh > install.sh && bash install.sh --default --version=latest --license_key=YOUR_KEY` | | Installs | Node.js 22 (with Volta) and PM2, Docker, MongoDB and Redis as containers, the Oracle client, Caddy for HTTPS, and API Maker itself in `~/projects/sava_api_maker`. | | Writes | `~/config/.env` (the [configuration](/v1/docs/am-resources/api-maker-configurations.html)), `~/config/Caddyfile`, the license file. Existing files are backed up and reused. | | Prints | The admin panel URL, the API and WebSocket URLs, the default users, the start and stop commands. | ### 1. Run the installer ```sh curl -fsSL https://apimaker.dev/v1/install.sh > install.sh && bash install.sh --default --version=latest --license_key=YOUR_KEY ``` | Flag | Meaning | |---|---| | `--license_key=YOUR_KEY` | The license key of your account (write to [contact@apimaker.dev](mailto:contact@apimaker.dev) for one). It is written to `license.txt` and bound to the server. | | `--version=latest` | The version of API Maker to install, or an exact one like `--version=3.3.0`. Run it again to upgrade or to reinstall. | | `--default` | No questions : Caddy without SSL on the IP of the server, MongoDB and Redis in Docker with generated passwords, a primary server. Leave it out to answer the questions below. | | `--mongoVersion=8.0.10` | The MongoDB image to run (`8.0.10` by default). | ### 2. What it asks without `--default` 1. **Do you want to install caddy server?** Caddy is the reverse proxy in front of API Maker : it serves the admin panel and the APIs on port 80, and HTTPS when you say yes to **Do you want to install SSL?** and give the host names of the API, the WebSocket server and the admin panel. 2. **Are you setting up server for QA | UAT | PROD environment?** 3. The swap size, when the server has less than the minimum. 4. **Primary** or **secondary** server : the first server of a cluster or another one attached to it. 5. On a primary server : the MongoDB and Redis to use (the containers it creates, or your own connection strings), and the API Maker settings (ports, passwords). ### 3. What it does 1. Checks the server : Ubuntu version, root, disk, AVX, glibc, swap. 2. Downloads API Maker from npm into `~/projects/sava_api_maker` and installs its dependencies. An earlier `node_modules` is reused when the version allows it. 3. Installs Docker if needed, then starts **MongoDB** (container `mongodb_api_maker`, a one node replica set, data in `~/docker-data`) and **Redis** (container `redis_api_maker`). 4. Installs the Oracle Instant Client and the D2 diagram library. 5. Writes `~/config/.env` : the connection strings, `passJWT`, `passDBEncryptDecrypt`, the ports (`38246` for the API, `38245` for the WebSocket server) and `BE_HOST_PORT`, then copies the values into the admin panel. 6. Starts the backend and the admin panel with **PM2**, and registers PM2 to start them again after a reboot. 7. Installs **Caddy**, writes `~/config/Caddyfile` and starts it : the admin panel on port 80 (or 443 with SSL), `/api` and the WebSocket server proxied to API Maker. 8. Registers the MAC address of the server for the license, and prints the summary. ```text title="The end of the output" ———— # Important URLs ———— Frontend Admin Panel http:// Backend APIs http:///api Backend WebSocket endpoint ws://:8081 ———— # Default User Credentials ———— root@root.com R00t_123456789 admin@admin.com Admin_123456789 ———— # Commands to start/stop API Maker processes ———— [START] : … [STOP] : … ``` ### 4. First sign in - Open the admin panel URL, sign in as `admin@admin.com` and change both passwords : the root user in **Root Settings**, the admin user in its profile. - The admin account comes with a [sample shop](/v1/docs/getting-started/first-api.html) : try its APIs, then add your own [instance](/v1/docs/apis-all/overview.html#instances). ### A second server - Run the same command on the next server and answer **secondary** : it installs API Maker only, and points it to the MongoDB and Redis of the primary server. - The installer then prints the two `reverse_proxy` lines to add to the Caddyfile of the primary server (`127.0.0.1:38246` and `:38245` become a list of servers), and the commands to restart Caddy. Every server of the cluster shows on the [Server Node Configurations](/v1/docs/dashboard/node-dashboard.html) dashboard, and [Deploy API Maker](/v1/docs/features/deploy-api-maker.html) upgrades them all. ## By hand The pieces are ordinary : a Node.js process (`node main.js` in the package, or PM2), MongoDB as a replica set, Redis, and any reverse proxy for TLS. The [configuration](/v1/docs/am-resources/api-maker-configurations.html) page lists every setting of `.env` ; the MongoDB connection string goes in `am__mongo_db_connection`, Redis in `am__redisInternal` and `am__redisExternal`. The blocks below set up each piece the way the installer does, on Ubuntu as root. ??? example "Node.js 22 with NVM" API Maker needs **Node.js 22 or newer** (`engines` of its package). ```bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" nvm install 22 nvm alias default 22 node --version ``` ??? example "Docker with Docker Compose" From the official apt repository of Docker, the commands the installer runs. They work on Ubuntu 20.04 to 26.04 ; a release too new for Docker's repository falls back to the one of 24.04 (`noble`). ```bash for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do apt-get remove -y $pkg; done apt-get update apt-get install -y ca-certificates curl gnupg install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME") curl -fsL -o /dev/null "https://download.docker.com/linux/ubuntu/dists/$CODENAME/Release" || CODENAME=noble echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $CODENAME stable" > /etc/apt/sources.list.d/docker.list apt-get update apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker compose version ``` ??? example "MongoDB as a one node replica set" API Maker needs MongoDB as a replica set : transactions only work there. A replica set with authentication needs a key file. ```bash mkdir -p ~/config && cd ~/config openssl rand -base64 756 > keyfile chmod 600 keyfile && chown 999:999 keyfile ``` ```yaml title="~/config/mongodb_api_maker.yml" services: am_mongodb: image: mongo:8.0.10 restart: always container_name: mongodb_api_maker command: [ "--replSet", "rs0", "--bind_ip_all", "--port", "27017", "--keyFile", "/opt/keyfile" ] ports: - 38248:27017 environment: - MONGO_INITDB_ROOT_USERNAME=api_maker_user # 👈 your user - MONGO_INITDB_ROOT_PASSWORD=CHANGE_ME # 👈 your password - MONGO_REPLICA_SET_NAME=rs0 healthcheck: # Starts the replica set on the first run. test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'127.0.0.1:27017'}]}) }" | mongosh -u "api_maker_user" -p "CHANGE_ME" --port 27017 --quiet interval: 10s timeout: 30s start_period: 5s retries: 10 volumes: - ~/docker-data/mongodb_api_maker/data:/data/db - ~/docker-data/mongodb_api_maker/configdb:/data/configdb - ./keyfile:/opt/keyfile ``` ```bash docker compose --project-name api_maker -f ~/config/mongodb_api_maker.yml up -d ``` ```text title="am__mongo_db_connection" mongodb://api_maker_user:CHANGE_ME@127.0.0.1:38248/api_maker_db?authSource=admin&replicaSet=rs0&directConnection=true ``` MongoDB Compass, Studio 3T or any other client connects with the same string. ??? example "Redis" ```yaml title="~/config/redis_api_maker.yml" services: redis_api_maker: image: 'redis:7.0.5-alpine' restart: always container_name: redis_api_maker ports: - '7479:6379' command: redis-server --loglevel warning --requirepass CHANGE_ME # 👈 your password volumes: - ~/docker-data/redis_api_maker:/data logging: driver: "json-file" options: max-size: "5m" max-file: "3" ``` ```bash docker compose --project-name api_maker -f ~/config/redis_api_maker.yml up -d ``` ```text title="am__redisInternal and am__redisExternal" {"nodes": [{port: 7479, host: "127.0.0.1", pass: "CHANGE_ME"}]} ``` ## Related - [Run on your computer](/v1/docs/getting-started/local-run.html) : API Maker Local Run in detail · [Local setup](/v1/docs/getting-started/local-setup.html) · [How it works](/v1/docs/getting-started/how-it-works.html) · [API Maker configuration](/v1/docs/am-resources/api-maker-configurations.html) · [Deployment architectures](https://apimaker.dev/architectures) Next : your first API → Local setup --- # Local Setup of API Maker > Run API Maker on your own machine for development with API Maker Local Run, the desktop app for macOS, Windows and Linux - with the download of every platform. Source: https://docs.apimaker.dev/v1/docs/getting-started/local-setup.html A local API Maker is the place to build and test APIs before they reach a server : your own admin panel, your own databases, and your code in your editor. **API Maker Local Run** sets it up for you : a desktop app for macOS, Windows and Linux which needs Docker on your computer, and the same app later deploys to your servers. ## API Maker Local Run The desktop app installs API Maker from npm on a Node.js runtime it downloads and verifies, starts MongoDB and Redis in Docker, runs the admin panel and shows you the URLs and the sign-in details. Pick a folder, press **Start**, and your API Maker is running. ⌘macOS 13 Ventura or later · Apple silicon and Intel UniversalDisk image .dmgZip .zip Open the .dmg and drag the app to Applications. ⊞Windows Windows 10 and 11 x64Installer .exePortable .zip ARM64Installer .exePortable .zip Installs for your user, no administrator rights needed. >_Linux Ubuntu 22.04+, Debian 12+, Fedora 39+ x64Debian, Ubuntu .debAny distro .tar.gz ARM64Debian, Ubuntu .debAny distro .tar.gz Needs GTK 3 and WebKitGTK 4.1 ; the .deb installs them. Version 1.0.0 · the SHA-256 checksum of every file and the install steps are on apimaker.dev/download - **Databases in a few clicks** : MongoDB, PostgreSQL, MySQL, MariaDB, SQL Server, Oracle, TiDB and Percona Server, several of each, with the API Maker instance created in one click. - **Your code in your editor** : the Local Client page keeps a folder in sync with API Maker both ways, for VS Code, WebStorm, Zed or any editor. - **Projects side by side** : every project runs on its own ports, in its own folder. - **Ready for production** : the **Cloud** tab of the same app deploys API Maker to your servers. See [Install on a server](/v1/docs/getting-started/install-on-server.html#deploy-with-api-maker-local-run). [Run on your computer](/v1/docs/getting-started/local-run.html) describes the app in detail : the first run, the ports, the folder, the license key and the Local Client. Next : API Maker Local Run in detail → Your first API --- # Run API Maker on Your Computer > API Maker Local Run is a desktop app for macOS, Windows and Linux which runs API Maker and its databases in Docker on your machine, adds databases in a few clicks, syncs your code with your editor, and can install API Maker on your own servers in its Cloud mode. Source: https://docs.apimaker.dev/v1/docs/getting-started/local-run.html **API Maker Local Run** is a desktop app which runs API Maker on your machine. Pick a folder and a version, click Start, and it installs API Maker from npm on a Node.js it downloads, starts the MongoDB and Redis API Maker needs in Docker, runs the admin panel and shows you the URLs and the sign-in details. Download it from [apimaker.dev/download](https://apimaker.dev/download). > Diagram : API Maker Local Run : one folder, Docker for the databases, API Maker and its admin panel on your computer ## What it does - **Installs and starts everything** : API Maker from npm, on a Node.js runtime it downloads and verifies itself ; MongoDB as a replica set and Redis in Docker ; the admin panel. - **Adds databases in a few clicks** : MongoDB, PostgreSQL, MySQL, MariaDB, SQL Server, Oracle, TiDB and Percona Server, several of each if you want, every one with its connection string shown. **Create instance** puts the credentials in the default secret and creates the instance in API Maker. - **Runs several projects at once**, each in its own folder and on its own ports. - **Keeps a folder in sync with your editor** through the local client (see below). - **Stops everything it started** when you close it, and cleans up after a crash. - **Deletes a project** with everything it created, after you confirm by typing its name. ## Requirements | | | |---|---| | Docker | Docker Desktop, OrbStack, Rancher Desktop, Colima or Docker Engine, with Docker Compose v2 | | macOS | 13 Ventura or later, Intel or Apple silicon | | Windows | 10 or 11, x64 or ARM64 | | Linux | x64 or ARM64 with GTK 3 and WebKitGTK 4.1 (Ubuntu 22.04+, Debian 12+, Fedora 39+…) | | Memory | about 4 GB free for API Maker, MongoDB and Redis ; each extra database needs more | ## First run
  1. Pick a folder An empty folder becomes a new workspace ; a folder which already has one is loaded from its .api_maker_local_run_state file. Recent folders are listed on the welcome screen.
  2. Set it up Choose the project name, the API Maker version (the list comes from npm), the license key and the ports. Ports already in use are flagged before anything starts.
  3. Start The progress shows step by step, with the logs of every service next to it. When it is done, the admin panel opens in your browser.
  4. Add a database On the Databases page, pick the engine and version, set the port, password, user and database name, and click Create instance. The instance is ready in API Maker with its connection string in the default secret.
  5. Call your first API Follow Your first API with the URL the app shows.
## Ports and folder The app keeps everything in the folder you picked : the API Maker install, its configuration (`config/.env`, `config/docker-compose.yml`), the logs and the data files of every database (`docker-data/…`). Docker only runs the containers ; no data lives inside them. | Service | Default port | |---|---| | API Maker backend | 38246 | | API Maker WebSocket | 38245 | | Admin panel | 4626 | | Internal MongoDB | 38248 | | Internal Redis | 7479 | | Extra databases | MongoDB 27018 · PostgreSQL 5433 · MySQL 3307 · MariaDB 3308 · Percona 3309 · TiDB 4001 · SQL Server 14330 · Oracle 1522 | New projects get free ports. Services bind to `127.0.0.1` on macOS and Windows and to `0.0.0.0` on Linux ; the bind address is a project setting. ## The license key API Maker needs a license key. The local run key `____your_license_key____` is accepted by API Maker versions which support local runs : with it, API Maker only answers requests from this computer and from private networks (loopback, `10/8`, `172.16/12`, `192.168/16`, link local, CGNAT and IPv6 unique local addresses). To serve other clients, set your own key in **Project Settings** or in the admin panel. For a key, write to [contact@apimaker.dev](mailto:contact@apimaker.dev). ## Your code in your editor : the Local Client The **Local Client** page of a project keeps a folder of your computer in sync with API Maker, both ways : edit custom APIs, utility classes, schemas, schedulers, hooks and test cases in VS Code, WebStorm, Zed or any editor, and what you save in the admin panel lands on the disk. It runs the local client which ships with API Maker (`npm run start` in the folder), started and watched by the app. - **Where the code is kept** : a Git repository on this computer (recommended, made by the app and served to API Maker on `127.0.0.1`), GitHub, GitLab or another host with a personal access token, or just a folder without Git. - Every folder has a card with the state of the sync, the Git branch, and **Open in** buttons for the editors found on your computer. - The folder is an ordinary local client folder : `npm run test` runs the [test cases](/v1/docs/test-cases/test-cases.html) of the account from the terminal and writes their coverage for your IDE, and an AI agent working in the folder finds the [security report](/v1/docs/apis-security/api-security-report.html) and every item of the account as files. See [Developer tools](/v1/docs/features/developer-tools.html#your-own-editor) for the local client on a server. ## Cloud mode The **Cloud** tab installs API Maker on servers you rent anywhere, and everything it needs : its MongoDB and Redis, a reverse proxy (Caddy, Ferron or Pingap), your databases with their replication and the firewall. Everything happens over SSH from your computer : no agent, no third party service. - **Servers** : Ubuntu 24.04 LTS, x86-64 or ARM64, 1 core, 900 MB of memory and 10 GB of disk at least (4 GB and 20 GB recommended). Sign in as root or a sudo user with a password, a key or your SSH agent. Credentials are stored encrypted on your computer, the key in the keychain of the operating system. - **Environments and architectures** : a project groups environments (production, staging…), each one an API Maker with its own servers, version and license key. Start from a layout (all in one, app + data, dedicated databases, load balanced) and drag components between servers on the diagram. - **Databases** : MongoDB replica sets, PostgreSQL, MySQL and MariaDB (Galera), SQL Server and Oracle, with their replication, password rotation and moves between servers with their data. - **Day two** : deploy new versions, rolling updates behind the load balancer, live logs and health of every server, and a full uninstall. [Install on a server](/v1/docs/getting-started/install-on-server.html#architectures) maps the [reference architectures](https://apimaker.dev/architectures) of the website to the layouts of Cloud mode. The install script on the same page sets up one server from its own terminal instead. Next : your first API → Install on a server --- # Your First API in 10 Minutes > A hands-on start with the sample shop every new API Maker account has - connect MongoDB, get a token, save a category and a product through the schema APIs, read them back with filters and deep populate, and see a validation error. Source: https://docs.apimaker.dev/v1/docs/getting-started/first-api.html Every new admin or developer account of API Maker comes with a **sample shop** : an instance named `mongodb`, a database `shop` with the schemas of `categories`, `customers`, `products`, `orders` and `orderItems`, custom APIs under `/default/…`, hooks, an event, a migration script and a utility class. This page uses it, so you only need a MongoDB to point it at. !!! tip "Nothing installed yet ?" [Install on a server](/v1/docs/getting-started/install-on-server.html) with one command, or [run API Maker on your computer](/v1/docs/getting-started/local-run.html) with the desktop app. Both print the URL of the admin panel and the sign-in details. The defaults of the install script are `admin@admin.com` / `Admin_123456789`. In the commands below, `$AM` is the URL of your API Maker (`http://127.0.0.1:38246`, or the domain of your server) and `admin` is the user path of the account. ## 1. Point the sample instance at a MongoDB
  1. Open the default secret Admin panel → API Security → Secret Management → the secret named Default. It is TypeScript : a common object with keys, connection strings and the passwords of API users.
  2. Set common.connectionString.mongodb The sample instance reads its connection string from that path. Paste the string of any MongoDB you can reach (the MongoDB of API Maker Local Run, a MongoDB Atlas cluster, the one the install script created : it is in /root/config/.env). Save.
    connectionString: {
        mongodb: 'mongodb://user:password@127.0.0.1:27017/?authSource=admin&replicaSet=rs0&directConnection=true',
        // ...
    },
  3. Check the instance API Info → Instance API (DB API) → instance mongodb → Test Instance. The database shop appears in the list after the first save below : MongoDB creates databases and collections on first write.
## 2. Get a token The account has an API user named `default`. Its password is the value of `common.apiUserPasswords.default` in the secret : `12345` until you change it. ```bash curl -s -X POST "$AM/api/system-api/admin/token" \ -H "Content-Type: application/json" \ -d '{ "u": "default", "p": "12345" }' ``` ```json { "success": true, "statusCode": 200, "data": { "token": "eyJhbGciOi…", "refresh_token": "eyJhbGciOi…", "expires_in": 259200 } } ``` Keep the token in a variable : `TOKEN=eyJhbGciOi…`. Every call below sends it in `x-am-authorization`. The group `Default` of this API user allows every API, which is fine for a first try and wrong for production : see [Handle role based permissions](/v1/docs/authorization/handle-role-based-permissions.html). ## 3. Save a category, then a product The product schema needs a `category` (a relation to `categories._id`), a `currency` from `IN`, `US` or `GB`, and a `price_cents`. So the category comes first. ```bash curl -s -X POST "$AM/api/schema/admin/mongodb/shop/categories/save-single-or-multiple" \ -H "Content-Type: application/json" -H "x-am-authorization: $TOKEN" \ -d '{ "name": "Accessories" }' ``` ```json { "success": true, "statusCode": 201, "data": { "_id": "66fa1c2d9b1e4a0012a3c001", "name": "Accessories", "slug": "accessories" } } ``` The `_id` was generated by API Maker (`isAutoGenerateByAM`) and the `slug` by the `conversionFun` of the schema. Now the product : ```bash curl -s -X POST "$AM/api/schema/admin/mongodb/shop/products/save-single-or-multiple" \ -H "Content-Type: application/json" -H "x-am-authorization: $TOKEN" \ -d '{ "name": " Wireless Mouse ", "currency": "in", "price_cents": 129900, "category": "66fa1c2d9b1e4a0012a3c001" }' ``` ```json { "success": true, "statusCode": 201, "data": { "_id": "66fa1c9a9b1e4a0012a3c002", "public_id": "01J8Z3C9F2X4Q7N8V1M6K5R0TA", "product_no": 1000, "name": "Wireless Mouse", "slug": "wireless-mouse", "status": "DRAFT", "currency": "IN", "price_cents": 129900, "is_active": true, "created_at": "2026-09-30T10:15:22.418Z", "updated_at": "2026-09-30T10:15:22.418Z", "category": "66fa1c2d9b1e4a0012a3c001" } } ``` Look at what the schema did : the name was trimmed, `currency` upper-cased, `status` and `is_active` got their defaults, `created_at` its default function, `product_no` the next number of its auto increment, `public_id` a ULID, `slug` a value computed from the name. That is the [schema pipeline](/v1/docs/schema/schema.html) ; the generated APIs under `/api/gen` would have stored the body as it came. ## 4. Read it back Get all products, only some fields, with the category populated from its relation : ```bash curl -s "$AM/api/schema/admin/mongodb/shop/products?select=name,slug,price_cents,category&deep=[{s_key:'category'}]" \ -H "x-am-authorization: $TOKEN" ``` ```json { "success": true, "statusCode": 200, "data": [ { "_id": "66fa1c9a9b1e4a0012a3c002", "name": "Wireless Mouse", "slug": "wireless-mouse", "price_cents": 129900, "category": { "_id": "66fa1c2d9b1e4a0012a3c001", "name": "Accessories", "slug": "accessories" } } ] } ``` `deep` only needed the source key : the target table and key come from the schema. The same filters work as query params on get all, or in the body of the query API : ```bash curl -s -X POST "$AM/api/schema/admin/mongodb/shop/products/query" \ -H "Content-Type: application/json" -H "x-am-authorization: $TOKEN" \ -d '{ "find": { "status": "DRAFT", "price_cents": { "$gte": 100000 } }, "sort": "-created_at", "limit": 10, "getTotalCount": true }' ``` ## 5. See a refusal Send a product that breaks three rules at once : ```bash curl -s -X POST "$AM/api/schema/admin/mongodb/shop/products/save-single-or-multiple" \ -H "Content-Type: application/json" -H "x-am-authorization: $TOKEN" \ -d '{ "name": "M", "currency": "EUR", "price_cents": -5, "category": "66fa1c2d9b1e4a0012a3c001", "discount_pct": 10 }' ``` ```json { "success": false, "statusCode": 400, "errors": [ { "type": "minLength", "field": "name", "message": "Property 'name' should have minimum length of '2'.", "code": 400 }, { "type": "enumValidation", "field": "currency", "message": "Property 'currency' should have any value from [IN, US, GB].", "code": 400 }, { "type": "min", "field": "price_cents", "message": "Please provide minimum '0' for 'price_cents' field.", "code": 400 }, { "type": "invalidValue", "field": "discount_pct", "message": "A discount requires approved_by.", "code": 400 } ] } ``` Every problem is reported in one reply, including the message thrown by the `validatorFun` of `discount_pct`, and nothing was saved. The messages can be translated per caller with [internationalization](/v1/docs/i18/i18.html). ## 6. Try the rest of the sample - **API testing page** (`API Info → API Testing`) : pick the instance, the table and the API, get a sample payload, send it and read the response, then copy the call as code in 21 languages. - **Custom APIs** under `API Info → Custom API` : `/default/deep-populate-orders`, `/default/find-join-products`, `/default/caching-example`, `/default/ws-notify`, `/default/login`… Open one to read a complete, commented example of the feature. They have the access type `NO_ACCESS`, so they run from the testing page and from other code, not from outside, except the login and captcha ones. - **Hooks** : the instance, database and collection hooks of the sample show how a person is kept to their own rows. - **Schemas** of the five collections show every schema feature in use. ## Where to go next All APIs at a glanceEvery operation of a table, with its method and URL. Table schemaWrite the schema of your own tables. Custom APIsYour first TypeScript function behind a URL. Role based permissionsReplace the Default group before going live. --- # API Maker Cheat Sheet > The URLs, headers, query params, schema options and g object of API Maker on one page - keep it next to your editor. Source: https://docs.apimaker.dev/v1/docs/cheat-sheet.html ## URLs ```text /api/schema////
[/operation] schema APIs (apply the schema) /api/gen////
[/operation] generated APIs (schemaless) /api/custom-api// custom APIs /api/system-api// system APIs (always POST) /api/third-party//// third party APIs (deprecated) :: a tenant of a multi-tenant instance ``` | Operation | Method and path suffix | |---|---| | Get all · get all by stream | `GET /` · `GET /stream` | | Get by id | `GET /get-by-id/:id[/:primaryKey]` | | Save · master save | `POST /save-single-or-multiple` · `POST /master-save` | | Update by id · update many · replace by id (MongoDB) | `PUT /update-by-id/:id[/:primaryKey]` · `PUT /update-many` · `PUT /replace-by-id/:id[/:primaryKey]` | | Array operations (MongoDB) | `PUT /array-operations` | | Remove by id · remove by query | `DELETE /:id[/:primaryKey]` · `POST /query/delete` | | Query · query by stream | `POST /query` · `POST /query-stream` | | Count · aggregate (MongoDB) | `POST /count` · `POST /aggregate` | | Distinct · distinct with query | `GET /distinct/:field[/:order]` · `POST /distinct/:field[/:order]` | ## Headers | Header | Values | |---|---| | `x-am-authorization` | token of the API user (the application) | | `x-am-user-authorization` · `x-google-authorization` · `x-azure-authorization` · `x-aws-authorization` · `x-custom-authorization` | token of the person, from an auth provider | | `x-am-response-case` | `noChange` `camelCase` `capitalCase` `constantCase` `dotCase` `headerCase` `noCase` `paramCase` `pascalCase` `pathCase` `sentenceCase` `snakeCase` | | `x-am-content-type-response` | `application/json` `text/xml` `text/yaml` `text/plain` `text/html` `application/octet-stream` | | `x-am-response-object-type` | `no_action` `make_flat` | | `x-am-meta` | `true` : execution time, plan, groups, sandbox in `meta` | | `x-am-cache-control` | `no_action` `reset_cache` | | `x-am-get-encrypted-data` | `no_encryption` `get_only_encryption` `get_data_and_encryption` | | `x-am-encrypted-payload` | `true` when the body is `{ dataEncFE }` | | `x-am-internationalization` | name of a language of i18N Management | | `x-am-tenant-username` | tenant of a multi-tenant instance | | `x-am-sandbox-timeout` | milliseconds, default 13000 | | `x-am-run-in-sandbox` | `0` any, `1` one sandbox, `n` first n | | `x-am-secret` | id of another secret | | `x-no-compression` | `true` | ## Query params (get all, get by id, streams ; in the body for the query APIs) ```text ?find={status:'ACTIVE',price:{$gte:100}} JSON5 : $eq $ne $gt $gte $lt $lte $in $nin $and $or $not $like $regex $isNull ?select=name,price ?select=-password include, or exclude with - ?sort=-created_at,name - for descending ?skip=20&limit=10 paging ?getTotalCount=true totalCount in the reply ?deep=[{s_key:'category'}] related rows, from the schema relation or with t_instance t_db t_col t_key find select isMultiple limit skip sort deep ?customer_id=42 ?price>=100 ?name=Bob,Alice any column as a filter ?find={'owner.customer_id':42} find and join : a field of a related table ?upsert=true ?returnDocument=before update by id ?throwErrorIfRecordNotFound=true update by id, replace by id, remove by id : 400 instead of null when the id is unknown ``` ## Response ```json { "success": true, "statusCode": 200, "data": [], "totalCount": 5, "warnings": [], "logs": [], "meta": {} } { "success": false, "statusCode": 400, "errors": [ { "type": "required", "field": "name", "message": "…", "code": 400 } ] } ``` ## Schema ```typescript name: { __type: EType.string, // string number boolean date objectId file files, [EType.string] for arrays, a nested object for objects isPrimaryKey: true, isAutoGenerateByAM: { valueGeneratorType: 'ObjectID' | 'GUID_UUID' | 'ULID' | 'ShortUUID' }, isAutoIncrementByAM: { start: 1000, step: 1 }, // or true isAutoIncrementByDB: true, isConcurrencyControlField: true, // version field of optimistic concurrency control validations: { required, min, max, minLength, maxLength, email, enum: [], validatorFun: (value, all) => true }, conversions: { trim, trimStart, trimEnd, toLowerCase, toUpperCase, conversionFun: (value, all) => value, encryption, hashing, defaults: { defaultValue, defaultFun, shouldReplaceNullWithDefault, shouldReplaceEmptyStringWithDefault } }, instance: 'x', database: 'y', collection: 'z', column: '_id', // relation for deep, find and join, master save isVirtualField: true, s_columnVirtualLinker: '_id', t_columnVirtualLinker: 'product_id', // one to many } ``` ## The global object g ```typescript g.req.body g.req.query g.req.params g.req.headers g.req.eventData g.req.auth.authAMUser .authAMDB .authGoogle .authAzure .authAWS .authCustom g.res.output g.res.statusCode g.res.contentType g.res.errors g.res.warnings g.res.shared g.sys.db.getAll({ instance, database, collection, queryParams, headers }) getAllByStream getById saveSingleOrMultiple masterSave arrayOperations updateById updateMany replaceById removeById query queryByStream removeByQuery aggregate count distinct distinctQuery g.sys.db.gen.getAllGen(…) the same, schemaless, with the Gen suffix g.sys.system.encrypt decrypt hash getToken callExternalApi executeQuery getSecret getTableMeta createIndexes dropIndexes getIndexes emitEvent emitEventWS isValidDataForTable isValidDataForCustomAPI isValidDataForThirdPartyAPI multiTenantInstanceUpdated g.sys.cache.getKey setKey removeKey resetCacheDB resetCacheCustomApis resetCacheSystemApis resetCacheThirdPartyApis g.logger.debug log info warn error g.shared.x = … shared between hooks and the API await g.sys.db.count({ … }, true) second argument true : the whole envelope, no throw import * as utils from 'utils/MyClass' utility classes ``` ## Where things are in the admin panel | | | |---|---| | Instances, databases, tables, schemas, APIs | API Info → Instance API (DB API) | | Custom APIs · Events · WebSocket events · Schedulers · Process initializers · API testing · Test cases | API Info | | Secrets · Groups · API users · Auth providers · Security report · Vulnerabilities | API Security | | i18N · Log profile · Log explorer · Sandbox settings · Utility classes · Database migration · Code finder · Auto increments · Generated interfaces · Swagger · Deploy API Maker | Utility | | Analytics · Server nodes · Redis · ER diagram | Dashboard | --- # API Maker Docs for AI Assistants > How to give ChatGPT, Claude, Cursor, Copilot or any agent the API Maker documentation - llms.txt and llms-full.txt, the Markdown twin of every page, the local client folder, and the security report an assistant can act on. Source: https://docs.apimaker.dev/v1/docs/ai/llms.html API Maker is built for the AI era, and so is this documentation. Every page is available as plain Markdown, the whole site fits in one text file, and an assistant working in your project folder finds the code and the security report of your account as files. ## Files an assistant can read | What | URL | Use it for | |---|---|---| | Index of the documentation | [`https://docs.apimaker.dev/llms.txt`](https://docs.apimaker.dev/llms.txt) | One page per line with its title, description and Markdown URL, grouped like the navigation. Give it to an agent so it fetches only the pages it needs. | | The whole documentation | [`https://docs.apimaker.dev/llms-full.txt`](https://docs.apimaker.dev/llms-full.txt) | Every page in one text file, in reading order. Paste it in a project knowledge base or a long context. | | Any page as Markdown | the page URL with `.md` instead of `.html`, for example [`/v1/docs/schema/schema.md`](https://docs.apimaker.dev/v1/docs/schema/schema.md) | Clean text without navigation, with the snippets resolved and the diagrams replaced by their caption. | | The website for AI | [`https://apimaker.dev/llms.txt`](https://apimaker.dev/llms.txt) and [`llms-full.txt`](https://apimaker.dev/llms-full.txt) | The feature pages and the deployment architectures. | Every page also carries `` pointing at its Markdown twin, and JSON-LD structured data, so crawlers and agents find the right version on their own. ## On every page The bar at the top of a page offers **Copy as Markdown** (the Markdown twin, ready to paste in a chat), **View Markdown**, and **Open in ChatGPT** or **Open in Claude**, which start a chat with the page already loaded. ## Prompts which work well ```text title="Ask about a feature" Read https://docs.apimaker.dev/v1/docs/apis-all/query-params/deep.md and https://docs.apimaker.dev/v1/docs/schema/schema.md, then write the schema of my orders table so that a customer is populated from the customers table in another database. ``` ```text title="Write a custom API" Using https://docs.apimaker.dev/v1/examples/custom-apis/custom-api.md and https://docs.apimaker.dev/v1/docs/pre-defined-terms/global-object-g.md, write a custom API for API Maker which returns the ten latest orders of the person in g.req.auth.authAMDB. ``` ```text title="Give an agent the whole documentation" The documentation of the backend platform we use is at https://docs.apimaker.dev/llms.txt. Fetch the pages you need from it before answering questions about API Maker. ``` ## An assistant in your project folder With the [local client](/v1/docs/features/developer-tools.html#your-own-editor) (or the Local Client page of [API Maker Local Run](/v1/docs/getting-started/local-run.html)), every item of the account is a file in a Git repository : `src/Custom APIs/…`, `src/Utility classes/…`, `src/Schemas/…`, hooks, events, schedulers, migrations, test cases and settings. An agent such as Claude Code, Cursor or Copilot can read and change them ; the local client syncs what it saves to API Maker. - **Types come with the code** : `import * as T from 'types'` gives every interface of API Maker and `import * as db from 'db-interfaces'` the interfaces generated from your schemas, so an assistant writes typed code. - **Tests run from the terminal** : `npm run test` runs the [test cases](/v1/docs/test-cases/test-cases.html) of the account with their mocks and writes the coverage to `coverage/lcov.info`. - **The security report is in the repository** : `src/API Security Report/report.md` says where a person could read rows which are not theirs and how to fix it. It is kept fresh while the local client is connected, so an assistant can fix hooks, groups and settings and see the result on the next scan. See [APIs Security Report & Actions](/v1/docs/apis-security/api-security-report.html#in-the-repository). - **The sample account is a tutorial** : a new account comes with a sample shop (schemas, custom APIs, hooks, an event, a migration, a utility class) written to be read. Point the assistant at `src/Custom APIs/` before asking for something similar. ## Swagger for the APIs of your account Every API user can have its own Swagger (OpenAPI) document with the APIs it may call, with a URL to share : enable it on the [API user](/v1/docs/features/developer-tools.html#swagger-docs-per-api-user). An assistant which generates a client for your APIs reads it directly. !!! ai "Tell us what is missing" If an assistant gives a wrong answer about API Maker, the page it read is probably unclear. Write to [contact@apimaker.dev](mailto:contact@apimaker.dev) with the question and the page ; it helps every reader. --- # All APIs at a Glance > Every API of API Maker on one page - the 17 operations of a table under /api/schema and /api/gen with their method and URL, the custom, system and third party APIs, the query params and headers they share, and how to call them from your code. Source: https://docs.apimaker.dev/v1/docs/apis-all/overview.html Everything API Maker serves sits behind one base URL and the same token header. This page is the map ; every operation has its own page with examples. > Diagram : The API families : one base URL, one token, five HTTP families and the code API Maker runs for you ## The URL of a table > Diagram : Anatomy of a database API URL : /api/schema/admin/mysql8/inventory/customers/get-by-id/42 | Segment | Meaning | |---|---| | `/api/schema` or `/api/gen` | The family : schema APIs apply the schema of the table, generated APIs are schemaless. | | `user-path` | The **API path** of the admin or developer account which owns the instance (`admin` for the first admin). It is shown on the profile of the account. | | `instance` | The name of the instance (the database server) in API Maker. For a tenant of a [multi-tenant](/v1/docs/features/multi-tenant.html) instance : `instance::tenant`. | | `database` | The database on that server. | | `table` | The table or collection. | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## Instances, databases and tables { #instances } `API Info → DB API` has four panels, left to right : instances, databases, tables, APIs. Pick one in each and the next panel fills. | Panel | What it holds | |---|---| | Instances | A connection to a database server : MongoDB, MySQL (TiDB, Percona), MariaDB, SQL Server, PostgreSQL, Oracle. **Add New** takes the database type, a unique **Instance Name** (used in every URL and in code, it can not change later), the **Connection String** picked among the keys of the [secret](/v1/docs/secrets/secrets.html) and **Max Pool Connections** ; Oracle adds the username, the password and the privilege. Saving tests the connection first : an instance which can not connect is not saved. | | Databases | The databases the connection sees, with their [settings](/v1/docs/settings/databaseSettings.html). **Bulk Schema Creation** generates the schema of every table at once ; **Compact Database** reclaims space on MongoDB. | | Tables | The tables or collections, with their [schema](/v1/docs/schema/schema.html), their indexes and their [settings](/v1/docs/settings/collectionSettings.html). **Structure changed** appears when the columns of a table differ from its schema : click to compare and update. | | APIs | The operations below, each with its [settings](/v1/docs/settings/apiSettings.html) and **Test API**, which opens the [API testing page](/v1/docs/features/developer-tools.html#api-testing-page) on it. | - **Connection String Sandbox** : another string for the code which runs in the sandbox, when the sandbox reaches the database through another address. Empty, the main one is used. - **Is Multi Tenant Structure Instance** and **Connection String Multi Tenant** make a [multi-tenant](/v1/docs/features/multi-tenant.html) instance ; **Select tenant** then loads the databases of one tenant. ## The 17 operations of a table The paths below follow `/api/schema////
` (and the same under `/api/gen`). `[/:primaryKey]` names another column to use as the key. | Operation | Method and path | Body | Page (schema · gen) | |---|---|---|---| | Get all | GET `/` | query params | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-all-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-get-all-api.html) | | Get all by stream | GET `/stream` | query params | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-all-by-stream-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-get-all-by-stream-api.html) | | Get by id | GET `/get-by-id/:id[/:primaryKey]` | | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-by-id-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-get-by-id-api.html) | | Save single or multiple | POST `/save-single-or-multiple` | one object or an array | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-save-single-or-multiple-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-save-single-or-multiple-api.html) | | Master save | POST `/master-save` | objects with nested related objects | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-master-save-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-master-save-api.html) | | Update by id | PUT `/update-by-id/:id[/:primaryKey]` | the fields to change | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-update-by-id-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-update-by-id-api.html) | | Update many | PUT `/update-many` | `{ find, updateData }` | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-update-many-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-update-many-api.html) | | Replace by id MongoDB | PUT `/replace-by-id/:id[/:primaryKey]` | the whole document | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-replace-by-id-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-replace-by-id-api.html) | | Array operations MongoDB | PUT `/array-operations` | `{ find, operations }` | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-array-operations-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-array-operations-api.html) | | Remove by id | DELETE `/:id[/:primaryKey]` | | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-remove-by-id-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-remove-by-id-api.html) | | Remove by query | POST `/query/delete` | `{ find }` | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-remove-by-query-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-remove-by-query-api.html) | | Query for get data | POST `/query` | `{ find, select, sort, skip, limit, deep, getTotalCount }` | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-query-for-get-data-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-query-for-get-data-api.html) | | Query for get data by stream | POST `/query-stream` | same as query | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-query-for-get-data-by-stream-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-query-for-get-data-by-stream-api.html) | | Aggregate MongoDB | POST `/aggregate` | a pipeline array | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-aggregate-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-aggregate-api.html) | | Count | POST `/count` | `{ find }` | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-count-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-count-api.html) | | Distinct | GET `/distinct/:field[/:order]` | | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-distinct-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-distinct-api.html) | | Distinct with query | POST `/distinct/:field[/:order]` | `{ find }` | [schema](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-distinct-with-query-api.html) · [gen](/v1/docs/apis-all/generated-apis/auto-generated-distinct-with-query-api.html) | - **MongoDB** gets all 17. **MySQL, MariaDB, SQL Server, PostgreSQL, Oracle, TiDB and Percona** get 14 : replace by id, array operations and aggregate are MongoDB only. - The schema operations exist for a table only once it has an active [schema](/v1/docs/schema/schema.html). The generated ones are always there. - Their id in settings, groups, hooks and WebSocket subscriptions is `SCHEMA_GET_ALL`, `GEN_POST_BULK_INSERT`… : the full list is under [API ids](#api-ids). ## Shared by every read | | | |---|---| | [Query params](/v1/docs/apis-all/query-params/query-params.html) | `find`, `skip`, `limit`, `sort`, `select`, `deep`, `getTotalCount`, and any column name as a filter. In the body for the query APIs. | | [Request headers](/v1/docs/apis-all/header/requestHeader.html) | Tokens, `x-am-response-case`, `x-am-content-type-response`, `x-am-response-object-type`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`… | | [Response format](/v1/docs/apis-all/response-format.html) | `{ success, statusCode, data, totalCount, errors, warnings, logs, meta }` | ## Your own APIs | Family | Method and path | Page | |---|---|---| | Custom API | ANY `/api/custom-api//` : the method and path of its settings | [Custom APIs](/v1/docs/apis-all/custom-apis/user-created-custom-api.html) | | Pre and post hooks | run around every API of the account | [Pre hook](/v1/docs/apis-all/hooks/preHook-api.html) · [Post hook](/v1/docs/apis-all/hooks/postHook-api.html) | | Events | emitted by an API hit or by code, with listeners | [Events](/v1/docs/apis-all/events/user-created-events-api.html) | | WebSocket events | pushed to subscribed clients | [WebSocket events](/v1/docs/pages/web-socket-event-page.html) | | Schedulers | intervals and cron | [Schedulers](/v1/docs/apis-all/schedulers/user-created-schedulers-api.html) | | Utility classes | shared code, `import * as x from 'utils/X'` | [Utility classes](/v1/docs/utility-class/utility-class.html) | ## System APIs All under `/api/system-api//…`, all POST, and all available in code as `g.sys.system.*` or `g.sys.cache.*`. | Group | APIs | |---|---| | Security | [`/token`](/v1/docs/apis-all/system-apis/system-generated-token-api.html) · [`/encrypt-data`](/v1/docs/apis-all/system-apis/system-generated-encrypt-data-api.html) · [`/decrypt-data`](/v1/docs/apis-all/system-apis/system-generated-decrypt-data-api.html) · [`/hash-data`](/v1/docs/apis-all/system-apis/system-generated-hash-data-api.html) · [`/get-secret-by-name`](/v1/docs/apis-all/system-apis/system-generated-get-secret-by-name-api.html) | | Redis and cache | [`/get-redis-key`](/v1/docs/apis-all/system-apis/system-generated-get-redis-key-api.html) · [`/set-redis-key`](/v1/docs/apis-all/system-apis/system-generated-set-redis-key-api.html) · [`/remove-redis-key`](/v1/docs/apis-all/system-apis/system-generated-remove-redis-key-api.html) · [`/reset-redis-cache-db`](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-db-api.html) · [`/reset-redis-cache-custom-apis`](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-custom-api.html) · [`/reset-redis-cache-system-apis`](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-system-api.html) · [`/reset-redis-cache-third-party-apis`](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-third-party-api.html) | | Databases | [`/get-table-meta`](/v1/docs/apis-all/system-apis/system-generated-get-table-meta-api.html) · [`/create-indexes`](/v1/docs/apis-all/system-apis/system-generated-create-indexes-api.html) · [`/drop-indexes`](/v1/docs/apis-all/system-apis/system-generated-drop-indexes-api.html) · [`/get-indexes`](/v1/docs/apis-all/system-apis/system-generated-get-indexes-api.html) · [`/multi-tenant-instance-updated`](/v1/docs/apis-all/system-apis/system-generated-multi-tenant-instance-updated-api.html) · `executeQuery` from [code](/v1/examples/sys/system/executeQuery.html) | | Events | [`/emit-event`](/v1/docs/apis-all/system-apis/system-generated-emit-event-api.html) · [`/emit-event-ws`](/v1/docs/apis-all/system-apis/system-generated-emit-event-ws-api.html) | | Validation | [`/is-valid-data-for-table`](/v1/docs/apis-all/system-apis/system-generated-is-valid-data-for-table-api.html) · [`/is-valid-data-for-custom-api`](/v1/docs/apis-all/system-apis/system-generated-is-valid-data-for-custom-api.html) · [`/is-valid-data-for-third-party-api`](/v1/docs/apis-all/system-apis/system-generated-is-valid-data-for-third-party-api.html) | | Outside | [`/call-external-api`](/v1/docs/apis-all/system-apis/system-generated-call-external-api.html) | ## From your code Every API above is a method of the [global object `g`](/v1/docs/pre-defined-terms/global-object-g.html) : `g.sys.db.getAll`, `g.sys.db.gen.queryGen`, `g.sys.system.encrypt`, `g.sys.cache.setKey`… with the same headers and query params, and [an example for each one](/v1/examples/index.html). ```typescript linenums="1" import * as T from 'types'; import * as db from 'db-interfaces'; async function main(g: T.IAMGlobal) { const products = await g.sys.db.getAll({ instance: 'mongodb', database: 'shop', collection: 'products', queryParams: { find: { status: 'ACTIVE' }, limit: 10, deep: [{ s_key: 'category' }] }, }); return products; } module.exports = main; ``` ## API ids { #api-ids } The ids used in groups, settings, hooks, events and WebSocket subscriptions. Schema APIs - SCHEMA_GET_ALL - SCHEMA_GET_ALL_STREAM - SCHEMA_GET_BY_ID - SCHEMA_POST_BULK_INSERT - SCHEMA_MASTER_SAVE - SCHEMA_ARRAY_OPERATIONS - SCHEMA_UPDATE_MANY - SCHEMA_PUT_UPDATE_BY_ID - SCHEMA_PUT_REPLACE_BY_ID - SCHEMA_DEL_DELETE_BY_ID - SCHEMA_POST_QUERY - SCHEMA_POST_QUERY_STREAM - SCHEMA_POST_QUERY_DELETE - SCHEMA_POST_AGGREGATE - SCHEMA_POST_COUNT - SCHEMA_GET_DISTINCT - SCHEMA_POST_DISTINCT_QUERY Generated APIs - GEN_GET_ALL - GEN_GET_ALL_STREAM - GEN_GET_BY_ID - GEN_POST_BULK_INSERT - GEN_MASTER_SAVE - GEN_ARRAY_OPERATIONS - GEN_UPDATE_MANY - GEN_PUT_UPDATE_BY_ID - GEN_PUT_REPLACE_BY_ID - GEN_DEL_DELETE_BY_ID - GEN_POST_QUERY - GEN_POST_QUERY_STREAM - GEN_POST_QUERY_DELETE - GEN_POST_AGGREGATE - GEN_POST_COUNT - GEN_GET_DISTINCT - GEN_POST_DISTINCT_QUERY System APIs - EXECUTE_PLAIN_QUERY - ENCRYPT_DATA - DECRYPT_DATA - HASH_DATA - GET_TOKEN - CALL_EXTERNAL_API - GET_SECRET - GET_REDIS_KEY - SET_REDIS_KEY - REMOVE_REDIS_KEY - CUSTOM_USER_CACHING - RESET_REDIS_CACHE_DB - RESET_REDIS_CACHE_CUSTOM_APIS - RESET_REDIS_CACHE_SYSTEM_APIS - RESET_REDIS_CACHE_TP_APIS - GET_TABLE_META - EMIT_EVENT - EMIT_EVENT_WS - IS_VALID_DATA_FOR_TABLE - IS_VALID_DATA_FOR_CUSTOM_API - IS_VALID_DATA_FOR_THIRD_PARTY_API --- # Response Format > What every API Maker API answers - the envelope with success, statusCode, data, totalCount, errors, warnings, logs and meta, the HTTP status codes, and how headers change the shape and the format of the reply. Source: https://docs.apimaker.dev/v1/docs/apis-all/response-format.html Every API of API Maker, generated, schema, custom, system or third party, answers with the same envelope, so one client handles all of them the same way. ## The envelope ```json title="A successful answer" { "success": true, "statusCode": 200, "data": [ { "customer_id": 1, "first_name": "Bob", "last_name": "Lin" } ], "totalCount": 5, "warnings": [], "logs": [], "meta": {} } ``` - `data` holds the result, `totalCount` comes with `getTotalCount=true`, `warnings` and `logs` when there are some, `meta` with the header `x-am-meta: true`. Absent keys are simply not sent. ```json title="A refused request" { "success": false, "statusCode": 400, "errors": [ { "type": "required", "field": "first_name", "message": "Please provide valid 'first_name' field with type 'string'.", "code": 400 } ], "warnings": [] } ``` - `errors` lists every problem found, each with its `code`, `message`, and for data problems the `field` and the `type` of the rule. The HTTP status of the reply is `statusCode`. | Key | When | Meaning | |---|---|---| | `success` | always | `true` when the request did what it was asked, `false` otherwise. | | `statusCode` | always | The HTTP status of the reply, repeated in the body : `200`, `201` for a save, `400`, `401`, `403`, `404` or `500`. | | `data` | on success, and on some failures | The result : an array for get all and the query APIs, an object for get by id, a save of one object, an update ; whatever a custom API returns. `null` when there is nothing. | | `totalCount` | with `getTotalCount=true` | The number of rows matching the filter, without `skip` and `limit`. | | `errors` | on failure | One entry per problem. Data problems name the `field` and the `type` of the rule ; the `message` follows the language asked with `x-am-internationalization`. | | `warnings` | when there are some | Problems which did not stop the request, in the same shape as errors : for example a `deep` which found no target. | | `logs` | when your code logged | What `g.logger` printed while a custom API, a hook or an event ran, so the caller can see it. | | `meta` | with `x-am-meta: true` | `executionTime`, `executionPlan` (MongoDB explain), `apiAccessGroups` (which group granted the call) and `runBy` (which server, process and sandbox ran the code). | | `encryptedData` | with `x-am-get-encrypted-data` | The response encrypted with the transfer key of the secret, next to or instead of `data`. | ## An error entry ```json { "type": "min", "field": "price_cents", "message": "Please provide minimum '0' for 'price_cents' field.", "code": 400, "dataIndex": 2 } ``` | Key | Meaning | |---|---| | `type` | The rule which failed : `required`, `min`, `max`, `minLength`, `maxLength`, `email`, `enumValidation`, `invalidValue` (a wrong type, a thrown `validatorFun`, a concurrency version mismatch…), `schemaKeyNotFound` (a key the schema does not have), `schemaNotFound`, `unique`, `virtualFieldUsedInFind`. | | `field` | The field concerned, with dots for nested fields. | | `message` | The text, from the [message templates](/v1/docs/apis-all/error-codes.html) or your own throw, translated when the caller asks for a language. | | `code` | The HTTP status of this problem. The `statusCode` of the reply is the highest one. | | `dataIndex` | For a save of several objects : the index of the object in the array. | | `stack`, `apiCallSequence` | Present for unexpected errors of your code, to help you debug : the stack as lines, and the chain of APIs which led there. | ## Status codes | Code | Meaning | |---|---| | `200` | Done. | | `201` | Created : a save through save single or multiple or master save. | | `400` | The request is wrong : validation errors, an unknown key, a bad query, a missing body. | | `401` | The token is missing, malformed or expired. Get a new one from the [token API](/v1/docs/apis-all/system-apis/system-generated-token-api.html). | | `403` | The token is fine but no group of the API user grants this API, this table or this field ; or the origin of the browser is not allowed. Fix the group, do not sign the user out. | | `404` | The path does not exist : a typo in the URL or a wrong method, an instance name which is not there. Also the answer of update by id, replace by id and remove by id with `throwErrorIfRecordNotFound=true` when the id is unknown. | | `500` | An error of your code or of the database. The message and, for your code, the stack are in `errors`. | ## The format of the reply The body is JSON unless the request asks otherwise with [headers](/v1/docs/apis-all/header/requestHeader.html) : | Header | Effect | |---|---| | `x-am-content-type-response` | `application/json` (default), `text/xml`, `text/yaml`, `text/plain`, `text/html`, `application/octet-stream`. The envelope is the same, only its encoding changes. | | `x-am-response-case` | The case of every key : `camelCase`, `snake_case`, `PascalCase`, `kebab-case`… | | `x-am-response-object-type: make_flat` | Nested objects flattened with `_` between the levels : `state_id_country_id_country_name`. | | `x-am-meta: true` | Adds `meta`. | | `x-am-get-encrypted-data` | Adds or replaces `data` with `encryptedData`. | | `x-no-compression: true` | No gzip or brotli, whatever the size (replies bigger than `compressThreshold`, 51200 bytes by default, are compressed otherwise). | A custom API can also answer something other than the envelope : set `g.res.contentType` to a MIME type and return a string, or return a file, see [Content types](/v1/examples/res/contentType/contentType.html) and the [download example](/v1/docs/apis-all/custom-apis/user-created-custom-api.html#files). ## Response headers | Header | Meaning | |---|---| | `x-am-data-source` | `cache` when the reply came from Redis, `api` when the API ran. | | `Content-Encoding` | `gzip` or `br` when the reply was compressed. | ## In your code The same envelope is what `g.sys.db.*`, `g.sys.system.*` and `g.sys.cache.*` return when you ask for the full response with the second argument `true`. Without it, they return `data` and throw the errors. ```typescript linenums="1" const full = await g.sys.db.getAll({ instance: 'mongodb', database: 'shop', collection: 'products' }, true); if (!full.success) g.logger.error(full.errors); const rows = full.data; ``` --- # Request Headers > Every request header API Maker understands - the tokens, the case and format of the response, flat objects, metadata, caching, encryption of the payload and the reply, the language of the messages, the tenant, the sandbox and compression - with an example of each. Source: https://docs.apimaker.dev/v1/docs/apis-all/header/requestHeader.html Headers change how API Maker answers a call, without changing the URL. They work on every API : generated, schema, custom, system and third party. The response carries a few headers of its own, listed at the end. ## All headers | Header | Values | What it does | |---|---|---| | [`x-am-authorization`](#x-am-authorization) | a token | The API user : which application calls. Needed unless the API is public. | | [`x-am-user-authorization`](#x-am-user-authorization) | a token | The person, from a DB token generator. | | [`x-aws-authorization`](#x-aws-authorization), [`x-google-authorization`](#x-google-authorization), [`x-azure-authorization`](#x-azure-authorization), [`x-custom-authorization`](#x-custom-authorization) | a token | The person, from AWS Cognito, Google, Azure AD or a custom provider. | | [`x-am-response-case`](#x-am-response-case) | `camelCase`, `snakeCase`… | The case of every key of the answer. | | [`x-am-content-type-response`](#x-am-content-type-response) | `application/json`, `text/xml`, `text/yaml`… | The format of the answer. | | [`x-am-response-object-type`](#x-am-response-object-type) | `no_action`, `make_flat` | Flatten nested objects. | | [`x-am-meta`](#x-am-meta) | `true`, `false` | Execution time, plan, groups and sandbox in `meta`. | | [`x-am-cache-control`](#x-am-cache-control) | `no_action`, `reset_cache` | Skip the cache for this call. | | [`x-am-get-encrypted-data`](#x-am-get-encrypted-data) | `no_encryption`, `get_only_encryption`, `get_data_and_encryption` | Encrypt the answer. | | [`x-am-encrypted-payload`](#x-am-encrypted-payload) | `true` | The body is encrypted. | | [`x-am-secret`](#x-am-secret) | the id of a secret | Use another secret than the default one. | | [`x-am-internationalization`](#x-am-internationalization) | the name of a language | The language of the messages. | | [`x-am-tenant-username`](#x-am-tenant-username) | a tenant | The tenant of a multi-tenant instance. | | [`x-am-sandbox-timeout`](#x-am-sandbox-timeout) | milliseconds | How long the code of the call may run. | | [`x-am-run-in-sandbox`](#x-am-run-in-sandbox) | `0`, `1`, `2`… | Which sandboxes may run the code. | | [`x-no-compression`](#x-no-compression), [`accept-encoding`](#accept-encoding) | `true` / `br`, `gzip`, `deflate`, `identity` | Compression of the answer. | ## x-am-authorization The token of an [API user](/v1/docs/apis-security/api-user-permission.html) : the application calling. Its [groups](/v1/docs/apis-security/api-group-permission.html) decide which APIs, tables and fields the call may reach. The token comes from the [token API](/v1/docs/apis-all/system-apis/system-generated-token-api.html) and expires after `jwtOptions.expiresIn` seconds (72 hours by default, `expiresInSeconds` in the token request changes it). ```text x-am-authorization: eyJhbGciOiJIUzI1NiIsInR1BydlRrblJlcS ``` - Missing or invalid : `401`. Valid but no group grants the API : `403`. - Public APIs (`apiAccessType: IS_PUBLIC` in the [settings](/v1/docs/settings/apiSettings.html)) do not need it. - Read it in your code from `g.req.auth.authAMUser`. ## x-am-user-authorization The token of a **person**, a row of your own users table, made by a DB token generator ([auth provider](/v1/docs/authorization/AMDB.html)) through the token API. It is required when the settings of the database, the table, the API or the custom API list that provider in `authProviders`. ```text x-am-user-authorization: eyJhbGciOiJIpXVCJBydlRrblJlcS ``` - Read it in your code from `g.req.auth.authAMDB` : the columns of the user selected by the generator, and the groups of the groups column. - A token made for a tenant works for that tenant only, see [Multi-tenant](/v1/docs/features/multi-tenant.html#users-of-a-tenant). ## x-aws-authorization The access token of AWS Cognito, when an [AWS auth provider](/v1/docs/authorization/AWS.html) is configured. Read from `g.req.auth.authAWS`. ```text x-aws-authorization: eyJhbGciOInR5cCI6IkpXVCJ9 ``` ## x-google-authorization The id token of a Google sign-in, when a [Google auth provider](/v1/docs/authorization/Google.html) is configured. Read from `g.req.auth.authGoogle`. ```text x-google-authorization: eyJhbGciOiJIUzI1I6IkpXVCJ9 ``` ## x-azure-authorization The token of Azure Active Directory, when an [Azure auth provider](/v1/docs/authorization/Azure.html) is configured. Read from `g.req.auth.authAzure`. ```text x-azure-authorization: eyJhbGciOiJIUzI1NI6IkpXVCJ9 ``` ## x-custom-authorization The token of a [custom auth provider](/v1/examples/req/auth/authCustom.html) : your own generator and validator code. What the validator returns is in `g.req.auth.authCustom`. ```text x-custom-authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ``` ## x-am-response-case Changes the case of every key of the answer, nested objects and arrays included. Default : `noChange`. | Value | `first_name` becomes | |---|---| | `noChange` | `first_name` | | `camelCase` | `firstName` | | `capitalCase` | `First Name` | | `constantCase` | `FIRST_NAME` | | `dotCase` | `first.name` | | `headerCase` | `First-Name` | | `noCase` | `first name` | | `paramCase` | `first-name` | | `pascalCase` | `FirstName` | | `pathCase` | `first/name` | | `sentenceCase` | `First name` | | `snakeCase` | `first_name` | ### NoChange response ```text x-am-response-case: noChange ``` ### CamelCase response ```text x-am-response-case: camelCase ``` ```json { "firstName": "JOHN", "lastName": "DOE" } ``` ### CapitalCase response ```json { "First Name": "JOHN", "Last Name": "DOE" } ``` ### ConstantCase response ```json { "FIRST_NAME": "JOHN", "LAST_NAME": "DOE" } ``` ### DotCase response ```json { "first.name": "JOHN", "last.name": "DOE" } ``` ### HeaderCase response ```json { "First-Name": "JOHN", "Last-Name": "DOE" } ``` ### NoCase response ```json { "first name": "JOHN", "last name": "DOE" } ``` ### ParamCase response ```json { "first-name": "JOHN", "last-name": "DOE" } ``` ### PascalCase response ```json { "FirstName": "JOHN", "LastName": "DOE" } ``` ### PathCase response ```json { "first/name": "JOHN", "last/name": "DOE" } ``` ### SentenceCase response ```json { "First name": "JOHN", "Last name": "DOE" } ``` ### SnakeCase response ```json { "first_name": "JOHN", "last_name": "DOE" } ``` ## x-am-response-object-type Flattens nested objects, such as the ones [deep](/v1/docs/apis-all/query-params/deep.html) produces, into one level with `_` between the names. Default : `no_action`. ### No action ```text x-am-response-object-type: no_action ``` ```json { "id": 101, "state_id": { "id": 201, "country_id": { "id": 301, "country_name": "INDIA" }, "state_name": "GUJARAT" }, "city_name": "AHMEDABAD" } ``` ### Make flat ```text x-am-response-object-type: make_flat ``` ```json { "id": 101, "state_id_id": 201, "state_id_country_id_id": 301, "state_id_country_id_country_name": "INDIA", "state_id_state_name": "GUJARAT", "city_name": "AHMEDABAD" } ``` - Handy for grids, CSV exports and spreadsheets. ## x-am-meta Adds `meta` to the answer. Default : `false`. ### False ```text x-am-meta: false ``` ### True ```text x-am-meta: true ``` ```json { "data": [ { "id": 301, "country_name": "INDIA" } ], "meta": { "executionTime": "15ms", "executionTimeMS": 15, "executionPlan": [], "apiAccessGroups": [ { "groupId": "6381b80359bdbd3a87c9abd5", "groupName": "all_permission", "hasAccess": true } ], "runBy": [ { "apiCategory": "CUSTOM_APIS", "serverId": "server1", "processId": "…", "workerId": "1", "port": 38246 } ] } } ``` | Key | Meaning | |---|---| | `executionTime`, `executionTimeMS` | Time spent in API Maker, without the network. | | `executionPlan` | The explain plan of MongoDB for the query. | | `apiAccessGroups` | The groups of the API user and which one granted the call. | | `runBy` | Which server, process, worker and sandbox ran the code. | ## x-am-secret The id of a [secret](/v1/docs/secrets/secrets.html) to use for this call instead of the default one : its encryption keys serve the `encryption` and `hashing` conversions and the encrypt, decrypt and hash system APIs. The default secret is used when the header is absent. ```text x-am-secret: 6381b80359bdbd3a87c9abd5 ``` ## x-am-internationalization The name of a language of [i18N Management](/v1/docs/i18/i18.html). Every message of the answer, the messages of API Maker as well as your own from `errorList` and thrown strings, comes back in that language. The value `Default` (or no header) gives the original messages. ```text x-am-internationalization: Hindi ``` ```json title="Default" { "code": 400, "message": "Please provide id param value" } ``` ```json title="Hindi" { "code": 400, "message": "कृपया आईडी पैरामीटर का मान प्रदान करें." } ``` ```json title="Chinese simple" { "code": 400, "message": "请提供id参数的值" } ``` ```json title="Spanish" { "code": 400, "message": "Proporcione el valor del parámetro id." } ``` ```json title="Japanese" { "code": 400, "message": "id パラメータの値を入力してください" } ``` ```json title="Urdu" { "code": 400, "message": "براہ کرم id پیرامیٹر کی قدر فراہم کریں۔" } ``` - Add a language, change a message, and the next call answers with it : no restart. ## x-am-run-in-sandbox Which of the sandboxes of the account may run the code of this call (custom API, hooks, events). An account has `sandboxCountForAdmin` sandboxes per API Maker process. ```text x-am-run-in-sandbox: 0 # any sandbox (default) x-am-run-in-sandbox: 1 # always the first sandbox x-am-run-in-sandbox: 2 # one of the first two x-am-run-in-sandbox: 3 # one of the first three ``` - Pin a call to one sandbox when your code keeps state in memory, such as a connection opened by a [process initializer](/v1/docs/features/process-initializers.html). ## x-am-content-type-response The format of the answer. Default : `application/json`. ### Application/json ```text x-am-content-type-response: application/json ``` ```json { "success": true, "statusCode": 200, "data": [ { "customer_id": 4, "first_name": "JOHNNY", "last_name": "LOLLOBRIGIDA" } ] } ``` ### Text/xml ```text x-am-content-type-response: text/xml ``` ```xml true 200 <_el> 4 JOHNNY LOLLOBRIGIDA ``` ### Text/yaml ```text x-am-content-type-response: text/yaml ``` ```yaml success: true statusCode: 200 data: - customer_id: 4 first_name: JOHNNY last_name: LOLLOBRIGIDA ``` ### Text/plain, text/html, application/octet-stream ```text x-am-content-type-response: text/plain ``` - The same envelope as text, HTML or bytes. A custom API can also decide the format itself with `g.res.contentType`, see [Content types](/v1/examples/res/contentType/contentType.html). ## x-am-cache-control For an API with [caching](/v1/docs/features/automatic-caching.html) on. Default : `no_action`. ### No action to cache ```text x-am-cache-control: no_action ``` - The answer comes from Redis when it is there. The response header `x-am-data-source` says `cache`. ### Reset cache ```text x-am-cache-control: reset_cache ``` - The API runs against the database and its answer replaces the cached one. `x-am-data-source` says `api`. ## x-am-get-encrypted-data Encrypts the answer with `encryptionAlgorithmFETransfer` and `secretFETransfer` of the secret, the key you share with your frontend or mobile app. Default : `no_encryption`. ### No encryption ```json { "success": true, "statusCode": 200, "data": [ { "customer_id": 53, "first_name": "Deli", "last_name": "Augustus" } ] } ``` ### Get only encryption ```text x-am-get-encrypted-data: get_only_encryption ``` ```json { "success": true, "statusCode": 200, "data": null, "encryptedData": "U2FsdGVkX1+6qMdb3jXwJAUWH/qlwBq75mtA1kzpWccrNZRAr+CE2c3VtGpkEtVjH==" } ``` ### Get data and encryption ```text x-am-get-encrypted-data: get_data_and_encryption ``` ```json { "success": true, "statusCode": 200, "data": [ { "customer_id": 53, "first_name": "Deli", "last_name": "Augustus" } ], "encryptedData": "U2FsdGVkX1+6qMdb3jXwJAUWH/qlwBq75mtA1kzpWccrNZRAr+CE2c3VtGpkEtVjH==" } ``` - The client decrypts `encryptedData` with the same algorithm and key (AES, RC4 or TripleDES, as in the secret). ## x-am-encrypted-payload Tells API Maker that the body is encrypted : `{ "dataEncFE": "…" }`, where `dataEncFE` is `{ data, createdAt }` encrypted with the transfer key of the secret. A payload older than `feTransferDataValidityInSeconds` is refused. With `acceptOnlyEncryptedData: true` in the settings, plain bodies are refused. See [Security features](/v1/docs/features/security-features.html#encrypted-request-payloads). ```text x-am-encrypted-payload: true ``` ## x-am-tenant-username The tenant of the request, for the APIs of a [multi-tenant](/v1/docs/features/multi-tenant.html) instance : they work on the database of that tenant. - It names the tenant only. Each multi-tenant instance looks the tenant up in its own tenants table, the entry of the default secret chosen as its **Connection String Multi Tenant**. So one header serves every multi-tenant instance a request or a custom API uses, even when they use different tenants tables. - It does what naming the tenant in the path does, after the instance name and two colons : `/api/schema/admin/crm::acme/crm/customers`. When a request does both, the tenant of the path is used. - A tenant the tenants table does not have, or which the `find` of its secret entry leaves out, is refused with 400 : `Unable to find tenant with username 'acme'.` - An empty value names no tenant : the request works on the structure database. - A custom API reads the tenant in `g.req.params.tenantUsername`, and passes it on to the APIs it calls with `g.sys.db` and `g.sys.system`. The hooks of the request do the same. - Custom, system and third party APIs with caching keep their cached answers per tenant. - A [token of a user](/v1/docs/features/multi-tenant.html#users-of-a-tenant) made with this header works for that tenant only. - An instance which is not multi-tenant has one database for every tenant : the header does not change the database it uses. ```text GET /api/schema/admin/crm/crm/customers x-am-tenant-username: acme ``` ## x-am-sandbox-timeout How long the code of this call (custom API, hooks, events, listeners) may run, in milliseconds. Default : `sandboxReqTimeout` of the [configuration](/v1/docs/am-resources/api-maker-configurations.html), `13000`. When the time is over, the sandbox stops the code and the answer is an error. ```text x-am-sandbox-timeout: 60000 ``` - A custom API can set its own limit in its settings with `customApiTimeoutInSeconds`. ## x-no-compression Answers bigger than `compressThreshold` bytes (51200 by default) are compressed. `true` sends this answer as it is. ```text x-no-compression: true ``` ## accept-encoding The standard header. API Maker compresses with Brotli when the header is absent or names `br`, and with `gzip` or `deflate` when asked ; `identity` means no compression. ```text accept-encoding: gzip ``` ## Response headers | Header | Meaning | |---|---| | `x-am-data-source` | `cache` when the answer came from Redis, `api` when the API ran. Always `api` for streams. | | `x-am-request-id` | The id of the request, the same in the logs of API Maker. | | `content-encoding` | `br`, `gzip` or `deflate` when the answer is compressed. | ## From your code The same headers go in `headers` of every `g.sys` call : ```typescript const rows = await g.sys.db.getAll({ instance: 'mysql8', database: 'inventory', collection: 'customers', headers: { 'x-am-response-case': 'camelCase', 'x-am-tenant-username': 'acme' }, }); ``` --- # Error Messages Reference > Every message template of API Maker (AM_00001 to AM_00125) with its meaning, what triggers it and how to fix it - validation, deep populate, tokens, secrets, encryption, multi-tenant, files and more. Each one can be translated per caller with internationalization. Source: https://docs.apimaker.dev/v1/docs/apis-all/error-codes.html Every message API Maker answers comes from a template with a code. The codes matter for two things : you recognise a problem quickly, and you can translate any template per caller with [internationalization](/v1/docs/i18/i18.html) (the key `x-am-internationalization`). The `{field}`, `{value}`… placeholders are filled with the details of the request. ## Data and schema | Code | Message | When | |---|---|---| | AM_00001 | Please provide valid '{field}' field with type '{type}'. | A required field is missing or has the wrong type. Also the version field of [optimistic concurrency control](/v1/docs/features/optimistic-concurrency-control.html) missing on an update. | | AM_00002 | Please provide minimum '{value}' for '{field}' field. | `validations.min` | | AM_00003 | Please provide maximum '{value}' for '{field}' field. | `validations.max` | | AM_00004 | Property '{field}' should have minimum length of '{value}'. | `validations.minLength` | | AM_00005 | Property '{field}' should have maximum length of '{value}'. | `validations.maxLength` | | AM_00006 | Please provide unique value '{value}' for key '{field}' in data index '{dataIndex}'. | The same value twice in one save for a field with `validations.unique`. | | AM_00007, AM_00008 | Please provide unique combination for keys ['{validationPath}'] in data index '{dataIndex}'. | The same combination twice in one save. | | AM_00009, AM_00020 | {field} should be array as it is logical operator. | `$and`, `$or`, `$nor` with something else than an array. | | AM_00010 | {field} should be array in schema as data is in array. | An array sent for a field the schema declares as a single value. | | AM_00011, AM_00013, AM_00014 | '{value}' is not a valid number for key '{field}'. | A value which can not become the number the schema asks. | | AM_00015, AM_00016 | Invalid date in '{field}' field. | A value which can not become a date. | | AM_00017, AM_00018, AM_00019 | Invalid objectId in '{field}' field. | An object, an array or a bad string for an objectId field. | | AM_00021, AM_00022 | Invalid date in min / max field in schema validation for '{field}' field. | The schema itself has a bad `min` or `max` date. | | AM_00023, AM_00024 | Array is expected in key '{field}'. | `$in` and `$nin` need an array, and not an empty one for some operators. | | AM_00058 | Found empty schema to validate. | A validation asked with no schema. | | AM_00061 | Invalid data found for saving. Please provide data in JSON object format to save. | The body of a save is not an object or an array of objects. | | AM_00062, AM_00112 | Type of '{field}' should be array in schema. | Array operations or an array value on a field which is not an array in the schema. | | AM_00087 | Please provide valid value in {field}. | A `validatorFun` returned a falsy value. A thrown error uses its own message instead. | | AM_00101 | {message} | Any message thrown by your code (`validatorFun`, `conversionFun`, custom API), passed through i18n. | | AM_00111 | Property '{field}' should be valid email address. | `validations.email` | | AM_00114 | [{field}] is duplicate having value [{value}]. | A duplicate the database refused (unique index). | | AM_00116 | Property '{field}' should have any value from {value}. | `validations.enum` | | AM_00117 | Concurrency version mismatch in '{field}'. This row/document is already updated. | The version sent is not the one of the row : [optimistic concurrency control](/v1/docs/features/optimistic-concurrency-control.html). | | AM_00118 | Virtual field can not be used in find join feature. '{field}' is virtual field. | A `find` on a virtual field. | | AM_00119 | Record not found. | `throwErrorIfRecordNotFound=true` and no row for the id. | ## Query, deep populate and find join | Code | Message | When | |---|---|---| | AM_00012 | Please provide id param value | Get by id, update by id, remove by id without an id. | | AM_00036 | Please provide t_col value. | A `deep` item without `t_col`, and no relation in the schema for its `s_key`. | | AM_00037 | Unable to find collection for s_key:'{field}', You can provide it in schema or deep by 't_col' property. | Same, for a nested level. | | AM_00039 | Unable to find collection with name '{collectionName}' on path '{instanceName} -> {databaseName} -> {collectionName}'. | A `t_col` which does not exist. | | AM_00040 | Unable to find primary key for target collection '{collectionName}'… | The target has no primary key and the deep item has no `t_key`. | | AM_00044 | Please provide array for aggregate body. | The aggregate API needs a pipeline array. | | AM_00045, AM_00047 | Please provide field for distinct API. You can also provide multiple fields separated by comma. | Distinct without a field. | | AM_00046, AM_00048 | Invalid column ['{field}'] found in your field list. | `select` or distinct with a column the schema does not have. | | AM_00049 | Please provide 'find' property having query to remove data. | Remove by query without `find` : nothing is ever deleted without a filter. | | AM_00051 | Primary key '{field}' not found in schema. | The `primaryKey` param names a column which is not in the schema. | | AM_00052, AM_00053 | Unable to convert '{value}' to number / objectId based on your schema type. | An id which does not fit the type of the primary key. | | AM_00054, AM_00055 | Invalid primary key type '{type}' found for '{field}'. / Primary key not found in '{instanceName}' -> '{databaseName}' -> '{collectionName}'. | The table has no usable primary key. | | AM_00056 | Schema is not defined for this collection '{collectionName}'. You can define schema or please try /api/gen version of the api. | A schema API called on a table without a schema. | | AM_00060 | find property should be string of valid JSON/JSON5. | A `find` query param which does not parse. | | AM_00063, AM_00066, AM_00067 | Unable to find schema / property '{field}' in schema of '{instanceName}' ➞ '{databaseName}' ➞ '{collectionName}'. | A `deep` or find join reaching a key or a table without a schema (schema APIs). | | AM_00085 | $in/$nin operator value must be non empty array. | | | AM_00086 | $gt/$gte/$lt/$lte value should be valid [string, number, date (iso format)]. | | | AM_00110 | Multiple operators [{value}] are not supported in SQL databases at this moment. You can use $and operator to achieve that. | Several operators on one field on a SQL instance : wrap them in `$and`. | | AM_00042, AM_00064, AM_00095 | This API is only supported for mongodb. / This API is not supported. / Replace by id API is not supported for instance type '{type}'. | Aggregate, array operations and replace by id on a SQL instance. | ## Tokens and access | Code | Message | When | |---|---|---| | AM_00034 | User '{name}' with username '{username}' is not having access to this API '{url}'. | No group of the API user grants the API : `403`. | | AM_00035 | Authorization header 'x-am-authorization' is not valid. | Bad or expired API user token : `401`. | | AM_00050 | User '{name}' does not have access to fields [{field}]. | A write to a field the group can not write. | | AM_00084, AM_00115 | Invalid access token. / Invalid JWT token provided. | A token which does not verify. | | AM_00090 | Please provide token request with ['u', 'p'] or ['refresh_token']. | The token API body is incomplete. | | AM_00092, AM_00093 | Please provide '{name}' token in request headers. / Invalid token provided in '{name}' header. | The table asks for a person token which is missing or invalid. | | AM_00094 | Token user not found in '{instanceName}' -> '{databaseName}' -> '{collectionName}'. | The person of the token is no longer in the users table. | | AM_00105 | Multiple entries exists for this username '{value}' in database collection|table. | The users table has the username twice. | | AM_00109 | Access groups not assigned to user. | The person has no group in the groups column. | | AM_00113 | You are not authorized to access this API. | Generic refusal. | | AM_00032 | Invalid request origin. You can give access to this host '{origin}' from settings. | The browser origin is not in the allowed origins of the [sandbox settings](/v1/docs/settings/sandboxSettings.html). | ## Secrets, encryption and payloads | Code | Message | When | |---|---|---| | AM_00025 | Header '{header}' is required because of encryption used in key '{field}'. | | | AM_00026 | Unable to find valid algorithm named '{algorithm}' for '{field}' field. These are valid encryption algorithms ['AES', 'RC4', 'TRIPLEDES']. | | | AM_00027, AM_00029 | Unable to find encryption algorithm / secret for '{field}' field. You can provide its value in 'common.encryptionAlgorithm' & 'common.secret' key. | The default secret lacks the keys a field with `encryption: true` needs. | | AM_00028, AM_00030 | Unable to find hashing algorithm / nonce for '{field}' field. | Same for `hashing: true`. | | AM_00059, AM_00088 | Secret with id '{value}' is not present in the system. / Provided secret id '{value}' is not valid. | A bad `x-am-secret` header. | | AM_00103, AM_00104, AM_00106, AM_00107, AM_00108 | Unable to decrypt data of 'dataEncFE' field. / Payload expired. / Please provide field 'createdAt'… | An [encrypted payload](/v1/docs/features/security-features.html#encrypted-request-payloads) which does not decrypt, is too old, or lacks `createdAt`. | | AM_00068 | Invalid request. Please provide valid value for header x-am-code-hash. | | ## Instances, custom code and files | Code | Message | When | |---|---|---| | AM_00031 | Instance with name '{instanceName}' does not exist. | | | AM_00057, AM_00099 | Path '{url}' does not exist. / Please provide valid API path. Can not find any user with path '{value}'. | A wrong user path or custom API path : `404`. | | AM_00065 | Unsupported value found for parameter '{field}' | | | AM_00069, AM_00070, AM_00071, AM_00072 | Migration script … not found / Execution is locked … | [Database migrations](/v1/docs/features/database-migration.html) | | AM_00073, AM_00074 | Unable to find custom API with name '{name}'. / … version '{versionNumber}' … | A `g.sys.system.callExternalApi` or a test naming a custom API which is not there. | | AM_00075, AM_00076 | Unable to find scheduler … | | | AM_00077 | Unable to find version '{versionNumber}' in schema of … | | | AM_00078, AM_00079 | Unable to find store API … | Third party APIs | | AM_00081, AM_00082 | Unable to find utility class with name '{name}'. / … version … | An `import * as x from 'utils/X'` of a class which does not exist. | | AM_00096 | Unable to find system API with name '{name}'. | | | AM_00097, AM_00098 | Please provide 'path' property. / Please provide request body. | | | AM_00100 | WebSocket event not found with name '{name}'. You can create this event in API Info → WebSocket Events. | `emitEventWS` with an unknown event. | | AM_00120 | File upload is not supported in [{name}] API. | | | AM_00121, AM_00122 | Minimum / Maximum file size supported is '{value}' bytes and you provided [{name}] file with '{value2}' bytes in '{field}' field. | Custom API file uploads | | AM_00123 | Allowed extensions are [{value}] and you provided file '{name}' with extension '{value2}'. | | | AM_00124 | Allowed fields are [{value}] and you have provided value in field '{field}'. | | | AM_00125 | Please provide ['oracleDBUsername', 'oracleDBPassword', 'oracleDBPrivilege']. | An Oracle instance without its credentials. | ## Translating them Open **Utility → i18N Management**, create a language, and change the value of any code. Send the name of the language in `x-am-internationalization` and every message of the reply follows it. Your own messages (custom API `errorList`, thrown strings) can be mapped too. See [Internationalization](/v1/docs/i18/i18.html). --- # Query Parameters > The query params every read API of API Maker understands - find, skip, limit, sort, select, deep and getTotalCount, plus any column as a filter, upsert and returnDocument for updates - with the same keys in the body of the query APIs. Source: https://docs.apimaker.dev/v1/docs/apis-all/query-params/query-params.html The read APIs of a table ([get all](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-all-api.html), [get all by stream](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-all-by-stream-api.html), [get by id](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-by-id-api.html), [distinct](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-distinct-api.html)) take their options as query params of the URL. The [query](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-query-for-get-data-api.html) APIs take the same keys in their JSON body, and the write APIs accept `select` and `deep` to shape their answer. | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The params | Param | Example | What it does | |---|---|---| | [find](/v1/docs/apis-all/query-params/find.html) | `?find={first_name:'Bob',pincode:{$gte:382345}}` | Filter with the MongoDB operators, on every database, including fields of related tables. | | any column | `?first_name=Bob,Alice&pincode>=382345` | A column name is a filter too, with `>`, `>=`, `<`, `<=`, `!=`, comma lists and regular expressions. | | [skip](/v1/docs/apis-all/query-params/skip.html) | `?skip=20` | Leave out the first rows. | | [limit](/v1/docs/apis-all/query-params/limit.html) | `?limit=10` | At most this many rows. Without it, every row. | | [sort](/v1/docs/apis-all/query-params/sort.html) | `?sort=-last_update,first_name` | Order, `-` for descending, several columns. | | [select](/v1/docs/apis-all/query-params/select.html) | `?select=first_name,last_name` or `?select=-password` | Only these fields, or everything but these. | | [deep](/v1/docs/apis-all/query-params/deep.html) | `?deep=[{s_key:'customer_id',t_col:'orders',t_key:'customer_id',isMultiple:true}]` | Replace ids with the related rows, from any table, database or instance, any depth. | | [getTotalCount](/v1/docs/apis-all/query-params/getTotalCount.html) | `?getTotalCount=true` | Add `totalCount` : the rows matching the filter before paging. | | [upsert](/v1/docs/apis-all/query-params/upsert.html) | `?upsert=true` | Update by id and replace by id : insert when the id does not exist. | | [returnDocument](/v1/docs/apis-all/query-params/returnDocument.html) | `?returnDocument=before` | Update by id and replace by id : answer with the row before the change. | | throwErrorIfRecordNotFound | `?throwErrorIfRecordNotFound=true` | Update, replace and remove by id : `404` instead of `data: null` for an unknown id. | ## Writing them - Values are **JSON5** : `find={first_name:'Bob'}` needs no quotes around keys and accepts single quotes. Strict JSON works too. - URL encode the value when your client does not : `find=%7Bfirst_name%3A'Bob'%7D`. The API testing page and `g.sys.db` do it for you. - Numbers, booleans and ISO dates in a column param are cast to their type before the query (`?pincode=382345` compares a number). - The same keys go in the body of the query APIs, as JSON : ```json { "find": { "first_name": "Bob" }, "sort": "-customer_id", "skip": 0, "limit": 10, "select": "first_name,last_name", "deep": [ { "s_key": "customer_id", "t_col": "orders", "t_key": "customer_id" } ], "getTotalCount": true } ``` ## From code `g.sys.db.getAll({ instance, database, collection, queryParams: { find: {…}, sort: '-customer_id', limit: 10 } })` : `queryParams` takes the same keys, as objects rather than strings. See the [code examples](/v1/examples/sys/db/getAll.html). ## Schema or generated The schema APIs convert the values of `find` to the types of the schema (`"8"` matches the number 8) and know the relations for `deep`. The generated APIs compare what you send as it is. See [All APIs at a glance](/v1/docs/apis-all/overview.html). --- # find Parameter > Filter rows with the find parameter of API Maker - the MongoDB operators $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or, $not, $like, $regex and $isNull on every database, and find and join on fields of related tables. Source: https://docs.apimaker.dev/v1/docs/apis-all/query-params/find.html `find` filters the rows. It takes an object with the MongoDB operators, and API Maker translates it for MySQL, MariaDB, PostgreSQL, SQL Server and Oracle as well, so one filter language serves every database. ```text title="As a query param (JSON5)" GET /api/schema/admin/mysql8/inventory/customers?find={first_name:'Bob'} ``` ```json title="In the body of the query APIs" { "find": { "first_name": "Bob" } } ``` | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## Operators | Operator | Example | Rows returned | |---|---|---| | a value | `{ "customer_id": 2 }` | equal to the value | | `$eq` | `{ "customer_id": { "$eq": 2 } }` | equal | | `$ne` | `{ "customer_id": { "$ne": 2 } }` | not equal | | `$gt`, `$gte` | `{ "customer_id": { "$gt": 2 } }` | greater than, greater or equal | | `$lt`, `$lte` | `{ "customer_id": { "$lte": 2 } }` | less than, less or equal | | `$in` | `{ "customer_id": { "$in": [ 1, 2 ] } }` | one of the values | | `$nin` | `{ "customer_id": { "$nin": [ 2, 3, 4 ] } }` | none of the values | | `$and` | `{ "$and": [ { "customer_id": 1 }, { "pincode": 382345 } ] }` | every condition | | `$or` | `{ "$or": [ { "customer_id": 1 }, { "pincode": 382345 } ] }` | any condition | | `$not` | `{ "customer_id": { "$not": { "$in": [ 1, 2, 3 ] } } }` | the opposite of the operator | | `$like` | `{ "first_name": { "$like": "Bob%" } }` | SQL pattern, `%` any text, `_` one character | | `$regex` | `{ "first_name": { "$regex": "^Ma" } }` | regular expression | | `$isNull` | `{ "last_update": { "$isNull": true } }` | null, or not null with `false` (SQL databases) | - Two keys in one object are both required : `{ "first_name": "Eve", "pincode": 382349 }`. - Comparisons work on numbers, strings and dates in ISO format : `{ "last_update": { "$gte": "2022-11-01T00:00:00.000Z" } }`. - `$in` and `$nin` need a non empty array. - **SQL databases** : one operator per field. `{ "customer_id": { "$gte": 1, "$lte": 9 } }` is refused ; write `{ "$and": [ { "customer_id": { "$gte": 1 } }, { "customer_id": { "$lte": 9 } } ] }`. ## Find and join : fields of related tables A key with dots reaches a field of a related table. API Maker reads the related table with its own query API, keeps the ids which match, and filters the main table with them : a join across tables, databases and even instances. ```json title="Customers whose orders are paid" { "find": { "orders.status": "PAID" }, "deep": [ { "s_key": "customer_id", "t_col": "orders", "t_key": "customer_id", "isMultiple": true } ] } ``` ```json title="Two levels, with $and and $or" { "find": { "$and": [ { "orders.items.qty": { "$gte": 2 } }, { "$or": [ { "orders.status": "PAID" }, { "orders.status": "SHIPPED" } ] } ] } } ``` - The relation comes from the `deep` item of the same call, or from the [schema](/v1/docs/schema/schema.html) of the table (`collection`, `column`, `database`, `instance` on the field). With the schema, the `deep` item is not needed for the filter. - The related rows are read with the permissions of the caller : a field or a table the group can not read can not be filtered on. - A virtual field can not be used in a find and join. ## Types - **Schema APIs** convert the values to the types of the schema : `{ "customer_id": "8" }` matches the number 8, a date string becomes a date, an id string an ObjectId. - **Generated APIs** compare what you send : `{ "customer_id": "8" }` looks for the string. ## A column as a query param On get all, get by id and the streams, a column name is a filter without `find` : ```text ?customer_id=1 ?first_name=Bob,Alice # one of them ?pincode>=382345 # also > < <= != ?first_name=/^ma/i # regular expression ?first_name=Bob&find={pincode:382345} # both apply ``` ## From code ```typescript const rows = await g.sys.db.query({ instance: 'mysql8', database: 'inventory', collection: 'customers', find: { pincode: { $gte: 382345 }, 'orders.status': 'PAID' }, }); ``` ## Security `find` is powerful : with it a caller reads every row a table has, unless a [pre hook](/v1/docs/apis-all/hooks/preHook-api.html) narrows it to their own rows. The [APIs Security Report](/v1/docs/apis-security/api-security-report.html) tells which tables need that hook and writes it for you. On SQL instances, keys and values which a query builder would paste into the statement (`__:` prefix, `__` helper) can be bound as plain parameters with the switch **Block inline SQL in find** of the report. --- # skip Parameter > Leave out the first rows of an answer with the skip parameter of API Maker, and page through a table together with limit. Source: https://docs.apimaker.dev/v1/docs/apis-all/query-params/skip.html `skip` leaves out the first rows of the result. With `limit`, it pages through a table : page 3 of 10 rows is `skip=20&limit=10`. ```text title="Query param" GET /api/schema/admin/mysql8/inventory/customers?skip=2&sort=customer_id ``` ```json title="Body of the query APIs" { "find": {}, "skip": 2, "sort": "customer_id" } ``` ```json title="Answer : rows 3, 4 and 5" { "success": true, "statusCode": 200, "data": [ { "customer_id": 3, "first_name": "Mallory", "last_name": "Brown" }, { "customer_id": 4, "first_name": "Eve", "last_name": "Mathly" }, { "customer_id": 5, "first_name": "Eve", "last_name": "Page" } ] } ``` - A whole number ; `0` or absent skips nothing. - Always add a `sort` when you page : without an order, the database is free to return the rows in any order and a page may repeat or miss rows. - `getTotalCount=true` gives the total to compute the number of pages. - Inside a `deep` item, `skip` applies to the related rows of each parent. limit → getTotalCount --- # limit Parameter > Cap the number of rows of an answer with the limit parameter of API Maker - the page size of a list, and a safety net on tables which grow. Source: https://docs.apimaker.dev/v1/docs/apis-all/query-params/limit.html `limit` caps the number of rows in the answer. Without it, get all and the query APIs return every row which matches, so give a `limit` as soon as a table can grow. ```text title="Query param" GET /api/schema/admin/mysql8/inventory/customers?limit=2&sort=customer_id ``` ```json title="Body of the query APIs" { "find": {}, "limit": 2, "sort": "customer_id" } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "customer_id": 1, "first_name": "Bob", "last_name": "Lin" }, { "customer_id": 2, "first_name": "Alice", "last_name": "Page" } ] } ``` - A whole number greater than zero. - With `skip`, it pages : `skip=(page - 1) * limit`. - With `getTotalCount=true`, the answer also says how many rows match in total. - Inside a `deep` item, `limit` applies to the related rows of each parent : the 5 latest orders of every customer, for example. - The streams ([get all by stream](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-all-by-stream-api.html), query by stream) send big results without a limit and without holding them in memory. sort → skip --- # sort Parameter > Order the rows of an answer with the sort parameter of API Maker - one or several columns, ascending or descending with a minus sign, as a string or as an object. Source: https://docs.apimaker.dev/v1/docs/apis-all/query-params/sort.html `sort` orders the rows. Name the columns, with `-` in front of a column for descending order. ```text title="Ascending" GET /api/schema/admin/mysql8/inventory/customers?sort=customer_id ``` ```text title="Descending" GET /api/schema/admin/mysql8/inventory/customers?sort=-customer_id ``` ```text title="Several columns : by last update, newest first, then by first name" GET /api/schema/admin/mysql8/inventory/customers?sort=-last_update,first_name ``` ```json title="Body of the query APIs : a string or an object" { "find": {}, "sort": "-last_update,first_name" } { "find": {}, "sort": { "last_update": -1, "first_name": 1 } } ``` ```json title="Answer of ?sort=-customer_id" { "success": true, "statusCode": 200, "data": [ { "customer_id": 5, "first_name": "Eve", "last_name": "Page" }, { "customer_id": 4, "first_name": "Eve", "last_name": "Mathly" }, { "customer_id": 3, "first_name": "Mallory", "last_name": "Brown" } ] } ``` - Spaces around the commas are fine : `-last_update, first_name`. - On MongoDB a nested field sorts too : `sort=address.city`. - Inside a `deep` item, `sort` orders the related rows of each parent. - Sorting a big table on a column without an index is slow ; [Index Maker](/v1/extensions/index_maker/introduction.html) finds the queries which need one. select → limit --- # select Parameter > Choose the fields of an answer with the select parameter of API Maker - a list of columns to keep, or a list to leave out with a minus sign, on reads and on the answers of writes. Source: https://docs.apimaker.dev/v1/docs/apis-all/query-params/select.html `select` keeps only the fields you name, or, with `-`, everything but the fields you name. Less data on the wire, and nothing a screen does not need. ```text title="Only these fields" GET /api/schema/admin/mysql8/inventory/customers?select=first_name,last_name ``` ```json title="Answer : the primary key always comes along" { "success": true, "statusCode": 200, "data": [ { "customer_id": 1, "first_name": "Bob", "last_name": "Lin" }, { "customer_id": 2, "first_name": "Alice", "last_name": "Page" } ] } ``` ```text title="Everything but these fields" GET /api/schema/admin/mysql8/inventory/customers?select=-last_update,-isActive ``` ```json title="Body of the query APIs : a string or an object" { "find": {}, "select": "first_name,last_name" } { "find": {}, "select": { "first_name": 1, "last_name": 1 } } ``` - On MongoDB a nested field selects too : `select=address.city`. - Do not mix included and excluded fields in one `select`. - The write APIs (save, master save, update by id, replace by id, remove by id) accept `select` to shape the row they answer with. - Inside a `deep` item, `select` picks the fields of the related rows : `deep=[{s_key:'customer_id',t_col:'orders',t_key:'customer_id',select:'order_no,total'}]`. - A field the group of the caller can not read never comes back, selected or not : that is the field permission of [API group permission](/v1/docs/apis-security/api-group-permission.html). deep → sort --- # deep Parameter (Deep Populate) > Replace ids with the rows they point to with the deep parameter of API Maker - from any table, database or instance, several levels deep, with a filter, a sort, a limit and a selection per level, one query per level. Source: https://docs.apimaker.dev/v1/docs/apis-all/query-params/deep.html `deep` replaces an id with the row it points to. The related table can be in the same database, in another one, or on another instance of another kind : a MongoDB collection populated from a PostgreSQL table works. API Maker reads the related rows with one query per level, whatever the number of parent rows. ```text title="Query param (JSON5)" GET /api/schema/admin/postgresql/inventory/cities?deep=[{s_key:'state_id',t_instance:'oracle',t_db:'inventory',t_col:'states',t_key:'id'}] ``` ```json title="Body of the query APIs" { "find": {}, "deep": [ { "s_key": "state_id", "t_instance": "oracle", "t_db": "inventory", "t_col": "states", "t_key": "id" } ] } ``` ## The tables of the examples | cities (PostgreSQL) | | | states (Oracle) | | | countries (MySQL) | | |---|---|---|---|---|---|---|---| | id | state_id | city_name | id | country_id | state_name | id | country_name | | 101 | 201 | AHMEDABAD | 201 | 301 | GUJARAT | 301 | INDIA | | 105 | 202 | KOCHI | 202 | 301 | KERALA | 302 | USA | | 110 | 204 | DUNCAN | 204 | 302 | ARIZONA | | | ## One level ```json { "find": { "id": 101 }, "deep": [ { "s_key": "state_id", "t_instance": "oracle", "t_db": "inventory", "t_col": "states", "t_key": "id" } ] } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "id": 101, "city_name": "AHMEDABAD", "state_id": { "id": 201, "country_id": 301, "state_name": "GUJARAT" } } ] } ``` ## The keys of a deep item | Key | Meaning | |---|---| | `s_key` | The field of the current table which holds the id (the **source key**). Required. | | `t_col` | The related table or collection (the **target**). Not needed when the schema gives it. | | `t_key` | The field of the related table to match. Its primary key when absent. | | `t_db`, `t_instance` | The database and the instance of the related table. The ones of the current table when absent. | | `isMultiple` | `true` : several related rows are expected, the field becomes an array. `false` (default) : one row, the field becomes an object. | | `find` | A filter on the related rows : `{ "status": "PAID" }`. | | `select` | The fields of the related rows to keep. | | `sort`, `skip`, `limit` | Order and paging of the related rows, per parent row. | | `deep` | The next level : an array of deep items on the related table. | | `fetchingTechnique` | For virtual fields : `chunk` (default, 1000 ids per query) or `one_by_one` (one query per parent, needed with `skip` and `limit` on big sets). `fetchingTechniqueSettings.chunkSize` changes the chunk. | ## Several levels ```json { "find": {}, "limit": 1, "deep": [ { "s_key": "state_id", "t_instance": "oracle", "t_db": "inventory", "t_col": "states", "t_key": "id", "deep": [ { "s_key": "country_id", "t_instance": "mysql", "t_db": "inventory", "t_col": "countries", "t_key": "id" } ] } ] } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "id": 101, "city_name": "AHMEDABAD", "state_id": { "id": 201, "state_name": "GUJARAT", "country_id": { "id": 301, "country_name": "INDIA" } } } ] } ``` - As many levels as you need. Each level is one query on its table, for all the parents at once. ## One to many ```json title="Every order of each customer, the latest 5, paid ones only" { "find": {}, "deep": [ { "s_key": "customer_id", "t_col": "orders", "t_key": "customer_id", "isMultiple": true, "find": { "status": "PAID" }, "sort": "-created_at", "limit": 5, "select": "order_no,total" } ] } ``` - `isMultiple: true` matches every related row and gives an array. ## Two relations at once ```json { "deep": [ { "s_key": "state_id", "t_col": "states", "t_key": "id" }, { "s_key": "created_by", "t_col": "users", "t_key": "id", "select": "name" } ] } ``` ## With the schema : just the source key When the [schema](/v1/docs/schema/schema.html) of the table names the relation on the field (`collection` or `table`, `column`, and `database`, `instance` when they differ), the deep item only needs `s_key`, and the target is never wrong twice : ```typescript title="cities.schema.ts" state_id: { __type: EType.number, instance: 'oracle', database: 'inventory', collection: 'states', column: 'id' }, ``` ```text GET /api/schema/admin/postgresql/inventory/cities?deep=[{s_key:'state_id'}] GET /api/schema/admin/postgresql/inventory/cities?deep=[{s_key:'state_id',deep:[{s_key:'country_id'}]}] ``` - A **virtual field** (`isVirtualField` with `s_columnVirtualLinker` and `t_columnVirtualLinker`) is a one to many relation stored on the other side : `deep=[{s_key:'orders'}]` on a customer fills its orders from the `orders` table, without a column on the customer. - The generated APIs (`/api/gen`) do not read the schema : give `t_col` and `t_key` in every deep item. ## On writes `deep` on save single or multiple, master save, update by id, replace by id and remove by id populates the row of the answer : ```text POST /api/schema/admin/mysql8/inventory/cities/save-single-or-multiple?deep=[{s_key:'state_id'}] ``` ## Flat answers The header `x-am-response-object-type: make_flat` turns the nested objects into one level with `_` between the names : `state_id_country_id_country_name`. See [request headers](/v1/docs/apis-all/header/requestHeader.html#x-am-response-object-type). ## Good to know - A related row the caller may not read (group permission) is left out, and a `deep` item which finds no table gives a `warning` in the answer, not an error. - Filtering the parent rows by a field of a related table is a different feature, [find and join](/v1/docs/apis-all/query-params/find.html#find-and-join-fields-of-related-tables), which combines with `deep`. --- # getTotalCount Parameter > Get the number of rows matching a filter next to the page you asked for with getTotalCount=true in API Maker - the total a pager needs, in the same call. Source: https://docs.apimaker.dev/v1/docs/apis-all/query-params/getTotalCount.html `getTotalCount=true` adds `totalCount` to the answer : the number of rows matching the filter, before `skip` and `limit`. One call gives the page and the total. ```text title="Query param" GET /api/schema/admin/mysql8/inventory/customers?find={isActive:1}&skip=0&limit=2&getTotalCount=true ``` ```json title="Body of the query APIs" { "find": { "isActive": 1 }, "skip": 0, "limit": 2, "getTotalCount": true } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "customer_id": 1, "first_name": "Bob", "last_name": "Lin" }, { "customer_id": 2, "first_name": "Alice", "last_name": "Page" } ], "totalCount": 5 } ``` - `false` or absent : no `totalCount` and no count query. - It works on get all, get all by stream, query and query by stream. The [count](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-count-api.html) API gives the number alone. - `totalCount` also appears in the answer of a stream, after the rows. - The count runs as a second query on the database ; on a very big table with a complex filter it costs as much as the query itself. upsert → skip --- # upsert Parameter > Insert the row when the id does not exist with upsert=true on update by id and replace by id in API Maker, on every database. Source: https://docs.apimaker.dev/v1/docs/apis-all/query-params/upsert.html `upsert=true` makes [update by id](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-update-by-id-api.html) and [replace by id](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-replace-by-id-api.html) insert a row when the id does not exist. Without it (the default, `false`), an unknown id changes nothing and answers `data: null`. ```text PUT /api/schema/admin/mysql8/inventory/customers/update-by-id/999?upsert=true ``` ```json title="Body" { "first_name": "John", "last_name": "Sina", "pincode": 382350, "isActive": 0 } ``` | Row 999 | What happens | Answer | |---|---|---| | exists | updated with the body | the row after the change | | does not exist | inserted with `customer_id: 999` and the body | the new row | - The inserted row is validated as a save : with a schema, every `required` field must be in the body. - Works on MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server and Oracle. - With `returnDocument=before` and an insert, the answer is `null` : there was no row before. - From code : `g.sys.db.updateById({ ..., upsert: true })`. returnDocument → getTotalCount --- # returnDocument Parameter > Choose whether update by id and replace by id answer with the row as it was before the change or as it is after it, with ?returnDocument=before or after. Source: https://docs.apimaker.dev/v1/docs/apis-all/query-params/returnDocument.html `returnDocument` decides which version of the row an **update by id** or a **replace by id** sends back : the row **after** the change (the default) or the row **before** it. | Value | The response holds | |---|---| | `after` (default) | The row as it is now, with the fields you changed. | | `before` | The row as it was, so you can show or log what changed. The update happens all the same. | ```text PUT /api/schema/admin/mysql8/inventory/customers/update-by-id/1?returnDocument=before ``` ```json title="Body" { "pincode": 382330 } ``` ```json title="Response with returnDocument=before" { "success": true, "statusCode": 200, "data": { "customer_id": 1, "first_name": "Bob", "last_name": "Lin", "pincode": 382345 } } ``` - It only changes the response, never what is written. - It works with `select` and `deep` : both apply to the version returned. - From code : `g.sys.db.updateById({ ..., returnDocument: 'before' })`. upsert Update by id --- # Get All API (schema) > Read the rows of a table with the schema get all API of API Maker - filter with find or any column, page with skip and limit, sort, select fields, populate related rows with deep, and get the total count. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-all-api.html Reads rows of a table. Without a query param it returns every row ; with `find`, `skip`, `limit`, `sort`, `select`, `deep` and `getTotalCount` it becomes the list API of your screens. | | | |---|---| | Method | GET | | URL | `/api/schema/admin/mysql8/inventory/customers` | | Body | none | | Query params | [find](/v1/docs/apis-all/query-params/find.html), [skip](/v1/docs/apis-all/query-params/skip.html), [limit](/v1/docs/apis-all/query-params/limit.html), [sort](/v1/docs/apis-all/query-params/sort.html), [select](/v1/docs/apis-all/query-params/select.html), [deep](/v1/docs/apis-all/query-params/deep.html), [getTotalCount](/v1/docs/apis-all/query-params/getTotalCount.html), any column | | Answer | `data` : an array of rows, `totalCount` with `getTotalCount=true` | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | yes, with `enableCaching` on the table | | From code | [`g.sys.db.getAll`](/v1/examples/sys/db/getAll.html) | | API id | `SCHEMA_GET_ALL` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Get all as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-get-all-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## Every row ```text GET /api/schema/admin/mysql8/inventory/customers ``` ```json { "success": true, "statusCode": 200, "data": [ { "customer_id": 1, "first_name": "Bob", "last_name": "lin", "last_update": "2022-11-14T04:34:58.000Z", "pincode": 382345, "isActive": 1 }, { "customer_id": 2, "first_name": "Alice", "last_name": "Page", "last_update": "2022-10-15T02:10:40.000Z", "pincode": 382346, "isActive": 1 } ] } ``` - Without `limit`, every row comes back. Add `limit` as soon as the table can grow. ## Filter with a column name Any column is a query param. The operators of [api-query-params](https://github.com/loris/api-query-params) work in the key : `>`, `>=`, `<`, `<=`, `!=`, a comma for a list, a regex between slashes. ```text GET /api/schema/admin/mysql8/inventory/customers?customer_id=1 GET /api/schema/admin/mysql8/inventory/customers?first_name=Bob&last_name=lin # both must match GET /api/schema/admin/mysql8/inventory/customers?first_name=Bob,Alice # one of them GET /api/schema/admin/mysql8/inventory/customers?pincode>=382346 GET /api/schema/admin/mysql8/inventory/customers?first_name!=Bob GET /api/schema/admin/mysql8/inventory/customers?first_name=/^ma/i # regular expression ``` - Numbers, booleans and ISO dates in a query param are cast to their type before the query. ## Filter with find `find` takes a JSON5 object with the MongoDB operators, on every database : `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$and`, `$or`, `$not`, `$like`, `$regex`, `$isNull`. See [find](/v1/docs/apis-all/query-params/find.html). ```text GET /api/schema/admin/mysql8/inventory/customers?find={first_name:'Bob'} GET /api/schema/admin/mysql8/inventory/customers?find={customer_id:{$in:[1,2,3]}} GET /api/schema/admin/mysql8/inventory/customers?find={$or:[{pincode:382345},{first_name:'Alice'}]} GET /api/schema/admin/mysql8/inventory/customers?find={first_name:{$like:'Ma%'}} ``` ## Page and sort ```text GET /api/schema/admin/mysql8/inventory/customers?skip=20&limit=10&sort=-last_update,first_name ``` - `sort` takes column names separated by commas, `-` for descending. The JSON form `sort={last_update:-1}` works too. - `skip` and `limit` are numbers. `limit` caps the rows of the answer ; `skip` leaves out the first rows. ## Select fields ```text GET /api/schema/admin/mysql8/inventory/customers?select=first_name,last_name # only these (the primary key stays) GET /api/schema/admin/mysql8/inventory/customers?select=-last_update,-isActive # everything but these ``` ## Related rows with deep `deep` replaces an id with the row it points to, from any table of any instance, as many levels as you want. See [deep](/v1/docs/apis-all/query-params/deep.html). ```text GET /api/schema/admin/mysql8/inventory/customers?deep=[{s_key:'customer_id',t_col:'orders',t_key:'customer_id',isMultiple:true,select:'order_no,total'}] ``` ```json { "success": true, "statusCode": 200, "data": [ { "customer_id": 1, "first_name": "Bob", "last_name": "lin", "orders": [ { "order_no": 1001, "total": 12900 } ] } ] } ``` ## Related rows from the schema When the schema of the table names the target of a field (`collection`, `column`, `database`, `instance`), the `deep` item only needs the source key : ```text GET /api/schema/admin/mysql8/inventory/customers?deep=[{s_key:'shipping_id'}] ``` ## Total count ```text GET /api/schema/admin/mysql8/inventory/customers?find={isActive:1}&skip=0&limit=10&getTotalCount=true ``` ```json { "success": true, "statusCode": 200, "data": [ "…10 rows…" ], "totalCount": 5 } ``` - `totalCount` counts every row matching the filter, before `skip` and `limit` : what a pager needs. ## Everything together ```text GET /api/schema/admin/mysql8/inventory/customers?find={first_name:'Bob'}&skip=1&limit=4&sort=-customer_id&select=first_name&deep=[{s_key:'customer_id',t_col:'orders',t_key:'customer_id'}]&getTotalCount=true ``` ## Caching, hooks, notifications - With [caching](/v1/docs/features/automatic-caching.html) on for the table, the answer comes from Redis until a write through API Maker changes the table ; the response header `x-am-data-source` says `cache` or `api`, and `x-am-cache-control: reset_cache` forces the database. - [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) can add conditions to `g.req.query.find` (for example the rows of the signed-in person) and post hooks can reshape `g.res.output`. When the answer comes from the cache, the hooks do not run. - Clients subscribed to this table with [WebSocket events](/v1/docs/pages/web-socket-event-page.html) are notified after a successful call. ## From your code [`g.sys.db.getAll`](/v1/examples/sys/db/getAll.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Get All by Stream API (schema) > Stream the rows of a table with the schema get all by stream API of API Maker - the same filters as get all, sent as they are read, for exports and large result sets without holding everything in memory. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-all-by-stream-api.html The same query as [get all](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-all-api.html), answered as a stream : rows go to the client as the database produces them, so a million rows cost the server a few kilobytes of memory, not the whole result. | | | |---|---| | Method | GET | | URL | `/api/schema/admin/mysql8/inventory/customers/stream` | | Body | none | | Query params | the same as get all : find, skip, limit, sort, select, deep, getTotalCount, any column | | Answer | `data` : an array of rows, streamed | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no : streams always come from the database | | From code | [`g.sys.db.getAllByStream`](/v1/examples/sys/db/getAllByStream.html) | | API id | `SCHEMA_GET_ALL_STREAM` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Get all by stream as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-get-all-by-stream-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers/stream` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text GET /api/schema/admin/mysql8/inventory/customers?find={isActive:1}&sort=customer_id&select=customer_id,first_name ``` ```json { "data": [ { "customer_id": 1, "first_name": "Bob" }, { "customer_id": 2, "first_name": "Alice" } ], "success": true, "statusCode": 200, "meta": {} } ``` - The envelope is the same as [get all](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-all-api.html), but `data` comes first and its rows are written in chunks while the query runs. `success`, `statusCode`, `meta` and `totalCount` close the answer. Parse it as JSON when it is complete, or read it with a streaming JSON parser as it arrives. - If the database fails while rows were already sent, the answer still ends as valid JSON : `], "success": false, "statusCode": 500, "errors": [...] }`. Read `success` at the end. ## When to use it - Exports, reports, synchronisations, copies of a table : anything which reads many rows and would not fit a normal answer. - Every filter of get all works : `find`, `sort`, `select`, `deep`, `skip`, `limit`. - The answer is never cached, and the rows are never compressed in memory : `x-am-data-source` is always `api`. ## Hooks on a stream - Pre hooks run before the query and can change it. - Post hooks run when the stream ends. They can not change what was sent, and `g.res.output` holds only the last chunk of rows, so do not use a post hook to reshape a stream : reshape in a custom API around `g.sys.db.getAllByStream`. ## From your code In a custom API the same call hands you every row as it arrives, without building the whole array : ```typescript linenums="1" let count = 0; await g.sys.db.getAllByStream({ instance: 'mysql8', database: 'inventory', collection: 'customers', queryParams: { find: { isActive: 1 } }, }, (row) => { count++; // one object at a time, in order }); return { count }; ``` ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Get by ID API (schema) > Read one row by its primary key with the schema get by id API of API Maker, or by any other column with the primaryKey param, with select and deep populate. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-by-id-api.html Reads one row by its primary key, or by any other column with the second param. With `select` and `deep`, it is the detail screen of your app in one call. | | | |---|---| | Method | GET | | URL | `/api/schema/admin/mysql8/inventory/customers/get-by-id/:id[/:primaryKey]` | | Body | none | | Query params | [select](/v1/docs/apis-all/query-params/select.html), [deep](/v1/docs/apis-all/query-params/deep.html), find (extra conditions) | | Answer | `data` : the row, or `null` when nothing matches | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | yes, with `enableCaching` on the table | | From code | [`g.sys.db.getById`](/v1/examples/sys/db/getById.html) | | API id | `SCHEMA_GET_BY_ID` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Get by id as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-get-by-id-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers/get-by-id/:id[/:primaryKey]` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## By primary key ```text GET /api/schema/admin/mysql8/inventory/customers/get-by-id/1 ``` ```json { "success": true, "statusCode": 200, "data": { "customer_id": 1, "first_name": "Bob", "last_name": "lin", "last_update": "2022-11-14T04:34:58.000Z", "pincode": 382345, "isActive": 1 } } ``` - The primary key is the one of the table (`_id` on MongoDB, the primary key column on SQL, or `isPrimaryKey` in the schema). An unknown id answers `data: null` with `success: true`. ## By any column Name the column as the second param : ```text GET /api/schema/admin/mysql8/inventory/customers/get-by-id/Bob/first_name GET /api/schema/admin/mysql8/inventory/customers/get-by-id/382345/pincode ``` - When several rows match, the first one is returned. - The value is converted to the type of the column, so `382345` is compared as a number. ## Fields and related rows ```text GET /api/schema/admin/mysql8/inventory/customers/get-by-id/1?select=first_name,last_name GET /api/schema/admin/mysql8/inventory/customers/get-by-id/1?deep=[{s_key:'customer_id',t_col:'orders',t_key:'customer_id',isMultiple:true}] ``` - `deep` works as on [get all](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-all-api.html#related-rows-with-deep), with the relations of the schema when the table has one. ## One more condition `find` narrows the match further, for example to make sure the row belongs to the caller : ```text GET /api/schema/admin/mysql8/inventory/customers/get-by-id/1?find={isActive:1} ``` - A pre hook can add such a condition to `g.req.query.find` for every call : the row scoping pattern of [Handle role based permissions](/v1/docs/authorization/handle-role-based-permissions.html). ## From your code [`g.sys.db.getById`](/v1/examples/sys/db/getById.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Save Single or Multiple API (schema) > Insert one row or many with the schema save single or multiple API of API Maker - one object or an array in the body, generated ids, and select or deep on the answer. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-save-single-or-multiple-api.html Inserts one row (an object) or many (an array) and answers `201` with what was saved, ids included. | | | |---|---| | Method | POST | | URL | `/api/schema/admin/mysql8/inventory/customers/save-single-or-multiple` | | Body | one object, or an array of objects | | Query params | [select](/v1/docs/apis-all/query-params/select.html), [deep](/v1/docs/apis-all/query-params/deep.html) : applied to the answer | | Answer | `201` and `data` : the saved row, or the array of saved rows | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.saveSingleOrMultiple`](/v1/examples/sys/db/saveSingleOrMultiple.html) | | API id | `SCHEMA_POST_BULK_INSERT` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Save single or multiple as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-save-single-or-multiple-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers/save-single-or-multiple` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## One row ```text POST /api/schema/admin/mysql8/inventory/customers/save-single-or-multiple ``` ```json title="Body" { "first_name": "Bob", "last_name": "Lin", "pincode": 382345 } ``` ```json title="Answer" { "success": true, "statusCode": 201, "data": { "customer_id": 6, "first_name": "Bob", "last_name": "Lin", "pincode": 382345 } } ``` - The primary key can be sent (`"customer_id": 27`) or left out : the database or API Maker generates it (`isAutoIncrementByDB`, `isAutoIncrementByAM`, `isAutoGenerateByAM` in the schema, `_id` on MongoDB). ## Many rows ```json title="Body" [ { "first_name": "Bob", "last_name": "Lin" }, { "customer_id": 29, "first_name": "Eve", "last_name": "Page" } ] ``` - The answer is an array in the same order. Objects with and without a primary key can mix. - Every row is checked before anything is written ; an error names the object with `dataIndex`. ## What the schema does to each row - Unknown keys are refused, values converted to their types, strings trimmed and cased, `conversionFun` run, fields encrypted or hashed, ids and defaults filled, then the rules and `validatorFun` run. Every error is returned at once with `400` and nothing is saved. See [Table schema](/v1/docs/schema/schema.html). - A field with a relation (`collection` and `column` in the schema) accepts a nested object : it is saved to its table first and replaced by its id. A failure reverts the rows saved so far. For nested updates and arrays, see [master save](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-master-save-api.html). ```json title="Body : the state is saved to the states table, then the city" { "city_name": "Wembley", "state_id": { "state_name": "London", "country_id": 302 } } ``` ## The answer you want ```text POST /api/schema/admin/mysql8/inventory/customers/save-single-or-multiple?select=customer_id,first_name POST /api/schema/admin/mysql8/inventory/customers/save-single-or-multiple?deep=[{s_key:'shipping_id',t_col:'shippings',t_key:'id'}] ``` - `select` keeps only some fields of the saved rows in the answer ; `deep` populates their relations. ## After the save - The cache of the table is reset, subscribed WebSocket clients are notified, events with an automatic trigger on this API run, and post hooks get the saved rows in `g.res.output`. ## From your code [`g.sys.db.saveSingleOrMultiple`](/v1/examples/sys/db/saveSingleOrMultiple.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Master Save API (schema) > Save or update a tree of related objects in one call with the schema master save API of API Maker - each object is inserted or updated by its primary key, nested objects go to their own tables, arrays fill one-to-many relations, and a failure reverts everything. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-master-save-api.html One call which saves or updates a whole object graph : each object is inserted when it has no primary key and updated when it has one, at every level of nesting, across tables and even databases. | | | |---|---| | Method | POST | | URL | `/api/schema/admin/mysql8/inventory/customers/master-save` | | Body | one object, or an array of objects, with nested related objects | | Query params | [select](/v1/docs/apis-all/query-params/select.html), [deep](/v1/docs/apis-all/query-params/deep.html) : applied to the answer | | Answer | `201` and `data` : the saved object(s), with the ids of the nested rows | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.masterSave`](/v1/examples/sys/db/masterSave.html) | | API id | `SCHEMA_MASTER_SAVE` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Master save as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-master-save-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers/master-save` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## Save or update ```text POST /api/schema/admin/mysql8/inventory/customers/master-save ``` ```json title="Body : no primary key, inserted" { "first_name": "Bob", "last_name": "Lin" } ``` ```json title="Body : a primary key which exists, updated with these fields" { "customer_id": 27, "pincode": 382330 } ``` - The rule is the same at every level : **primary key present, the row is read and updated ; absent, the row is inserted.** An update sends only the fields you give. - An array in the body saves or updates each object and answers an array. ## A tree of objects With relations in the schemas (`collection`, `column`, `database`, `instance` on a field), a nested object is saved or updated in its own table first and its id is written in the parent : ```json title="Body : three tables, one call" { "city_name": "Wembley", "state_id": { "state_name": "London", "country_id": { "country_name": "UK" } } } ``` ```json title="Answer" { "success": true, "statusCode": 201, "data": { "id": 107, "city_name": "Wembley", "state_id": 205 } } ``` - The country is saved first, its id goes into the state, the state is saved, its id goes into the city. The tables can be in different databases and instances : the relation says where. - `deep` on the call gives the nested rows back populated : `?deep=[{s_key:'state_id',deep:[{s_key:'country_id'}]}]`. ## One to many with virtual fields A [virtual field](/v1/docs/schema/schema.html) (`isVirtualField` with `s_columnVirtualLinker` and `t_columnVirtualLinker`) takes an array : each item is saved or updated in the target table with the key of the parent. ```json title="Body : an order and its items" { "order_no": 1001, "customer_id": 1, "items": [ { "product_id": "66fa1c9a9b1e4a0012a3c002", "qty": 2 }, { "id": 55, "qty": 3 } ] } ``` - The first item is inserted, the second (it has its primary key) is updated. ## When something fails - Every row written by the call is reverted when a later one fails : a wrong field in the country of the example above leaves no city and no state behind. - The errors name the object and the field, with `dataIndex` for arrays. ## Optimistic concurrency control - A table whose schema has a version field (`isConcurrencyControlField`) requires the version in every update of master save, and refuses a stale one with `400`. See [Optimistic concurrency control](/v1/docs/features/optimistic-concurrency-control.html). ## From your code [`g.sys.db.masterSave`](/v1/examples/sys/db/masterSave.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Array Operations API (schema) > Push, add to set, pull, pull all, pop and set elements of array fields in MongoDB documents with the schema array operations API of API Maker, several operations in one call. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-array-operations-api.html Changes array fields of MongoDB documents in place : push, addToSet, pull, pullAll, pop and set, on every document matching `find`, several operations in one call. | | | |---|---| | Method | PUT | | URL | `/api/schema/admin/mysql8/inventory/customers/array-operations` | | Body | `{ find, select?, operations: [ … ] }` | | Query params | none | | Answer | `data` : one array per operation with the documents matched, projected to the array (or `select`) | | Databases | MongoDB only | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.arrayOperations`](/v1/examples/sys/db/arrayOperations.html) | | API id | `SCHEMA_ARRAY_OPERATIONS` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Array operations as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-array-operations-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers/array-operations` : replace `admin` with the user path of your account, `mongodb` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The body ```json { "find": { "customer_id": 1 }, "operations": [ { "operation": "push", "path": "ages", "dataToPush": [ { "age": 33, "birth_year": 1989 } ] } ] } ``` | Key | Meaning | |---|---| | `find` | Which documents to change. `{}` : all of them. | | `select` | The fields to return for the matched documents. Without it, the array of `path`. | | `operations` | Run one after the other, each on every matched document. | | Operation | Keys | Does | |---|---|---| | `push` | `path`, `dataToPush` (array), `position`, `slice`, `sort` | Appends the items. `position` inserts at an index, `slice` keeps the first N (or last N when negative), `sort` (`{ field: 1 or -1 }`) reorders. | | `addToSet` | `path`, `dataToPush` (one value or an array) | Appends only the values which are not there yet. | | `pull` | `path`, `queryToRemove` | Removes every item matching the query. | | `pullAll` | `path`, `dataToPull` (array) | Removes every item equal to one of the values. | | `pop` | `path`, `direction` | `1` removes the last item, `-1` the first. | | `set` | `dataToSet`, `arrayFilters`, `upsert` | Updates fields of items with the positional `$[item]` syntax and its filters. | ## Push and pull ```json title="Push two items at the front, keep the array at 10" { "find": {}, "operations": [ { "operation": "push", "path": "ages", "dataToPush": [ { "age": 2 }, { "age": 5 } ], "position": 0, "slice": 10, "sort": { "age": 1 } } ] } ``` ```json title="Remove the items of a year" { "find": { "customer_id": 1 }, "operations": [ { "operation": "pull", "path": "ages", "queryToRemove": { "birth_year": 1989 } } ] } ``` ```json title="Remove exact values" { "find": {}, "operations": [ { "operation": "pullAll", "path": "tags", "dataToPull": [ "old", "draft" ] } ] } ``` ## Set inside an array ```json { "find": {}, "operations": [ { "operation": "set", "dataToSet": { "ages.$[item].age": 45, "ages.$[item].birth_year": 1999 }, "arrayFilters": [ { "item.birth_year": 1999 } ], "upsert": true } ] } ``` - `arrayFilters` decide which items `$[item]` means, `upsert: true` inserts a document when `find` matches none. ## Several operations at once ```json { "find": {}, "operations": [ { "operation": "push", "path": "ages", "dataToPush": [ { "age": 60 } ] }, { "operation": "pull", "path": "ages", "queryToRemove": { "age": 2 } } ] } ``` ```json title="Answer : one entry per operation" { "success": true, "statusCode": 200, "data": [ [ { "_id": "63e0775abd0e063920533f7c", "ages": [ { "age": 33 }, { "age": 60 } ] } ], [ { "_id": "63e0775abd0e063920533f7c", "ages": [ { "age": 33 }, { "age": 60 } ] } ] ] } ``` ## With a schema - The items pushed, added or set are converted and validated against the schema of the array field before they are written, like a save. Encrypted fields inside an array are handled too. ## From your code [`g.sys.db.arrayOperations`](/v1/examples/sys/db/arrayOperations.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Update by ID API (schema) > Change some fields of one row with the schema update by id API of API Maker - by primary key or any column, with upsert, returnDocument, select and deep, and the version check of optimistic concurrency control. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-update-by-id-api.html Changes the fields you send on one row and leaves the others alone. The row is found by its primary key, or by the column you name. | | | |---|---| | Method | PUT | | URL | `/api/schema/admin/mysql8/inventory/customers/update-by-id/:id[/:primaryKey]` | | Body | the fields to change | | Query params | [select](/v1/docs/apis-all/query-params/select.html), [deep](/v1/docs/apis-all/query-params/deep.html), [upsert](/v1/docs/apis-all/query-params/upsert.html), [returnDocument](/v1/docs/apis-all/query-params/returnDocument.html), throwErrorIfRecordNotFound, find | | Answer | `data` : the row after the change (or before, with `returnDocument=before`) | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.updateById`](/v1/examples/sys/db/updateById.html) | | API id | `SCHEMA_PUT_UPDATE_BY_ID` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Update by id as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-update-by-id-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers/update-by-id/:id[/:primaryKey]` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## Some fields ```text PUT /api/schema/admin/mysql8/inventory/customers/update-by-id/1 ``` ```json title="Body" { "pincode": 382330 } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": { "customer_id": 1, "first_name": "Bob", "last_name": "lin", "last_update": "2022-11-14T04:34:58.000Z", "pincode": 382330, "isActive": 1 } } ``` - Send only what changes. Several fields at once are fine. On MongoDB, a nested key like `"address.city"` updates that path. ## By another column ```text PUT /api/schema/admin/mysql8/inventory/customers/update-by-id/Bob/first_name ``` - The row whose `first_name` is `Bob` is updated. The column named as the key can itself be in the body : `/update-by-id/Mallory/first_name` with `{ "first_name": "Alice" }` renames her. ## Insert when missing : upsert ```text PUT /api/schema/admin/mysql8/inventory/customers/update-by-id/999?upsert=true ``` ```json title="Body" { "first_name": "Daphney", "last_name": "Sonia", "pincode": 980220 } ``` - No row with `customer_id` 999 : one is inserted with the id and the body (the body is validated as a save). A row exists : it is updated. On every database. ## The row before the change ```text PUT /api/schema/admin/mysql8/inventory/customers/update-by-id/1?returnDocument=before ``` - The answer holds the row as it was ; the update happens all the same. Default : `after`. See [returnDocument](/v1/docs/apis-all/query-params/returnDocument.html). ## Unknown id - By default an unknown id answers `success: true` with `data: null` and changes nothing. With `?throwErrorIfRecordNotFound=true` it answers `404` and `Record not found.` - `find` adds conditions the row must match : `?find={isActive:1}`. A pre hook can add the condition for every call. ## Fields and related rows in the answer ```text PUT /api/schema/admin/mysql8/inventory/customers/update-by-id/1?select=first_name,last_name&deep=[{s_key:'customer_id',t_col:'orders',t_key:'customer_id'}] ``` ## What the schema does - The fields sent are converted and validated like a save (types, trim, case, `conversionFun`, encryption, rules, `validatorFun`) ; unknown keys are refused ; `required` only applies to the fields present. Every error comes back at once with `400`. - A nested object on a relation field is saved or updated in its table and replaced by its id, as in [master save](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-master-save-api.html). - **Version check.** When the schema has a field with `isConcurrencyControlField`, the body must carry it and its value must be the one of the row : otherwise `400` with `Concurrency version mismatch in 'version'. This row/document is already updated.` and nothing changes. See [Optimistic concurrency control](/v1/docs/features/optimistic-concurrency-control.html). ## From your code [`g.sys.db.updateById`](/v1/examples/sys/db/updateById.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Update Many API (schema) > Change every row matching a filter in one statement with the schema update many API of API Maker - a find and an updateData in the body, the count of changed rows in the answer. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-update-many-api.html Applies the same change to every row matching `find`, in one statement of the database, and answers how many rows changed. | | | |---|---| | Method | PUT | | URL | `/api/schema/admin/mysql8/inventory/customers/update-many` | | Body | `{ find, updateData }` | | Query params | none | | Answer | `data` : `{ updatedRowsCount }` | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.updateMany`](/v1/examples/sys/db/updateMany.html) | | API id | `SCHEMA_UPDATE_MANY` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Update many as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-update-many-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers/update-many` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text PUT /api/schema/admin/mysql8/inventory/customers/update-many ``` ```json title="Body" { "find": { "customer_id": { "$in": [ 2, 3 ] } }, "updateData": { "last_name": "Brown" } } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": { "updatedRowsCount": 2 } } ``` - `find` takes every operator of [find](/v1/docs/apis-all/query-params/find.html) : `$in`, `$gt`, `$and`, `$or`, `$like`, dotted keys… `{}` updates every row. - `updatedRowsCount` counts the rows the database actually changed. ## More filters ```json title="$and" { "find": { "$and": [ { "isActive": 1 }, { "pincode": { "$gt": 382345 } } ] }, "updateData": { "isActive": 0 } } ``` ```json title="$or" { "find": { "$or": [ { "first_name": "Bob" }, { "first_name": "Alice" } ] }, "updateData": { "pincode": 380001 } } ``` ```json title="a related table (find and join)" { "find": { "orders.status": "CANCELLED" }, "updateData": { "isActive": 0 } } ``` ## What the schema does - `updateData` is converted and validated like an update (types, `conversionFun`, encryption, rules) and `find` like a query, before the statement runs. - The version check of optimistic concurrency control does not apply here : the rows are changed directly in the database, without being read first. ## Good to know - Nothing is read back : the answer has no rows. Read them with [query](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-query-for-get-data-api.html) if you need them. - The cache of the table is reset and WebSocket subscribers are notified once for the call. ## From your code [`g.sys.db.updateMany`](/v1/examples/sys/db/updateMany.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Replace by ID API (schema) > Replace a whole MongoDB document with the schema replace by id API of API Maker - by primary key or any column, with upsert, returnDocument, select and deep. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-replace-by-id-api.html Replaces one MongoDB document with the body : fields you do not send are gone. For a partial change, use [update by id](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-update-by-id-api.html). | | | |---|---| | Method | PUT | | URL | `/api/schema/admin/mongodb/inventory/customers/replace-by-id/:id[/:primaryKey]` | | Body | the whole new document | | Query params | [select](/v1/docs/apis-all/query-params/select.html), [deep](/v1/docs/apis-all/query-params/deep.html), [upsert](/v1/docs/apis-all/query-params/upsert.html), [returnDocument](/v1/docs/apis-all/query-params/returnDocument.html), throwErrorIfRecordNotFound | | Answer | `data` : the document after the change (or before, with `returnDocument=before`) | | Databases | MongoDB only | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.replaceById`](/v1/examples/sys/db/replaceById.html) | | API id | `SCHEMA_PUT_REPLACE_BY_ID` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Replace by id as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-replace-by-id-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mongodb/inventory/customers/replace-by-id/:id[/:primaryKey]` : replace `admin` with the user path of your account, `mongodb` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text PUT /api/schema/admin/mongodb/inventory/customers/replace-by-id/60ae24c8c37cd955cc144162 ``` ```json title="Body" { "customer_id": 1, "first_name": "Rex", "last_name": "Nestor", "pincode": 879654, "isActive": 0 } ``` - The document keeps its `_id` and gets exactly these fields. A field of the old document which is not in the body disappears. - By another column : `/replace-by-id/4/customer_id`. ## Options - `?upsert=true` inserts the document when the id does not exist. - `?returnDocument=before` answers the old document. - `?throwErrorIfRecordNotFound=true` answers `404` instead of `data: null` for an unknown id. - `select` and `deep` shape the answer. ## What the schema does - The body is treated as a full save : conversions, defaults, generated values and every rule apply, `required` fields must all be there, and the version field of [optimistic concurrency control](/v1/docs/features/optimistic-concurrency-control.html) must match the document. ## SQL tables - Replace by id exists only on MongoDB (`Replace by id API is not supported for instance type …` otherwise). On a SQL table, [update by id](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-update-by-id-api.html) with every column does the same. ## From your code [`g.sys.db.replaceById`](/v1/examples/sys/db/replaceById.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Remove by ID API (schema) > Delete one row by its primary key or any column with the schema remove by id API of API Maker, and get the removed row back, with select and deep. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-remove-by-id-api.html Deletes one row and answers with the row as it was, so the caller can show or log what disappeared. | | | |---|---| | Method | DELETE | | URL | `/api/schema/admin/mysql8/inventory/customers/:id[/:primaryKey]` | | Body | none | | Query params | [select](/v1/docs/apis-all/query-params/select.html), [deep](/v1/docs/apis-all/query-params/deep.html), throwErrorIfRecordNotFound, find | | Answer | `data` : the removed row, or `null` when no row matched | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.removeById`](/v1/examples/sys/db/removeById.html) | | API id | `SCHEMA_DEL_DELETE_BY_ID` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Remove by id as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-remove-by-id-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers/:id[/:primaryKey]` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text DELETE /api/schema/admin/mysql8/inventory/customers/1 ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": { "customer_id": 1, "first_name": "Bob", "last_name": "lin", "pincode": 382345, "isActive": 1 } } ``` - The id is the primary key. `DELETE /api/schema/admin/mysql8/inventory/customers/Bob/first_name` removes the first row whose `first_name` is `Bob`. ## Options - `?select=first_name` and `?deep=[…]` shape the returned row (deep populates it before it is removed). - `?throwErrorIfRecordNotFound=true` answers `404` and `Record not found.` for an unknown id ; the default is `data: null`. - `?find={isActive:0}` adds a condition : the row is removed only when it matches. A pre hook can add such a condition to keep callers to their own rows. ## After the call - The cache of the table is reset, WebSocket subscribers are notified, post hooks receive the removed row in `g.res.output`. - To remove many rows at once, use [remove by query](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-remove-by-query-api.html). ## From your code [`g.sys.db.removeById`](/v1/examples/sys/db/removeById.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Query for Get Data API (schema) > Read rows with a JSON query in the body with the schema query API of API Maker - find with operators, find and join across tables, select, sort, paging, deep populate and total count. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-query-for-get-data-api.html The read API for queries too long or too private for a URL : the same `find`, `select`, `sort`, `skip`, `limit`, `deep` and `getTotalCount` as get all, in the body. | | | |---|---| | Method | POST | | URL | `/api/schema/admin/mysql8/inventory/customers/query` | | Body | `{ find, select, sort, skip, limit, deep, getTotalCount }` | | Query params | none | | Answer | `data` : an array of rows, `totalCount` with `getTotalCount: true` | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | yes, with `enableCaching` on the table | | From code | [`g.sys.db.query`](/v1/examples/sys/db/query.html) | | API id | `SCHEMA_POST_QUERY` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Query for get data as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-query-for-get-data-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers/query` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The body | Key | Meaning | |---|---| | `find` | Required. The filter, with every operator of [find](/v1/docs/apis-all/query-params/find.html). `{}` for everything. | | `select` | Fields to return : `"first_name,last_name"`, `"-password"`, or `{ first_name: 1 }`. | | `sort` | `"-last_update,first_name"` or `{ last_update: -1 }`. | | `skip`, `limit` | Paging. | | `deep` | Related rows, see [deep](/v1/docs/apis-all/query-params/deep.html). | | `getTotalCount` | `true` adds `totalCount`, the number of rows matching `find` before paging. | ```text POST /api/schema/admin/mysql8/inventory/customers/query ``` ```json title="Body" { "find": { "isActive": 1, "pincode": { "$gte": 382345 } }, "select": "customer_id,first_name,last_name", "sort": "-customer_id", "skip": 0, "limit": 10, "getTotalCount": true } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "customer_id": 5, "first_name": "Eve", "last_name": "Page" } ], "totalCount": 5 } ``` ## Operators Every operator works on every database ; API Maker translates them to SQL where needed. | Operator | Example | Meaning | |---|---|---| | `$eq`, `$ne` | `{ "first_name": { "$ne": "Bob" } }` | equal, not equal (a plain value means `$eq`) | | `$gt`, `$gte`, `$lt`, `$lte` | `{ "pincode": { "$gte": 382345 } }` | comparisons on numbers, strings and ISO dates | | `$in`, `$nin` | `{ "customer_id": { "$in": [ 1, 2 ] } }` | in a list, not in a list (a non empty array) | | `$and`, `$or` | `{ "$or": [ { "customer_id": 1 }, { "pincode": 382345 } ] }` | combine conditions (an array) | | `$not` | `{ "customer_id": { "$not": { "$in": [ 1, 2 ] } } }` | negate an operator | | `$like` | `{ "first_name": { "$like": "Ma%" } }` | SQL like pattern, `%` and `_` | | `$regex` | `{ "first_name": { "$regex": "^Ma" } }` | regular expression | | `$isNull` | `{ "last_update": { "$isNull": true } }` | null (or not null with `false`), SQL databases | - On SQL databases, two operators on one field (`{ "$gte": 1, "$lte": 9 }`) are refused : write them as two conditions in `$and`. ## Find and join A dotted key in `find` filters on a field of a **related** table, across databases and instances : ```json title="Customers whose orders have status PAID" { "find": { "orders.status": "PAID" }, "deep": [ { "s_key": "customer_id", "t_col": "orders", "t_key": "customer_id", "isMultiple": true } ] } ``` - The relation comes from the `deep` item of the call, or from the schema of the table. It goes as many levels as the dots : `"orders.items.product.category": "Books"`. - The rows of the related table are read through their own query API, with the permissions of the caller. - See [find and join](/v1/docs/apis-all/query-params/find.html#find-and-join-fields-of-related-tables) for every rule. ## Deep populate ```json { "find": {}, "limit": 5, "deep": [ { "s_key": "customer_id", "t_col": "orders", "t_key": "customer_id", "isMultiple": true, "select": "order_no,total", "deep": [ { "s_key": "shipping_id", "t_col": "shippings", "t_key": "id" } ] } ] } ``` - Two items in `deep` populate two fields ; `deep` inside an item goes one level further. With relations in the schema, `{ "s_key": "customer_id" }` is enough. ## Types in filters - The values of `find` are converted to the types of the schema before the query : `"customer_id": "8"` matches the number 8. ## Caching - With caching on for the table, the same body answers from Redis until a write changes the table. `x-am-cache-control: reset_cache` forces the database. ## From your code [`g.sys.db.query`](/v1/examples/sys/db/query.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Query for Get Data by Stream API (schema) > Stream the rows of a JSON query with the schema query by stream API of API Maker - the body of the query API, the answer of get all by stream, for exports and big result sets. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-query-for-get-data-by-stream-api.html The body of [query for get data](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-query-for-get-data-api.html) with the answer of [get all by stream](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-all-by-stream-api.html) : rows are sent as the database produces them. | | | |---|---| | Method | POST | | URL | `/api/schema/admin/mysql8/inventory/customers/query-stream` | | Body | `{ find, select, sort, skip, limit, deep, getTotalCount }` | | Query params | none | | Answer | `data` : an array of rows, streamed | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no : streams always come from the database | | From code | [`g.sys.db.queryByStream`](/v1/examples/sys/db/queryByStream.html) | | API id | `SCHEMA_POST_QUERY_STREAM` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Query for get data by stream as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-query-for-get-data-by-stream-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers/query-stream` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text POST /api/schema/admin/mysql8/inventory/customers/query-stream ``` ```json title="Body" { "find": { "isActive": 1 }, "sort": "customer_id", "select": "customer_id,first_name" } ``` ```json title="Answer, written in chunks" { "data": [ { "customer_id": 1, "first_name": "Bob" }, { "customer_id": 2, "first_name": "Alice" } ], "success": true, "statusCode": 200, "meta": {} } ``` - Same keys and operators as the query API, find and join and deep included. Read `success` at the end : an error after the first rows closes the answer with `"success": false` and the errors. - Never cached ; post hooks can not change what was sent. ## From your code ```typescript linenums="1" const out: any[] = []; await g.sys.db.queryByStream({ instance: 'mysql8', database: 'inventory', collection: 'customers', find: { isActive: 1 }, select: 'customer_id,first_name', }, (row) => out.push(row)); ``` ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Remove by Query API (schema) > Delete every row matching a filter with the schema remove by query API of API Maker - a find in the body, the removed rows, their count and their ids in the answer. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-remove-by-query-api.html Deletes every row matching `find` and answers with the rows removed, their ids and their count. A body without `find` is refused : nothing is ever deleted without a filter. | | | |---|---| | Method | POST | | URL | `/api/schema/admin/mysql8/inventory/customers/query/delete` | | Body | `{ find, select? }` | | Query params | none | | Answer | `data` : `{ deletedRows, deletedRowsCount, ids }` | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.removeByQuery`](/v1/examples/sys/db/removeByQuery.html) | | API id | `SCHEMA_POST_QUERY_DELETE` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Remove by query as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-remove-by-query-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers/query/delete` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text POST /api/schema/admin/mysql8/inventory/customers/query/delete ``` ```json title="Body" { "find": { "isActive": 0 } } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": { "deletedRows": [ { "customer_id": 7, "first_name": "Old", "last_name": "Row", "isActive": 0 } ], "deletedRowsCount": 1, "ids": [ 7 ] } } ``` - `find` takes every operator of [find](/v1/docs/apis-all/query-params/find.html), including dotted keys of related tables. `{}` matches everything. - The rows are read before the delete, so `deletedRows` holds them as they were ; `select` keeps only some of their fields. ## More filters ```json { "find": { "customer_id": { "$in": [ 2, 3, 4 ] } } } ``` ```json { "find": { "$and": [ { "isActive": 0 }, { "last_update": { "$lt": "2022-01-01T00:00:00.000Z" } } ] } } ``` ```json { "find": { "orders.status": "CANCELLED" } } ``` ## Missing find ```json { "success": false, "statusCode": 400, "errors": [ { "code": 400, "message": "Please provide 'find' property having query to remove data." } ] } ``` ## After the call - The cache of the table is reset and WebSocket subscribers are notified once. Post hooks receive the answer in `g.res.output`. ## From your code [`g.sys.db.removeByQuery`](/v1/examples/sys/db/removeByQuery.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Aggregate API (schema) > Run a MongoDB aggregation pipeline through the schema aggregate API of API Maker - $match, $group, $project, $addFields, $bucket, $facet, $lookup, $count and the other stages, with the permissions of the caller. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-aggregate-api.html Runs a MongoDB aggregation pipeline on a collection : the body is the array of stages, the answer their result. | | | |---|---| | Method | POST | | URL | `/api/schema/admin/mongodb/inventory/customers/aggregate` | | Body | the pipeline : an array of stages | | Query params | none | | Answer | `data` : the documents produced by the pipeline | | Databases | MongoDB only | | Cached | yes, with `enableCaching` on the table | | From code | [`g.sys.db.aggregate`](/v1/examples/sys/db/aggregate.html) | | API id | `SCHEMA_POST_AGGREGATE` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Aggregate as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-aggregate-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mongodb/inventory/customers/aggregate` : replace `admin` with the user path of your account, `mongodb` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text POST /api/schema/admin/mongodb/inventory/customers/aggregate ``` ```json title="Body : orders per customer, the biggest first" [ { "$match": { "status": "PAID" } }, { "$group": { "_id": "$customer_id", "orders": { "$sum": 1 }, "total": { "$sum": "$total" } } }, { "$sort": { "total": -1 } }, { "$limit": 10 } ] ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "_id": 1, "orders": 4, "total": 51600 } ] } ``` - A body which is not an array is refused with `Please provide array for aggregate body.` ## Stages Every stage of the MongoDB version you run is accepted. The ones used most : | Stage | Example | |---|---| | `$match` | `{ "$match": { "pincode": { "$gt": 380000 } } }` | | `$group` | `{ "$group": { "_id": "$pincode", "count": { "$sum": 1 } } }` | | `$project` | `{ "$project": { "name": { "$toLower": "$first_name" } } }` | | `$addFields` | `{ "$addFields": { "full_name": { "$concat": [ "$first_name", " ", "$last_name" ] } } }` | | `$sort`, `$skip`, `$limit` | `{ "$sort": { "total": -1 } }` | | `$count` | `{ "$count": "customers" }` | | `$bucket` | `{ "$bucket": { "groupBy": "$price", "boundaries": [ 0, 100, 500 ], "default": "Other", "output": { "count": { "$sum": 1 } } } }` | | `$facet` | several pipelines in one : `{ "$facet": { "byPrice": [ … ], "byCategory": [ … ] } }` | | `$lookup` | a join inside MongoDB : `{ "$lookup": { "from": "orders", "localField": "customer_id", "foreignField": "customer_id", "as": "orders" } }` | | `$unwind` | `{ "$unwind": "$orders" }` | | `$collStats` | `{ "$collStats": { "storageStats": {}, "count": {} } }` | ## With a schema - The `find` of a first `$match` stage is converted with the schema (types, ObjectIds, dotted keys of relations), so `{ "$match": { "orders.status": "PAID" } }` filters through the related collection as a find and join. ## Good to know - Only MongoDB : SQL databases answer `This API is only supported for mongodb.` For SQL, run a query with [executeQuery](/v1/examples/sys/system/executeQuery.html) from a custom API. - Field permissions of the groups apply to what a stage returns. A caller who can not read a field does not get it, projected or not. - With caching on, a pipeline answers from Redis until the collection changes. ## From your code [`g.sys.db.aggregate`](/v1/examples/sys/db/aggregate.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Count API (schema) > Count the rows matching a filter with the schema count API of API Maker - a find in the body, a number in the answer, on every database. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-count-api.html Answers how many rows match `find`, without reading them. | | | |---|---| | Method | POST | | URL | `/api/schema/admin/mysql8/inventory/customers/count` | | Body | `{ find }` | | Query params | none | | Answer | `data` : the number of rows | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | yes, with `enableCaching` on the table | | From code | [`g.sys.db.count`](/v1/examples/sys/db/count.html) | | API id | `SCHEMA_POST_COUNT` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Count as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-count-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers/count` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text POST /api/schema/admin/mysql8/inventory/customers/count ``` ```json title="Body" { "find": { "isActive": 1 } } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": 5 } ``` - `find` takes every operator of [find](/v1/docs/apis-all/query-params/find.html) : `$gt`, `$in`, `$and`, `$or`, `$like`, dotted keys of related tables… `{ "find": {} }` counts the table. ## Examples ```json { "find": { "pincode": { "$gte": 382345 } } } ``` ```json { "find": { "$or": [ { "first_name": "Bob" }, { "first_name": "Alice" } ] } } ``` ```json { "find": { "first_name": { "$like": "%all%" } } } ``` ```json title="rows whose orders are paid" { "find": { "orders.status": "PAID" } } ``` ## Types in the filter - The values are converted with the schema : `"pincode": "100050"` counts the number 100050. ## Good to know - For a list with its total in one call, [query](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-query-for-get-data-api.html) with `getTotalCount: true` saves the second request. - With caching on, the count answers from Redis until a write changes the table. ## From your code [`g.sys.db.count`](/v1/examples/sys/db/count.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Distinct API (schema) > List the distinct values of one or several columns with the schema distinct API of API Maker - fields and their order in the path, an optional find filter, on every database. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-distinct-api.html Lists the distinct values of a column, or the distinct combinations of several, sorted the way you ask. | | | |---|---| | Method | GET | | URL | `/api/schema/admin/mysql8/inventory/customers/distinct/:field[/:order]` | | Body | none | | Query params | [find](/v1/docs/apis-all/query-params/find.html) | | Answer | `data` : an array of objects, one per distinct value or combination | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | yes, with `enableCaching` on the table | | From code | [`g.sys.db.distinct`](/v1/examples/sys/db/distinct.html) | | API id | `SCHEMA_GET_DISTINCT` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Distinct as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-distinct-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers/distinct/:field[/:order]` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## One column ```text GET /api/schema/admin/mysql8/inventory/customers/distinct/pincode ``` ```json { "success": true, "statusCode": 200, "data": [ { "pincode": 382345 }, { "pincode": 382346 }, { "pincode": 382347 } ] } ``` - Ascending by default. Descending : `/api/schema/admin/mysql8/inventory/customers/distinct/pincode/desc` (also `-1`, `dsc`, `0`, `false` ; ascending is `asc`, `1`, `true`). ## Several columns ```text GET /api/schema/admin/mysql8/inventory/customers/distinct/first_name,pincode GET /api/schema/admin/mysql8/inventory/customers/distinct/first_name,pincode/asc,desc ``` ```json { "success": true, "statusCode": 200, "data": [ { "first_name": "Alice", "pincode": 382346 }, { "first_name": "Bob", "pincode": 382345 }, { "first_name": "Eve", "pincode": 382349 }, { "first_name": "Eve", "pincode": 382348 } ] } ``` - One order per column, in the same position. A missing order is ascending. ## With a filter ```text GET /api/schema/admin/mysql8/inventory/customers/distinct/pincode?find={isActive:1} ``` - The same `find` as get all. For a filter in the body, use [distinct with query](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-distinct-with-query-api.html). ## Good to know - A column which is empty everywhere gives `[]`. - Field permissions apply : a column the group can not read can not be listed. ## From your code [`g.sys.db.distinct`](/v1/examples/sys/db/distinct.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Distinct with Query API (schema) > List the distinct values of columns for the rows matching a find in the body with the schema distinct with query API of API Maker. Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-distinct-with-query-api.html [Distinct](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-distinct-api.html) with the filter in the body : the distinct values of a column among the rows matching `find`. | | | |---|---| | Method | POST | | URL | `/api/schema/admin/mysql8/inventory/customers/distinct/:field[/:order]` | | Body | `{ find }` | | Query params | none | | Answer | `data` : an array of objects, one per distinct value or combination | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | yes, with `enableCaching` on the table | | From code | [`g.sys.db.distinctQuery`](/v1/examples/sys/db/distinctQuery.html) | | API id | `SCHEMA_POST_DISTINCT_QUERY` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Distinct with query as a generated API](/v1/docs/apis-all/generated-apis/auto-generated-distinct-with-query-api.html) | !!! info "Generated (`/api/gen`) or schema (`/api/schema`) ?" Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema. ## The URL `/api/schema/admin/mysql8/inventory/customers/distinct/:field[/:order]` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text POST /api/schema/admin/mysql8/inventory/customers/distinct/customer_id/asc ``` ```json title="Body" { "find": { "pincode": { "$in": [ 382345, 382346 ] } } } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "customer_id": 1 }, { "customer_id": 2 } ] } ``` - Fields and orders in the path work as in [distinct](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-distinct-api.html) : `first_name,pincode/asc,desc`. - `{ "find": {} }` lists every distinct value. ## Examples ```json { "find": { "pincode": { "$gte": 382347 } } } ``` ```json { "find": { "$or": [ { "pincode": 382345 }, { "customer_id": 4 } ] } } ``` ```json { "find": { "first_name": { "$like": "%llory" } } } ``` ## Types in the filter - The values of `find` are converted with the schema : `"pincode": "382350"` matches the number. ## From your code [`g.sys.db.distinctQuery`](/v1/examples/sys/db/distinctQuery.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) - [Table schema](/v1/docs/schema/schema.html) : what the conversions and validations do to every write. --- # Get All API (generated) > Read the rows of a table with the generated get all API of API Maker - filter with find or any column, page with skip and limit, sort, select fields, populate related rows with deep, and get the total count. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-get-all-api.html Reads rows of a table. Without a query param it returns every row ; with `find`, `skip`, `limit`, `sort`, `select`, `deep` and `getTotalCount` it becomes the list API of your screens. | | | |---|---| | Method | GET | | URL | `/api/gen/admin/mysql8/inventory/customers` | | Body | none | | Query params | [find](/v1/docs/apis-all/query-params/find.html), [skip](/v1/docs/apis-all/query-params/skip.html), [limit](/v1/docs/apis-all/query-params/limit.html), [sort](/v1/docs/apis-all/query-params/sort.html), [select](/v1/docs/apis-all/query-params/select.html), [deep](/v1/docs/apis-all/query-params/deep.html), [getTotalCount](/v1/docs/apis-all/query-params/getTotalCount.html), any column | | Answer | `data` : an array of rows, `totalCount` with `getTotalCount=true` | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | yes, with `enableCaching` on the table | | From code | [`g.sys.db.gen.getAllGen`](/v1/examples/sys/db/gen/getAllGen.html) | | API id | `GEN_GET_ALL` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Get all as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-all-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## Every row ```text GET /api/gen/admin/mysql8/inventory/customers ``` ```json { "success": true, "statusCode": 200, "data": [ { "customer_id": 1, "first_name": "Bob", "last_name": "lin", "last_update": "2022-11-14T04:34:58.000Z", "pincode": 382345, "isActive": 1 }, { "customer_id": 2, "first_name": "Alice", "last_name": "Page", "last_update": "2022-10-15T02:10:40.000Z", "pincode": 382346, "isActive": 1 } ] } ``` - Without `limit`, every row comes back. Add `limit` as soon as the table can grow. ## Filter with a column name Any column is a query param. The operators of [api-query-params](https://github.com/loris/api-query-params) work in the key : `>`, `>=`, `<`, `<=`, `!=`, a comma for a list, a regex between slashes. ```text GET /api/gen/admin/mysql8/inventory/customers?customer_id=1 GET /api/gen/admin/mysql8/inventory/customers?first_name=Bob&last_name=lin # both must match GET /api/gen/admin/mysql8/inventory/customers?first_name=Bob,Alice # one of them GET /api/gen/admin/mysql8/inventory/customers?pincode>=382346 GET /api/gen/admin/mysql8/inventory/customers?first_name!=Bob GET /api/gen/admin/mysql8/inventory/customers?first_name=/^ma/i # regular expression ``` - Numbers, booleans and ISO dates in a query param are cast to their type before the query. ## Filter with find `find` takes a JSON5 object with the MongoDB operators, on every database : `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$and`, `$or`, `$not`, `$like`, `$regex`, `$isNull`. See [find](/v1/docs/apis-all/query-params/find.html). ```text GET /api/gen/admin/mysql8/inventory/customers?find={first_name:'Bob'} GET /api/gen/admin/mysql8/inventory/customers?find={customer_id:{$in:[1,2,3]}} GET /api/gen/admin/mysql8/inventory/customers?find={$or:[{pincode:382345},{first_name:'Alice'}]} GET /api/gen/admin/mysql8/inventory/customers?find={first_name:{$like:'Ma%'}} ``` ## Page and sort ```text GET /api/gen/admin/mysql8/inventory/customers?skip=20&limit=10&sort=-last_update,first_name ``` - `sort` takes column names separated by commas, `-` for descending. The JSON form `sort={last_update:-1}` works too. - `skip` and `limit` are numbers. `limit` caps the rows of the answer ; `skip` leaves out the first rows. ## Select fields ```text GET /api/gen/admin/mysql8/inventory/customers?select=first_name,last_name # only these (the primary key stays) GET /api/gen/admin/mysql8/inventory/customers?select=-last_update,-isActive # everything but these ``` ## Related rows with deep `deep` replaces an id with the row it points to, from any table of any instance, as many levels as you want. See [deep](/v1/docs/apis-all/query-params/deep.html). ```text GET /api/gen/admin/mysql8/inventory/customers?deep=[{s_key:'customer_id',t_col:'orders',t_key:'customer_id',isMultiple:true,select:'order_no,total'}] ``` ```json { "success": true, "statusCode": 200, "data": [ { "customer_id": 1, "first_name": "Bob", "last_name": "lin", "orders": [ { "order_no": 1001, "total": 12900 } ] } ] } ``` ## Total count ```text GET /api/gen/admin/mysql8/inventory/customers?find={isActive:1}&skip=0&limit=10&getTotalCount=true ``` ```json { "success": true, "statusCode": 200, "data": [ "…10 rows…" ], "totalCount": 5 } ``` - `totalCount` counts every row matching the filter, before `skip` and `limit` : what a pager needs. ## Everything together ```text GET /api/gen/admin/mysql8/inventory/customers?find={first_name:'Bob'}&skip=1&limit=4&sort=-customer_id&select=first_name&deep=[{s_key:'customer_id',t_col:'orders',t_key:'customer_id'}]&getTotalCount=true ``` ## Caching, hooks, notifications - With [caching](/v1/docs/features/automatic-caching.html) on for the table, the answer comes from Redis until a write through API Maker changes the table ; the response header `x-am-data-source` says `cache` or `api`, and `x-am-cache-control: reset_cache` forces the database. - [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) can add conditions to `g.req.query.find` (for example the rows of the signed-in person) and post hooks can reshape `g.res.output`. When the answer comes from the cache, the hooks do not run. - Clients subscribed to this table with [WebSocket events](/v1/docs/pages/web-socket-event-page.html) are notified after a successful call. ## From your code [`g.sys.db.gen.getAllGen`](/v1/examples/sys/db/gen/getAllGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Get All by Stream API (generated) > Stream the rows of a table with the generated get all by stream API of API Maker - the same filters as get all, sent as they are read, for exports and large result sets without holding everything in memory. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-get-all-by-stream-api.html The same query as [get all](/v1/docs/apis-all/generated-apis/auto-generated-get-all-api.html), answered as a stream : rows go to the client as the database produces them, so a million rows cost the server a few kilobytes of memory, not the whole result. | | | |---|---| | Method | GET | | URL | `/api/gen/admin/mysql8/inventory/customers/stream` | | Body | none | | Query params | the same as get all : find, skip, limit, sort, select, deep, getTotalCount, any column | | Answer | `data` : an array of rows, streamed | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no : streams always come from the database | | From code | [`g.sys.db.gen.getAllByStreamGen`](/v1/examples/sys/db/gen/getAllByStreamGen.html) | | API id | `GEN_GET_ALL_STREAM` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Get all by stream as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-all-by-stream-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers/stream` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text GET /api/gen/admin/mysql8/inventory/customers?find={isActive:1}&sort=customer_id&select=customer_id,first_name ``` ```json { "data": [ { "customer_id": 1, "first_name": "Bob" }, { "customer_id": 2, "first_name": "Alice" } ], "success": true, "statusCode": 200, "meta": {} } ``` - The envelope is the same as [get all](/v1/docs/apis-all/generated-apis/auto-generated-get-all-api.html), but `data` comes first and its rows are written in chunks while the query runs. `success`, `statusCode`, `meta` and `totalCount` close the answer. Parse it as JSON when it is complete, or read it with a streaming JSON parser as it arrives. - If the database fails while rows were already sent, the answer still ends as valid JSON : `], "success": false, "statusCode": 500, "errors": [...] }`. Read `success` at the end. ## When to use it - Exports, reports, synchronisations, copies of a table : anything which reads many rows and would not fit a normal answer. - Every filter of get all works : `find`, `sort`, `select`, `deep`, `skip`, `limit`. - The answer is never cached, and the rows are never compressed in memory : `x-am-data-source` is always `api`. ## Hooks on a stream - Pre hooks run before the query and can change it. - Post hooks run when the stream ends. They can not change what was sent, and `g.res.output` holds only the last chunk of rows, so do not use a post hook to reshape a stream : reshape in a custom API around `g.sys.db.getAllByStream`. ## From your code In a custom API the same call hands you every row as it arrives, without building the whole array : ```typescript linenums="1" let count = 0; await g.sys.db.gen.getAllByStreamGen({ instance: 'mysql8', database: 'inventory', collection: 'customers', queryParams: { find: { isActive: 1 } }, }, (row) => { count++; // one object at a time, in order }); return { count }; ``` ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Get by ID API (generated) > Read one row by its primary key with the generated get by id API of API Maker, or by any other column with the primaryKey param, with select and deep populate. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-get-by-id-api.html Reads one row by its primary key, or by any other column with the second param. With `select` and `deep`, it is the detail screen of your app in one call. | | | |---|---| | Method | GET | | URL | `/api/gen/admin/mysql8/inventory/customers/get-by-id/:id[/:primaryKey]` | | Body | none | | Query params | [select](/v1/docs/apis-all/query-params/select.html), [deep](/v1/docs/apis-all/query-params/deep.html), find (extra conditions) | | Answer | `data` : the row, or `null` when nothing matches | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | yes, with `enableCaching` on the table | | From code | [`g.sys.db.gen.getByIdGen`](/v1/examples/sys/db/gen/getByIdGen.html) | | API id | `GEN_GET_BY_ID` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Get by id as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-get-by-id-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers/get-by-id/:id[/:primaryKey]` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## By primary key ```text GET /api/gen/admin/mysql8/inventory/customers/get-by-id/1 ``` ```json { "success": true, "statusCode": 200, "data": { "customer_id": 1, "first_name": "Bob", "last_name": "lin", "last_update": "2022-11-14T04:34:58.000Z", "pincode": 382345, "isActive": 1 } } ``` - The primary key is the one of the table (`_id` on MongoDB, the primary key column on SQL, or `isPrimaryKey` in the schema). An unknown id answers `data: null` with `success: true`. ## By any column Name the column as the second param : ```text GET /api/gen/admin/mysql8/inventory/customers/get-by-id/Bob/first_name GET /api/gen/admin/mysql8/inventory/customers/get-by-id/382345/pincode ``` - When several rows match, the first one is returned. - The value is converted to the type of the column, so `382345` is compared as a number. ## Fields and related rows ```text GET /api/gen/admin/mysql8/inventory/customers/get-by-id/1?select=first_name,last_name GET /api/gen/admin/mysql8/inventory/customers/get-by-id/1?deep=[{s_key:'customer_id',t_col:'orders',t_key:'customer_id',isMultiple:true}] ``` - `deep` works as on [get all](/v1/docs/apis-all/generated-apis/auto-generated-get-all-api.html#related-rows-with-deep), with the relations of the schema when the table has one. ## One more condition `find` narrows the match further, for example to make sure the row belongs to the caller : ```text GET /api/gen/admin/mysql8/inventory/customers/get-by-id/1?find={isActive:1} ``` - A pre hook can add such a condition to `g.req.query.find` for every call : the row scoping pattern of [Handle role based permissions](/v1/docs/authorization/handle-role-based-permissions.html). ## From your code [`g.sys.db.gen.getByIdGen`](/v1/examples/sys/db/gen/getByIdGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Save Single or Multiple API (generated) > Insert one row or many with the generated save single or multiple API of API Maker - one object or an array in the body, generated ids, and select or deep on the answer. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-save-single-or-multiple-api.html Inserts one row (an object) or many (an array) and answers `201` with what was saved, ids included. | | | |---|---| | Method | POST | | URL | `/api/gen/admin/mysql8/inventory/customers/save-single-or-multiple` | | Body | one object, or an array of objects | | Query params | [select](/v1/docs/apis-all/query-params/select.html), [deep](/v1/docs/apis-all/query-params/deep.html) : applied to the answer | | Answer | `201` and `data` : the saved row, or the array of saved rows | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.gen.saveSingleOrMultipleGen`](/v1/examples/sys/db/gen/saveSingleOrMultipleGen.html) | | API id | `GEN_POST_BULK_INSERT` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Save single or multiple as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-save-single-or-multiple-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers/save-single-or-multiple` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## One row ```text POST /api/gen/admin/mysql8/inventory/customers/save-single-or-multiple ``` ```json title="Body" { "first_name": "Bob", "last_name": "Lin", "pincode": 382345 } ``` ```json title="Answer" { "success": true, "statusCode": 201, "data": { "customer_id": 6, "first_name": "Bob", "last_name": "Lin", "pincode": 382345 } } ``` - The primary key can be sent (`"customer_id": 27`) or left out : the database or API Maker generates it (`isAutoIncrementByDB`, `isAutoIncrementByAM`, `isAutoGenerateByAM` in the schema, `_id` on MongoDB). ## Many rows ```json title="Body" [ { "first_name": "Bob", "last_name": "Lin" }, { "customer_id": 29, "first_name": "Eve", "last_name": "Page" } ] ``` - The answer is an array in the same order. Objects with and without a primary key can mix. - Every row is checked before anything is written ; an error names the object with `dataIndex`. ## Nested objects - On MongoDB a nested object is stored as an embedded document, as sent. It is not saved to another collection : that needs the relations of a schema and the [schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-save-single-or-multiple-api.html). - On a SQL table every key must be a column. ## The answer you want ```text POST /api/gen/admin/mysql8/inventory/customers/save-single-or-multiple?select=customer_id,first_name POST /api/gen/admin/mysql8/inventory/customers/save-single-or-multiple?deep=[{s_key:'shipping_id',t_col:'shippings',t_key:'id'}] ``` - `select` keeps only some fields of the saved rows in the answer ; `deep` populates their relations. ## After the save - The cache of the table is reset, subscribed WebSocket clients are notified, events with an automatic trigger on this API run, and post hooks get the saved rows in `g.res.output`. ## From your code [`g.sys.db.gen.saveSingleOrMultipleGen`](/v1/examples/sys/db/gen/saveSingleOrMultipleGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Master Save API (generated) > Save or update a tree of related objects in one call with the generated master save API of API Maker - each object is inserted or updated by its primary key, nested objects go to their own tables, arrays fill one-to-many relations, and a failure reverts everything. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-master-save-api.html One call which saves or updates a whole object graph : each object is inserted when it has no primary key and updated when it has one, at every level of nesting, across tables and even databases. | | | |---|---| | Method | POST | | URL | `/api/gen/admin/mysql8/inventory/customers/master-save` | | Body | one object, or an array of objects, with nested related objects | | Query params | [select](/v1/docs/apis-all/query-params/select.html), [deep](/v1/docs/apis-all/query-params/deep.html) : applied to the answer | | Answer | `201` and `data` : the saved object(s), with the ids of the nested rows | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.gen.masterSaveGen`](/v1/examples/sys/db/gen/masterSaveGen.html) | | API id | `GEN_MASTER_SAVE` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Master save as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-master-save-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers/master-save` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## Save or update ```text POST /api/gen/admin/mysql8/inventory/customers/master-save ``` ```json title="Body : no primary key, inserted" { "first_name": "Bob", "last_name": "Lin" } ``` ```json title="Body : a primary key which exists, updated with these fields" { "customer_id": 27, "pincode": 382330 } ``` - The rule is the same at every level : **primary key present, the row is read and updated ; absent, the row is inserted.** An update sends only the fields you give. - An array in the body saves or updates each object and answers an array. ## When something fails - Every row written by the call is reverted when a later one fails : a wrong field in the country of the example above leaves no city and no state behind. - The errors name the object and the field, with `dataIndex` for arrays. ## Without a schema - Without relations, a nested object is stored as an embedded document on MongoDB and refused on SQL. The tree of tables needs the [schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-master-save-api.html). - Save or update by primary key works : `_id` on MongoDB, the primary key column on SQL. ## From your code [`g.sys.db.gen.masterSaveGen`](/v1/examples/sys/db/gen/masterSaveGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Array Operations API (generated) > Push, add to set, pull, pull all, pop and set elements of array fields in MongoDB documents with the generated array operations API of API Maker, several operations in one call. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-array-operations-api.html Changes array fields of MongoDB documents in place : push, addToSet, pull, pullAll, pop and set, on every document matching `find`, several operations in one call. | | | |---|---| | Method | PUT | | URL | `/api/gen/admin/mysql8/inventory/customers/array-operations` | | Body | `{ find, select?, operations: [ … ] }` | | Query params | none | | Answer | `data` : one array per operation with the documents matched, projected to the array (or `select`) | | Databases | MongoDB only | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.gen.arrayOperationsGen`](/v1/examples/sys/db/gen/arrayOperationsGen.html) | | API id | `GEN_ARRAY_OPERATIONS` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Array operations as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-array-operations-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers/array-operations` : replace `admin` with the user path of your account, `mongodb` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The body ```json { "find": { "customer_id": 1 }, "operations": [ { "operation": "push", "path": "ages", "dataToPush": [ { "age": 33, "birth_year": 1989 } ] } ] } ``` | Key | Meaning | |---|---| | `find` | Which documents to change. `{}` : all of them. | | `select` | The fields to return for the matched documents. Without it, the array of `path`. | | `operations` | Run one after the other, each on every matched document. | | Operation | Keys | Does | |---|---|---| | `push` | `path`, `dataToPush` (array), `position`, `slice`, `sort` | Appends the items. `position` inserts at an index, `slice` keeps the first N (or last N when negative), `sort` (`{ field: 1 or -1 }`) reorders. | | `addToSet` | `path`, `dataToPush` (one value or an array) | Appends only the values which are not there yet. | | `pull` | `path`, `queryToRemove` | Removes every item matching the query. | | `pullAll` | `path`, `dataToPull` (array) | Removes every item equal to one of the values. | | `pop` | `path`, `direction` | `1` removes the last item, `-1` the first. | | `set` | `dataToSet`, `arrayFilters`, `upsert` | Updates fields of items with the positional `$[item]` syntax and its filters. | ## Push and pull ```json title="Push two items at the front, keep the array at 10" { "find": {}, "operations": [ { "operation": "push", "path": "ages", "dataToPush": [ { "age": 2 }, { "age": 5 } ], "position": 0, "slice": 10, "sort": { "age": 1 } } ] } ``` ```json title="Remove the items of a year" { "find": { "customer_id": 1 }, "operations": [ { "operation": "pull", "path": "ages", "queryToRemove": { "birth_year": 1989 } } ] } ``` ```json title="Remove exact values" { "find": {}, "operations": [ { "operation": "pullAll", "path": "tags", "dataToPull": [ "old", "draft" ] } ] } ``` ## Set inside an array ```json { "find": {}, "operations": [ { "operation": "set", "dataToSet": { "ages.$[item].age": 45, "ages.$[item].birth_year": 1999 }, "arrayFilters": [ { "item.birth_year": 1999 } ], "upsert": true } ] } ``` - `arrayFilters` decide which items `$[item]` means, `upsert: true` inserts a document when `find` matches none. ## Several operations at once ```json { "find": {}, "operations": [ { "operation": "push", "path": "ages", "dataToPush": [ { "age": 60 } ] }, { "operation": "pull", "path": "ages", "queryToRemove": { "age": 2 } } ] } ``` ```json title="Answer : one entry per operation" { "success": true, "statusCode": 200, "data": [ [ { "_id": "63e0775abd0e063920533f7c", "ages": [ { "age": 33 }, { "age": 60 } ] } ], [ { "_id": "63e0775abd0e063920533f7c", "ages": [ { "age": 33 }, { "age": 60 } ] } ] ] } ``` ## From your code [`g.sys.db.gen.arrayOperationsGen`](/v1/examples/sys/db/gen/arrayOperationsGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Update by ID API (generated) > Change some fields of one row with the generated update by id API of API Maker - by primary key or any column, with upsert, returnDocument, select and deep, and the version check of optimistic concurrency control. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-update-by-id-api.html Changes the fields you send on one row and leaves the others alone. The row is found by its primary key, or by the column you name. | | | |---|---| | Method | PUT | | URL | `/api/gen/admin/mysql8/inventory/customers/update-by-id/:id[/:primaryKey]` | | Body | the fields to change | | Query params | [select](/v1/docs/apis-all/query-params/select.html), [deep](/v1/docs/apis-all/query-params/deep.html), [upsert](/v1/docs/apis-all/query-params/upsert.html), [returnDocument](/v1/docs/apis-all/query-params/returnDocument.html), throwErrorIfRecordNotFound, find | | Answer | `data` : the row after the change (or before, with `returnDocument=before`) | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.gen.updateByIdGen`](/v1/examples/sys/db/gen/updateByIdGen.html) | | API id | `GEN_PUT_UPDATE_BY_ID` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Update by id as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-update-by-id-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers/update-by-id/:id[/:primaryKey]` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## Some fields ```text PUT /api/gen/admin/mysql8/inventory/customers/update-by-id/1 ``` ```json title="Body" { "pincode": 382330 } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": { "customer_id": 1, "first_name": "Bob", "last_name": "lin", "last_update": "2022-11-14T04:34:58.000Z", "pincode": 382330, "isActive": 1 } } ``` - Send only what changes. Several fields at once are fine. On MongoDB, a nested key like `"address.city"` updates that path. ## By another column ```text PUT /api/gen/admin/mysql8/inventory/customers/update-by-id/Bob/first_name ``` - The row whose `first_name` is `Bob` is updated. The column named as the key can itself be in the body : `/update-by-id/Mallory/first_name` with `{ "first_name": "Alice" }` renames her. ## Insert when missing : upsert ```text PUT /api/gen/admin/mysql8/inventory/customers/update-by-id/999?upsert=true ``` ```json title="Body" { "first_name": "Daphney", "last_name": "Sonia", "pincode": 980220 } ``` - No row with `customer_id` 999 : one is inserted with the id and the body (the body is validated as a save). A row exists : it is updated. On every database. ## The row before the change ```text PUT /api/gen/admin/mysql8/inventory/customers/update-by-id/1?returnDocument=before ``` - The answer holds the row as it was ; the update happens all the same. Default : `after`. See [returnDocument](/v1/docs/apis-all/query-params/returnDocument.html). ## Unknown id - By default an unknown id answers `success: true` with `data: null` and changes nothing. With `?throwErrorIfRecordNotFound=true` it answers `404` and `Record not found.` - `find` adds conditions the row must match : `?find={isActive:1}`. A pre hook can add the condition for every call. ## Fields and related rows in the answer ```text PUT /api/gen/admin/mysql8/inventory/customers/update-by-id/1?select=first_name,last_name&deep=[{s_key:'customer_id',t_col:'orders',t_key:'customer_id'}] ``` ## From your code [`g.sys.db.gen.updateByIdGen`](/v1/examples/sys/db/gen/updateByIdGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Update Many API (generated) > Change every row matching a filter in one statement with the generated update many API of API Maker - a find and an updateData in the body, the count of changed rows in the answer. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-update-many-api.html Applies the same change to every row matching `find`, in one statement of the database, and answers how many rows changed. | | | |---|---| | Method | PUT | | URL | `/api/gen/admin/mysql8/inventory/customers/update-many` | | Body | `{ find, updateData }` | | Query params | none | | Answer | `data` : `{ updatedRowsCount }` | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.gen.updateManyGen`](/v1/examples/sys/db/gen/updateManyGen.html) | | API id | `GEN_UPDATE_MANY` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Update many as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-update-many-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers/update-many` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text PUT /api/gen/admin/mysql8/inventory/customers/update-many ``` ```json title="Body" { "find": { "customer_id": { "$in": [ 2, 3 ] } }, "updateData": { "last_name": "Brown" } } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": { "updatedRowsCount": 2 } } ``` - `find` takes every operator of [find](/v1/docs/apis-all/query-params/find.html) : `$in`, `$gt`, `$and`, `$or`, `$like`, dotted keys… `{}` updates every row. - `updatedRowsCount` counts the rows the database actually changed. ## More filters ```json title="$and" { "find": { "$and": [ { "isActive": 1 }, { "pincode": { "$gt": 382345 } } ] }, "updateData": { "isActive": 0 } } ``` ```json title="$or" { "find": { "$or": [ { "first_name": "Bob" }, { "first_name": "Alice" } ] }, "updateData": { "pincode": 380001 } } ``` ```json title="a related table (find and join)" { "find": { "orders.status": "CANCELLED" }, "updateData": { "isActive": 0 } } ``` ## Good to know - Nothing is read back : the answer has no rows. Read them with [query](/v1/docs/apis-all/generated-apis/auto-generated-query-for-get-data-api.html) if you need them. - The cache of the table is reset and WebSocket subscribers are notified once for the call. ## From your code [`g.sys.db.gen.updateManyGen`](/v1/examples/sys/db/gen/updateManyGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Replace by ID API (generated) > Replace a whole MongoDB document with the generated replace by id API of API Maker - by primary key or any column, with upsert, returnDocument, select and deep. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-replace-by-id-api.html Replaces one MongoDB document with the body : fields you do not send are gone. For a partial change, use [update by id](/v1/docs/apis-all/generated-apis/auto-generated-update-by-id-api.html). | | | |---|---| | Method | PUT | | URL | `/api/gen/admin/mongodb/inventory/customers/replace-by-id/:id[/:primaryKey]` | | Body | the whole new document | | Query params | [select](/v1/docs/apis-all/query-params/select.html), [deep](/v1/docs/apis-all/query-params/deep.html), [upsert](/v1/docs/apis-all/query-params/upsert.html), [returnDocument](/v1/docs/apis-all/query-params/returnDocument.html), throwErrorIfRecordNotFound | | Answer | `data` : the document after the change (or before, with `returnDocument=before`) | | Databases | MongoDB only | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.gen.replaceByIdGen`](/v1/examples/sys/db/gen/replaceByIdGen.html) | | API id | `GEN_PUT_REPLACE_BY_ID` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Replace by id as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-replace-by-id-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mongodb/inventory/customers/replace-by-id/:id[/:primaryKey]` : replace `admin` with the user path of your account, `mongodb` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text PUT /api/gen/admin/mongodb/inventory/customers/replace-by-id/60ae24c8c37cd955cc144162 ``` ```json title="Body" { "customer_id": 1, "first_name": "Rex", "last_name": "Nestor", "pincode": 879654, "isActive": 0 } ``` - The document keeps its `_id` and gets exactly these fields. A field of the old document which is not in the body disappears. - By another column : `/replace-by-id/4/customer_id`. ## Options - `?upsert=true` inserts the document when the id does not exist. - `?returnDocument=before` answers the old document. - `?throwErrorIfRecordNotFound=true` answers `404` instead of `data: null` for an unknown id. - `select` and `deep` shape the answer. ## SQL tables - Replace by id exists only on MongoDB (`Replace by id API is not supported for instance type …` otherwise). On a SQL table, [update by id](/v1/docs/apis-all/generated-apis/auto-generated-update-by-id-api.html) with every column does the same. ## From your code [`g.sys.db.gen.replaceByIdGen`](/v1/examples/sys/db/gen/replaceByIdGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Remove by ID API (generated) > Delete one row by its primary key or any column with the generated remove by id API of API Maker, and get the removed row back, with select and deep. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-remove-by-id-api.html Deletes one row and answers with the row as it was, so the caller can show or log what disappeared. | | | |---|---| | Method | DELETE | | URL | `/api/gen/admin/mysql8/inventory/customers/:id[/:primaryKey]` | | Body | none | | Query params | [select](/v1/docs/apis-all/query-params/select.html), [deep](/v1/docs/apis-all/query-params/deep.html), throwErrorIfRecordNotFound, find | | Answer | `data` : the removed row, or `null` when no row matched | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.gen.removeByIdGen`](/v1/examples/sys/db/gen/removeByIdGen.html) | | API id | `GEN_DEL_DELETE_BY_ID` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Remove by id as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-remove-by-id-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers/:id[/:primaryKey]` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text DELETE /api/gen/admin/mysql8/inventory/customers/1 ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": { "customer_id": 1, "first_name": "Bob", "last_name": "lin", "pincode": 382345, "isActive": 1 } } ``` - The id is the primary key. `DELETE /api/gen/admin/mysql8/inventory/customers/Bob/first_name` removes the first row whose `first_name` is `Bob`. ## Options - `?select=first_name` and `?deep=[…]` shape the returned row (deep populates it before it is removed). - `?throwErrorIfRecordNotFound=true` answers `404` and `Record not found.` for an unknown id ; the default is `data: null`. - `?find={isActive:0}` adds a condition : the row is removed only when it matches. A pre hook can add such a condition to keep callers to their own rows. ## After the call - The cache of the table is reset, WebSocket subscribers are notified, post hooks receive the removed row in `g.res.output`. - To remove many rows at once, use [remove by query](/v1/docs/apis-all/generated-apis/auto-generated-remove-by-query-api.html). ## From your code [`g.sys.db.gen.removeByIdGen`](/v1/examples/sys/db/gen/removeByIdGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Query for Get Data API (generated) > Read rows with a JSON query in the body with the generated query API of API Maker - find with operators, find and join across tables, select, sort, paging, deep populate and total count. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-query-for-get-data-api.html The read API for queries too long or too private for a URL : the same `find`, `select`, `sort`, `skip`, `limit`, `deep` and `getTotalCount` as get all, in the body. | | | |---|---| | Method | POST | | URL | `/api/gen/admin/mysql8/inventory/customers/query` | | Body | `{ find, select, sort, skip, limit, deep, getTotalCount }` | | Query params | none | | Answer | `data` : an array of rows, `totalCount` with `getTotalCount: true` | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | yes, with `enableCaching` on the table | | From code | [`g.sys.db.gen.queryGen`](/v1/examples/sys/db/gen/queryGen.html) | | API id | `GEN_POST_QUERY` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Query for get data as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-query-for-get-data-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers/query` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The body | Key | Meaning | |---|---| | `find` | Required. The filter, with every operator of [find](/v1/docs/apis-all/query-params/find.html). `{}` for everything. | | `select` | Fields to return : `"first_name,last_name"`, `"-password"`, or `{ first_name: 1 }`. | | `sort` | `"-last_update,first_name"` or `{ last_update: -1 }`. | | `skip`, `limit` | Paging. | | `deep` | Related rows, see [deep](/v1/docs/apis-all/query-params/deep.html). | | `getTotalCount` | `true` adds `totalCount`, the number of rows matching `find` before paging. | ```text POST /api/gen/admin/mysql8/inventory/customers/query ``` ```json title="Body" { "find": { "isActive": 1, "pincode": { "$gte": 382345 } }, "select": "customer_id,first_name,last_name", "sort": "-customer_id", "skip": 0, "limit": 10, "getTotalCount": true } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "customer_id": 5, "first_name": "Eve", "last_name": "Page" } ], "totalCount": 5 } ``` ## Operators Every operator works on every database ; API Maker translates them to SQL where needed. | Operator | Example | Meaning | |---|---|---| | `$eq`, `$ne` | `{ "first_name": { "$ne": "Bob" } }` | equal, not equal (a plain value means `$eq`) | | `$gt`, `$gte`, `$lt`, `$lte` | `{ "pincode": { "$gte": 382345 } }` | comparisons on numbers, strings and ISO dates | | `$in`, `$nin` | `{ "customer_id": { "$in": [ 1, 2 ] } }` | in a list, not in a list (a non empty array) | | `$and`, `$or` | `{ "$or": [ { "customer_id": 1 }, { "pincode": 382345 } ] }` | combine conditions (an array) | | `$not` | `{ "customer_id": { "$not": { "$in": [ 1, 2 ] } } }` | negate an operator | | `$like` | `{ "first_name": { "$like": "Ma%" } }` | SQL like pattern, `%` and `_` | | `$regex` | `{ "first_name": { "$regex": "^Ma" } }` | regular expression | | `$isNull` | `{ "last_update": { "$isNull": true } }` | null (or not null with `false`), SQL databases | - On SQL databases, two operators on one field (`{ "$gte": 1, "$lte": 9 }`) are refused : write them as two conditions in `$and`. ## Find and join A dotted key in `find` filters on a field of a **related** table, across databases and instances : ```json title="Customers whose orders have status PAID" { "find": { "orders.status": "PAID" }, "deep": [ { "s_key": "customer_id", "t_col": "orders", "t_key": "customer_id", "isMultiple": true } ] } ``` - The relation comes from the `deep` item of the call, or from the schema of the table. It goes as many levels as the dots : `"orders.items.product.category": "Books"`. - The rows of the related table are read through their own query API, with the permissions of the caller. - See [find and join](/v1/docs/apis-all/query-params/find.html#find-and-join-fields-of-related-tables) for every rule. ## Deep populate ```json { "find": {}, "limit": 5, "deep": [ { "s_key": "customer_id", "t_col": "orders", "t_key": "customer_id", "isMultiple": true, "select": "order_no,total", "deep": [ { "s_key": "shipping_id", "t_col": "shippings", "t_key": "id" } ] } ] } ``` - Two items in `deep` populate two fields ; `deep` inside an item goes one level further. With relations in the schema, `{ "s_key": "customer_id" }` is enough. ## Types in filters - Nothing is converted : `"customer_id": "8"` looks for the string `"8"` and matches no number. Send the type the column has. ## Caching - With caching on for the table, the same body answers from Redis until a write changes the table. `x-am-cache-control: reset_cache` forces the database. ## From your code [`g.sys.db.gen.queryGen`](/v1/examples/sys/db/gen/queryGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Query for Get Data by Stream API (generated) > Stream the rows of a JSON query with the generated query by stream API of API Maker - the body of the query API, the answer of get all by stream, for exports and big result sets. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-query-for-get-data-by-stream-api.html The body of [query for get data](/v1/docs/apis-all/generated-apis/auto-generated-query-for-get-data-api.html) with the answer of [get all by stream](/v1/docs/apis-all/generated-apis/auto-generated-get-all-by-stream-api.html) : rows are sent as the database produces them. | | | |---|---| | Method | POST | | URL | `/api/gen/admin/mysql8/inventory/customers/query-stream` | | Body | `{ find, select, sort, skip, limit, deep, getTotalCount }` | | Query params | none | | Answer | `data` : an array of rows, streamed | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no : streams always come from the database | | From code | [`g.sys.db.gen.queryByStreamGen`](/v1/examples/sys/db/gen/queryByStreamGen.html) | | API id | `GEN_POST_QUERY_STREAM` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Query for get data by stream as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-query-for-get-data-by-stream-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers/query-stream` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text POST /api/gen/admin/mysql8/inventory/customers/query-stream ``` ```json title="Body" { "find": { "isActive": 1 }, "sort": "customer_id", "select": "customer_id,first_name" } ``` ```json title="Answer, written in chunks" { "data": [ { "customer_id": 1, "first_name": "Bob" }, { "customer_id": 2, "first_name": "Alice" } ], "success": true, "statusCode": 200, "meta": {} } ``` - Same keys and operators as the query API, find and join and deep included. Read `success` at the end : an error after the first rows closes the answer with `"success": false` and the errors. - Never cached ; post hooks can not change what was sent. ## From your code ```typescript linenums="1" const out: any[] = []; await g.sys.db.gen.queryByStreamGen({ instance: 'mysql8', database: 'inventory', collection: 'customers', find: { isActive: 1 }, select: 'customer_id,first_name', }, (row) => out.push(row)); ``` ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Remove by Query API (generated) > Delete every row matching a filter with the generated remove by query API of API Maker - a find in the body, the removed rows, their count and their ids in the answer. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-remove-by-query-api.html Deletes every row matching `find` and answers with the rows removed, their ids and their count. A body without `find` is refused : nothing is ever deleted without a filter. | | | |---|---| | Method | POST | | URL | `/api/gen/admin/mysql8/inventory/customers/query/delete` | | Body | `{ find, select? }` | | Query params | none | | Answer | `data` : `{ deletedRows, deletedRowsCount, ids }` | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | no (a write ; it resets the cache of the table) | | From code | [`g.sys.db.gen.removeByQueryGen`](/v1/examples/sys/db/gen/removeByQueryGen.html) | | API id | `GEN_POST_QUERY_DELETE` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Remove by query as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-remove-by-query-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers/query/delete` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text POST /api/gen/admin/mysql8/inventory/customers/query/delete ``` ```json title="Body" { "find": { "isActive": 0 } } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": { "deletedRows": [ { "customer_id": 7, "first_name": "Old", "last_name": "Row", "isActive": 0 } ], "deletedRowsCount": 1, "ids": [ 7 ] } } ``` - `find` takes every operator of [find](/v1/docs/apis-all/query-params/find.html), including dotted keys of related tables. `{}` matches everything. - The rows are read before the delete, so `deletedRows` holds them as they were ; `select` keeps only some of their fields. ## More filters ```json { "find": { "customer_id": { "$in": [ 2, 3, 4 ] } } } ``` ```json { "find": { "$and": [ { "isActive": 0 }, { "last_update": { "$lt": "2022-01-01T00:00:00.000Z" } } ] } } ``` ```json { "find": { "orders.status": "CANCELLED" } } ``` ## Missing find ```json { "success": false, "statusCode": 400, "errors": [ { "code": 400, "message": "Please provide 'find' property having query to remove data." } ] } ``` ## After the call - The cache of the table is reset and WebSocket subscribers are notified once. Post hooks receive the answer in `g.res.output`. ## From your code [`g.sys.db.gen.removeByQueryGen`](/v1/examples/sys/db/gen/removeByQueryGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Aggregate API (generated) > Run a MongoDB aggregation pipeline through the generated aggregate API of API Maker - $match, $group, $project, $addFields, $bucket, $facet, $lookup, $count and the other stages, with the permissions of the caller. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-aggregate-api.html Runs a MongoDB aggregation pipeline on a collection : the body is the array of stages, the answer their result. | | | |---|---| | Method | POST | | URL | `/api/gen/admin/mongodb/inventory/customers/aggregate` | | Body | the pipeline : an array of stages | | Query params | none | | Answer | `data` : the documents produced by the pipeline | | Databases | MongoDB only | | Cached | yes, with `enableCaching` on the table | | From code | [`g.sys.db.gen.aggregateGen`](/v1/examples/sys/db/gen/aggregateGen.html) | | API id | `GEN_POST_AGGREGATE` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Aggregate as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-aggregate-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mongodb/inventory/customers/aggregate` : replace `admin` with the user path of your account, `mongodb` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text POST /api/gen/admin/mongodb/inventory/customers/aggregate ``` ```json title="Body : orders per customer, the biggest first" [ { "$match": { "status": "PAID" } }, { "$group": { "_id": "$customer_id", "orders": { "$sum": 1 }, "total": { "$sum": "$total" } } }, { "$sort": { "total": -1 } }, { "$limit": 10 } ] ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "_id": 1, "orders": 4, "total": 51600 } ] } ``` - A body which is not an array is refused with `Please provide array for aggregate body.` ## Stages Every stage of the MongoDB version you run is accepted. The ones used most : | Stage | Example | |---|---| | `$match` | `{ "$match": { "pincode": { "$gt": 380000 } } }` | | `$group` | `{ "$group": { "_id": "$pincode", "count": { "$sum": 1 } } }` | | `$project` | `{ "$project": { "name": { "$toLower": "$first_name" } } }` | | `$addFields` | `{ "$addFields": { "full_name": { "$concat": [ "$first_name", " ", "$last_name" ] } } }` | | `$sort`, `$skip`, `$limit` | `{ "$sort": { "total": -1 } }` | | `$count` | `{ "$count": "customers" }` | | `$bucket` | `{ "$bucket": { "groupBy": "$price", "boundaries": [ 0, 100, 500 ], "default": "Other", "output": { "count": { "$sum": 1 } } } }` | | `$facet` | several pipelines in one : `{ "$facet": { "byPrice": [ … ], "byCategory": [ … ] } }` | | `$lookup` | a join inside MongoDB : `{ "$lookup": { "from": "orders", "localField": "customer_id", "foreignField": "customer_id", "as": "orders" } }` | | `$unwind` | `{ "$unwind": "$orders" }` | | `$collStats` | `{ "$collStats": { "storageStats": {}, "count": {} } }` | ## Good to know - Only MongoDB : SQL databases answer `This API is only supported for mongodb.` For SQL, run a query with [executeQuery](/v1/examples/sys/system/executeQuery.html) from a custom API. - Field permissions of the groups apply to what a stage returns. A caller who can not read a field does not get it, projected or not. - With caching on, a pipeline answers from Redis until the collection changes. ## From your code [`g.sys.db.gen.aggregateGen`](/v1/examples/sys/db/gen/aggregateGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Count API (generated) > Count the rows matching a filter with the generated count API of API Maker - a find in the body, a number in the answer, on every database. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-count-api.html Answers how many rows match `find`, without reading them. | | | |---|---| | Method | POST | | URL | `/api/gen/admin/mysql8/inventory/customers/count` | | Body | `{ find }` | | Query params | none | | Answer | `data` : the number of rows | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | yes, with `enableCaching` on the table | | From code | [`g.sys.db.gen.countGen`](/v1/examples/sys/db/gen/countGen.html) | | API id | `GEN_POST_COUNT` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Count as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-count-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers/count` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text POST /api/gen/admin/mysql8/inventory/customers/count ``` ```json title="Body" { "find": { "isActive": 1 } } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": 5 } ``` - `find` takes every operator of [find](/v1/docs/apis-all/query-params/find.html) : `$gt`, `$in`, `$and`, `$or`, `$like`, dotted keys of related tables… `{ "find": {} }` counts the table. ## Examples ```json { "find": { "pincode": { "$gte": 382345 } } } ``` ```json { "find": { "$or": [ { "first_name": "Bob" }, { "first_name": "Alice" } ] } } ``` ```json { "find": { "first_name": { "$like": "%all%" } } } ``` ```json title="rows whose orders are paid" { "find": { "orders.status": "PAID" } } ``` ## Types in the filter - Nothing is converted : `"pincode": "100050"` counts the string and finds no number. Send the type the column has. ## Good to know - For a list with its total in one call, [query](/v1/docs/apis-all/generated-apis/auto-generated-query-for-get-data-api.html) with `getTotalCount: true` saves the second request. - With caching on, the count answers from Redis until a write changes the table. ## From your code [`g.sys.db.gen.countGen`](/v1/examples/sys/db/gen/countGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Distinct API (generated) > List the distinct values of one or several columns with the generated distinct API of API Maker - fields and their order in the path, an optional find filter, on every database. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-distinct-api.html Lists the distinct values of a column, or the distinct combinations of several, sorted the way you ask. | | | |---|---| | Method | GET | | URL | `/api/gen/admin/mysql8/inventory/customers/distinct/:field[/:order]` | | Body | none | | Query params | [find](/v1/docs/apis-all/query-params/find.html) | | Answer | `data` : an array of objects, one per distinct value or combination | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | yes, with `enableCaching` on the table | | From code | [`g.sys.db.gen.distinctGen`](/v1/examples/sys/db/gen/distinctGen.html) | | API id | `GEN_GET_DISTINCT` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Distinct as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-distinct-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers/distinct/:field[/:order]` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## One column ```text GET /api/gen/admin/mysql8/inventory/customers/distinct/pincode ``` ```json { "success": true, "statusCode": 200, "data": [ { "pincode": 382345 }, { "pincode": 382346 }, { "pincode": 382347 } ] } ``` - Ascending by default. Descending : `/api/gen/admin/mysql8/inventory/customers/distinct/pincode/desc` (also `-1`, `dsc`, `0`, `false` ; ascending is `asc`, `1`, `true`). ## Several columns ```text GET /api/gen/admin/mysql8/inventory/customers/distinct/first_name,pincode GET /api/gen/admin/mysql8/inventory/customers/distinct/first_name,pincode/asc,desc ``` ```json { "success": true, "statusCode": 200, "data": [ { "first_name": "Alice", "pincode": 382346 }, { "first_name": "Bob", "pincode": 382345 }, { "first_name": "Eve", "pincode": 382349 }, { "first_name": "Eve", "pincode": 382348 } ] } ``` - One order per column, in the same position. A missing order is ascending. ## With a filter ```text GET /api/gen/admin/mysql8/inventory/customers/distinct/pincode?find={isActive:1} ``` - The same `find` as get all. For a filter in the body, use [distinct with query](/v1/docs/apis-all/generated-apis/auto-generated-distinct-with-query-api.html). ## Good to know - A column which is empty everywhere gives `[]`. - Field permissions apply : a column the group can not read can not be listed. ## From your code [`g.sys.db.gen.distinctGen`](/v1/examples/sys/db/gen/distinctGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Distinct with Query API (generated) > List the distinct values of columns for the rows matching a find in the body with the generated distinct with query API of API Maker. Source: https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-distinct-with-query-api.html [Distinct](/v1/docs/apis-all/generated-apis/auto-generated-distinct-api.html) with the filter in the body : the distinct values of a column among the rows matching `find`. | | | |---|---| | Method | POST | | URL | `/api/gen/admin/mysql8/inventory/customers/distinct/:field[/:order]` | | Body | `{ find }` | | Query params | none | | Answer | `data` : an array of objects, one per distinct value or combination | | Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona | | Cached | yes, with `enableCaching` on the table | | From code | [`g.sys.db.gen.distinctQueryGen`](/v1/examples/sys/db/gen/distinctQueryGen.html) | | API id | `GEN_POST_DISTINCT_QUERY` (groups, settings, hooks, WebSocket subscriptions) | | The other family | [Distinct with query as a schema API](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-distinct-with-query-api.html) | !!! info "Schemaless : the data passes as it is" The generated APIs never read the [schema](/v1/docs/schema/schema.html) of the table. Nothing is converted, validated, defaulted, encrypted or numbered : what you send is what the database gets, and a filter matches the type you send (`"22"` does not match the number `22`). On MongoDB, strings which look like ObjectIds are converted. Every table has these APIs, with or without a schema ; the same operations under `/api/schema` apply the schema. ## The URL `/api/gen/admin/mysql8/inventory/customers/distinct/:field[/:order]` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table : | customer_id | first_name | last_name | last_update | pincode | isActive | |-------------|------------|-----------|-----------------------|---------|----------| | 1 | Bob | lin | 2022-11-14 04: 34: 58 | 382345 | 1 | | 2 | Alice | Page | 2022-10-15 02: 10: 40 | 382346 | 1 | | 3 | Mallory | Brown | 2022-09-13 03: 44: 05 | 382347 | 1 | | 4 | Eve | Mathly | 2022-11-12 01: 59: 33 | 382348 | 1 | | 5 | Eve | Page | 2022-11-12 01: 59: 33 | 382349 | 1 | ## The call ```text POST /api/gen/admin/mysql8/inventory/customers/distinct/customer_id/asc ``` ```json title="Body" { "find": { "pincode": { "$in": [ 382345, 382346 ] } } } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "customer_id": 1 }, { "customer_id": 2 } ] } ``` - Fields and orders in the path work as in [distinct](/v1/docs/apis-all/generated-apis/auto-generated-distinct-api.html) : `first_name,pincode/asc,desc`. - `{ "find": {} }` lists every distinct value. ## Examples ```json { "find": { "pincode": { "$gte": 382347 } } } ``` ```json { "find": { "$or": [ { "pincode": 382345 }, { "customer_id": 4 } ] } } ``` ```json { "find": { "first_name": { "$like": "%llory" } } } ``` ## Types in the filter - Nothing is converted : `"pincode": "382350"` looks for a string and matches no number. ## From your code [`g.sys.db.gen.distinctQueryGen`](/v1/examples/sys/db/gen/distinctQueryGen.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](/v1/docs/apis-all/response-format.html) instead of `data`. ## Headers Every call takes the [request headers](/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`. ## Errors | Code | When | |---|---| | `401` | The token in `x-am-authorization` is missing or invalid. | | `403` | No group of the API user grants this API of this table, or a field of the request. | | `404` | The instance, database or table of the URL does not exist. | | `400` | The query or the body is wrong : the [error messages](/v1/docs/apis-all/error-codes.html) say which key and why. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Query params](/v1/docs/apis-all/query-params/query-params.html) · [Response format](/v1/docs/apis-all/response-format.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # Custom APIs > Write a TypeScript function in API Maker and it is live as an API - path and method, settings for caching, access, validation, files and timeouts, the global object g to reach your databases, file uploads and downloads, any content type, hooks, versions and tests. Source: https://docs.apimaker.dev/v1/docs/apis-all/custom-apis/user-created-custom-api.html A custom API is a TypeScript function behind a path and a method. Save it in the admin panel (`API Info → Custom API`) or in your editor through the local client, and it answers at `/api/custom-api//` right away, in a sandbox, with the whole of API Maker available through the [global object `g`](/v1/docs/pre-defined-terms/global-object-g.html). | | | |---|---| | URL | `/api/custom-api//` | | Method | the `requestMethod` of the settings : `GET`, `POST`, `PUT` or `DELETE` | | Body, query, params, headers, files | `g.req.body`, `g.req.query`, `g.req.params`, `g.req.headers`, `g.req.body.files` | | Answer | what `main` returns, in `data` of the [envelope](/v1/docs/apis-all/response-format.html), or a file, or any content type | | Runs in | the sandbox of the account ; the native process with `runOnNativeProcess: true` | | Access | `apiAccessType` : `TOKEN_ACCESS` (default), `IS_PUBLIC`, `NO_ACCESS` (only from other code) | | From code | `g.sys.system.callExternalApi` over HTTP, or [`g.sys.test.runCustomApi`](/v1/docs/test-cases/test-cases.html) in a test | ## Hello world Every custom API has two files : the **basic info** (its settings) and the **code**. ```typescript title="Basic info" linenums="1" import * as T from 'types'; let customApi: T.ICustomApiSettingsTypes = { name: 'Hello World', path: '/hello-world', requestMethod: T.ERequestMethod.GET, apiAccessType: T.EAPIAccessType.TOKEN_ACCESS, errorList: [], }; module.exports = customApi; ``` ```typescript title="Code" linenums="1" import * as T from 'types'; import * as db from 'db-interfaces'; async function main(g: T.IAMGlobal) { return { hello: g.req.query.name || 'world' }; } module.exports = main; ``` ```bash curl "$AM/api/custom-api/admin/hello-world?name=Bob" -H "x-am-authorization: $TOKEN" ``` ```json { "success": true, "statusCode": 200, "data": { "hello": "Bob" } } ``` - The path and the method must match exactly : `/hello-world` with `GET`. Variables go in the query string or the body, not in the path. - `path` + `requestMethod` is unique in the account ; `name` is unique too and names the folder in Git. ## The settings | Key | Meaning | |---|---| | `name`, `path`, `requestMethod` | Identity and URL. | | `apiAccessType` | `TOKEN_ACCESS` : the API user token, plus the person token when `authProviders` names one. `IS_PUBLIC` : no token. `NO_ACCESS` : not reachable over HTTP, only from other code and the testing page. | | `authProviders` | Names of the [auth providers](/v1/docs/authorization/AMDB.html) whose token the person must send. Absent : the providers of the default secret. `[]` : the API user token only. | | `enableCaching` | Cache the answer in Redis, per URL, body and headers that matter, until `resetCacheOnModificationOf` says otherwise or the [reset custom API cache](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-custom-api.html) system API runs. | | `resetCacheOnModificationOf` | What resets the cache : `'DB:instance:database:table'` when that table is written through API Maker, `'CA:name'` when that custom API is called, `'TP:bundle:version'` when a third party API of that version writes. | | `acceptOnlyEncryptedData` | Refuse plain bodies : the body must be an [encrypted payload](/v1/docs/features/security-features.html#encrypted-request-payloads). | | `reqBodySchema`, `reqQueryParametersSchema` | A [schema](/v1/docs/schema/schema.html) for the body and for the query params : converted and validated before the code runs, every error at once with `400`. | | `errorList` | The messages your API throws, listed so they can be translated with [i18n](/v1/docs/i18/i18.html) and shown in the docs of the API. | | `runOnNativeProcess` | Run in the API Maker process instead of the sandbox : native modules and the packages of API Maker, no isolation. Use with care. | | `customApiTimeoutInSeconds` | How long the code may run, in seconds (10 by default). Sent as `x-am-sandbox-timeout`. | | `separateSandboxSettings` | A sandbox of its own for this API, with its own packages and memory. | | `fileUpload` | `enable`, `allowFileUploadFields`, and per field `minFileSizeBytes`, `maxFileSizeBytes`, `allowedExtensionsArr`. | | `swaggerDocs` | What the Swagger document of the API users shows for this API. | See [Custom API settings](/v1/docs/settings/customApiSettings.html) for every key with its example. ## Validate the request with a schema ```typescript title="Basic info" linenums="1" let customApi: T.ICustomApiSettingsTypes = { name: 'Create Order', path: '/orders', requestMethod: T.ERequestMethod.POST, errorList: [], reqBodySchema: { customer_id: { __type: T.EType.number, validations: { required: true } }, items: [ { product_id: { __type: T.EType.string, validations: { required: true } }, qty: { __type: T.EType.number, validations: { min: 1 } } } ], }, reqQueryParametersSchema: { dryRun: { __type: T.EType.boolean, conversions: { defaults: { defaultValue: false } } }, }, }; ``` - The body and the query params reach your code converted (types, trims, defaults) and checked (required, min, enum…). A wrong request never reaches the code. - The [is valid data for custom API](/v1/docs/apis-all/system-apis/system-generated-is-valid-data-for-custom-api.html) system API runs the same check without calling the API. ## Reach your data and the rest of API Maker ```typescript linenums="1" import * as T from 'types'; import * as db from 'db-interfaces'; import * as Pricing from 'utils/Pricing'; async function main(g: T.IAMGlobal) { const person = g.req.auth.authAMDB; // the signed-in person const orders = await g.sys.db.query({ instance: 'mongodb', database: 'shop', collection: 'orders', find: { customer_id: person.customer_id, status: 'PAID' }, sort: '-created_at', limit: 10, deep: [ { s_key: 'items' } ], }); const total = Pricing.sum(orders); // a utility class of yours g.logger.log(`${orders.length} orders`); // reaches the log table and the caller's logs await g.sys.cache.setKey(`orders:${person.customer_id}`, JSON.stringify(orders), 60); return { orders, total }; } module.exports = main; ``` - `await` every `g.sys` call. The second argument `true` returns the whole [envelope](/v1/docs/apis-all/response-format.html) instead of throwing. - `import * as db from 'db-interfaces'` gives the interfaces generated from your schemas, `db...I
`. - Every method of `g` has a page in the [code examples](/v1/examples/index.html). ## Errors and status codes ```typescript linenums="1" async function main(g: T.IAMGlobal) { if (!g.req.body?.customer_id) throw new Error('Please provide customer_id.'); // 500 with the message g.res.statusCode = T.EStatusCode.BAD_REQUEST; // 400 with your data return { field: 'customer_id', message: 'Please provide customer_id.' }; } ``` - A thrown error answers `success: false` with the message in `errors` ; a message listed in `errorList` is translated by i18n. - `g.res.statusCode` sets the status of a normal answer ; `g.res.warnings` adds warnings. ## Files **Upload.** Send `multipart/form-data` with the files in the field `files` (or `files1`, `files2`… up to `files41` to keep groups apart). They are on disk when the code runs, in `g.req.body.files` : ```typescript linenums="1" async function main(g: T.IAMGlobal) { const files: { originalname: string; mimetype: string; size: number; path: string; filename: string }[] = g.req.body.files || []; return files.map(f => ({ name: f.originalname, bytes: f.size })); } ``` - `fileUpload.validations.files.maxFileSizeBytes` and `allowedExtensionsArr` in the settings refuse a wrong file before the code runs. Uploaded files are cleaned from the uploads folder after `uploadedFileRemoveOlderThanThisTimeInSeconds` (30 minutes by default). **Download.** Return an object with the `__am__` keys of `IDownloadResponse` : the file (or a zip of several) is streamed to the caller. ```typescript linenums="1" async function main(g: T.IAMGlobal) { // a file written earlier in the uploads folder of the sandbox return { __am__downloadFilePath: 'reports/2026-09.pdf', __am__downloadFolderFileName: 'September report.pdf', __am__cleanupFileOrFolderPaths: 'reports/2026-09.pdf', }; } ``` - `__am__downloadFileOrFolderPaths` with several paths, or `{ fsSource, archiveDestination }` objects, answers a zip. ## Any content type Set `g.res.contentType` and return the content : HTML, text, XML, YAML, or bytes as base64 for any other MIME type. ```typescript linenums="1" async function main(g: T.IAMGlobal) { g.res.contentType = T.EContentType.HTML; return '

Hello

'; } ``` ```typescript linenums="1" title="A tiny gif" async function main(g: T.IAMGlobal) { g.res.contentType = 'image/gif'; // not in EContentType : the return is base64 return 'R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOwA='; } ``` - `g.res.headers` adds response headers. See [Content types](/v1/examples/res/contentType/contentType.html). ## Around the code - **Hooks** : pre and post hooks on the custom API itself, see [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html). - **Events and WebSockets** : an event can trigger automatically after the API, and WebSocket clients subscribed to the API get its answer. - **Versions** : several versions of the code, one active ; switch back in one click. - **Docs** tab : notes for the people who use the API, shown in the API testing page and the Swagger of the API users. - **Test cases** : [`g.sys.test.runCustomApi`](/v1/docs/test-cases/test-cases.html#testing-a-custom-api) runs the code with mocks and measures its coverage. - **Git** : `src/Custom APIs//` holds the basic info, the code and the docs. - **Groups** : an API user calls a custom API only when a group grants it, see [API group permission](/v1/docs/apis-security/api-group-permission.html). ## Native process `runOnNativeProcess: true` runs the function inside API Maker itself : no sandbox start, access to the packages of API Maker and to native modules such as a browser for [Playwright](/v1/docs/guides/browser-automation.html). A bug there can hurt the whole server, so keep it for the few APIs which need it. ## Samples in every account A new account comes with custom APIs under `/default/…` which show the features at work : deep populate, find and join, caching, a WebSocket emitter, S3 upload and download, a login which returns both tokens, a captcha, data isolation with hooks. Open them on the Custom API page and read the code. --- # Custom API Settings > The Basic Info of a custom API in API Maker - name, path and method, access type and auth providers, caching with reset rules, request schemas, timeout, native process, error list, file upload rules and Swagger docs. Source: https://docs.apimaker.dev/v1/docs/settings/customApiSettings.html The **Basic Info** tab of a custom API is a TypeScript object : where the API answers, who may call it, and how it behaves. | | | |---|---| | Page | `API Info → Custom API` → the API → **Basic Info**. | | Type | `T.ICustomApiSettingsTypes` | | URL | `/api/custom-api/` with `requestMethod`. Path and method together are unique. | ```typescript title="Basic Info" linenums="1" import * as T from 'types'; import { EType } from 'types'; let customApi: T.ICustomApiSettingsTypes = { name: 'Create Customer', // unique ; the folder in Git requestMethod: T.ERequestMethod.POST, path: '/customers', enableCaching: false, resetCacheOnModificationOf: [ // 'DB:mongodb:shop:customers', // a write to this table resets the cache of this API // 'CA:other_custom_api', // a call of that custom API resets it ], acceptOnlyEncryptedData: false, // customApiTimeoutInSeconds: 10, errorList: [ 'Unable to process this request' ], // translatable in i18n apiAccessType: T.EAPIAccessType.TOKEN_ACCESS, // authProviders: ['users_tg'], reqBodySchema: { first_name: { __type: EType.string, validations: { required: true } }, phone: { __type: EType.number, validations: { min: 5 } }, }, fileUpload: { enable: false, allowFileUploadFields: [ T.EFilesVariables.files ], validations: {} }, }; module.exports = customApi; ``` ## The keys | Key | Meaning | |---|---| | `name` | Unique name, also the folder of the API in Git. | | `path`, `requestMethod` | Where it answers : `GET`, `POST`, `PUT` or `DELETE`. `/customers/:id` gives `g.req.params.id`. | | `apiAccessType` | `TOKEN_ACCESS` (default), `IS_PUBLIC`, or `NO_ACCESS` for an API only your code calls. | | `authProviders` | The person tokens the call must carry. Absent : `common.authProviders` of the secret ; `[]` : the API user token alone. | | `enableCaching` | Cache the answer in Redis per body, query and person. | | `resetCacheOnModificationOf` | `DB:instance:database:table`, `CA:custom API name`, `TP:bundle:version` : what drops the cached answers. Otherwise [reset custom API cache](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-custom-api.html). | | `acceptOnlyEncryptedData` | Refuse plain payloads : [encrypted payloads](/v1/docs/features/security-features.html#encrypted-request-payloads). | | `reqBodySchema`, `reqQueryParametersSchema` | A [schema](/v1/docs/schema/schema.html) for the body and the query : validated and converted before the code runs, checkable with [is valid data for custom API](/v1/docs/apis-all/system-apis/system-generated-is-valid-data-for-custom-api.html). | | `customApiTimeoutInSeconds` | How long the code may run. | | `runOnNativeProcess` | Run on the process of API Maker instead of the sandbox : faster, less isolated ; `console.log` is not captured, use `g.logger`. | | `errorList` | The messages this API throws, listed for [internationalization](/v1/docs/i18/i18.html). | | `fileUpload` | `enable`, `allowFileUploadFields` (`files`…), and per field `validations` : `minFileSizeBytes`, `maxFileSizeBytes`, `allowedExtensionsArr`. See [files](/v1/docs/apis-all/custom-apis/user-created-custom-api.html#files). | | `swaggerDocs` | `tag`, `summary`, `description`, `parameters` for the Swagger docs of the API users. | ## Related - [Custom APIs](/v1/docs/apis-all/custom-apis/user-created-custom-api.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) · [Auth of a database user](/v1/docs/authorization/AMDB.html) --- # Pre Hooks > Run your TypeScript before any API of API Maker - at the level of an instance, a database, a table, one API, a custom, system or third party API, or a group - to check, change, narrow or answer a request, in the order API Maker runs them. Source: https://docs.apimaker.dev/v1/docs/apis-all/hooks/preHook-api.html A pre hook is a TypeScript function of yours 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, a system API or a third party 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 ## 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, system, third party 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](/v1/docs/apis-all/hooks/postHook-api.html). - 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. ## The code ```typescript linenums="1" 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.shared : values for the post hooks of the same request } module.exports = main; ``` 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 : `throw new Error('…')` gives `500` and the message, or throw an `IResponseError[]` with a `code` for another status. | - Every hook runs in the sandbox (or on the native process with `runOnNativeProcess`), within the sandbox timeout of the call. - When the answer of an API comes from the [cache](/v1/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 The most useful pre hook : a filter added to every read and a stamp on every write, so a person only reaches their own rows. ```typescript linenums="1" title="Collection level pre hook on orders" import * as T from 'types'; async function main(g: T.IAMGlobal) { if (!g.req.isApiRequestFromUser) return; const person = g.req.auth.authAMDB; // the person, from x-am-user-authorization if (!person) throw new Error('Sign in first.'); const mine = { customer_id: person.customer_id }; const api = g.req.reqInfo.apiInfo.id; // SCHEMA_GET_ALL, SCHEMA_POST_QUERY, … if (api.endsWith('_GET_ALL') || api.endsWith('_GET_ALL_STREAM') || api.endsWith('_GET_BY_ID') || api.endsWith('_DEL_DELETE_BY_ID') || api.endsWith('_PUT_UPDATE_BY_ID')) { // filters travel in query.find on these APIs const find = g.req.query.find ? (typeof g.req.query.find === 'string' ? JSON.parse(g.req.query.find) : g.req.query.find) : {}; g.req.query.find = { $and: [ find, mine ] }; } else if (api.endsWith('_POST_QUERY') || api.endsWith('_POST_QUERY_STREAM') || api.endsWith('_POST_COUNT') || api.endsWith('_UPDATE_MANY') || api.endsWith('_POST_QUERY_DELETE') || api.endsWith('_POST_DISTINCT_QUERY')) { g.req.body.find = { $and: [ g.req.body.find || {}, mine ] }; } else if (api.endsWith('_POST_BULK_INSERT') || api.endsWith('_MASTER_SAVE')) { const rows = Array.isArray(g.req.body) ? g.req.body : [ g.req.body ]; for (const row of rows) row.customer_id = person.customer_id; // stamp the owner } else if (api.endsWith('_POST_AGGREGATE') || api.endsWith('_GET_DISTINCT')) { throw new Error('Not allowed on this table.'); } } module.exports = main; ``` The [APIs Security Report](/v1/docs/apis-security/api-security-report.html) writes this hook for you, for the tables which need it, and [Handle role based permissions](/v1/docs/authorization/handle-role-based-permissions.html) explains the pattern step by step. ## Answer without the API ```typescript linenums="1" title="A pre hook which answers from the cache of the process" 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; ``` ## Validate and change ```typescript linenums="1" async function main(g: T.IAMGlobal) { if (!g.req.isApiRequestFromUser) return; if (g.req.body?.price_cents < 0) throw new Error('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.shared.startedAt = Date.now(); // for the post hook } module.exports = main; ``` ## Utility classes in a hook ```typescript linenums="1" 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; ``` - Press ++ctrl+space++ in the editor for the list of your [utility classes](/v1/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 → The global object g --- # Post Hooks > Run your TypeScript after any API of API Maker - reshape the answer in g.res.output, notify, log, or replace it - at the level of an instance, a database, a table, one API or a custom, system or third party API, in the reverse order of the pre hooks. Source: https://docs.apimaker.dev/v1/docs/apis-all/hooks/postHook-api.html A post hook is a TypeScript function of yours which runs **after** an API, with its result in `g.res.output`. It can reshape the answer, add to it, replace it, send something somewhere, or refuse to answer at all. It lives at the same levels as a [pre hook](/v1/docs/apis-all/hooks/preHook-api.html) : instance, database, table, API, custom, system and third party API, with optional groups. > 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 Post hooks run from the narrowest level to the widest : the hooks of the API, then of the table, then of the database, then of the instance, each list top to bottom. The last one to run sees what every other one did. ## The code ```typescript linenums="1" import * as T from 'types'; import * as db from 'db-interfaces'; async function main(g: T.IAMGlobal) { if (!g.req.isApiRequestFromUser) return; // g.res.output : the answer of the API (data), changeable // g.res.statusCode, g.res.warnings, g.res.errors // g.req : the request as the API saw it, after the pre hooks // g.shared : what the pre hooks left for you } module.exports = main; ``` | The hook | Effect | |---|---| | returns nothing | The next post hook runs, then the answer is sent. | | changes `g.res.output` | The answer is what is in `g.res.output` at the end. | | returns a value | That value becomes the answer and the remaining post hooks do not run. | | throws | The request answers with the error, whatever the API did. The database change of the API is **not** rolled back by that. | - Hooks run in the sandbox (or on the native process with `runOnNativeProcess`), within the sandbox timeout of the call. - No hook runs when the answer comes from the [cache](/v1/docs/features/automatic-caching.html). Calls from your own code skip hooks unless `skipHookRunning: false`. ## Reshape the answer ```typescript linenums="1" title="Hide a field and add a computed one" async function main(g: T.IAMGlobal) { if (!g.req.isApiRequestFromUser) return; const rows = Array.isArray(g.res.output) ? g.res.output : [ g.res.output ]; for (const row of rows) { if (!row) continue; delete row.internal_notes; row.total_with_tax = Math.round(row.total * 1.18); } } module.exports = main; ``` ## Do something after a write ```typescript linenums="1" title="Audit row and an event after a save" async function main(g: T.IAMGlobal) { if (!g.req.isApiRequestFromUser) return; await g.sys.db.saveSingleOrMultiple({ instance: 'mongodb', database: 'shop', collection: 'audit_logs', saveData: { api: g.req.reqInfo.apiInfo.id, by: g.req.auth.authAMDB?.email, at: new Date(), took: Date.now() - g.shared.startedAt }, }); await g.sys.system.emitEvent('order-placed', g.res.output); } module.exports = main; ``` - For a notification to WebSocket clients, nothing to write : subscribers of the table are notified after every successful call. Emit a custom WebSocket event with `g.sys.system.emitEventWS` when the notification needs another shape. - For an event after an API hit without code, attach the event to the API on the [Events](/v1/docs/apis-all/events/user-created-events-api.html) page (automatic trigger). ## Streams On get all by stream and query by stream the rows are already on their way when the post hooks run : `g.res.output` only holds the last chunk, and a value returned or thrown can not change what was sent. Reshape a stream in a custom API around `g.sys.db.getAllByStream` instead. ## Good to know - WebSocket notifications and automatic events run after the post hooks, on the final answer. A post hook which throws still lets the notification go out ; a pre hook which throws does not. - Post hooks have versions and a Test button on the API testing page, like pre hooks, and deploy with Git. ← Pre hooks Events --- # Encrypt Data API > Encrypt any value with the encrypt data system API of API Maker, using the transfer key of your secret or a key of your own, and get a string a client or a later call can decrypt. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-encrypt-data-api.html Encrypts a string, a number, an object or an array and answers the encrypted text. By default it uses `encryptionAlgorithmFETransfer` and `secretFETransfer` of the default [secret](/v1/docs/secrets/secrets.html) : the key you share with your frontend or mobile app, so the two sides can exchange encrypted data. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/encrypt-data` : `admin` is the user path of your account | | Body | `{ data, algorithm?, pass? }` | | Answer | `data` : the encrypted string | | From code | [`g.sys.system.encrypt`](/v1/examples/sys/system/encrypt.html) | ## The call ```json title="Body" { "data": { "name": "Joseph", "card": "4111 1111 1111 1111" } } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": "U2FsdGVkX19iIBA3FQy3OgFXeE4B2cC8lAfaJjCbMmUllceqb58YwvQqU33PkQEQ" } ``` | Key | Meaning | |---|---| | `data` | Required. Any JSON value. It is serialised before encryption, so [decrypt](/v1/docs/apis-all/system-apis/system-generated-decrypt-data-api.html) gives the same type back. | | `algorithm` | `AES` (default from the secret), `RC4` or `TRIPLEDES`. | | `pass` | Your own key, instead of `secretFETransfer` of the secret. | - `x-am-secret` picks another secret than the default one. ## From code ```typescript const text = await g.sys.system.encrypt({ name: 'Joseph' }); // with the transfer key of the secret const mine = await g.sys.system.encrypt('hello', T.EEncryptionAlgorithm.AES, 'my-own-key'); ``` ## Good to know - Fields of a table are encrypted at rest with `conversions: { encryption: true }` in the [schema](/v1/docs/schema/schema.html), with the `secret` key of the secret, not this API. - To send an encrypted request body, see [encrypted payloads](/v1/docs/features/security-features.html#encrypted-request-payloads) ; to get an encrypted answer, the header [`x-am-get-encrypted-data`](/v1/docs/apis-all/header/requestHeader.html#x-am-get-encrypted-data). ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key, for example an unknown `algorithm`. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Decrypt Data API > Decrypt a string made by the encrypt data API, or by a client with the transfer key of your secret, with the decrypt data system API of API Maker. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-decrypt-data-api.html Decrypts what [encrypt data](/v1/docs/apis-all/system-apis/system-generated-encrypt-data-api.html) produced, or what a client encrypted with `encryptionAlgorithmFETransfer` and `secretFETransfer` of the secret, and gives the original value back. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/decrypt-data` : `admin` is the user path of your account | | Body | `{ data, algorithm?, pass? }` | | Answer | `data` : the decrypted value, parsed as JSON when it is JSON | | From code | [`g.sys.system.decrypt`](/v1/examples/sys/system/decrypt.html) | ## The call ```json title="Body" { "data": "U2FsdGVkX19iIBA3FQy3OgFXeE4B2cC8lAfaJjCbMmUllceqb58YwvQqU33PkQEQ" } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": { "name": "Joseph", "card": "4111 1111 1111 1111" } } ``` - `algorithm` and `pass` work as on encrypt data : give the same ones the text was encrypted with. - A text which does not decrypt with the key answers `data: null`. ## From code ```typescript const value = await g.sys.system.decrypt(encryptedText); const mine = await g.sys.system.decrypt(encryptedText, T.EEncryptionAlgorithm.AES, 'my-own-key'); ``` ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Hash Data API > Hash a value with the hash data system API of API Maker - HMAC SHA-256 with the nonce or secret of your secret, the same hash the hashing conversion of a schema stores, so you can look a hashed column up. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-hash-data-api.html Answers the SHA-256 hash of the body, computed with the `nonce` (or, without one, the `secret`) of the default secret. A string is hashed as it is, exactly like the `hashing` conversion of a [schema](/v1/docs/schema/schema.html) hashes a field, so the hash of a password or a tax id given here equals the one stored in the table and can be used to find the row. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/hash-data` : `admin` is the user path of your account | | Body | the value to hash : a string, or any JSON | | Answer | `data` : the hash, a hex string | | From code | [`g.sys.system.hash`](/v1/examples/sys/system/hash.html) | ## The call ```json title="Body : a string" "Joseph" ``` ```json title="Body : an object (hashed as its JSON text)" { "name": "Joseph" } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": "be2a6a457cc36a48e4e231411d8d6498213f7ffb6d1e03066dd7fbaa6ec9402e" } ``` - The algorithm is `hashingAlgorithm` of the secret : `SHA256` is the one supported. - The same value always gives the same hash for the same secret, which is the point : hash a value on the way in and compare hashes on the way out. ## Look up a hashed column ```typescript title="Find the customer whose tax id (hashed in the table) is known" const hash = await g.sys.system.hash(taxId); const rows = await g.sys.db.query({ instance: 'mongodb', database: 'shop', collection: 'products', find: { supplier_tax_id_hash: hash } }); ``` - Keep an encrypted copy of the value in another field when you also need to read it back : encryption is reversible, hashing is not. ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Get Token API > Get the token of an API user or of a person from API Maker with the token API - username and password, or the name of a token generator, refresh tokens, several tokens in one call, and tenant tokens. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-token-api.html Gives the two tokens every call may need : the token of an **API user** (which application calls, sent in `x-am-authorization`) and the token of a **person**, a row of your own users table read by a DB token generator (sent in `x-am-user-authorization`). See [the two gates](/v1/docs/getting-started/how-it-works.html#the-two-gates). | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/token` : `admin` is the user path of your account | | Body | `{ u, p, expiresInSeconds? }`, `{ name, u, p }`, `{ refresh_token, name? }`, or an array of them | | Answer | `data` : `{ token, refresh_token, expires_in }`, or an array | | From code | [`g.sys.system.getToken`](/v1/examples/sys/system/getToken.html) | ## The token of an API user ```json title="Body : without name, an API user of API Maker" { "u": "default", "p": "12345", "expiresInSeconds": 259200 } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": { "token": "eyJhbGciOi…", "refresh_token": "eyJhbGciOi…", "expires_in": 259200 } } ``` - `expiresInSeconds` is optional : `jwtOptions.expiresIn` of the [configuration](/v1/docs/am-resources/api-maker-configurations.html) otherwise (72 hours by default). - The password of an API user lives in the secret, under the path set on the API user (`common.apiUserPasswords.default` for the API user of a new account). ## The token of a person ```json title="Body : with the name of a token generator" { "name": "users_tg", "u": "alice@acme.com", "p": "PASSWORD" } ``` - The generator (an [auth provider](/v1/docs/authorization/AMDB.html) of type DB) knows the users table and its username and password columns. A hashed password column works : the password given is hashed and compared. - The groups of the person come from the groups column of the generator. ## Refresh ```json { "name": "users_tg", "refresh_token": "eyJhbGciOi…" } ``` - `refresh_token` of an earlier answer gives a new token without the password. For a person, add the `name` of the generator. A refresh token is valid `refreshTokenValidForS` seconds (900 by default) after the token expired. ## Several tokens in one call ```json [ { "u": "default", "p": "12345" }, { "name": "users_tg", "u": "alice@acme.com", "p": "PASSWORD" } ] ``` - The answer is an array in the same order. The sample custom API `/default/login` of a new account does exactly this and returns both tokens to the app. ## The token of a tenant user ```text POST /api/system-api/admin/token x-am-tenant-username: acme { "name": "users_tg", "u": "alice@acme.com", "p": "PASSWORD" } ``` - When the generator reads a users table of a [multi-tenant](/v1/docs/features/multi-tenant.html) instance, name the tenant : the person is read from the database of that tenant, and the token works for that tenant only, also after a refresh. ## Who may call it - The token API is public by default : anybody can ask for a token with a username and a password. - When its [system API settings](/v1/docs/settings/systemApiSettings.html) list auth providers, it needs the tokens those settings ask for, like any other API. - A token is a JWT signed with `passJWT` of the server. Changing the password of the API user, or `passwordChangedAtColumn` of a person, invalidates the tokens made before. ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. The token API itself is public unless its settings say otherwise. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key, `Please provide token request with ['u', 'p'] or ['refresh_token'].` for an incomplete body. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Call External API > Call any HTTP API from API Maker with the call external API system API - one call, a list of calls in parallel or in sequence, with data copied from one answer into the next request. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-call-external-api.html Makes HTTP calls to any URL from the server : one call, or a list of calls run in parallel or one after the other, with `preProcess` and `postProcess` rules which copy a value of one answer into the request of another. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/call-external-api` : `admin` is the user path of your account | | Body | one call, an array of calls, or `{ type: "parallel" | "sequential", data: [ … ] }` | | Answer | `data` : the response of the call, `{ statusCode, headers, body, … }`, or an array of them | | From code | [`g.sys.system.callExternalApi`](/v1/examples/sys/system/callExternalApi.html) | ## One call ```json title="Body" { "url": "https://api.example.com/customers", "method": "POST", "timeout": 5000, "headers": { "authorization": "Bearer …" }, "queryParams": { "select": "first_name" }, "body": { "first_name": "James", "last_name": "Bond" } } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": { "statusCode": 201, "headers": { "content-type": "application/json" }, "body": { "id": 42, "first_name": "James" } } } ``` | Key | Meaning | |---|---| | `url` | Required. The full URL. | | `method` | `GET` (default), `HEAD`, `POST`, `PUT`, `DELETE`, `CONNECT`, `OPTIONS`, `TRACE`, `PATCH`. | | `timeout` | Milliseconds. | | `headers`, `queryParams`, `body` | What to send. | | `id` | A name for this call, used by `preProcess` and `postProcess` of other calls. | | `preProcess`, `postProcess` | Rules `{ from, to }` run before and after the call : `from` is a path starting with the `id` of a call (`saveApi.output.body.data.id`), `to` a path in a call (`setApi.body.customerId`). | | `output` | Filled with the answer of the call, for the rules of the others. | - The answer of a call is the response object of the HTTP client (undici) : `statusCode`, `headers`, and `body` parsed as JSON. A body which is not JSON comes as text. ## In parallel, in sequence ```json title="Body : a save, then two calls at once with its result" [ { "id": "saveApi", "url": "https://api.example.com/customers", "method": "POST", "body": { "first_name": "James" }, "postProcess": [ { "from": "saveApi.output.body.id", "to": "setApi.body.customerId" } ] }, { "type": "parallel", "data": [ { "id": "setApi", "url": "https://api.example.com/preferences", "method": "PUT", "body": {} }, { "id": "notify", "url": "https://hooks.example.com/new-customer", "method": "POST", "body": { "source": "shop" } } ] } ] ``` - The top level array runs in sequence ; `type: "parallel"` runs its `data` at once, `type: "sequential"` one after the other. Groups nest. - The answer is an array with one entry per call, nested like the body. ## From code ```typescript const resp = await g.sys.system.callExternalApi<{ id: number }>({ url: 'https://api.example.com/customers/42' }); if (resp.statusCode === 200) g.logger.log(resp.body.id); ``` - Custom APIs can also use any HTTP client of npm from the sandbox ; this API adds the parallel and sequential plans and the data copying without code. ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key, or a `url` which is not valid. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Execute Plain Query API > Run a SQL statement or a MongoDB command on any instance of the account with the execute plain query system API of API Maker - DDL, DML, joins, stored procedures, database commands - with the permissions of the caller. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-execute-plain-query-api.html Runs what the generated APIs can not : a SQL statement as text on a SQL instance, or a database command on MongoDB, and answers what the driver returns. It is the tool of [migration scripts](/v1/docs/features/database-migration.html) and of the few custom APIs which need a join, a stored procedure or a DDL statement. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/execute-plain-query` : `admin` is the user path of your account | | Body | `{ instance, database?, collection?, query }` | | Answer | `data` : the result of the driver (rows, or the result of the command) | | From code | [`g.sys.system.executeQuery`](/v1/examples/sys/system/executeQuery.html) | ## The call ```json title="Body : SQL" { "instance": "mysql8", "query": "SELECT status, COUNT(*) AS orders FROM inventory.orders GROUP BY status" } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "status": "PAID", "orders": 128 }, { "status": "NEW", "orders": 7 } ] } ``` ```json title="Body : a MongoDB command" { "instance": "mongodb", "database": "shop", "query": { "dbStats": 1 } } ``` | Key | Meaning | |---|---| | `instance` | Required. `crm::acme` runs on the tenant `acme` of a multi-tenant instance ; in a call made for a tenant, the plain name is enough. | | `database` | Required for PostgreSQL (one connection per database) and MongoDB. Other SQL instances name the database in the statement. | | `collection` | MongoDB, for commands which need it. | | `query` | The SQL text, or the command object of MongoDB (anything `db.command()` accepts). | ## Per database | Database | Example | |---|---| | MySQL, MariaDB, TiDB, Percona | `SELECT * FROM inventory.employees;` | | SQL Server | `SELECT * FROM inventory.dbo.employees;` · `USE inventory; exec dbo.procedureName;` | | PostgreSQL | `database: "e_commerce"` and `SELECT * FROM "public"."customers";` | | Oracle | `SELECT * FROM "INVENTORY"."employees"` : no semicolon at the end, backticks around the statement in code | | MongoDB | `{ "listIndexes": "orders" }`, `{ "createIndexes": "orders", "indexes": [ … ] }`, `{ "dbStats": 1 }` | - Several statements in one call work on the SQL databases whose connection string allows it (`multipleStatements=true` on MySQL). - The [code example](/v1/examples/sys/system/executeQuery.html) shows DDL, DML, joins, stored procedures and MongoDB indexes. ## Good to know - The statement runs with the user of the connection string : it can create, alter and drop. The [APIs Security Report](/v1/docs/apis-security/api-security-report.html) lists this API with the sensitive system APIs ; grant it to the groups which really need it. - Values from a request must be escaped by you, or better, passed through the generated APIs. For indexes, prefer [create indexes](/v1/docs/apis-all/system-apis/system-generated-create-indexes-api.html), which works the same on every database. ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key, `Unable to get instance with name …` for an unknown instance. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Get Secret API > Read one or several keys of a secret by their path with the get secret system API of API Maker - connection strings, API keys of external services, your own keys - from the default secret or a named one. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-get-secret-by-name-api.html Reads values of a [secret](/v1/docs/secrets/secrets.html) by their path : `common.encryptionAlgorithm`, `stripe.apiKey`… One key gives its value, several give an array in the same order. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/get-secret-by-name` : `admin` is the user path of your account | | Body | `{ keys }` ; from code also `fromSecretName` | | Answer | `data` : the value, or an array of values | | From code | [`g.sys.system.getSecret`](/v1/examples/sys/system/getSecret.html) | ## The call ```json title="Body : one key" { "keys": "stripe.apiKey" } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": "sk_live_…" } ``` ```json title="Body : several keys" { "keys": [ "stripe.apiKey", "common.connectionString.mysql_8" ] } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ "sk_live_…", "mysql://…" ] } ``` - `keys` is the full path in the secret object, with dots. A path to an object gives the object. - The header `x-am-secret` (the id of a secret) reads another secret of the account than the default one. From code, `fromSecretName` names it instead ; over HTTP that key is ignored. ## From code ```typescript const key = await g.sys.system.getSecret('stripe.apiKey'); const [ key2, cs ] = await g.sys.system.getSecret([ 'stripe.apiKey', 'common.connectionString.mysql_8' ]); const staging = await g.sys.system.getSecret('stripe.apiKey', 'staging'); // from the secret named staging ``` ## Good to know - Secrets are never in Git and never in the answer of another API : this API is the way to read them, and it is a sensitive one. Grant it to the groups which need it, or read the secret from your code only (`NO_ACCESS` custom APIs). ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key, `Provided secret id … is not valid.` for a bad `x-am-secret`. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Get Redis Key API > Read one or several keys of your own from the Redis of API Maker with the get redis key system API - values set with set redis key, in the namespace of your account. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-get-redis-key-api.html Reads values you stored with [set redis key](/v1/docs/apis-all/system-apis/system-generated-set-redis-key-api.html) from the external Redis of API Maker (the one which also holds the cache of the APIs). Keys are kept in a namespace of the account, so two accounts never see each other's keys. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/get-redis-key` : `admin` is the user path of your account | | Body | `{ keys }` | | Answer | `data` : the value (a string, `null` when the key is not there), or an array of values | | From code | [`g.sys.cache.getKey`](/v1/examples/sys/cache/getKey.html) | ## The call ```json title="Body : one key" { "keys": "session:user123" } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": "{\"cart\":3}" } ``` ```json title="Body : several keys" { "keys": [ "session:user123", "captcha:abc" ] } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ "{\"cart\":3}", null ] } ``` - Values are strings : `JSON.parse` what you stored as JSON. ## From code ```typescript const raw = await g.sys.cache.getKey('session:user123'); const session = raw ? JSON.parse(raw) : null; ``` ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Set Redis Key API > Store one or several keys of your own in the Redis of API Maker with the set redis key system API - a value, a TTL in seconds, and NX to write only when the key is new. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-set-redis-key-api.html Stores values in the external Redis of API Maker, in the namespace of the account : sessions, captchas, counters, anything a later call or another server needs. Each key can expire on its own. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/set-redis-key` : `admin` is the user path of your account | | Body | an array of `{ key, value, ttl?, NX? }` | | Answer | `data` : `true` | | From code | [`g.sys.cache.setKey`](/v1/examples/sys/cache/setKey.html) | ## The call ```json title="Body" [ { "key": "session:user123", "value": "{\"cart\":3}", "ttl": 3600 }, { "key": "captcha:abc", "value": "7H2K", "ttl": 180, "NX": true } ] ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": true } ``` | Key | Meaning | |---|---| | `key` | The name. It is stored under the namespace of the account. | | `value` | A string. Serialise objects with `JSON.stringify`. | | `ttl` | Seconds before the key expires. Without it, `redisValueExpireInSeconds` of the [configuration](/v1/docs/am-resources/api-maker-configurations.html) (7200 by default) : keys never live forever. | | `NX` | `true` : write only when the key does not exist yet. | ## From code ```typescript await g.sys.cache.setKey('session:user123', JSON.stringify({ cart: 3 }), 3600); await g.sys.cache.setKey([ { key: 'captcha:abc', value: '7H2K', ttl: 180, NX: true } ]); ``` - The [Redis dashboard](/v1/docs/dashboard/redis-dashboard.html) shows the keys with their TTL and lets you edit them. ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Remove Redis Key API > Delete one or several keys of your own from the Redis of API Maker with the remove redis key system API - sessions, captchas, temporary data - a missing key is not an error. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-remove-redis-key-api.html Deletes keys stored with [set redis key](/v1/docs/apis-all/system-apis/system-generated-set-redis-key-api.html). A key which is not there is skipped without an error, so the call can run again. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/remove-redis-key` : `admin` is the user path of your account | | Body | `{ keys }` | | Answer | `data` : `true` | | From code | [`g.sys.cache.removeKey`](/v1/examples/sys/cache/removeKey.html) | ## The call ```json title="Body : one key" { "keys": [ "session:user123" ] } ``` ```json title="Body : several keys" { "keys": [ "session:user123", "captcha:abc" ] } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": true } ``` - No wildcards, on purpose : a pattern could delete the cache of the APIs. Name the keys. ## From code ```typescript await g.sys.cache.removeKey('session:user123'); await g.sys.cache.removeKey([ 'session:user123', 'captcha:abc' ]); ``` - To clear the cached answers of an API rather than your own keys, use the reset cache APIs : [database](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-db-api.html), [custom](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-custom-api.html), [system](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-system-api.html), [third party](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-third-party-api.html). ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Reset Database Cache API > Drop the cached answers of the APIs of a table, a database or an instance with the reset database cache system API of API Maker, for one tenant or all of them. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-db-api.html Drops the cached answers of the generated and schema APIs of a table, of every table of a database, or of every database of an instance. Writes through API Maker do it on their own ; this API is for data changed outside API Maker, or a cache you want gone now. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/reset-redis-cache-db` : `admin` is the user path of your account | | Body | `{ instance, database?, collection? }` or an array of them | | Answer | `data` : the number of cache entries removed, or an array of numbers | | From code | [`g.sys.cache.resetCacheDB`](/v1/examples/sys/cache/resetCacheDB.html) | ## The call ```json title="Body : one table" { "instance": "mysql8", "database": "inventory", "collection": "customers" } ``` ```json title="Body : every table of a database" { "instance": "mysql8", "database": "inventory" } ``` ```json title="Body : several at once" [ { "instance": "mysql8", "database": "inventory", "collection": "customers" }, { "instance": "mongodb", "database": "shop", "collection": "orders" } ] ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": 1 } ``` - `table` works in place of `collection`. - An array body answers an array of counts in the same order ; an unknown instance gives `-1` there and a `404` in `errors`. ## One tenant ```json { "instance": "crm::acme", "database": "crm", "collection": "customers" } ``` - Names the tenant after the instance : only the cached answers of `acme` go. Without a tenant, the cache of the table is reset for the structure database and every tenant. See [Multi-tenant](/v1/docs/features/multi-tenant.html#caching). ## From code ```typescript await g.sys.cache.resetCacheDB({ instance: 'mysql8', database: 'inventory', collection: 'customers' }); ``` - One call for one answer : the header [`x-am-cache-control: reset_cache`](/v1/docs/apis-all/header/requestHeader.html#x-am-cache-control) on the read itself. ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Reset Custom API Cache API > Drop the cached answers of one or several custom APIs with the reset custom API cache system API of API Maker, for one tenant or all of them. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-custom-api.html Drops the cached answers of a custom API with `enableCaching`. `resetCacheOnModificationOf` in its settings does it on its own when a table it names is written ; this API is for the other cases. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/reset-redis-cache-custom-apis` : `admin` is the user path of your account | | Body | `{ name, tenantUsername? }` or an array of them | | Answer | `data` : the number of cache entries removed | | From code | [`g.sys.cache.resetCacheCustomApis`](/v1/examples/sys/cache/resetCacheCustomApis.html) | ## The call ```json title="Body : one custom API" { "name": "customers-report" } ``` ```json title="Body : several" [ { "name": "customers-report" }, { "name": "top-products" } ] ``` ```json title="Body : one tenant" { "name": "customers-report", "tenantUsername": "acme" } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": 1 } ``` - `name` is the name of the custom API (its settings), not its path. An unknown name gives `-1` and a `404` in `errors`. - Without `tenantUsername`, the cached answers of every tenant and of the calls without a tenant are reset. ## From code ```typescript await g.sys.cache.resetCacheCustomApis('customers-report'); await g.sys.cache.resetCacheCustomApis({ name: 'customers-report', tenantUsername: 'acme' }); ``` ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Reset System API Cache API > Drop the cached answers of one or several system APIs with the reset system API cache system API of API Maker, for one tenant or all of them. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-system-api.html Drops the cached answers of a system API whose [settings](/v1/docs/settings/systemApiSettings.html) have `enableCaching`, for example get table meta or get secret. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/reset-redis-cache-system-apis` : `admin` is the user path of your account | | Body | `{ name, tenantUsername? }` or an array of them | | Answer | `data` : the number of cache entries removed | | From code | [`g.sys.cache.resetCacheSystemApis`](/v1/examples/sys/cache/resetCacheSystemApis.html) | ## The call ```json title="Body" { "name": "Get table meta data" } ``` ```json title="Body : several, one for a tenant" [ { "name": "Get table meta data" }, { "name": "Get secret key-keys", "tenantUsername": "acme" } ] ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": 1 } ``` - `name` is the name shown on the System API page : Encrypt data, Decrypt data, Hash data, Get token, Call external API, Execute plain query, Get secret key-keys, Get redis key-keys, Set redis key-keys, Remove redis key-keys, Reset database cache, Reset third party API cache, Reset custom apis cache, Reset system apis cache, Get table meta data, Create indexes, Drop indexes, Get indexes, Emit event, Emit event WS, Is valid data for table, Is valid data for custom API, Is valid data for third party API, Multi tenant instance updated, Is valid connection string. An unknown name gives `-1` and a `404` in `errors`. ## From code ```typescript await g.sys.cache.resetCacheSystemApis('Get table meta data'); ``` ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Reset Third Party API Cache API > Drop the cached answers of the APIs of an installed third party bundle version with the reset third party API cache system API of API Maker. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-third-party-api.html Drops the cached answers of the APIs of a version of an installed [third party bundle](/v1/docs/apis-all/thirdParty-apis/installed-third-party-api.html) (deprecated feature). | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/reset-redis-cache-third-party-apis` : `admin` is the user path of your account | | Body | `{ apiBundleName, apiVersionName?, tenantUsername? }` | | Answer | `data` : the number of cache entries removed | | From code | [`g.sys.cache.resetCacheThirdPartyApis`](/v1/examples/sys/cache/resetCacheThirdPartyApis.html) | ## The call ```json title="Body" { "apiBundleName": "bundle_name", "apiVersionName": "api_version_name" } ``` ```json title="Body : one tenant" { "apiBundleName": "bundle_name", "apiVersionName": "api_version_name", "tenantUsername": "acme" } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": 2 } ``` ## From code ```typescript await g.sys.cache.resetCacheThirdPartyApis({ apiBundleName: 'bundle_name', apiVersionName: 'api_version_name' }); ``` ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Get Table Meta API > Read the columns of a table or the fields of a collection in one common format with the get table meta system API of API Maker - names, types, counts - on every database, for migrations and dynamic code. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-get-table-meta-api.html Describes a table : the name and the type of every column (or of every field found in a MongoDB collection), in the same format for every database. Migration scripts use it to check whether a column exists before adding it. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/get-table-meta` : `admin` is the user path of your account | | Body | `{ instance, database, collection }` or an array of them | | Answer | `data` : an array of fields, or an array of arrays | | From code | [`g.sys.system.getTableMeta`](/v1/examples/sys/system/getTableMeta.html) | ## The call ```json title="Body" { "instance": "mysql8", "database": "inventory", "collection": "customers" } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "name": "customer_id", "path": "customer_id", "count": 24, "total_count": 24, "type": "Number", "has_duplicates": false, "probability": 1 }, { "name": "first_name", "path": "first_name", "count": 24, "total_count": 24, "type": [ "String", "Null" ], "has_duplicates": true, "probability": 1 } ] } ``` | Key | Meaning | |---|---| | `name`, `path` | The column, with its path for nested MongoDB fields. | | `type` | The type, or the list of types seen (MongoDB). | | `count`, `total_count`, `probability` | For MongoDB, in how many of the sampled documents the field appears. | | `has_duplicates` | Whether the same value appears twice in the sample. | - `table` works in place of `collection`. An array of tables answers an array of arrays, in the same order. - A table of a tenant : `"instance": "crm::acme"`, or the header `x-am-tenant-username`. ## From code ```typescript title="A migration which adds a column once" const meta = await g.sys.system.getTableMeta({ instance: 'mysql8', database: 'inventory', table: 'order_transactions' }); if (!meta.find(c => c.name === 'description')) { await g.sys.system.executeQuery({ instance: 'mysql8', query: 'ALTER TABLE `inventory`.`order_transactions` ADD COLUMN `description` varchar(255) NULL' }); } ``` ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Create Indexes API > Create indexes on any database of the account with one system API of API Maker - the same request for MongoDB, MySQL, MariaDB, SQL Server, PostgreSQL and Oracle, safe to run again and again. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-create-indexes-api.html Creates one or more indexes with the same request on every database. An index which exists already is left alone, a field the database can not index is skipped with the reason, and the call can run again and again : it is what [Index Maker](/v1/extensions/index_maker/introduction.html) runs from its migration script `am_index_maker` on deploy. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/create-indexes` : `admin` is the user path of your account | | Body | `{ indexes: [ … ], ifNotExists?, continueOnError?, skipUnindexable? }` | | Answer | `data` : one result per definition, in order | | From code | [`g.sys.system.createIndexes`](/v1/examples/sys/system/createIndexes.html) | ## The call ```json title="Body" { "indexes": [ { "instance": "mysql8", "database": "inventory", "table": "orders", "fields": [ { "name": "customer_id" }, { "name": "status", "order": "DESC" } ] }, { "instance": "mongodb", "database": "inventory", "collection": "products", "name": "ux_products_sku", "unique": true, "fields": [ { "name": "sku" } ] } ], "ifNotExists": true, "continueOnError": true, "skipUnindexable": true } ``` | Property | Required | Meaning | |---|---|---| | `indexes` | yes | The definitions, one per index. | | `indexes[].instance` | yes | Instance name. `crm::acme` names the tenant `acme` of the multi-tenant instance `crm` ; in a call made for a tenant, the plain name is enough. | | `indexes[].database` | yes | Database name, as the account sees it. | | `indexes[].collection` or `table` | yes | The table. PostgreSQL : `public.orders`, SQL Server : `dbo.orders`. | | `indexes[].fields` | yes | `[{ name, order?, nullsFirst? }]`. `order` : `ASC` (default), `DESC`, `HASHED` (MongoDB), `TEXT` (MongoDB). `nullsFirst` : PostgreSQL. | | `indexes[].name` | no | Index name. Missing : `am_ix_
__` (`am_ixu_` when unique), cut to the limit of the database. | | `indexes[].unique` | no | A unique index. | | `indexes[].type` | no | MySQL : `FULLTEXT`, `SPATIAL` ; SQL Server : `CLUSTERED`, `XML`, `SPATIAL` ; PostgreSQL : `hash`, `gin`, `gist`, `spgist`, `brin`. | | `indexes[].method` | no | MySQL : `BTREE`, `HASH`, `RTREE`. | | `ifNotExists` | no, `true` | An index with the same name, or with the very same fields under another name, is `EXISTS` : nothing is created twice. | | `continueOnError` | no, `true` | An index the database refuses is `FAILED` in the results and the next ones go on. `false` : the call fails at the first refusal. | | `skipUnindexable` | no, `true` | A field of a type no plain index can hold (TEXT on MySQL, json on PostgreSQL, CLOB on Oracle, `varchar(max)` on SQL Server, a primary key…) is `SKIPPED` with the reason, before the database is asked. `false` : the database decides. | ## The answer ```json { "success": true, "statusCode": 200, "data": [ { "instance": "mysql8", "database": "inventory", "collection": "orders", "name": "am_ix_orders_customer_id_status_3f9a1c", "fields": [ "customer_id", "status" ], "status": "CREATED" }, { "instance": "mongodb", "database": "inventory", "collection": "products", "name": "ux_products_sku", "fields": [ "sku" ], "status": "EXISTS", "message": "The index \"ux_products_sku\" exists already." } ] } ``` | Status | Meaning | |---|---| | `CREATED` | The index was created, and logged in the index logs of Index Maker. | | `EXISTS` | An index with this name, or with the same fields, is there already. | | `SKIPPED` | A field can not go in a plain index of this database ; `message` says which and why. | | `FAILED` | The database refused ; `message` is its error. | ## Good to know - Every database is supported : MongoDB, MySQL, MariaDB, SQL Server, PostgreSQL and Oracle. The statements are the plain `CREATE INDEX` of each one, so they run on every version. - The API changes the database : the APIs Security Report lists it with the sensitive system APIs. Give it only to the groups that need it. - Every index created is logged with its source (`SYSTEM_API`, or `MIGRATION_SCRIPT` when the body says `"source": "MIGRATION_SCRIPT"`). ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Drop Indexes API > Drop indexes by name on any database of the account with one system API of API Maker - a missing index is reported, not an error, so the call can run again. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-drop-indexes-api.html Drops one or more indexes of a table by name, on any database, and reports what happened to each one. A name the table does not have is `NOT_FOUND`, not an error. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/drop-indexes` : `admin` is the user path of your account | | Body | `{ instance, database, collection | table, indexes: [ names ], ifExists?, continueOnError? }` or an array of them | | Answer | `data` : one result per name | | From code | [`g.sys.system.dropIndexes`](/v1/examples/sys/system/dropIndexes.html) | ## The call ```json title="Body" { "instance": "mysql8", "database": "inventory", "table": "orders", "indexes": [ "am_ix_orders_customer_id_status_3f9a1c", "old_idx_status" ], "ifExists": true, "continueOnError": true } ``` | Property | Required | Meaning | |---|---|---| | `instance` | yes | Instance name, `crm::acme` for a tenant of a multi-tenant instance. | | `database` | yes | Database name, as the account sees it. | | `collection` or `table` | yes | The table. | | `indexes` | yes | The names to drop. | | `ifExists` | no, `true` | A name the table does not have is `NOT_FOUND`. `false` : it is `FAILED`. | | `continueOnError` | no, `true` | A drop the database refuses is `FAILED` and the next ones go on. `false` : the call fails at the first one. | ## The answer ```json { "success": true, "statusCode": 200, "data": [ { "instance": "mysql8", "database": "inventory", "collection": "orders", "name": "am_ix_orders_customer_id_status_3f9a1c", "fields": [ "customer_id", "status" ], "status": "DROPPED" }, { "instance": "mysql8", "database": "inventory", "collection": "orders", "name": "old_idx_status", "fields": [], "status": "NOT_FOUND", "message": "The index \"old_idx_status\" does not exist." } ] } ``` - The `fields` of a dropped index are the ones it had. Every drop is logged in the index logs of Index Maker with its source. ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Get Indexes API > List the indexes of a table on any database of the account in one common format with the get indexes system API of API Maker - name, key fields with their order, unique or not. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-get-indexes-api.html Lists the indexes of a table in the same format for every database : the name, the fields with their direction, whether it is unique, and the access method when the database says it. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/get-indexes` : `admin` is the user path of your account | | Body | `{ instance, database, collection | table }` or an array of them | | Answer | `data` : an array of indexes, or an array of arrays | | From code | [`g.sys.system.getIndexes`](/v1/examples/sys/system/getIndexes.html) | ## The call ```json title="Body" { "instance": "mysql8", "database": "inventory", "table": "orders" } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "name": "PRIMARY", "key": { "id": 1 }, "unique": true }, { "name": "am_ix_orders_customer_id_status_3f9a1c", "key": { "customer_id": 1, "status": -1 }, "unique": false, "method": "BTREE" } ] } ``` | Property | Meaning | |---|---| | `name` | Index name. | | `key` | The fields in order, with their direction : `1` ascending, `-1` descending, `hashed` or `text` on MongoDB. | | `unique` | A unique index (the primary key included). | | `method` | The access method when the database tells it (`BTREE`, `HASH`, `gin`…). | - An array of tables answers an array of lists, in the same order. ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Emit Event API > Emit one or several events of API Maker over HTTP with the emit event system API - the event data reaches the listeners, and their outputs come back. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-emit-event-api.html Emits an [event](/v1/docs/apis-all/events/user-created-events-api.html) of the account : its listeners run with the data, and the call answers what they returned. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/emit-event` : `admin` is the user path of your account | | Body | `{ name, eventData?, executeListeners? }` or an array of them | | Answer | `data` : the event with `outputArr`, one entry per listener | | From code | [`g.sys.system.emitEvent`](/v1/examples/sys/system/emitEvent.html) | ## The call ```json title="Body" { "name": "order-placed", "eventData": { "order_no": 1001 }, "executeListeners": [ "send-invoice" ] } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": { "name": "order-placed", "eventData": { "order_no": 1001 }, "executeListeners": [ "send-invoice" ], "outputArr": [ { "listenerName": "send-invoice", "output": { "notified": true } } ] } } ``` ```json title="Body : several events" [ { "name": "order-placed", "eventData": { "order_no": 1001 } }, { "name": "stock-changed", "executeListeners": [ "sync-warehouse", "alert-low-stock" ] } ] ``` - `executeListeners` names the listeners to run ; absent, every listener of the event runs. - A chain which comes back to an event already running is stopped : no infinite loop. ## From code ```typescript const result = await g.sys.system.emitEvent('order-placed', { order_no: 1001 }, [ 'send-invoice' ]); g.logger.log(result.outputArr); ``` ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. The group must also grant the event. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Emit WebSocket Event API > Push a notification to the WebSocket clients subscribed to a custom event of API Maker with the emit event WS system API, from any server or any code. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-emit-event-ws-api.html Sends the data to every client subscribed to a custom [WebSocket event](/v1/docs/pages/web-socket-event-page.html) whose subscription condition matches, whatever server the client is connected to. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/emit-event-ws` : `admin` is the user path of your account | | Body | `{ name, eventData? }` or an array of them | | Answer | `data` : `true` | | From code | [`g.sys.system.emitEventWS`](/v1/examples/sys/system/emitEventWS.html) | ## The call ```json title="Body" { "name": "default/orders-updated", "eventData": { "recipientId": "42", "order_no": 1001, "status": "SHIPPED" } } ``` ```json title="Body : several events" [ { "name": "default/orders-updated", "eventData": { "recipientId": "42", "order_no": 1001 } }, { "name": "stock-alerts", "eventData": { "sku": "WM-1", "qty": 2 } } ] ``` - The event must exist on the WebSocket Events page. A client receives the data when its subscription has no condition, or when the emitted data has the values of its `criteria`. - To notify one person, put their id in the data and let their client subscribe with that id in `criteria`. ## From code ```typescript await g.sys.system.emitEventWS('default/orders-updated', { recipientId: '42', order_no: 1001 }); ``` ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. The group must also grant the WebSocket event. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key, `WebSocket event not found with name …` for an unknown event. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Is Valid Data for Table API > Check data against the schema of a table without saving it with the is valid data for table system API of API Maker - the same conversions and validations as a save or an update, the errors as the answer. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-is-valid-data-for-table-api.html Runs the [schema](/v1/docs/schema/schema.html) of a table on some data without writing anything, and answers the list of errors a save (or an update) would have refused it with. Empty list : the data is fine. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/is-valid-data-for-table` : `admin` is the user path of your account | | Body | `{ instance, database, collection | table, data, sendingTo? }` or an array of them | | Answer | `data` : an array of errors (empty when valid), or an array of arrays | | From code | [`g.sys.system.isValidDataForTable`](/v1/examples/sys/system/isValidDataForTable.html) | ## The call ```json title="Body" { "instance": "mongodb", "database": "shop", "collection": "products", "data": { "name": "M", "currency": "EUR", "price_cents": -5, "contact_email": "x@example.com" } } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "type": "schemaKeyNotFound", "field": "contact_email", "message": "Property not found in schema for key 'contact_email'.", "code": 400 }, { "type": "minLength", "field": "name", "message": "Property 'name' should have minimum length of '2'.", "code": 400 }, { "type": "enumValidation", "field": "currency", "message": "Property 'currency' should have any value from [IN, US, GB].", "code": 400 }, { "type": "min", "field": "price_cents", "message": "Please provide minimum '0' for 'price_cents' field.", "code": 400 } ] } ``` | Key | Meaning | |---|---| | `instance`, `database`, `collection` or `table` | The table whose schema is used. A table without a schema is refused. | | `data` | One object, or an array of objects : then the answer is an array of error lists, one per object. | | `sendingTo` | `SAVE` (default) : `required` fields must be there. `UPDATE` : only the fields present are checked, the way update by id checks them. | - The call is `success: true` even when the data is not : the errors are the answer, not a failure of the call. - An array of bodies checks several tables in one call. ## From code ```typescript const errors = await g.sys.system.isValidDataForTable({ instance: 'mongodb', database: 'shop', collection: 'products', data: g.req.body, sendingTo: 'SAVE' }); if (errors.length) { g.res.statusCode = T.EStatusCode.BAD_REQUEST; return errors; } ``` - Useful for a wizard which validates each step before the final save, or for an import which reports every bad row before writing any. ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key, `Unable to find schema for …` for a table without a schema. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Is Valid Data for Custom API > Check a body or query params against the request schema of a custom API without calling it, with the is valid data for custom API system API of API Maker. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-is-valid-data-for-custom-api.html Runs the `reqBodySchema` or `reqQueryParametersSchema` of a [custom API](/v1/docs/apis-all/custom-apis/user-created-custom-api.html) on some data and answers the errors the API would refuse it with, without running the API. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/is-valid-data-for-custom-api` : `admin` is the user path of your account | | Body | `{ name, type, data }` or an array of them | | Answer | `data` : an array of errors (empty when valid), or an array of arrays | | From code | [`g.sys.system.isValidDataForCustomAPI`](/v1/examples/sys/system/isValidDataForCustomAPI.html) | ## The custom API settings ```typescript title="Basic info of the custom API" linenums="1" let customApi: T.ICustomApiSettingsTypes = { name: 'Create Customer', path: '/customers', requestMethod: T.ERequestMethod.POST, errorList: [], reqBodySchema: { first_name: { __type: T.EType.string, validations: { required: true } }, phone: { __type: T.EType.number, validations: { required: true, min: 5, max: 999 } }, }, }; ``` ## The call ```json title="Body" { "name": "Create Customer", "type": "BODY", "data": { "phone": 3 } } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "type": "required", "field": "first_name", "message": "Please provide valid 'first_name' field with type 'string'.", "code": 400 }, { "type": "min", "field": "phone", "message": "Please provide minimum '5' for 'phone' field.", "code": 400 } ] } ``` | Key | Meaning | |---|---| | `name` | The name of the custom API. | | `type` | `BODY` for `reqBodySchema`, `QUERY_PARAMS` for `reqQueryParametersSchema`. | | `data` | The body or the query params to check. | ## From code ```typescript const errors = await g.sys.system.isValidDataForCustomAPI({ name: 'Create Customer', type: T.ECustomAPIDataValidationType.BODY, data: draft }); ``` ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key, `Unable to find custom API with name …`. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Is Valid Data for Third Party API > Check a body or query params against the request schema of an installed third party API with the is valid data for third party API system API of API Maker (deprecated feature). Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-is-valid-data-for-third-party-api.html Runs the request schema declared by a [third party API](/v1/docs/apis-all/thirdParty-apis/installed-third-party-api.html) (a deprecated feature) on some data and answers the errors, without calling the API. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/is-valid-data-for-third-party-api` : `admin` is the user path of your account | | Body | `{ apiBundleName, apiVersionName, name, type, data, isArray? }` or an array of them | | Answer | `data` : an array of errors (empty when valid), or an array of arrays | | From code | [`g.sys.system.isValidDataForThirdPartyAPI`](/v1/examples/sys/system/isValidDataForThirdPartyAPI.html) | ## The call ```json title="Body" { "apiBundleName": "bundle_name", "apiVersionName": "api_version", "name": "api_name", "type": "BODY", "data": { "name": "Bob", "mobile": 1234567890 } } ``` ```json title="Body : several, one on query params" [ { "apiBundleName": "bundle_name", "apiVersionName": "api_version", "name": "api_name", "type": "BODY", "data": { "name": "Bob" } }, { "apiBundleName": "bundle_name", "apiVersionName": "api_version", "name": "api_name", "type": "QUERY_PARAMS", "data": { "size": 123456 } } ] ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "type": "required", "field": "mobile", "message": "Please provide valid 'mobile' field with type 'number'.", "code": 400 } ] } ``` - `type` is `BODY` or `QUERY_PARAMS` ; `isArray: true` when the API expects an array body. ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Multi Tenant Instance Updated API > Drop the connection pools of a tenant on every API Maker server after its connection string changed, with the multi tenant instance updated system API, so its next request uses the new database. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-multi-tenant-instance-updated-api.html Call it when the connection string of a tenant of a [multi-tenant](/v1/docs/features/multi-tenant.html) instance changes in the tenants table : every server drops the connection pools of that tenant and the cached answers of the instance and of the tenants table, so the next request of the tenant reads its row again and connects to the new database. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/multi-tenant-instance-updated` : `admin` is the user path of your account | | Body | `{ instanceName, username }` or an array of them | | Answer | `data` : one answer per item, `{ success, data }` | | From code | [`g.sys.system.multiTenantInstanceUpdated`](/v1/examples/sys/system/multiTenantInstanceUpdated.html) | ## The call ```json title="Body : one tenant of one instance" { "instanceName": "crm", "username": "acme" } ``` ```json title="Body : several" [ { "instanceName": "crm", "username": "acme" }, { "instanceName": "billing", "username": "acme" } ] ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": [ { "success": true, "data": true }, { "success": false, "data": false, "error": { "code": 400, "message": "Instance 'billing' is not a multi tenant structure instance." } } ] } ``` - Each item is answered on its own : an unknown instance (404) or an instance which is not multi-tenant (400) does not stop the other items. - Both fields are required. - Until this API is called, the servers keep using the pools they have. For PostgreSQL, the pools of every database of the tenant are dropped. ## From code ```typescript await g.sys.system.multiTenantInstanceUpdated({ instanceName: 'crm', username: 'acme' }); ``` ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Is Valid Connection String API > Test a database connection string before saving it with the is valid connection string system API of API Maker - MongoDB, MySQL, MariaDB, SQL Server, PostgreSQL and Oracle - the check behind the Test Instance button. Source: https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-is-valid-connection-string-api.html Opens a connection with the string given and closes it : the check the **Test Instance** button of the instances page makes. Useful for an onboarding screen which lets a customer register their own database, or before writing a tenant row. | | | |---|---| | Method | POST | | URL | `/api/system-api/admin/is-valid-connection-string` : `admin` is the user path of your account | | Body | `{ connectionString, instanceType, oracleDBUsername?, oracleDBPassword?, oracleDBPrivilege? }` or an array of them | | Answer | `data` : `{ success, data: true | false, error? }`, or an array of them | | From code | [`g.sys.system.isValidConnectionString`](/v1/examples/sys/system/system.html) | ## The call ```json title="Body" { "connectionString": "postgresql://crm:secret@db-1:5432/crm_acme", "instanceType": "POSTGRE_SQL_DB" } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": { "success": true, "data": true } } ``` ```json title="Answer when the database refuses" { "success": true, "statusCode": 200, "data": { "success": false, "data": false, "error": "password authentication failed for user \"crm\"" } } ``` | Key | Meaning | |---|---| | `connectionString` | The string, in the format of the database : see the [connection strings](/v1/docs/Database-connection-string/mongodb-connection-strings.html) pages. | | `instanceType` | `MONGO_DB`, `MYSQL_DB`, `MARIA_DB`, `SQL_SERVER_DB`, `POSTGRE_SQL_DB` or `ORACLE_DB`. | | `oracleDBUsername`, `oracleDBPassword`, `oracleDBPrivilege` | Oracle : the user, the password and the privilege, as on the instance form. | - An array of bodies checks several strings in one call and answers an array in the same order. ## From code ```typescript const check = await g.sys.system.isValidConnectionString({ connectionString: cs, instanceType: T.EInstanceType.POSTGRE_SQL_DB }); if (!check.success) throw new Error('This database can not be reached : ' + check.error); ``` ## Access and settings - Over HTTP, a system API answers once its [settings](/v1/docs/settings/systemApiSettings.html) give it `apiAccessType: TOKEN_ACCESS` (the token of an API user whose [group](/v1/docs/apis-security/api-group-permission.html) grants this system API, in `x-am-authorization`) or `IS_PUBLIC`. Without settings it is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused. - The settings can also cache the answer or require person tokens (`authProviders`) ; [pre and post hooks](/v1/docs/apis-all/hooks/preHook-api.html) run around it like around any API. - The [request headers](/v1/docs/apis-all/header/requestHeader.html) apply : `x-am-response-case`, `x-am-content-type-response`, `x-am-internationalization`, `x-am-tenant-username`… ## Errors | Code | When | |---|---| | `400` | The body is wrong : the message names the missing or invalid key. | | `401` | No valid API user token, or the API is `NO_ACCESS` : `You are not authorized to access this API.` | | `403` | No group grants this system API. | ## Related - [All APIs at a glance](/v1/docs/apis-all/overview.html) · [Response format](/v1/docs/apis-all/response-format.html) · [System API settings](/v1/docs/settings/systemApiSettings.html) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Events and Listeners > Events in API Maker - named events with any number of TypeScript listeners, emitted from your code, from the emit-event system API or automatically after an API hit, with the outputs of the listeners returned and infinite chains stopped. Source: https://docs.apimaker.dev/v1/docs/apis-all/events/user-created-events-api.html An event is a name with listeners : TypeScript functions which run when the event is emitted. Your code emits it, a client emits it through the system API, or API Maker emits it on its own after an API you attach it to. Events keep the side effects of an operation (mails, stock, notifications, exports) out of the API which triggers them. > Diagram : Events : emitted by code, by the system API or after an API hit, with N listeners, and no infinite loops ## Create an event `API Info → Events`, then **+**. | Field | Meaning | |---|---| | Name | The name used to emit it. Unique in the account. | | Automatically trigger on API hit | On : the event is emitted after every successful call of the APIs listed under it. | | Trigger on APIs | Instance APIs (an API of a table), custom APIs, system APIs, third party APIs. Several at once. | | Listeners | One or more, each with a name, a code, a timeout in minutes, versions, and `runOnNativeProcess`. | | Save response in log | Keep what the listeners returned in the log table. | ## A listener ```typescript linenums="1" import * as T from 'types'; import * as db from 'db-interfaces'; async function main(g: T.IAMGlobal) { const data = g.req.eventData; // what was emitted await g.sys.db.saveSingleOrMultiple({ instance: 'mongodb', database: 'shop', collection: 'notifications', saveData: { order_no: data.order_no, text: `Order ${data.order_no} placed` }, }); return { notified: true }; // returned to the emitter } module.exports = main; ``` - Every listener gets the same `g` as a custom API : `g.req.eventData` holds the emitted data, `g.sys` reaches every API, `g.logger` writes to the log table. - Listeners run one after the other, each within its timeout. - For an **automatic trigger**, `g.req.eventData` is the whole answer of the API which triggered it (`{ success, statusCode, data, … }`), so a listener sees what was saved or read. ## Emit from your code ```typescript linenums="1" const result = await g.sys.system.emitEvent('order-placed', { order_no: 1001, customer_id: 42 }); // result.outputArr : [ { listenerName: 'send-invoice', output: { notified: true } }, … ] await g.sys.system.emitEvent('order-placed', order, [ 'send-invoice' ]); // only these listeners ``` - The third argument names the listeners to run ; absent, every listener runs. - The emitter gets the output of each listener back, so an event can also be a way to run several pieces of code and collect their results. ## Emit over HTTP ```text POST /api/system-api/admin/emit-event ``` ```json { "name": "order-placed", "eventData": { "order_no": 1001 }, "executeListeners": [ "send-invoice" ] } ``` - An array of such objects emits several events in one call. See [Emit event](/v1/docs/apis-all/system-apis/system-generated-emit-event-api.html). ## Chains without loops A listener may emit another event, which may emit another. API Maker keeps the list of the events already running for the request : an emit which comes back to one of them is stopped, so a chain never becomes an infinite loop. Each level of depth is allowed ; only the cycle is refused. ## Where it fits | Need | Use | |---|---| | Something must happen after an API, in code | An event with an automatic trigger, or a [post hook](/v1/docs/apis-all/hooks/postHook-api.html) when it must change the answer | | A client must be told live | A [WebSocket event](/v1/docs/pages/web-socket-event-page.html), emitted with `emitEventWS` | | Something must happen on a schedule | A [scheduler](/v1/docs/apis-all/schedulers/user-created-schedulers-api.html) | ## Good to know - Events and their listeners are files in Git and deploy with a pull. Listeners have versions. - Test a listener from the [API testing page](/v1/docs/features/developer-tools.html#api-testing-page) : pick the event, the listener, put the event data in the body and send. - On the Events page, **Move up** and **Move down** order the listeners, **Grid View** lists every listener of every event, **Clone** copies an event with its listeners and **Copy full event name** gives the name to emit. - On the native process, `console.log` is not captured : use `g.logger`. - A [log profile](/v1/docs/logs/log-profile.html) records each listener run. - Groups grant events : an API user needs a group with the event to emit it through the system API. --- # WebSocket Events > Push live updates to web and mobile apps with API Maker - connect a WebSocket with the tokens, subscribe to the answers of tables, custom, system and third party APIs or to your own events with a condition, and emit from your code with emitEventWS. Source: https://docs.apimaker.dev/v1/docs/pages/web-socket-event-page.html API Maker runs a WebSocket server (port `38245`, `wss://` behind Caddy). A client connects with its tokens, subscribes to what it wants to hear about, and receives a message every time a matching API succeeds on any server of the cluster, or when your code emits a custom event. No polling, no message broker of your own. > Diagram : WebSockets : connect with the tokens, register with a condition, get notified from any server ## 1. Connect The tokens go in the query string of the WebSocket URL, with the same names as the [request headers](/v1/docs/apis-all/header/requestHeader.html). ```javascript linenums="1" const ws = new WebSocket( 'wss://ws.example.com/' // the WebSocket URL of your API Maker + '?x-am-authorization=' + encodeURIComponent(apiUserToken) + '&x-am-user-authorization=' + encodeURIComponent(personToken) // when your events need the person ); ``` - `x-am-authorization` : the API user, always. The person token of the [auth provider](/v1/docs/authorization/AMDB.html) of the events you subscribe to : `x-am-user-authorization`, `x-google-authorization`, `x-azure-authorization`, `x-aws-authorization` or `x-custom-authorization`. API Maker finds out which provider a token belongs to ; `&authProviders=name1,name2` narrows it when you want. - The first message from the server is `CONNECTED` : ```json { "type": "CONNECTED", "response": { "connected": true } } ``` - A bad token closes the connection with a `TOKEN_VALIDATION` message which says which header is wrong. ## 2. Subscribe Send a `REGISTER` message with the events you want, then read the answer : the ones accepted, with their `eventId`, and the ones refused with the reason. ```javascript linenums="1" ws.onmessage = (e) => { const msg = JSON.parse(e.data); if (msg.type === 'CONNECTED') { ws.send(JSON.stringify({ objType: 'REGISTER', onEvents: [ { // every successful get all on this table eventType: 'INSTANCES', apiName: 'SCHEMA_GET_ALL', instance: 'mongodb', database: 'shop', collection: 'orders', getEventData: true, select: { order_no: 1, status: 1 }, }, { // a custom event, only when the criteria matches the emitted data eventType: 'CUSTOM_WS_EVENTS', apiName: 'default/orders-updated', condition: { conditionType: 'RESPONSE', criteria: { recipientId: myUserId } }, getEventData: true, }, ], })); } else if (msg.type === 'REGISTER') { console.log(msg.response.validOnEvents, msg.response.invalidOnEvents); // eventId per subscription } else if (msg.type === 'NOTIFICATION') { render(msg.response.eventId, msg.response.eventData); } }; ``` | Key of an event | Meaning | |---|---| | `eventType` | `INSTANCES`, `CUSTOM_APIS`, `SYSTEM_APIS`, `THIRD_PARTY_APIS` or `CUSTOM_WS_EVENTS`. | | `apiName` | The [API id](/v1/docs/apis-all/overview.html#api-ids) for a table (`SCHEMA_GET_ALL`, `GEN_POST_BULK_INSERT`…), the name of the custom, system or third party API, or the name of the custom WebSocket event. | | `instance`, `database`, `collection` or `table` | The table, for `INSTANCES`. | | `apiBundleName`, `apiVersion` | The bundle and version, for `THIRD_PARTY_APIS`. | | `condition` | `{ conditionType: 'RESPONSE', criteria: { field: value } }` : only the answers whose data has these values reach this client. For a custom event, the emitted data must match the criteria exactly, which is how one notification reaches one person. | | `select` | The fields of the data to send, `{ field: 1 }`. | | `getEventData` | `true` sends the data ; `false` sends only the fact that it happened. | - Subscriptions are checked against the groups of the API user and the auth provider of the event : `invalidOnEvents` says why one was refused. ## 3. Receive ```json { "type": "NOTIFICATION", "response": { "eventId": "…", "eventType": "INSTANCES", "eventData": [ { "order_no": 1001, "status": "PAID" } ] } } ``` - Notifications come from **any server** of the cluster : Redis passes them between the servers. - Unsubscribe with the `eventId` of the register answer : ```javascript ws.send(JSON.stringify({ objType: 'UNREGISTER', onEvents: [ eventId ] })); ``` ## When a notification is sent - After every **successful** call of a subscribed API : the answer of the call is the data, filtered by `condition` and `select`. The call can come over HTTP or from your code (custom APIs, hooks, events…). - Not when an error happens before the answer exists : a pre hook which throws sends nothing. A post hook which throws does not stop the notification. - For a custom WebSocket event, when your code emits it : ```typescript linenums="1" await g.sys.system.emitEventWS('default/orders-updated', { recipientId: '42', type: 'order-updated', order_no: 1001 }); ``` ```text POST /api/system-api/admin/emit-event-ws { "name": "default/orders-updated", "eventData": { "recipientId": "42", "order_no": 1001 } } ``` - To notify N people, emit once per person with their id in the data ; each client subscribes with its id in `criteria`. ## Custom WebSocket events `API Info → WebSocket Events` lists the event names of the account. A custom event needs to exist there before it can be subscribed to or emitted. | Field | Meaning | |---|---| | Name | The `apiName` of the subscription and the name given to `emitEventWS`. | | Auth providers | The provider whose token a client must have validated to subscribe. Empty : the first auth provider of the account. | | Can user connect code | A function which decides whether a client may subscribe : return `{ canConnect: true }` or `{ canConnect: false, errorText: '…' }`. It sees the tokens in `g.req.auth`. | | Trigger on API | Optional : an API whose success also emits this event. | ## From your code - `g.sys.system.emitEventWS(name, data)` from any custom API, hook, event, scheduler or migration. - The sample custom API `/default/ws-notify` of a new account has the complete client snippet in its comments. - The [system API](/v1/docs/apis-all/system-apis/system-generated-emit-event-ws-api.html) does the same over HTTP. ## Good to know - The admin panel and the local client use the same server with their own tokens. - Groups grant WebSocket events : an API user subscribes to a custom event only when a group allows it. - Behind Caddy, the install script gives the WebSocket its own host or port : the install prints the WebSocket URL (`BE_WS_HOST_PORT`). --- # Schedulers > Run TypeScript on a schedule in API Maker - intervals from every second to every year or a cron expression, a time zone per interval, one run per cluster with failover, a timeout, versions, and start, stop or run now from the admin panel. Source: https://docs.apimaker.dev/v1/docs/apis-all/schedulers/user-created-schedulers-api.html A scheduler is a TypeScript function which runs on a schedule : every few seconds, every night at two, on the first of the month, or on any cron expression, in the time zone you pick. On a cluster of servers each run happens once, on one server, and moves to another one when a server goes down. > Diagram : Schedulers on a cluster : every interval runs once, on one server, spread over the servers, with failover ## Create a scheduler `API Info → Schedulers`, then **+**. | Field | Meaning | |---|---| | Name | Unique in the account, can not change later (it names the folder in Git). Label can. | | Timeout in minutes | The run is stopped after this long. | | Code | The function, with the global object `g`. | | Intervals | One or more, each with a time interval, a time zone and an active switch. | | Active | The master switch of the scheduler. | | Run on native process | Run in API Maker itself instead of the sandbox. | | Save response in log | Keep what the run returned in the log table. | ## The code ```typescript linenums="1" import * as T from 'types'; import * as db from 'db-interfaces'; async function main(g: T.IAMGlobal) { const stale = await g.sys.db.query({ instance: 'mongodb', database: 'shop', collection: 'carts', find: { updated_at: { $lt: new Date(Date.now() - 7 * 86400000).toISOString() } }, select: '_id', }); const removed = await g.sys.db.removeByQuery({ instance: 'mongodb', database: 'shop', collection: 'carts', find: { _id: { $in: stale.map(c => c._id) } }, }); g.logger.log(`${removed.deletedRowsCount} carts removed`); return { removed: removed.deletedRowsCount }; } module.exports = main; ``` - The same `g` as a custom API, without a request : `g.req.body` and `g.req.query` are empty. Use `g.shared` and the [cache](/v1/examples/sys/cache/setKey.html) to keep state between runs. ## Intervals | Group | Choices | |---|---| | Seconds | every second, 5, 10, 15, 30 seconds | | Minutes | every minute, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30 minutes | | Hours | every hour, 2, 3, 4, 6, 8, 12 hours | | Days | every day, 2, 3, 5, 10, 15 days | | Working days | Monday to Friday at 11:30 | | Months | every month, 2, 3, 4, 6 months | | Year | every year | | Custom | a cron expression | - Each interval has its **time zone** : `Asia/Kolkata`, `Europe/Paris`, `UTC`… A scheduler with two intervals runs both. - Each interval has its own active switch, so one can be paused without touching the other. ### Custom cron expressions Six fields, seconds first : `second minute hour day-of-month month day-of-week`. Five fields work too (the seconds default to `0`). | Expression | Runs | |---|---| | `0 0 22 * * 1-5` | at 22:00, Monday to Friday | | `0 15 14 1 * *` | at 14:15 on the first of every month | | `*/30 * * * * *` | every 30 seconds | | `0 0 */6 * * *` | every 6 hours | ## On a cluster - **Once per tick.** Whatever the number of API Maker servers, an interval runs once : the servers agree through Redis which one runs each scheduler, and a scheduler already running is not started again. - **Spread out.** The schedulers of an account are spread over the servers instead of all running on one. - **Failover.** When a server stops, the schedulers it ran are picked up by the others. - **Safe deployments.** A Git pull or a deployment does not interrupt a run in progress ; the next run uses the new code. ## Start, stop, run now - The **Active** switch starts and stops a scheduler right away, on every server : stop a misbehaving job in production in one click. - **Run now** from the [API testing page](/v1/docs/features/developer-tools.html#api-testing-page) (pick the scheduler, send) runs it once, outside its schedule, and shows the result and the logs. - The Schedulers page marks a scheduler running now on a node, and one whose intervals are all switched off : it never runs. **Clone** copies a scheduler. - Versions : keep several versions of the code, one active. ## Good to know - A [log profile](/v1/docs/logs/log-profile.html) records every run with its duration and its output. - Schedulers are files in Git (`src/Schedulers//`) and deploy with a pull. Their active state and intervals travel with them. - A scheduler which needs more than its timeout should split the work : page through the rows and keep a cursor in the cache. - Groups grant schedulers to API users for the testing page and the run API. --- # Utility Classes > Share TypeScript between all your code in API Maker with utility classes - a class exported as an instance, imported with utils/Name in custom APIs, hooks, events, schedulers, migrations and test cases, with interfaces, versions and Git. Source: https://docs.apimaker.dev/v1/docs/utility-class/utility-class.html A utility class is TypeScript shared by all your code : helpers, validations, pricing rules, clients of external services, interfaces. Write it once on the **Utility Classes** page (`Utility → Utility Classes`) and import it anywhere with `utils/`. ## Create one Click **+**, give a path (a folder to group them) and a name. The name is unique, starts with a capital letter, and is what you import. ```typescript title="utils/Calculator" linenums="1" class Calculator { add(x: number, y: number) { return x + y; } multi(x: number, y: number) { return x * y; } } let temp = new Calculator(); export = temp; ``` - The file exports an **instance** (`export = temp`), so the importer calls the methods directly. - The class can hold state : a connection opened in a method stays for the life of the sandbox. ## Use one ```typescript linenums="1" import * as T from 'types'; import * as calc from 'utils/Calculator'; async function main(g: T.IAMGlobal) { return { sum: calc.add(3, 6), product: calc.multi(3, 6) }; } module.exports = main; ``` - Works in custom APIs, pre and post hooks, event listeners, schedulers, migration scripts, process initializers, WebSocket connect code and test cases. - Press ++ctrl+space++ after `utils/` in the editor to see every class. - A method which needs API Maker takes `g` as a parameter : `Pricing.total(g, order)`. ## Interfaces A utility class can export interfaces and types, for every piece of code to share : ```typescript title="utils/Inf" linenums="1" class Inf { } let temp = new Inf(); export = temp; export interface IUserP { firstName: string; lastName: string; phone: number; } ``` ```typescript linenums="1" import * as T from 'types'; import * as inf from 'utils/Inf'; async function main(g: T.IAMGlobal) { const data: inf.IUserP = { firstName: 'John', lastName: 'Doe', phone: 123456789 }; return data; } module.exports = main; ``` ## Versions A utility class has versions and one active version. The code which imports it always gets the active one, at once, without a restart. Keep the old version until the new one has proven itself, and switch back in one click. ## Testing A [test case](/v1/docs/test-cases/test-cases.html#testing-a-utility-class) imports the class the way custom APIs do and calls its methods, with mocks for the `g.sys` calls it makes and coverage of its lines. ## Good to know - Utility classes are files in Git under `src/Utility classes/` and deploy with a pull. - A utility class runs where its importer runs : in the sandbox, or on the native process for a native custom API. - A new account comes with a sample utility class to read. --- # Third Party APIs (deprecated) > Third party APIs are bundles of APIs installed from the API Maker store into an account, called under /api/third-party - deprecated and removed in API Maker v4, kept here for the projects which still use them. Source: https://docs.apimaker.dev/v1/docs/apis-all/thirdParty-apis/installed-third-party-api.html !!! deprecated "Deprecated : removed in API Maker v4" Third party APIs and the API Maker store are deprecated. They keep working in v3 for the projects which installed bundles, and the pages of the admin panel are grouped under **Deprecated Features**. New projects should write the same code as [custom APIs](/v1/docs/apis-all/custom-apis/user-created-custom-api.html) and [utility classes](/v1/docs/utility-class/utility-class.html), which the local client and Git share between projects. A third party API is one API of a **bundle** published on the [API Maker store](/v1/docs/apis-all/thirdParty-apis/am-store.html) : a version of the bundle is installed into the account, and its APIs answer under the account like custom APIs. | | | |---|---| | URL | `/api/third-party////` | | Method | the method of the API in the bundle | | Settings | caching, encrypted payloads, access type and auth providers per API : [Third party API settings](/v1/docs/settings/thirdPartyApiSettings.html) | | Hooks | pre and post hooks per API | | Secrets | the keys the bundle needs live in the secret of the account, under the names the bundle documents | ## Install a bundle 1. `Utility → AM Store` (under Deprecated Features on the dashboard) lists the published bundles. 2. Open a bundle, pick a version and click **Install API Version**. The npm dependencies of the version are added to the sandbox. 3. The APIs appear on `API Info → Third Party API`, in a tree per bundle and version, each with its method, name and path. ## Use an API - Test it on the API testing page (the vial icon) : headers, query params, body, response and logs, with the code of the API readable next to it. - Call it over HTTP with the token of an API user whose group grants the bundle. - Validate a body against the schema the bundle declares with [is valid data for third party API](/v1/docs/apis-all/system-apis/system-generated-is-valid-data-for-third-party-api.html). - Reset its cache with [reset third party API cache](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-third-party-api.html). ## Uninstall - The library dialog of the store uninstalls a version. Reverting the YAML file of the bundle in Git brings it back, with its utility classes. ![Packages in the store](../../img/ThirdParty-apis/appStorePackageSelection.png) ![Library details](../../img/ThirdParty-apis/library-details.png) ![Installed packages](../../img/ThirdParty-apis/listOfInstalledPackage.png) --- # API Maker Store (deprecated) > The API Maker store where bundles of APIs were published and installed into accounts - bundles, versions, APIs, dependencies, docs and testing - deprecated and removed in API Maker v4. Source: https://docs.apimaker.dev/v1/docs/apis-all/thirdParty-apis/am-store.html !!! deprecated "Deprecated : removed in API Maker v4" The store and the third party APIs are deprecated. This page stays for the teams which still publish or install bundles. Share code between projects with [custom APIs](/v1/docs/apis-all/custom-apis/user-created-custom-api.html), [utility classes](/v1/docs/utility-class/utility-class.html) and Git instead. The store is a separate application where a publisher writes **bundles** of APIs, and where an account of API Maker [installs](/v1/docs/apis-all/thirdParty-apis/installed-third-party-api.html) a version of a bundle. ## Bundles - A bundle is a container of API versions. It has a label, a name and a description ; label and name can not change once saved. - A new version starts as `DRAFT`. Publishing it sets `PENDING_APPROVAL` ; once API Maker approves it, it is published and installable. ![Add a bundle](../../img/ThirdParty-apis/add-bundle.png) ## Versions - Major, minor and patch numbers, with the dependencies and the names of the secret keys the APIs need. - The **Docs** tab explains the version to the people who install it. - A version can carry its own utility classes. - ✓ published (read only), ⚠ pending approval (read only), 🗎 draft (editable). ![Add a version](../../img/ThirdParty-apis/api-version.png) ![Published version](../../img/ThirdParty-apis/api-version-publish.png) ## APIs of a version ![APIs](../../img/ThirdParty-apis/apis.png) | Tab | Content | |---|---| | API Details | Name, path, method, access type, the schema of the body and of the query params. | | API Code | The TypeScript the installer runs, with the same `g` as a custom API. | | Dependencies | Sandbox npm packages, secret keys, Docker file additions. | | API Testing | Headers, query params and body to run the code, with the response and the logs. | | API Docs | Notes for the people who use the API. | ![Edit an API](../../img/ThirdParty-apis/edit-api.png) ![API details](../../img/ThirdParty-apis/api-details.png) ![API code](../../img/ThirdParty-apis/api-code.png) ![Dependencies](../../img/ThirdParty-apis/dependencies.png) ![API testing](../../img/ThirdParty-apis/api-testing.png) ![API docs](../../img/ThirdParty-apis/api-docs.png) ![API response](../../img/ThirdParty-apis/api-response.png) --- # Guides > The concepts of API Maker explained one by one - schema and data, your code and its tools, security and access, settings, performance and operations with Index Maker, the connection strings of every database and the deprecated UI Maker. Source: https://docs.apimaker.dev/v1/docs/guides/index.html The [APIs](/v1/docs/apis-all/overview.html) tab tells what each API does. These guides tell how the pieces around them work, in the order you meet them. { }Schema and dataThe schema of a table, auto increments, optimistic concurrency control, multi-tenant databases and migration scripts. g.Your codeThe global object g, process initializers, test cases, developer tools, Git, developer accounts, code search and browsers with Playwright or Puppeteer. ⛨Security and accessSecrets, groups, API users, role based permissions, the security report, single sign-on and every auth provider. ⚙SettingsCaching, access type and auth providers per database, table and API ; sandbox settings ; the configuration of API Maker. ↗Performance and operationsCaching, Index Maker and automatic indexes, logs, dashboards, internationalization, deploying a new version of API Maker. ⛁Connection stringsThe connection string of MongoDB, MySQL, MariaDB, PostgreSQL, Oracle and SQL Server, with SSL and cloud examples. ▦UI MakerGrids and forms generated from your tables, configured in JSON. Kept for the projects which use it.deprecated ## Read in this order 1. [Table schema](/v1/docs/schema/schema.html) : how a table is described and what the schema APIs do with it. 2. [Global object g](/v1/docs/pre-defined-terms/global-object-g.html) : the one object your code gets. 3. [Secrets](/v1/docs/secrets/secrets.html), then [API group permission](/v1/docs/apis-security/api-group-permission.html) and [Handle role based permissions](/v1/docs/authorization/handle-role-based-permissions.html) : who may do what. 4. [Settings](/v1/docs/settings/databaseSettings.html) : caching, access type and auth providers at every level. 5. [Automatic caching](/v1/docs/features/automatic-caching.html), [Index Maker](/v1/extensions/index_maker/introduction.html), [Log profile](/v1/docs/logs/log-profile.html) and the dashboards : running it. 6. [Git integration](/v1/docs/Git/git.html) and [Deploy API Maker](/v1/docs/features/deploy-api-maker.html) : shipping it. --- # Table Schema > The schema of a table in API Maker - types, conversions, validations, defaults, keys, relations, virtual fields, auto increment, encryption and hashing - written in TypeScript, checked against the code, with the errors each rule gives and a field to copy for each rule. Source: https://docs.apimaker.dev/v1/docs/schema/schema.html A schema describes one table or collection : the type of every field, how a value is cleaned before it is saved, what makes it invalid, which fields point to other tables. It is a TypeScript file kept with the table in Git, and it is what the [schema APIs](/v1/docs/apis-all/overview.html) enforce. The [generated APIs](/v1/docs/apis-all/generated-apis/auto-generated-get-all-api.html) never read it. > Diagram : The stages of a write through a schema API, in order, from the request to 201 or 400 | | | |---|---| | Where | `API Info → DB API` : pick the instance, the database and the table, then the schema editor. **Generate** writes a first schema from the columns or from the documents of the table. | | Shape | `const schema: ISchemaType = { field: EType.string, other: { __type, validations, conversions, … } }` and `module.exports = { schema }`. | | Used by | The `/api/schema/…` APIs : save, master save, update, replace, get with `deep`, the [is valid data for table](/v1/docs/apis-all/system-apis/system-generated-is-valid-data-for-table-api.html) check. | | Gives | Validation, conversions, defaults, relations, the [TypeScript interface](/v1/docs/features/developer-tools.html#typescript-interfaces-from-your-schemas) `db...I
`, the sample payloads of the API testing page and of Swagger. | ## 1. A schema, field by field ```typescript title="orders" linenums="1" import { EType, ISchemaType, ISchemaProperty } from 'types'; const schema: ISchemaType = { _id: { __type: EType.objectId, isPrimaryKey: true, isAutoGenerateByAM: { valueGeneratorType: 'ObjectID' } }, order_no: { __type: EType.number, isAutoIncrementByAM: { start: 1, step: 1 } }, customer_id: { __type: EType.number, validations: { required: true }, database: 'shop', collection: 'customers', column: 'customer_id' }, status: { __type: EType.string, validations: { enum: ['PENDING', 'PAID', 'SHIPPED', 'CANCELLED'] }, conversions: { toUpperCase: true, defaults: { defaultValue: 'PENDING' } } }, total_cents: { __type: EType.number, validations: { min: 0 } }, created_at: { __type: EType.date, conversions: { defaults: { defaultFun: () => new Date() } } }, items: { __type: EType.objectId, isVirtualField: true, database: 'shop', collection: 'orderItems', s_columnVirtualLinker: '_id', t_columnVirtualLinker: 'order_id' }, version: { __type: EType.number, isConcurrencyControlField: true }, }; module.exports = { schema }; ``` - This is the `orders` table of the [sample shop](/v1/docs/getting-started/first-api.html) every new account gets : open it in the admin panel to see the whole thing with the `products`, `customers` and `orderItems` schemas. - A field is a type alone (`name: EType.string`) or an object with `__type` and the rules below. A key the schema does not have is refused on save : the error names it. - The examples of the sections below are single fields : paste them inside `const schema: ISchemaType = { … }`. ## 2. Types | `__type` | Holds | Notes | |---|---|---| | `EType.string` | text | `minLength`, `maxLength`, `email`, `enum`, trims and case conversions | | `EType.number` | numbers | `min`, `max`, auto increment | | `EType.boolean` | `true` / `false` | | | `EType.date` | dates | `min`, `max` ; sent and received as ISO strings | | `EType.objectId` | MongoDB ObjectId | strings in JSON | | `EType.file`, `EType.files` | uploaded files | the request schema of a custom API, not a table | ```typescript title="One field per type" linenums="1" first_name: { __type: EType.string, validations: { required: true } }, employee_no: { __type: EType.number, validations: { required: true } }, is_active: { __type: EType.boolean }, joined_at: { __type: EType.date }, department_id: { __type: EType.objectId, validations: { required: true } }, ``` - MongoDB documents nest : `address: { city: EType.string }` is an embedded object, `tags: [EType.string]` an array of strings, `lines: [{ sku: EType.string, qty: EType.number }]` an array of objects. SQL tables are flat. ```typescript title="Nested documents and arrays (MongoDB)" linenums="1" address: { city: EType.string, zip: EType.string }, tags: [EType.string], lines: [{ sku: EType.string, qty: EType.number }], ``` ## 3. Conversions : clean the value first Conversions run before the validations, on save and on update. | Key | Does | |---|---| | `trim`, `trimStart`, `trimEnd` | Removes the spaces around a string. | | `toLowerCase`, `toUpperCase` | Changes the case. | | `conversionFun: (value, row) => …` | Your function ; its return value is stored. It runs even when the field is absent from the payload, which is how a `updated_at: () => new Date()` field is stamped on every save and update. | | `defaults` | `{ defaultValue }` or `{ defaultFun: () => … }` for a field absent from a save or a replace. `defaultValue` wins over `defaultFun`. `shouldReplaceNullWithDefault` and `shouldReplaceEmptyStringWithDefault` also replace `null` and `""`. | | `encryption: true` | Stored encrypted with `encryptionAlgorithm` and `secret` of the [secret](/v1/docs/secrets/secrets.html), decrypted when read. An object `{ encryptionAlgorithm, secret, nonce }` names other keys of the secret. | | `hashing: true` | Stored as an HMAC SHA-256 hash with the `nonce` (or `secret`) of the secret : passwords, tax ids. Look a hashed value up with the [hash data](/v1/docs/apis-all/system-apis/system-generated-hash-data-api.html) API. | ```typescript title="Trim and case" linenums="1" first_name: { __type: EType.string, conversions: { trim: true } }, code: { __type: EType.string, conversions: { toUpperCase: true } }, email: { __type: EType.string, conversions: { toLowerCase: true, trimStart: true, trimEnd: true } }, ``` ```typescript title="conversionFun : change the value, or stamp it on every save and update" linenums="1" city_name: { __type: EType.string, conversions: { conversionFun: (city_name, fullObj) => city_name ? city_name + '_IND' : city_name }, }, updated_at: { __type: EType.date, conversions: { conversionFun: () => new Date() }, // runs even when the field is absent from the payload }, ``` ```typescript title="defaults" linenums="1" active: { __type: EType.boolean, conversions: { defaults: { defaultValue: true, shouldReplaceEmptyStringWithDefault: true, shouldReplaceNullWithDefault: true } }, }, created_at: { __type: EType.date, conversions: { defaults: { defaultFun: () => new Date() } }, // defaultValue wins over defaultFun when both are given }, ``` ```typescript title="Encrypted and hashed fields" linenums="1" card_number: { __type: EType.string, conversions: { encryption: true } }, // decrypted when read password: { __type: EType.string, conversions: { hashing: true } }, // compare with the hash data API ``` ## 4. Validations : refuse a bad value | Key | Refuses | Error `type` | |---|---|---| | `required: true` | A missing, `null` or empty value on save. On update, only a `null` or empty value sent. | `required` | | `min`, `max` | A number or a date outside the range. | `min`, `max` | | `minLength`, `maxLength` | A string or an array outside the length range. | `minLength`, `maxLength` | | `email: true` | A string which is not an email address. | `emailNotValid` | | `enum: [ … ]` | A value outside the list. An empty list checks nothing. The sample data generator picks from the list. | `enumValidation` | | `validatorFun: (value, row) => …` | Your function, run last : return `true` to accept ; return `false` or a message, or throw, to refuse. | `invalidValue` | | `unique: true` | A value another row already has, or which appears twice in the same batch. Checked with a query before the write, so prefer a unique index for busy tables ([create indexes](/v1/docs/apis-all/system-apis/system-generated-create-indexes-api.html)). | `unique` | ```typescript title="required, min and max, minLength and maxLength" linenums="1" registration_number: { __type: EType.number, validations: { required: true, min: 4, max: 10 } }, address: { __type: EType.string, validations: { required: true, minLength: 20, maxLength: 100 } }, ``` ```typescript title="email and enum" linenums="1" mail_id: { __type: EType.string, validations: { required: true, email: true } }, country: { __type: EType.string, validations: { enum: ['India', 'Africa'] } }, ``` ```typescript title="validatorFun : your own rule, run last" linenums="1" city_name: { __type: EType.string, validations: { required: true, validatorFun: (city_name, fullObj) => { if (city_name && ['AHMEDABAD', 'SURAT'].includes(city_name)) throw new Error(`You can not save city_name as '${city_name}'`); return true; }, }, }, ``` ```typescript title="unique : checked with a query before the write" linenums="1" mobile_no: { __type: EType.number, validations: { unique: true } }, // prefer a unique index for busy tables ``` - The answer of a refused save is `400` with one entry per problem in `errors` : `{ type, field, message, code, dataIndex }`, `dataIndex` being the position of the row in an array body. See [Error codes](/v1/docs/apis-all/error-codes.html). ## 5. Keys and generated values | Key | Meaning | |---|---| | `isPrimaryKey: true` | The primary key of the table : the id of get by id, update by id, remove by id. | | `isAutoIncrementByDB: true` | The database assigns the value (SQL identity columns). API Maker sends nothing for it. | | `isAutoIncrementByAM: true` or `{ start, step }` | API Maker assigns the next number, on every database, MongoDB included : see [Auto increment](/v1/docs/features/auto-increment.html). No effect with `isAutoIncrementByDB`. | | `isAutoGenerateByAM: { valueGeneratorType }` | A random id when the payload has none : `ObjectID`, `GUID_UUID`, `ULID` or `ShortUUID`. Wins over `isAutoIncrementByAM`. | | `isConcurrencyControlField: true` | The version field of [optimistic concurrency control](/v1/docs/features/optimistic-concurrency-control.html). | ```typescript title="Primary keys, generated ids and the version field" linenums="1" customer_id: { __type: EType.number, isPrimaryKey: true, isAutoIncrementByAM: { start: 1000, step: 1 } }, id: { __type: EType.number, isPrimaryKey: true, isAutoIncrementByDB: true }, _id: { __type: EType.objectId, isPrimaryKey: true, isAutoGenerateByAM: { valueGeneratorType: 'ObjectID' } }, public_id: { __type: EType.string, isAutoGenerateByAM: { valueGeneratorType: 'ULID' } }, // GUID_UUID, ULID, ShortUUID, ObjectID version: { __type: EType.number, isConcurrencyControlField: true, conversions: { conversionFun: () => new Date().getTime() } }, ``` ## 6. Relations and virtual fields ```typescript title="A relation, its reverse, and a relation to another instance" linenums="1" // orderItems.order_id points to orders._id order_id: { __type: EType.objectId, validations: { required: true }, database: 'shop', collection: 'orders', column: '_id' }, // orders.items : the line items of an order, read from orderItems through their order_id items: { __type: EType.objectId, isVirtualField: true, database: 'shop', collection: 'orderItems', s_columnVirtualLinker: '_id', t_columnVirtualLinker: 'order_id' }, // a relation to a table of another instance and database owner_id: { __type: EType.number, instance: 'mysql8', database: 'crm', table: 'owners', column: 'id' }, ``` | Key | Meaning | |---|---| | `collection` or `table`, `column` | The table and the column this field points to. `database` and `instance` when they differ from the ones of the schema. | | `isVirtualField: true` | A field which is not in the table : it is filled from the other table, and can be saved through master save. `s_columnVirtualLinker` is the column of this table, `t_columnVirtualLinker` the column of the other table which holds it. | - Relations feed [deep populate](/v1/docs/apis-all/query-params/deep.html) (`deep` on any get), [master save](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-master-save-api.html) (one call saves the order and its items) and the [ER diagram](/v1/docs/dashboard/diagram.html). - A virtual field can not be used in `find` : the error `virtualFieldUsedInFind` says so. ## 7. Other keys - `uim` : settings of the deprecated UI Maker ; they change nothing at runtime. - A schema file can also export `validations`, a list of `SUPER_KEY` rules which refuse two rows with the same combination of fields. It is deprecated : create a unique index on the fields instead, it is faster and it holds for writes made outside API Maker too. ## Related - [Optimistic concurrency control](/v1/docs/features/optimistic-concurrency-control.html) · [Auto increment](/v1/docs/features/auto-increment.html) · [Deep populate](/v1/docs/apis-all/query-params/deep.html) · [Is valid data for table](/v1/docs/apis-all/system-apis/system-generated-is-valid-data-for-table-api.html) - [Generated interfaces](/v1/docs/features/developer-tools.html#typescript-interfaces-from-your-schemas) · [Error codes](/v1/docs/apis-all/error-codes.html) --- # Auto Increment > Sequential numbers on every database, MongoDB included - isAutoIncrementByAM in the schema, start and step, how the next value is chosen, the Auto Increments page, and what travels with Git. Source: https://docs.apimaker.dev/v1/docs/features/auto-increment.html MongoDB has no auto increment column. API Maker gives one to every database : mark a number field with `isAutoIncrementByAM` and every row saved through the schema APIs gets the next number. | | | |---|---| | Schema | `order_no: { __type: EType.number, isAutoIncrementByAM: { start: 1000, step: 5 } }` ; `true` means start `1`, step `1`. | | Counter | One per field, kept in the internal Redis of API Maker, shared by every server. | | Page | `Utility → Auto Increments` : every counter of the account, with its last value, which you can edit. | | Git | The counters of a branch are written to `Auto Increments/auto_increments_.yml` in the repository. | ## How the next value is chosen 1. The counter of the field is increased by `step`. This is atomic : two servers saving at the same time get two different numbers. 2. When the result is not above `start` (a new counter, or a counter reset below `start`), API Maker reads the highest value the table already has : the next value is that maximum plus `step`, and at least `start`. 3. The value is written in the row. A value sent in the payload is replaced. | Start | Step | Counter | Highest in table | Next value | |---|---|---|---|---| | 1000 | 5 | 1020 | | 1025 | | 1000 | 5 | none | 1100 | 1105 | | 1000 | 5 | none | empty table | 1000 | | 1 | 1 | none | empty table | 1 | - Rows inserted outside API Maker are picked up the next time the counter starts from scratch ; edit the counter on the Auto Increments page to realign it right away. - `isAutoGenerateByAM` on the same field wins, and `isAutoIncrementByDB` disables it : let the database count when it can. ## The Auto Increments page - Lists the counters of the environment : instance, database, table, field path and the last generated value. - **Edit** sets the last value ; the next row gets it plus `step`. The dialog shows the values the next number will be the maximum of. - **Delete** removes a counter : the next save reads the highest value of the table again. ## With Git - A pull writes the counters of the branch into Redis when they are missing there ; a counter which exists on the server is never replaced by the one of Git. - A new environment therefore starts from the counters of the branch, and a running one keeps its own. ## Related - [Table schema](/v1/docs/schema/schema.html#5-keys-and-generated-values) · [Git integration](/v1/docs/Git/git.html) --- # Optimistic Concurrency Control > Stop lost updates with one schema flag - isConcurrencyControlField makes update by id and replace by id compare the version sent with the version stored, and refuse a stale write with a 400. Source: https://docs.apimaker.dev/v1/docs/features/optimistic-concurrency-control.html Two people open the same row. Both save. Without a check the second save silently erases the first one. With a version field the second save is refused, and the app can reload and ask. | | | |---|---| | Schema | One field with `isConcurrencyControlField: true`, usually a number stamped by a `conversionFun`. | | Checked by | [Update by id](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-update-by-id-api.html) and [replace by id](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-replace-by-id-api.html) of the schema APIs. Not update many. | | Refused with | `400` : `Concurrency version mismatch in 'version'. This row/document is already updated.` | | From code | `g.sys.db.updateById(…)` skips the check unless you pass `skipConcurrencyControl: false`. | ## 1. Add the version field ```typescript linenums="1" import { EType, ISchemaType, ISchemaProperty } from 'types'; const schema: ISchemaType = { _id: EType.objectId, name: EType.string, version: { __type: EType.number, conversions: { conversionFun: () => new Date().getTime() }, // a new version on every save and update isConcurrencyControlField: true, }, }; module.exports = { schema }; ``` ## 2. Send it back with every update ```text title="Read, then update with the version you read" GET /api/schema/admin/mongodb/shop/products/get-by-id/68d...a1 → { "name": "Mug", "price": 20, "version": 1727683200850 } PUT /api/schema/admin/mongodb/shop/products/update-by-id/68d...a1 { "price": 22, "version": 1727683200850 } → 200, the row now carries a new version ``` - The value sent must equal the value stored. Another write in between changed it : the update answers `400` with the message above, and nothing is written. Reload the row and try again with its new version. - The field is mandatory in the payload once the row has a version : an update without it is refused the same way. ## From your code - Calls made with `g.sys.db` skip the check by default, so a scheduler or an event can update rows without carrying versions. Pass `skipConcurrencyControl: false` in the parameters to enforce it there too. - The [website](https://apimaker.dev/schema-validation) animates the whole exchange. ## Related - [Table schema](/v1/docs/schema/schema.html) · [Update by id](/v1/docs/apis-all/schema-apis/auto-generated-schema-based-update-by-id-api.html) · [Error codes](/v1/docs/apis-all/error-codes.html) --- # 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/v1/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. ## 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 linenums="1" [ { "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 linenums="1" import * as T from 'types'; let Secret: T.ISecretType | any = { // ... common and your other keys multiTenant: { crm: { 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 linenums="1" 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](/v1/docs/apis-all/header/requestHeader.html#x-am-tenant-username) header. ```text linenums="1" 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 linenums="1" GET /api/custom-api/admin/customers-report x-am-tenant-username: acme ``` ```typescript title="The tenant of the request, and its data" linenums="1" 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; ``` - Another tenant, or the structure database, on purpose: ```typescript title="Name the tenant of one call" linenums="1" // 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': '' }, }); ``` - Queries and table meta run in the database of the tenant too: ```typescript title="SQL of the tenant of the request" linenums="1" 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' }); ``` - A custom API with `enableCaching` keeps its answers per tenant. Reset them for one tenant: ```typescript title="Reset the cache of a custom API for one tenant" linenums="1" await g.sys.cache.resetCacheCustomApis({ name: 'customers-report', tenantUsername: '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](#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 title="Post hook : an audit row in the database of the tenant" linenums="1" 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; ``` ## 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 title="Reset the cache of a table" linenums="1" 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 ``` ## 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](/v1/docs/apis-all/system-apis/system-generated-token-api.html) and the tenant header: ```text linenums="1" 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 linenums="1" await g.sys.system.multiTenantInstanceUpdated({ instanceName: 'crm', username: '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 linenums="1" 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. --- # Database Migration Scripts > Migration scripts in API Maker - TypeScript with the same g as a custom API, run once per environment in order after a Git pull or a deployment hook, or by hand from the Database Migration page, with the Index Maker script among them. Source: https://docs.apimaker.dev/v1/docs/features/database-migration.html A migration script changes the structure or the data of your databases : add a column, backfill a value, create an index. It is code of the account, kept in Git, and API Maker runs each script **once per environment**, in order. | | | |---|---| | Page | `Utility → Database Migration` : name, label, order, timeout in minutes, active, and the code. | | Runs | After a Git pull and after a [deployment hook](/v1/docs/Git/git.html#deployment-hooks) : the active scripts not yet executed on this environment, by `order`. Or by hand : **Execute script**, **Execute Selected Migration**. | | Remembers | The names of the scripts executed, per environment. **Mark as executed** skips one without running it ; a script already run shows **Script executed**. | | Code | `async function main(g: T.IAMGlobal)` with the same `g` as a custom API. Test it on the [API testing page](/v1/docs/features/developer-tools.html#api-testing-page). | ## A script ```typescript title="Add a column once" linenums="1" import * as T from 'types'; import * as _ from 'lodash'; async function main(g: T.IAMGlobal) { const meta = await g.sys.system.getTableMeta({ instance: 'mysql8', database: 'inventory', table: 'order_transactions' }); const hasDescription = !!_.find(meta, { name: 'description' }); if (!hasDescription) { await g.sys.system.executeQuery({ instance: 'mysql8', query: 'ALTER TABLE `inventory`.`order_transactions` ADD COLUMN `description` varchar(255) NULL AFTER `qty`', }); } return { hasDescription }; } module.exports = main; ``` - [Get table meta](/v1/docs/apis-all/system-apis/system-generated-get-table-meta-api.html) tells what is there, [execute plain query](/v1/docs/apis-all/system-apis/system-generated-execute-plain-query-api.html) runs any SQL or MongoDB command, [create indexes](/v1/docs/apis-all/system-apis/system-generated-create-indexes-api.html) adds indexes the same way on every database. Data moves with `g.sys.db`. - Write scripts which can run twice without harm, like the one above : a script which fails half way is run again after the fix. ## Order and timeout - `order` sorts the scripts ; the pull runs them from the lowest. Use it for a script which needs the column another one adds. - `Timeout in minutes` stops a script which runs too long (`10` when empty). - A script which throws stops with its error, shown by **View error** on its row ; the next scripts still run. ## The Index Maker script - `am_index_maker` is managed by [Index Maker](/v1/extensions/index_maker/introduction.html) : it holds the indexes decided by the automatic mode, the suggestions and the index form as a JSON list, runs on every deploy, and can not be deleted while Index Maker uses it. Edit the code outside the marked region as you like. ## Related - [Git integration](/v1/docs/Git/git.html) · [Execute plain query](/v1/docs/apis-all/system-apis/system-generated-execute-plain-query-api.html) · [Create indexes](/v1/docs/apis-all/system-apis/system-generated-create-indexes-api.html) --- # The Global Object g > Everything your code gets in API Maker through g - the request (headers, params, query, body, auth, event data), the response (status, content type, output, headers, errors), g.sys for the database, system and cache APIs, the logger and the shared space. Source: https://docs.apimaker.dev/v1/docs/pre-defined-terms/global-object-g.html Every piece of code you write in API Maker, a custom API, a hook, an event listener, a scheduler, a migration, a process initializer, a test case, is a function of one argument : ```typescript linenums="1" import * as T from 'types'; import * as db from 'db-interfaces'; async function main(g: T.IAMGlobal) { const rows = await g.sys.db.getAll({ instance: 'mongodb', database: 'shop', collection: 'products' }); g.logger.log(rows.length, 'products'); return rows; } module.exports = main; ``` | | | |---|---| | `g.req` | What came in : headers, params, query, body, event data, the opened tokens. | | `g.res` | What goes out : status code, content type, output, headers, errors, warnings. | | `g.sys` | The APIs of API Maker from code : `db`, `db.gen`, `system`, `cache`, and `test` inside a test case. | | `g.logger` | `debug`, `log`, `info`, `warn`, `error` : shown with the response on the testing pages, kept by the [log profiles](/v1/docs/logs/log-profile.html). | | `g.shared` | A place for values which pre hook, API and post hook of one request share. | ## g.req | Key | Holds | |---|---| | `headers` | The request headers, editable : a pre hook can set `x-am-content-type-response` for the whole call. | | `params` | The path parameters : `instanceName`, `database`, `collection`, `id`, `primaryKey`, `field` and `order` of distinct, `tenantUsername` for a [multi-tenant](/v1/docs/features/multi-tenant.html) request… | | `query` | The query parameters as an object : `find`, `select`, `limit`… editable in a pre hook, which is how [row level security](/v1/docs/authorization/handle-role-based-permissions.html) is done. | | `body` | The body, editable. | | `eventData` | The data of the event, in an event listener. | | `auth` | The opened tokens : `authAMUser` (the API user), `authAMDB` (the person, from a DB auth provider), `authCustom`, `authGoogle`, `authAWS`, `authAzure`. | | `reqInfo` | `url`, `apiCategory`, `reqMethod` and `apiInfo` (`id`, `name`, `schemaType`) : which API is running. | | `isApiRequestFromUser` | `true` for an HTTP request, `false` for a call made from code. | ```typescript title="Who is calling" const person = g.req.auth.authAMDB; // the row of your users table, minus its password const apiUser = g.req.auth.authAMUser; // name and groups of the API user ``` ## g.res | Key | Holds | |---|---| | `statusCode` | `T.EStatusCode.OK` (200), `CREATED`, `NO_CONTENT`, `BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `RESOURCE_NOT_FOUND`, `INTERNAL_SERVER_ERROR`… Set by your code, it wins over the one of the API. | | `contentType` | `T.EContentType.JSON` (default), `XML`, `YAML`, `TEXT`, `HTML`, `OCTET_STREAM`. | | `output` | The answer. In a post hook, the answer of the API, editable. A custom API returns its output instead. | | `headers` | Headers to add to the reply. | | `errors`, `warnings` | The lists of the reply. | | `shared` | Same as `g.shared`. | ```typescript title="An HTML page from a custom API" g.res.contentType = T.EContentType.HTML; return '

Hello

'; ``` - To send a file, return an object with `__am__downloadFilePath` or `__am__downloadFileOrFolderPaths` : see [files](/v1/docs/apis-all/custom-apis/user-created-custom-api.html#files). ## g.sys | Part | Holds | |---|---| | `g.sys.db` | The [schema APIs](/v1/docs/apis-all/overview.html) of any table : `getAll`, `getById`, `query`, `saveSingleOrMultiple`, `masterSave`, `updateById`, `updateMany`, `replaceById`, `removeById`, `removeByQuery`, `aggregate`, `count`, `distinct`, `distinctQuery`, `arrayOperations`, `getAllByStream`, `queryByStream`. [Examples](/v1/examples/sys/db/getAll.html). | | `g.sys.db.gen` | The same calls on the generated APIs, without schema : `getAllGen`, `queryGen`… [Examples](/v1/examples/sys/db/gen/getAllGen.html). | | `g.sys.system` | The [system APIs](/v1/examples/sys/system/system.html) : `encrypt`, `decrypt`, `hash`, `getToken`, `getSecret`, `callExternalApi`, `executeQuery`, `getTableMeta`, `createIndexes`, `dropIndexes`, `getIndexes`, `emitEvent`, `emitEventWS`, `isValidDataForTable`, `isValidDataForCustomAPI`, `isValidDataForThirdPartyAPI`, `multiTenantInstanceUpdated`, `isValidConnectionString`. | | `g.sys.cache` | Your own keys in Redis and the cache resets : `getKey`, `setKey`, `removeKey`, `resetCacheDB`, `resetCacheCustomApis`, `resetCacheSystemApis`, `resetCacheThirdPartyApis`. [Examples](/v1/examples/sys/cache/setKey.html). | | `g.sys.test` | In a [test case](/v1/docs/test-cases/test-cases.html) only : `runCustomApi`, `mock`, `clearMocks`, `calls`. | - Every `g.sys.db` call takes the same `headers` and `queryParams` an HTTP call takes, and runs with the groups of the API user of the request. Hooks are skipped unless you pass `skipHookRunning: false`. - Pass `true` as the last argument (`getFullResponse`) to get the whole `{ success, data, errors, … }` envelope instead of `data`. ## g.logger and g.shared ```typescript g.logger.log('order ', order._id); // with the response on the testing pages, in the logs of a log profile g.logger.error('payment failed : ', e.message); g.shared.count = 234; // set in a pre hook… const count = g.shared.count; // …read in the post hook of the same request ``` - `debug`, `log`, `info`, `warn` and `error` write the same line : the level is not kept. The arguments are joined without a separator, objects are printed as indented JSON. The calls are synchronous, no `await`. - For an error, log `e.message` or `e.stack`. - On the native process (`runOnNativeProcess`), `console.log` is not captured : use `g.logger`. ## Related - [Custom APIs](/v1/docs/apis-all/custom-apis/user-created-custom-api.html) · [Pre hooks](/v1/docs/apis-all/hooks/preHook-api.html) · [Code examples](/v1/examples/index.html) · [Cheat sheet](/v1/docs/cheat-sheet.html) --- # Process Initializers > Code which runs when a sandbox process of your account starts, before any API - subscribe to MQTT or Redis, warm a cache, prepare a client - with the same g as a custom API. Source: https://docs.apimaker.dev/v1/docs/features/process-initializers.html A process initializer runs when a sandbox process of your account starts, before it serves any of your code. It is the place for work which must exist in every process : a subscription to a message broker, a client kept for the life of the process, a warm cache. | | | |---|---| | Page | `API Info → Process Initializers` : name, order, timeout in minutes, active, and the code. | | Runs | In every sandbox process, when it starts ; the active initializers in the order given, one after the other. | | Code | `async function main(g: T.IAMGlobal)`, the same `g` as a custom API. | | Test | **Test API** runs it on the API testing page. | ```typescript title="Subscribe to a topic and write the messages" linenums="1" import * as T from 'types'; import * as mqtt from 'mqtt'; // added on the Sandbox Settings page async function main(g: T.IAMGlobal) { const client = mqtt.connect(await g.sys.system.getSecret('mqtt.url')); client.on('message', async (topic, payload) => { await g.sys.db.saveSingleOrMultiple({ instance: 'mongodb', database: 'shop', collection: 'telemetry', saveData: { topic, payload: payload.toString(), at: new Date() } }); }); client.subscribe('shop/#'); g.shared.mqtt = client; // reachable by the code of this process } module.exports = main; ``` - Other code runs on an action : an HTTP call, a schedule, an event. An initializer runs on its own, once per process, so keep it light and idempotent. - The number of sandbox processes is set on the [Sandbox Settings](/v1/docs/settings/sandboxSettings.html) page : each one runs the initializers. ## Related - [Sandbox settings](/v1/docs/settings/sandboxSettings.html) · [The global object g](/v1/docs/pre-defined-terms/global-object-g.html) · [Schedulers](/v1/docs/apis-all/schedulers/user-created-schedulers-api.html) --- # Test Cases Feature > Write, run and debug test cases for custom APIs and utility classes in API Maker, with mocks, a run from the terminal (npm run test) and the code coverage of every run in VS Code and WebStorm. Source: https://docs.apimaker.dev/v1/docs/test-cases/test-cases.html - In API Maker, you can write test cases of your custom APIs and utility classes. - They run on the server, in the sandbox of your account, with the same `g` your custom APIs get. You can run them from the **Test Cases** page (menu `API Info → Test Cases`), or from the folder of the local client with `npm run test`. - Every run reports which lines of your custom APIs, utility classes and test cases ran (code coverage), on the page and in your IDE. ## The Test Cases page The page has three parts : - **Left** : every test case of the account, with the state its last run gave it (passed, failed, not run). Search by name, custom API or utility class, filter by state, use the arrow keys to move and `Enter` to run. The play button next to a test case runs that one. - **Middle** : the code of the selected test case. `Ctrl+S` saves, `Ctrl+Enter` runs it. After a run, the line of every failure is marked, and the gutter shows which lines ran (coverage). The **Mocks** button opens the mocks of the test case (see below). - **Right** : the run going on, or the last one : each file and each test as the server reports them, the failures with what was expected and what came, then the coverage of everything which ran. A run started from a terminal (`npm run test`) or from another tab shows here as well. The switch **Collect the code coverage** at the bottom of the list decides whether the runs of this browser collect the coverage. It slows a run a little. ## Add a new test case ### Basic info - Give the name of the test case. - Select the type of the test case : Custom API or Utility class. - Select the custom API or the utility class it is about ('Test Item'). ### Mocks - The **Mocks** tab holds the mocks of the test case : calls of `g.sys` (`g.sys.db.getAll`, `g.sys.system.hash`, `g.sys.cache.getKey` ...) which answer with your data instead of reaching the database, redis or the web. When the tab is empty, it offers mocks to start from : rows of a table, one row by its id, a count, an external API, a secret, a cached value. - Each mock reads as a sentence : **when the test calls** a method, **with the arguments** (optional, one condition per argument, in order), **answer with** a value. - Write values the way a test writes them : JSON (`{ "collection": "orders" }`, `[1, 2]`, `"text"`, `42`, `true`, `null`), objects written the JavaScript way (`{ collection: 'orders' }`) or plain text. The type comes from what you write. - An object matches an argument which has the same keys with the same values : its other keys do not matter, and only the first level is compared. A text, a number or a boolean must be equal. Without a condition, the mock answers every call of the method. An empty answer answers `null`. - The mocks are tried from the top : the first one whose method and arguments match answers. The tab warns about a mock which an earlier one hides, and about a condition on an argument the call never receives. - **In code** shows the same mock written for the code of a test, ready to copy. - Mocks can also be written in the code, next to the test which needs them (see below). They come first, so they win over the mocks of the tab. ### Description - Here you can add more readable text to understand the test case. Once you save the new test case you will get the default code of the test case like below. ```ts linenums="1" import * as T from 'types'; import * as db from 'db-interfaces'; import * as assert from 'node:assert'; module.exports = [{ name: 'Test case name 1', code: async (g: T.IAMGlobal) => { assert.strictEqual(10, 10); } }]; ``` - As you can see in the above code, we have an array of test objects : one object per test, with its name and its code. - Every test gets the same `g` as a custom API : `g.sys` for the APIs of API Maker, `g.logger` for logs which show with the result, `g.req` and `g.res`. ## Mocks in the code Next to a test, `mocks` holds the mocks of that test alone : ```ts linenums="1" import * as T from 'types'; import * as assert from 'node:assert'; module.exports = [{ name: 'counts the orders of a customer', mocks: [ // the call, when it applies (one entry per argument, optional), and what it answers { method: 'db.getAll', when: [{ collection: 'orders' }], return: [{ id: 1 }, { id: 2 }] }, // the answer can be a function of the arguments, async if needed { method: 'g.sys.system.hash', return: (data: string) => 'hashed:' + data }, // a condition can be a function of the argument { method: 'db.count', when: [(query: any) => query.collection === 'orders'], return: 2 }, ], code: async (g: T.IAMGlobal) => { const rows = await g.sys.db.getAll({ instance: 'mongodb', database: 'shop', collection: 'orders' }); assert.strictEqual(rows.length, 2); assert.strictEqual(g.sys.test.calls('db.getAll').length, 1); // the calls the mocks answered }, }]; ``` - `method` is the id of the API (`SCHEMA_GET_ALL`) or its name in code (`g.sys.db.getAll`, `db.getAll`). The methods of the Mocks tab are the ones which can be mocked. - `g.sys.test.mock(...)` adds a mock while the test runs, `g.sys.test.clearMocks(method?)` removes mocks, `g.sys.test.calls(method?)` gives the calls the mocks answered so far in this test. - A call which no mock answers reaches the real API, with the full permissions of the account. ## Testing a custom API `g.sys.test.runCustomApi` runs the code of a custom API of the account inside the test, with the request you describe. The `g.sys` calls the API makes are answered by the mocks of the test, and the coverage of the run sees the API. The hooks of the API are not run : it is the code of the API alone. ```ts linenums="1" import * as T from 'types'; import * as assert from 'node:assert'; module.exports = [{ name: 'orderCount answers the number of orders', mocks: [{ method: 'db.getAll', when: [{ collection: 'orders' }], return: [{ id: 1 }, { id: 2 }, { id: 3 }] }], code: async (g: T.IAMGlobal) => { const out = await g.sys.test.runCustomApi({ name: 'orderCount', query: { customer: 7 }, headers: { 'x-role': 'admin' } }); assert.deepStrictEqual(out, { count: 3 }); // the whole answer : data, logs, headers, warnings const full = await g.sys.test.runCustomApi({ name: 'orderCount', query: { customer: 7 } }, true); assert.deepStrictEqual(full.logs, ['3 orders']); // what the API throws is thrown here await assert.rejects(g.sys.test.runCustomApi({ name: 'orderCount', body: { fail: true } }), /failed on purpose/); }, }]; ``` - `name` is the name of the custom API, `version` (its version or the name of the version) runs another version than the one the API serves. - `body`, `query`, `params` and `headers` are the request the API sees. ## Testing a utility class A utility class is imported the way custom APIs import it, and its functions are called directly : ```ts linenums="1" import * as T from 'types'; import * as assert from 'node:assert'; import * as GetClientSoftwareVersions from 'cloud/getVersion'; import * as ClientSoftware from 'cloud/clientSoftware'; import * as CloudTypes from 'cloud/CloudTypes'; module.exports = [ { name: 'Get client software versions', code: async (g: T.IAMGlobal) => { const versions = await GetClientSoftwareVersions.getSoftwareVersions(g); assert.strictEqual(versions.length > 0, true); } }, { name: 'Get specific software version', code: async (g: T.IAMGlobal) => { const version: CloudTypes.IVersion = await ClientSoftware.getSoftwareVersion(g, 'API Maker', 'v1.0.0'); assert.strictEqual(version.version === 'v1.0.0', true); } } ]; ``` - 'GetClientSoftwareVersions', 'ClientSoftware' and 'CloudTypes' are imported from the utility classes. The `g.sys` calls they make are answered by the mocks of the test too. ## Execute / Run - On the page, the play button next to a test case runs that one, **Run** in the editor runs the selected one, **Run all** runs every test case of the account. - The results show test by test : a failed test shows its message, and a click on it shows what was expected and what came, side by side, with the line of the test in the editor. - A run can be cancelled : it stops after the file which is running. - The last run is kept 30 days, so the page shows it again after a reload. **Clear results** above the results of the run panel clears it : its results and its coverage. **Clear coverage** above the coverage clears only the coverage, the results stay. Neither is possible while a run is going on. - A failing test does not stop the file : every test of the file runs and reports. ## Run from the terminal : `npm run test` The git repository of the account holds the local client (`localClient.js`), and its `package.json` has two scripts : `npm run start` keeps your files in sync with the server, `npm run test` runs the test cases. ``` npm run test # every test case of the account npm run test -- orders # only the test cases whose name, custom API or utility class contains "orders" npm run test -- --no-coverage # without the code coverage npm run test -- --bail # stop after the first test case file which fails npm run test -- --json run.json # also write the whole result to a file npm run test -- --help # every option ``` - The test cases run on the server, in the sandbox of the account, with their mocks : `npm run test` only sends the request and prints the steps as they happen. Keep `npm run start` running in another terminal so the files of your folder are what the server runs. - The exit code is `0` when everything passed, `1` when a test failed, `2` when the run did not complete, so it fits in a script or a CI job. - `Ctrl+C` cancels the run on the server. ## Code coverage in your IDE Every run collects which lines ran. `npm run test` writes them to `coverage/lcov.info` (and `coverage/coverage-summary.json`) in the folder of the local client, in the standard lcov format, with the absolute paths of the files of the repository on your machine (`/src/Custom APIs/orderCount/orderCount.ts`, `.../src/Utility classes/...`, `.../src/Test cases/...`), the way istanbul writes them. - **VS Code** : install the extension **Coverage Gutters**. Run `Coverage Gutters: Watch` (or `Display Coverage`) from the command palette : the lines which ran are marked green in the gutter, the ones which did not red, in the files of `src`. The marks refresh after every `npm run test`. - **WebStorm** : `Run → Show Coverage Data…`, then `+` and pick `coverage/lcov.info`. The coverage shows in the gutter of the editor and in the project tree. - The `coverage` folder is in the `.gitignore` of the repository : it is made on every run, for the IDE, never for git. - The coverage counts the custom APIs run with `g.sys.test.runCustomApi`, the utility classes the tests use, and the test cases themselves. A custom API called through the web (`g.sys.system.callExternalApi`) runs in a request of its own and is not part of it. A sandbox running on Bun has no profiler : the run says so, and reports no coverage. ## Run through the API `POST /api/sites/am-test-cases//run` runs the test cases and answers with the whole run : the plan, the result of every file and test, the totals and the coverage. The body holds `testCaseIds` (every test case when absent), `filter`, `coverage`, `bail` and `timeoutMS`. `POST .../run//cancel` stops a run, `GET .../run/last` gives the last run of the account, `DELETE .../run/last` clears it and `DELETE .../run/last/coverage` clears only its coverage. --- # Developer Tools > Swagger docs per API user, TypeScript interfaces from your schemas, client code in 21 languages, a debugger for sandbox code, code search and editing in your own editor. Source: https://docs.apimaker.dev/v1/docs/features/developer-tools.html What API Maker gives a developer besides the APIs : documentation for the people who call them, types for the code which uses them, a debugger, and a way to work from your own editor. ## Swagger docs per API user - Open an API user, enable Swagger docs and generate a **Swagger Token**. **View Swagger JSON** opens its URL. - The docs list only the APIs that the API user can call, like its database, custom and system APIs. - Share the URL with the team or partner that uses that API user. - The URL answers only while Swagger docs are enabled for that API user. Disable them, or generate a new token, to stop an old URL from working. ## TypeScript interfaces from your schemas - Your table schemas become TypeScript interfaces, grouped by instance and database : `db...I
`. See them in `Utility → Generated Interfaces`. - Import them from `db-interfaces` in your code. ```typescript linenums="1" import * as T from 'types'; import * as db from 'db-interfaces'; async function main(g: T.IAMGlobal) { const products = await g.sys.db.getAll({ instance: 'shop', database: 'main', collection: 'products', }); return products.map(p => p.name); // p is typed } module.exports = main; ``` ## API testing page - Every API of your account in one place: pick an API, get a sample payload, set the headers and tokens, send it and read the response. - `API Info → API Testing`, or **Test API** on the DB API, Custom API, System API, Events and Schedulers pages. On a listener the body holds the event data; on a scheduler, sending runs the code now. - The request: **Headers**, **Params**, **Query Params**, **Body** (**Generate Data** builds a sample payload from the schema), **Code** (the code of a custom API, listener, scheduler or migration, with its versions), **Pre Hooks** and **Post Hooks** of the API. On a multi-tenant instance, **Select tenant**. - The response: the answer or the errors, with the status code, the time taken and whether it came from the cache; the TypeScript interface of the answer; **Logs** with every `g.logger` line of the code which ran, hooks included. - Every **Send** is recorded. Save a request as a state to run it again later, star it as a favourite. States go to Git with the project. - Copy the same request as client code from **Integrations**: C, C#, cURL, Dart, Go, HTTP, Java, JavaScript, Kotlin, Node.js, Objective-C, OCaml, PHP, Postman CLI, PowerShell, Python, R, Ruby, Rust, Shell and Swift, with their variants. ## Debugger - Enable debugging, attach Chrome DevTools to the sandbox and stop on breakpoints in your TypeScript. - It works for code that runs in the sandbox, not for code that runs on the native process. - Reach debugging ports only through an SSH tunnel: the debug settings show the command. ## Code finder - Search a word in all your code at once: custom APIs, hooks, events, schedulers, migrations, utility classes, schemas, secrets and more, and open the result to edit it. - Learn more [Code finder](/v1/docs/features/code-finder.html). ## Your own editor - `localClient.js` in your Git repository syncs your files with API Maker both ways. - Generate a sync token in your user profile and set it in `local-client.config.js`. - `npm run start` keeps the folder in sync, `npm run test` runs the test cases of the account from the terminal and writes their code coverage to `coverage/lcov.info` for VS Code and WebStorm. Learn more [Test cases](/v1/docs/test-cases/test-cases.html). - While it is connected, API Maker also scans the account on its own and keeps `src/API Security Report/` up to date on your disk and in git, so an AI assistant working in the folder reads a fresh report. Learn more [APIs Security Report & Actions](/v1/docs/apis-security/api-security-report.html). ```javascript linenums="1" module.exports = () => { return { webSocketURL: 'wss://ws.example.com', // WebSocket URL of your API Maker syncToken: '...', // generate it in your user profile adminUserPath: 'admin', }; }; ``` ## Notes (deprecated) - Personal notes inside the admin panel, a deprecated feature. Learn more [Notes](/v1/docs/notes/notes.html). --- # Git Integration > Your whole API Maker project in Git - branches, pull, push with a commit, status and diff of every change, revert, secrets kept out, migration scripts run after a pull, and deployment hooks for CI. Source: https://docs.apimaker.dev/v1/docs/Git/git.html Everything you build in API Maker is text : schemas, custom APIs, hooks, events, schedulers, settings, groups, test cases… API Maker keeps it in a Git repository of yours, branch by branch, and pulls it back on any other server. | | | |---|---| | Set up | Profile → **User info** : the Git URL with credentials, and the email of the commit user. | | Branch | The **Branch list** of the Git panel ; the branch selected is the one pulled and pushed. **Create branch** from another one, **Delete branch**. | | Pull | Replaces the code of the account with the branch, then runs the [migration scripts](/v1/docs/features/database-migration.html) not yet executed here. | | Push | Shows the status, lets you compare and revert, then commits with your message. Pull requests are raised in your Git host. | | Never in Git | Secrets and notes. | ## What goes to Git - One folder per kind, one folder per item, the code as `.ts` and the settings as YAML : `Custom APIs`, `Schemas`, `Instances`, `Instance API settings`, the four kinds of hooks, `Events`, `WebSocket events`, `Schedulers`, `Process initializers`, `Database migrations`, `Utility classes`, `Test cases`, `Groups`, `API users`, `Auth Providers`, `i18ns`, `Log profiles`, `API testing states`, `Auto Increments`, the `API Security Report`… - Secrets never leave the server. Each environment keeps its own, which is the point : the same code runs against another database because the secret differs. - The folder is what the [local client](/v1/docs/getting-started/local-run.html#your-code-in-your-editor-the-local-client) syncs to your disk, so the same files open in your editor. ## Pull 1. Pick the branch, click **Pull**. The panel logs **Steps on the server**. 2. The items of the branch replace the ones of the account. **Last pulled branch** shows what the server runs. 3. The active migration scripts which never ran on this environment are listed : run them, in order. - **Pull Without Secret** (in the Pull dropdown) : for a repository made on another installation, whose `passDBEncryptDecrypt` differs. The passwords of the API users can not be decrypted, so they are set to read their password from the secret (`common.apiUserPasswords.default`). Add those passwords to your secret. ## Push 1. **Git status of commit** lists every file which differs from the branch : added, modified, deleted. Filter the list, expand all. 2. **Compare** opens one file in the compare editor ; **View all changes in one diff** shows them together. 3. **Revert** puts a file (or the files selected) back to the branch, in the account too. 4. Write the message and **Commit changes to branch** : the commit is pushed. - Renaming a hook shows as one file deleted and one added : the folder is its name. ## Deployment hooks - A deployment hook is an URL of your server which pulls a branch, for a CI pipeline or the webhook of your Git host. Create it in the Git panel : a name, an access token and a secret, and the IPs allowed to call it. ```text POST /api/sites/deploy//?token=&secret=&branch=main ``` - `syncMode=SYNC_WITHOUT_SECRET` pulls like the Pull Without Secret button ; `runMigrationScripts=false` skips the migration scripts. - The last callers are kept, so the panel can show where the deployments came from. ## Related - [Database migration scripts](/v1/docs/features/database-migration.html) · [Developer accounts](/v1/docs/dev-accounts/dev-accounts.html) · [Secrets](/v1/docs/secrets/secrets.html) · [Local client](/v1/docs/getting-started/local-run.html) --- # Developer Accounts > One admin user per developer in API Maker - own user path, own secrets, own branch, same Git repository - created by the root user on the Accounts page or through the management API. Source: https://docs.apimaker.dev/v1/docs/dev-accounts/dev-accounts.html A team works on one project with one admin user per developer. Each developer signs in to the admin panel with their own account, pulls the shared branch, works, and pushes. The APIs of each account answer under its own user path. | | | |---|---| | Who creates them | The root user, on the Accounts page : name, email, API path, password, active. **Clone** copies one. | | Unique | The API path and the email. | | Own to each account | Its secrets, its Git branch and credentials, its masked database names, its sandbox processes, its Redis cache. | | Shared | The Git repository, and through it every item of the project. | | From a script | The [management API](/v1/docs/am-resources/operate-api-maker-using-api.html) creates, updates and deletes admin users. | ## A developer joins 1. The root user creates the account and gives the credentials. 2. The developer sets the Git URL and commit email in their **User info**, pulls the branch of the team, and adds the connection strings and keys of their own databases to their secret. 3. When the database names of their machine differ from the ones in the code, they [mask](/v1/docs/features/mask-database.html) them. 4. They work under `/api/…//…`, test with their own API users, and push to a branch of their own for a pull request. ## Related - [Git integration](/v1/docs/Git/git.html) · [Mask database names](/v1/docs/features/mask-database.html) · [Secrets](/v1/docs/secrets/secrets.html) --- # Mask Database Names > Keep the production database names in your code while your own server uses other names - a per developer mapping from the name in the code to the name on the machine. Source: https://docs.apimaker.dev/v1/docs/features/mask-database.html The code of the project names the databases the way production does : `shop`. On a developer machine the same database may be `shop_dev`, or two developers may share one server with `shop_alice` and `shop_bob`. Masking maps one to the other, for one account, so nobody edits a database name before a pull request. | | | |---|---| | Where | Profile → **User info** → masked databases : instance, database name, masked database name. | | Scope | The account of the developer only. Git, schemas, settings and code keep the name of the project. | | Effect | Every API, hook, migration and query of the account runs on the masked name : the name in the URL and in the code stays the one of the project. | | Instance | Database name (in the code) | Masked database name (on this server) | |---|---|---| | `mysql8` | `shop` | `shop_alice` | - Add one row per database whose name differs. Names not listed are used as they are. - Table meta, migrations, plain queries which name the database in the API also go through the mapping ; SQL text you write yourself does not. ## Related - [Developer accounts](/v1/docs/dev-accounts/dev-accounts.html) · [Instances](/v1/docs/apis-all/overview.html#instances) --- # Code Finder > Search one word across every piece of code of the account - custom APIs and their versions, hooks, events, schedulers, migrations, process initializers, auth providers, utility classes, schemas, secrets - and edit the hit in place. Source: https://docs.apimaker.dev/v1/docs/features/code-finder.html `Utility → Code Finder` searches a word in all the code of the account at once and opens the hit for editing. | | | |---|---| | Searches | Custom APIs (every version), pre and post hooks of instances, databases, tables, APIs and system APIs, events and listeners, schedulers, migration scripts, process initializers, auth providers, utility classes, schemas, secrets, third party libraries. | | Result | The item, its kind and where it lives (instance, database, table, API, version…), with the code open. | | Edit | Change the code in the result and **Save changes** : no need to open the page of the item. | 1. Type a keyword and press `Enter`. The options make the search case sensitive. 2. Read the list : each hit names its kind (Custom API name, Hook name and type, Listener name, Schedulers name, Secret name, Utility class name…) and where it is. 3. Open a hit, edit, save. - Use it before renaming a field or a table : every custom API, hook and schema which names it shows up. ## Related - [Compare code](/v1/docs/features/code-comparator.html) · [Developer tools](/v1/docs/features/developer-tools.html) --- # Browsers in Custom APIs - Playwright and Puppeteer > Run a headless Chromium from API Maker custom APIs to make PDFs, take screenshots or read web pages - Playwright or Puppeteer on the native process, or Playwright inside the sandbox with its own Docker file. Source: https://docs.apimaker.dev/v1/docs/guides/browser-automation.html A custom API can drive a headless Chromium with [Playwright](https://playwright.dev/) or [Puppeteer](https://pptr.dev/) : turn HTML into a PDF, take a screenshot, or read a page. A browser needs system libraries next to its npm package, so it runs in one of two places. | | Native process | Sandbox | |---|---|---| | Where the code runs | Inside API Maker itself (`runOnNativeProcess: true`) | In the sandbox containers of the account | | Where the browser is installed | On the server, next to API Maker | In the Docker file of the sandbox | | Isolation | None : a crash of the browser can hurt the server | Your code stays apart from API Maker | | Set up by | The root user of the server | An admin, from the admin panel | A browser needs memory : plan 2 GB of RAM and 40 GB of disk for the server, and avoid many browsers at the same time. ## Playwright on the native process 1. Sign in to the server as root (`whoami` prints `root` ; `sudo -s` otherwise) and install Chromium with the libraries it needs : ```bash npx playwright install --with-deps chromium ``` 2. Add the package to API Maker : ```bash cd /root/projects/sava_api_maker/ npm i playwright ``` 3. Create a custom API and set `runOnNativeProcess: true` in its configuration (the **Basic Info** section of the [custom API](/v1/docs/apis-all/custom-apis/user-created-custom-api.html#native-process)). 4. Use it in the code. This one writes a PDF from HTML into the `uploads` folder : ```ts title="Custom API : HTML to PDF with Playwright" import * as T from 'types'; import { join } from 'path'; const playwright = require('playwright'); async function main(g: T.IAMGlobal) { const browser = await playwright.chromium.launch(); const page = await browser.newPage(); await page.setContent('

Hello from Playwright

', { waitUntil: 'load' }); await page.pdf({ path: join(__dirname, 'uploads', 'playwright.pdf'), format: 'A4', printBackground: true, margin: { top: '20mm', right: '20mm', bottom: '20mm', left: '20mm' }, }); await browser.close(); // uploads/playwright.pdf is on the server : upload it to a storage provider or send it as a download. return 'pdf created on the server'; } module.exports = main; ``` !!! tip "Every server, every upgrade" A package installed by hand lives on one server. Put the install command in **Root Settings → Deployment Settings → Scripts → Server Startup Script** : the script runs when the API Maker process starts, on every server of the cluster, so the package is there on new servers too. ## Playwright in the sandbox The default image of the sandbox has no browser libraries. Two changes in the [sandbox settings](/v1/docs/settings/sandboxSettings.html) add them. 1. **Docker file** : replace the content with the file below and click **Save** (top right). It installs Chromium, its system libraries and pnpm. ```dockerfile FROM node:22-bookworm WORKDIR /usr/src/app RUN npm install -g pnpm@10.27.0 RUN apt-get update && apt-get install -y --no-install-recommends build-essential g++ make libc6 python3 python3-dev && rm -rf /var/lib/apt/lists/* ARG A_DOCKERFILE_HASH ENV A_DOCKERFILE_HASH=${A_DOCKERFILE_HASH} ARG A_PACKAGE_JSON_HASH ENV A_PACKAGE_JSON_HASH=${A_PACKAGE_JSON_HASH} COPY ./package.json ./ RUN pnpm install --dangerously-allow-all-builds RUN npx -y playwright@1.58.0 install --with-deps ARG A_CODE_HASH ENV A_CODE_HASH=${A_CODE_HASH} ARG NODE_OPTIONS ENV NODE_OPTIONS=${NODE_OPTIONS} COPY . . ARG A_NEW_PACKAGES_INSTALL_CMD ENV A_NEW_PACKAGES_INSTALL_CMD=${A_NEW_PACKAGES_INSTALL_CMD} RUN $A_NEW_PACKAGES_INSTALL_CMD EXPOSE 4631 EXPOSE 4632 EXPOSE 9229 CMD [ "npm", "run", "start" ] ``` 2. **Dependencies** : click **Add (+)**, search for `playwright`, pick the latest stable version and click **Add**. Launch Chromium with `--no-sandbox` inside the container. This custom API saves a web page as a PDF and sends it as a download : ```ts title="Custom API : a web page to PDF, in the sandbox" import * as T from 'types'; const fs = require('fs'); const path = require('path'); const { chromium } = require('playwright'); async function main(g: T.IAMGlobal) { const outDir = path.join(__dirname, 'uploads'); if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true }); const browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] }); const page = await browser.newPage(); await page.goto('https://playwright.dev', { waitUntil: 'networkidle' }); await page.pdf({ path: path.join(outDir, 'playwright-dev.pdf'), format: 'A4' }); await browser.close(); return { __am__downloadFilePath: 'playwright-dev.pdf', __am__downloadFolderFileName: 'playwright-dev.pdf', }; } module.exports = main; ``` ## Puppeteer on the native process 1. Sign in to the server as root and install the libraries of Chromium (Ubuntu ; for other systems see [the troubleshooting page of Puppeteer](https://pptr.dev/troubleshooting#chrome-doesnt-launch-on-linux)) : ```bash apt update && apt install -y ca-certificates fonts-liberation libatk1.0-0t64 libatk-bridge2.0-0t64 \ libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 \ libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 \ libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 \ libxss1 libxtst6 libasound2t64 lsb-release wget xdg-utils ``` 2. Add the package to API Maker : ```bash cd /root/projects/sava_api_maker/ npm install puppeteer ``` 3. Set `runOnNativeProcess: true` in the configuration of the custom API, and use it : ```ts title="Custom API : HTML to PDF with Puppeteer" import * as T from 'types'; import { join } from 'path'; const puppeteer = require('puppeteer'); async function main(g: T.IAMGlobal) { const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'] }); const page = await browser.newPage(); await page.setContent('

Hello, this paragraph is converted to a PDF using Puppeteer.

', { waitUntil: 'load' }); await page.pdf({ path: join(__dirname, 'uploads', 'puppeteer.pdf'), format: 'A4', printBackground: true }); await browser.close(); // uploads/puppeteer.pdf is on the server : upload it to a storage provider or send it as a download. return 'ok'; } module.exports = main; ``` ## Good to know - Write the files you want to send into the `uploads` folder and return `__am__downloadFilePath` : see [Download file](/v1/examples/custom-apis/custom-api.html#download-file). Upload them to a storage service instead when they must be kept. - Close the browser in every path of the code, errors included : an open browser keeps its memory. - A bug on the native process can hurt the whole server : prefer the sandbox when you can, and keep the native process for the APIs which need it. ## Related - [Custom APIs](/v1/docs/apis-all/custom-apis/user-created-custom-api.html) · [Sandbox settings](/v1/docs/settings/sandboxSettings.html) · [How API Maker works](/v1/docs/getting-started/how-it-works.html#where-your-code-runs) - Blogs : [Playwright on the native process](/v1/docs/blog/General-Blogs/Playwright.html) · [Playwright in the sandbox](/v1/docs/blog/General-Blogs/Playwright-in-sandbox.html) · [Puppeteer in native custom APIs](/v1/docs/blog/General-Blogs/setup-puppeteer-and-use-in-Native-custom-APIs.html) --- # Notes (deprecated) > Personal notes inside the admin panel of API Maker, kept on the server and never pushed to Git - a deprecated feature, listed under Deprecated Features on the dashboard. Source: https://docs.apimaker.dev/v1/docs/notes/notes.html !!! deprecated "Deprecated" Notes are a deprecated feature. They still work, from **Deprecated Features** on the dashboard, and will be removed in API Maker v4. Keep the notes of a project in its Git repository, next to the code. - Personal notes of the signed in admin user, with text formatting, as many as you like. - They stay on the server : a Git pull does not touch them and a push does not send them. --- # Secrets > The secret of an API Maker account - hashing and encryption keys, the transfer key shared with apps, API user passwords, connection strings, default auth providers, multi-tenant entries - a TypeScript object kept encrypted on the server and never in Git. Source: https://docs.apimaker.dev/v1/docs/secrets/secrets.html A secret holds what must never be in the code : keys, passwords, connection strings. Every account gets one at creation, the **default secret**, and can add more. Code reads them with [get secret](/v1/docs/apis-all/system-apis/system-generated-get-secret-by-name-api.html) ; instances pick their connection string from them ; conversions and system APIs use their keys. | | | |---|---| | Page | `API Security → Secret Management` : the secrets of the account, one of them the default. | | Shape | A TypeScript file which exports an object. `common` is read by API Maker ; add your own sections next to it. | | Kept | Encrypted on the server of this environment. Never pushed to Git : each environment has its own values. | | Another secret | The header `x-am-secret: ` makes a request use another secret of the account ; from code, `getSecret(keys, fromSecretName)`. | ## The default secret of a new account ```typescript linenums="1" import * as T from 'types'; let Secret: T.ISecretType | any = { common: { hashingAlgorithm: 'SHA256', // the hashing conversion and the hash data API nonce: '85491cec-…', // key of the hashes ; without it, secret is used encryptionAlgorithm: 'AES', // the encryption conversion of the schemas secret: '1eaaca38-…', // never change it once data is encrypted with it encryptionAlgorithmFETransfer: 'AES', // encrypt and decrypt data system APIs secretFETransfer: '78e493ee-…', // shared with your frontend or mobile app feTransferDataValidityInSeconds: 300, // an encrypted payload older than this is refused apiUserPasswords: { // API users can read their password from a path of the secret default: '12345', }, connectionString: { // offered when you add an instance mongodb: 'mongodb://your_username:your_password@server_ip:27017/?authSource=admin&replicaSet=rs0&directConnection=true', mysql_8: 'mysql://your_username:your_password@server_ip:server_port?multipleStatements=true', mariadb: 'mariadb://your_username:your_password@server_ip:server_port?multipleStatements=true', sqlServer: 'Server=server_ip;User Id=user_id;Password=your_password;Trusted_Connection=True;TrustServerCertificate=True;', postgresql: 'postgresql://your_username:your_password@server_ip:server_port', oracle: 'server_ip:server_port/oracle_process_name', oracle_username: 'your_username', oracle_password: 'your_password', }, // authProviders: [], // the auth providers every API needs unless its settings say otherwise }, }; module.exports = Secret; ``` ## The keys of `common` | Key | Used by | |---|---| | `hashingAlgorithm`, `nonce` | The `hashing` conversion of a [schema](/v1/docs/schema/schema.html#3-conversions-clean-the-value-first) and the [hash data](/v1/docs/apis-all/system-apis/system-generated-hash-data-api.html) API. `SHA256` is the algorithm supported. Without `nonce`, `secret` is the key. | | `encryptionAlgorithm`, `secret` | The `encryption` conversion : `AES`, `RC4` or `TRIPLEDES`. Changing `secret` means re-encrypting every encrypted value, and re-hashing when there was no nonce. | | `encryptionAlgorithmFETransfer`, `secretFETransfer` | The [encrypt](/v1/docs/apis-all/system-apis/system-generated-encrypt-data-api.html) and [decrypt](/v1/docs/apis-all/system-apis/system-generated-decrypt-data-api.html) APIs, the [encrypted payloads](/v1/docs/features/security-features.html#encrypted-request-payloads) and the encrypted answers. This is the key you give to your apps. | | `feTransferDataValidityInSeconds` | How old an encrypted payload may be. | | `apiUserPasswords` | Passwords an [API user](/v1/docs/apis-security/api-user-permission.html) reads by path (`common.apiUserPasswords.default`), so the password is not in Git. | | `connectionString` | The list offered by the instance form. Any key of the secret can hold one ; this section is a convention. | | `authProviders` | The names of the [auth providers](/v1/docs/authorization/AMDB.html) every API needs by default. Absent : only the API user token. The settings of an API, a table or a database override it. | | `multiTenant` | The tenants tables of [multi-tenant](/v1/docs/features/multi-tenant.html#2-point-the-default-secret-to-that-table) instances. | ## Your own keys ```typescript title="Read them from code" const apiKey = await g.sys.system.getSecret('stripe.apiKey'); const [ key, cs ] = await g.sys.system.getSecret([ 'stripe.apiKey', 'common.connectionString.mysql_8' ]); ``` - Add sections freely : `stripe: { apiKey: '…' }`. A path with dots reaches any key. - A team keeps one secret per environment with the same keys and other values : the code does not change between a laptop and production. ## Related - [Security features](/v1/docs/features/security-features.html) · [Connection strings](/v1/docs/Database-connection-string/mongodb-connection-strings.html) · [Get secret API](/v1/docs/apis-all/system-apis/system-generated-get-secret-by-name-api.html) · [Git integration](/v1/docs/Git/git.html) --- # API Group Permissions > Groups are the permission unit of API Maker - which tables, APIs and fields, which custom, system and third party APIs, which WebSocket events - given to API users and named in the groups column of your own users. Source: https://docs.apimaker.dev/v1/docs/apis-security/api-group-permission.html A group is a list of what may be called : tables and their APIs, fields readable and writable, custom APIs, system APIs, third party APIs, WebSocket events. An [API user](/v1/docs/apis-security/api-user-permission.html) gets groups ; a person of your users table gets groups through the `groupsColumn` of its [auth provider](/v1/docs/authorization/AMDB.html). A call passes when the API user grants it and, when a person is involved, the person too. > Diagram : Two tokens, two gates : the API user (which application) and the person (which rows) | | | |---|---| | Page | `API Security → API Group Permissions` : the groups on the left, the tree of the account on the right. | | Grants | Instances → databases → tables → APIs, and the fields of a table with **R** and **W** ; custom APIs ; system APIs ; third party APIs ; WebSocket events. | | Shortcuts | **All APIs**, **All Fields**, **All Third Party APIs** and the checkbox of a column check everything below it. New tables and APIs are covered by an "all" grant. | | Refused with | `403` when no group of the caller grants the API ; a field not readable is absent from the answer, a field not writable is refused on save. | ## Make a group 1. **Add New**, give a unique name, **Save Group**. **Clone** copies an existing group. 2. Open the tree. Each API shows how it is reachable, from its settings : **Public**, **AM-User** (API user token), **DB-User** (person token), **Google**, **Azure**, or **No-Access**. 3. Check the APIs and, under a table, the fields : **R** to read them, **W** to write them. 4. **Save group settings**. **Active API users in this group** shows who has it. ## Give it - To an API user : the groups list on the [API User Permissions](/v1/docs/apis-security/api-user-permission.html) page. - To a person : write the group names, comma separated, in the column named by `groupsColumn` of the auth provider. `*` gives the person every group of the API user. ## Good to know - The pre and post hooks of an API can be limited to some groups : they run only for callers of those groups. - The [APIs Security Report](/v1/docs/apis-security/api-security-report.html) lists the groups which reach too far. - Groups go to Git : the same permissions on every environment. ## Related - [Handle role based permissions](/v1/docs/authorization/handle-role-based-permissions.html) · [API user permissions](/v1/docs/apis-security/api-user-permission.html) · [Auth of database user](/v1/docs/authorization/AMDB.html) --- # API User Permissions > An API user is the identity of an application which calls API Maker - its token goes in x-am-authorization, its groups decide the APIs reachable, its password can live in the secret, and it can publish Swagger docs of its APIs. Source: https://docs.apimaker.dev/v1/docs/apis-security/api-user-permission.html An API user is an application : the web app, the mobile app, a partner. It signs in with a username and a password, gets a token, and every call carries that token in `x-am-authorization`. Its groups decide which APIs the application can call at all. | | | |---|---| | Page | `API Security → API User Permissions` : the API users on the left, the groups of the selected one on the right. | | Fields | Name, username, password, or **Enable Password From Secret Path** with the path of the password in the [secret](/v1/docs/secrets/secrets.html) (`common.apiUserPasswords.default`). | | Token | [`POST /api/system-api//token`](/v1/docs/apis-all/system-apis/system-generated-token-api.html) with `{ "u": "username", "p": "password" }`. | | Swagger | **Enable Swagger Docs** and a **Swagger Token** publish the documentation of the APIs this API user can call : **View Swagger JSON** opens it. | | New account | An API user `default`, password `12345` from the secret, with the group `Default`. | ## Set one up 1. **Add New**, give the name and the username. 2. Type a password, or enable the password from a secret path : the password then lives in the secret of each environment, not in Git. 3. Check the [groups](/v1/docs/apis-security/api-group-permission.html) it gets. An API user with several groups can call what any of them grants. 4. **Save User**. The application gets its token with the username and the password. ## Swagger docs per API user - Generate a random Swagger token and enable the docs : the URL shown answers with the OpenAPI document of exactly the APIs this API user can call, database, custom and system APIs alike. - Share it with the team which builds on that API user. Disable the docs, or generate a new token, to stop an old URL. ## Good to know - Changing the password invalidates the tokens made before. - API users go to Git ; with a password from a secret path, the password does not. ## Related - [Get token API](/v1/docs/apis-all/system-apis/system-generated-token-api.html) · [API group permissions](/v1/docs/apis-security/api-group-permission.html) · [Auth of API user](/v1/docs/authorization/AMApiUser.html) --- # Handle Role Based Permissions > Model every checkbox of your role screen as one small API Maker group, store group names in your own users table, and scope rows with a database level pre-hook. Source: https://docs.apimaker.dev/v1/docs/authorization/handle-role-based-permissions.html Sooner or later every business application grows this screen. Modules down the left, operations across the top, a checkbox wherever the two meet. ![Role permission matrix mapped to an API Maker group](../../../assets/images/role-based-permissions/rbac-checkbox-is-a-group.svg) --- Drawing the grid is a morning's work. Deciding what the backend should do when somebody ticks a box is what eats the rest of the sprint. --- ## Two gates, and you need both Every authenticated request in API Maker gets checked twice, and the two checks answer different questions. ![The API gate and the row gate](../../../assets/images/role-based-permissions/rbac-two-gates.svg) | | Question | Answered by | Enforced by | |---|---|---|---| | **Gate 1 — API gate** | May this caller call this endpoint at all? | The caller's **groups** | API Maker, before your code runs | | **Gate 2 — Row gate** | Of the rows this endpoint can reach, which ones are *this person's*? | A **pre-hook** that injects the user's id into the filter | Your hook, before the query runs | Drop either one and you have left a hole: - With only gate 1, `PURCHASE_INVOICE_VIEW` is allowed to call `get-by-id`, so anyone in that group can walk ids and read the whole company's invoices. - With only gate 2, someone who can see just their own rows can still call `remove-by-id` and delete them. The rest of this page sets both up for a Purchase Invoice module. --- ## How authorization works in API Maker Two identities ride along on every request, in two separate headers. People mix them up constantly, so it is worth being precise about which is which. | Header | Identity | Where it comes from | Read it with | |---|---|---|---| | `x-am-authorization` | The **API user** — *which application* | An API user created in API Maker | `g.req.auth.authAMUser` | | `x-am-user-authorization` | The **database user** — *which person* | A row in **your own** users table | `g.req.auth.authAMDB` | Groups hang off the API user and grants hang off the groups. When a request arrives, API Maker walks down this chain and stops at the first level that allows it: ```text instance → database → collection → supported API → field ``` Custom APIs, System APIs and WebSocket events do not cascade. They are flat lists: either the group names them or it does not. > Worth memorising, because it saves a lot of wasted debugging: `401` means the token is missing, malformed or expired. `403` means the token is fine and **no group grants that API**. If you are seeing 403s, go and fix the group. Do not log the user out, and do not send them back to the login screen. --- ## One group per checkbox This is the part people push back on, so let me make the case for it. Do not create a group called `Accountant`. Job titles are combinations, and combinations breed. The week HR invents "Junior Accountant, South Region" you are back in the console building another group, working out which APIs it needs, and hoping you remembered all of them. Make each group the size of one checkbox instead. A job title then becomes nothing more than a list. | Screen | API Maker group | |---|---| | Purchase › Purchase Invoices › View | `PURCHASE_INVOICE_VIEW` | | Purchase › Purchase Invoices › Create | `PURCHASE_INVOICE_CREATE` | | Purchase › Purchase Invoices › Edit | `PURCHASE_INVOICE_EDIT` | | Purchase › Purchase Invoices › Delete | `PURCHASE_INVOICE_DELETE` | | Purchase › Purchase Invoices › Export | `PURCHASE_INVOICE_EXPORT` | | Purchase › Purchase Invoices › Confirm | `PURCHASE_INVOICE_CONFIRM` | | Purchase › Debit Notes › View | `DEBIT_NOTE_VIEW` | | Purchase › Parcel Entry › View | `PARCEL_ENTRY_VIEW` | Small groups combine. `Accountant` is just `PURCHASE_INVOICE_VIEW,PURCHASE_INVOICE_CREATE,DEBIT_NOTE_VIEW`. That new job title is another list of the same groups, and nobody has to build anything. You will want one deliberately coarser group per module, for supervisors: | Group | Meaning | |---|---| | `PURCHASE_INVOICE_VIEW_ALL` | Same APIs as `PURCHASE_INVOICE_VIEW`, but exempt from row scoping. A manager or auditor sees the whole company's invoices | That exemption is configuration rather than code. You [gate the row-scoping hook with `groupNames`](#gating-a-hook-to-specific-groups) and it never runs for that group at all. --- ## Step 1 — Grant the group exactly what it needs Open **APIs Security › API group permission**, add `PURCHASE_INVOICE_VIEW`, and then tick almost nothing. ![Granting a single API to one group](../../../assets/images/role-based-permissions/rbac-group-grants.svg) Going back to the screenshot at the top of the page, the **View** checkbox on **Purchase Invoices** needs exactly two grants: | Category | Granted | |---|---| | Instance API | `erp_mongo › erp_db › purchase_invoices › SCHEMA_GET_BY_ID` | | Custom API | `purchase-invoice-print` | Everything else stays off, `SCHEMA_GET_ALL` and `SCHEMA_POST_QUERY` included. A detail screen opens one invoice at a time, so it has no business calling an API that hands back all of them. The list screen gets its own group for that. The full set for the Purchase Invoice module: | Group | Instance APIs granted | Custom APIs granted | |---|---|---| | `PURCHASE_INVOICE_VIEW` | `SCHEMA_GET_BY_ID` | `purchase-invoice-print` | | `PURCHASE_INVOICE_LIST` | `SCHEMA_GET_ALL` | — | | `PURCHASE_INVOICE_CREATE` | `SCHEMA_POST_BULK_INSERT` | — | | `PURCHASE_INVOICE_EDIT` | `SCHEMA_PUT_UPDATE_BY_ID` | — | | `PURCHASE_INVOICE_DELETE` | `SCHEMA_DEL_DELETE_BY_ID` | — | | `PURCHASE_INVOICE_EXPORT` | `SCHEMA_GET_ALL_STREAM` | `purchase-invoice-export-xlsx` | | `PURCHASE_INVOICE_CONFIRM` | — | `purchase-invoice-confirm` | `PURCHASE_INVOICE_CONFIRM` is worth a second look. Confirming an invoice is not CRUD: it checks stock, posts ledger entries and locks the document. So it is a Custom API, and a group can grant one Custom API and nothing besides. The checkbox on screen still lines up with exactly one group. Never switch the broad flags on for an application group. `allCollections` does not only grant what exists today, it grants whatever you add next month and next year too. That is how a `salaries` table ends up readable by a group nobody has looked at in two years. ### Field level: hiding columns inside a row Groups control individual fields as well. Anything a group cannot read gets stripped out of the response, `deep`-populated documents included. That last part is the one people forget. ```json linenums="1" { "fields": { "erp_mongo": { "erp_db": { "purchase_invoices": { "landed_cost": { "allowRead": false, "allowWrite": false }, "supplier_margin": { "allowRead": false, "allowWrite": false }, "invoice_total": { "allowRead": true, "allowWrite": false } } } } } } ``` `PURCHASE_INVOICE_VIEW` can now open an invoice without ever learning what the company paid for the goods. --- ## Step 2 — You do **not** create groups or users in API Maker at runtime This one trips up almost everyone arriving from another stack. ![The groups column drives everything](../../../assets/images/role-based-permissions/rbac-groups-column.svg) There is no "create group" API to call when you onboard a customer, no API user per employee, and no nightly job pushing your permission tables into API Maker's. You write the groups once, in the console, and they go into Git along with everything else. Handing someone a role at runtime is an `UPDATE` on a column in your own table, which the admin screen you were already building can do. ### Add a `groups` column to your users table ```ts title="erp_users schema" linenums="1" import { ISchemaType, EType, ISchemaProperty, IPropertyValidation } from 'types'; let schema: ISchemaType = { _id: EType.objectId, user_name: { __type: EType.string, validations: { required: true } }, password: { __type: EType.string, validations: { required: true }, conversions: { hashing: true } }, passwordChangedAt: { __type: EType.number }, department: { __type: EType.string }, is_active: { __type: EType.boolean }, // 👇 the whole role model, in one column groups: { __type: EType.string } }; module.exports = { schema }; ``` Rows look like this: | `_id` | `user_name` | `groups` | |---|---|---| | `101` | nikita | `PURCHASE_INVOICE_VIEW` | | `102` | rakesh | `PURCHASE_INVOICE_VIEW,PURCHASE_INVOICE_CREATE` | | `103` | meera | `PURCHASE_INVOICE_VIEW_ALL,DEBIT_NOTE_VIEW` | | `104` | root | `*` | `*` is shorthand for *every group the API user has*. Keep it for your own internal service account. ### Point API Maker at that column Set `groupsColumn` in the default **[secret](/v1/docs/secrets/secrets.html#auth-token-info)**, or override it per [database](/v1/docs/settings/databaseSettings.html#auth-token-info), [collection](/v1/docs/settings/collectionSettings.html#auth-token-info) or [API](/v1/docs/settings/apiSettings.html#auth-token-info). ```ts linenums="1" import * as T from 'types'; authTokenInfo: [ { authTokenType: T.EAuthTokenType.AM_DB, authTokenAMDB: { instance: 'erp_mongo', database: 'erp_db', collection: 'erp_users', usernameColumn: 'user_name', passwordColumn: 'password', // A password change invalidates every token issued before it. // Without this the password itself is embedded in the JWT. passwordChangedAtColumn: 'passwordChangedAt', // 👇 API Maker reads this column and resolves the grants itself groupsColumn: 'groups', // Columns embedded in the token — available as g.req.auth.authAMDB select: { _id: 1, user_name: 1, department: 1, groups: 1 } } } ] ``` That is the whole wiring. From here on: - The user logs in, and API Maker reads `groups` off their row. - Access becomes the **union** of those groups' grants. - Change the column, and the next token the user gets carries the new permissions. > Keep `_id` in `select`. The hook in the next step needs it to work out who owns a row, and anything you leave out of `select` is not in the token. You do not need to read group membership yourself. API Maker pulls that from `groupsColumn` and enforces it for you. The same `groupsColumn` field turns up on the [Google](/v1/docs/authorization/Google.html), [AWS Cognito](/v1/docs/authorization/AWS.html) and [Azure AD](/v1/docs/authorization/Azure.html) configurations, under `groupsDataSource`. Swap your own login screen for single sign-on later and the groups half of this does not change. --- ## Step 3 — Log in and mint both tokens One public Custom API, and it is the only place an API user password should ever show up. It hands the client both headers in a single round trip. ```ts title="Custom API: login (apiAccessType: IS_PUBLIC)" linenums="1" import * as T from 'types'; import * as db from 'db-interfaces'; const INSTANCE = 'erp_mongo'; const DATABASE = 'erp_db'; const USERS = 'erp_users'; async function main(g: T.IAMGlobal) { const userName = g.req.body.user_name; const password = g.req.body.password; if (!userName || !password) { g.res.statusCode = T.EStatusCode.BAD_REQUEST; throw new Error('user_name and password are required.'); } const user: db.erp_mongo.erp_db.IErpUsers = await g.sys.db.getById({ instance: INSTANCE, database: DATABASE, collection: USERS, primaryKey: 'user_name', id: userName, // 👇 Ask for the columns this API uses, and nothing else. // The password column is never one of them. select: '_id,user_name,department,groups,is_active' }); if (!user || user.is_active !== true) throw new Error('Invalid credentials.'); // The API user password lives in the secret, never in code and never in the frontend. const apiUserPassword = await g.sys.system.getSecret('common.apiUserPasswords.erp_app'); const tokens = await g.sys.system.getToken([ // No name : the token of an API user { u: 'erp_app', p: apiUserPassword }, // The DB token generator 'erp_users_token' (Token Generators page) reads the erp_users table, // with user_name as its username column and password as its password column { name: 'erp_users_token', u: userName, p: password } ]); return { amToken: tokens[0], // → send as x-am-authorization amDbToken: tokens[1], // → send as x-am-user-authorization user: { _id: user._id, user_name: user.user_name, groups: user.groups } }; }; module.exports = main; ``` `select` earns its keep there. Leave it out and the lookup drags the whole row into your sandbox, password hash and all, where it can end up in a log line or an error payload without anyone meaning it to. `getToken` checks the credentials against that same table on its own, so this code never has a reason to hold the hash. Pass a comma-separated list of the columns you want, or `-password` to drop just that one. Sending `groups` back lets the frontend grey out buttons the user cannot use. That is a usability thing, nothing more. The server has already made the real decision, and it makes it again on every call. --- ## Step 4 — Row level security with a database level pre-hook Groups answered "can rakesh call `get-by-id` on `purchase_invoices`?". They cannot answer "is `PI-1042` rakesh's invoice?", because that is a fact about the data rather than about configuration. Pre-hooks handle the second question. ### Pick the scope Pre-hooks attach at four scopes and run outermost first. Post-hooks run the same chain in reverse. ```text pre : instance → database → collection → api post : api → collection → database → instance ``` | Scope | Applies to | |---|---| | **Instance** | Every API on every collection in every database of that instance | | **Database** | Every API on every collection in that database | | **Collection** | Every API on that collection | | **API** | Just that one endpoint | Put row scoping at the **database** scope. One hook then covers `purchase_invoices`, `debit_notes`, `parcel_entries`, and whatever collection somebody adds next quarter without mentioning it. That last case is the one that kills the per-endpoint `if`. Attach it under **Instances → `erp_mongo` → `erp_db` → Pre-hooks**. ### What a pre-hook can change, per API Worth knowing, because the generated APIs do not all keep their filter in the same place. By the time your hook runs, `find` has already been parsed into a plain object, so mutating it changes the query. | API | Where the filter lives | How to scope it | |---|---|---| | `SCHEMA_GET_ALL`, `SCHEMA_GET_ALL_STREAM` | `g.req.query.find` | merge the owner into `find` | | `SCHEMA_POST_QUERY`, `SCHEMA_POST_QUERY_STREAM`, `SCHEMA_POST_QUERY_DELETE`, `SCHEMA_UPDATE_MANY`, `SCHEMA_POST_DISTINCT_QUERY`, `SCHEMA_ARRAY_OPERATIONS` | `g.req.body.find` | merge the owner into `find` | | `SCHEMA_GET_BY_ID`, `SCHEMA_PUT_UPDATE_BY_ID`, `SCHEMA_PUT_REPLACE_BY_ID`, `SCHEMA_DEL_DELETE_BY_ID` | `g.req.query.find`, **ANDed onto the primary key** | merge the owner into `find` | | `SCHEMA_POST_BULK_INSERT`, `SCHEMA_MASTER_SAVE` | `g.req.body` | stamp the owner server-side | | `SCHEMA_POST_AGGREGATE`, `SCHEMA_POST_COUNT`, `SCHEMA_GET_DISTINCT` | a pipeline / a raw query object | cannot be scoped generically, so do not grant them | The third row catches people out. `get-by-id` and its write siblings take an id in the URL and the caller has no way to send a `find` of their own, but the engine still folds whatever your hook leaves on `g.req.query.find` into the filter it builds around the primary key: ```ts const findQuery = { [primaryKey]: _id, ...(sandboxReq?.query?.find || {}), // ← your hook's filter }; ``` That is `findOneById` in `MongodbOps`. `generateQueryGetById`, `generateUpdateQuery` and `generateDeleteQuery` in `CommonSqlUtils` do the same for the SQL engines. One filter therefore scopes every row-driven API, so you do not have to load the row and compare owners yourself. You should not, either, since it costs a database round trip on every call. You also get the better failure mode without asking for it. A row that is not yours never matches, so the response comes back empty rather than 403, and there is nothing for an attacker to distinguish between "no such id" and "not your id". ### The database level pre-hook ```ts title="Database pre-hook on erp_mongo › erp_db" linenums="1" import * as T from 'types'; // ── Which collections are owned by a user, and by which column ────────────── // A collection that is not in this map is not row-scoped at all. const OWNER_COLUMN = new Map([ ['purchase_invoices', 'created_by'], ['debit_notes', 'created_by'], ['parcel_entries', 'entered_by'] ]); // ── apiId → where that API carries its filter ────────────── // One O(1) lookup per request instead of scanning four arrays. const SCOPE_BY_API = new Map([ // API Maker ANDs g.req.query.find onto the primary key for the *-by-id APIs, // so one filter covers the list, the single read, and the writes alike. ['SCHEMA_GET_ALL', 'QUERY_FIND'], ['SCHEMA_GET_ALL_STREAM', 'QUERY_FIND'], ['SCHEMA_GET_BY_ID', 'QUERY_FIND'], ['SCHEMA_PUT_UPDATE_BY_ID', 'QUERY_FIND'], ['SCHEMA_PUT_REPLACE_BY_ID', 'QUERY_FIND'], ['SCHEMA_DEL_DELETE_BY_ID', 'QUERY_FIND'], ['SCHEMA_POST_QUERY', 'BODY_FIND'], ['SCHEMA_POST_QUERY_STREAM', 'BODY_FIND'], ['SCHEMA_POST_QUERY_DELETE', 'BODY_FIND'], ['SCHEMA_UPDATE_MANY', 'BODY_FIND'], ['SCHEMA_POST_DISTINCT_QUERY', 'BODY_FIND'], ['SCHEMA_ARRAY_OPERATIONS', 'BODY_FIND'], ['SCHEMA_POST_BULK_INSERT', 'STAMP_OWNER'] ]); async function main(g: T.IAMGlobal) { // 1. Only scope real HTTP traffic. Custom APIs, events and schedulers calling // g.sys.db run under their own rules. if (!g.req.isApiRequestFromUser) return; const collection = g.req.params['collection']; const ownerColumn = OWNER_COLUMN.get(collection); if (!ownerColumn) return; // not a user-owned collection // 2. Which person is acting? Not the application — the person. // API Maker has already rejected the request if the user token is missing. const userId = g.req.auth.authAMDB._id; const scope = { [ownerColumn]: userId }; // MERGE, never replace. $and keeps whatever the caller asked for and adds ours // on top, so a caller can never widen the scope by sending its own created_by. const merge = (existing) => (existing && Object.keys(existing).length ? { $and: [existing, scope] } : scope); // 3. One lookup decides where the filter goes. switch (SCOPE_BY_API.get(g.req.reqInfo?.apiInfo?.id)) { // get-all, get-by-id, update-by-id, replace-by-id, remove-by-id. // A row that is not yours is simply never matched — no extra round-trip, // and no 404-vs-403 difference for an attacker to probe. case 'QUERY_FIND': g.req.query.find = merge(g.req.query.find); return; case 'BODY_FIND': g.req.body.find = merge(g.req.body.find); return; // Inserts: stamp the owner on the server. Never trust the body. case 'STAMP_OWNER': { const rows = Array.isArray(g.req.body) ? g.req.body : [g.req.body]; for (const row of rows) row[ownerColumn] = userId; return; } // Fail closed. Aggregate, count and distinct cannot be scoped generically, // so if one of them is ever granted by mistake, it stops here. default: g.res.statusCode = T.EStatusCode.FORBIDDEN; throw new Error('This API is not available for row-scoped users.'); } }; module.exports = main; ``` A few details in there are easy to skim past. The `isApiRequestFromUser` guard lets internal calls through untouched. Custom APIs, events and schedulers reaching the collection via `g.sys.db` run under their own rules, and a scheduler rebuilding a report is not a user at all, so there is no `authAMDB` to scope by. The merge uses `$and` rather than a spread, and that is deliberate. `{ ...find, created_by: userId }` looks like the same thing and is not: if the caller sends `find={created_by:{$ne:null}}` their key survives next to yours, and which one wins comes down to operator precedence. `$and` applies both conditions no matter what, so the caller cannot widen the result set. Both lookup tables are `Map`s. The hook runs on every single request against the database, so scanning four arrays with `indexOf` is work you would be paying for constantly and getting nothing back for. A `Map` is also the safer shape here: `ownerColumn` is keyed off the URL, and a plain object would happily hand back `constructor` or `toString` for a collection with one of those names, sailing straight past the `if (!ownerColumn)` guard. The `default` branch throws rather than falling through. A hook that ends in an implicit `return` quietly allows every API added after it was written. This way a mis-grant turns into a support ticket instead of a breach. And because the scope goes into the filter rather than into a check afterwards, someone else's invoice just does not match. The caller cannot tell "no such id" from "not your id", which is what you want and costs nothing to get. ### A narrower version, at collection scope That hook fires on every call to every collection in `erp_db`. The two `Map` lookups keep it cheap, but if only one collection needs scoping there is no reason to carry the table around. Move the hook down to **collection** scope: ```ts title="Collection pre-hook on purchase_invoices" linenums="1" import * as T from 'types'; const OWNER_COLUMN = 'created_by'; async function main(g: T.IAMGlobal) { if (!g.req.isApiRequestFromUser) return; // API Maker has already rejected the request if the user token is missing. const userId = g.req.auth.authAMDB._id; const scope = { [OWNER_COLUMN]: userId }; // Covers get-all AND the *-by-id APIs — API Maker ANDs this onto the primary key. const existing = g.req.query.find; g.req.query.find = existing && Object.keys(existing).length ? { $and: [existing, scope] } : scope; }; module.exports = main; ``` > Same idea one level up. A hook at instance scope that hits the database adds that round trip to every single call against the instance. Pick the narrowest scope that still covers what you need, and keep whatever runs there fast. ### Gating a hook to specific groups Hooks can carry a `groupNames` list. Give it one and the hook only runs for callers who are in those groups. | Hook has `groupNames`? | Caller's groups intersect? | Runs? | |---|---|---| | No | — | always | | Yes | Yes | yes | | Yes | No | skipped | | Yes | Caller has no groups | skipped | That is how you row-scope customer-facing API users while leaving an internal integration account alone, with no `if` inside the hook. It is also how supervisors see everything. List only the row-scoped groups: ```text groupNames: ['PURCHASE_INVOICE_VIEW', 'PURCHASE_INVOICE_LIST', 'PURCHASE_INVOICE_EDIT'] ``` A token carrying `PURCHASE_INVOICE_VIEW_ALL` never triggers the hook, so nothing filters its reads. Resist writing that exemption in code. API Maker already knows the caller's groups and already decides whether the hook runs. A hand-rolled `if (groups.includes('...VIEW_ALL')) return;` puts the same decision in a second place, and sooner or later the two drift apart. --- ## Step 5 — Try to break it Nobody should believe a permission model until they have tried to get round it. Run these. ```bash linenums="1" # 1. log in — one call, both tokens TOKENS=$(curl -s -X POST "$BASE/api/custom-api/erp/login" \ -H 'content-type: application/json' \ -d '{"user_name":"rakesh","password":""}') AM=$(echo "$TOKENS" | jq -r '.data.amToken.token') USR=$(echo "$TOKENS" | jq -r '.data.amDbToken.token') # 2. his own invoice → 200 curl -s -o /dev/null -w '%{http_code}\n' \ "$BASE/api/schema/erp/erp_mongo/erp_db/purchase_invoices/get-by-id/PI-1042" \ -H "x-am-authorization: $AM" -H "x-am-user-authorization: $USR" # 3. somebody else's invoice → no data (the row gate: it never matches) curl -s "$BASE/api/schema/erp/erp_mongo/erp_db/purchase_invoices/get-by-id/PI-2001" \ -H "x-am-authorization: $AM" -H "x-am-user-authorization: $USR" \ | jq '.data' # → null, same as an id that does not exist # 4. an API his groups never granted → 403 (the API gate) curl -s -o /dev/null -w '%{http_code}\n' -X DELETE \ "$BASE/api/schema/erp/erp_mongo/erp_db/purchase_invoices/PI-1042" \ -H "x-am-authorization: $AM" -H "x-am-user-authorization: $USR" # 5. a collection nobody granted → 403 curl -s -o /dev/null -w '%{http_code}\n' \ "$BASE/api/schema/erp/erp_mongo/erp_db/salaries" \ -H "x-am-authorization: $AM" -H "x-am-user-authorization: $USR" # 6. a field the group cannot read is absent, not null curl -s "$BASE/api/schema/erp/erp_mongo/erp_db/purchase_invoices/get-by-id/PI-1042" \ -H "x-am-authorization: $AM" -H "x-am-user-authorization: $USR" \ | jq '.data | has("landed_cost")' # → false # 7. a list call cannot be widened from the client curl -s "$BASE/api/schema/erp/erp_mongo/erp_db/purchase_invoices?find={created_by:{\$ne:null}}" \ -H "x-am-authorization: $AM" -H "x-am-user-authorization: $USR" \ | jq '[.data[].created_by] | unique' # → ["102"] ``` If any of them comes back wider than you expected, your grants are wider than you think they are. Three more things that help here: - Send `x-am-meta: true` and the response tells you which groups were consulted and whether each one granted access. - Every API user gets a Swagger document covering exactly the APIs that user can reach. Diff it in CI, and a group that quietly got wider shows up as a comment on a pull request rather than as an incident six months later. - **API Security › APIs Security Report & Actions** reads every group, auth provider and pre-hook of the account and flags most of the mistakes below: a collection no hook scopes, a spread merge, an owner taken from the body, `allCollections`, `allSystemApis`, a password in the token. The missing hook and the broad grants are fixed in one click, the others open the place to change. See [APIs Security Report & Actions](/v1/docs/apis-security/api-security-report.html). --- ## Putting it together | Question | Where it is answered | |---|---| | Which application is calling? | `x-am-authorization` — the API user | | Which person is acting? | `x-am-user-authorization` — a row in **your** table | | Which roles does that person hold? | The `groups` column on that row | | What may those roles call? | API group permission, in API Maker, authored once | | Which **rows** may they touch? | A database level pre-hook | | Which **fields** may they see? | Field level access on the group | What makes this hold up over time is that none of the enforcement lives in your endpoint code. A new collection is denied until some group grants it, and a new endpoint on an existing collection picks up the database hook the moment it exists. There is nothing you have to remember to write. --- ## Common mistakes | Mistake | What happens | Fix | |---|---|---| | One group per job title | Group count grows with the org chart; every new title is a fresh audit | One group per checkbox; job titles are comma-separated lists | | `allCollections: true` on an application group | Every table you add later is instantly reachable | Allow-list collections explicitly | | `allSystemApis: true` | Grants `EXECUTE_PLAIN_QUERY` and `GET_SECRET` | Never set it; allow-list individual system APIs if you truly need them | | Merging the owner with a spread instead of `$and` | A crafted `find` can widen the result set | `find = { $and: [caller, ours] }` | | Loading the row to compare owners on `get-by-id` | An extra database round-trip on every call, for a check the engine already does | Set `g.req.query.find`; API Maker ANDs it onto the primary key | | Omitting `_id` from `select` | The hook cannot see who is calling | Keep `_id` in `select` | | Checking group membership inside a hook | The same decision now lives in two places and will drift | Gate the hook with `groupNames` and let API Maker decide | | No `passwordChangedAtColumn` | The password, as stored in the table, sits in every JWT: anyone holding a token can read it | Set the column and update it on every password change | | Trusting `created_by` from the request body | A caller can create rows owned by someone else | Stamp the owner in the pre-hook | | Row scoping enforced in a Custom API only | Schedulers and test cases bypass authorization entirely | Enforce at the hook, and treat a scheduler as being as privileged as your database credentials | --- ## Related reading - [APIs Security Report & Actions](/v1/docs/apis-security/api-security-report.html) - [API group permission](/v1/docs/apis-security/api-group-permission.html) - [API user permission](/v1/docs/apis-security/api-user-permission.html) - [Authorization of AM database user](/v1/docs/authorization/AMDB.html) - [Pre-hook](/v1/docs/apis-all/hooks/preHook-api.html) · [Post-hook](/v1/docs/apis-all/hooks/postHook-api.html) - [Global object `g`](/v1/docs/pre-defined-terms/global-object-g.html) - [Default secret](/v1/docs/secrets/secrets.html#auth-token-info) - [Single sign on authentication](/v1/docs/features/single-sign-on-authentication.html) --- # APIs Security Report & Actions > Scan an API Maker account for data leaks (unscoped collections, wide groups, weak auth providers, public APIs), fix them with one click or skip them with a reason, and keep the report in git for people and AI assistants. Source: https://docs.apimaker.dev/v1/docs/apis-security/api-security-report.html **API Security › APIs Security Report & Actions** scans the account and tells you where a person could read or change data which is not theirs, then lets you fix it from the same page. ## Two gates, one report Every request carries two identities, and the report checks both : | Gate | Header | Question | What the report checks | |---|---|---|---| | API gate | `x-am-authorization` | May this application call this API ? | Groups with broad flags (`allInstances`, `allCollections`, `allSystemApis` ...), sensitive system APIs, bulk update / delete grants, unused groups | | Row gate | `x-am-user-authorization`, `x-aws-authorization`, `x-google-authorization`, `x-azure-authorization`, `x-custom-authorization` | Of the rows this API reaches, which ones belong to *this person* ? | For every collection the groups reach : whether an **active pre-hook** reads the person (`g.req.auth.authAMDB` ...) and limits **every family of APIs** (`query.find` for get-all and the *-by-id APIs, `body.find` for query, count, update-many, remove-by-query ...) to their rows, merges with `$and`, stamps the owner on writes and refuses aggregate / distinct | The row gate is the point of the feature. The generated APIs are powerful : with `find`, `deep`, `find-join`, `query`, `update-many` and `remove-by-query` a caller who is allowed on a collection reaches every row of it unless a pre-hook narrows the request. The report reads the code of your pre-hooks (instance, database, collection and API level) and decides, per collection and per API, whether that narrowing happens. It also reviews the **auth providers** (password in the token, unhashed passwords, no groups column, long lived tokens), **public APIs** (instance APIs and custom APIs with `IS_PUBLIC`) and a **platform switch** it can turn on for you : - **Block inline SQL in find** : values and keys which json-sql-builder2 would paste into the statement as-is (`__:` prefix, `__` helper) are bound as plain parameters on the SQL instances, so they match nothing instead of rewriting the statement. `deep` and `find-join` need no switch : the rows of the related collection are read through its own query API, with the group check of the caller, and only for the keys of the rows the caller could see. ## The score Every open finding takes points off 100 (critical 20, high 10, medium 4, low 1). Skipped and fixed findings take nothing. The grade (A to F) and the trend over the last scans are shown on the page and printed in the report. ## Actions Each finding offers what fixes it : an action when the fix can be automated, otherwise a link to the page where the change is made. Nothing is changed behind your back : an action runs when you click it, goes through the same routes as the admin panel (validation, TypeScript compile, git sync), and the account is scanned again right after so the report shows the result. | Action | What it does | |---|---| | Add a row scoping pre-hook | Installs a collection (or database) level pre-hook which merges `{ ownerColumn: personId }` into `query.find` / `body.find` with `$and`, stamps the owner on every write and refuses aggregate / distinct. You pick the owner column and the field of the token, can limit the hook to some groups of API users (the groups which reach the collection are listed first, with their API users), and can edit the code before it is saved. | | Add "column" to the token of a provider | Puts a column of the users table into the `select` of a DB token generator, so the token carries it and the hook can compare it. Persons log in again to get the new token. | | Require a person token on this collection | Writes collection settings naming the auth providers, so every API of the collection needs a person token next to the API user token. | | Activate the hook | Switches an inactive row scoping hook back on. | | Freeze to an explicit allow-list / turn the flag off | Narrows a broad group flag. Freeze writes what the group reaches today as explicit grants first, so nothing added later is reachable through it. | | Revoke grants | Takes sensitive system APIs or bulk APIs away from a group. | | Block inline SQL in find | The platform switch above. | | Skip | Marks a finding as acceptable, with a reason. It leaves the score, and the reason is kept in git. | A finding which suggests code shows it in a read only editor, and links such as "Open the collection" or "Open the group" open the page of the target in a new tab, so the report stays where it is. ## Automatic scans While a **local client** is connected to the account, API Maker scans it on its own and keeps the report fresh, on the disk of the developer and in git, so an AI assistant working in the folder of the project always reads the current report. - **The pace follows the account.** The next scan comes 100 times the duration of the last one later, never closer than 1 minute and never further than 30 minutes : a scan which takes 300 ms runs every minute, one which takes 20 s runs every 30 minutes. - **Nothing runs when nothing changed.** Between two scans, only a fingerprint of the inputs is read (one count and one date per collection of API Maker : groups, API users, auth providers, hooks, settings, schemas, instances, custom APIs, the secrets and the admin user). When it is the one of the last scan, no scan runs. With the live catalog on, a scan still runs every 30 minutes, for the tables added to a live database. - **Nothing is saved when the report did not change.** A scan which finds the same findings saves nothing, so the local client and git see nothing. A scan which finds a different report saves it, sends the folder to the local client and, when the account has a git repository, commits the folder `src/API Security Report/` alone (what you have not committed yet stays as it is), at most once every 5 minutes. - **Never in the way.** A scan runs in the process which holds the connection, one at a time per account, and waits when the server is busy answering requests. The panel **Automatic scans** of the page shows whether a local client is connected, what the last cycle did, when the next one is due at the earliest, and the last commit. Two switches, saved with the report, turn the automatic scans and the automatic commits off ; the play button runs one cycle right away. ## In the repository The report is committed with the rest of the project as the folder `src/API Security Report/` : | File | Content | |---|---| | `README.md` | What the folder is, for people and AI assistants : read only, work on the findings, rescan | | `report.md` | The full report : summary, scope, every finding with its evidence, the request which exploits it, what to do and the suggested code | | `report.yaml` | The same, machine readable | | `actions.yaml` | The skipped findings with their reasons, and the log of the actions taken from the page | API Maker only ever **writes** these files. The one thing it reads back is the `skipped` section of `actions.yaml`, on a git pull, so the decisions of the team follow the project. An AI assistant working on the repository can read `report.md`, fix the hooks, groups, settings and providers in their own folders, commit, and the automatic scan (or you, from the admin panel) rescans. ## Printing The download menu of the page prints the report (A4, with or without the skipped findings) through the print dialog of the browser, where "Save as PDF" makes the file, and downloads `report.md`, `report.yaml` or `report.json`. ## Related reading - [Handle role based permissions](/v1/docs/authorization/handle-role-based-permissions.html) - [API group permission](/v1/docs/apis-security/api-group-permission.html) - [Authorization of AM database user](/v1/docs/authorization/AMDB.html) - [Pre-hook](/v1/docs/apis-all/hooks/preHook-api.html) --- # Security Features > A security report of your account, encrypted payloads with a replay window, allowed origins, two-factor sign-in for the admin panel and npm package audits. Source: https://docs.apimaker.dev/v1/docs/features/security-features.html - Tokens and groups decide who can call which API, table and field. The features below add more layers on top of them. ## APIs Security Report & Actions - **API Security › APIs Security Report & Actions** scans the account and shows where a person could read or change rows that are not theirs: collections no pre-hook limits to the caller, groups that reach too far, weak auth providers and public APIs. - A leak comes with the request that proves it, and many findings come with an action that fixes them in one click, such as a row scoping pre-hook. Findings you accept are skipped with a reason. - The report gets a score out of 100 and is kept in git as `src/API Security Report/`, where an AI assistant working on the project can read it. - Learn more [APIs Security Report & Actions](/v1/docs/apis-security/api-security-report.html). ## Encrypted request payloads - Send the header `x-am-encrypted-payload: true` and put the encrypted payload in `dataEncFE`. - `dataEncFE` is `{ data, createdAt }` encrypted with `encryptionAlgorithmFETransfer` and `secretFETransfer` of the secret. Share that key with your frontend or mobile app. - A payload older than `feTransferDataValidityInSeconds` is refused, so a captured request can not be replayed later. - Turn on `acceptOnlyEncryptedData` and plain bodies and query strings are refused. It can be set for a database, a table, an API, and for custom, system and third party APIs. ```text linenums="1" POST /api/schema/admin/bank/main/transfers/save-single-or-multiple x-am-authorization: x-am-encrypted-payload: true { "dataEncFE": "U2FsdGVkX1+q3n..." } ``` ```typescript linenums="1" common: { encryptionAlgorithmFETransfer: 'AES', secretFETransfer: '...', feTransferDataValidityInSeconds: 300, // older payloads are refused }, ``` ## Encrypted responses - Send the header `x-am-get-encrypted-data` to get the response encrypted in `encryptedData`, with the same transfer key. | Value | Response | |---|---| | `no_encryption` | `data` only | | `get_only_encryption` | `encryptedData` only, `data` is null | | `get_data_and_encryption` | `data` and `encryptedData` | ## Allowed origins - Add the web origins of your apps in **Allowed Origins** on the Sandbox Settings page. - When the list has entries, a browser request from any other origin is refused with 403 before anything runs. - An empty list allows every origin. ## Two-factor sign-in - The root user can ask for a second factor when people sign in to the admin panel: a code from an authenticator app, a code sent by email, or both. - Recovery codes are shown once and stored hashed. - The root user also sets the code length and expiry, the attempts and the resend delay. - Email codes need the SMTP settings of the root user settings. ## Vulnerabilities - `API Security → Vulnerabilities` audits the packages of API Maker and the npm packages of your sandboxes for known vulnerabilities : a summary by severity (critical, high, moderate…), then each package with its range, whether it is a direct, prod, peer or optional dependency, and the details of every advisory. ## Encrypted and hashed fields - Schema conversions can store a field encrypted or as an HMAC SHA-256 hash, with the keys of your secret. ## Good to know - API Maker itself speaks plain HTTP. Run it behind Caddy or another proxy that serves HTTPS: the installer sets up Caddy. - Encrypted payloads do not replace HTTPS. They add a layer on top of it, useful when TLS ends before API Maker. --- # Single Sign-On Authentication > Accept Google, Azure AD and AWS Cognito tokens in API Maker and map each user to your groups, or write your own token provider. Source: https://docs.apimaker.dev/v1/docs/features/single-sign-on-authentication.html - Your app signs users in with Google, Azure AD or AWS Cognito and sends the token it gets to API Maker. - API Maker checks the token with the keys of the provider, finds the user in a table of yours, and runs the call with the groups of that user. - Send the token in its header: `x-google-authorization`, `x-azure-authorization` or `x-aws-authorization`. - Read the opened token in your code from `g.req.auth.authGoogle`, `g.req.auth.authAzure` or `g.req.auth.authAWS`. - For any other system, write a custom provider: a token generator and a token validator in TypeScript. Send its token in `x-custom-authorization` and read the result in `g.req.auth.authCustom`. - Learn more [Google](/v1/docs/authorization/Google.html), [Azure AD](/v1/docs/authorization/Azure.html), [AWS Cognito](/v1/docs/authorization/AWS.html) and [custom auth provider](/v1/examples/req/auth/authCustom.html). --- # Auth of an API User > The first gate of every call - the token of an API user in x-am-authorization, how to get and refresh it, and what g.req.auth.authAMUser gives your code. Source: https://docs.apimaker.dev/v1/docs/authorization/AMApiUser.html Every non-public API needs the token of an [API user](/v1/docs/apis-security/api-user-permission.html) : the application calling. It is the first of the [two gates](/v1/docs/getting-started/how-it-works.html#the-two-gates). | | | |---|---| | Header | `x-am-authorization: ` | | Get it | `POST /api/system-api//token` with `{ "u", "p", "expiresInSeconds"? }` | | Answer | `{ "token", "refresh_token", "expires_in" }` | | In code | `g.req.auth.authAMUser` : the API user, with its name and groups. | ```json title="Get the token" { "u": "default", "p": "12345", "expiresInSeconds": 259200 } ``` ```typescript title="Who is calling" const apiUser = g.req.auth.authAMUser; g.logger.log(apiUser.name, apiUser.groups); ``` - `expiresInSeconds` is optional : `jwtOptions.expiresIn` of the [configuration](/v1/docs/am-resources/api-maker-configurations.html) otherwise, 72 hours by default. Refresh with `{ "refresh_token" }` while the refresh token is valid (`refreshTokenValidForS` after the token expired, 900 seconds by default). - The groups of the API user decide which APIs, tables and fields the application reaches. A person token, when the API asks for one, narrows it further. ## Related - [Get token API](/v1/docs/apis-all/system-apis/system-generated-token-api.html) · [Auth of a database user](/v1/docs/authorization/AMDB.html) · [Handle role based permissions](/v1/docs/authorization/handle-role-based-permissions.html) --- # Auth of a Database User > Sign the people of your own users table in with API Maker - a DB auth provider names the table and its columns, the token API gives the person a token, x-am-user-authorization carries it, and the groups column decides what the person may call. Source: https://docs.apimaker.dev/v1/docs/authorization/AMDB.html Your users are rows of a table of yours. An **auth provider** of type DB tells API Maker where : the table, the username column, the password column, the groups column. From then on the [token API](/v1/docs/apis-all/system-apis/system-generated-token-api.html) signs those people in, and every call carries their token in `x-am-user-authorization`. > Diagram : Two tokens, two gates : the API user (which application) and the person (which rows) | | | |---|---| | Page | `API Security → Auth Providers` : one provider per users table. Types : DB, AWS Cognito, Azure AD, Google, Custom. | | Header | `x-am-user-authorization: ` | | Get it | `POST /api/system-api//token` with `{ "name": "", "u", "p" }` | | Require it | `authProviders: ['']` in the settings of an API, a table, a database, or in `common.authProviders` of the secret. | | In code | `g.req.auth.authAMDB` : the row of the person, without its password. | ## 1. Declare the provider ```typescript title="Basic Info of the provider" linenums="1" import * as T from 'types'; let dbTokenGenerator: T.IAuthTokenAMDB = { name: 'users_tg', instance: 'mongodb', database: 'shop', collection: 'users', // table: '…' for SQL usernameColumn: 'email', passwordColumn: 'password', // a hashed column works : the password sent is hashed and compared passwordChangedAtColumn: 'passwordChangedAt', // optional : the token carries this value instead of the password groupsColumn: 'groups', // comma separated group names ; '*' = every group of the API user expiresInSeconds: 259200, runOnNativeProcess: false, // where the Fields Generator runs }; module.exports = dbTokenGenerator; ``` - `select` limits the columns of the person put in `g.req.auth.authAMDB` ; `condition` adds a filter to the lookup (`{ active: true }`). - `passwordChangedAtColumn` : update it whenever a password changes, and every token made before stops working, refresh tokens included. ## 2. Require it on the APIs ```typescript title="Settings of an API, a table or a database" let settings: T.IInstanceApiSettingsTypes = { apiAccessType: T.EAPIAccessType.TOKEN_ACCESS, authProviders: ['users_tg'], }; module.exports = settings; ``` - Without `authProviders` in any settings, the APIs need what `common.authProviders` of the [secret](/v1/docs/secrets/secrets.html) names ; nothing there means the API user token alone. - Several DB providers in the list : a token of any of them works. A provider of another type in the list (Google…) adds its own header to what the call must carry. - A call without the token answers `401` : `Please provide 'x-am-user-authorization' token in request headers.` A token whose person is not in the table any more : `401`, `Token user not found in 'mongodb' -> 'shop' -> 'users'.` ## 3. Sign in ```json title="POST /api/system-api/admin/token" { "name": "users_tg", "u": "alice@acme.com", "p": "PASSWORD" } ``` ```json title="Answer" { "success": true, "statusCode": 200, "data": { "token": "eyJ…", "refresh_token": "eyJ…", "expires_in": 259200 } } ``` - The app sends the token in `x-am-user-authorization`, next to `x-am-authorization` of the API user. The sample custom API `/default/login` of a new account gets both in one call. - The person may call an API when a group of theirs grants it **and** a group of the API user grants it. ## The Fields Generator - The **Fields Generator** tab of a DB provider is a function which returns extra fields to put in the token, from the row of the person (`g.req.body`) : a display name, a department… They come back in `g.req.auth.authAMDB` on every call without a lookup. - It runs in the sandbox, or on the native process with `runOnNativeProcess: true` (a **Native** chip on the list ; use `g.logger` there, not `console.log`). ## Custom providers - A **Custom** provider is your own logic : a **token generator** function which answers the token API for `{ "name": "", … }` with whatever it returns, and a **token validator** function which receives `g.req.body.token` and returns a truthy value (or an object) when the token is valid. Calls send that token in `x-custom-authorization` and your code reads the result in `g.req.auth.authCustom`. [Example](/v1/examples/req/auth/authCustom.html). ## Related - [Handle role based permissions](/v1/docs/authorization/handle-role-based-permissions.html) · [API group permissions](/v1/docs/apis-security/api-group-permission.html) · [Multi-tenant users](/v1/docs/features/multi-tenant.html#users-of-a-tenant) · [Custom API example](/v1/examples/req/auth/authAMDB.html) --- # Auth with AWS Cognito > Accept the tokens of an AWS Cognito user pool in API Maker - an AWS auth provider with the pool id, the region and the token use, the person found in your table, and the token sent in x-aws-authorization. Source: https://docs.apimaker.dev/v1/docs/authorization/AWS.html Your app signs people in with Cognito and sends the token it gets to API Maker. An auth provider of type **AWS** checks the token with the keys of the user pool, finds the person in a table of yours, and runs the call with the groups of that person. | | | |---|---| | Page | `API Security → Auth Providers` → type AWS. | | Header | `x-aws-authorization: ` | | In code | `g.req.auth.authAWS` : the opened token, and the columns selected from your table. | ```typescript title="The provider" linenums="1" import * as T from 'types'; let awsTokenGenerator: T.IAuthTokenAWS & { name: string; testObj?: any; } = { name: 'aws_token_generator', cognitoUserPoolId: 'us-east-1_xxxxxx', region: 'us-east-1', tokenUse: 'access', // 'access' | 'id' : which Cognito token the app sends tokenExpiration: 3600000, // milliseconds, at most 3600000 sourceFieldOfUniqueId: 'sub', // the claim which identifies the person groupsDataSource: { instance: 'mongodb', database: 'shop', collection: 'users', targetFieldForUniqueId: 'cognito_sub', groupsColumn: 'groups', select: { name: 1, email: 1 } }, }; module.exports = awsTokenGenerator; ``` ## The groups of the person - `sourceFieldOfUniqueId` is the claim of the token which identifies the person (an email, `sub`, `oid`). - `groupsDataSource` names the table of yours which holds that person : `targetFieldForUniqueId` is the column with the same value, `groupsColumn` the comma separated [groups](/v1/docs/apis-security/api-group-permission.html) of the person, `select` the columns to put in `g.req.auth`. - Without `groupsDataSource`, the token is checked but the person gets no group of its own : only the API user decides. ## Require it - `authProviders: ['aws_token_generator']` in the settings of the APIs, tables or databases, or in `common.authProviders` of the secret. - The call carries the API user token in `x-am-authorization` and the token of the provider in ``x-aws-authorization``. - `testObj` in the provider is the person the API testing page pretends to be. ## Related - [Single sign-on](/v1/docs/features/single-sign-on-authentication.html) · [Auth of a database user](/v1/docs/authorization/AMDB.html) · [API group permissions](/v1/docs/apis-security/api-group-permission.html) --- # Auth with Azure AD > Accept the tokens of Microsoft Entra ID (Azure AD) in API Maker - an Azure auth provider with the app id, the tenant, audience and issuer, the person found in your table, and the token sent in x-azure-authorization. Source: https://docs.apimaker.dev/v1/docs/authorization/Azure.html Your app signs people in with Microsoft Entra ID (Azure AD) and sends the token to API Maker. An auth provider of type **Azure** checks it against your application registration, finds the person in a table of yours, and runs the call with the groups of that person. | | | |---|---| | Page | `API Security → Auth Providers` → type Azure. | | Header | `x-azure-authorization: ` | | In code | `g.req.auth.authAzure` : the opened token, and the columns selected from your table. | ```typescript title="The provider" linenums="1" import * as T from 'types'; let azureTokenGenerator: T.IAuthTokenAzureAD & { name: string; testObj?: any; } = { name: 'azure_token_generator', appId: '', tenant: '', audience: '', // when your tokens carry another audience issuer: '', // when your tokens carry another issuer maxRetries: 3, // fetching the signing keys sourceFieldOfUniqueId: 'oid', // or 'preferred_username' for the email groupsDataSource: { instance: 'mongodb', database: 'shop', collection: 'users', targetFieldForUniqueId: 'azure_oid', groupsColumn: 'groups', select: { name: 1, email: 1 } }, }; module.exports = azureTokenGenerator; ``` ## The groups of the person - `sourceFieldOfUniqueId` is the claim of the token which identifies the person (an email, `sub`, `oid`). - `groupsDataSource` names the table of yours which holds that person : `targetFieldForUniqueId` is the column with the same value, `groupsColumn` the comma separated [groups](/v1/docs/apis-security/api-group-permission.html) of the person, `select` the columns to put in `g.req.auth`. - Without `groupsDataSource`, the token is checked but the person gets no group of its own : only the API user decides. ## Require it - `authProviders: ['azure_token_generator']` in the settings of the APIs, tables or databases, or in `common.authProviders` of the secret. - The call carries the API user token in `x-am-authorization` and the token of the provider in ``x-azure-authorization``. - `testObj` in the provider is the person the API testing page pretends to be. ## Related - [Single sign-on](/v1/docs/features/single-sign-on-authentication.html) · [Auth of a database user](/v1/docs/authorization/AMDB.html) · [API group permissions](/v1/docs/apis-security/api-group-permission.html) --- # Auth with Google > Accept Google sign-in tokens in API Maker - a Google auth provider with the OAuth client id, the person found in your table, and the token sent in x-google-authorization. Source: https://docs.apimaker.dev/v1/docs/authorization/Google.html Your app signs people in with Google and sends the id token to API Maker. An auth provider of type **Google** checks it against your OAuth client, finds the person in a table of yours, and runs the call with the groups of that person. | | | |---|---| | Page | `API Security → Auth Providers` → type Google. | | Header | `x-google-authorization: ` | | In code | `g.req.auth.authGoogle` : the opened token, and the columns selected from your table. | ```typescript title="The provider" linenums="1" import * as T from 'types'; let googleTokenGenerator: T.IAuthTokenGoogle & { name: string; testObj?: any; } = { name: 'google_token_generator', clientId: '.apps.googleusercontent.com', sourceFieldOfUniqueId: 'sub', // or 'email' groupsDataSource: { instance: 'mongodb', database: 'shop', collection: 'users', targetFieldForUniqueId: 'google_sub', groupsColumn: 'groups', select: { name: 1, email: 1 } }, }; module.exports = googleTokenGenerator; ``` ## The groups of the person - `sourceFieldOfUniqueId` is the claim of the token which identifies the person (an email, `sub`, `oid`). - `groupsDataSource` names the table of yours which holds that person : `targetFieldForUniqueId` is the column with the same value, `groupsColumn` the comma separated [groups](/v1/docs/apis-security/api-group-permission.html) of the person, `select` the columns to put in `g.req.auth`. - Without `groupsDataSource`, the token is checked but the person gets no group of its own : only the API user decides. ## Require it - `authProviders: ['google_token_generator']` in the settings of the APIs, tables or databases, or in `common.authProviders` of the secret. - The call carries the API user token in `x-am-authorization` and the token of the provider in ``x-google-authorization``. - `testObj` in the provider is the person the API testing page pretends to be. ## Related - [Single sign-on](/v1/docs/features/single-sign-on-authentication.html) · [Auth of a database user](/v1/docs/authorization/AMDB.html) · [API group permissions](/v1/docs/apis-security/api-group-permission.html) --- # Database Settings > Settings which apply to every table of a database in API Maker - access type, auth providers, caching, encrypted payloads - inherited by the tables and the APIs which do not override them. Source: https://docs.apimaker.dev/v1/docs/settings/databaseSettings.html The settings of a database apply to every table of it, and to every API of those tables, unless a table or an API sets the same key itself. | | | |---|---| | Page | `API Info → DB API` → select the database → **Add Database Settings**. | | Type | `T.IInstanceApiSettingsTypes` | | Applies to | The APIs of every table of the database. | ```typescript title="Database settings" linenums="1" import * as T from 'types'; // Remove properties which you want to apply from parent level let instanceColSetting: T.IInstanceApiSettingsTypes = { enableCaching: false, acceptOnlyEncryptedData: false, apiAccessType: T.EAPIAccessType.TOKEN_ACCESS, // authProviders: ['users_tg'], // absent : common.authProviders of the secret ; [] : API user token only }; module.exports = instanceColSetting; ``` ## The keys | Key | Values | Meaning | |---|---|---| | `apiAccessType` | `TOKEN_ACCESS` (default), `IS_PUBLIC`, `NO_ACCESS` | Who may call : with the token of an API user, anybody, or only your own code and the admin panel. | | `authProviders` | names of [auth providers](/v1/docs/authorization/AMDB.html) | The person tokens the call must carry. Absent : `common.authProviders` of the secret. `[]` : the API user token alone. | | `acceptOnlyEncryptedData` | `true` / `false` | Refuse plain bodies and query strings : see [encrypted payloads](/v1/docs/features/security-features.html#encrypted-request-payloads). | | `enableCaching` | `true` / `false` | Cache the answers of the reads in Redis until a write of the table : see [automatic caching](/v1/docs/features/automatic-caching.html). Table and database level only. | - A setting is used when it is present : leave a key out to inherit it. The order is **API settings → table settings → database settings → secret → default**. ## Related - [Collection settings](/v1/docs/settings/collectionSettings.html) · [API settings](/v1/docs/settings/apiSettings.html) · [Auth of a database user](/v1/docs/authorization/AMDB.html) --- # Collection Settings > Settings of one table or collection in API Maker - access type, auth providers, caching, encrypted payloads - over the settings of its database, under the settings of its APIs. Source: https://docs.apimaker.dev/v1/docs/settings/collectionSettings.html The settings of a table apply to every API of the table. They override the settings of the database, and an API can override them in turn. | | | |---|---| | Page | `API Info → DB API` → select the table → **Add Collection Settings**. | | Type | `T.IInstanceApiSettingsTypes` | | Applies to | The APIs of this table. | ```typescript title="Collection settings" linenums="1" import * as T from 'types'; // Remove properties which you want to apply from parent level let instanceColSetting: T.IInstanceApiSettingsTypes = { enableCaching: true, // the reads of this table are cached until a write acceptOnlyEncryptedData: false, apiAccessType: T.EAPIAccessType.TOKEN_ACCESS, authProviders: ['users_tg'], // every API of the table needs a person of users_tg }; module.exports = instanceColSetting; ``` ## The keys | Key | Values | Meaning | |---|---|---| | `apiAccessType` | `TOKEN_ACCESS` (default), `IS_PUBLIC`, `NO_ACCESS` | Who may call : with the token of an API user, anybody, or only your own code and the admin panel. | | `authProviders` | names of [auth providers](/v1/docs/authorization/AMDB.html) | The person tokens the call must carry. Absent : `common.authProviders` of the secret. `[]` : the API user token alone. | | `acceptOnlyEncryptedData` | `true` / `false` | Refuse plain bodies and query strings : see [encrypted payloads](/v1/docs/features/security-features.html#encrypted-request-payloads). | | `enableCaching` | `true` / `false` | Cache the answers of the reads in Redis until a write of the table : see [automatic caching](/v1/docs/features/automatic-caching.html). Table and database level only. | - A setting is used when it is present : leave a key out to inherit it. The order is **API settings → table settings → database settings → secret → default**. ## Related - [Database settings](/v1/docs/settings/databaseSettings.html) · [API settings](/v1/docs/settings/apiSettings.html) · [Automatic caching](/v1/docs/features/automatic-caching.html) --- # API Settings > Settings of one generated or schema API of a table in API Maker - access type, auth providers, encrypted payloads - the most specific level, over the table and the database. Source: https://docs.apimaker.dev/v1/docs/settings/apiSettings.html The settings of one API of a table, for example the get all of `products` : the most specific level. A key set here wins over the table and the database. | | | |---|---| | Page | `API Info → DB API` → select the table → the API → **Add API Settings**. | | Type | `T.IInstanceApiSettingsTypesForAPI` | | Applies to | This API only. Caching is not set here : it belongs to the table or the database. | ```typescript title="Make one read public, keep the writes behind tokens" linenums="1" import * as T from 'types'; // Remove properties which you want to apply from parent level let instanceColSetting: T.IInstanceApiSettingsTypesForAPI = { acceptOnlyEncryptedData: false, apiAccessType: T.EAPIAccessType.IS_PUBLIC, }; module.exports = instanceColSetting; ``` ## The keys | Key | Values | Meaning | |---|---|---| | `apiAccessType` | `TOKEN_ACCESS` (default), `IS_PUBLIC`, `NO_ACCESS` | Who may call : with the token of an API user, anybody, or only your own code and the admin panel. | | `authProviders` | names of [auth providers](/v1/docs/authorization/AMDB.html) | The person tokens the call must carry. Absent : `common.authProviders` of the secret. `[]` : the API user token alone. | | `acceptOnlyEncryptedData` | `true` / `false` | Refuse plain bodies and query strings : see [encrypted payloads](/v1/docs/features/security-features.html#encrypted-request-payloads). | - A setting is used when it is present : leave a key out to inherit it. The order is **API settings → table settings → database settings → secret → default**. - `NO_ACCESS` hides an API from HTTP while your custom APIs keep using it through `g.sys.db` : a way to expose a narrow custom API instead of the raw table. ## Related - [Collection settings](/v1/docs/settings/collectionSettings.html) · [Database settings](/v1/docs/settings/databaseSettings.html) · [Security features](/v1/docs/features/security-features.html) --- # System API Settings > Settings of one system API of API Maker - open it over HTTP with TOKEN_ACCESS or IS_PUBLIC, require person tokens with authProviders, cache its answers, refuse plain payloads. Source: https://docs.apimaker.dev/v1/docs/settings/systemApiSettings.html Each [system API](/v1/docs/apis-all/overview.html#system-apis) has its own settings. Without settings, a system API is `NO_ACCESS` : your code calls it through `g.sys`, the admin panel tests it, and an HTTP call is refused with `401`. The token API is the exception : public unless its settings say otherwise. | | | |---|---| | Page | `API Info → System API` → the plus button of the API. | | Type | `T.ISystemApiSettingsTypes` | | Default | `NO_ACCESS` for every system API but the token API. | ```typescript title="Open the hash data API to the applications" linenums="1" import * as T from 'types'; let systemApi: T.ISystemApiSettingsTypes = { enableCaching: false, acceptOnlyEncryptedData: false, apiAccessType: T.EAPIAccessType.TOKEN_ACCESS, // authProviders: ['users_tg'], }; module.exports = systemApi; ``` ## The keys | Key | Values | Meaning | |---|---|---| | `apiAccessType` | `NO_ACCESS` (default), `TOKEN_ACCESS`, `IS_PUBLIC` | Who may call it over HTTP : nobody, an API user whose group grants the system API, anybody. | | `authProviders` | names of [auth providers](/v1/docs/authorization/AMDB.html) | The person tokens the call must carry. Absent : `common.authProviders` of the secret. `[]` : the API user token alone. | | `enableCaching` | `true` / `false` | Cache the answers in Redis. Reset them with [reset system API cache](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-system-api.html). | | `acceptOnlyEncryptedData` | `true` / `false` | Refuse plain bodies : [encrypted payloads](/v1/docs/features/security-features.html#encrypted-request-payloads). | - Open only what applications really need : execute plain query, get secret, create indexes and the cache resets change or reveal a lot. The [APIs Security Report](/v1/docs/apis-security/api-security-report.html) lists the sensitive ones which are open. ## Related - [All system APIs](/v1/docs/apis-all/overview.html#system-apis) · [System APIs from code](/v1/examples/sys/system/system.html) --- # Third Party API Settings (deprecated) > Settings of an installed third party API or of a whole bundle version in API Maker - access type, auth providers, caching with reset rules, encrypted payloads. Third party APIs are deprecated. Source: https://docs.apimaker.dev/v1/docs/settings/thirdPartyApiSettings.html !!! deprecated "Deprecated : removed in API Maker v4" Third party APIs and the store are deprecated. See [Third party APIs](/v1/docs/apis-all/thirdParty-apis/installed-third-party-api.html). Two levels : the settings of a **version** of a bundle (with caching) and the settings of **one API** of it. | | | |---|---| | Page | `API Info → Third Party API` (under Deprecated Features) → the version or the API → **Add Third party API settings**. | | Types | `T.ITPApiSettingsTypes` for a version, `T.ITPApiSettingsTypesAPILevel` for one API. | ```typescript title="Settings of a version" linenums="1" import * as T from 'types'; let thirdPartyApiSetting: T.ITPApiSettingsTypes = { enableCaching: false, resetCacheOnModificationOf: [ // 'DB:instance_name:database_name:collection', // a write to this table resets the cache // 'TP:api_bundle_name:api_version', // a data changing API of this version resets it // 'CA:custom_api_name', // a call of this custom API resets it ], apiAccessType: T.EAPIAccessType.TOKEN_ACCESS, acceptOnlyEncryptedData: false, // authProviders: ['users_tg'], }; module.exports = thirdPartyApiSetting; ``` - The keys mean the same as in the [custom API settings](/v1/docs/settings/customApiSettings.html). The API level settings have `apiAccessType`, `acceptOnlyEncryptedData` and `authProviders`. --- # Sandbox Settings > The sandbox of an API Maker account - how many processes run your code, the npm packages they get, the Docker image they run in, the origins allowed to call the APIs from a browser, and the automatic restart. Source: https://docs.apimaker.dev/v1/docs/settings/sandboxSettings.html Your code runs in sandbox processes : containers built for the account, with the npm packages you choose. This page shapes them. | | | |---|---| | Page | `Utility → Sandbox Settings` | | Sandbox count | How many sandbox processes run at once. Each one runs the [process initializers](/v1/docs/features/process-initializers.html). | | Dependencies | The npm packages available to every custom API, hook, event, scheduler and utility class, with their versions and sub dependencies. | | Docker file | The image of the sandbox : a base file, an extended base and the final file, each editable and resettable, with the build logs and the final `package.json`. | | Allowed origins | The web origins allowed to call the APIs from a browser : a name and a domain per row. Empty : every origin. | | Auto restart | Restart the sandboxes every N seconds, for code which leaks. | ## Dependencies 1. Add a package with its name and version. The build installs it and lists its sub dependencies. 2. `import * as dayjs from 'dayjs'` in your code, like any npm package. 3. A package which needs a system library goes in the Docker file : install Python, ImageMagick or a client library there. - Private registries are not supported. - The schema generation runs once, without your packages : keep computations out of it. ## Allowed origins - With one origin listed, a browser request from any other origin is refused with `403` before anything runs. Add the origins of your web apps, and keep the list empty while developing locally if you prefer. ## Related - [Process initializers](/v1/docs/features/process-initializers.html) · [Security features](/v1/docs/features/security-features.html#allowed-origins) · [Custom APIs](/v1/docs/apis-all/custom-apis/user-created-custom-api.html) --- # API Maker Configuration > Every setting of an API Maker server - ports, databases and Redis of API Maker itself, JWT and token validity, sandbox, logs, limits - in the am section of package.json, overridable from a .env file with the am__ prefix. Source: https://docs.apimaker.dev/v1/docs/am-resources/api-maker-configurations.html The server reads its settings from the `am` section of its `package.json`, and a `.env` file overrides any of them. | | | |---|---| | Override | `am__` in `.env` : `am__port=38246`. A nested key joins with `__` : `am__jwtOptions__issuer=AM_JWT`. | | Where is `.env` | Next to the install, or `node main.js --envPath=/root/config/.env`. The [installer](/v1/docs/getting-started/install-on-server.html) writes the file for you. | | See them | `Dashboard → Server Node Configurations` shows the values every server runs with. | ## The keys
serverName It will be seen in nodes summary dashboard, so you can easily identify different servers. ```json "Default" : "server1" ```
processTitle This will be title of API Maker process when it starts. ```json "Default" : "api_maker_be" ```
cpuCount 1 = default. One API Maker process will be created.
AUTO = If you give this value, it will create N number of processes where N = CPU core.
2 = It will create 2 API Maker processes. ```json "Default" : "1" ```
port API Maker will start on this port. ```json "Default" : "38246" ```
wsPort API Maker will start WebSocket connection on this port. So frontend or mobile app can connect to this port for WebSocket notifications. ```json "Default" : "38245" ```
mongo_db_connection Database connection string of API Maker`s internal use.
logs
enableLogs If true, logging is enable. ```json "Default" : "true" ```
mongo_db_connection_logs Logging database connection string. API Maker will store user's logs in this database.
logRemoveSchedulerInterval API Maker will start log removal scheduler based on this value.
Default is every day. ```json "Default" : "0 0 0 * * *" ```
maxLogsCount Maximum amount of logs to keep in log database.
API Maker will run scheduler based on `logRemoveSchedulerInterval` and remove older logs. ```json "Default" : "100000" ```
redisInternal
Used by API Maker for it's internal use.
It stores user's auto increment values in this redis.
Clustering supported.
nodes List of redis nodes.
port Internal redis port. ```json "Default" : "6379" ```
host Internal redis host. ```json "Default" : "127.0.0.1" ```
pass Internal redis password.
maxCharsToStoreInConsoleLogs Console logs in custom code will allow these many characters to be stored for each admin & developer user, it will be cleared after that.
AM version required >= 1.15.0
redisExternal
Used to give caching support for user's APIs.
Clustering supported.
nodes List of redis nodes.
port External redis port. ```json "Default" : "6390" ```
host External redis host. ```json "Default" : "127.0.0.1" ```
pass External redis password.
otherRedisClusterOptions Other redis cluster related options supported by ioredis NPM Package.
redisValueExpireInSeconds Every key set by user in redis will expire automatically after this many seconds.
It will keep redis clean.
Ex : If you set value 29 over here, it means every key in redis expires after 29 seconds or TTL given by user while setting the key. ```json "Default" : "7200" ```
maxCharsResToCache 1000000 = If value has more than 1000000 characters, it will not be stored in redis.
We should not store huge strings in redis. ```json "Default" : "1000000" ```
jwtOptions [For API Maker users sessions only]
expiresIn We can specify Token expire time limit from here, it should be always number in seconds. ```json "Default" : "259200" // 72 hours ``` For your application's JWT token settings, Please log in into root user settings.
issuer Specify the API Maker issuer of JWT generator. ```json "Default" : "API Maker JWT Generator" ```
sandbox
imageName: { sandboxNode : } ```json "Default": "am/sandbox-node, nodejs sandbox image name" ```
sandboxReqTimeout Defines the maximum milliSecond allowed to complete the execution process. ```json "Default" : "13000" ```
sandboxCountForAdmin Process will create these many sandbox environment containers to handle multiple requests. ```json "Default" : "2" ```
sandboxMaxOldSpaceMB Default is 2500 or (total_memory / cpu_count) MB whichever is max.
AM version required >= 1.10.0
removeSandboxInactiveSinceMinutes Remove sandbox of admins which is not used since this minutes to free resources. For that scheduler will run every (removeSandboxInactiveCron = 10) minutes to check these minutes. ```json "Default" : "5" ``` (Note : We can give like 0.25 float value also)
defaultCreation
apiUserPass Here, we can set password for the default API User. ```json "Default" : "12345" ```
passCommunication Used for communication between API Maker frontend and API Maker backend.
It is not used for user data transmission.
It's value should be same in all servers of environment(dev|qa|uat|prod) of project.
passJWT Used for generating jwt token.
It's value should be same for all environments of that project in all server deployment.
passDBEncryptDecrypt Values in API Maker mongoDB database and passwords in git repository, will be encrypted using this password.
It's value should be same for all environments of that project in all server deployment.
If value is not same, API Maker will not able to decrypt git repository values.
feTransferDataValidityInSecondsDefault Encrypted data sent by frontend or mobile apps, will be expired after this many seconds. ```json "Default" : "15" ```
refreshTokenValidForS Refresh token will be valid after these many seconds of expiring access token. ```json "Default" : "900" ```
importDefaultUsers API Maker will insert default users from this JSON file when these emails are not present in API Maker's MongoDB. ```json "Default" : "src/json/DefaultUsers.json" ```
compressThreshold If response is greater than 51200 characters then it will be compressed otherwise not. ```json "Defaults" : "51200" ```
bodyLimit Defines the maximum payload in bytes, the server is allowed to accept. ```json "Default" : "10484711424" (9999MiB) Google: Byte -> Mebibyte(MiB) ```
cron_job_time_zone API Maker's internal schedulers will run on this time zone. ```json "Default" : "Asia/Kolkata" ```
store_url API Maker will install APIs from store from below URL. ```json "Default" : "https://store.be.apimaker.dev" ```
maximum_test_user_count Fetch maximum these many test usernames from table if count is not provided in schema ```json "Default" : "1000" ```
uploadedFileCleaningSchedulerCron Scheduler will run every this much time and clean files which are older than uploadedFileRemoveOlderThanThisTimeInSeconds.
Default : 30 minutes ```json "Default" : "0 */30 * * * *" ```
uploadedFileRemoveOlderThanThisTimeInSeconds Remove uploaded files older than time specified in second at this field.
Default : 1800 seconds (30 Minutes) ```json "Default" : "1800" ```
other keys
BE_HOST_PORT, BE_WS_HOST_PORTThe public URL of the API and of the WebSocket server, used in the URLs API Maker prints and in the links of the admin panel. The installer sets them.
envPath, logFilePath, oracleClientPathWhere the .env file is, where the process log is written, where the Oracle Instant Client is installed.
dockerHow to reach the Docker daemon which runs the sandboxes : socketPath, or protocol, host and port.
debugging.portStart, debugging.portEndThe ports the debugger of the sandboxes may use.
db.common.cleanPoolInMinutesIdle database connection pools are closed after this many minutes.
db.mongo.max_rows_returnThe number of rows a get all or a query returns when the request gives no limit.
## Related - [Install API Maker](/v1/docs/getting-started/install-on-server.html) · [Node dashboard](/v1/docs/dashboard/node-dashboard.html) · [Deploy API Maker](/v1/docs/features/deploy-api-maker.html) --- # Automatic Caching > Turn caching on for a table, a database, a custom API or a system API and API Maker answers the reads from Redis, drops the cached answers on every write through it, and lets you reset them from code or per request. Source: https://docs.apimaker.dev/v1/docs/features/automatic-caching.html Turn caching on and the reads of a table are answered from Redis, for every server of the cluster. API Maker knows when the table changes, because the writes go through it too, so it drops the cached answers itself : no stale reads, no code. > Diagram : Automatic caching : reads served from Redis, writes drop the cached answers | | | |---|---| | Turn on | `enableCaching: true` in the [collection settings](/v1/docs/settings/collectionSettings.html) or the [database settings](/v1/docs/settings/databaseSettings.html) ; in the settings of a [custom API](/v1/docs/settings/customApiSettings.html) or a [system API](/v1/docs/settings/systemApiSettings.html). | | Key | The URL, the query, the body and the tokens of the request : two callers with different rights never share an answer, tenants never share one. | | Lifetime | Until a write of the table, or `redisValueExpireInSeconds` (7200 by default) of the [configuration](/v1/docs/am-resources/api-maker-configurations.html). Answers longer than `maxCharsResToCache` are not cached. | | Reset | A write through any API of the table ; the [reset cache system APIs](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-db-api.html) ; the header `x-am-cache-control: reset_cache` on one request. | | See it | The [Redis dashboard](/v1/docs/dashboard/redis-dashboard.html) lists the keys ; the API testing page says whether an answer came from the cache. | ## Tables - Set it on the table, or on the database for all its tables. The setting is not per API : every read of the table is cached, every write of the table resets. - Data changed outside API Maker is not seen : reset the table with [reset database cache](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-db-api.html) from the job which changed it, or send `x-am-cache-control: reset_cache` with the next read. - Cached answers run no hook : a pre hook which scopes rows to the caller still holds, because the caller is part of the key. ## Custom APIs ```typescript title="Basic Info" enableCaching: true, resetCacheOnModificationOf: [ 'DB:mongodb:shop:products', 'DB:mongodb:shop:categories' ], ``` - The answer of the API is cached per body, query and caller. `resetCacheOnModificationOf` names the tables (`DB:instance:database:table`), custom APIs (`CA:name`) and bundle versions (`TP:bundle:version`) whose writes drop it. - Without the list, reset it yourself with [reset custom API cache](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-custom-api.html). ## System APIs - `enableCaching: true` in the settings of the system API ; reset with [reset system API cache](/v1/docs/apis-all/system-apis/system-generated-reset-redis-cache-system-api.html). ## Per request | Header | Effect | |---|---| | `x-am-cache-control: no_action` | Answer without reading or writing the cache. | | `x-am-cache-control: reset_cache` | Drop the cached answers of the API, then answer from the database and cache that. | ## Related - [Request headers](/v1/docs/apis-all/header/requestHeader.html#x-am-cache-control) · [Redis dashboard](/v1/docs/dashboard/redis-dashboard.html) · [Multi-tenant caching](/v1/docs/features/multi-tenant.html#caching) --- # Index Maker > Record the database queries of your APIs during a scanning period, see which ones use no index with explain, and create the missing indexes. Source: https://docs.apimaker.dev/v1/extensions/index_maker/introduction.html - Index Maker records the database queries of your APIs, groups them by the shape of their condition, and asks your database with explain whether each one uses an index. - It is in the **Index Maker** menu of API Maker + Extensions. - Since v3.3.0 it can do all of it on its own : the [automatic mode](automatic_indexes.md) records at regular intervals, creates the indexes the slow or frequent queries of big tables need, and writes them into the migration script `am_index_maker` so every environment gets them on deploy. ## 1. Start a scanning period - Give it a name, a start and an end. - Pick what to watch: the database APIs of every instance, or of some instances, databases and tables, and system APIs. - Turn on internal queries to also record the queries API Maker runs on its own while serving a call, like deep populate and unique checks. ## 2. Use your app - While the period runs, every watched call that succeeded and did not come from the cache is recorded. - Calls are grouped by API, table, tenant and the shape of their condition: the `find` and `deep` of the query without their values. - Paging, sorting and selected fields do not split a group. ```json linenums="1" [ { "find": { "status": "paid", "customer_id": 42 }, "limit": 10 }, { "find": { "customer_id": 7, "status": "new" }, "sort": { "_id": -1 } } ] ``` - Both calls above are one query shape, `find { customer_id, status }`. - For each shape you see how often it ran, the rows returned, the rows saved or updated and its time, with up to 100 samples that keep the real values. ## 3. Run the index analysis - For each query shape, Index Maker runs explain on its database with a recorded sample of the query. - Each shape is marked as using a full index, part of an index, or no index. - Supported for MongoDB, MySQL, MariaDB, SQL Server, PostgreSQL and Oracle. ## 4. Create the missing indexes - **Suggestions** lists the query shapes that use no index. Each one opens the index form of its table, and **Add to script** puts the selected shapes into the migration script `am_index_maker` in one go, with the fields chosen the way the automatic mode chooses them. - The index form shows every field of the table with whether a plain index can hold it (and why not), the indexes the table already has and its size. The name is built from the fields, the same in every environment. Pick the fields and the options of your database, and create the index. For a multi-tenant API, the index goes to the database of the tenant of that query. - **Also write it to the migration script** (on by default in the form) adds the index to `am_index_maker`, so every environment which deploys the repository creates it too. | Database | Options | |---|---| | MongoDB | unique, ascending, descending, hashed, text | | MySQL, MariaDB | FULLTEXT, SPATIAL, UNIQUE, with BTREE, HASH or RTREE | | SQL Server | CLUSTERED, NONCLUSTERED, XML, SPATIAL, unique or not | | PostgreSQL | btree, hash, gist, gin, spgist, brin, unique or not, NULLS FIRST or LAST | | Oracle | unique, ASC, DESC | - Every index created or removed from API Maker is logged with its table, fields, date and source : the form, a suggestion, the automatic mode, custom code (`g.sys.system.createIndexes`) or the migration script on deploy. ## Automatic mode - Switch it on in the **Automatic indexes** panel and Index Maker starts its own scanning periods, evaluates them when they end and creates the indexes that pass its thresholds (table size, average time, number of calls). Everything it decides is written into the migration script `am_index_maker`, which can not be deleted, and every decision is kept on the period. See [Automatic indexes](automatic_indexes.md). ## From your code - `g.sys.system.createIndexes`, `g.sys.system.dropIndexes` and `g.sys.system.getIndexes` create, drop and list indexes on any database of the account, in the same words for every database : see the [examples](/v1/examples/sys/system/createIndexes.html). ## One run from the API testing page - Run a custom API from the API testing page. Its **Index Maker** tab shows the database queries of that run only, with no scanning period to start. - For each query: whether it used an index, how often it ran and its samples. A timeline shows the calls, and the queries that use no index are listed as suggestions. - The data of a run is read once, right after the run, and then removed. ## Good to know - Each watched call adds a few writes to the database of API Maker, after its response. Run scanning periods when you need them, not all the time. - Without API Maker + Extensions, the index list of a table on the instances page still shows, creates and removes indexes. 👉 Index Maker comes with [API Maker + Extensions](https://www.npmjs.com/package/@sava-info-systems/api-maker-with-extensions), the package installed with a license key. For a license key, contact us at `contact@apimaker.dev`.
--- # Automatic indexes > Switch on the automatic mode of Index Maker - it records the queries of your APIs on its own, decides which slow or frequent queries on big tables deserve an index, creates it, and writes it into the migration script am_index_maker so every environment gets it on deploy. Source: https://docs.apimaker.dev/v1/extensions/index_maker/automatic_indexes.html ### Automatic indexes (v3.3.0+) - Switch the automatic mode on and Index Maker works on its own : it records the database queries of your APIs at regular intervals, asks the database with explain which ones use no index, and gives an index to the queries that are slow or frequent on tables that are big enough. - Every index it decides on goes to two places : the database of the server that decided it (optional), and the migration script **`am_index_maker`**, so every other environment creates the same index on its next deploy. - It is in **API Maker + Extensions**, in the **Index Maker** menu : the **Automatic indexes** panel next to the scanning periods. ## The panel - **The switch** turns the mode on and off for the account (an admin or a developer account has its own periods, script and settings). - **Scan now** starts an automatic scanning period at once instead of waiting for the next one. - **Evaluate last period** runs the evaluation of the last automatic period again. - **Script** opens the entries of `am_index_maker` : remove one, open the code, run the script on this server. - **Run here** runs the script on this server, like the play button of the migrations page. - The panel shows the period recording right now, when the next one is due, the last evaluation with its decisions, and how many indexes were written and created. ## What it does, step by step 1. **Record.** A scanning period named `Automatic ` records the database APIs of every instance for `scanWindowMinutes`. It costs what a scanning period costs : a few writes to the database of API Maker after each watched call, only while it records. 2. **Explain.** When the period ends, every recorded query shape is run through `explain` on its database, like the index analysis of a scanning period. Only the shapes that use no index go on (`includePartialIndexQueries` adds the ones that use part of an index). 3. **Decide.** For every shape, in the order of the queries that matter most (calls × time) : - the collection is not in `excludedCollections` ; - the query is worth it : slower than `minAvgQueryTimeMS` on average, **or** called at least `minQueryCount` times ; - the table is big enough : `minTableRows` rows or more. The size comes from the catalog and a count bounded by `minTableRows`, so a big table is never counted to its end ; - the fields of the index : the equality fields first, then the `$in` fields, then one range or pattern field, at most `maxFieldsPerIndex`. A field of a type the database can not index (TEXT and BLOB on MySQL, json on PostgreSQL, CLOB on Oracle, `varchar(max)` on SQL Server...) is dropped ; - no existing index of the table starts with those fields, the script does not hold them already, and the table stays under `maxIndexesPerTable`. 4. **Create.** With `applyOnThisServer`, the index is created on the database of this server right away, with the name Index Maker gives it. An index the database refuses stays out of the script. 5. **Write.** The definitions are written into the JSON list of `am_index_maker`, and the script is marked "not executed" on this server so that every environment runs it on its next deploy. 6. **Keep going.** The next period starts `scanEveryMinutes` after the previous one ended. Old automatic periods are removed beyond `keepAutomaticPeriods`, with their recorded queries. Every decision is kept on the period : open it in the scanning periods grid, tab **Automatic indexes**, to see for each query shape what was decided and why (`APPLIED`, `WRITTEN`, `SMALL_TABLE`, `NOT_WORTH_IT`, `COVERED_BY_EXISTING_INDEX`, `ALREADY_IN_SCRIPT`, `TOO_MANY_INDEXES`, `NO_INDEXABLE_FIELD`, `UNSUPPORTED_SHAPE`, `EXCLUDED_COLLECTION`, `TABLE_NOT_READABLE`, `FAILED`). ## Settings - The settings are kept on the account (`settings.indexMaker`), so they travel with the project in git. A number outside its limits is brought back inside them, never refused. | Setting | Default | Limits | Meaning | |---|---|---|---| | `autoIndexEnabled` | `false` | | The master switch. Off, nothing runs on its own ; the script, the suggestions and the index form still work by hand. | | `applyOnThisServer` | `true` | | Besides the script, create the index on the database of this server as soon as it is decided. | | `scanEveryMinutes` | `360` | 5 to 43200 | A new automatic period starts this long after the previous one ended. | | `scanWindowMinutes` | `30` | 1 to 1440 | How long an automatic period records. | | `minTableRows` | `10000` | 0 to 10⁹ | A table with fewer rows gets no index : a scan of a small table is cheap and every index costs on writes. | | `minAvgQueryTimeMS` | `100` | 0 to 600000 | A query shape slower than this, on average, gets an index. | | `minQueryCount` | `50` | 1 to 10⁸ | A query shape called at least this often during the period gets an index even when it is fast : the table grows. | | `maxFieldsPerIndex` | `3` | 1 to 8 | Fields of a compound index : the equality fields first, then the ranges, the rest is dropped. | | `maxIndexesPerTable` | `12` | 1 to 64 | A table that already carries this many indexes gets no more. | | `includePartialIndexQueries` | `false` | | Also look at the shapes that use part of an index, not only the ones without any. | | `keepAutomaticPeriods` | `3` | 1 to 50 | Older automatic periods, with their recorded queries, are removed. | | `excludedCollections` | `[]` | | `instance:database:collection` patterns, `*` for any part. For a multi-tenant instance, the plain name of the instance. | | `logInternalQueries` | `false` | | Also record the queries API Maker runs on its own (deep populate, unique checks). | ## The migration script `am_index_maker` - It is a normal migration script of the account, on the **Databases migration** page, created the first time the mode is switched on (or the first time an entry is added by hand). It can not be deleted while Index Maker uses it ; remove its entries from the Index Maker page instead. - Index Maker only rewrites the JSON between the two markers. Edit the code outside them as you like, or add your own entries to the list (keep the JSON valid). - Every environment that pulls the repository runs the script on deploy. An index which exists already is reported as `EXISTS` and left alone, so the script can run again and again. A failed index is reported to the deploy after every other index of the list was created. ```ts linenums="1" import * as T from 'types'; // const INDEXES: T.IIndexMakerScriptEntry[] = [ { "instance": "mysql8", "database": "inventory", "collection": "orders", "name": "am_ix_orders_customer_id_status_3f9a1c", "fields": [{ "name": "customer_id" }, { "name": "status" }], "operation": "CREATE", "source": "AUTOMATIC", "reason": "Called 1,240 times, 310 ms on average, orders has 2,400,000 rows.", "addedAt": "2026-09-29T10:15:00.000Z", "scanningPeriodId": "6abbacb0bc471602b5f71099" } ]; // async function main(g: T.IAMGlobal) { const creates = INDEXES.filter(e => (e.operation || 'CREATE') === 'CREATE'); const results = await g.sys.system.createIndexes({ indexes: creates, ifNotExists: true, continueOnError: true, skipUnindexable: true, source: 'MIGRATION_SCRIPT', }); // ... DROP entries go through g.sys.system.dropIndexes, table by table } module.exports = main; ``` | Entry field | Meaning | |---|---| | `instance` | Instance name. `crm::acme` names the tenant `acme` of the multi-tenant instance `crm`. | | `database` | Database name, as the account sees it (database name masking is applied). | | `collection` or `table` | The table. PostgreSQL : `public.orders`, SQL Server : `dbo.orders`. | | `name` | Index name. Missing : Index Maker builds one (see below). | | `fields` | `[{ "name": "status", "order": "ASC" }]`. Orders : `ASC`, `DESC`, `HASHED` (MongoDB), `TEXT` (MongoDB) ; `nullsFirst: true` on PostgreSQL. | | `unique` | A unique index. | | `type` | MySQL : `FULLTEXT`, `SPATIAL` ; SQL Server : `CLUSTERED`, `XML`, `SPATIAL` ; PostgreSQL : `hash`, `gin`, `gist`, `spgist`, `brin`. Plain when missing. | | `method` | MySQL : `BTREE`, `HASH`, `RTREE`. | | `operation` | `CREATE` (default) or `DROP` (the name is enough). | | `source`, `reason`, `addedAt`, `scanningPeriodId` | Who added the entry and why ; for people, not for the database. | ### Index names - `am_ix___`, `am_ixu_` for a unique index. The same fields give the same name in every environment, so `EXISTS` is found by name first and by fields second. - The name is cut to the shortest limit of the database : 100 characters on MongoDB, 64 on MySQL and MariaDB, 128 on SQL Server, 63 on PostgreSQL, 30 on Oracle. The hash keeps cut names apart. ## By hand : suggestions and the index form - **Suggestions** of a scanning period : select the shapes and click **Add to script**. Index Maker turns each shape into a definition the way the automatic mode does (without the thresholds : you chose them) and adds it to the script. - **The index form** shows every field of the table with whether a plain index can hold it and why not, the indexes the table already has and its size. The name is built from the fields ; the switch **Also write it to the migration script** (on by default) adds the index to `am_index_maker` after creating it. - **Index logs** keep who created or dropped every index : the form (`UI`), a suggestion (`SUGGESTION`), the automatic mode (`AUTOMATIC`), custom code (`SYSTEM_API`) or the script on deploy (`MIGRATION_SCRIPT`), with the reason and the scanning period. ## From your code - The same three operations are system APIs, for custom APIs, schedulers and your own migration scripts : [createIndexes](/v1/examples/sys/system/createIndexes.html), [dropIndexes](/v1/examples/sys/system/dropIndexes.html) and [getIndexes](/v1/examples/sys/system/getIndexes.html). They are also HTTP APIs : `POST /api/system-api/user-path/create-indexes`, `/drop-indexes`, `/get-indexes`. - `createIndexes` and `dropIndexes` change the database : the APIs Security Report lists them with the other sensitive system APIs, so give them only to the groups that need them. ## Safe on every version - The statements Index Maker runs are the plain `CREATE INDEX` and `DROP INDEX` of each database, without `IF NOT EXISTS` (PostgreSQL 9.5+ only, MySQL never) : the existence check is made in code from the catalog, so the same script works on every version API Maker supports. - Identifiers are quoted on Oracle (`"INVENTORY"."am_ix_..."`) and bracketed on SQL Server (`[dbo].[orders]`), so tables with lower-case or mixed-case names work. - A field the database can not put in a plain index is `SKIPPED` before the database is asked, with the reason : TEXT and BLOB (a prefix is needed) and JSON on MySQL and MariaDB ; `text`, `ntext`, `image`, `xml`, the spatial types, `(n)varchar(max)`, `varbinary(max)` and keys over 900 bytes on SQL Server ; `json`, `xml`, the geometric types, `tsvector` on PostgreSQL ; LOBs, LONG, XMLTYPE and SDO_GEOMETRY on Oracle ; a primary key everywhere. ## Performance - Nothing changes for a request : the only cost is the recording of a scanning period while one runs, which is the cost Index Maker always had. - The loop wakes once a minute in every process, but one process of the whole cluster does the work (a Redis claim of the minute), and its tick is one small read of the accounts with the mode on. - The evaluation runs once per period, at its end, and waits for a quieter minute when the event loop of the process is busy. It reads the catalog of the tables, never the rows beyond `minTableRows`. 👉 Index Maker comes with [API Maker + Extensions](https://www.npmjs.com/package/@sava-info-systems/api-maker-with-extensions), the package installed with a license key. For a license key, contact us at `contact@apimaker.dev`.
--- # Redis Dashboard > The keys of the external Redis of an API Maker account - cached answers and your own keys - as a tree, with their value and TTL, editable, plus a flush of the whole database. Source: https://docs.apimaker.dev/v1/docs/dashboard/redis-dashboard.html `Dashboard → Redis Dashboard` shows the external Redis of the account : the answers cached by [automatic caching](/v1/docs/features/automatic-caching.html) and the keys your code stores with [set redis key](/v1/docs/apis-all/system-apis/system-generated-set-redis-key-api.html). | | | |---|---| | Tree | The keys grouped by their parts (account, kind, instance, database, table…) : **Expand All**, **Collapse All**, search. | | Per key | Its value and its TTL in seconds. **UpdateTTL** and the value are editable. **Delete** removes a key. | | New key | **Add New Key** : a name, a value and a TTL. Without a TTL the key lives until it is deleted. | | Flush | **Flush redis database** empties it : every cached answer and every key of yours. | | Refresh | Reloads the tree. | - A cached answer of a table reads as `…:::
:…`. Deleting it is a manual cache reset for that one request. - The internal Redis of API Maker (auto increment counters, cluster coordination) is not shown here : the [Auto Increments](/v1/docs/features/auto-increment.html) page manages the counters. ## Related - [Automatic caching](/v1/docs/features/automatic-caching.html) · [Get redis key](/v1/docs/apis-all/system-apis/system-generated-get-redis-key-api.html) · [Set redis key](/v1/docs/apis-all/system-apis/system-generated-set-redis-key-api.html) --- # Log Profiles > Choose which APIs of an API Maker account write a log entry for every call - tables, custom, system and third party APIs, events, schedulers, WebSocket events - with or without the response, and read them in the Log Explorer. Source: https://docs.apimaker.dev/v1/docs/logs/log-profile.html A log profile says which calls are written to the log database. Several profiles can be active ; a call is logged when any active profile selects its API. | | | |---|---| | Page | `Utility → Log Profile` : the profiles on the left, the tree of the account on the right. | | Selects | Tables and their APIs, custom APIs, system APIs, third party APIs, events, schedulers, WebSocket events. | | Per API | **Save this API's response in the log** keeps the answer next to the request. | | Active | Only active profiles log. **Clone** copies a profile ; a **default** profile can be marked. | | Read | [Log Explorer](/v1/docs/logs/log-table.html). | 1. **Add New**, give a name, check the APIs, decide per API whether the response is kept, **Save Log Profile**. 2. Make it active. From now on every call of those APIs, on every server, writes an entry : who called, from where, how long it took, the payload and the console output of your code. 3. Open the Log Explorer to search them. - Logs live in the log database of the [configuration](/v1/docs/am-resources/api-maker-configurations.html) (`logs.mongo_db_connection_logs`) and are trimmed to `maxLogsCount` by a daily job. - Log profiles go to Git. ## Related - [Log Explorer](/v1/docs/logs/log-table.html) · [API Maker configuration](/v1/docs/am-resources/api-maker-configurations.html) --- # Log Explorer > Search the logged calls of an API Maker account - by time, method, status, duration, category, API user, tenant, server, client - open one with its payload, response and console output, copy it as cURL, export as CSV or JSON. Source: https://docs.apimaker.dev/v1/docs/logs/log-table.html `Utility → Log Explorer` is where the entries written by the [log profiles](/v1/docs/logs/log-profile.html) are searched. | | | |---|---| | Columns | Time, method, status, duration, category, API and table, API user, tenant, server, client IP, user agent, request id, rows returned, payload size, size, cached response, log id. Pick the columns and the density ; save a search. | | Filters | A time window (relative or absolute), the duration between two values, a category, a status, a text ; **Exclude** removes what matches. | | One entry | The request (headers, query, path variables, payload), the response when the profile kept it, the console output of your code, the errors. **Copy this call as a cURL command**, **Copy the whole log as JSON**. | | Export | Every matching event as CSV or JSON. | | Delete | Every stored log of the account. | - The **request id** links every log of one request : copy it and search it to see the custom API and the calls it made. - **Has console output** filters the entries with `g.logger` lines. - **Log storage is not configured** means the server has no log database : set `logs.mongo_db_connection_logs`. ## Related - [Log profiles](/v1/docs/logs/log-profile.html) · [The global object g](/v1/docs/pre-defined-terms/global-object-g.html#glogger-and-gshared) --- # Server Node Dashboard > What every API Maker server of the cluster runs with - its workers, platform, memory, database connections and the configuration values it read - on the Server Node Configurations dashboard of the root user. Source: https://docs.apimaker.dev/v1/docs/dashboard/node-dashboard.html `Dashboard → Server Node Configurations` shows every server of the cluster with the values it started with. It is the place to check that all servers run the same configuration after a change of `.env`. | | | |---|---| | Worker Information | The processes of the server (`cpuCount`), the master worker marked. | | Platform Information | Host name, CPU count and model, uptime, Node.js version, home and temp directories. | | Memory | The memory of the machine and of each process : RSS, heap used and total, external. | | DB Connections | The connection pools open on the server, per instance. | | Configuration | The values read from `package.json` and `.env` : JWT expiry and issuer, refresh token validity, Redis value expiry, log retention, body size limit, compression threshold, sandbox count, cron time zone, store URL, uploaded files cleanup, Oracle client path, process logs path… and some important environment variables. | - **Name**, **Type**, **Shared** and **Available** describe the sandboxes of the server. - Learn what each value does in [API Maker configuration](/v1/docs/am-resources/api-maker-configurations.html). ## Related - [API Maker configuration](/v1/docs/am-resources/api-maker-configurations.html) · [Deploy API Maker](/v1/docs/features/deploy-api-maker.html) --- # Entity Relationship Diagram > A live diagram of every active instance, database and table of an API Maker account with the relations drawn from the schemas - primary keys, auto increments and required fields marked. Source: https://docs.apimaker.dev/v1/docs/dashboard/diagram.html `Dashboard → Entity Relationship Diagram` draws the account : every active instance, its databases, their tables, and the relations the [schemas](/v1/docs/schema/schema.html#6-relations-and-virtual-fields) declare with `collection` and `column`. | | | |---|---| | Boxes | One per table, with its fields and their types, grouped by database and instance. **Expand All** and **Collapse All**. | | Lines | A relation from a field to the column of another table. Virtual fields draw the reverse. | | Marks | `PK` primary key · `AM+` auto increment by API Maker · `DB+` auto increment by the database · `R` required | | Zoom | Double click zooms in, `Shift` + double click zooms out, the mouse wheel does both. | - A table without a schema shows without relations : generate its schema to see them. ## Related - [Table schema](/v1/docs/schema/schema.html) · [Deep populate](/v1/docs/apis-all/query-params/deep.html) --- # Internationalization > Answer the errors and messages of API Maker in the language of the caller - an i18n item maps every message to its translation, and the header x-am-internationalization picks it per request. Source: https://docs.apimaker.dev/v1/docs/i18/i18.html An i18n item is a dictionary : the messages of API Maker and of your custom APIs on the left, their text in a language on the right. A request names the item in a header and gets its errors and messages translated. | | | |---|---| | Page | `Utility → Internationalization` : a name and a TypeScript file which exports the dictionary. | | Header | `x-am-internationalization: ` (its id works too). Without it, the messages are the ones of API Maker. | | Translates | The constant errors of API Maker (validation, authorization, not found…), the `errorList` of your [custom APIs](/v1/docs/settings/customApiSettings.html), the errors of third party APIs. | | Placeholders | A constant error keeps its `{field}`, `{value}`… placeholders in the translation. | ## 1. Create the item ```typescript title="hindi" linenums="1" module.exports = { // constant errors of API Maker : keep the placeholders "Please provide valid '{field}' field with type '{type}'.": "कृपया '{field}' फ़ील्ड का सही मान दें ({type}).", "You are not authorized to access this API.": "आपको इस API की अनुमति नहीं है।", // the errorList of your custom APIs "Unable to process this request.": "यह अनुरोध पूरा नहीं हो सका।", }; ``` - The page lists the constant messages of API Maker to start from, and every message named in the `errorList` of a custom API. ## 2. Throw the message ```typescript title="Custom API" throw new Error('Unable to process this request.'); ``` ## 3. Ask for the language ```text POST /api/custom-api/admin/customers x-am-internationalization: hindi ``` ```json { "success": false, "statusCode": 500, "errors": [ { "code": 500, "message": "यह अनुरोध पूरा नहीं हो सका।" } ], "warnings": [] } ``` - A message the item does not have is answered as it is. - i18n items go to Git. ## Related - [Custom API settings](/v1/docs/settings/customApiSettings.html) · [Request headers](/v1/docs/apis-all/header/requestHeader.html) · [Error codes](/v1/docs/apis-all/error-codes.html) --- # Deploy API Maker > Upgrade API Maker on every server of the cluster from the admin panel, from npm or from a .tgz bundle, with live progress for each server. Source: https://docs.apimaker.dev/v1/docs/features/deploy-api-maker.html - Install another version of API Maker on every server of the cluster from the **Deploy API Maker** page of the admin panel. - This page upgrades API Maker itself. To deploy your APIs, use a Git pull. Learn more [Git](/v1/docs/Git/git.html). > Diagram : Deploy API Maker : admin panel → Caddy → API Maker → Redis → every server (5 stages, live progress) → restart, wait for the new version, reload ## Two ways - **Deploy from NPM**: pick a published version and click **Deploy**. It is downloaded from the official npm registry. - **Upload a bundle**: select a local API Maker `.tgz` or `.tar.gz` file, up to 100 MB. ## What happens on each server 1. The bundle is written to disk and extracted. 2. The old install is cleared. `.env`, `local.env` and `license.txt` are kept. 3. The new files are copied into place. 4. Dependencies are installed. 5. The environment is written into the bundled admin panel. - The page shows a card per server and the live log of each stage. - When every server has installed it, the page restarts all backend processes and waits until they answer on the new version. - If a server reports an error or does not answer in time, the cluster is not restarted. ## Good to know - A deployment waits up to 20 minutes for the servers to finish. - If the WebSocket connection of the page is down, the deployment keeps running and the page reports the result when it ends. --- # Operate API Maker Using API > Learn how to manage API Maker programmatically using its dedicated management API, secured with a communication token. Source: https://docs.apimaker.dev/v1/docs/am-resources/operate-api-maker-using-api.html API Maker can be operated programmatically via a dedicated management API. This allows external systems or scripts to perform administrative operations—such as creating or updating admin users—without using the UI. ## Configuration To enable this feature: 1. Go to **Root Settings → Deployment Settings → API Access**. 2. Check the **Operate API Maker Using API** checkbox. 3. Set a **Communication Token** (minimum 48 characters). Use the **Generate** button to auto-generate a secure token. 4. Save the settings. !!! warning The Communication Token acts as the authentication secret for all management API calls. Keep it confidential and rotate it if compromised. Changing it immediately invalidates any existing integrations using the old token. --- ## API Endpoint ```text POST {API_MAKER_HOST}/api/sites/root-user-settings/operate-api-maker-using-api ``` **Example:** ```text POST http://api-maker.example.com/api/sites/root-user-settings/operate-api-maker-using-api ``` --- ## Request ### Headers | Header | Value | Required | |----------------|--------------------|----------| | `Content-Type` | `application/json` | Yes | ### Request Body Structure | Field | Type | Required | Description | |-------------|----------|----------|------------------------------------------------------------------------------------| | `token` | `string` | Yes | The Communication Token configured in Deployment Settings. Minimum 48 characters. | | `operation` | `string` | Yes | The operation to perform. See [Supported Operations](#supported-operations) below. | | `payload` | `object` | Depends | Operation-specific data. See each operation's payload table for required fields. | --- ## Supported Operations | Operation | Description | |----------------------|------------------------------------| | [`CREATE_ADMIN_USER`](#create_admin_user) | Creates a new admin user. | | [`UPDATE_ADMIN_USER`](#update_admin_user) | Updates an existing admin user. | | [`DELETE_ADMIN_USER`](#delete_admin_user) | Permanently deletes an admin user. | | [`UPDATE_SECRET`](#update_secret) | Updates the secret for an admin user. | --- ### `CREATE_ADMIN_USER` Creates a new admin user in API Maker. On success, the system also performs a git pull for the user's repository, saves their secret, and generates a deployment hook — all returned in the response. **Sample Request:** ```json { "token": "aBcDe_FgHiJ_KlMnO_PqRsT_UvWxY_ZaBcD_EfGhI_JkLmN_OpQrS", "operation": "CREATE_ADMIN_USER", "payload": { "user": { "name": "John Doe", "email": "john@example.com", "password": "securePassword123", "apiPath": "john_doe", "gitUrlWithCredentials": "https://username:access_token@github.com/username/repo.git", "gitCommitUserEmail": "john@example.com", "gitBranch": "main" }, "secret": { "name": "Default", "keysCode": "" } } } ``` #### `payload` Fields | Field | Type | Required | Description | |----------|----------|----------|---------------------------------------------------| | `user` | `object` | Yes | Object containing the new admin user's details. | | `secret` | `object` | No | Optional object for initial secret configuration. | #### `payload.user` Fields | Field | Type | Required | Description | |-------------------------|----------|----------|------------------------------------------------------------------------------------------------------| | `name` | `string` | Yes | Full name of the user. | | `email` | `string` | Yes | Email address of the user. Must be unique. | | `password` | `string` | Yes | Plain-text password. API Maker encrypts it before storing. | | `apiPath` | `string` | Yes | Unique path segment for this user's APIs. Allowed characters: `[a-z, 0-9, _]`. Example: `john_doe`. | | `gitUrlWithCredentials` | `string` | No | Git repository URL with embedded credentials. Example: `https://user:token@github.com/user/repo.git`. Stored encrypted. | | `gitCommitUserEmail` | `string` | No | Email used in git commits for this user. | | `gitBranch` | `string` | No | Default git branch. Example: `main`. | #### `payload.secret` Fields | Field | Type | Required | Description | |------------|----------|----------|-----------------------------------------------------| | `name` | `string` | Yes | Secret name. Example: `Default`. | | `keysCode` | `string` | No | Application secrets code associated with this user. | #### Response — Success `200` ```json { "success": true, "statusCode": 200, "data": { "userResponse": { "guid": "01KP2MCKX5DNBGM7RCAASM3BMQ", "name": "John Doe", "email": "john@example.com", "apiPath": "john_doe", "settings": { "sandbox": { "dependencies": { "nodeJS": [] } }, "allowedOrigins": [] }, "gitCommitUserEmail": "john@example.com", "gitBranch": "main", "isDebuggingEnabled": false, "userType": "ADMIN", "active": true, "_id": "69dc7c7ead52c49d2086d81e", "id": "69dc7c7ead52c49d2086d81e" }, "gitPullResponse": true, "secretSaveResponse": true, "deploymentHookResponse": { "hookUrl": "6MgUnOSSqR36JTU9M7LON4BcbSvwxn4r", "hookAccessToken": "ASIQ4BJOHDl9RRUE5DH6cIuyvCUJQwCM", "hookSecret": "lIfV52PsStG3Nq4Wx2tWqmFd3uL0QOSU", "deploymentUrl": "http://__ip_address__:38246/api/sites/deploy/john_doe/6MgUnOSSqR36JTU9M7LON4BcbSvwxn4r?token=ASIQ4BJOHDl9RRUE5DH6cIuyvCUJQwCM&secret=lIfV52PsStG3Nq4Wx2tWqmFd3uL0QOSU&branch=main" } } } ``` #### Response Fields | Field | Type | Description | |-----------------------------------------------|-----------|------------------------------------------------------------------------------------| | `data.userResponse` | `object` | The created user record. | | `data.userResponse.guid` | `string` | Globally unique identifier for the user. | | `data.userResponse.name` | `string` | Name of the created user. | | `data.userResponse.email` | `string` | Email of the created user. | | `data.userResponse.apiPath` | `string` | API path assigned to the user. | | `data.userResponse.userType` | `string` | Role of the user. Will be `ADMIN`. | | `data.userResponse.active` | `boolean` | Whether the user account is active. | | `data.userResponse._id` | `string` | Database identifier of the created user. | | `data.gitPullResponse` | `boolean` | `true` if the initial git pull was successful. | | `data.secretSaveResponse` | `boolean` | `true` if the secret was saved successfully. | | `data.deploymentHookResponse` | `object` | Auto-generated deployment hook details for this user. | | `data.deploymentHookResponse.hookUrl` | `string` | Unique hook URL token for this user's deployment endpoint. | | `data.deploymentHookResponse.hookAccessToken` | `string` | Access token to authenticate deployment hook calls. | | `data.deploymentHookResponse.hookSecret` | `string` | Secret used to verify the deployment hook request. | | `data.deploymentHookResponse.deploymentUrl` | `string` | Full deployment webhook URL. Replace `__ip_address__` with your server IP. | --- ### `UPDATE_ADMIN_USER` Updates an existing admin user in API Maker. The user to update is located using the `find` criteria, and the fields in `updateData` are applied to the matched user. **Sample Request:** ```json { "token": "aBcDe_FgHiJ_KlMnO_PqRsT_UvWxY_ZaBcD_EfGhI_JkLmN_OpQrS", "operation": "UPDATE_ADMIN_USER", "payload": { "find": { "apiPath": "john_doe" }, "updateData": { "name": "John Doe Updated", "email": "john.updated@example.com", "password": "newPassword123", "apiPath": "john_doe_updated", "gitUrlWithCredentials": "https://username:access_token@github.com/username/repo.git", "gitCommitUserEmail": "john.updated@example.com", "gitBranch": "main" } } } ``` #### `payload` Fields | Field | Type | Required | Description | |--------------|----------|----------|--------------------------------------------------------------------------| | `find` | `object` | Yes | Criteria to locate the user to update. | | `updateData` | `object` | Yes | Fields to update on the matched user. Only provided fields are changed. | #### `payload.find` Fields | Field | Type | Required | Description | |-----------|----------|----------|---------------------------------------------------------------------| | `apiPath` | `string` | Yes | The current `apiPath` of the user to find. Must match exactly. | #### `payload.updateData` Fields | Field | Type | Required | Description | |-------------------------|----------|----------|----------------------------------------------------------------------------------------------------------| | `name` | `string` | No | Updated full name of the user. | | `email` | `string` | No | Updated email address. Must be unique. | | `password` | `string` | No | New plain-text password. API Maker encrypts it before storing. Omit to keep the existing password. | | `apiPath` | `string` | No | New API path. Allowed characters: `[a-z, 0-9, _]`. Changing this renames the user's API path. | | `gitUrlWithCredentials` | `string` | No | Updated Git repository URL with embedded credentials. Stored encrypted. | | `gitCommitUserEmail` | `string` | No | Updated email used in git commits for this user. | | `gitBranch` | `string` | No | Updated default git branch. | #### Response — Success `200` ```json { "success": true, "statusCode": 200, "data": { "_id": "69dca794c282e099a4e6bbd3", "guid": "01KP2YX78WY1DQ62VTB4Y8T17E", "name": "John Doe Updated", "email": "john.updated@example.com", "apiPath": "john_doe_updated", "settings": { "allowedOrigins": [], "sandbox": { "automaticSandboxRestartInSeconds": null, "dependencies": { "nodeJS": [] }, "sandboxCountOverrideAdmin": 1 }, "dockerFile": "", "hashOfRunCommand": "46252046", "hashOfDockerfile": "1089711499" }, "gitCommitUserEmail": "john.updated@example.com", "gitBranch": "main", "isDebuggingEnabled": false, "userType": "ADMIN", "active": true, "__v": 0, "executedMigrationScripts": { "Migration 1": true } } } ``` #### Response Fields | Field | Type | Description | |----------------------------------------|-----------|----------------------------------------------------------------------------------| | `data._id` | `string` | Database identifier of the updated user. | | `data.guid` | `string` | Globally unique identifier for the user. | | `data.name` | `string` | Updated name of the user. | | `data.email` | `string` | Updated email of the user. | | `data.apiPath` | `string` | Updated API path of the user. | | `data.userType` | `string` | Role of the user. e.g. `ADMIN`. | | `data.active` | `boolean` | Whether the user account is active. | | `data.isDebuggingEnabled` | `boolean` | Whether sandbox debug mode is enabled for this user. | | `data.gitCommitUserEmail` | `string` | Git commit email of the user. | | `data.gitBranch` | `string` | Default git branch of the user. | | `data.settings` | `object` | User sandbox and origin settings. | | `data.settings.allowedOrigins` | `array` | List of allowed CORS origins for this user. | | `data.settings.sandbox` | `object` | Sandbox configuration for this user. | | `data.settings.sandbox.sandboxCountOverrideAdmin` | `number` | Override for the number of sandboxes allocated to this user. | | `data.settings.sandbox.automaticSandboxRestartInSeconds` | `number\|null` | Interval in seconds for automatic sandbox restart. `null` if not set. | | `data.settings.sandbox.dependencies.nodeJS` | `array` | List of Node.js package dependencies for this user's sandbox. | | `data.executedMigrationScripts` | `object` | Map of migration script names to their execution status (`true` = executed). | --- ### `DELETE_ADMIN_USER` Permanently deletes an existing admin user from API Maker. The user is located by their `apiPath`. !!! danger This operation is irreversible. All data associated with the user will be permanently removed. **Sample Request:** ```json { "token": "aBcDe_FgHiJ_KlMnO_PqRsT_UvWxY_ZaBcD_EfGhI_JkLmN_OpQrS", "operation": "DELETE_ADMIN_USER", "payload": { "apiPath": "john_doe" } } ``` #### `payload` Fields | Field | Type | Required | Description | |-----------|----------|----------|----------------------------------------------------------| | `apiPath` | `string` | Yes | The `apiPath` of the user to delete. Must match exactly. | #### Response — Success `200` ```json { "success": true, "statusCode": 200, "data": true } ``` #### Response Fields | Field | Type | Description | |--------|-----------|--------------------------------------------------| | `data` | `boolean` | `true` if the user was successfully deleted. | --- ### `UPDATE_SECRET` Updates the secrets code for an existing admin user, identified by their `apiPath`. **Sample Request:** ```json { "token": "aBcDe_FgHiJ_KlMnO_PqRsT_UvWxY_ZaBcD_EfGhI_JkLmN_OpQrS", "operation": "UPDATE_SECRET", "payload": { "apiPath": "john_doe", "keysCode": "your-secrets-code-here" } } ``` #### `payload` Fields | Field | Type | Required | Description | |------------|----------|----------|---------------------------------------------------------------| | `apiPath` | `string` | Yes | The `apiPath` of the user whose secret will be updated. | | `keysCode` | `string` | Yes | The new secrets code to apply to the user. | #### Response — Success `200` ```json { "success": true, "statusCode": 200, "data": true } ``` #### Response Fields | Field | Type | Description | |--------|-----------|-------------------------------------------------------| | `data` | `boolean` | `true` if the secret was successfully updated. | --- ## Error Responses The following error responses apply to all operations. #### Invalid Token — `401` ```json { "success": false, "statusCode": 401, "errors": [ { "message": "Unauthorized. Invalid communication token." } ] } ``` #### Feature Disabled — `403` ```json { "success": false, "statusCode": 403, "errors": [ { "message": "Operate API Maker using API feature is not enabled." } ] } ``` --- ## Validation Rules ### Top-level Fields | Field | Rule | |-------------|-------------------------------------------------------------------------------| | `token` | Required. Must be a `string`. Minimum 48 characters. | | `operation` | Required. Must be one of: `CREATE_ADMIN_USER`, `UPDATE_ADMIN_USER`, `DELETE_ADMIN_USER`, `UPDATE_SECRET`. | | `payload` | Required. Must be an `object`. | ### `CREATE_ADMIN_USER` — `payload` Fields | Field | Rule | |----------|--------------------------------------------------------------| | `user` | Required. Must be an `object` containing admin user details. | | `secret` | Optional. Must be an `object` if provided. | ### `CREATE_ADMIN_USER` — `payload.user` Fields | Field | Rule | |-------------------------|-----------------------------------------------------------| | `name` | Required. Must be a `string`. | | `email` | Required. Must be a valid email `string`. | | `password` | Required. Must be a `string`. Minimum 4 characters. | | `apiPath` | Required. Must be a `string`. Allowed: `[a-z, 0-9, _]`. | | `gitUrlWithCredentials` | Optional. Must be a `string` if provided. | | `gitCommitUserEmail` | Optional. Must be a `string` if provided. | | `gitBranch` | Optional. Must be a `string` if provided. | ### `CREATE_ADMIN_USER` — `payload.secret` Fields | Field | Rule | |------------|-------------------------------------------| | `name` | Required. Must be a `string`. | | `keysCode` | Optional. Must be a `string` if provided. | ### `UPDATE_ADMIN_USER` — `payload` Fields | Field | Rule | |--------------|------------------------------------------------------------------------------| | `find` | Required. Must be an `object`. | | `updateData` | Required. Must be an `object`. At least one field must be present. | ### `UPDATE_ADMIN_USER` — `payload.find` Fields | Field | Rule | |-----------|-----------------------------------------| | `apiPath` | Required. Must be a `string`. | ### `UPDATE_ADMIN_USER` — `payload.updateData` Fields | Field | Rule | |-------------------------|-----------------------------------------------------------| | `name` | Optional. Must be a `string` if provided. | | `email` | Optional. Must be a valid email `string` if provided. | | `password` | Optional. Must be a `string`. Minimum 4 characters. | | `apiPath` | Optional. Must be a `string`. Allowed: `[a-z, 0-9, _]`. | | `gitUrlWithCredentials` | Optional. Must be a `string` if provided. | | `gitCommitUserEmail` | Optional. Must be a `string` if provided. | | `gitBranch` | Optional. Must be a `string` if provided. | --- ### `DELETE_ADMIN_USER` — `payload` Fields | Field | Rule | |-----------|---------------------------------------------------| | `apiPath` | Required. Must be a `string`. Must match exactly. | ### `UPDATE_SECRET` — `payload` Fields | Field | Rule | |------------|-------------------------------| | `apiPath` | Required. Must be a `string`. | | `keysCode` | Required. Must be a `string`. | --- ## Security Notes - This endpoint does **not** require an AM user session or JWT token. Authentication is performed solely via the `token` field in the request body. - Always use HTTPS in production to prevent token exposure. - The Communication Token should be at least 48 characters. The built-in generator creates a strong token exceeding this minimum. - `gitUrlWithCredentials` is stored encrypted in the database. - The `deploymentUrl` in the `CREATE_ADMIN_USER` response contains sensitive tokens. Store it securely and never expose it in client-side code. --- # MongoDB Connection Strings in API Maker > Learn MongoDB URI formats for API Maker:- use standalone, replica-set, or sharded cluster connections with examples and parameters. Source: https://docs.apimaker.dev/v1/docs/Database-connection-string/mongodb-connection-strings.html ## Introduction When connecting MongoDB to API Maker, the **connection string** defines how your API Maker instance communicates with your MongoDB database. Whether you’re using **MongoDB Atlas**, a self-hosted MongoDB instance, or a cloud provider’s managed service, the correct connection string is essential for secure and reliable data access. In this guide, we’ll explain connection string formats, parameters, examples, troubleshooting tips, and best practices to help you integrate MongoDB with API Maker effectively. --- ## MongoDB Connection String Format Below is a **visual breakdown** of the MongoDB connection string. ``` mongodb[+srv]://[username:password@]host1[:port1][,host2[:port2],...]/[defaultDatabase][?options] └───┬─────┘ └───────┬────────┘ └───────┬────────────┘ └───────┬───────────┘ └───────┬───────────┘ │ │ │ │ │ Protocol (scheme) │ │ │ │ (mongodb or mongodb+srv)Credentials Host(s) + Ports Default Database Options ``` ## MongoDB Connection String Examples ### 🧩 Connection String Parts Explained - **Protocol (scheme):** - `mongodb://` → Standard connection (manual host list). - `mongodb+srv://` → DNS seed list (Atlas, auto-discovery). - **Credentials (optional):** - Format: `username:password@`. - Use URL-encoding for special characters in password (`p@ss` → `p%40ss`). - **Host(s) + Ports:** - Single host → `localhost:27017` - Multiple hosts (replica set/sharded cluster) → `host1:27017,host2:27017,host3:27017` - **Default Database (optional):** - After `/`, e.g., `/mydb`. - Used when none specified in queries. - **Options (query params):** - After `?`, key-value pairs separated by `&`. - Example: `?retryWrites=true&w=majority&ssl=true` --- ### Basic Localhost Connection ```text mongodb://localhost:27017 ``` ### Localhost with Database Name ```text mongodb://localhost:27017/mydb ``` ### Localhost with Username & Password ```text mongodb://user:password@localhost:27017/mydb ``` ### Localhost with Authentication Database ```text mongodb://user:password@localhost:27017/mydb?authSource=admin ``` ### Replica Set Connection ```text mongodb://host1:27017,host2:27017,host3:27017/mydb?replicaSet=myReplicaSet ``` ### Connection with SRV Record (Atlas) ```text mongodb+srv://cluster0.mongodb.net/mydb ``` ### Atlas with Username & Password ```text mongodb+srv://user:password@cluster0.mongodb.net/mydb ``` ### Connection with SSL/TLS Enabled ```text mongodb://user:password@localhost:27017/mydb?ssl=true ``` ### Connection with Retry Writes ```text mongodb+srv://user:password@cluster0.mongodb.net/mydb?retryWrites=true&w=majority ``` ### Connection with Read Preference ```text mongodb://user:password@localhost:27017/mydb?readPreference=secondary ``` ### Connection with Write Concern ```text mongodb://user:password@localhost:27017/mydb?w=majority&wtimeoutMS=5000 ``` ### Connection with Connection Pool Size ```text mongodb://user:password@localhost:27017/mydb?maxPoolSize=50&minPoolSize=5 ``` ### Connection with Timeouts ```text mongodb://user:password@localhost:27017/mydb?connectTimeoutMS=3000&socketTimeoutMS=5000 ``` ### Advanced Atlas Example with Multiple Options ```text mongodb+srv://user:password@cluster0.mongodb.net/mydb?retryWrites=true&w=majority&readPreference=secondaryPreferred&connectTimeoutMS=10000 ``` ### Complex Replica Set with SSL, Pooling & App Name ```text mongodb://user:password@host1:27017,host2:27017,host3:27017/mydb?replicaSet=myReplicaSet&ssl=true&maxPoolSize=100&appName=MyApp&authSource=admin ``` --- ## Connection string parts breakdown | Part | Description | Example | |-----------------------------|-----------------------------------------------------------------------------|---------------------------------------| | **Scheme** | Protocol prefix: `mongodb://` (manual hosts) or `mongodb+srv://` (DNS SRV). | `mongodb://`, `mongodb+srv://` | | **Username** (optional) | Database username for authentication. | `user` | | **Password** (optional) | Password for authentication (URL-encoded if special chars). | `p%40ss` for `p@ss` | | **@** | Separator between credentials and host(s). | `user:password@` | | **Host(s)** | One or more MongoDB server addresses. | `localhost`, `cluster0.mongodb.net` | | **defaultauthdb** | Default authentication database (often `admin`). | | **Port** (optional) | Port number (default: `27017`). | `:27017` | | **Comma-separated Hosts** | Multiple hosts for replica sets or sharded clusters. | `host1:27017,host2:27017,host3:27017` | | **/** (slash after hosts) | Separator between hosts and default database. | `/` | | **Default Database** | Database to connect to if none specified in queries. | `mydb` | | **?options** (query params) | Connection options in `key=value` format (chained with `&`). | `?retryWrites=true&w=majority` | --- ## Connection string options breakdown and explanation | Parameter | Description | Example Value | |---------------------------|--------------------------------------------------------------------------|----------------------------------------------| | **authSource** | Database to authenticate against (default: `admin`). | `authSource=admin` | | **replicaSet** | Name of the replica set to connect to. | `replicaSet=myReplicaSet` | | **ssl / tls** | Enable SSL/TLS for connections. | `ssl=true` | | **retryWrites** | Enables retryable writes (recommended for MongoDB Atlas). | `retryWrites=true` | | **w** | Write concern (acknowledgement level for writes). | `w=majority`, `w=1` | | **wtimeoutMS** | Timeout (ms) for write concern acknowledgment. | `wtimeoutMS=5000` | | **readPreference** | Which members to read from (`primary`, `secondary`, `nearest`). | `readPreference=secondary` | | **maxPoolSize** | Maximum number of connections in the connection pool. | `maxPoolSize=100` | | **minPoolSize** | Minimum number of connections in the pool. | `minPoolSize=5` | | **connectTimeoutMS** | Maximum time (ms) to wait for a connection. | `connectTimeoutMS=3000` | | **socketTimeoutMS** | Timeout (ms) for socket inactivity before closing. | `socketTimeoutMS=5000` | | **appName** | Custom name for the application (useful for monitoring in MongoDB logs). | `appName=MyApp` | | **readConcernLevel** | Level of isolation for reads (`local`, `majority`, `linearizable`). | `readConcernLevel=majority` | | **tlsCAFile** | Path to Certificate Authority (CA) file for TLS validation. | `tlsCAFile=/etc/ssl/ca.pem` | | **tlsCertificateKeyFile** | Path to TLS key+cert file for client authentication. | `tlsCertificateKeyFile=/etc/ssl/mongodb.pem` | | **compressors** | Enable network compression (`zlib`, `snappy`, `zstd`). | `compressors=zlib` | | **journal** | If writes should be committed to the journal. | `journal=true` | | **directConnection** | Connect directly to a single host, bypassing replica set discovery. | `directConnection=true` | | **srvMaxHosts** | Limits the number of hosts when using `mongodb+srv`. | `srvMaxHosts=3` | --- ## Secure Connection Best Practices - Always use **SRV connection strings** for MongoDB Atlas. - Enable **SSL/TLS** to encrypt data in transit. - Avoid hardcoding credentials in code. Use **API Maker Secrets Management**. - Restrict database user permissions to only what’s necessary. - Use **environment variables** to manage sensitive credentials. --- ## Connecting MongoDB in API Maker 1. Go to **API Maker Secret Management** > **Default**. 2. Choose **MongoDB** as the database type. 3. Paste your connection string. 4. Test the connection to verify credentials and permissions. Once connected, you can: - Create **schemas** for collections. - Use `/api/schema/...` endpoints for optimized queries. - Leverage **Deep Populate** to join MongoDB with other databases. --- ## Troubleshooting When connecting to MongoDB (local, self-hosted, or cloud like Atlas), you may encounter several common errors. Below is a categorized list with explanations: ### 🔑 Authentication & Authorization Errors | Error Code | Name | Description | Common Fix | |------------|----------------------|--------------------------------------------------------|-----------------------------------------------------| | 2 | BadValue | Invalid parameter or option. | Check query syntax or field types. | | 13 | Unauthorized | User lacks required permissions. | Grant appropriate roles with `db.grantRolesToUser`. | | 18 | AuthenticationFailed | Invalid username/password. | Verify credentials and authentication database. | | 8000 | AtlasError (generic) | MongoDB Atlas returned a generic error. | Check Atlas logs for specific issue. | | 8001 | AtlasUnauthorized | Atlas rejected request due to insufficient privileges. | Verify API key/permissions in Atlas. | | 403 | Forbidden | Attempted action not allowed by access rules. | Grant required permissions. | --- ### 🗂️ Duplicate & Index Errors | Error Code | Name | Description | Common Fix | |------------|-----------------------|-----------------------------------|-------------------------------------------| | 11000 | DuplicateKey | Duplicate value for unique index. | Use unique values or handle duplicates. | | 11001 | DuplicateKey (legacy) | Same as 11000 (deprecated). | Same as above. | | 12582 | IndexOptionsConflict | Conflicting index options. | Drop/recreate index with correct options. | --- ### 🌐 Network & Connectivity Errors | Error Code | Name | Description | Common Fix | |------------|-------------------------|----------------------------------------------------|------------------------------------------------------| | 6 | HostUnreachable | Target host unreachable. | Check server availability & firewall settings. | | 7 | HostNotFound | Host/DNS cannot be resolved. | Verify hostname in connection string. | | 89 | NetworkTimeout | Request timed out due to slow/unavailable server. | Increase timeout or fix connectivity issues. | | 9002 | ExceededTimeLimit | Request exceeded maximum allowed network time. | Optimize queries, check latency. | | 10107 | NotMaster | Node is not primary, write attempted on secondary. | Direct writes to primary or enable `readPreference`. | | 13436 | NotPrimaryNoSecondaryOk | No primary or secondary available. | Ensure replica set health, retry connection. | --- ### ⚡ Write & Transaction Errors | Error Code | Name | Description | Common Fix | |------------|-----------------------|------------------------------------------------|-------------------------------------------| | 50 | ExceededTimeLimit | Operation exceeded allowed time. | Optimize query or increase timeout. | | 91 | ShutdownInProgress | Operation interrupted (server shutdown). | Retry after server restarts. | | 112 | WriteConflict | Write conflict in transaction. | Retry operation. | | 11600 | InterruptedAtShutdown | Operation stopped during shutdown. | Retry after restart. | | 11601 | Interrupted | Operation manually interrupted. | Retry operation. | | 64 | WriteConcernFailed | Write concern not satisfied. | Increase replication factor or adjust WC. | | 10990 | TransactionAborted | Transaction aborted due to conflict or error. | Retry transaction. | | 251 | NoSuchTransaction | Transaction ID not found (expired or invalid). | Ensure valid transaction session. | | 263 | TooManyTransactions | Too many open transactions. | Limit concurrent transactions. | --- ### 🗄️ Query & Cursor Errors | Error Code | Name | Description | Common Fix | |------------|----------------------------------|--------------------------------------------------|---------------------------------------------| | 59 | CommandNotFound | Command not recognized. | Use valid MongoDB command. | | 120 | IllegalOperation | Operation not valid in this context. | Ensure operator/command is used correctly. | | 133 | CursorNotFound | Cursor no longer exists. | Rerun query or keep cursor alive. | | 13127 | IndexNotFound | Attempt to use non-existent index. | Ensure index exists or create required one. | | 17287 | CannotImplicitlyCreateCollection | Operation tried to create collection implicitly. | Explicitly create collection first. | | 17399 | BSONObjectTooLarge | BSON document exceeds maximum size (16MB). | Break document into smaller chunks. | --- ### 🔒 TLS/SSL & Security Errors | Error Code | Name | Description | Common Fix | |------------|-------------------|------------------------------------------|------------------------------------------------| | 8000 | AtlasError | Generic MongoDB Atlas error. | Check Atlas logs for detailed cause. | | 9001 | SocketException | Network socket failure. | Check network, TLS config, and cluster health. | | 11002 | StaleShardVersion | Outdated shard metadata used in request. | Refresh shard routing info / retry operation. | --- ### 🛠️ Miscellaneous Errors | Error Code | Name | Description | Common Fix | |------------|--------------------|-----------------------------------------------------|---------------------------------------| | 26 | NamespaceNotFound | Collection/DB does not exist. | Create DB/collection before querying. | | 43 | CursorInUse | Cursor already in use. | Use new cursor or close previous one. | | 60 | DatabaseDifferCase | DB names differ only by letter case. | Use consistent casing in DB names. | | 174 | OutOfDiskSpace | Server ran out of disk space. | Free space or resize storage. | | 140 | MapReduceError | Error while running MapReduce job. | Debug MapReduce functions. | | 17280 | CappedPositionLost | Attempted to query capped collection past position. | Adjust query logic. | | 8002 | AtlasClusterPaused | Cluster is paused (Atlas free tier auto-pause). | Resume cluster in Atlas dashboard. | --- ### 🔒 Sharding & Replica Errors | Error Code | Name | Description | Common Fix | |------------|----------------------|----------------------------------------|--------------------------------------------| | 148 | StaleConfig | Cluster config is outdated. | Refresh cluster metadata. | | 13388 | SendStaleConfig | Shard config mismatch during request. | Retry operation, ensure cluster stability. | | 13435 | NotMasterOrSecondary | Node is neither primary nor secondary. | Retry on valid node. | --- ### 🧩 Error Types Summary | Category | Typical Causes | Example Error Codes | |------------------------------------|----------------------------------------------------------------------|----------------------------------| | **Authentication & Authorization** | Invalid login, insufficient privileges, Atlas access issues | 2, 13, 18, 403, 8000, 8001 | | **Duplicate & Index** | Duplicate key insert, conflicting or missing indexes | 11000, 11001, 12582, 13127 | | **Network & Connectivity** | Host unreachable, DNS issues, timeouts, replica set errors | 6, 7, 89, 9002, 10107, 13436 | | **Write & Transaction** | Write concern not met, transaction aborts, conflicts | 50, 64, 91, 112, 10990, 251, 263 | | **Query & Cursor** | Invalid commands, lost cursors, BSON/document issues | 59, 120, 133, 17287, 17399 | | **Sharding & Replica Set** | Stale configs, invalid primary/secondary nodes | 148, 13388, 13435, 11002 | | **TLS/SSL & Security** | Socket/TLS handshake errors, invalid certificates | 9001, 8000, 8002 | | **Miscellaneous** | Missing namespace, disk issues, capped collections, MapReduce errors | 26, 43, 60, 140, 174, 17280 | - **Authentication** now includes extended Atlas-specific codes (`8000`, `8001`, `403`). - **Network** extended with replica set errors like `10107 (NotMaster)`. - **Transactions** expanded with codes like `251 (NoSuchTransaction)` and `263 (TooManyTransactions)`. - **Query** extended with large BSON errors (`17399`). - **Sharding** errors clarified with stale config and replica role mismatches. - **Miscellaneous** now includes capped collection and cluster pause errors. --- ## FAQ **Q1:** _Can I use MongoDB Atlas free tier with API Maker?_ Yes, the free tier works perfectly with API Maker. **Q2:** _What’s the difference between mongodb:// and mongodb+srv?_ `mongodb://` is a standard format, while `mongodb+srv://` uses DNS SRV records for simplified configuration. **Q3:** _Does API Maker support replica sets?_ Yes, simply include the replica set name in the connection string options. --- ## Related Links - [Official MongoDB Connection String Docs](https://www.mongodb.com/docs/manual/reference/connection-string/) - [API Maker Schemas Overview](https://docs.apimaker.dev/v1/docs/schema/schema.html) --- ## List of all MongoDB cloud service providers ### MongoDB Atlas (by MongoDB Inc.) - Official managed service by MongoDB. - Runs on **AWS, Azure, and Google Cloud**. - Features: auto-scaling, backup/restore, global clusters, built-in security, charts & BI connector. - [https://www.mongodb.com/atlas](https://www.mongodb.com/atlas) --- ### **API Maker Cloud** - Provides one click mongodb installation with latest versions. - Runs on dedicated VPS of your choice, so you can select based on your budget. - You can install MongoDB with API Maker's server also which saves a lot of money. - It is great choice for self hosted MongoDB, you can scale up or down mongodb server anytime you want, to handle more load and more users. - [https://cloud.apimaker.dev](https://cloud.apimaker.dev) --- ### Amazon DocumentDB (with MongoDB compatibility) - Managed service by **AWS**. - Compatible with MongoDB APIs (not 100% feature identical). - Fully integrated with AWS ecosystem (IAM, VPC, CloudWatch). - [https://aws.amazon.com/documentdb/](https://aws.amazon.com/documentdb/) --- ### Azure Cosmos DB (MongoDB API) - Microsoft’s globally distributed multi-model database. - Offers **MongoDB-compatible API**. - Features: multi-region writes, high availability, serverless options. - [https://azure.microsoft.com/services/cosmos-db/mongodb/](https://azure.microsoft.com/services/cosmos-db/mongodb/) --- ### Google Cloud (via MongoDB Atlas partnership) - Google Cloud does not provide a native MongoDB API service. - Instead, **MongoDB Atlas is available directly through GCP Marketplace**. - [https://cloud.mongodb.com](https://cloud.mongodb.com) --- ### ScaleGrid - Fully managed database hosting (**MongoDB, Redis, MySQL, PostgreSQL**). - Supports hosting on **AWS, Azure, GCP, DigitalOcean**. - Features: custom backups, dedicated servers, SSH access. - [https://scalegrid.io/mongodb/](https://scalegrid.io/mongodb/) --- ### ObjectRocket (by Rackspace) - Managed MongoDB hosting. - Features: automation, scaling, high availability. - Targets enterprise-grade workloads. - [https://www.objectrocket.com/mongodb](https://www.objectrocket.com/mongodb) --- ### Aiven - Open-source database as a service provider. - Offers fully managed MongoDB alongside **PostgreSQL, Kafka, Redis**, etc. - Runs on **AWS, Azure, GCP, DigitalOcean, UpCloud**. - [https://aiven.io/mongodb](https://aiven.io/mongodb) --- ### IBM Cloud Databases for MongoDB - Fully managed MongoDB service on **IBM Cloud**. - Features: automated backups, scaling, high availability. - Integrated with other IBM Cloud services. - [https://www.ibm.com/cloud/databases-for-mongodb](https://www.ibm.com/cloud/databases-for-mongodb) --- ### DigitalOcean Managed MongoDB - Simple, developer-friendly managed MongoDB clusters. - Built-in metrics, automated backups, scaling. - Popular with startups and indie developers. - [https://www.digitalocean.com/mongodb](https://www.digitalocean.com/mongodb) --- ### Kamatera - Cloud platform offering managed MongoDB hosting. - Pay-as-you-go pricing, custom server configurations. - Flexible deployments on global data centers. - [https://www.kamatera.com/solutions/mongodb-hosting/](https://www.kamatera.com/solutions/mongodb-hosting/) --- ### Severalnines (ClusterControl) - Provides automation and management for **MongoDB clusters**. - Self-hosted + managed options. - Features: backup, scaling, monitoring, cluster management. - [https://severalnines.com/mongodb](https://severalnines.com/mongodb) --- ### Compose (Legacy - Now Part of IBM Cloud) - Originally independent MongoDB hosting provider. - Acquired by IBM, now integrated with **IBM Cloud Databases**. - Still used by some legacy users. - [https://www.compose.com/](https://www.compose.com/) --- ### Bitnami (MongoDB Helm Charts / Containers) - Not a hosted service but widely used for **self-managed MongoDB deployments**. - Offers Helm charts and container images for Kubernetes and cloud providers. - Good for developers who want full control over hosting. - [https://bitnami.com/stack/mongodb](https://bitnami.com/stack/mongodb) --- ### ScaleXtremeDB (via VMWare Tanzu / PaaS integrations) - Provides MongoDB deployments via Kubernetes & Tanzu integrations. - Targets enterprises using hybrid-cloud setups. - [https://tanzu.vmware.com](https://tanzu.vmware.com) --- ### CloudClusters (MongoDB-as-a-Service) - Offers **managed MongoDB hosting** on dedicated instances. - Affordable developer-friendly pricing. - Features: automated backups, scaling, SSL, monitoring. - [https://www.cloudclusters.io/mongodb](https://www.cloudclusters.io/mongodb) --- ### Instaclustr (by NetApp) - Managed data platform provider. - Supports MongoDB along with Cassandra, PostgreSQL, Kafka, Redis. - Strong focus on **SLA-backed enterprise deployments**. - [https://www.instaclustr.com/mongodb/](https://www.instaclustr.com/mongodb/) --- ### ObjectBox (Edge/IoT MongoDB Alternative) - Not strictly MongoDB, but offers a **MongoDB-compatible API** for edge/IoT. - Targets embedded/edge use cases. - [https://objectbox.io/mongodb-alternative/](https://objectbox.io/mongodb-alternative/) --- ### Crunchy Bridge (Postgres-first but with MongoDB compatibility layers) - Focused on PostgreSQL primarily, but offers tools/plugins for **MongoDB interoperability**. - Sometimes chosen for migration paths. - [https://www.crunchydata.com/](https://www.crunchydata.com/) --- ### Private Cloud / Kubernetes-based MongoDB Operators - Not exactly SaaS, but widely adopted for MongoDB “cloud-like” experience: - **Percona Server for MongoDB (PSMDB)** → [https://www.percona.com/software/mongodb](https://www.percona.com/software/mongodb) - **Crunchy Data Kubernetes Operator** - **MongoDB Kubernetes Operator (by MongoDB)** - Ideal for enterprises who want **self-managed MongoDB cloud** in their private infra. --- ### OVHcloud – Databases for MongoDB - [https://us.ovhcloud.com/public-cloud/mongodb/](https://us.ovhcloud.com/public-cloud/mongodb/) - A true turn-key managed MongoDB DBaaS in OVH’s Public Cloud. Automates setup, maintenance, backups, elasticity, and security. Includes a free “Discovery” tier and paid production plans. --- ### Yandex Cloud – Managed Service for MongoDB - [https://cloud.yandex.com/services/mongodb](https://cloud.yandex.com/services/mongodb) - A fully managed MongoDB service within Yandex Cloud, ideal for users in CIS regions seeking low-latency or localized deployments. *(Note: Yandex Cloud support info inferred; page referenced in our ecosystem but no detailed site snippet)* --- ### Clever Cloud – Managed MongoDB Add-on - [https://www.clever-cloud.com/](https://www.clever-cloud.com/) - Offers a MongoDB-managed add-on with a free 500 MB plan—good for developers wanting seamless hosting with no overhead. --- ### Instaclustr - [https://www.instaclustr.com/mongodb/](https://www.instaclustr.com/mongodb/) - Enterprise-ready, SLA-backed platform offering managed MongoDB (plus Cassandra, Kafka, etc.) with robust support and performance guarantees. --- ### CloudClusters (MongoDB-as-a-Service) - [https://www.cloudclusters.io/mongodb](https://www.cloudclusters.io/mongodb) - Developer-friendly managed MongoDB on dedicated servers—features include automated backups, SSL, scaling, and straightforward pricing. --- ### Rackspace Cloud (via MongoLab Legacy) - [Website](http://www.rackspace.com](Website](http://www.rackspace.com) - Historically provided MongoDB through the MongoLab add-on in Rackspace’s cloud offerings. Now largely defunct since mLab’s acquisition by MongoDB. --- ### mLab (now legacy / absorbed) - [https://mlab.com/](https://mlab.com/) - Was a popular MongoDB-as-a-Service provider (formerly MongoLab), hosted on AWS, GCP, Azure, and PaaS like Heroku. Acquired by MongoDB Inc.; now users are migrated to Atlas. --- ### Linode Managed MongoDB Service - **Yes** — Linode offers a **managed MongoDB service** as part of its Managed Databases portfolio. - **Details**: - This is powered by **Akamai’s Linode Managed Database** offering and includes support for **MongoDB**, along with MySQL, PostgreSQL, and Redis. - It's a true **DBaaS (Database-as-a-Service)** — developers can provision MongoDB clusters directly from the Linode control panel. - **Website**: https://www.linode.com/products/databases/ --- ### A2 Hosting - [https://www.a2hosting.com/](https://www.a2hosting.com/) - Offers fully-managed MongoDB instances via their shared or VPS hosting. Geared toward users looking for low-cost, reliable MongoDB hosting with 24/7 support. --- ### Verpex Hosting - [https://www.verpex.com/](https://www.verpex.com/) - Cloud hosting solutions suitable for MongoDB deployments. While not a native DBaaS, it delivers quick setup and reliable performance for MongoDB instances. --- ### CoreWeave - [https://www.coreweave.com/](https://www.coreweave.com/) - Originally focused on GPU and HPC workloads, CoreWeave provides flexible infrastructure where MongoDB can be deployed manually. Great for AI/ML teams needing both compute and database hosting. --- ### Paperspace - [https://www.paperspace.com/](https://www.paperspace.com/) - Known for AI-focused cloud services, Paperspace lets developers spin up MongoDB manually on its VMs. Best suited for developers combining MongoDB with ML workflows. --- ### RunPod - [https://www.runpod.io/](https://www.runpod.io/) - AI-oriented cloud provider that also allows manual deployments of MongoDB. Popular among ML/AI startups needing fast infra with flexible pricing. --- ### Jelastic (by Virtuozzo) - [https://www.virtuozzo.com/](https://www.virtuozzo.com/) - A multi-cloud PaaS that supports running MongoDB as a containerized or Kubernetes service. Good for developers who want scalability without full DBA overhead. --- ### A2 Hosting - [https://www.a2hosting.com/](https://www.a2hosting.com/) - Offers MongoDB hosting on VPS and dedicated servers. Known for speed optimization and developer-friendly setup with 24/7 support. --- ### Verpex Hosting - [https://verpex.com/](https://verpex.com/) - Provides cloud hosting with support for MongoDB. Simple deployment, SSD storage, and global reach for small to medium projects. --- ### FastHosts - [https://www.fasthosts.co.uk/](https://www.fasthosts.co.uk/) - UK-based hosting provider. While not offering managed MongoDB, its VPS and cloud solutions are commonly used to deploy MongoDB manually. --- ### DreamHost (VPS) - [https://www.dreamhost.com/](https://www.dreamhost.com/) - Popular US-based hosting provider that supports MongoDB on VPS and dedicated servers. Developer-focused with open-source friendly policies. --- ### GreenGeeks - [https://www.greengeeks.com/](https://www.greengeeks.com/) - Eco-friendly hosting provider. Supports MongoDB deployment on VPS servers. Marketed toward small businesses and startups. --- ### Namecheap (VPS Hosting) - [https://www.namecheap.com/](https://www.namecheap.com/) - Known mostly for domains, but also offers VPS hosting where MongoDB can be installed. Affordable entry point for small MongoDB workloads. --- ### DigitalOcean (Managed Databases for MongoDB) - [https://www.digitalocean.com/products/managed-databases-mongodb](https://www.digitalocean.com/products/managed-databases-mongodb) - Fully managed MongoDB clusters with automatic failover, scaling, daily backups, and monitoring. A good choice for startups and developers seeking simplicity. --- ### FastComet - [https://www.fastcomet.com/](https://www.fastcomet.com/) - Provides MongoDB hosting on its VPS and cloud hosting plans. Known for excellent customer support, SSD storage, and developer-friendly environments. --- ### Bluehost - [https://www.bluehost.com/](https://www.bluehost.com/) - Popular hosting provider that supports MongoDB on VPS and dedicated hosting. Ideal for small businesses or developers wanting MongoDB alongside websites/apps. --- ### Ultahost - [https://ultahost.com/](https://ultahost.com/) - Offers secure and high-performance VPS hosting optimized for MongoDB. Includes free SSL, backups, and 24/7 support. --- ### InterServer - [https://www.interserver.net/](https://www.interserver.net/) - Budget-friendly VPS hosting provider with support for MongoDB. Pay-as-you-go pricing model and customizable configurations. --- ### IONOS by 1&1 - [https://www.ionos.com/](https://www.ionos.com/) - Affordable VPS hosting service supporting MongoDB deployments. Popular in Europe for cost-effective and reliable infrastructure. --- ### Vultr - [https://www.vultr.com/](https://www.vultr.com/) - Cloud infrastructure provider with powerful VPS solutions. Developers often use Vultr for manually hosting MongoDB with global data center options. --- ### Hetzner - [https://www.hetzner.com/](https://www.hetzner.com/) - German hosting and cloud provider. Offers affordable cloud servers widely used in Europe for hosting MongoDB clusters manually. --- ### Scaleway - [https://www.scaleway.com/](https://www.scaleway.com/) - European cloud provider offering flexible cloud instances. While not a managed MongoDB service, it’s widely used for deploying MongoDB manually. --- ### UpCloud - [https://www.upcloud.com/](https://www.upcloud.com/) - High-performance cloud servers with 100% uptime SLA. Developers deploy MongoDB manually on its ultra-fast VPS. --- --- # MySQL Connection Strings in API Maker > Learn how to format MySQL URI strings in API Maker—username, password, host, port, database name, and advanced options. Includes SSL, URL encoding tips, and examples for cloud-hosted services. Source: https://docs.apimaker.dev/v1/docs/Database-connection-string/mysql-connection-strings.html ## Introduction A **MySQL connection string** defines how API Maker connects to your MySQL database. It contains the necessary details such as **username, password, host, port, and database name**, along with optional parameters for **SSL, pooling, and timeouts**. This connection string is the starting point for establishing a secure and reliable connection to MySQL. --- ## MySQL Connection String Formats Below is a **visual breakdown** of the MySQL connection string. ```text mysql://username:password@host:port/DB_Name?options └───┬──┘ └───────┬───────┘ └───┬────┘ └──┬──┘ └──┬──┘ Scheme Credentials Host + Port Database Options ``` --- ## MySQL Connection String Examples ### 🧩 Connection String Parts Explained - **Protocol (scheme):** - `mysql://` → Standard MySQL connection. - `mariadb://` → Also accepted for MariaDB compatibility. - **Credentials (optional):** - Format: `username:password@`. - Use **URL-encoding** for special characters (`pa@ss` → `pa%40ss`). - **Host + Port:** - Default host: `localhost` - Default port: `3306` - **Database (optional):** - After `/`, e.g., `/mydb`. - If omitted, connection starts without selecting a default database. - **Options (query params):** - Added after `?`, key-value pairs separated by `&`. - Examples: - `?ssl-mode=REQUIRED` → Enforce SSL/TLS. - `?connectTimeout=5000` → Timeout in ms. - `?allowPublicKeyRetrieval=true` → Needed for some cloud connections. --- ### Basic Localhost Connection ```text mysql://localhost:3306 ``` ### Localhost with Database Name ```text mysql://localhost:3306/mydb ``` ### Localhost with Username & Password ```text mysql://user:password@localhost:3306/mydb ``` ### Localhost with SSL Enabled ```text mysql://user:password@localhost:3306/mydb?ssl=true ``` ### Localhost with Connection Timeout ```text mysql://user:password@localhost:3306/mydb?connectTimeout=10000 ``` ### Localhost with Charset Setting ```text mysql://user:password@localhost:3306/mydb?charset=utf8mb4 ``` ### Localhost with Multiple Options ```text mysql://user:password@localhost:3306/mydb?ssl=true&charset=utf8mb4&connectTimeout=10000 ``` ### Multi-Host / Cluster Connection ```text mysql://user:password@host1:3306,host2:3306,host3:3306/mydb ``` ### Replication Connection ```text mysql://replica_user:password@replica_host:3306/mydb?replication=true ``` ### Cloud Provider Connection (AWS RDS Example) ```text mysql://user:password@mydb.xxxxxx.us-east-1.rds.amazonaws.com:3306/mydb ``` ### Cloud with SSL Required (Azure Database for MySQL) ```text mysql://user:password@myserver.mysql.database.azure.com:3306/mydb?ssl=true ``` ### Cloud with Multi-Host Failover (Google Cloud SQL HA Setup) ```text mysql://user:password@host1:3306,host2:3306/mydb?ssl=true ``` --- ## MySQL Connection String Parts Breakdown |Part|Description|Example| |---|---|---| |**Scheme**|Protocol prefix for MySQL.|`mysql://`| |**Username** (optional)|Database username for authentication.|`user`| |**Password** (optional)|Password for authentication (URL-encoded if special chars).|`p%40ss` for `p@ss`| |**@**|Separator between credentials and host(s).|`user:password@`| |**Host(s)**|One or more MySQL server addresses.|`localhost`, `mydb.xxxxxx.us-east-1.rds.amazonaws.com`| |**Port** (optional)|Port number (default: `3306`).|`:3306`| |**Comma-separated Hosts**|Multiple hosts for HA / load balancing.|`host1:3306,host2:3306`| |**/** (slash after hosts)|Separator between hosts and default database.|`/`| |**Default Database**|Database name to connect to if none specified.|`mydb`| |**?options** (query params)|Connection options in `key=value` format (joined with `&`).|`?ssl-mode=REQUIRED&connect_timeout=10`| --- ## Connection String Options Breakdown and Explanation |Parameter|Description|Example Value| |---|---|---| |**ssl_mode**|Controls SSL/TLS usage (`DISABLED`, `PREFERRED`, `REQUIRED`, `VERIFY_CA`, `VERIFY_IDENTITY`).|`ssl_mode=REQUIRED`| |**ssl_ca**|Path to the CA certificate for SSL validation.|`ssl_ca=/etc/ssl/ca.pem`| |**ssl_cert**|Path to client SSL certificate.|`ssl_cert=/etc/ssl/client-cert.pem`| |**ssl_key**|Path to client SSL key.|`ssl_key=/etc/ssl/client-key.pem`| |**connect_timeout**|Maximum wait time (in seconds) for a new connection.|`connect_timeout=10`| |**database**|Default database to connect to.|`database=mydb`| |**charset**|Sets the character set for the connection.|`charset=utf8mb4`| |**autocommit**|Controls autocommit mode (`0` = off, `1` = on).|`autocommit=1`| |**allow_multi_statements**|Allow executing multiple SQL statements in one query.|`allow_multi_statements=true`| |**read_timeout**|Seconds to wait for a read operation before timing out.|`read_timeout=30`| |**write_timeout**|Seconds to wait for a write operation before timing out.|`write_timeout=30`| |**max_allowed_packet**|Maximum packet size (bytes) for sending/receiving data.|`max_allowed_packet=67108864`| |**allowPublicKeyRetrieval**|Allows retrieval of RSA public key for secure password exchange.|`allowPublicKeyRetrieval=true`| |**useSSL**|Deprecated SSL flag; prefer `ssl_mode`.|`useSSL=true`| |**serverTimezone**|Sets the timezone for the connection.|`serverTimezone=UTC`| |**replication**|For replication connections (`true` or `database`).|`replication=true`| --- ## Secure Connection Best Practices - Always enable **SSL/TLS (`sslmode=require`)** in production. - Use **environment variables** instead of hardcoding credentials. - Leverage **API Maker Secrets Management** to store and rotate sensitive keys. - Restrict MySQL users to **least privilege access**. - Use **firewall rules** or `bind-address` to allow only trusted IP addresses. - Enable **connection timeouts** to prevent hanging connections. --- ## Connecting MySQL in API Maker 1. Open **API Maker Dashboard** → **Secret Management** → **Default**. 2. Select **MySQL** as the database type. 3. Paste your MySQL connection string (with DB and SSL parameters). 4. Click **Test Connection** to verify connectivity. 5. Save the configuration. Once connected, you can: - Define **schemas** for your MySQL tables. - Query data using `/api/schema/...` endpoints. - Perform **cross-database joins** with PostgreSQL, MongoDB, or SQL Server. --- ## Troubleshooting When connecting to MySQL (local, self-hosted, or managed services like RDS/Azure/CloudSQL), you may encounter several common errors. Below is a categorized list with explanations: --- ### 🔑 Authentication & Authorization Errors | Error Code | Name | Description | Common Fix | |------------|-----------------------|----------------------------------------------------|--------------------------------------------------------| | 1045 | AccessDenied | Wrong username or password. | Verify username/password in connection string. | | 1130 | HostNotAllowed | User cannot connect from this host. | Grant privileges using `GRANT` and whitelist IP. | | 1044 | InsufficientPrivilege | User does not have privileges on the database. | Grant required privileges using `GRANT`. | | 1203 | TooManyConnections | Connection limit exceeded. | Increase `max_connections` or close idle sessions. | --- ### 🌐 Network & Connectivity Errors | Error Code | Name | Description | Common Fix | |------------|----------------------------|----------------------------------------------|----------------------------------------------------------| | 2002 | ConnectionRefused | Client could not connect to server. | Verify hostname, port, and firewall rules. | | 2003 | ConnectionFailure | Connection unexpectedly terminated. | Check server logs, network stability. | | 2013 | LostConnection | Connection was closed unexpectedly. | Reconnect before executing queries. | | 1047 | ServerShutdown | Server shutting down or not responding. | Wait for restart and reconnect. | --- ### 🗂️ Duplicate & Constraint Errors | Error Code | Name | Description | Common Fix | |------------|-------------------------|--------------------------------------------|--------------------------------------------------------| | 1062 | DuplicateEntry | Duplicate value violates unique constraint.| Ensure unique values or handle conflict with `ON DUPLICATE KEY UPDATE`. | | 1452 | ForeignKeyViolation | Insert/update violates foreign key. | Ensure referenced key exists before inserting/updating. | | 3819 | CheckConstraintViolation | Value violates a `CHECK` constraint. | Insert valid values matching constraint rules. | | 1215 | ForeignKeyConstraintFail | Foreign key constraint creation failed. | Verify referenced keys and types match. | --- ### ⚡ Write & Transaction Errors | Error Code | Name | Description | Common Fix | |------------|----------------------|------------------------------------------------|---------------------------------------------------------| | 1213 | DeadlockFound | Deadlock occurred between transactions. | Redesign queries/locking, or retry after backoff. | | 1205 | LockWaitTimeout | Transaction timed out waiting for a lock. | Retry the transaction or increase lock wait timeout. | | 1048 | NotNullViolation | Tried to insert NULL into NOT NULL column. | Provide a value or alter column to allow NULL. | | 1021 | DiskFull | Server ran out of disk space. | Free disk space or increase storage. | --- ### 🗄️ Query & Syntax Errors | Error Code | Name | Description | Common Fix | |------------|------------------------|------------------------------------------|---------------------------------------------------------| | 1064 | SyntaxError | Invalid SQL syntax. | Fix query syntax. | | 1054 | UnknownColumn | Column does not exist. | Check column name spelling or schema. | | 1146 | TableDoesNotExist | Table does not exist. | Create table or use correct table name. | | 1305 | UnknownFunction | Function/operator not defined. | Define function or cast arguments properly. | | 1065 | EmptyQuery | Query is empty or invalid. | Provide a valid SQL statement. | --- ### 🔒 TLS/SSL & Security Errors | Error Code | Name | Description | Common Fix | |------------|----------------------|------------------------------------------------|-------------------------------------------------------------| | 2026 | SSLConnectionError | TLS/SSL negotiation failed. | Ensure certificates, SSL mode (`require`, `verify-ca`). | | 1045 | AccessDeniedSSL | User not allowed for SSL/host restrictions. | Grant access or adjust SSL-related configs. | | 1040 | TooManyConnectionsSSL | Too many SSL connections. | Increase `max_connections` or close idle sessions. | --- ### 🛠️ Miscellaneous Errors | Error Code | Name | Description | Common Fix | |------------|------------------------|----------------------------------------------|-------------------------------------------| | 1221 | IncorrectKeyFile | Query exceeded internal limit or config issue.| Check table indexes or optimize query. | | 1114 | TableIsFull | Table storage limit reached. | Free disk space or increase table size. | | 1406 | DataTooLong | Value too long for column type. | Increase column size or truncate data. | | 1194 | InternalError | Unexpected internal error. | Check server logs and MySQL version. | --- ### 🧩 Error Types Summary | Category | Typical Causes | Example Error Codes | | ---------------------------------- | ------------------------------------------------ | ----------------------------- | | **Authentication & Authorization** | Invalid login, role issues, too many connections | 1045, 1130, 1044, 1203 | | **Network & Connectivity** | Host unreachable, server shutdown/recovery | 2002, 2003, 2013, 1047 | | **Constraints & Duplicates** | Unique, foreign key, check constraint violations | 1062, 1452, 3819, 1215 | | **Write & Transaction** | Deadlocks, lock timeouts, null inserts | 1213, 1205, 1048, 1021 | | **Query & Syntax** | Invalid SQL syntax, undefined columns/functions | 1064, 1054, 1146, 1305 | | **TLS/SSL & Security** | SSL handshake errors, host/user restrictions | 2026, 1045, 1040 | | **Miscellaneous** | Data truncation, table full, internal errors | 1221, 1114, 1406, 1194 | - **Authentication** covers errors like `1045 (AccessDenied)` and `1130 (HostNotAllowed)`. - **Network** includes connection shutdowns (`2002`, `2003`) and lost connections (`2013`). - **Transactions** cover deadlocks and lock timeouts (`1213`, `1205`). - **Query** includes syntax issues and unknown columns/functions (`1064`, `1054`). - **Constraints** cover duplicate entries and foreign key violations (`1062`, `1452`). - **Miscellaneous** includes disk/table space issues (`1114`, `1406`) and internal errors (`1194`). --- ## FAQ **Q1:** _Is `ssl-mode=REQUIRED` mandatory?_ Not strictly, but it’s strongly recommended for production to secure data in transit. **Q2:** _Can I use MySQL cloud services like AWS RDS, Azure Database, or GCP Cloud SQL?_ Yes. Just provide the full connection string from your provider and whitelist API Maker’s IP. **Q3:** _How do I connect to a MySQL instance running in Docker?_ Use the container’s IP or host machine IP, e.g., `mysql://user:pass@172.17.0.2:3306/mydb`. --- ## MySQL Cloud Providers - **API Maker Cloud** - Provides a fully managed MySQL instance with instant APIs and schema management. - Ideal for API-first development where you want to skip manual server setup. - Offers seamless integration with API Maker features such as auto-increment, joins, and advanced querying. - Perfect for small to medium projects needing fast prototyping. - [apimaker.dev](https://apimaker.dev) - **Amazon RDS** - Fully managed MySQL database with automated backups, patching, and failover support. - Suitable for production workloads running on AWS services like EC2, Lambda, or S3. - High availability, monitoring, and security are built-in with minimal operational overhead. - Offers flexibility to scale up or out depending on workload demands. - [aws.amazon.com/rds/mysql](https://aws.amazon.com/rds/mysql/) - **Amazon Aurora** - MySQL-compatible relational database optimized for high performance and low latency. - Supports read replicas and multi-AZ deployment for high availability. - Fully managed by AWS, reducing operational complexity while enabling auto-scaling. - Ideal for applications requiring enterprise-grade reliability and performance. - [aws.amazon.com/rds/aurora](https://aws.amazon.com/rds/aurora/mysql-features/) - **Google Cloud SQL** - Fully managed MySQL with automated failover, backups, and maintenance. - Integrates seamlessly with other Google Cloud services like BigQuery, GKE, and Cloud Storage. - Provides easy scaling and built-in monitoring to handle variable workloads. - Excellent for cloud-native applications running on Google Cloud infrastructure. - [cloud.google.com/sql/mysql](https://cloud.google.com/sql/mysql) - **Azure Database** - Managed MySQL service with enterprise-grade security and compliance features. - Offers automatic scaling, high availability, and point-in-time backups. - Perfect for workloads within the Azure ecosystem or hybrid cloud setups. - Provides integration with other Azure services such as App Services and Functions. - [azure.microsoft.com/mysql](https://azure.microsoft.com/en-us/products/mysql) - **Aiven** - Managed MySQL service across multiple cloud providers including AWS, GCP, and Azure. - Offers automated backups, monitoring, and high availability for production-ready workloads. - Supports multi-cloud deployments with easy migrations and flexible configurations. - Designed for teams wanting full cloud flexibility with minimal operational effort. - [aiven.io/mysql](https://aiven.io/mysql) - **ScaleGrid** - Managed MySQL with full root access for advanced configuration and automation. - Balances automation with full customization for production-grade databases. - Supports deployment across multiple cloud providers with easy scaling and high availability. - Excellent for enterprises and developers requiring control without managing servers manually. - [scalegrid.io/mysql-hosting](https://scalegrid.io/mysql-hosting/) - **PlanetScale** - Serverless MySQL platform built for massive scale and performance. - Offers zero-downtime schema changes and branch-based development workflow. - Globally distributed with high availability and automatic failover. - Ideal for modern cloud-native applications needing horizontal scaling. - [planetscale.com](https://planetscale.com) - **ClearDB** - Specialized MySQL cloud service focused on web apps and SaaS deployments. - Provides automated scaling, backups, and high availability with minimal management overhead. - Supports multi-cloud deployments across AWS and other providers. - Perfect for applications hosted on PaaS platforms like Heroku. - [cleardb.com](https://www.cleardb.com) - **Clever Cloud MySQL** - Fully managed MySQL with automatic updates, backups, and replication. - Offers high availability and horizontal scaling for enterprise applications. - Supports secure connections and monitoring dashboards for better observability. - Designed specifically for developers seeking hands-off database management. - [clever-cloud.com/mysql](https://www.clever-cloud.com/mysql) --- ## Related Links - [MySQL Connection String Documentation](https://dev.mysql.com/doc/dev/connector-nodejs/latest/) - [API Maker Schemas Overview](https://docs.apimaker.dev/v1/docs/schema/schema.html) - [API Maker Secrets Management](https://docs.apimaker.dev/v1/docs/secrets/secrets.html) --- --- # MariaDB Connection Strings in API Maker > Learn how to format MariaDB URI strings in API Maker—username, password, host, port, database name, and advanced options. Includes SSL, URL encoding tips, and examples for cloud-hosted services. Source: https://docs.apimaker.dev/v1/docs/Database-connection-string/mariadb-connection-strings.html ## Introduction A **MariaDB connection string** defines how API Maker connects to your MariaDB database. It contains essential information such as **username, password, host, port, and database name**, along with optional parameters for **SSL, pooling, and timeouts**. This connection string is the foundation for establishing a secure, reliable connection to MariaDB and leveraging API Maker features such as schema definition, cross-database joins, and API queries. --- ## MariaDB Connection String Formats Below is a **visual breakdown** of the MariaDB connection string. ``` mariadb://username:password@host:port/DB_Name?options └───┬──┘ └───────┬───────┘ └───┬────┘ └──┬──┘ └──┬──┘ Scheme Credentials Host + Port Database Options ``` ## MariaDB Connection String Examples ### 🧩 Connection String Parts Explained - **Protocol (scheme):** - `mariadb://` → Standard MariaDB connection. - **Credentials (optional):** - Format: `username:password@`. - Use **URL-encoding** for special characters (`pa@ss` → `pa%40ss`). - **Host + Port:** - Default host: `localhost` - Default port: `3306` - **Database (optional):** - After `/`, e.g., `/mydb`. - If omitted, connection starts without selecting a default database. - **Options (query params):** - Added after `?`, key-value pairs separated by `&`. - Examples: - `?ssl=true` → Enforce SSL/TLS. - `?connectTimeout=10000` → Timeout in ms. - `?allowPublicKeyRetrieval=true` → Needed for certain cloud setups. --- ### Basic Localhost Connection ```text mariadb://localhost:3306 ``` ### Localhost with Database Name ```text mariadb://localhost:3306/mydb ``` ### Localhost with Username & Password ```text mariadb://user:password@localhost:3306/mydb ``` ### Localhost with SSL Enabled ```text mariadb://user:password@localhost:3306/mydb?ssl=true ``` ### Localhost with Connection Timeout ```text mariadb://user:password@localhost:3306/mydb?connectTimeout=10000 ``` ### Localhost with Charset Setting ```text mariadb://user:password@localhost:3306/mydb?charset=utf8mb4 ``` ### Localhost with Multiple Options ```text mariadb://user:password@localhost:3306/mydb?ssl=true&charset=utf8mb4&connectTimeout=10000 ``` ### Multi-Host / Cluster Connection ```text mariadb://user:password@host1:3306,host2:3306,host3:3306/mydb ``` ### Replication Connection ```text mariadb://replica_user:password@replica_host:3306/mydb?replication=true ``` ### Cloud Provider Connection Example ```text mariadb://user:password@mydb.instance.region.mdbcloud.com:3306/mydb ``` ### Cloud with SSL Required ```text mariadb://user:password@mydb.instance.region.mdbcloud.com:3306/mydb?ssl=true ``` ### Cloud with Multi-Host Failover ```text mariadb://user:password@host1:3306,host2:3306/mydb?ssl=true ``` --- ## MariaDB Connection String Parts Breakdown |Part|Description|Example| |---|---|---| |**Scheme**|Protocol prefix for MariaDB.|`mariadb://`| |**Username** (optional)|Database username for authentication.|`user`| |**Password** (optional)|Password for authentication (URL-encoded if special characters).|`p%40ss` for `p@ss`| |**@**|Separator between credentials and host(s).|`user:password@`| |**Host(s)**|One or more MariaDB server addresses.|`localhost`, `mydb.xxxxxx.us-east-1.rds.amazonaws.com`| |**Port** (optional)|Port number (default: `3306`).|`:3306`| |**Comma-separated Hosts**|Multiple hosts for HA / load balancing.|`host1:3306,host2:3306`| |**/** (slash after hosts)|Separator between hosts and default database.|`/`| |**Default Database**|Database name to connect to if none specified.|`mydb`| |**?options** (query params)|Connection options in `key=value` format (joined with `&`).|`?ssl=true&connectTimeout=10000&charset=utf8mb4`| --- ## MariaDB Connection String Options Breakdown and Explanation |Parameter|Description|Example Value| |---|---|---| |**ssl_mode**|Controls SSL/TLS usage (`DISABLED`, `PREFERRED`, `REQUIRED`, `VERIFY_CA`, `VERIFY_IDENTITY`).|`ssl_mode=REQUIRED`| |**ssl_ca**|Path to the CA certificate for SSL validation.|`ssl_ca=/etc/ssl/ca.pem`| |**ssl_cert**|Path to client SSL certificate.|`ssl_cert=/etc/ssl/client-cert.pem`| |**ssl_key**|Path to client SSL key.|`ssl_key=/etc/ssl/client-key.pem`| |**connect_timeout**|Maximum wait time (in seconds) for establishing a new connection.|`connect_timeout=10`| |**database**|Default database to connect to.|`database=mydb`| |**charset**|Sets the character set for the connection.|`charset=utf8mb4`| |**autocommit**|Controls autocommit mode (`0` = off, `1` = on).|`autocommit=1`| |**allow_multi_statements**|Allow executing multiple SQL statements in one query.|`allow_multi_statements=true`| |**read_timeout**|Seconds to wait for a read operation before timing out.|`read_timeout=30`| |**write_timeout**|Seconds to wait for a write operation before timing out.|`write_timeout=30`| |**max_allowed_packet**|Maximum packet size (bytes) for sending/receiving data.|`max_allowed_packet=67108864`| |**allowPublicKeyRetrieval**|Allows retrieval of RSA public key for secure password exchange.|`allowPublicKeyRetrieval=true`| |**useSSL**|Deprecated SSL flag; prefer `ssl_mode`.|`useSSL=true`| |**serverTimezone**|Sets the timezone for the connection.|`serverTimezone=UTC`| |**replication**|For replication connections (`true` or `database`).|`replication=true`| --- ## Secure Connection Best Practices - Always enable **SSL/TLS (`ssl=true` or `ssl-mode=REQUIRED`)** in production to protect data in transit. - Use **environment variables** instead of hardcoding database credentials in your connection strings. - Leverage **API Maker Secrets Management** to securely store, manage, and rotate sensitive keys. - Restrict MariaDB users to **least privilege access**, only granting necessary permissions. - Use **firewall rules** or `bind-address` settings to allow connections only from trusted IP addresses. - Enable **connection and read/write timeouts** to prevent hanging connections and improve reliability. --- ## Connecting MariaDB in API Maker 1. Open **API Maker Dashboard** → **Secret Management** → **Default**. 2. Select **MariaDB** as the database type. 3. Paste your MariaDB connection string (including DB and SSL parameters). 4. Click **Test Connection** to verify connectivity. 5. Save the configuration. Once connected, you can: - Define **schemas** for your MariaDB tables. - Query data using `/api/schema/...` endpoints. - Perform **cross-database joins** with MySQL, PostgreSQL, or MongoDB. --- ## Troubleshooting When connecting to MariaDB (local, self-hosted, or managed cloud instances), you may encounter several common errors. Below is a categorized list with explanations: --- ### 🔑 Authentication & Authorization Errors |Error Code|Name|Description|Common Fix| |---|---|---|---| |1045|AccessDenied|Wrong username or password.|Verify username/password in connection string.| |1130|HostNotAllowed|User cannot connect from this host.|Grant privileges using `GRANT` and whitelist IP.| |1044|InsufficientPrivilege|User does not have privileges on the database.|Grant required privileges using `GRANT`.| |1203|TooManyConnections|Connection limit exceeded.|Increase `max_connections` or close idle sessions.| --- ### 🌐 Network & Connectivity Errors |Error Code|Name|Description|Common Fix| |---|---|---|---| |2002|ConnectionRefused|Client could not connect to server.|Verify hostname, port, and firewall rules.| |2003|ConnectionFailure|Connection unexpectedly terminated.|Check server logs and network stability.| |2013|LostConnection|Connection was closed unexpectedly.|Reconnect before executing queries.| |1047|ServerShutdown|Server shutting down or not responding.|Wait for restart and reconnect.| --- ### 🗂️ Duplicate & Constraint Errors |Error Code|Name|Description|Common Fix| |---|---|---|---| |1062|DuplicateEntry|Duplicate value violates unique constraint.|Ensure unique values or handle conflict with `ON DUPLICATE KEY UPDATE`.| |1452|ForeignKeyViolation|Insert/update violates foreign key.|Ensure referenced key exists before inserting/updating.| |3819|CheckConstraintViolation|Value violates a `CHECK` constraint.|Insert valid values matching constraint rules.| |1215|ForeignKeyConstraintFail|Foreign key constraint creation failed.|Verify referenced keys and types match.| --- ### ⚡ Write & Transaction Errors |Error Code|Name|Description|Common Fix| |---|---|---|---| |1213|DeadlockFound|Deadlock occurred between transactions.|Redesign queries/locking, or retry after backoff.| |1205|LockWaitTimeout|Transaction timed out waiting for a lock.|Retry the transaction or increase lock wait timeout.| |1048|NotNullViolation|Tried to insert NULL into NOT NULL column.|Provide a value or alter column to allow NULL.| |1021|DiskFull|Server ran out of disk space.|Free disk space or increase storage.| --- ### 🗄️ Query & Syntax Errors |Error Code|Name|Description|Common Fix| |---|---|---|---| |1064|SyntaxError|Invalid SQL syntax.|Fix query syntax.| |1054|UnknownColumn|Column does not exist.|Check column name spelling or schema.| |1146|TableDoesNotExist|Table does not exist.|Create table or use correct table name.| |1305|UnknownFunction|Function/operator not defined.|Define function or cast arguments properly.| |1065|EmptyQuery|Query is empty or invalid.|Provide a valid SQL statement.| --- ### 🔒 TLS/SSL & Security Errors |Error Code|Name|Description|Common Fix| |---|---|---|---| |2026|SSLConnectionError|TLS/SSL negotiation failed.|Ensure certificates, SSL mode (`REQUIRED`, `VERIFY_CA`).| |1045|AccessDeniedSSL|User not allowed for SSL/host restrictions.|Grant access or adjust SSL-related configs.| |1040|TooManyConnectionsSSL|Too many SSL connections.|Increase `max_connections` or close idle sessions.| --- ### 🛠️ Miscellaneous Errors |Error Code|Name|Description|Common Fix| |---|---|---|---| |1221|IncorrectKeyFile|Query exceeded internal limit or config issue.|Check table indexes or optimize query.| |1114|TableIsFull|Table storage limit reached.|Free disk space or increase table size.| |1406|DataTooLong|Value too long for column type.|Increase column size or truncate data.| |1194|InternalError|Unexpected internal error.|Check server logs and MariaDB version.| --- ### 🧩 Error Types Summary |Category|Typical Causes|Example Error Codes| |---|---|---| |**Authentication & Authorization**|Invalid login, role issues, too many connections|1045, 1130, 1044, 1203| |**Network & Connectivity**|Host unreachable, server shutdown/recovery|2002, 2003, 2013, 1047| |**Constraints & Duplicates**|Unique, foreign key, check constraint violations|1062, 1452, 3819, 1215| |**Write & Transaction**|Deadlocks, lock timeouts, null inserts|1213, 1205, 1048, 1021| |**Query & Syntax**|Invalid SQL syntax, undefined columns/functions|1064, 1054, 1146, 1305| |**TLS/SSL & Security**|SSL handshake errors, host/user restrictions|2026, 1045, 1040| |**Miscellaneous**|Data truncation, table full, internal errors|1221, 1114, 1406, 1194| --- ## FAQ **Q1:** _Can I use MariaDB cloud services like Amazon RDS, Azure Database, or Google Cloud SQL?_ Yes. Just provide the full connection string from your provider and whitelist API Maker’s IP. **Q2:** _How do I connect to a MariaDB instance running in Docker?_ Use the container’s IP or host machine IP, e.g., `mariadb://user:pass@172.17.0.2:3306/mydb`. **Q3:** _How do I handle timezone differences in MariaDB connections?_ You can set the timezone in the connection string using `?serverTimezone=UTC` or another supported timezone. **Q4:** _Is it possible to connect to multiple MariaDB instances for replication or failover?_ Yes. You can provide multiple hosts in the connection string separated by commas, e.g., `mariadb://user:pass@host1:3306,host2:3306/mydb?replication=true`. **Q5:** _Can I enable automatic reconnection on connection drops?_ Yes. Use the `?autoReconnect=true` option in your connection string to allow automatic reconnection. --- --- ## MariaDB Cloud Providers - **MariaDB SkySQL** - Official cloud service by MariaDB Corporation, designed specifically for MariaDB. - Fully managed with automated backups, scaling, high availability, and monitoring. - Offers enterprise features like advanced security, clustering, and hybrid cloud deployment. - Ideal for businesses seeking production-ready MariaDB with official support. - [mariadb.com/products/skysql](https://mariadb.com/products/skysql) - **DB4Free MariaDB** - Free-to-use MariaDB hosting for development, testing, and learning purposes. - Provides an easy-to-use web interface and standard MariaDB features. - Not intended for production, but great for prototyping and experiments. - Offers latest MariaDB versions to try new features. - [db4free.net](https://www.db4free.net/) - **PlanetScale (MariaDB compatible)** - Serverless platform supporting MariaDB-compatible workloads for modern applications. - Offers horizontal scaling, zero-downtime schema changes, and automatic failover. - Designed for global, distributed cloud-native applications. - Ideal for developers requiring elasticity without managing database servers. - [planetscale.com](https://planetscale.com) - **MariaDB SkyCloud** - Managed MariaDB with cloud-native optimizations. - High availability, automated backups, and global replication. - Enterprise-grade features like query optimization, security, and analytics integration. - Best for organizations looking for MariaDB-exclusive managed service. - [mariadb.com/products/skycloud](https://mariadb.com/) - **API Maker Cloud** - Fully managed MariaDB instance with instant API generation and schema management. - Seamless integration with API Maker features such as auto-increment, joins, and advanced querying. - Perfect for small to medium projects needing rapid prototyping and production-ready deployments. - [apimaker.dev](https://apimaker.dev) - **ScaleGrid (MariaDB)** - Managed MariaDB with root access for advanced configuration and automation. - Supports multi-cloud deployments with high availability, scaling, and monitoring. - Ideal for developers who want control without manual server management. - [scalegrid.io/mariadb-hosting](https://scalegrid.io/mariadb-hosting/) --- ## Related Links - [MariaDB Connection String Documentation](https://mariadb.com/docs/server/server-usage/connecting/mariadb-connecting-guide-1) - [API Maker Schemas Overview](https://docs.apimaker.dev/v1/docs/schema/schema.html) - [API Maker Secrets Management](https://docs.apimaker.dev/v1/docs/secrets/secrets.html) --- # PostgreSQL Connection Strings in API Maker > Learn how to format Postgres URI strings in API Maker—user, password, host, port, database name. Includes URL encoding tips for special chars. Source: https://docs.apimaker.dev/v1/docs/Database-connection-string/postgres-connection-strings.html ## Introduction When integrating PostgreSQL with **API Maker**, the connection string defines how API Maker connects to your PostgreSQL database. It includes authentication details, host, port, database name, and optional parameters such as SSL, timezones, and advanced settings. --- ## PostgreSQL Connection String Formats Below is a **visual breakdown** of the PostgreSQL connection string. ```text postgres://username:password@host:port/DB_Name?options └───┬──┘ └───────┬───────┘ └───┬────┘ └──┬──┘ └──┬──┘ Scheme Credentials Host + Port Database Options ``` ## PostgreSQL Connection String Examples ### 🧩 Connection String Parts Explained - **Protocol (scheme):** - `postgres://` → Standard PostgreSQL connection. - `postgresql://` → Alternative, fully valid scheme (interchangeable). - **Credentials (optional):** - Format: `username:password@`. - Use **URL-encoding** for special characters (`my@pass` → `my%40pass`). - **Host + Port:** - Single host → `localhost:5432` - Custom port if not default (5432). - **Database (optional):** - After `/`, e.g., `/mydb`. - If omitted, PostgreSQL defaults to the **username** as the database. - **Options (query params):** - After `?`, key-value pairs separated by `&`. - Examples: `?sslmode=require&application_name=apimaker` --- ### Basic Localhost Connection ```text postgres://localhost:5432 ``` ### Localhost with Database Name ```text postgres://localhost:5432/mydb ``` ### Localhost with Username & Password ```text postgres://user:password@localhost:5432/mydb ``` ### Localhost with SSL Enabled ```text postgres://user:password@localhost:5432/mydb?sslmode=require ``` ### Localhost with Application Name ```text postgres://user:password@localhost:5432/mydb?application_name=apimaker ``` ### Connection with Search Path (Schema) ```text postgres://user:password@localhost:5432/mydb?options=-csearch_path%3Dmyschema ``` ### Connection with Timeouts ```text postgres://user:password@localhost:5432/mydb?connect_timeout=10 ``` ### Connection with Pool Size (via pgpool / pgbouncer params) ```text postgres://user:password@localhost:5432/mydb?application_name=MyApp&pool_size=20 ``` ### Multi-Host Failover (Cluster / HA Setup) ```text postgres://user:password@host1:5432,host2:5432,host3:5432/mydb?target_session_attrs=read-write ``` ### Replication Connection ```text postgres://replica_user:password@replica_host:5432/mydb?replication=true ``` ### Cloud Provider Connection (AWS RDS Example) ```text postgres://user:password@mydb.xxxxxx.us-east-1.rds.amazonaws.com:5432/mydb ``` ### Cloud with SSL Required (Azure Database for PostgreSQL) ```text postgres://user:password@myserver.postgres.database.azure.com:5432/mydb?sslmode=require ``` ### Cloud with Multi-Host Failover (Google Cloud SQL HA Setup) ```text postgres://user:password@host1:5432,host2:5432/mydb?target_session_attrs=read-write&sslmode=require ``` --- ## Connection String Parts Breakdown | Part | Description | Example | |-----------------------------|-------------------------------------------------------------|--------------------------------------------------------| | **Scheme** | Protocol prefix for PostgreSQL. | `postgres://` or `postgresql://` | | **Username** (optional) | Database username for authentication. | `user` | | **Password** (optional) | Password for authentication (URL-encoded if special chars). | `p%40ss` for `p@ss` | | **@** | Separator between credentials and host(s). | `user:password@` | | **Host(s)** | One or more PostgreSQL server addresses. | `localhost`, `mydb.xxxxxx.us-east-1.rds.amazonaws.com` | | **Port** (optional) | Port number (default: `5432`). | `:5432` | | **Comma-separated Hosts** | Multiple hosts for HA / load balancing. | `host1:5432,host2:5432` | | **/** (slash after hosts) | Separator between hosts and default database. | `/` | | **Default Database** | Database name to connect to if none specified. | `mydb` | | **?options** (query params) | Connection options in `key=value` format (joined with `&`). | `?sslmode=require&connect_timeout=10` | --- ## Connection String Options Breakdown and Explanation | Parameter | Description | Example Value | |--------------------------|-----------------------------------------------------------------------------|-------------------------------------| | **sslmode** | Controls SSL/TLS usage (`disable`, `require`, `verify-ca`, `verify-full`). | `sslmode=require` | | **sslrootcert** | Path to the root CA certificate for SSL validation. | `sslrootcert=/etc/ssl/ca.pem` | | **sslcert** | Path to client SSL certificate. | `sslcert=/etc/ssl/client.crt` | | **sslkey** | Path to client SSL key. | `sslkey=/etc/ssl/client.key` | | **connect_timeout** | Maximum wait time (in seconds) for a new connection. | `connect_timeout=10` | | **application_name** | Sets a name for the application, shown in PostgreSQL logs/pg_stat_activity. | `application_name=MyApp` | | **options** | Runtime parameters passed at session start. | `options='-c search_path=myschema'` | | **target_session_attrs** | Ensures connection only to certain servers (`read-write`, `any`). | `target_session_attrs=read-write` | | **keepalives** | Enables TCP keepalives (`1` = on, `0` = off). | `keepalives=1` | | **keepalives_idle** | Seconds of idle time before keepalive probes are sent. | `keepalives_idle=30` | | **keepalives_interval** | Interval (seconds) between keepalive probes. | `keepalives_interval=10` | | **keepalives_count** | Number of failed keepalive probes before dropping connection. | `keepalives_count=5` | | **tcp_user_timeout** | Timeout (ms) for unacknowledged TCP packets before connection is closed. | `tcp_user_timeout=5000` | | **gssencmode** | GSSAPI encryption (`disable`, `prefer`, `require`). | `gssencmode=prefer` | | **krbsrvname** | Kerberos service name for authentication (default: `postgres`). | `krbsrvname=postgres` | | **replication** | Used for replication connections (`true` or `database`). | `replication=true` | --- ## Secure Connection Best Practices - Always enable **SSL/TLS (`sslmode=require`)** in production. - Use **environment variables** instead of hardcoding credentials. - Leverage **API Maker Secrets Management** to store and rotate sensitive keys. - Restrict PostgreSQL users to **least privilege access**. - Use **firewall rules** or `pg_hba.conf` to allow only trusted IP addresses. - Enable **connection timeouts** to prevent hanging connections. --- ## Connecting PostgreSQL in API Maker 1. Open **API Maker Dashboard** → **Secret Management** → **Default**. 2. Select **PostgreSQL** as the database type. 3. Paste your PostgreSQL connection string (with DB and SSL parameters). 4. Click **Test Connection** to verify connectivity. 5. Save the configuration. Once connected, you can: - Define **schemas** for your PostgreSQL tables. - Query data using `/api/schema/...` endpoints. - Perform **cross-database joins** with MySQL, MongoDB, or SQL Server. --- ## Troubleshooting When connecting to PostgreSQL (local, self-hosted, or managed services like RDS/Azure/CloudSQL), you may encounter several common errors. Below is a categorized list with explanations: --- ### 🔑 Authentication & Authorization Errors | Error Code | Name | Description | Common Fix | |------------|-----------------------|-----------------------------------------------------|---------------------------------------------------------| | 28P01 | InvalidPassword | Wrong password for given user. | Verify username/password in connection string. | | 28000 | InvalidAuthorization | Role does not exist or is not permitted to connect. | Grant role access with `CREATE ROLE` / `GRANT CONNECT`. | | 42501 | InsufficientPrivilege | User does not have privileges on the object. | Grant required privileges using `GRANT`. | | 53300 | TooManyConnections | Connection limit exceeded. | Increase `max_connections` or close idle sessions. | --- ### 🌐 Network & Connectivity Errors | Error Code | Name | Description | Common Fix | |------------|--------------------------------------|--------------------------------------|--------------------------------------------| | 08001 | SQLClientUnableToEstablishConnection | Client could not connect to server. | Verify hostname, port, and firewall rules. | | 08006 | ConnectionFailure | Connection unexpectedly terminated. | Check server logs, network stability. | | 08003 | ConnectionDoesNotExist | Tried to use a closed connection. | Reconnect before executing queries. | | 57P01 | AdminShutdown | Server shutting down. | Wait for restart and reconnect. | | 57P03 | CannotConnectNow | Server is starting up / in recovery. | Retry after startup completes. | --- ### 🗂️ Duplicate & Constraint Errors | Error Code | Name | Description | Common Fix | |------------|---------------------|---------------------------------------------|-------------------------------------------------------------| | 23505 | UniqueViolation | Duplicate value violates unique constraint. | Ensure unique values or handle conflict with `ON CONFLICT`. | | 23503 | ForeignKeyViolation | Insert/update violates foreign key. | Ensure referenced key exists before inserting/updating. | | 23514 | CheckViolation | Value violates a `CHECK` constraint. | Insert valid values matching constraint rules. | | 23P01 | ExclusionViolation | Insert conflicts with exclusion constraint. | Adjust values or change exclusion policy. | --- ### ⚡ Write & Transaction Errors | Error Code | Name | Description | Common Fix | |------------|----------------------|------------------------------------------------|---------------------------------------------------------| | 40001 | SerializationFailure | Concurrent transaction conflict. | Retry the transaction. | | 40P01 | DeadlockDetected | Deadlock occurred between transactions. | Redesign queries/locking, or retry after backoff. | | 23502 | NotNullViolation | Tried to insert NULL into NOT NULL column. | Provide a value or alter column to allow NULL. | | 53100 | DiskFull | Server ran out of disk space. | Free disk space or increase storage. | --- ### 🗄️ Query & Syntax Errors | Error Code | Name | Description | Common Fix | |------------|--------------------|--------------------------------------------|---------------------------------------------| | 42601 | SyntaxError | Invalid SQL syntax. | Fix query syntax. | | 42703 | UndefinedColumn | Column does not exist. | Check column name spelling or schema. | | 42P01 | UndefinedTable | Table does not exist. | Create table or use correct table name. | | 42883 | UndefinedFunction | Function/operator not defined. | Define function or cast arguments properly. | | 42P02 | UndefinedParameter | Parameter not found in prepared statement. | Use correct placeholder ($1, $2, …). | --- ### 🔒 TLS/SSL & Security Errors | Error Code | Name | Description | Common Fix | |-------------|----------------------|---------------------------------------------|------------------------------------------------------------| | 08001 (SSL) | SSLHandshakeFailure | TLS/SSL negotiation failed. | Ensure certificates, SSL mode (`require`, `verify-full`). | | FATAL | No pg_hba.conf entry | Client not allowed by pg_hba.conf. | Update `pg_hba.conf` to allow IP/user, then reload server. | | 28000 | InvalidAuthorization | User not allowed for SSL/host restrictions. | Grant access or adjust SSL-related configs. | --- ### 🛠️ Miscellaneous Errors | Error Code | Name | Description | Common Fix | |------------|------------------------|----------------------------------------------|-------------------------------------------| | 54000 | ProgramLimitExceeded | Query exceeded an internal limit. | Optimize query, reduce joins/columns. | | 54001 | StatementTooComplex | Query too complex for planner. | Break query into smaller parts. | | 22001 | StringDataRightTrunc | Value too long for column type. | Increase column size or truncate data. | | XX000 | InternalError | Unexpected internal error. | Check server logs, update PostgreSQL. | --- ### 🧩 Error Types Summary | Category | Typical Causes | Example Error Codes | | |------------------------------------|--------------------------------------------------|----------------------------|-----| | **Authentication & Authorization** | Invalid login, role issues, too many connections | 28P01, 28000, 42501, 53300 | | | **Network & Connectivity** | Host unreachable, server shutdown/recovery | 08001, 08006, 57P01, 57P03 | | | **Constraints & Duplicates** | Unique, foreign key, check constraint violations | 23505, 23503, 23514, 23P01 | | | **Write & Transaction** | Deadlocks, serialization failures, null inserts | 40001, 40P01, 23502, 53100 | | | **Query & Syntax** | Invalid SQL syntax, undefined columns/functions | 42601, 42703, 42P01, 42883 | | | **TLS/SSL & Security** | SSL handshake errors, pg_hba.conf misconfig | 08001 (SSL), FATAL, 28000 | | | **Miscellaneous** | Data truncation, internal errors, program limits | 22001, 54000, 54001, XX000 | | - **Authentication** covers codes like `28P01 (InvalidPassword)` and `28000 (InvalidAuthorizationSpecification)`. - **Network** extended with connection shutdowns (`57P02`) and database dropped errors (`57P04`). - **Transactions** expanded with concurrency issues like `40001 (SerializationFailure)` and `40P01 (DeadlockDetected)`. - **Query** includes limits and parsing errors (`54001 StatementTooComplex`, `42601 SyntaxError`). - **Constraints** extended with unique and check violations (`23505`, `23513`). - **Miscellaneous** now includes disk space issues (`53100`) and internal errors (`XX000`). --- ## FAQ **Q1:** _Is `sslmode=require` mandatory?_ Not strictly, but it’s strongly recommended for production to secure data in transit. **Q2:** _Can I use PostgreSQL cloud services like AWS RDS, Azure Database, or GCP Cloud SQL?_ Yes. Just provide the full connection string from your provider and whitelist API Maker’s IP. **Q3:** _How do I connect to a PostgreSQL instance running in Docker?_ Use the container’s IP or host machine IP, e.g., `postgres://user:pass@172.17.0.2:5432/mydb`. --- ## Related Links - [PostgreSQL Connection String Documentation](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING) - [API Maker Schemas Overview](https://docs.apimaker.dev/v1/docs/schema/schema.html) - [API Maker Secrets Management](https://docs.apimaker.dev/v1/docs/secrets/secrets.html) --- ## PostgreSQL Cloud Providers - **API Maker Cloud** - Built-in PostgreSQL with instant APIs and schema management. - Ideal for API-first development without server setup. - Fast integration with API Maker features. - [apimaker.dev](https://apimaker.dev) - **Amazon RDS** - Managed PostgreSQL with automated backups and patching. - Best for AWS apps using EC2, Lambda, or S3. - High availability and monitoring built-in. - [aws.amazon.com/rds/postgresql](https://aws.amazon.com/rds/postgresql/) - **Amazon Aurora** - PostgreSQL-compatible with high performance. - Built for scaling with read replicas and HA. - Fully managed by AWS with minimal ops. - [aws.amazon.com/rds/aurora](https://aws.amazon.com/rds/aurora/postgresql-features/) - **Google Cloud SQL** - Fully managed PostgreSQL with HA and failover. - Integrates with BigQuery, GKE, and Cloud Storage. - Auto-scaling and monitoring included. - [cloud.google.com/sql/postgresql](https://cloud.google.com/sql/postgresql) - **Azure Database** - Managed PostgreSQL with enterprise-grade security. - Automatic scaling, HA, and backups included. - Best for workloads in Azure ecosystem. - [azure.microsoft.com/postgresql](https://azure.microsoft.com/en-us/products/postgresql) - **Heroku Postgres** - One-click PostgreSQL provisioning. - Easy scaling for startups and SaaS apps. - Deep integration with Heroku platform. - [heroku.com/postgres](https://www.heroku.com/postgres) - **Aiven** - PostgreSQL on AWS, GCP, Azure, and others. - Managed service with monitoring and HA. - Flexible for multi-cloud strategies. - [aiven.io/postgresql](https://aiven.io/postgresql) - **Crunchy Bridge** - Reliable managed PostgreSQL clusters. - Monitoring, HA, and migration tooling included. - Suited for enterprises and production apps. - [crunchydata.com/bridge](https://www.crunchydata.com/products/crunchy-bridge) - **EDB BigAnimal** - Enterprise-ready PostgreSQL service. - Adds resilience, compliance, and Oracle migration. - Designed for large-scale workloads. - [enterprisedb.com/biganimal](https://www.enterprisedb.com/biganimal) - **ScaleGrid** - Managed PostgreSQL with root access. - Automation + full customization balance. - Supports multiple clouds with flexibility. - [scalegrid.io/postgresql-hosting](https://scalegrid.io/postgresql-hosting/) --- # Oracle Database Connection Strings in API Maker > Use correct Oracle connection formats in API Maker, including host, service name, username, password, port, and admin privilege. Source: https://docs.apimaker.dev/v1/docs/Database-connection-string/oracledb-connection-strings.html ## Introduction When connecting Oracle Database to API Maker, proper configuration defines how your API Maker instance communicates with your Oracle database. Whether you're using Oracle Autonomous Database, a self-hosted Oracle instance, or a cloud provider's managed Oracle service, the correct connection parameters are essential for secure and reliable data access. In this guide, we'll explain connection formats, parameters, examples, troubleshooting tips, and best practices to help you integrate Oracle Database with API Maker effectively. --- ## Oracle Connection Format in API Maker Unlike traditional Oracle connection strings, API Maker requires Oracle connection details in a **specific key-value format** within the Secrets object. Below is a visual breakdown of the required format. ### Connection Format Breakdown ``` oracle: 'server_ip:server_port/oracle_process_name', oracle_username: 'your_username', oracle_password: 'your_password', oracle_privilege: 'SYSDBA' // optional, only if needed ``` ```plaintext ┌───────────────────────────┐ │ Oracle Connection Format │ └───────────────────────────┘ oracle: 'server_ip:server_port/oracle_process_name' │ └─ Server IP ── Port (1521) ── Service/SID │ oracle_username: 'your_username' │ oracle_password: 'your_password' │ oracle_privilege: 'SYSDBA' (optional) ``` ### Connection Parameters Explained | Parameter | Description | Required | Example Value | Notes | |--------------------|-----------------------------------------------------------|----------|------------------------------|--------------------------------------------| | `oracle` | Server connection string in format `ip:port/service_name` | ✅ Yes | `192.168.1.100:1521/ORCLPDB` | Combines host, port, and service name | | `oracle_username` | Database username for authentication | ✅ Yes | `appuser`, `hr`, `admin` | Should match Oracle user account | | `oracle_password` | Password for the database user | ✅ Yes | `securePass123` | Store securely in API Maker Secrets | | `oracle_privilege` | Optional administrative privilege | ❌ No | `SYSDBA`, `SYSOPER` | Use only when required for elevated access | --- ## Oracle Connection Examples ### Basic Connection (Standard User) ``` oracle: '192.168.1.100:1521/ORCLPDB', oracle_username: 'appuser', oracle_password: 'mySecurePass123' ``` ### Connection with Default Port (1521) ``` oracle: 'oracle.company.com/XEPDB1', oracle_username: 'hr_user', oracle_password: 'hrPass456' ``` ### Connection with Custom Port ``` oracle: '10.0.0.50:1522/TESTDB', oracle_username: 'test_user', oracle_password: 'testPass789' ``` ### Administrative Connection (SYSDBA) ``` oracle: '192.168.1.100:1521/ORCLPDB', oracle_username: 'sys', oracle_password: 'sysPassword', oracle_privilege: 'SYSDBA' ``` ### Connection with SYSOPER Privilege ``` oracle: 'prod-oracle.internal:1521/PRODDB', oracle_username: 'backup_admin', oracle_password: 'backupPass123', oracle_privilege: 'SYSOPER' ``` ### Oracle Autonomous Database (Cloud) ``` oracle: 'adb.us-east-1.oraclecloud.com:1522/myatp_high', oracle_username: 'admin', oracle_password: 'AutonPass123' ``` ### Multi-Tenant Container Database (CDB) ``` oracle: 'oracle-cdb.company.com:1521/CDB1', oracle_username: 'c##global_user', oracle_password: 'globalPass456' ``` ### Oracle Express Edition (XE) ``` oracle: 'localhost:1521/XE', oracle_username: 'app_user', oracle_password: 'localPass789' ``` ### Oracle RAC Connection (Single Node) ``` oracle: 'rac-node1.company.com:1521/RACDB', oracle_username: 'rac_user', oracle_password: 'racPass123' ``` ### Development Environment with Custom SID ``` oracle: 'dev-oracle:1521/DEVSID', oracle_username: 'developer', oracle_password: 'devPass456' ``` ### Oracle Cloud Infrastructure (OCI) Connection ``` oracle: 'db.us-phoenix-1.oraclecloud.com:1521/pdb1_phx', oracle_username: 'oci_user', oracle_password: 'ociSecurePass789' ``` --- ## Connection Parameters Detailed Breakdown ### Core Parameters | Parameter | Format | Description | Example Values | |------------------------|----------------------------|--------------------------------------|----------------------------------------------------| | **Server IP/Hostname** | `hostname` or `ip_address` | Oracle database server address | `localhost`, `192.168.1.100`, `oracle.company.com` | | **Port** | `:port_number` | Oracle listener port (default: 1521) | `:1521`, `:1522`, `:1525` | | **Service Name/SID** | `/service_name` | Oracle service identifier | `/ORCLPDB`, `/XE`, `/XEPDB1`, `/CDB1` | | **Username** | `string` | Database user account | `hr`, `appuser`, `sys`, `c##common_user` | | **Password** | `string` | User account password | Must be stored in API Maker Secrets | | **Privilege** | `SYSDBA` or `SYSOPER` | Administrative privileges | Only use when necessary | ### Oracle Service Types | Service Type | Format | Description | Use Case | |------------------------------|----------------------|----------------------------------|----------------------------| | **Pluggable Database (PDB)** | `host:port/pdb_name` | Modern multi-tenant architecture | Most common in Oracle 12c+ | | **Container Database (CDB)** | `host:port/cdb_name` | Root container database | Administrative operations | | **Traditional SID** | `host:port/sid_name` | Legacy service identifier | Older Oracle versions | | **Express Edition** | `host:port/XE` | Oracle XE service | Development/testing | --- ## Secure Connection Best Practices ### Security Guidelines - **Always store credentials in API Maker Secrets Management** - Never hardcode passwords - **Use least privilege principle** - Avoid `SYSDBA`/`SYSOPER` unless absolutely required - **Implement strong passwords** - Use complex passwords with numbers, symbols, and mixed case - **Enable Oracle wallet authentication** when possible for enhanced security - **Use dedicated database users** for application connections, not shared accounts - **Regularly rotate passwords** and update API Maker secrets accordingly ### Network Security - **Configure Oracle listener security** - Restrict listener access to authorized hosts - **Use VPN or private networks** - Avoid exposing Oracle directly to the internet - **Enable Oracle Network Encryption** - Encrypt data in transit - **Implement firewall rules** - Allow only necessary ports (typically 1521) - **Monitor connection logs** - Track access patterns and failed login attempts --- ## Connecting Oracle Database in API Maker ### Step-by-Step Connection Process 1. **Go to API Maker Secret Management → Default** 2. **Choose Oracle Database as the database type** 3. **Enter connection parameters:** - Oracle connection string in the specified format - Username and password - Optional privilege level 4. **Test the connection** to verify credentials and accessibility 5. **Save the configuration** once connection is successful ### Once Connected, You Can: - **Create schemas** for Oracle tables and views - **Use `/api/schema/...` endpoints** for optimized Oracle queries - **Leverage Deep Populate** to join Oracle data with other databases - **Execute complex Oracle procedures** and functions - **Access Oracle-specific data types** like CLOB, BLOB, XMLType - **Utilize Oracle advanced features** like materialized views and partitions --- ## Troubleshooting When connecting to Oracle Database (local, self-hosted, or cloud), you may encounter several common errors. Below is a categorized list with explanations and solutions: ### Authentication & Authorization Errors | Error Code | Name | Description | Common Fix | |------------|-------------------------------------|-----------------------------------|---------------------------------------------------------------| | ORA-01017 | Invalid username/password | Incorrect credentials provided | Verify username, password, and case sensitivity | | ORA-01031 | Insufficient privileges | User lacks required permissions | Grant appropriate roles or use privileged account | | ORA-28000 | Account is locked | User account has been locked | Unlock account: `ALTER USER username ACCOUNT UNLOCK;` | | ORA-28001 | Password has expired | User password needs to be changed | Change password: `ALTER USER username IDENTIFIED BY newpass;` | | ORA-01045 | User lacks CREATE SESSION privilege | Cannot establish session | Grant session privilege: `GRANT CREATE SESSION TO username;` | | ORA-00942 | Table or view does not exist | Insufficient object privileges | Grant table access or verify object name | ### Network & Connectivity Errors | Error Code | Name | Description | Common Fix | |------------|---------------------------------------|-------------------------------------------|------------------------------------------------------------------| | ORA-12541 | TNS:no listener | Oracle listener is not running | Start Oracle listener service | | ORA-12545 | Connect failed (host unreachable) | Cannot reach database server | Check network connectivity and firewall | | ORA-12154 | TNS:could not resolve service name | Service name not found in tnsnames.ora | Verify service name or use direct connection format | | ORA-12514 | TNS:listener does not know of service | Service name not registered with listener | Check service registration or restart database | | ORA-12170 | TNS:Connect timeout occurred | Connection attempt timed out | Check network latency, increase timeout, or verify server status | | ORA-12560 | TNS:protocol adapter error | Local connection protocol issue | Verify Oracle client installation and environment variables | ### Database & Instance Errors | Error Code | Name | Description | Common Fix | |------------|--------------------------------------|----------------------------------|----------------------------------------------------------| | ORA-01034 | Oracle not available | Database instance is not started | Start the Oracle database instance | | ORA-01089 | Immediate shutdown in progress | Database shutting down | Wait for shutdown to complete, then restart | | ORA-00257 | Archiver error | Archive log destination full | Clear archive log space or increase storage | | ORA-01033 | Oracle initialization in progress | Database still starting up | Wait for database startup to complete | | ORA-00020 | Maximum number of processes exceeded | Too many concurrent connections | Increase PROCESSES parameter or close unused connections | | ORA-00018 | Maximum number of sessions exceeded | Session limit reached | Increase SESSIONS parameter or terminate idle sessions | ### SSL/TLS & Security Errors | Error Code | Name | Description | Common Fix | |------------|-----------------------------------------|---------------------------------------|--------------------------------------------------| | ORA-29024 | Certificate validation failure | SSL certificate issues | Verify certificate chain and validity | | ORA-29040 | Encryption or crypto-checksumming error | SSL/TLS handshake failed | Check SSL configuration and cipher compatibility | | ORA-12629 | Network Data Encryption error | Encryption negotiation failed | Verify encryption settings match client/server | | ORA-28040 | No matching authentication protocol | Client/server authentication mismatch | Update client or server authentication settings | ### Performance & Resource Errors | Error Code | Name | Description | Common Fix | |------------|---------------------------------------|-------------------------------------|---------------------------------------------| | ORA-00604 | Error occurred at recursive SQL level | Internal recursive operation failed | Check underlying cause and system resources | | ORA-04031 | Unable to allocate memory | Shared pool or SGA memory exhausted | Increase SGA_MAX_SIZE or restart database | | ORA-01652 | Unable to extend temp segment | Temporary tablespace full | Add space to temporary tablespace | | ORA-01653 | Unable to extend table | Tablespace full | Add datafiles or increase tablespace size | | ORA-12505 | TNS:listener does not know of SID | SID not registered or misspelled | Verify SID name or use service name instead | ### Cloud Provider Specific Errors | Error Code | Name | Description | Common Fix | |------------|----------------------------------|---------------------------------------|----------------------------------------------------| | ORA-00001 | Unique constraint violated | Duplicate key in cloud environment | Handle duplicates in application logic | | Cloud-001 | Oracle Cloud connection timeout | OCI-specific timeout | Check OCI network configuration and security lists | | ATP-001 | Autonomous DB wallet required | Missing wallet for ATP/ADW connection | Download and configure Oracle wallet | | RDS-001 | RDS Oracle parameter group issue | AWS RDS configuration problem | Modify RDS parameter group or contact AWS support | --- ## FAQ **Q1:** _Can I use Oracle Express Edition (XE) with API Maker?_ Yes, [Oracle XE](https://www.oracle.com/in/database/technologies/appdev/xe.html) works perfectly with [API Maker](https://apimaker.dev). Use the service name `XE` in your connection string. **Q2:** _What's the difference between service name and SID?_ Service names are the modern approach (Oracle 9i+) and support multiple instances. SIDs are legacy identifiers for single instances. Use service names when possible. **Q3:** _Does API Maker support Oracle RAC (Real Application Clusters)?_ Yes, you can connect to individual RAC nodes. For high availability, consider connection pooling at the application level. **Q4:** _Can I use Oracle Autonomous Database with API Maker?_ Yes, Oracle Autonomous Database works with API Maker. Use the provided connection details from the [Oracle Cloud Console](https://docs.oracle.com/en-us/iaas/Content/Database/home.htm). **Q5:** _Do I always need the `oracle_privilege` parameter?_ No, only use it for administrative operations requiring `SYSDBA` or `SYSOPER` privileges. Regular application users don't need it. **Q6:** _Can I use a hostname instead of an IP address?_ Yes, as long as DNS resolution works in your environment. Hostnames are often preferred for cloud deployments. **Q7:** _What happens if I omit the port number?_ Oracle defaults to port 1521. If your listener uses a different port, you must specify it. **Q8:** _Can I connect to Oracle in a Docker container?_ Yes, use the container's network IP or published port mapping. Ensure the Oracle listener is configured to accept external connections. **Q9:** _Does API Maker support Oracle PL/SQL procedures?_ Yes, you can execute stored procedures and functions through API Maker's schema endpoints. **Q10:** _Can I connect to multiple Oracle databases simultaneously?_ Yes, configure different Oracle connections in separate API Maker secret configurations. --- ## List of Oracle Cloud Service Providers ### Oracle Cloud Infrastructure (OCI) - **Official Oracle cloud platform** - **Services:** Autonomous Database, Base Database Service, Exadata Cloud Service - **Features:** Auto-scaling, automated patching, built-in security, machine learning - [https://www.oracle.com/in/cloud/](https://www.oracle.com/in/cloud/) ### API Maker Cloud - **Provides one-click Oracle Database installation** with latest versions - **Runs on dedicated VPS** of your choice, budget-friendly options available - **Can install Oracle with API Maker's server** to save costs significantly - **Great choice for self-hosted Oracle** with flexible scaling options - [https://cloud.apimaker.dev](https://cloud.apimaker.dev) ### Amazon RDS for Oracle - **Managed Oracle service by AWS** - **Features:** Multi-AZ deployments, automated backups, read replicas - **Integrated with AWS ecosystem** (IAM, VPC, CloudWatch, Lambda) - [https://aws.amazon.com/rds/oracle/](https://aws.amazon.com/rds/oracle/) ### Microsoft Azure Database for Oracle - **Oracle on Azure Virtual Machines** - **Features:** High availability, disaster recovery, integration with Azure services - **Supports Oracle RAC and Data Guard** - [https://azure.microsoft.com/en-us/solutions/oracle](https://azure.microsoft.com/en-us/solutions/oracle) ### Google Cloud Oracle Solutions - **Oracle Database on Google Cloud** - **Features:** Bare metal solutions, high-performance computing - **Certified Oracle Cloud VMware Solution** - [https://www.rackspace.com/applications/oracle](https://www.rackspace.com/applications/oracle) ### IBM Cloud for Oracle - **IBM Cloud Virtual Servers for Oracle** - **Features:** Bare metal servers, high-performance storage - **Integrated with IBM Cloud services and AI capabilities** - [https://www.ibm.com/cloud/oracle](https://www.ibm.com/cloud/oracle) ### ScaleGrid - **Fully managed database hosting** (Oracle, MySQL, PostgreSQL, MongoDB, Redis) - **Supports hosting on AWS, Azure, GCP, DigitalOcean** - **Features:** Custom backups, dedicated servers, SSH access, monitoring - [https://scalegrid.io/](https://scalegrid.io/) ### Rackspace Managed Oracle - **Enterprise-grade managed Oracle hosting** - **Features:** 24/7 expert support, performance optimization, security management** - **Targets enterprise workloads with SLA guarantees** - [https://www.rackspace.com/managed-cloud/oracle](https://www.rackspace.com/managed-cloud/oracle) ### Oracle Cloud Marketplace Partners #### Aiven - **Multi-cloud database service provider** - **Supports Oracle alongside PostgreSQL, MySQL, Kafka, Redis** - **Runs on AWS, Azure, GCP, DigitalOcean, UpCloud** - [https://aiven.io/](https://aiven.io/) #### Instaclustr (by NetApp) - **Managed data platform provider** - **Enterprise SLA-backed Oracle deployments** - **Features:** 24/7 support, monitoring, backup management** - [https://www.instaclustr.com/](https://www.instaclustr.com/) #### Severalnines (ClusterControl) - **Database automation and management platform** - **Self-hosted + managed options for Oracle** - **Features:** Backup automation, scaling, monitoring, cluster management** - [https://severalnines.com/](https://severalnines.com/) ### Infrastructure-as-a-Service Providers #### DigitalOcean - **Droplets (VPS) suitable for Oracle Database installation** - **Features:** SSD storage, monitoring, automated backups, global data centers** - **Popular with startups and developers for cost-effective Oracle hosting** - [https://www.digitalocean.com/](https://www.digitalocean.com/) #### Linode (by Akamai) - **High-performance cloud compute instances** - **Features:** NVMe SSD storage, 40 Gbps network, 24/7 support** - **Developer-friendly pricing and Oracle-compatible infrastructure** - [https://www.linode.com/](https://www.linode.com/) #### Vultr - **Global cloud infrastructure provider** - **Features:** High-frequency compute, block storage, private networking** - **Optimized for database workloads with fast NVMe storage** - [https://www.vultr.com/](https://www.vultr.com/) #### Hetzner - **European cloud and dedicated server provider** - **Features:** Cost-effective pricing, powerful hardware, excellent network** - **Popular in Europe for Oracle Database hosting** - [https://www.hetzner.com/](https://www.hetzner.com/) #### OVHcloud - **European cloud provider with global presence** - **Features:** Bare metal servers, private cloud, hybrid solutions** - **GDPR-compliant Oracle hosting solutions** - [https://www.ovhcloud.com/](https://www.ovhcloud.com/) ### Specialized Oracle Hosting Providers #### CloudClusters - **Oracle Database-as-a-Service on dedicated instances** - **Features:** Automated backups, SSL security, scaling, monitoring** - **Affordable pricing for small to medium Oracle workloads** - [https://www.cloudclusters.io/](https://www.cloudclusters.io/) #### A2 Hosting - **Performance-optimized web and database hosting** - **Features:** Turbo servers, SSD storage, 24/7 support** - **Suitable for Oracle Database on VPS and dedicated servers** - [https://www.a2hosting.com/](https://www.a2hosting.com/) #### Kamatera - **Global cloud infrastructure platform** - **Features:** Custom server configurations, pay-as-you-go pricing** - **Flexible Oracle Database deployments across global data centers** - [https://www.kamatera.com/](https://www.kamatera.com/) #### Contabo - **High-performance VPS and dedicated server provider** - **Features:** Large RAM configurations ideal for Oracle, DDoS protection** - **Cost-effective Oracle hosting with excellent price-to-performance ratio** - [https://www.contabo.com/](https://www.contabo.com/) --- ## Related Resources - [Oracle Listener Configuration Guide](https://docs.oracle.com/en/database/oracle/oracle-database/19/netag/configuring-and-administering-oracle-net-listener.html) - [Oracle Cloud Infrastructure Database Service](https://docs.oracle.com/en-us/iaas/Content/Database/home.htm) - [Oracle Security Best Practices](https://docs.oracle.com/en/database/oracle/oracle-database/19/dbseg/introduction-to-oracle-database-security.html) --- # SQL Server Connection Strings in API Maker > Format SQL Server connection strings easily:- include server, credentials, trusted connection, and escape special chars like @. Source: https://docs.apimaker.dev/v1/docs/Database-connection-string/sqlserver-connection-strings.html ## Introduction When connecting SQL Server to API Maker, the connection string defines how your API Maker instance communicates with your SQL Server database. Whether you're using Azure SQL Database, SQL Server Express, Amazon RDS for SQL Server, or a self-hosted SQL Server instance, the correct connection string is essential for secure and reliable data access. In this comprehensive guide, we'll explain connection string formats, parameters, authentication methods, cloud provider examples, troubleshooting tips, and best practices to help you integrate SQL Server with API Maker effectively. --- ## SQL Server Connection String Format Below is a visual breakdown of the SQL Server connection string format: ``` Server=[server_name];Database=[database_name];User Id=[username];Password=[password];[options] └────┬────┘ └───────┬────────┘ └──────┬────────┘ └───────┬─────────┘ └───┬───┘ │ │ │ │ │ Server Database User ID Password Options (Host/Instance) Name (optional) (optional) (key=value) ``` --- ## Connection String Parts Explained ### Server/Host - **Single Instance**: `Server=myServerName` or `Server=192.168.1.100` - **Named Instance**: `Server=myServer\SQLEXPRESS` - **With Port**: `Server=myServer,1433` or `Server=tcp:myServer.database.windows.net,1433` ### Database - Specifies the target database name - Format: `Database=myDataBase` or `Initial Catalog=myDataBase` ### Authentication - **SQL Authentication**: `User Id=myUser;Password=myPass` - **Windows Authentication**: `Trusted_Connection=True` or `Integrated Security=SSPI` - **Azure AD**: `Authentication=Active Directory Integrated` ### Options - Additional parameters separated by semicolons - Example: `Encrypt=True;TrustServerCertificate=False;Connection Timeout=30` --- ## SQL Server Connection String Examples ### Basic Local Connection ``` Server=localhost;Database=myDB;Trusted_Connection=True; ``` ### Local with SQL Authentication ``` Server=localhost;Database=myDB;User Id=sa;Password=myPassword; ``` ### Local SQL Express Instance ``` Server=.\SQLEXPRESS;Database=myDB;Trusted_Connection=True; ``` ### Remote Server with Port ``` Server=192.168.1.100,1433;Database=myDB;User Id=dbuser;Password=myPassword; ``` ### Azure SQL Database ``` Server=tcp:myserver.database.windows.net,1433;Initial Catalog=myDB;Persist Security Info=False;User ID=myUser;Password=myPassword;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30; ``` ### Amazon RDS SQL Server ``` Server=mydb.cxxxxxxxxx.us-east-1.rds.amazonaws.com,1433;Database=myDB;User Id=admin;Password=myPassword;Encrypt=True; ``` ### Encrypted Connection with Certificate Trust ``` Server=myserver.com;Database=myDB;User Id=myUser;Password=myPassword;Encrypt=True;TrustServerCertificate=True; ``` ### Connection with Application Intent ``` Server=myserver;Database=myDB;User Id=myUser;Password=myPassword;ApplicationIntent=ReadOnly; ``` ### Multi-Subnet Failover ``` Server=tcp:ag-listener.domain.com,1433;Database=myDB;User Id=myUser;Password=myPassword;MultiSubnetFailover=True; ``` ### Connection Pooling Configuration ``` Server=myserver;Database=myDB;User Id=myUser;Password=myPassword;Pooling=true;Min Pool Size=5;Max Pool Size=100;Connection Lifetime=0; ``` --- ## Connection String Parts Breakdown | Part | Description | Example | |------------------------------------|---------------------------------------------|---------------------------------------------------| | Server/Data Source | SQL Server instance name, IP, or FQDN | `Server=myserver`, `Data Source=192.168.1.1,1433` | | Database/Initial Catalog | Target database name | `Database=myDB`, `Initial Catalog=myDB` | | User Id/User ID | Username for SQL Authentication | `User Id=sa`, `User ID=dbuser` | | Password | Password for SQL Authentication | `Password=myPassword` | | Trusted_Connection | Enable Windows Authentication | `Trusted_Connection=True` | | Integrated Security | Alternative to Trusted_Connection | `Integrated Security=SSPI` | | Encrypt | Enable SSL/TLS encryption | `Encrypt=True` | | TrustServerCertificate | Trust server certificate without validation | `TrustServerCertificate=False` | | Connection Timeout/Connect Timeout | Connection establishment timeout (seconds) | `Connection Timeout=30` | | Command Timeout | Command execution timeout (seconds) | `Command Timeout=120` | --- ## Connection String Options Breakdown and Explanation | Parameter | Description | Example Value | |------------------------------|------------------------------------------------|----------------------------------| | **Encrypt** | Enables SSL/TLS encryption for data in transit | `Encrypt=True` | | **TrustServerCertificate** | Bypasses certificate validation (dev only) | `TrustServerCertificate=False` | | **Connection Timeout** | Max time to wait for connection (default: 15s) | `Connection Timeout=30` | | **Command Timeout** | Max time for command execution (default: 30s) | `Command Timeout=120` | | **MultipleActiveResultSets** | Enables MARS for concurrent result sets | `MultipleActiveResultSets=False` | | **Pooling** | Enables connection pooling (default: true) | `Pooling=true` | | **Min Pool Size** | Minimum connections in pool | `Min Pool Size=5` | | **Max Pool Size** | Maximum connections in pool (default: 100) | `Max Pool Size=200` | | **Connection Lifetime** | Max lifetime of pooled connection (seconds) | `Connection Lifetime=0` | | **ApplicationIntent** | Read-only or read-write intent | `ApplicationIntent=ReadOnly` | | **MultiSubnetFailover** | Support for Always On availability groups | `MultiSubnetFailover=True` | | **Workstation ID** | Client workstation identifier | `Workstation ID=MyApp` | | **Application Name** | Application name for monitoring | `Application Name=MyApplication` | | **Packet Size** | Network packet size in bytes | `Packet Size=4096` | | **Persist Security Info** | Keep security info in connection string | `Persist Security Info=False` | --- ## Secure Connection Best Practices - **Always use encrypted connections** in production with `Encrypt=True` - **Avoid certificate trust bypass** - set `TrustServerCertificate=False` in production - **Use Windows Authentication** when possible instead of SQL Authentication - **Store credentials securely** using API Maker's Secrets Management - **Implement least privilege** - grant only necessary database permissions - **Regular credential rotation** and monitoring of database access - **Enable connection pooling** for better performance and resource management --- ## Connecting SQL Server in API Maker 1. **Navigate to API Maker Dashboard** → **Database Configuration** 2. **Select SQL Server** as your database type 3. **Enter your connection string** in the designated field 4. **Test the connection** to verify credentials and network accessibility 5. **Configure schema introspection** settings if needed Once connected, you can: - **Auto-generate REST APIs** from your SQL Server tables and views - **Use schema-based endpoints** like `/api/schema/tablename` for optimized queries - **Leverage Deep Populate** to join SQL Server data with other connected databases - **Enable real-time streaming** and caching for high-performance applications --- ## Troubleshooting Scenarios When connecting to SQL Server (local, cloud, or managed services), you may encounter various common errors. Below are categorized troubleshooting scenarios with solutions: ### Authentication & Authorization Errors | Error Code | Error Message | Common Causes | Suggested Solutions | |------------|-----------------------------------------------------|--------------------------------------------------|----------------------------------------------------------------------------------| | **18456** | Login failed for user 'username' | Invalid credentials, disabled SQL Auth, firewall | Verify username/password, enable SQL Server Authentication, check firewall rules | | **18452** | Login failed. The login is from an untrusted domain | Windows Auth on unsupported domain | Use SQL Authentication or configure domain trust | | **40607** | Login failed due to client IP address | Azure SQL firewall restrictions | Add client IP to Azure SQL firewall rules | | **40615** | Login failed - server firewall rules | Azure SQL server-level firewall | Configure server-level firewall rules in Azure portal | | **18470** | Login failed for user - account disabled | SQL Server login disabled | Re-enable the login account using SQL Server Management Studio | **Reference**: [SQL Server Authentication Troubleshooting](https://learn.microsoft.com/en-us/sql/relational-databases/errors-events/mssqlserver-18456-database-engine-error) ### Network & Connectivity Errors | Error Code | Error Message | Common Causes | Suggested Solutions | |------------|-------------------------------------------------|-----------------------------------------------------|----------------------------------------------------------------------| | **53** | Named Pipes Provider, could not open connection | SQL Server not running, incorrect instance name | Verify SQL Server service is running, check instance name format | | **2** | System cannot find the file specified | Incorrect server/instance name | Verify server name and instance configuration | | **10060** | Connection timeout expired | Network latency, firewall blocking, server overload | Increase Connection Timeout, check network connectivity and firewall | | **26** | Error Locating Server/Instance Specified | SQL Browser not running, instance not found | Start SQL Server Browser service, verify instance name | | **10061** | Connection actively refused | SQL Server not accepting connections | Enable remote connections, check SQL Server network configuration | **Reference**: [SQL Server Network Connectivity Troubleshooting](https://learn.microsoft.com/en-us/troubleshoot/sql/database-engine/connect/network-related-or-instance-specific-error-occurred-while-establishing-connection) ### SSL/TLS & Security Errors | Error Code | Error Message | Common Causes | Suggested Solutions | |-----------------|--------------------------------------------------------------------------|------------------------------------------|---------------------------------------------------------------------------| | **19** | SSL Provider: The certificate chain was issued by an untrusted authority | Invalid or self-signed certificate | Install trusted certificate or set TrustServerCertificate=True (dev only) | | **20** | SSL Provider: The client certificate is not valid | Client certificate authentication failed | Verify client certificate installation and validity | | **-2146893022** | SSL Provider: The target principal name is incorrect | Certificate name mismatch | Ensure certificate matches server FQDN or use IP address | **Reference**: [Enable Encrypted Connections to SQL Server](https://learn.microsoft.com/en-us/sql/database-engine/configure-windows/enable-encrypted-connections-to-the-database-engine) ### Performance & Resource Errors | Error Code | Error Message | Common Causes | Suggested Solutions | |------------|-------------------------------------------------------|--------------------------------------|------------------------------------------------------------| | **1222** | Lock request time out period exceeded | Long-running transactions, deadlocks | Optimize queries, implement proper transaction handling | | **8645** | A timeout occurred while waiting for memory resources | Memory pressure | Increase available memory or optimize memory usage | | **701** | There is insufficient system memory | Out of memory condition | Scale up server resources or optimize memory configuration | ### Database & Configuration Errors | Error Code | Error Message | Common Causes | Suggested Solutions | |------------|---------------------------------------------|---------------------------------------|------------------------------------------------| | **4060** | Cannot open database requested by the login | Database doesn't exist, access denied | Verify database name, grant access permissions | | **15128** | The specified database name is not valid | Invalid database name characters | Use valid database naming conventions | | **208** | Invalid object name | Table/view doesn't exist | Verify object exists and user has permissions | ### Cloud Provider Specific Errors #### Azure SQL Database | Error Code | Description | Solution | |------------|----------------------------------------------------|----------------------------------------------------| | **40544** | Database has reached its size quota | Scale up service tier or clean up data | | **40549** | Session terminated due to long-running transaction | Optimize transaction scope and duration | | **40613** | Database currently unavailable | Wait for service recovery or contact Azure support | #### Amazon RDS SQL Server | Error Code | Description | Solution | |------------|-----------------------------------|------------------------------------------------| | **N/A** | Connection failed to RDS instance | Check security groups, VPC configuration | | **N/A** | Parameter group modifications | Reboot RDS instance to apply parameter changes | --- ## FAQ **Q: Can I use SQL Server Express with API Maker?** A: Yes, SQL Server Express works perfectly for development and small to medium-sized applications. Use the connection format: `Server=.\SQLEXPRESS;Database=myDB;Trusted_Connection=True;` **Q: What's the default port for SQL Server?** A: The default TCP port is `1433`. Named instances use dynamic ports unless configured otherwise. **Q: How do I connect to SQL Server in a Docker container?** A: Use `Server=localhost,1433;Database=myDB;User Id=sa;Password=YourPassword;TrustServerCertificate=True;` **Q: Does API Maker support Always On Availability Groups?** A: Yes, use the availability group listener in your connection string with `MultiSubnetFailover=True`. **Q: Can I connect to multiple SQL Server databases simultaneously?** A: Yes, API Maker supports multiple database connections. Configure each database separately in the dashboard. **Q: What authentication methods does API Maker support for SQL Server?** A: API Maker supports SQL Authentication, Windows Authentication (for on-premises), and Azure AD authentication. --- ## List of SQL Server Cloud Service Providers ### Microsoft Azure SQL Services **Azure SQL Database** - Fully managed SQL Server database service - Built-in high availability, automated backups, intelligent performance - Serverless and hyperscale options available - [https://azure.microsoft.com/en-us/products/azure-sql/database/](https://azure.microsoft.com/en-us/products/azure-sql/database/) **Azure SQL Managed Instance** - Near 100% compatibility with SQL Server on-premises - VNet deployment with private IP addressing - Cross-database queries and SQL Agent support - [https://azure.microsoft.com/en-us/products/azure-sql/managed-instance/](https://azure.microsoft.com/en-us/products/azure-sql/managed-instance/) ### Amazon Web Services (AWS) **Amazon RDS for SQL Server** - Managed SQL Server with automated patching and backups - Multi-AZ deployments for high availability - Read replicas and automated failover - [https://aws.amazon.com/rds/sqlserver/](https://aws.amazon.com/rds/sqlserver/) **Amazon EC2 with SQL Server** - Self-managed SQL Server on virtual machines - Full control over configuration and customization - License-included and BYOL options - [https://aws.amazon.com/microsoft/sql-server/](https://aws.amazon.com/microsoft/sql-server/) ### Google Cloud Platform (GCP) **Google Cloud SQL for SQL Server** - Fully managed SQL Server database service - Automatic replication, backup, and failover - Integration with Google Cloud services - [https://cloud.google.com/sql/docs/sqlserver](https://cloud.google.com/sql/docs/sqlserver) **Google Compute Engine with SQL Server** - Self-managed SQL Server on virtual machines - Custom machine types and persistent disks - Windows Server and SQL Server licensing options - [https://cloud.google.com/compute/docs/instances](https://cloud.google.com/compute/docs/instances) ### API Maker Cloud **API Maker Managed SQL Server** - One-click SQL Server installation with API Maker integration - Dedicated VPS hosting with flexible scaling - Cost-effective alternative to major cloud providers - Pre-configured for optimal API Maker performance - [https://cloud.apimaker.dev](https://cloud.apimaker.dev) ### Enterprise & Specialized Providers **IBM Cloud Databases for SQL Server** - Enterprise-grade managed SQL Server service - High availability and disaster recovery - Integration with IBM Watson and AI services - [https://www.ibm.com/cloud/databases](https://www.ibm.com/cloud/databases) **Oracle Cloud Infrastructure (OCI)** - SQL Server on Oracle Linux or Windows - Autonomous database options - High-performance computing capabilities - [https://www.oracle.com/in/cloud/](https://www.oracle.com/in/cloud/) **Alibaba Cloud RDS for SQL Server** - Managed SQL Server in Asia-Pacific regions - Multi-zone deployment and read replicas - Integration with Alibaba Cloud ecosystem - [https://www.alibabacloud.com/en/product/apsaradb-for-rds-sql-server?_p_lc=1](https://www.alibabacloud.com/en/product/apsaradb-for-rds-sql-server?_p_lc=1) ### Infrastructure-as-a-Service (IaaS) Providers **DigitalOcean Droplets** - Self-managed SQL Server installation on VPS - Simple pricing and developer-friendly interface - Global data center locations - [https://www.digitalocean.com/products/droplets](https://www.digitalocean.com/products/droplets) **Linode (Akamai)** - Virtual machines for SQL Server installation - High-performance computing with SSD storage - 24/7 support and global presence - [https://www.linode.com/products/essential-compute/](https://www.linode.com/products/essential-compute/) **Vultr** - Cloud compute instances for SQL Server - High-frequency compute options - Multiple global locations - [https://www.vultr.com/products/cloud-compute/](https://www.vultr.com/products/cloud-compute/) **Hetzner Cloud** - European-based cloud infrastructure - Cost-effective VPS with excellent performance - ARM64 and x86 instance types - [https://www.hetzner.com/cloud](https://www.hetzner.com/cloud) ### Specialized Database Hosting **ScaleGrid** - Fully managed SQL Server hosting - Multi-cloud deployment (AWS, Azure, GCP) - Database monitoring and optimization tools - [https://scalegrid.io/sqlserver/](https://scalegrid.io/sql-server/) **Rackspace Technology** - Managed SQL Server on multiple clouds - Database administration services - 24x7x365 expert support - [https://www.rackspace.com/data/databases](https://www.rackspace.com/data/databases) **OVHcloud** - European cloud provider with SQL Server support - Public and private cloud options - GDPR-compliant infrastructure - [https://www.ovhcloud.com/](https://www.ovhcloud.com/) --- ## Related Resources - [Microsoft SQL Server Documentation](https://learn.microsoft.com/en-us/sql/sql-server/) - [SQL Server Connection String Reference](https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/connection-string-syntax) - [Azure SQL Database Documentation](https://learn.microsoft.com/en-us/azure/azure-sql/) - [Amazon RDS for SQL Server User Guide](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_SQLServer.html) - [Google Cloud SQL for SQL Server Documentation](https://cloud.google.com/sql/docs/sqlserver) - [API Maker Documentation Hub](https://docs.apimaker.dev/) - [SQL Server Performance Tuning Guide](https://learn.microsoft.com/en-us/sql/relational-databases/performance/performance-monitoring-and-tuning-tools) - [SQL Server Security Best Practices](https://learn.microsoft.com/en-us/sql/relational-databases/security/security-center-for-sql-server-database-engine-and-azure-sql-database) --- # UI Maker Introduction > Learn what UI Maker offers within API Maker:- an overview of its features and benefits. Source: https://docs.apimaker.dev/v1/extensions/ui_maker/introduction.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). ## ❓What is UI Maker 👉 UI Maker is a part of API Maker.
👉 As name suggests, UI Maker generates rich grid & form based on JSON configuration which is highly customizable.
## ❓How to install 👉 UI Maker comes with [API Maker + Extensions](https://www.npmjs.com/package/@sava-info-systems/api-maker-with-extensions), the package installed with a license key. For a license key, contact us at `contact@apimaker.dev`.
--- # UI Maker Getting Started Guide > Quick setup guide for using UI Maker to build forms and UIs in API Maker. Source: https://docs.apimaker.dev/v1/extensions/ui_maker/getting_started.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). 👉 When you install API Maker + Extensions, you will see this icon available in API Maker dashboard.
![API Maker Dashboard with UI Maker](/v1/extensions/img/am_dashboard_ui_maker.png) 👉 On click of that you will see empty list of UI Pages.
👉 Add new UI Page by clicking on top right corner.
![Add Form 1](/v1/extensions/img/students_form_1.png) ![Add Form Config](/v1/extensions/img/Students_form_config_1.png) 👉 Below is students table schema ![Students Table Schema](/v1/extensions/img/Students_table_schema.png) 👉 Your basic UI Page is ready. To view this UI Page, you can click on blue icon as shown in below image of grid. ![UI Pages Grid](/v1/extensions/img/Students_one_column_grid.png) 👉 URL will look like below & you need to pass tokens as per your authorization settings. ```text http://____host_port____/?admin-path=admin&db-master-name=Students&x-am-authorization=____token____ ``` 👉 Simple UI page will look like below. ![Simple UI Page One Column View](/v1/extensions/img/Students_list_view_1.png) --- # Add More Fields in Grid – UI Maker > Learn how to add and customize additional fields within the UI Maker grid layout. Source: https://docs.apimaker.dev/v1/extensions/ui_maker/more_fields_in_grid.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). 👉 Let's add all remaining fields of schema into grid JSON. ![Fields added in grid](/v1/extensions/img/more_fields_added_in_grid_step2.png) 👉 And after that grid looks like this ![Grid status after adding more fields](/v1/extensions/img/grid_status_after_adding_more_fields.png) --- # Format Date in Grid – UI Maker > Instructions for applying date formatting within grid views using UI Maker. Source: https://docs.apimaker.dev/v1/extensions/ui_maker/format_date_in_grid.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). 👉 If you want to convert grid date, you can use "autoFormatDate" property of config. - It will convert into default format of "dd-MM-yyyy hh:mm:ss a". - You can also provide optional properties given in below screenshot. ![Convert Grid Date Into This Timezone](/v1/extensions/img/convert_grid_date_into_this_timezone.png) ![Grid Output After Adding DateFormat Property](/v1/extensions/img/grid_output_after_adding_date_format.png) 👉 Assign css class as shown below. ![Assign CSS class to grid field](/v1/extensions/img/assign_css_class_to_grid_field.png) ![CSS class assigned to grid field output](/v1/extensions/img/css_class_assigned_to_grid_field_output.png) --- # UI Maker Forms – Getting Started > Your step-by-step guide to start building form layouts in UI Maker. Source: https://docs.apimaker.dev/v1/extensions/ui_maker/getting_started_with_form.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). 👉 Let's add feature for adding new records.
👉 Form fields is two dimensional array. Top level array represents rows and second level represents columns of fields.
👉 Add below configuration for add operation.



👉 And form will look like this.

--- # Add Date Field to Form – UI Maker > Learn how to insert and configure date picker fields inside your UI Maker forms. Source: https://docs.apimaker.dev/v1/extensions/ui_maker/adding_date_field_in_form.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). 👉 Let's add date field in our form for "dob" field.
![Add date picker field Date of birth in form](/v1/extensions/img/add_date_picker_field_dob.png) 👉 And control looks like this in UI.
![Date picker in form](/v1/extensions/img/date_picker_in_form.png) 👉 It will work automatically in Add/Edit/Delete functionality.
--- # Add Checkbox in Form – UI Maker > Guide for adding and configuring checkbox field types in UI Maker forms. Source: https://docs.apimaker.dev/v1/extensions/ui_maker/adding_checkbox_field_in_form.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). 👉 Let's add checkbox field in our form for "active" field.
![Add checkbox field in form](/v1/extensions/img/add_checkbox_field_in_form.png) 👉 And control looks like this in UI.
![Add checkbox field in UI](/v1/extensions/img/add_checkbox_field_in_form_preview.png) 👉 It will work automatically in Add/Edit/Delete functionality.
--- # All UI Maker Form Controls > Reference list of all form control types supported in UI Maker. Source: https://docs.apimaker.dev/v1/extensions/ui_maker/list_of_all_supported_controls_in_form.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). ### List of supported controls
Input Supported Controls - Input Input Number Supported Controls - Input Number Input Mask Supported Controls - Input Mask Input Otp Supported Controls - Input Otp Password Supported Controls - Password Date Picker Supported Controls - Date Picker Textarea Supported Controls - Textarea Checkbox Supported Controls - Checkbox Radio Supported Controls - Radio Dropdown Supported Controls - Dropdown Auto Complete Supported Controls - Auto Complete Multi Select Supported Controls - Multi Select File Upload Supported Controls - File Upload Divider Supported Controls - Divider Tab View Supported Controls - Tab View Button Supported Controls - Button Rating Supported Controls - Rating Knob Supported Controls - Knob Accordion Supported Controls - Accordion Image Supported Controls - Image Grid Supported Controls - Grid customHTML Supported Controls - customHTML Editor Supported Controls - editor --- # Input Form Control Config > Source: https://docs.apimaker.dev/v1/extensions/ui_maker/form_controls/input_form_control.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). ## Simple textbox ```typescript let dbMasterConfig: T.IDBMasterConfig = { form: { fields: [ [{ // field label: 'First Name', control: T.EDBMasterFormControl.input, path: 'name', }] ] } }; ``` ## Example with extended behaviour ```typescript let dbMasterConfig: T.IDBMasterConfig = { form: { fields: [ [{ // field label: 'First Name', control: T.EDBMasterFormControl.input, path: 'name', // disabled: false, validations: { required: true, }, validationErrors: { required: 'Please provide first name.', }, helpText: `It is always good to provide name`, inputTextSettings: { maxLength: 5, minLength: 5, spellcheck: 'false', autocomplete: 'off', jsCode: [{ appendTo: T.EDBMasterInputTextAppendTo.ngModelChange, code: ` console.log(event); console.log(formData); console.log(config); console.log(column); `, }, { appendTo: T.EDBMasterInputTextAppendTo.blur, code: ` console.log(formData); `, }, { appendTo: T.EDBMasterInputTextAppendTo.focus, code: ` console.log(formData); `, }], validationErrors: { minLength: 'Please provide minimum 5 characters.' } } }] ] } }; ``` ## Interface Documentation ```typescript /** * Form field configuration for UI controls. * Defines the appearance, behavior, and validation of individual form inputs. */ export interface IDBMasterConfigFormField { /** * Unique identifier for programmatic element access. * Useful for dynamic field manipulation. */ hiddenId?: string; /** Display label for the form field. */ label?: string; /** * Help text displayed below the control. * Supports HTML formatting for rich content. */ helpText?: string; /** * Database field path where the control value is stored. * Supports nested paths using dot notation. * @example 'name', 'address.city', 'user.contact.email' */ path?: string; /** Type of UI control to render. */ control?: EDBMasterFormControl; /** * CSS classes for the parent div wrapping the control. * @default 'col-lg mt-4 col-md-{calculated based on columns.length}' */ cssClassDiv?: string; /** * Auto-focus this control when form opens. * @default false */ autofocus?: boolean; /** * Disable the control or use expression to conditionally disable. * When a string is provided, it's evaluated as JavaScript. * @example true | false | "formData.type === 'readonly'" */ disabled?: boolean | string; /** * Control visibility or use expression to conditionally show/hide. * When a string is provided, it's evaluated as JavaScript. * @default true * @example true | false | "formData.userRole === 'admin'" */ visible?: boolean | string; /** * Nested form fields for complex layouts. * Enables hierarchical form structures within this field. */ fields?: IDBMasterConfigFormField[][]; /** Validation rules for this field. */ validations?: Pick & { /** * Dynamic required validation function. * Evaluated when form data changes to determine if field is required. * Note: When present, this takes precedence over static 'required' property. * @example "formData.type === 'individual' ? true : false" */ requiredFun?: string; }; /** Custom validation error messages. */ validationErrors?: { /** Custom error message for required field validation. */ required?: string; }; /** * Text input control configuration. * Single-line text entry with validation and autocomplete support. * * **Features:** * - Character length limits (min/max) * - Autocomplete on/off control * - Spellcheck toggle * - Tooltip support * - Custom event handlers * * @example * // Basic text input with length limit * inputTextSettings: { * placeholder: 'Enter your name', * maxLength: 100, * autocomplete: 'off' * } * * @example * // Text input with validation and tooltip * inputTextSettings: { * minLength: 3, * maxLength: 50, * tooltip: 'Username must be 3-50 characters', * tooltipPosition: 'top', * validationErrors: { * minLength: 'Username must be at least 3 characters' * } * } */ inputTextSettings?: { /** Give style object in angular style. */ style?: any; placeholder?: string; /** Default is off */ autocomplete?: 'on' | 'off' | undefined; /** Default is false */ spellcheck?: 'true' | 'false' | undefined; /** Maximum number of character allows in the input field. */ maxLength?: number; /** Minimum number of character allows in the input field. */ minLength?: number; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterInputTextAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], validationErrors?: { minLength?: string; }; }; } /** * Validation rules for schema properties and form fields. * Define constraints that values must satisfy before being saved. */ export interface IPropertyValidation { /** * Field is mandatory and must have a non-null, non-empty value. * Applicable to all data types. */ required?: boolean; /** * Minimum allowed value. * Applicable to: number, date */ min?: number; /** * Maximum allowed value. * Applicable to: number, date */ max?: number; /** * Minimum string/array length. * Applicable to: string, array */ minLength?: number; /** * Maximum string/array length. * Applicable to: string, array */ maxLength?: number; /** * Value must be unique across all records. * @deprecated API Maker maintains uniqueness internally. * Avoid using on tables with frequent updates due to performance impact. */ unique?: boolean; /** * Validate email address format. * Applicable to: string */ email?: boolean; /** * Custom validation function. * Return true if valid, false or error message if invalid. */ validatorFun?: Function; /** * Enumeration constraint - value must be from this array. * @example ['active', 'inactive', 'pending'] */ enum?: any[]; } export enum EDBMasterInputTextAppendTo { visible = 'visible', disabled = 'disabled', ngModelChange = 'ngModelChange', focus = 'focus', blur = 'blur', keyUp = 'keyUp', keyDown = 'keyDown', } /** * Available form control types for UI generation. * Each control type has specific settings and behavior. */ export enum EDBMasterFormControl { /** Single-line text input. */ input = 'input', /** Numeric input with spinner buttons and formatting. */ inputNumber = 'inputNumber', /** Text input with pattern-based masking (phone, SSN, etc.). */ inputMask = 'inputMask', /** One-time password multi-character input. */ inputOtp = 'inputOtp', /** Password input with visibility toggle and strength meter. */ password = 'password', /** Date and/or time picker with calendar popup. */ date_picker = 'date_picker', /** Multi-line text input. */ textarea = 'textarea', /** Rich text WYSIWYG editor. */ editor = 'editor', /** Binary checkbox (true/false). */ checkbox = 'checkbox', /** Radio button group for single selection. */ radio = 'radio', /** Color picker with multiple format support. */ color_picker = 'color_picker', /** Dropdown select with single selection. */ dropdown = 'dropdown', /** Autocomplete with type-ahead search. */ auto_complete = 'auto_complete', /** Multi-select dropdown with chip display. */ multi_select = 'multi_select', /** File upload with validation. */ file_upload = 'file_upload', /** Nested data grid for one-to-many relationships. */ grid = 'grid', /** Visual separator line. */ divider = 'divider', /** Star-based rating input. */ rating = 'rating', /** Circular dial for numeric input. */ knob = 'knob', /** Collapsible accordion panels (field container). */ accordion = 'accordion', /** Tabbed view for organizing fields (field container). */ tab_view = 'tab_view', /** Clickable button with custom actions. */ button = 'button', /** Image display with preview. */ image = 'image', /** Custom HTML content. */ customHTML = 'customHTML', } export enum EDBMasterCustomActionButtonAppendTo { click = 'click', } ``` --- # Input Number Form Control > Source: https://docs.apimaker.dev/v1/extensions/ui_maker/form_controls/inputNumber_form_control.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). ## Example ```typescript let dbMasterConfig: T.IDBMasterConfig = { form: { fields: [ [{ label: 'Current CTC', control: T.EDBMasterFormControl.inputNumber, path: 'current_ctc', inputNumberSettings: { mode: 'currency', currency: 'INR', } }] ] } }; ``` ## Interface Documentation ```typescript /** * Form field configuration for UI controls. * Defines the appearance, behavior, and validation of individual form inputs. */ export interface IDBMasterConfigFormField { /** * Unique identifier for programmatic element access. * Useful for dynamic field manipulation. */ hiddenId?: string; /** Display label for the form field. */ label?: string; /** * Help text displayed below the control. * Supports HTML formatting for rich content. */ helpText?: string; /** * Database field path where the control value is stored. * Supports nested paths using dot notation. * @example 'name', 'address.city', 'user.contact.email' */ path?: string; /** Type of UI control to render. */ control?: EDBMasterFormControl; /** * CSS classes for the parent div wrapping the control. * @default 'col-lg mt-4 col-md-{calculated based on columns.length}' */ cssClassDiv?: string; /** * Auto-focus this control when form opens. * @default false */ autofocus?: boolean; /** * Disable the control or use expression to conditionally disable. * When a string is provided, it's evaluated as JavaScript. * @example true | false | "formData.type === 'readonly'" */ disabled?: boolean | string; /** * Control visibility or use expression to conditionally show/hide. * When a string is provided, it's evaluated as JavaScript. * @default true * @example true | false | "formData.userRole === 'admin'" */ visible?: boolean | string; /** * Nested form fields for complex layouts. * Enables hierarchical form structures within this field. */ fields?: IDBMasterConfigFormField[][]; /** Validation rules for this field. */ validations?: Pick & { /** * Dynamic required validation function. * Evaluated when form data changes to determine if field is required. * Note: When present, this takes precedence over static 'required' property. * @example "formData.type === 'individual' ? true : false" */ requiredFun?: string; }; /** Custom validation error messages. */ validationErrors?: { /** Custom error message for required field validation. */ required?: string; }; /** Doc : https://primeng.org/inputnumber */ /** * Number input control configuration. * Numeric entry with spinner buttons, formatting, and validation. * * **Features:** * - Min/max range validation * - Step increment/decrement * - Decimal precision control (minFractionDigits) * - Locale-based formatting * - Currency mode with ISO 4217 codes * - Prefix/suffix symbols * - Grouping separators (thousands, lakhs, crores) * - Spinner button layouts * * @see {@link https://primeng.org/inputnumber PrimeNG InputNumber Documentation} * * @example * // Basic number input with range and step * inputNumberSettings: { * min: 0, * max: 100, * step: 5, * showButtons: true, * buttonLayout: 'stacked' * } * * @example * // Currency input with formatting * inputNumberSettings: { * mode: 'currency', * currency: 'USD', * locale: 'en-US', * minFractionDigits: 2, * useGrouping: true * } * * @example * // Percentage input * inputNumberSettings: { * suffix: '%', * min: 0, * max: 100, * step: 0.1, * minFractionDigits: 1 * } * * @example * // Decimal number with custom locale * inputNumberSettings: { * mode: 'decimal', * locale: 'de-DE', * minFractionDigits: 2, * useGrouping: true, * showButtons: true, * buttonLayout: 'horizontal' * } */ inputNumberSettings?: { /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, placeholder?: string; min?: number; max?: number; minFractionDigits?: number; mode?: 'decimal' | 'currency' | ''; /** The currency to use in currency formatting. Possible values are the ISO 4217 currency codes, such as "USD" for the US dollar, "EUR" for the euro, or "CNY" for the Chinese RMB. There is no default value; if the style is "currency", the currency property must be provided. */ currency?: string; /** Default : 'en-US' */ locale?: string; prefix?: string; suffix?: string; // buttons showButtons?: boolean; /** Default : stacked */ buttonLayout?: 'stacked' | 'horizontal' | 'vertical'; step?: number; /** Whether to use grouping separators, such as thousands separators or thousand/lakh/crore separators. */ useGrouping?: boolean; /** Maximum number of character allows in the input field. */ maxLength?: number; /** Minimum number of character allows in the input field. */ minLength?: number; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterInputNumberAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], validationErrors?: { minLength?: string; }; }; } /** * Validation rules for schema properties and form fields. * Define constraints that values must satisfy before being saved. */ export interface IPropertyValidation { /** * Field is mandatory and must have a non-null, non-empty value. * Applicable to all data types. */ required?: boolean; /** * Minimum allowed value. * Applicable to: number, date */ min?: number; /** * Maximum allowed value. * Applicable to: number, date */ max?: number; /** * Minimum string/array length. * Applicable to: string, array */ minLength?: number; /** * Maximum string/array length. * Applicable to: string, array */ maxLength?: number; /** * Value must be unique across all records. * @deprecated API Maker maintains uniqueness internally. * Avoid using on tables with frequent updates due to performance impact. */ unique?: boolean; /** * Validate email address format. * Applicable to: string */ email?: boolean; /** * Custom validation function. * Return true if valid, false or error message if invalid. */ validatorFun?: Function; /** * Enumeration constraint - value must be from this array. * @example ['active', 'inactive', 'pending'] */ enum?: any[]; } export enum EDBMasterInputNumberAppendTo { visible = 'visible', disabled = 'disabled', ngModelChange = 'ngModelChange', focus = 'focus', blur = 'blur', keyUp = 'keyUp', keyDown = 'keyDown', } /** * Available form control types for UI generation. * Each control type has specific settings and behavior. */ export enum EDBMasterFormControl { /** Single-line text input. */ input = 'input', /** Numeric input with spinner buttons and formatting. */ inputNumber = 'inputNumber', /** Text input with pattern-based masking (phone, SSN, etc.). */ inputMask = 'inputMask', /** One-time password multi-character input. */ inputOtp = 'inputOtp', /** Password input with visibility toggle and strength meter. */ password = 'password', /** Date and/or time picker with calendar popup. */ date_picker = 'date_picker', /** Multi-line text input. */ textarea = 'textarea', /** Rich text WYSIWYG editor. */ editor = 'editor', /** Binary checkbox (true/false). */ checkbox = 'checkbox', /** Radio button group for single selection. */ radio = 'radio', /** Color picker with multiple format support. */ color_picker = 'color_picker', /** Dropdown select with single selection. */ dropdown = 'dropdown', /** Autocomplete with type-ahead search. */ auto_complete = 'auto_complete', /** Multi-select dropdown with chip display. */ multi_select = 'multi_select', /** File upload with validation. */ file_upload = 'file_upload', /** Nested data grid for one-to-many relationships. */ grid = 'grid', /** Visual separator line. */ divider = 'divider', /** Star-based rating input. */ rating = 'rating', /** Circular dial for numeric input. */ knob = 'knob', /** Collapsible accordion panels (field container). */ accordion = 'accordion', /** Tabbed view for organizing fields (field container). */ tab_view = 'tab_view', /** Clickable button with custom actions. */ button = 'button', /** Image display with preview. */ image = 'image', /** Custom HTML content. */ customHTML = 'customHTML', } export enum EDBMasterCustomActionButtonAppendTo { click = 'click', } ``` --- # Input Mask Form Control > Source: https://docs.apimaker.dev/v1/extensions/ui_maker/form_controls/inputMask_form_control.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). ## Example ```typescript let dbMasterConfig: T.IDBMasterConfig = { form: { fields: [ [{ label: 'Current CTC', control: T.EDBMasterFormControl.inputMask, path: 'current_ctc', inputMaskSettings: { mask: '99:99 aa', autoClear: true, // placeholder: 'HH:MM A', // slotChar: 'HH:MM A', jsCode: [{ appendTo: T.EDBMasterInputMaskAppendTo.ngModelChange, code: ` console.log(event); console.log(formData); console.log(config); console.log(column); `, }, { appendTo: T.EDBMasterInputMaskAppendTo.complete, code: ` console.log('from blur/complete'); console.log(formData.current_ctc); `, }, { appendTo: T.EDBMasterInputMaskAppendTo.focus, code: ` console.log(formData); `, }] } }] ] } }; ``` ## Interface Documentation ```typescript /** * Form field configuration for UI controls. * Defines the appearance, behavior, and validation of individual form inputs. */ export interface IDBMasterConfigFormField { /** * Unique identifier for programmatic element access. * Useful for dynamic field manipulation. */ hiddenId?: string; /** Display label for the form field. */ label?: string; /** * Help text displayed below the control. * Supports HTML formatting for rich content. */ helpText?: string; /** * Database field path where the control value is stored. * Supports nested paths using dot notation. * @example 'name', 'address.city', 'user.contact.email' */ path?: string; /** Type of UI control to render. */ control?: EDBMasterFormControl; /** * CSS classes for the parent div wrapping the control. * @default 'col-lg mt-4 col-md-{calculated based on columns.length}' */ cssClassDiv?: string; /** * Auto-focus this control when form opens. * @default false */ autofocus?: boolean; /** * Disable the control or use expression to conditionally disable. * When a string is provided, it's evaluated as JavaScript. * @example true | false | "formData.type === 'readonly'" */ disabled?: boolean | string; /** * Control visibility or use expression to conditionally show/hide. * When a string is provided, it's evaluated as JavaScript. * @default true * @example true | false | "formData.userRole === 'admin'" */ visible?: boolean | string; /** * Nested form fields for complex layouts. * Enables hierarchical form structures within this field. */ fields?: IDBMasterConfigFormField[][]; /** Validation rules for this field. */ validations?: Pick & { /** * Dynamic required validation function. * Evaluated when form data changes to determine if field is required. * Note: When present, this takes precedence over static 'required' property. * @example "formData.type === 'individual' ? true : false" */ requiredFun?: string; }; /** Custom validation error messages. */ validationErrors?: { /** Custom error message for required field validation. */ required?: string; }; /** * Input mask control configuration. * Format text input with predefined patterns (phone, SSN, date, etc.). * * **Mask Patterns:** * - **a**: Alphabetic character (A-Z, a-z) * - **9**: Numeric character (0-9) * - **\***: Alphanumeric character (A-Z, a-z, 0-9) * - **?**: Marks everything after as optional * - Formatting chars: `( ) - / . , :` * * @example * // Phone number mask * inputMaskSettings: { * mask: '(999) 999-9999', * placeholder: '(123) 456-7890', * autoClear: true * } * * @example * // SSN mask * inputMaskSettings: { * mask: '999-99-9999', * slotChar: 'mm/dd/yyyy' * } * * @example * // Optional extension * inputMaskSettings: { * mask: '(999) 999-9999? x99999', * placeholder: '(123) 456-7890 x12345' * } * * @example * // Date mask * inputMaskSettings: { * mask: '99/99/9999', * placeholder: 'mm/dd/yyyy', * slotChar: 'mm/dd/yyyy' * } */ inputMaskSettings?: { /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, /** Ex : mask="99-999999", mask="(999) 999-9999? x99999"
* Mask format can be a combination of the following definitions;
* a for alphabetic characters,
* 9 for numeric characters and
* * for alphanumeric characters.
* In addition, formatting characters like ( , ) , - are also accepted.
* * ? is used to mark anything after the question mark optional.
* */ mask?: string; /** Advisory information to display on input. */ placeholder?: string; /** Ex : slotChar="mm/dd/yyyy"
* Default placeholder for a mask is underscore that can be customized using slotChar property. * */ slotChar?: string; /** Default : true, Clears the incomplete value on blur. */ autoClear?: boolean; /** Maximum number of character allows in the input field. */ maxLength?: number; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterInputMaskAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; } /** * Validation rules for schema properties and form fields. * Define constraints that values must satisfy before being saved. */ export interface IPropertyValidation { /** * Field is mandatory and must have a non-null, non-empty value. * Applicable to all data types. */ required?: boolean; /** * Minimum allowed value. * Applicable to: number, date */ min?: number; /** * Maximum allowed value. * Applicable to: number, date */ max?: number; /** * Minimum string/array length. * Applicable to: string, array */ minLength?: number; /** * Maximum string/array length. * Applicable to: string, array */ maxLength?: number; /** * Value must be unique across all records. * @deprecated API Maker maintains uniqueness internally. * Avoid using on tables with frequent updates due to performance impact. */ unique?: boolean; /** * Validate email address format. * Applicable to: string */ email?: boolean; /** * Custom validation function. * Return true if valid, false or error message if invalid. */ validatorFun?: Function; /** * Enumeration constraint - value must be from this array. * @example ['active', 'inactive', 'pending'] */ enum?: any[]; } export enum EDBMasterInputMaskAppendTo { visible = 'visible', disabled = 'disabled', ngModelChange = 'ngModelChange', focus = 'focus', /** If you are trying to modify model value in blur it will not work. It is old bug in PrimeNG. Use complete method instead. */ blur = 'blur', complete = 'complete', keyUp = 'keyUp', keyDown = 'keyDown', } /** * Available form control types for UI generation. * Each control type has specific settings and behavior. */ export enum EDBMasterFormControl { /** Single-line text input. */ input = 'input', /** Numeric input with spinner buttons and formatting. */ inputNumber = 'inputNumber', /** Text input with pattern-based masking (phone, SSN, etc.). */ inputMask = 'inputMask', /** One-time password multi-character input. */ inputOtp = 'inputOtp', /** Password input with visibility toggle and strength meter. */ password = 'password', /** Date and/or time picker with calendar popup. */ date_picker = 'date_picker', /** Multi-line text input. */ textarea = 'textarea', /** Rich text WYSIWYG editor. */ editor = 'editor', /** Binary checkbox (true/false). */ checkbox = 'checkbox', /** Radio button group for single selection. */ radio = 'radio', /** Color picker with multiple format support. */ color_picker = 'color_picker', /** Dropdown select with single selection. */ dropdown = 'dropdown', /** Autocomplete with type-ahead search. */ auto_complete = 'auto_complete', /** Multi-select dropdown with chip display. */ multi_select = 'multi_select', /** File upload with validation. */ file_upload = 'file_upload', /** Nested data grid for one-to-many relationships. */ grid = 'grid', /** Visual separator line. */ divider = 'divider', /** Star-based rating input. */ rating = 'rating', /** Circular dial for numeric input. */ knob = 'knob', /** Collapsible accordion panels (field container). */ accordion = 'accordion', /** Tabbed view for organizing fields (field container). */ tab_view = 'tab_view', /** Clickable button with custom actions. */ button = 'button', /** Image display with preview. */ image = 'image', /** Custom HTML content. */ customHTML = 'customHTML', } export enum EDBMasterCustomActionButtonAppendTo { click = 'click', } ``` --- # Input OTP Form Control > Source: https://docs.apimaker.dev/v1/extensions/ui_maker/form_controls/inputOtp_form_control.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). ## Example ```typescript let dbMasterConfig: T.IDBMasterConfig = { form: { fields: [ [{ label: 'Login PIN', control: T.EDBMasterFormControl.inputOtp, path: 'login_pin', inputOtpSettings: { length: 6, integerOnly: true, mask: true, } }], ] } }; ``` ## Interface Documentation ```typescript /** * Form field configuration for UI controls. * Defines the appearance, behavior, and validation of individual form inputs. */ export interface IDBMasterConfigFormField { /** * Unique identifier for programmatic element access. * Useful for dynamic field manipulation. */ hiddenId?: string; /** Display label for the form field. */ label?: string; /** * Help text displayed below the control. * Supports HTML formatting for rich content. */ helpText?: string; /** * Database field path where the control value is stored. * Supports nested paths using dot notation. * @example 'name', 'address.city', 'user.contact.email' */ path?: string; /** Type of UI control to render. */ control?: EDBMasterFormControl; /** * CSS classes for the parent div wrapping the control. * @default 'col-lg mt-4 col-md-{calculated based on columns.length}' */ cssClassDiv?: string; /** * Auto-focus this control when form opens. * @default false */ autofocus?: boolean; /** * Disable the control or use expression to conditionally disable. * When a string is provided, it's evaluated as JavaScript. * @example true | false | "formData.type === 'readonly'" */ disabled?: boolean | string; /** * Control visibility or use expression to conditionally show/hide. * When a string is provided, it's evaluated as JavaScript. * @default true * @example true | false | "formData.userRole === 'admin'" */ visible?: boolean | string; /** * Nested form fields for complex layouts. * Enables hierarchical form structures within this field. */ fields?: IDBMasterConfigFormField[][]; /** Validation rules for this field. */ validations?: Pick & { /** * Dynamic required validation function. * Evaluated when form data changes to determine if field is required. * Note: When present, this takes precedence over static 'required' property. * @example "formData.type === 'individual' ? true : false" */ requiredFun?: string; }; /** Custom validation error messages. */ validationErrors?: { /** Custom error message for required field validation. */ required?: string; }; /** * OTP (One-Time Password) input control configuration. * Multi-character input for verification codes and OTPs. * * **Features:** * - Fixed-length character inputs * - Auto-focus navigation between fields * - Integer-only mode * - Masked display for security * - Customizable width * * @example * // 6-digit OTP input * inputOtpSettings: { * length: 6, * integerOnly: true, * mask: true * } * * @example * // 4-character code (alphanumeric) * inputOtpSettings: { * length: 4, * integerOnly: false, * uiControlWidth: '250px' * } */ inputOtpSettings?: { /** Give style object in angular style. */ style?: any; /** Enable the mask option to hide the values in the input fields. */ mask?: boolean; /** When integerOnly is present, only integers can be accepted as input. */ integerOnly?: boolean; /** Default : 300px; */ uiControlWidth?: string; /** Number of characters to initiate. */ length?: number; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; }; } /** * Validation rules for schema properties and form fields. * Define constraints that values must satisfy before being saved. */ export interface IPropertyValidation { /** * Field is mandatory and must have a non-null, non-empty value. * Applicable to all data types. */ required?: boolean; /** * Minimum allowed value. * Applicable to: number, date */ min?: number; /** * Maximum allowed value. * Applicable to: number, date */ max?: number; /** * Minimum string/array length. * Applicable to: string, array */ minLength?: number; /** * Maximum string/array length. * Applicable to: string, array */ maxLength?: number; /** * Value must be unique across all records. * @deprecated API Maker maintains uniqueness internally. * Avoid using on tables with frequent updates due to performance impact. */ unique?: boolean; /** * Validate email address format. * Applicable to: string */ email?: boolean; /** * Custom validation function. * Return true if valid, false or error message if invalid. */ validatorFun?: Function; /** * Enumeration constraint - value must be from this array. * @example ['active', 'inactive', 'pending'] */ enum?: any[]; } export enum EDBMasterInputOtpAppendTo { visible = 'visible', disabled = 'disabled', } /** * Available form control types for UI generation. * Each control type has specific settings and behavior. */ export enum EDBMasterFormControl { /** Single-line text input. */ input = 'input', /** Numeric input with spinner buttons and formatting. */ inputNumber = 'inputNumber', /** Text input with pattern-based masking (phone, SSN, etc.). */ inputMask = 'inputMask', /** One-time password multi-character input. */ inputOtp = 'inputOtp', /** Password input with visibility toggle and strength meter. */ password = 'password', /** Date and/or time picker with calendar popup. */ date_picker = 'date_picker', /** Multi-line text input. */ textarea = 'textarea', /** Rich text WYSIWYG editor. */ editor = 'editor', /** Binary checkbox (true/false). */ checkbox = 'checkbox', /** Radio button group for single selection. */ radio = 'radio', /** Color picker with multiple format support. */ color_picker = 'color_picker', /** Dropdown select with single selection. */ dropdown = 'dropdown', /** Autocomplete with type-ahead search. */ auto_complete = 'auto_complete', /** Multi-select dropdown with chip display. */ multi_select = 'multi_select', /** File upload with validation. */ file_upload = 'file_upload', /** Nested data grid for one-to-many relationships. */ grid = 'grid', /** Visual separator line. */ divider = 'divider', /** Star-based rating input. */ rating = 'rating', /** Circular dial for numeric input. */ knob = 'knob', /** Collapsible accordion panels (field container). */ accordion = 'accordion', /** Tabbed view for organizing fields (field container). */ tab_view = 'tab_view', /** Clickable button with custom actions. */ button = 'button', /** Image display with preview. */ image = 'image', /** Custom HTML content. */ customHTML = 'customHTML', } export enum EDBMasterCustomActionButtonAppendTo { click = 'click', } ``` --- # Password Form Control > Source: https://docs.apimaker.dev/v1/extensions/ui_maker/form_controls/password_form_control.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). ## Example ```typescript let dbMasterConfig: T.IDBMasterConfig = { form: { fields: [ [{ label: 'Password', control: T.EDBMasterFormControl.password, path: 'user_password', inputPasswordSettings: { jsCode: [{ appendTo: T.EDBMasterInputPasswordAppendTo.ngModelChange, code: ` console.log(event); console.log(formData); console.log(config); console.log(column); `, }, { appendTo: T.EDBMasterInputPasswordAppendTo.blur, code: ` console.log(formData); `, }, { appendTo: T.EDBMasterInputPasswordAppendTo.focus, code: ` console.log(formData); `, }] } }] ] } }; ``` ## Interface Documentation ```typescript /** * Form field configuration for UI controls. * Defines the appearance, behavior, and validation of individual form inputs. */ export interface IDBMasterConfigFormField { /** * Unique identifier for programmatic element access. * Useful for dynamic field manipulation. */ hiddenId?: string; /** Display label for the form field. */ label?: string; /** * Help text displayed below the control. * Supports HTML formatting for rich content. */ helpText?: string; /** * Database field path where the control value is stored. * Supports nested paths using dot notation. * @example 'name', 'address.city', 'user.contact.email' */ path?: string; /** Type of UI control to render. */ control?: EDBMasterFormControl; /** * CSS classes for the parent div wrapping the control. * @default 'col-lg mt-4 col-md-{calculated based on columns.length}' */ cssClassDiv?: string; /** * Auto-focus this control when form opens. * @default false */ autofocus?: boolean; /** * Disable the control or use expression to conditionally disable. * When a string is provided, it's evaluated as JavaScript. * @example true | false | "formData.type === 'readonly'" */ disabled?: boolean | string; /** * Control visibility or use expression to conditionally show/hide. * When a string is provided, it's evaluated as JavaScript. * @default true * @example true | false | "formData.userRole === 'admin'" */ visible?: boolean | string; /** * Nested form fields for complex layouts. * Enables hierarchical form structures within this field. */ fields?: IDBMasterConfigFormField[][]; /** Validation rules for this field. */ validations?: Pick & { /** * Dynamic required validation function. * Evaluated when form data changes to determine if field is required. * Note: When present, this takes precedence over static 'required' property. * @example "formData.type === 'individual' ? true : false" */ requiredFun?: string; }; /** Custom validation error messages. */ validationErrors?: { /** Custom error message for required field validation. */ required?: string; }; /** * Password input control configuration. * Secure password entry with strength meter and visibility toggle. * * **Features:** * - Masked character display * - Toggle visibility button * - Password strength feedback * - Custom validation prompts * * @example * // Basic password input * inputPasswordSettings: { * placeholder: 'Enter password', * toggleMask: true * } * * @example * // Password with strength meter * inputPasswordSettings: { * placeholder: 'Create a strong password', * toggleMask: true, * feedback: true, * promptLabel: 'Enter a password', * weakLabel: 'Weak', * mediumLabel: 'Medium', * strongLabel: 'Strong' * } */ inputPasswordSettings?: { /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, placeholder?: string; /** Maximum number of character allows in the input field. */ maxLength?: number; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterInputPasswordAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; } /** * Validation rules for schema properties and form fields. * Define constraints that values must satisfy before being saved. */ export interface IPropertyValidation { /** * Field is mandatory and must have a non-null, non-empty value. * Applicable to all data types. */ required?: boolean; /** * Minimum allowed value. * Applicable to: number, date */ min?: number; /** * Maximum allowed value. * Applicable to: number, date */ max?: number; /** * Minimum string/array length. * Applicable to: string, array */ minLength?: number; /** * Maximum string/array length. * Applicable to: string, array */ maxLength?: number; /** * Value must be unique across all records. * @deprecated API Maker maintains uniqueness internally. * Avoid using on tables with frequent updates due to performance impact. */ unique?: boolean; /** * Validate email address format. * Applicable to: string */ email?: boolean; /** * Custom validation function. * Return true if valid, false or error message if invalid. */ validatorFun?: Function; /** * Enumeration constraint - value must be from this array. * @example ['active', 'inactive', 'pending'] */ enum?: any[]; } export enum EDBMasterInputPasswordAppendTo { visible = 'visible', disabled = 'disabled', ngModelChange = 'ngModelChange', focus = 'focus', blur = 'blur', keyUp = 'keyUp', keyDown = 'keyDown', } /** * Available form control types for UI generation. * Each control type has specific settings and behavior. */ export enum EDBMasterFormControl { /** Single-line text input. */ input = 'input', /** Numeric input with spinner buttons and formatting. */ inputNumber = 'inputNumber', /** Text input with pattern-based masking (phone, SSN, etc.). */ inputMask = 'inputMask', /** One-time password multi-character input. */ inputOtp = 'inputOtp', /** Password input with visibility toggle and strength meter. */ password = 'password', /** Date and/or time picker with calendar popup. */ date_picker = 'date_picker', /** Multi-line text input. */ textarea = 'textarea', /** Rich text WYSIWYG editor. */ editor = 'editor', /** Binary checkbox (true/false). */ checkbox = 'checkbox', /** Radio button group for single selection. */ radio = 'radio', /** Color picker with multiple format support. */ color_picker = 'color_picker', /** Dropdown select with single selection. */ dropdown = 'dropdown', /** Autocomplete with type-ahead search. */ auto_complete = 'auto_complete', /** Multi-select dropdown with chip display. */ multi_select = 'multi_select', /** File upload with validation. */ file_upload = 'file_upload', /** Nested data grid for one-to-many relationships. */ grid = 'grid', /** Visual separator line. */ divider = 'divider', /** Star-based rating input. */ rating = 'rating', /** Circular dial for numeric input. */ knob = 'knob', /** Collapsible accordion panels (field container). */ accordion = 'accordion', /** Tabbed view for organizing fields (field container). */ tab_view = 'tab_view', /** Clickable button with custom actions. */ button = 'button', /** Image display with preview. */ image = 'image', /** Custom HTML content. */ customHTML = 'customHTML', } export enum EDBMasterCustomActionButtonAppendTo { click = 'click', } ``` --- # Date Picker Form Control > Source: https://docs.apimaker.dev/v1/extensions/ui_maker/form_controls/date_picker_form_control.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). ## Normal Date Picker ```typescript let dbMasterConfig: T.IDBMasterConfig = { form: { fields: [ [{ label: 'Remark Date', control: T.EDBMasterFormControl.date_picker, path: 'remarkDate', datePickerSettings: { showTime: false, showSeconds: false, // dateTimeFormat: 'dd-MM-yyyy', } }] ] } }; ``` ## Only Month Picker ```typescript let dbMasterConfig: T.IDBMasterConfig = { form: { fields: [ [{ label: 'Birth Month', control: T.EDBMasterFormControl.date_picker, path: 'birth_month', datePickerSettings: { showTime: false, showSeconds: false, view: 'month', dateTimeFormat: 'MM-yyyy', } }] ] } }; ``` ## Only Time Picker ```typescript let dbMasterConfig: T.IDBMasterConfig = { form: { fields: [ [{ label: 'Birth Time', control: T.EDBMasterFormControl.date_picker, path: 'birth_time', datePickerSettings: { timeOnly: true, hourFormat: '12', } }] ] } }; ``` ## Date Picker With Hooks ```typescript let dbMasterConfig: T.IDBMasterConfig = { form: { fields: [ [{ label: 'Created At', control: T.EDBMasterFormControl.date_picker, path: 'createdAt', datePickerSettings: { jsCode: [{ appendTo: T.EDBMasterDatePickerAppendTo.ngModelChange, code: ` console.log(event); console.log(formData); console.log(config); console.log(column); `, }, { appendTo: T.EDBMasterDatePickerAppendTo.blur, code: ` console.log(formData); `, }, { appendTo: T.EDBMasterDatePickerAppendTo.focus, code: ` console.log(formData); `, }] } }] ] } }; ``` ## Interface Documentation ```typescript /** * Form field configuration for UI controls. * Defines the appearance, behavior, and validation of individual form inputs. */ export interface IDBMasterConfigFormField { /** * Unique identifier for programmatic element access. * Useful for dynamic field manipulation. */ hiddenId?: string; /** Display label for the form field. */ label?: string; /** * Help text displayed below the control. * Supports HTML formatting for rich content. */ helpText?: string; /** * Database field path where the control value is stored. * Supports nested paths using dot notation. * @example 'name', 'address.city', 'user.contact.email' */ path?: string; /** Type of UI control to render. */ control?: EDBMasterFormControl; /** * CSS classes for the parent div wrapping the control. * @default 'col-lg mt-4 col-md-{calculated based on columns.length}' */ cssClassDiv?: string; /** * Auto-focus this control when form opens. * @default false */ autofocus?: boolean; /** * Disable the control or use expression to conditionally disable. * When a string is provided, it's evaluated as JavaScript. * @example true | false | "formData.type === 'readonly'" */ disabled?: boolean | string; /** * Control visibility or use expression to conditionally show/hide. * When a string is provided, it's evaluated as JavaScript. * @default true * @example true | false | "formData.userRole === 'admin'" */ visible?: boolean | string; /** * Nested form fields for complex layouts. * Enables hierarchical form structures within this field. */ fields?: IDBMasterConfigFormField[][]; /** Validation rules for this field. */ validations?: Pick & { /** * Dynamic required validation function. * Evaluated when form data changes to determine if field is required. * Note: When present, this takes precedence over static 'required' property. * @example "formData.type === 'individual' ? true : false" */ requiredFun?: string; }; /** Custom validation error messages. */ validationErrors?: { /** Custom error message for required field validation. */ required?: string; }; /** * It's value will be Date object. * Date picker control configuration. * Date and time selection with calendar popup. * Stores value as JavaScript Date object. * * **Features:** * - Date-only, time-only, or date-time modes * - 12/24 hour format * - Min/max date constraints * - Custom date format display * - Seconds display toggle * - Inline calendar option * * @example * // Basic date picker * datePickerSettings: { * placeholder: 'Select a date', * dateTimeFormat: 'dd-MM-yyyy' * } * * @example * // Date and time picker with 12-hour format * datePickerSettings: { * showTime: true, * hourFormat: '12', * dateTimeFormat: 'dd-MM-yyyy hh:mm a' * } * * @example * // Time-only picker with seconds * datePickerSettings: { * timeOnly: true, * hourFormat: '24', * showSeconds: true, * dateTimeFormat: 'HH:mm:ss' * } * * @example * // Date picker with range constraints * datePickerSettings: { * minDate: new Date(2020, 0, 1), * maxDate: new Date(), * dateTimeFormat: 'dd/MM/yyyy' * } */ datePickerSettings?: { /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, placeholder?: string; showSeconds?: boolean; showTime?: boolean; /** Default : 24 */ hourFormat?: '12' | '24'; /** ex: dd-MM-yyyy hh:mm:ss a */ dateTimeFormat?: string; /** Whether to display timepicker only. Use hourFormat 12 to display AM/PM in UI. */ timeOnly?: boolean; /** The minimum selectable date. */ minDate?: Date; /** The maximum selectable date. */ maxDate?: Date; /** * Default : date * Update dateTimeFormat for 'month'(MM-yyyy) & 'year'(yyyy) values. */ view?: 'date' | 'month' | 'year'; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterDatePickerAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; } /** * Validation rules for schema properties and form fields. * Define constraints that values must satisfy before being saved. */ export interface IPropertyValidation { /** * Field is mandatory and must have a non-null, non-empty value. * Applicable to all data types. */ required?: boolean; /** * Minimum allowed value. * Applicable to: number, date */ min?: number; /** * Maximum allowed value. * Applicable to: number, date */ max?: number; /** * Minimum string/array length. * Applicable to: string, array */ minLength?: number; /** * Maximum string/array length. * Applicable to: string, array */ maxLength?: number; /** * Value must be unique across all records. * @deprecated API Maker maintains uniqueness internally. * Avoid using on tables with frequent updates due to performance impact. */ unique?: boolean; /** * Validate email address format. * Applicable to: string */ email?: boolean; /** * Custom validation function. * Return true if valid, false or error message if invalid. */ validatorFun?: Function; /** * Enumeration constraint - value must be from this array. * @example ['active', 'inactive', 'pending'] */ enum?: any[]; } export enum EDBMasterDatePickerAppendTo { visible = 'visible', disabled = 'disabled', ngModelChange = 'ngModelChange', focus = 'focus', blur = 'blur', } /** * Available form control types for UI generation. * Each control type has specific settings and behavior. */ export enum EDBMasterFormControl { /** Single-line text input. */ input = 'input', /** Numeric input with spinner buttons and formatting. */ inputNumber = 'inputNumber', /** Text input with pattern-based masking (phone, SSN, etc.). */ inputMask = 'inputMask', /** One-time password multi-character input. */ inputOtp = 'inputOtp', /** Password input with visibility toggle and strength meter. */ password = 'password', /** Date and/or time picker with calendar popup. */ date_picker = 'date_picker', /** Multi-line text input. */ textarea = 'textarea', /** Rich text WYSIWYG editor. */ editor = 'editor', /** Binary checkbox (true/false). */ checkbox = 'checkbox', /** Radio button group for single selection. */ radio = 'radio', /** Color picker with multiple format support. */ color_picker = 'color_picker', /** Dropdown select with single selection. */ dropdown = 'dropdown', /** Autocomplete with type-ahead search. */ auto_complete = 'auto_complete', /** Multi-select dropdown with chip display. */ multi_select = 'multi_select', /** File upload with validation. */ file_upload = 'file_upload', /** Nested data grid for one-to-many relationships. */ grid = 'grid', /** Visual separator line. */ divider = 'divider', /** Star-based rating input. */ rating = 'rating', /** Circular dial for numeric input. */ knob = 'knob', /** Collapsible accordion panels (field container). */ accordion = 'accordion', /** Tabbed view for organizing fields (field container). */ tab_view = 'tab_view', /** Clickable button with custom actions. */ button = 'button', /** Image display with preview. */ image = 'image', /** Custom HTML content. */ customHTML = 'customHTML', } export enum EDBMasterCustomActionButtonAppendTo { click = 'click', } ``` --- # Textarea Form Control > Source: https://docs.apimaker.dev/v1/extensions/ui_maker/form_controls/textarea_form_control.html !!! deprecated "UI Maker is deprecated" UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](/v1/docs/apis-all/overview.html). ## Example ```typescript let dbMasterConfig: T.IDBMasterConfig = { form: { fields: [{ label: 'Current Address', control: T.EDBMasterFormControl.textarea, path: 'current_address', textAreaSettings: { jsCode: [{ appendTo: T.EDBMasterTextAreaAppendTo.ngModelChange, code: ` console.log(event); console.log(formData); console.log(config); console.log(column); `, }, { appendTo: T.EDBMasterTextAreaAppendTo.blur, code: ` console.log(formData); `, }, { appendTo: T.EDBMasterTextAreaAppendTo.focus, code: ` console.log(formData); `, }] } }] } }; ``` ## Interface Documentation ```typescript /** * Form field configuration for UI controls. * Defines the appearance, behavior, and validation of individual form inputs. */ export interface IDBMasterConfigFormField { /** * Unique identifier for programmatic element access. * Useful for dynamic field manipulation. */ hiddenId?: string; /** Display label for the form field. */ label?: string; /** * Help text displayed below the control. * Supports HTML formatting for rich content. */ helpText?: string; /** * Database field path where the control value is stored. * Supports nested paths using dot notation. * @example 'name', 'address.city', 'user.contact.email' */ path?: string; /** Type of UI control to render. */ control?: EDBMasterFormControl; /** * CSS classes for the parent div wrapping the control. * @default 'col-lg mt-4 col-md-{calculated based on columns.length}' */ cssClassDiv?: string; /** * Auto-focus this control when form opens. * @default false */ autofocus?: boolean; /** * Disable the control or use expression to conditionally disable. * When a string is provided, it's evaluated as JavaScript. * @example true | false | "formData.type === 'readonly'" */ disabled?: boolean | string; /** * Control visibility or use expression to conditionally show/hide. * When a string is provided, it's evaluated as JavaScript. * @default true * @example true | false | "formData.userRole === 'admin'" */ visible?: boolean | string; /** * Nested form fields for complex layouts. * Enables hierarchical form structures within this field. */ fields?: IDBMasterConfigFormField[][]; /** Validation rules for this field. */ validations?: Pick & { /** * Dynamic required validation function. * Evaluated when form data changes to determine if field is required. * Note: When present, this takes precedence over static 'required' property. * @example "formData.type === 'individual' ? true : false" */ requiredFun?: string; }; /** Custom validation error messages. */ validationErrors?: { /** Custom error message for required field validation. */ required?: string; }; /** * Multi-line text input configuration. */ textAreaSettings?: { /** Inline style object (Angular style binding format). */ style?: any; /** Placeholder text displayed when empty. */ placeholder?: string; /** Initial number of visible text rows. */ rows?: number; /** * Automatically adjust height based on content. * @default false */ autoResize?: boolean; /** Maximum character length allowed. */ maxLength?: number; /** Tooltip text displayed on hover. */ tooltip?: string; /** * Tooltip position relative to the element. * @default 'top' */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; /** CSS class for tooltip styling. */ tooltipStyleClass?: string; /** Custom event handlers and behavior scripts. */ jsCode?: { /** * Event type or lifecycle hook for code execution. * Available variables in scope: * - formData: Complete form object * - column: This field's configuration * - allDropdownDataMap: Map of all dropdown data by path * - globalData: User-provided global context * - utils: Common utility functions * - queryParams: URL query parameters * - config: Form field configuration * - event: Native event object */ appendTo: EDBMasterTextAreaAppendTo, /** * JavaScript code or function to execute. * @example * // Async operation * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * formData.description = 'Updated'; * resolve(); * }); */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; editorSettings?: { /** Give style object in angular style. */ style?: any; placeholder?: string; /** Maximum number of character allows in the input field. */ maxLength?: number; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; /** Only those controls and formats will be allowed * ex: ['background', 'bold', 'color', 'font', 'code', 'italic', 'link', 'size', 'strike', 'script', 'underline', 'blockquote', 'header', 'indent', 'list', 'align', 'direction', 'code-block', 'image', 'video', 'clean'] * */ formats?: string[]; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterEditorAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; /** * Text input control configuration. * Single-line text entry with validation and autocomplete support. * * **Features:** * - Character length limits (min/max) * - Autocomplete on/off control * - Spellcheck toggle * - Tooltip support * - Custom event handlers * * @example * // Basic text input with length limit * inputTextSettings: { * placeholder: 'Enter your name', * maxLength: 100, * autocomplete: 'off' * } * * @example * // Text input with validation and tooltip * inputTextSettings: { * minLength: 3, * maxLength: 50, * tooltip: 'Username must be 3-50 characters', * tooltipPosition: 'top', * validationErrors: { * minLength: 'Username must be at least 3 characters' * } * } */ inputTextSettings?: { /** Give style object in angular style. */ style?: any; placeholder?: string; /** Default is off */ autocomplete?: 'on' | 'off' | undefined; /** Default is false */ spellcheck?: 'true' | 'false' | undefined; /** Maximum number of character allows in the input field. */ maxLength?: number; /** Minimum number of character allows in the input field. */ minLength?: number; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterInputTextAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], validationErrors?: { minLength?: string; }; }; /** Doc : https://primeng.org/inputnumber */ /** * Number input control configuration. * Numeric entry with spinner buttons, formatting, and validation. * * **Features:** * - Min/max range validation * - Step increment/decrement * - Decimal precision control (minFractionDigits) * - Locale-based formatting * - Currency mode with ISO 4217 codes * - Prefix/suffix symbols * - Grouping separators (thousands, lakhs, crores) * - Spinner button layouts * * @see {@link https://primeng.org/inputnumber PrimeNG InputNumber Documentation} * * @example * // Basic number input with range and step * inputNumberSettings: { * min: 0, * max: 100, * step: 5, * showButtons: true, * buttonLayout: 'stacked' * } * * @example * // Currency input with formatting * inputNumberSettings: { * mode: 'currency', * currency: 'USD', * locale: 'en-US', * minFractionDigits: 2, * useGrouping: true * } * * @example * // Percentage input * inputNumberSettings: { * suffix: '%', * min: 0, * max: 100, * step: 0.1, * minFractionDigits: 1 * } * * @example * // Decimal number with custom locale * inputNumberSettings: { * mode: 'decimal', * locale: 'de-DE', * minFractionDigits: 2, * useGrouping: true, * showButtons: true, * buttonLayout: 'horizontal' * } */ inputNumberSettings?: { /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, placeholder?: string; min?: number; max?: number; minFractionDigits?: number; mode?: 'decimal' | 'currency' | ''; /** The currency to use in currency formatting. Possible values are the ISO 4217 currency codes, such as "USD" for the US dollar, "EUR" for the euro, or "CNY" for the Chinese RMB. There is no default value; if the style is "currency", the currency property must be provided. */ currency?: string; /** Default : 'en-US' */ locale?: string; prefix?: string; suffix?: string; // buttons showButtons?: boolean; /** Default : stacked */ buttonLayout?: 'stacked' | 'horizontal' | 'vertical'; step?: number; /** Whether to use grouping separators, such as thousands separators or thousand/lakh/crore separators. */ useGrouping?: boolean; /** Maximum number of character allows in the input field. */ maxLength?: number; /** Minimum number of character allows in the input field. */ minLength?: number; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterInputNumberAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], validationErrors?: { minLength?: string; }; }; /** * Input mask control configuration. * Format text input with predefined patterns (phone, SSN, date, etc.). * * **Mask Patterns:** * - **a**: Alphabetic character (A-Z, a-z) * - **9**: Numeric character (0-9) * - **\***: Alphanumeric character (A-Z, a-z, 0-9) * - **?**: Marks everything after as optional * - Formatting chars: `( ) - / . , :` * * @example * // Phone number mask * inputMaskSettings: { * mask: '(999) 999-9999', * placeholder: '(123) 456-7890', * autoClear: true * } * * @example * // SSN mask * inputMaskSettings: { * mask: '999-99-9999', * slotChar: 'mm/dd/yyyy' * } * * @example * // Optional extension * inputMaskSettings: { * mask: '(999) 999-9999? x99999', * placeholder: '(123) 456-7890 x12345' * } * * @example * // Date mask * inputMaskSettings: { * mask: '99/99/9999', * placeholder: 'mm/dd/yyyy', * slotChar: 'mm/dd/yyyy' * } */ inputMaskSettings?: { /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, /** Ex : mask="99-999999", mask="(999) 999-9999? x99999"
* Mask format can be a combination of the following definitions;
* a for alphabetic characters,
* 9 for numeric characters and
* * for alphanumeric characters.
* In addition, formatting characters like ( , ) , - are also accepted.
* * ? is used to mark anything after the question mark optional.
* */ mask?: string; /** Advisory information to display on input. */ placeholder?: string; /** Ex : slotChar="mm/dd/yyyy"
* Default placeholder for a mask is underscore that can be customized using slotChar property. * */ slotChar?: string; /** Default : true, Clears the incomplete value on blur. */ autoClear?: boolean; /** Maximum number of character allows in the input field. */ maxLength?: number; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterInputMaskAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; /** * OTP (One-Time Password) input control configuration. * Multi-character input for verification codes and OTPs. * * **Features:** * - Fixed-length character inputs * - Auto-focus navigation between fields * - Integer-only mode * - Masked display for security * - Customizable width * * @example * // 6-digit OTP input * inputOtpSettings: { * length: 6, * integerOnly: true, * mask: true * } * * @example * // 4-character code (alphanumeric) * inputOtpSettings: { * length: 4, * integerOnly: false, * uiControlWidth: '250px' * } */ inputOtpSettings?: { /** Give style object in angular style. */ style?: any; /** Enable the mask option to hide the values in the input fields. */ mask?: boolean; /** When integerOnly is present, only integers can be accepted as input. */ integerOnly?: boolean; /** Default : 300px; */ uiControlWidth?: string; /** Number of characters to initiate. */ length?: number; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; }; /** * Password input control configuration. * Secure password entry with strength meter and visibility toggle. * * **Features:** * - Masked character display * - Toggle visibility button * - Password strength feedback * - Custom validation prompts * * @example * // Basic password input * inputPasswordSettings: { * placeholder: 'Enter password', * toggleMask: true * } * * @example * // Password with strength meter * inputPasswordSettings: { * placeholder: 'Create a strong password', * toggleMask: true, * feedback: true, * promptLabel: 'Enter a password', * weakLabel: 'Weak', * mediumLabel: 'Medium', * strongLabel: 'Strong' * } */ inputPasswordSettings?: { /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, placeholder?: string; /** Maximum number of character allows in the input field. */ maxLength?: number; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterInputPasswordAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; /** * Checkbox control configuration. * Binary selection control for true/false or yes/no values. * * **Features:** * - Boolean value binding * - Custom event handlers (onChange) * - Tooltip support * - Custom styling * * @example * // Basic checkbox * checkboxSettings: { * tooltip: 'Check to enable notifications' * } * * @example * // Checkbox with change handler * checkboxSettings: { * jsCode: [{ * appendTo: 'onChange', * code: ($scope) => { * if ($scope.formData.agreeToTerms) { * $scope.utils.messageService.showInfoToast('Thank you for agreeing!'); * } * } * }] * } */ checkboxSettings?: { /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterCheckboxAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; /** * Rating control configuration. * Star-based rating input with customizable icons and count. * * **Features:** * - Configurable number of stars * - Cancel button to clear rating * - Custom icon classes and HTML * - Separate styles for on/off/cancel states * - Event handlers for rating changes * * @example * // Basic 5-star rating * ratingSettings: { * stars: 5, * cancel: true * } * * @example * // Custom icon rating (hearts) * ratingSettings: { * stars: 5, * iconOnClass: 'pi pi-heart-fill', * iconOffClass: 'pi pi-heart', * iconCancelClass: 'pi pi-ban', * cancel: true * } * * @example * // 10-star rating with custom HTML * ratingSettings: { * stars: 10, * onIconHTML: '', * offIconHTML: '', * cancel: false * } */ ratingSettings?: { /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; /** Default : true, When specified a cancel icon is displayed to allow removing the value. */ cancel?: boolean; /** Style class of the on icon. */ iconOnClass?: string; /** Inline style of the on icon. */ iconOnStyle?: any; /** Style class of the off icon. */ iconOffClass?: string; /** Inline style of the off icon. */ iconOffStyle?: any; /** Style class of the cancel icon. */ iconCancelClass?: string; /** Inline style of the cancel icon. */ iconCancelStyle?: any; /** Number of stars. */ stars?: number; /** cancel rating custom HTML */ cancelIconHTML?: string; onIconHTML?: string; offIconHTML?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterRatingAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; /** * Radio button control configuration. * Single selection from a list of mutually exclusive options. * * **Features:** * - Inline or vertical layout * - Center alignment option * - Custom label-value pairs * - Event handlers for selection changes * * @example * // Inline radio buttons * radioSettings: { * displayType: 'inline', * options: [ * { label: 'Active', value: 'active' }, * { label: 'Inactive', value: 'inactive' }, * { label: 'Pending', value: 'pending' } * ] * } * * @example * // Vertical radio buttons centered * radioSettings: { * displayType: 'new_line', * displayInCenter: true, * options: [ * { label: 'Yes', value: true }, * { label: 'No', value: false } * ] * } */ radioSettings?: { displayType?: 'inline' | 'new_line'; displayInCenter?: boolean; options: { label: string; value: any }[]; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterRadioAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; /** * It's value will be Date object. * Date picker control configuration. * Date and time selection with calendar popup. * Stores value as JavaScript Date object. * * **Features:** * - Date-only, time-only, or date-time modes * - 12/24 hour format * - Min/max date constraints * - Custom date format display * - Seconds display toggle * - Inline calendar option * * @example * // Basic date picker * datePickerSettings: { * placeholder: 'Select a date', * dateTimeFormat: 'dd-MM-yyyy' * } * * @example * // Date and time picker with 12-hour format * datePickerSettings: { * showTime: true, * hourFormat: '12', * dateTimeFormat: 'dd-MM-yyyy hh:mm a' * } * * @example * // Time-only picker with seconds * datePickerSettings: { * timeOnly: true, * hourFormat: '24', * showSeconds: true, * dateTimeFormat: 'HH:mm:ss' * } * * @example * // Date picker with range constraints * datePickerSettings: { * minDate: new Date(2020, 0, 1), * maxDate: new Date(), * dateTimeFormat: 'dd/MM/yyyy' * } */ datePickerSettings?: { /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, placeholder?: string; showSeconds?: boolean; showTime?: boolean; /** Default : 24 */ hourFormat?: '12' | '24'; /** ex: dd-MM-yyyy hh:mm:ss a */ dateTimeFormat?: string; /** Whether to display timepicker only. Use hourFormat 12 to display AM/PM in UI. */ timeOnly?: boolean; /** The minimum selectable date. */ minDate?: Date; /** The maximum selectable date. */ maxDate?: Date; /** * Default : date * Update dateTimeFormat for 'month'(MM-yyyy) & 'year'(yyyy) values. */ view?: 'date' | 'month' | 'year'; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterDatePickerAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; /** * Color picker control configuration. * Interactive color selection with multiple format support. * * **Features:** * - Multiple color formats (hex, rgb, hsb) * - Visual color palette * - Manual color input * - Color preview * * @example * // Hex color picker * colorPickerSettings: { * format: 'hex' * } * * @example * // RGB color picker with tooltip * colorPickerSettings: { * format: 'rgb', * tooltip: 'Select a color', * tooltipPosition: 'top' * } */ colorPickerSettings?: { /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, format?: 'hex' | 'rgb' | 'hsb'; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterColorPickerAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; /** * Dropdown (select) control configuration. * Supports static data, database queries, and custom API calls. */ dropdownSettings?: { /** Inline style object (Angular style binding format). */ style?: any; /** Space-separated CSS class names. */ cssClass?: string, /** Placeholder text displayed when no selection is made. */ placeholder?: string; /** * Show clear button to reset selection. * @default false */ showClear?: boolean; /** * Data source type for dropdown options. * - `static_data`: Use predefined array of options * - `db_data`: Query database for options * - `api_call`: Call custom API endpoint */ dataSource: 'static_data' | 'db_data' | 'api_call'; /** * Static dropdown options. * Used when dataSource is 'static_data'. * @example [{ label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 }] */ staticData?: any[]; /** * Database query configuration for dropdown options. * Can inherit values from schema if field has database relationship defined. */ dbData?: Partial & Pick>; /** * Property name to display in dropdown. * @default 'label' */ optionLabel?: string; /** * Property name to use as option value. * @default 'value' */ optionValue?: string; /** * Enable dropdown filtering. * @default false */ filter?: boolean; /** * Fields to search when filtering. * Supports multiple fields (comma-separated, no spaces). * @example 'name', 'name,email,phone' */ filterBy?: string; /** * Filter matching strategy. * @default 'contains' */ filterMatchMode?: 'contains' | 'startsWith' | 'endsWith' | 'equals' | 'notEquals' | 'in' | 'lt' | 'lte' | 'gt' | 'gte'; /** * Reload dropdown data when form opens. * Ensures options are always up-to-date. * @default false */ alwaysGetLatestDataOnFormOpen?: boolean; /** * Enable virtual scrolling for large datasets. * Improves performance when dealing with thousands of options. * @default false */ virtualScroll?: boolean; /** * Dependent dropdown cascade. * When this dropdown value changes, reload these other dropdowns. * @example ['city', 'area'] - Reload city and area dropdowns */ reloadDropdownsOfPath?: string[]; /** * Required field dependencies. * Dropdown only loads data when these fields have values. * @example ['country', 'state'] - Only load if country and state are selected */ isDependentOnPath?: string[]; /** Tooltip text displayed on hover. */ tooltip?: string; /** Tooltip position relative to the element. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; /** CSS class for tooltip styling. */ tooltipStyleClass?: string; /** Custom event handlers and data transformation scripts. */ jsCode?: { /** * Event type or lifecycle hook: * - modifyDropdownRequest: Before API call * - onceDropdownDataLoaded: After data loaded * - onChange: When selection changes * * Available variables: * - reqBody: Query object for modification * - formData: Complete form data * - dropdownData: Loaded options array * - reloadDropdownsOfPath: Array to trigger dependent reloads */ appendTo: EDBMasterDropdownAppendTo, code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], /** * Nested form configuration for adding new options. * Opens a modal to create new records that can be immediately selected. */ addNewFormConfig?: IDBMasterConfig; /** * Custom API endpoint configuration. * Override default query API with custom endpoint and logic. */ apiCallOverrides?: IDBMasterAPICallOverrides; }; /** * Autocomplete control configuration. * Search and select from a list of options with type-ahead functionality. * * **Data Sources:** * - **static_data**: Predefined options array * - **db_data**: Options from database collection/table * - **api_call**: Custom API endpoint for dynamic data * * **Key Features:** * - Real-time search filtering * - Dropdown on arrow click * - Clear button support * - Lazy loading with virtual scrolling * - Min search length threshold * - Dependent field reloading * * @example * // Basic autocomplete with static data * autocompleteSettings: { * dataSource: 'static_data', * staticData: countries, * optionLabel: 'name', * showClear: true * } * * @example * // Database autocomplete with search filtering * autocompleteSettings: { * dataSource: 'db_data', * dbData: { * collection: 'users', * select: { fullName: 1, email: 1 }, * limit: 50 * }, * optionLabel: 'fullName', * filterBy: 'fullName,email', * minLengthForSearch: 2 * } * * @example * // Cascading autocomplete with dependencies * autocompleteSettings: { * dataSource: 'db_data', * dbData: { collection: 'cities' }, * isDependentOnPath: ['countryId', 'stateId'], * reloadDropdownsOfPath: ['districtId'], * dropdown: true * } */ autocompleteSettings?: { /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, placeholder?: string; showClear?: boolean; /** Minimum number of characters to initiate a search. */ minLengthForSearch?: number; /** Delay between keystrokes to wait before sending a query. */ delay?: number; /** When present, autocomplete clears the manual input if it does not match of the suggestions to force only accepting values from the suggestions. */ forceSelection?: number; dataSource: 'static_data' | 'db_data' | 'api_call'; // custom_code = We can call any API in that. /** it will use used when dataSource is 'static_data'. */ staticData?: any[]; // { label: string; value: any; }[] works default. /** * it can pickup IDB values from schema also. */ dbData?: Partial & Pick>; /** Default : label */ optionLabel?: string; /** Default : value */ // optionValue?: string; /** one field or multiple comma separated fields are supported without any space in between. */ filterBy?: string; filterMatchMode?: 'contains' | 'startsWith' | 'endsWith'; /** Default : false, if true it will get latest data when form opens for add/edit operation. */ alwaysGetLatestDataOnFormOpen?: boolean; /** Default : false, Make it true to handle huge amount of data. */ virtualScroll?: boolean; /** on value change of current dropdown | auto complete | multi select, it will change values of these dropdowns | auto completes | multi selects and reload them. */ reloadDropdownsOfPath?: string[]; /** API call will happen when these values of path are present in formData */ isDependentOnPath?: string[]; /** Displays a button next to the input field when enabled. */ dropdown?: boolean; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; /** Maximum number of character allows in the input field. */ maxLength?: number; addNewFormConfig?: IDBMasterConfig; apiCallOverrides?: IDBMasterAPICallOverrides; jsCode?: { /** * modifyDropdownRequest = It will run before hitting API call. So we can do whatever we want.
* onceDropdownDataLoaded = Execute code when dropdown data is loaded.
* * Available variables:
* reqBody: IQueryFormat | any. Useful to modify apiCallOverrides also,
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* dropdownData: any[] = Latest loaded dropdown data
* reloadDropdownsOfPath: string[] = Add path to this variable to reload its dropdown data.
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
*/ appendTo: EDBMasterAutoCompleteAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; /** * Multiselect control configuration. * Select multiple values from a list with chip-based display. * * **Data Sources:** * - **static_data**: Predefined options array * - **db_data**: Options from database collection/table * - **api_call**: Custom API endpoint for dynamic data * * **Key Features:** * - Multiple selection with checkboxes * - Selected items displayed as chips or comma-separated * - Searchable dropdown with filtering * - Select all / deselect all toggle * - Virtual scrolling for large datasets * - Selection limits and label overflow handling * - Option grouping support * * @example * // Basic multiselect with static data * multiselectSettings: { * dataSource: 'static_data', * staticData: skills, * optionLabel: 'name', * optionValue: 'id', * display: 'chip', * filter: true * } * * @example * // Database multiselect with selection limits * multiselectSettings: { * dataSource: 'db_data', * dbData: { collection: 'permissions' }, * optionLabel: 'name', * display: 'chip', * selectionLimit: 5, * maxSelectedLabels: 3, * selectedItemsLabel: '{0} permissions selected', * showToggleAll: true * } * * @example * // Grouped multiselect options * multiselectSettings: { * dataSource: 'static_data', * staticData: groupedCities, * optionLabel: 'name', * optionGroupLabel: 'state', * optionGroupChildren: 'cities', * display: 'chip' * } */ multiselectSettings?: { /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, placeholder?: string; showClear?: boolean; /** Minimum number of characters to initiate a search. */ minLength?: number; /** Delay between keystrokes to wait before sending a query. */ delay?: number; dataSource: 'static_data' | 'db_data' | 'api_call'; // custom_code = We can call any API in that. /** it will use used when dataSource is 'static_data'. */ staticData?: any[]; // { label: string; value: any; }[] works default. /** * it can pickup IDB values from schema also. */ dbData?: Partial & Pick>; /** Default : label */ optionLabel?: string; /** Default : value */ optionValue?: string; /** Whether to show the header. */ showHeader?: boolean; /** Enable filter */ filter?: boolean; /** one field or multiple comma separated fields are supported without any space in between. */ filterBy?: string; /** Default: 'contains', Defines how the items are filtered. */ filterMatchMode?: 'endsWith' | 'startsWith' | 'contains' | 'equals' | 'notEquals' | 'in' | 'lt' | 'lte' | 'gt' | 'gte'; /** Default : false, if true it will get latest data when form opens for add/edit operation. */ alwaysGetLatestDataOnFormOpen?: boolean; /** Default : false, Make it true to handle huge amount of data. */ virtualScroll?: boolean; /** Decides how many selected item labels to show at most. */ maxSelectedLabels?: number; /** Decides how many items can be selected at most. */ selectionLimit?: number; /** Ex: "{0} items selected", Label to display after exceeding max selected labels. defaults "ellipsis" keyword to indicate a text-overflow. */ selectedItemsLabel?: string; /** Whether to show the checkbox at header to toggle all items at once. */ showToggleAll?: boolean; /** Name of the disabled field of an option. */ optionDisabled?: string; /** Name of the label field of an option group. */ optionGroupLabel?: string; /** Name of the options field of an option group. */ optionGroupChildren?: string; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; /** * Defines how the selected items are displayed. * @group Props */ display: 'comma' | 'chip'; /** on value change of current dropdown | auto complete | multi select, it will change values of these dropdowns | auto completes | multi selects and reload them. */ reloadDropdownsOfPath?: string[]; /** API call will happen when these values of path are present in formData */ isDependentOnPath?: string[]; addNewFormConfig?: IDBMasterConfig; apiCallOverrides?: IDBMasterAPICallOverrides; jsCode?: { /** * modifyDropdownRequest = It will run before hitting API call. So we can do whatever we want.
* onceDropdownDataLoaded = Execute code when dropdown data is loaded.
* * Available variables:
* reqBody: IQueryFormat | any. Useful to modify apiCallOverrides also,
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* dropdownData: any[] = Latest loaded dropdown data
* reloadDropdownsOfPath: string[] = Add path to this variable to reload its dropdown data.
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
*/ appendTo: EDBMasterMultiSelectAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; /** * File upload control configuration. * Upload single or multiple files with validation and preview. * * **Key Features:** * - Single or multiple file selection * - File type restrictions (accept) * - File size validation * - File count limits * - Auto-upload or manual upload * - Upload/download/remove API integration * * **API Requirements:** * - Upload API should accept files in "files" form data field * - Upload API must return: `[{ originalName: string, uploadPath: string }]` * - Download API should return file content in base64 format * * @example * // Single image upload * fileUploadSettings: { * uploadApiUrl: '/api/upload', * downloadApiUrl: '/api/download', * removeApiUrl: '/api/remove', * accept: 'image/*', * maxFileSize: 5000000, // 5MB * multiple: false, * auto: true * } * * @example * // Multiple document upload with limit * fileUploadSettings: { * uploadApiUrl: '/api/upload-docs', * accept: '.pdf,.doc,.docx', * multiple: true, * fileLimit: 5, * maxFileSize: 10000000, // 10MB * auto: false * } */ fileUploadSettings?: { /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, /** Default : false, When enabled, upload begins automatically after selection is completed. */ auto?: boolean; /** * API URL which will be used to upload files.
* API should return array of objects in below format.
* You can have other properties and below properties are required. * [{ * originalName: string, * uploadPath: string, * }] * API Maker's custom API will accept files in "files" form data field. * * */ uploadApiUrl: string; /** API URL which returns content of file in base64 format. */ downloadApiUrl?: string; removeApiUrl?: string; /** Allow to select multiple files or not */ multiple?: boolean; /** ex : image/* */ accept?: string; /** Maximum file size allowed in bytes. Default : 10000000 (10MB) */ maxFileSize?: number; /** Maximum number of files that can be uploaded. */ fileLimit?: number; /** Internal use property to show/hide upload button on UI control. */ _showUploadButton?: boolean; /** internal use only */ _fileSelectEvent?: any; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; }; /** * Divider control configuration. * Visual separator for form sections with optional text label. * * **Key Features:** * - Horizontal or vertical orientation * - Text alignment options * - Custom styling support * - Content types (solid line, dashed, dotted) * * @example * // Simple horizontal divider * dividerSettings: { * layout: 'horizontal', * type: 'solid' * } * * @example * // Divider with centered label * dividerSettings: { * layout: 'horizontal', * align: 'center', * type: 'dashed' * } * // Use 'label' property in parent field to display text */ dividerSettings?: { /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, align?: 'center' | 'left' | 'right' | 'bottom' | 'top'; type?: 'dashed' | 'dotted' | 'solid'; dividerText?: string; }; /** * Accordion control configuration. * Collapsible panels for organizing form fields into sections. * * **Features:** * - Single or multiple panel expansion * - Default active panel selection * - Nested form fields within each panel * - Custom header styling and HTML * - Disabled panel support * - Event handlers for panel changes * * @example * // Single-panel accordion * accordionSettings: { * defaultIndex: 0, * multiple: false, * tabs: [ * { * header: 'Personal Information', * fields: [[nameField], [emailField]] * }, * { * header: 'Address Details', * fields: [[addressField], [cityField]] * } * ] * } * * @example * // Multiple panels with custom styling * accordionSettings: { * defaultIndex: 0, * multiple: true, * activeIndex: [0, 1], * tabs: [ * { * header: ' Profile', * headerCssClass: 'custom-header', * fields: [[profileFields]] * }, * { * header: 'Settings', * disabled: true, * fields: [[settingsFields]] * } * ] * } */ accordionSettings?: { /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, /** Index of the active tab or an array of indexes in multiple mode. */ activeIndex?: number | number[]; /** Default : 0, When form opens, this will be applied. */ defaultIndex?: number; multiple?: boolean; tabs: { /** Give style object in angular style. */ style?: any; /** Tab header, HTML is supported. */ header: string; /** Give style object in angular style. */ headerStyle?: any; /** You can provide single or multiple classes. */ headerCssClass?: string, disabled?: boolean | string; fields: IDBMasterConfigFormField[][]; }[]; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterAccordionAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; /** * Tab view control configuration. * Tabbed interface for organizing form fields into separate views. * * **Features:** * - Multiple tabs with individual form fields * - Default active tab selection * - Programmatic tab switching * - Custom tab header styling and HTML * - Disabled tab support * - Event handlers for tab changes * * @example * // Basic tab view * tabViewSettings: { * defaultIndex: 0, * tabs: [ * { * header: 'General', * fields: [[nameField], [descriptionField]] * }, * { * header: 'Advanced', * fields: [[advancedField1], [advancedField2]] * } * ] * } * * @example * // Tabs with icons and custom styling * tabViewSettings: { * activeIndex: 1, * tabs: [ * { * header: ' Home', * headerCssClass: 'tab-home', * fields: [[homeFields]] * }, * { * header: ' Settings', * disabled: false, * fields: [[settingsFields]] * }, * { * header: 'Admin', * disabled: true, * fields: [[adminFields]] * } * ] * } */ tabViewSettings?: { /** Index of the active tab to change selected tab programmatically. */ activeIndex?: number; /** Default : 0, When form opens, this will be applied. */ defaultIndex?: number; /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, tabs: { /** Give style object in angular style. */ style?: any; /** Tab header, HTML is supported. */ header: string; /** Give style object in angular style. */ headerStyle?: any; /** You can provide single or multiple classes. */ headerCssClass?: string, disabled?: boolean; fields: IDBMasterConfigFormField[][]; }[]; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterTabViewAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; /** * Button control configuration. * Clickable button with icon, loading state, and style variants. * * **Features:** * - Icon support with positioning (left/right/top/bottom) * - Loading state with custom icon * - Multiple severity levels (primary, success, info, warning, danger) * - Style variants (raised, rounded, text, outlined, link) * - Badge support * - Size options (small, large) * - Custom click handlers * * @example * // Basic button * buttonSettings: { * label: 'Submit', * severity: 'primary', * icon: 'pi pi-check' * } * * @example * // Loading button * buttonSettings: { * label: 'Processing...', * loading: true, * loadingIcon: 'pi pi-spin pi-spinner', * severity: 'info' * } * * @example * // Outlined button with badge * buttonSettings: { * label: 'Notifications', * icon: 'pi pi-bell', * outlined: true, * badge: '5', * badgeClass: 'p-badge-danger' * } * * @example * // Rounded icon button * buttonSettings: { * label: '', * icon: 'pi pi-plus', * rounded: true, * raised: true, * severity: 'success', * size: 'large' * } */ buttonSettings?: { /** button label text */ label: string; /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, /** Add a link style to the button. */ link?: boolean; /** ex: pi pi-check , give icon to button */ icon?: string, /** default : left */ iconPos?: 'left' | 'right' | 'top' | 'bottom'; /** Whether the button is in loading state. */ loading?: boolean; /** Icon to display in loading state. */ loadingIcon?: string; /** Defines the style of the button. */ severity?: uiMakerComponentSeverity | null | undefined; /** Add a shadow to indicate elevation. */ raised?: boolean; /** Add a circular border radius to the button. */ rounded?: boolean; /** Add a textual class to the button without a background initially. */ text?: boolean; /** Add a border class without a background initially. */ outlined?: boolean; /** Value of the badge. */ badge?: string; /** Style class of the badge. */ badgeClass?: string; /** Style class of the badge. */ size?: 'small' | 'large' | undefined | null; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterButtonAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; /** * Image display control configuration. * Display images with preview functionality and custom styling. * * **Features:** * - Image source URL * - Width/height control * - Preview mode with fullscreen * - Alt text for accessibility * - Custom parent container styling * - Tooltip support * * @example * // Basic image display * imageSettings: { * src: '/assets/logo.png', * width: '200px', * height: 'auto', * alt: 'Company Logo' * } * * @example * // Image with preview * imageSettings: { * src: '/uploads/product-thumb.jpg', * previewImageSrc: '/uploads/product-full.jpg', * preview: true, * width: '100px', * height: '100px' * } * * @example * // Dynamic image from form data * imageSettings: { * src: '', // Set via jsCode * preview: true, * jsCode: [{ * appendTo: 'onInit', * code: ($scope) => { * $scope.column.imageSettings.src = $scope.formData.profilePicture; * } * }] * } */ imageSettings?: { src: string; /** Give style object in angular style. */ style?: any; /** custom CSS class to assign to control */ cssClass?: string, /** it will be assigned to span which is parent of image tag. */ imageParentSpanClass?: string, /** Attribute of the image element. */ width?: string; /** Attribute of the image element. */ height?: string; /** Attribute of the preview image element. */ alt?: string; /** Controls the preview functionality. */ preview?: boolean; /** The source path for the preview image. */ previewImageSrc?: string; /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterImageAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; /** * Custom HTML control configuration. * Render arbitrary HTML content within the form. * * **Use Cases:** * - Custom formatted text and headings * - Embedded widgets or iframes * - Custom styling and layout elements * - Rich content display * * ⚠️ **Security Warning:** * HTML content is sanitized to prevent XSS attacks. * Some tags and attributes may be stripped. * * @example * // Custom heading * customHTMLSettings: { * htmlCode: '

Section Title

' * } * * @example * // Information box * customHTMLSettings: { * htmlCode: ` * * Note: Please review all fields carefully. * * ` * } */ customHTMLSettings?: { htmlCode: string; }; /** * Knob control configuration. * Circular dial input for numeric value selection. * * **Features:** * - Min/max range * - Step increment * - Custom colors (value, range, text) * - Adjustable size and stroke width * - Value template formatting * - Rotary interaction * * @example * // Volume knob (0-100) * knobSettings: { * min: 0, * max: 100, * step: 1, * size: 150, * valueColor: '#3b82f6', * valueTemplate: '{value}%' * } * * @example * // Temperature knob with custom colors * knobSettings: { * min: -10, * max: 50, * step: 0.5, * valueColor: '#ef4444', * rangeColor: '#d1d5db', * textColor: '#1f2937', * valueTemplate: '{value}°C', * size: 200, * strokeWidth: 12 * } */ knobSettings?: { /** Advisory information to display in a tooltip on hover. */ tooltip?: string; /** Type of CSS position. */ tooltipPosition?: 'left' | 'top' | 'bottom' | 'right'; tooltipStyleClass?: string; /** Style class of the component. */ cssClass?: string | undefined; /** Inline style of the component. */ style?: any; /** Background of the value. */ valueColor?: string | undefined; /** Background color of the range. */ rangeColor?: string; /** Color of the value text. */ textColor?: string; /** Template string of the value. */ valueTemplate?: string; /** Size of the component in pixels. */ size?: number; /** Step factor to increment/decrement the value. */ step?: number; /** Minimum boundary value. */ min?: number; /** Maximum boundary value. */ max?: number; /** Width of the knob stroke. */ strokeWidth?: number; jsCode?: { /** * Available variables:
* formData: any = Entire form object
* column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data.
* allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data
* globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.
* utils: any = Common utility functions for user to use.
* queryParams: any = Query params received from URL.
* config: IDBMasterConfigFormField
* event: any
*/ appendTo: EDBMasterKnobAppendTo, /** * // dropdownData is available to use. * * // Return promise for long awaiting tasks. * new Promise(async (resolve, reject) => { * await new Promise(r => setTimeout(r, 3000)); * dropdownData[0].name = 'Sample data'; * resolve(); * }); * * // Directly modify data of grid * dropdownData[0].name = 'Sample data'; * * // Return function * (function setData() { dropdownData[0].name = 'Sample data'; } ); * */ code: string | (($scope: IDBMasterUIPageUtilsScope) => any), }[], }; } /** * Validation rules for schema properties and form fields. * Define constraints that values must satisfy before being saved. */ export interface IPropertyValidation { /** * Field is mandatory and must have a non-null, non-empty value. * Applicable to all data types. */ required?: boolean; /** * Minimum allowed value. * Applicable to: number, date */ min?: number; /** * Maximum allowed value. * Applicable to: number, date */ max?: number; /** * Minimum string/array length. * Applicable to: string, array */ minLength?: number; /** * Maximum string/array length. * Applicable to: string, array */ maxLength?: number; /** * Value must be unique across all records. * @deprecated API Maker maintains uniqueness internally. * Avoid using on tables with frequent updates due to performance impact. */ unique?: boolean; /** * Validate email address format. * Applicable to: string */ email?: boolean; /** * Custom validation function. * Return true if valid, false or error message if invalid. */ validatorFun?: Function; /** * Enumeration constraint - value must be from this array. * @example ['active', 'inactive', 'pending'] */ enum?: any[]; } export enum EDBMasterTextAreaAppendTo { visible = 'visible', disabled = 'disabled', ngModelChange = 'ngModelChange', focus = 'focus', blur = 'blur', keyUp = 'keyUp', keyDown = 'keyDown', } export enum EDBMasterEditorAppendTo { visible = 'visible', disabled = 'disabled', ngModelChange = 'ngModelChange', onTextChange = 'onTextChange', onInit = 'onInit', onSelectionChange = 'onSelectionChange', focus = 'focus', blur = 'blur', keyUp = 'keyUp', keyDown = 'keyDown', } export enum EDBMasterCheckboxAppendTo { visible = 'visible', disabled = 'disabled', ngModelChange = 'ngModelChange', } export enum EDBMasterRatingAppendTo { visible = 'visible', disabled = 'disabled', ngModelChange = 'ngModelChange', onRate = 'onRate', onCancel = 'onCancel', onFocus = 'onFocus', onBlur = 'onBlur', } export enum EDBMasterKnobAppendTo { visible = 'visible', disabled = 'disabled', ngModelChange = 'ngModelChange', } export enum EDBMasterRadioAppendTo { visible = 'visible', disabled = 'disabled', ngModelChange = 'ngModelChange', } export enum EDBMasterColorPickerAppendTo { visible = 'visible', disabled = 'disabled', ngModelChange = 'ngModelChange', } export enum EDBMasterDatePickerAppendTo { visible = 'visible', disabled = 'disabled', ngModelChange = 'ngModelChange', focus = 'focus', blur = 'blur', } export enum EDBMasterFileUploadAppendTo { visible = 'visible', disabled = 'disabled', } export enum EDBMasterGridAppendTo { visible = 'visible', disabled = 'disabled', } /** * Custom code execution hooks for DB Master lifecycle events. * Defines when and where custom JavaScript code runs in the application. */ export enum EDBMasterConfigAppendTo { /** * Execute once on initial page load. * Appends script to page