Environments & Authentication (v2, Beta)

An environment (env) is a named, copy-on-write view into your database. Every database starts with env.master; you branch new envs from it, write to them independently, and atomically promote them back โ€” all without copying data. Branching a 50 GB database happens in milliseconds and uses essentially no extra disk; only the changes you make in the new env take space.

Branches, tags & the ref graph

Environments, releases, and mounts are three roles of one underlying mechanism โ€” a ref: a name that points at a committed database head. Heads are content-addressed and immutable; a ref is just a movable (or frozen) label over one. That single idea is why branching is instant and free:

  • Nothing is copied when you branch โ€” the new ref points at the same head, and only the changes you write afterward take disk. This is copy-on-write layering over an immutable head.
  • The three roles differ only in how the label behaves:
RoleRef kindBehaves likeGuide
Environmentmutable brancha git branch โ€” advances as you write, promotablethis page
Releaseimmutable taga git tag โ€” frozen at publish, never movesReleases
Mountread-through layeranother database's release, layered in read-onlyMountable Repositories

GET /api/v2/_refs lists every ref (branches and releases together) so a console can render the whole version graph in one call. The rest of this page is the branch role; Releases covers the tag role and the branch-vs-tag comparison in depth.

Moving data between environments. Promoting swaps a whole branch. To move specific tables instead โ€” a partial migration from a sandbox, or restoring one table from a release โ€” copy across refs with { "from": "env.sandbox:orders", "copy": "orders", "replace": true } on /api/v2/data/_modify. See Copying across refs for the full grammar.

Authentication โ€” one key, path-selected envs

Where keys come from: v2 instances and their database-level keys are provisioned on request โ€” email support@aito.ai. You then manage envs within that instance using the one key, as below.

Two facts prevent almost every auth problem on v2:

1. There is no env-scoped API key. Keys are database-level: a read key reads any env in the database, a read-write key writes any env. The same key that reads or writes master reads or writes every env. If you need hard isolation between tenants, use separate databases (/db/{db}/โ€ฆ), not separate envs.

2. An env is selected purely by URL path โ€” never by a dot suffix, a request-body field, a query parameter, or a header:

โœ…  /db/{db}/env/{name}/api/v2/_query      (multi-tenant server)
โœ…  /env/{name}/api/v2/_query              (single-db server)
โŒ  /db/{db}/env.{name}/api/v2/_query      (dot โ€” not an env selector)
โŒ  body: { "env": "{name}", ... }         (ignored)

So a 401/403 that reads fine on master is almost always an addressing bug (a dot instead of /env/, or a gateway rewriting the path), and a 401 "not authorized to perform this operation" on a write is almost always a read-only key on a write endpoint. Confirm the env with GET /db/{db}/api/v2/_envs, then use the slash form above. (More in Common Errors.)

When to use envs

  • Throwaway sandboxes โ€” branch master, run integration tests, drop the env; each run gets a clean slate without re-uploading.
  • Per-user demo copies โ€” every trial user gets an isolated env branched from a shared template; they mutate freely, the template stays clean.
  • Side-prepared releases โ€” build the next dataset on a staging env, warm it, then atomically swap into master with one promote. No downtime.
  • Multiple scales for comparison โ€” branch scale-1k / scale-10k / scale-100k after each ingest stage; all stay queryable for benchmarking.

URL pattern

Every endpoint accepts an env scope via the URL prefix:

URLResolves to
/api/v2/...env.master (default)
/env/{name}/api/v2/...the env named {name}
/db/{db}/api/v2/...master in that database
/db/{db}/env/{name}/api/v2/...named env in that database

Switching env is purely a URL change โ€” point AITO_URL at โ€ฆ/env/sandbox/api/v2 and every client script targets sandbox instead of master. Schema, data, queries, and admin all scope identically.

Destructive operations & data-loss safety

The same two facts that make envs convenient โ€” the default URL resolves to master, and one key writes every env โ€” make one mistake dangerous: if a destructive call drops or mistypes its /env/{name}/ segment, it silently targets master (production) and succeeds with 200 OK. There is no error to catch โ€” the write goes to the wrong place, not nowhere. This is the most likely way to lose data on v2.

The destructive endpoints to guard:

EndpointEffect
DELETE /api/v2/schema/{table}Drops the table and its data. Cannot be undone.
POST /api/v2/data/_deleteDeletes the rows matching where.
POST /api/v2/data/_modifydelete / update verbs mutate existing rows.

What is already guarded (so you can rely on it): _delete refuses an empty or missing where with a 400 โ€” {"where":{}} would otherwise match every row, so an accidental full-table wipe is rejected, not executed. To clear a whole table you must delete it explicitly via DELETE /api/v2/schema/{table}. (Missing where or from is likewise a 400, never a no-op that deletes everything.)

Practices that prevent the path-segment footgun:

  • Pin and print the full URL โ€” including the /env/{name}/ segment โ€” before any destructive call, and confirm the env exists with GET /db/{db}/api/v2/_envs. A destructive script should log the exact AITO_URL it is about to hit.
  • Rehearse on a branched env first. Branching is instant and copies no data (Branches), so run a risky migration or bulk delete on a throwaway branch of master, verify the result, then either re-run it against master or promote the branch into master (Promote). A branch/tag taken right before the change is your restore point.
  • Use a read-only key for query-only clients, so a bug in a reporting or analytics job can never issue a write or delete at all.
  • Use separate databases (/db/{db}/โ€ฆ) for hard isolation โ€” a key crosses every env in its database, but not across databases.

Managing envs

The _envs admin endpoints (shared across v1 and v2 โ€” the env mechanism is one mechanism) list, branch, promote, and delete:

OperationCallKey
ListGET /api/v2/_envsread
BranchPOST /api/v2/_envs { "name": "v2", "basedOn": "env.master" }read-write
Promote into masterPOST /api/v2/_envs/{name}/promoteread-write
DeleteDELETE /api/v2/_envs/{name}read-write

Promote semantics

promote is an atomic swap, not a move:

  • After promote, env.master and the source env reference the same state; queries against master see the promoted content.
  • The source env is not deleted โ€” you can still query it under /env/{name}/โ€ฆ.
  • Later writes to the source do not propagate to master; they diverge. Promote again to pick them up.
  • Clean up with DELETE /api/v2/_envs/{name} after a successful promote.

This makes "warm and commit" safe: prepare the next state on the side, verify it with full queries, then promote when ready.

Naming rules

  • Allowed: A-Z, a-z, 0-9, ., _, -; must start with a letter or digit.
  • Max length 128. Names starting with _ are reserved.
  • env.master can't appear in /env/{name}/โ€ฆ URLs โ€” master is the unscoped path.

A worked example

The public sandbox is built exactly this way: branch a v2 env from master, declare the collections, batch-load. The aito-demo upload-data.js script is the complete recipe:

AITO_URL=https://your-instance.aito.ai/db/your-db \
AITO_API_KEY=<read-write-key> \
node upload-data.js

Related: Quickstart ยท CollectionDb ยท Common Errors ยท Sandbox