Pull, run, predict.
From docker pull to your first prediction in five minutes: an HTTP API on port 9005 and SQL over the Postgres wire protocol on port 5432.
docker pull ghcr.io/aitohq/aito
export AITO_KEY=$(openssl rand -hex 24) # read-write key: keep it
export AITO_READ_KEY=$(openssl rand -hex 24) # read-only key
docker run -d --name aito \
-p 127.0.0.1:9005:9005 \
-p 127.0.0.1:5432:5432 \
-v aito-state:/io/state \
-e READ_WRITE_APIKEY="$AITO_KEY" \
-e APIKEY="${AITO_READ_KEY:?set AITO_READ_KEY first}" \
ghcr.io/aitohq/aito
The 127.0.0.1: prefix keeps both ports on your own machine. Docker publishes ports past host firewalls such as ufw, so leave it in unless you mean to expose the instance.
The container does not run unauthenticated. It uses two API keys: the read-write key (READ_WRITE_APIKEY) can insert and change data, and the read-only key (APIKEY) can query but not INSERT, UPDATE, DELETE or DROP. Give the read-only one to anything that only reads.
Pin your own keys, as above, for anything you intend to keep running, and hold them wherever you already hold secrets. Pinned keys are used as-is, and nothing is generated.
If you start without keys, the container generates a pair on first boot and stores it on the aito-state volume, where later restarts reuse it. The startup log shows the pair on first boot only, so read it from the volume:
# while the container exists
docker exec aito cat /io/state/.aito-api-keys
# even if the container is gone: the volume is enough
docker run --rm -v aito-state:/io/state alpine cat /io/state/.aito-api-keys
For a throwaway local container you can opt out entirely with -e AITO_DISABLE_AUTH=true. Do not do that on a published port.
Upgrading from 1.0.1? Two things change:
x-api-key header, and SQL connections use the key as the password. Pin your own keys, or read the generated pair from the volume as above.AITO_LICENSE_KEY works. In 1.0.1, setting it left the container running with nothing listening. If you tried a licence key against 1.0.1 and it looked broken, it was.Three requests: create a table, insert rows, ask for a prediction. The example predicts how an incoming purchase invoice should be coded, the same pattern behind predictive accounting and ERP automation.
1. Create the table. One collection of purchase invoices. The description column is analyzed as text, so Aito can learn from individual words:
curl -X PUT http://127.0.0.1:9005/api/v2/schema/invoices \
-H "x-api-key: ${AITO_KEY:?set AITO_KEY first}" \
-H 'content-type: application/json' \
-d '{
"type": "collection",
"columns": {
"vendor": { "type": "String" },
"description": { "type": "Text", "analyzer": "english" },
"amount": { "type": "Decimal" },
"gl_account": { "type": "String" },
"approver": { "type": "String" }
}
}'
2. Insert rows. Twelve invoices that have already been coded, the kind of history every accounting system has:
curl -X POST http://127.0.0.1:9005/api/v2/data/invoices/batch \
-H "x-api-key: ${AITO_KEY:?set AITO_KEY first}" \
-H 'content-type: application/json' \
-d '[
{ "vendor": "AWS", "description": "Monthly cloud hosting and compute", "amount": 2340.50, "gl_account": "6510 Cloud services", "approver": "sara" },
{ "vendor": "Google Cloud", "description": "Cloud compute and BigQuery usage", "amount": 1480.00, "gl_account": "6510 Cloud services", "approver": "sara" },
{ "vendor": "Slack", "description": "Team messaging subscription, 25 seats", "amount": 218.75, "gl_account": "6520 Software subscriptions", "approver": "sara" },
{ "vendor": "Atlassian", "description": "Jira and Confluence annual subscription", "amount": 1890.00, "gl_account": "6520 Software subscriptions", "approver": "sara" },
{ "vendor": "Finnair", "description": "Flights Helsinki to Berlin, customer visit", "amount": 420.30, "gl_account": "6310 Travel", "approver": "tom" },
{ "vendor": "Scandic Hotels", "description": "Hotel two nights, conference trip", "amount": 358.00, "gl_account": "6310 Travel", "approver": "tom" },
{ "vendor": "Lyreco", "description": "Office paper, pens and notebooks", "amount": 86.40, "gl_account": "6110 Office supplies", "approver": "tom" },
{ "vendor": "IKEA", "description": "Office chairs and desk lamps", "amount": 540.00, "gl_account": "6110 Office supplies", "approver": "tom" },
{ "vendor": "Google Ads", "description": "Search advertising campaign April", "amount": 1500.00, "gl_account": "6610 Marketing", "approver": "lisa" },
{ "vendor": "LinkedIn", "description": "Sponsored content campaign, lead gen", "amount": 980.00, "gl_account": "6610 Marketing", "approver": "lisa" },
{ "vendor": "Wolt", "description": "Team lunch, quarterly planning day", "amount": 156.80, "gl_account": "6210 Staff catering", "approver": "tom" },
{ "vendor": "Kespro", "description": "Office coffee, milk and snacks", "amount": 92.30, "gl_account": "6210 Staff catering", "approver": "tom" }
]'
3. Ask for a prediction. A new invoice arrives from Datadog, a vendor that appears nowhere in the data:
curl -X POST http://127.0.0.1:9005/api/v2/_predict \
-H "x-api-key: ${AITO_KEY:?set AITO_KEY first}" \
-H 'content-type: application/json' \
-d '{
"from": "invoices",
"where": { "vendor": "Datadog", "description": "Monitoring subscription for production servers" },
"predict": "gl_account",
"select": ["$value", "$p", "$why"],
"limit": 3
}'
{
"offset": 0,
"total": 6,
"hits": [
{ "$p": 0.281, "$value": "6520 Software subscriptions", "$why": { … } },
{ "$p": 0.144, "$value": "6110 Office supplies", "$why": { … } },
{ "$p": 0.144, "$value": "6210 Staff catering", "$why": { … } }
]
}
Aito has never seen Datadog, but the word "subscription" in the description carries the prediction toward the software account, and $why shows which words and values moved it. Change "predict": "gl_account" to "predict": "approver" and the same rows suggest who should approve it.
There is no training step: every prediction is computed live from the rows you just inserted, and the next query already includes any rows you add.
Port 5432 speaks the Postgres wire protocol, so psql, a BI tool or any Postgres driver connects directly. The password is the API key:
PGPASSWORD="${AITO_KEY:?set AITO_KEY first}" psql -h 127.0.0.1 -p 5432 -U aito -d aito
SELECT value, p FROM predict('invoices', 'gl_account',
where => 'vendor = ''Datadog'' AND description = ''Monitoring subscription for production servers''');
If a local Postgres already holds port 5432, docker run fails to publish it. Map Aito's SQL port to another host port instead, for example -p 127.0.0.1:15432:5432, and connect with psql -h 127.0.0.1 -p 15432.
The examples use 127.0.0.1 rather than localhost: the ports are published on IPv4, and some systems (macOS among them) resolve localhost to IPv6 first.
See the SQL guide for predict(), recommend() and relate() inside SELECT.
To start over, remove the container and the volume (docker rm -f aito && docker volume rm aito-state). Removing only the container keeps both your data and your keys.
| Query | What it does |
|---|---|
_predict | Predict a field's value from the other fields, with a calibrated probability |
_recommend | Rank the values of a field toward a goal |
_relate | Find the conditions that go with an outcome, ranked by lift |
_match | Match a record to the rows of a linked table |
_search | Full-text and structured search |
_query | Plain filtered reads, with links followed by name |
_evaluate | Measure prediction accuracy on your own held-out rows |
v2 quickstart → · API reference →
The free-tier image holds up to 10,000 rows per table and 50,000 rows total. Beyond that, inserts return HTTP 429. Reads keep working at any size — the cap only stops new data.
That covers most evaluation, CI, and prototyping work. For production deployments — anything that serves your customers or participates in a revenue workflow — you'll want a Production License.
If your Aito instance is part of any of the following, it's production:
Not production: local dev, CI, side projects, personal use, academic research, evaluation up to 90 days. All free.
Email sales@aito.ai with a one-line description of your use case. You'll get a license key back within a working day — no qualifying call, no SDR follow-up. Founder-led, by design.
Once you have a Production License key, set it as an environment variable and the row caps lift:
docker run -d --name aito \
-p 127.0.0.1:9005:9005 \
-p 127.0.0.1:5432:5432 \
-v aito-state:/io/state \
-e READ_WRITE_APIKEY="${AITO_KEY:?set AITO_KEY first}" \
-e APIKEY="${AITO_READ_KEY:?set AITO_READ_KEY first}" \
-e AITO_LICENSE_KEY=ak_live_... \
ghcr.io/aitohq/aito:latest
The image validates the licence key against console.aito.ai on startup and caches the response, encrypted, on the /io/state volume, so the container keeps running for up to 7 days without internet if you need offline operation. If validation cannot complete, the server starts in free mode and says so in its log rather than refusing to start.
| Variable | Default | Meaning |
|---|---|---|
READ_WRITE_APIKEY | generated | Full-access key; set it to pin your own |
APIKEY | generated | Read-only key; set it to pin your own |
AITO_DISABLE_AUTH | false | true disables all authentication. Local use only |
AITO_LICENSE_KEY | unset | Production licence key |
AITO_LICENSE_TIMEOUT | 5 | Seconds per licence validation request |
AITO_LICENSE_STARTUP_TIMEOUT | 60 | Seconds to wait for validation at startup before starting in free mode |
AITO_LICENSE_CACHE_MAX_AGE_SECONDS | 604800 (7 days) | How long a cached validation is honoured offline |
JVM_XMX / JVM_XMS | 2g / 512m | JVM heap |
PORT / PGWIRE_PORT | 9005 / 5432 | HTTP and Postgres wire ports |
STATE_PATH | /io/state | Where the database, keys and licence cache persist |
Every Aito instance phones home on startup with:
We do not collect: row contents, query text, schemas, table data. The telemetry is how we tell free-tier evaluation traffic from production traffic — and it's how sales knows to reach out about a license. Full details in our privacy policy under "Free-tier Docker image telemetry".
Disabling telemetry is a breach of the license terms §4(b).
Prefer a managed instance? console.aito.ai runs Aito for you with the same query interface, plus backups and the Aito UI on top. The Sandbox tier is free; Dev and Prod tiers are paid. The cloud service is governed by separate Terms of Service.
The image is also published to AWS ECR Public for users in AWS-native environments:
docker pull public.ecr.aws/aitoai/aito
Identical image, identical contents.
Questions: support@aito.ai · GitHub · License terms
Episto Oy
Putouskuja 6 a 2
01600 Vantaa
Finland
VAT ID FI34337429