Getting Started
This guide is also rendered (with the rest of the docs) at iondrive.dev/docs/getting-started.
Ion Drive is the self-hostable, MCP-native backend an AI agent stands up in
minutes — with domain blocks you own as editable code. This guide goes from
an empty machine to an agent creating and querying real data, in about five
minutes. You own the project it creates: a thin server.ts plus a /blocks
directory, with the platform arriving as npm dependencies (see
Framework mode).
Prerequisites
Section titled “Prerequisites”- Node.js 22+
- Docker (for PostgreSQL)
1. Scaffold and run
Section titled “1. Scaffold and run”npx @ion-drive/cli init my-appcd my-app
docker compose up -d # PostgreSQLnpm installnpm run dev # tsx watch server.ts- API:
http://localhost:3000 - Admin console:
http://localhost:3000/admin
Open the admin console and create your account — the first user to sign up
becomes the admin. (.env was generated with fresh secrets and
ION_REQUIRE_AUTH=true, so the API is authenticated from the first request.
Production hardening knobs are documented in .env.example.)
Ion Drive will refuse to boot in production (
NODE_ENV=production) with authentication disabled, unless you setION_ALLOW_OPEN=trueto acknowledge a deliberately open deployment. KeepION_REQUIRE_AUTH=truefor anything reachable by others.
Deploying somewhere public? Don’t race to be the first signup — provision the admin account from the environment instead:
ION_ADMIN_EMAIL=you@example.comION_ADMIN_PASSWORD=a-strong-password # or ION_ADMIN_PASSWORD_FILE=/run/secrets/pwOn a fresh database the server creates that admin at boot (before it starts
listening, so there is no exposure window) and public signup starts
locked — no second trip into your host’s env settings afterwards. Once any
user exists the variables are ignored with an info log, so they are safe to
leave set. Set ION_DISABLE_SIGNUP=false explicitly if you really want
public signup to stay open alongside a bootstrapped admin. See
Authentication for
details.
Contributor path: working on Ion Drive itself? Clone
jaredgrabill/ion-driveandpnpm dev— see CONTRIBUTING.
2. Connect your agent
Section titled “2. Connect your agent”In the admin console go to API Keys and create a key with a role
assigned (e.g. admin) — a key with no role has no permissions. Then add the
MCP server to your agent:
# Claude Code, for example:claude mcp add --transport http ion-drive http://localhost:3000/api/v1/mcp \ --header "X-API-Key: iond_…"Any MCP-compatible client works the same way — it’s a Streamable HTTP server at
/api/v1/mcp (see MCP server). The scaffolded project also
contains an AGENTS.md, so coding agents working in the repo already know the
endpoints, the query language, and the schema-change etiquette.
3. Let the agent build
Section titled “3. Let the agent build”Ask your agent something like:
Create a
contactsobject with a required full name, an email, and a status enum. Add a few sample records, then show me everyone whose status isn’tarchived.
The agent has MCP tools for all of it — create_object, create_record,
query_data, modify_field (preview-first), relationship management, and the
tools of any installed block. The moment an object is created it is live on
every surface, no restart:
- REST:
GET/POST/PATCH/DELETE /api/v1/data/contacts - GraphQL:
contacts,create_contacts, … at/api/v1/graphql - MCP: introspection + CRUD tools at
/api/v1/mcp - OpenAPI: reflected in
/api/v1/openapi.json
Open Objects → contacts in the admin console and the records are there, in an editable grid.
4. Bootstrap with building blocks
Section titled “4. Bootstrap with building blocks”Instead of defining every object by hand, install a ready-made building block — a bundle of objects, relationships, seed data, tasks, roles, and (optionally) vendored logic:
npx ion-drive list # the registry catalog (crm, invoicing, …)npx ion-drive add crm # schema-only: objects + APIs light up immediatelynpx ion-drive add invoicing # vendored logic: its code lands in blocks/invoicing/Blocks with logic are your code — edit blocks/invoicing/stripe.ts and the
dev server hot-reloads. Their actions are live at
POST /api/v1/blocks/invoicing/actions/create_payment_link, in the OpenAPI
spec, and as MCP tools. See Building Blocks and
Actions & hooks. To author and ship your own block, see
Publishing a block.
5. The same thing, by hand
Section titled “5. The same thing, by hand”Everything the agent does is a plain HTTP API you can drive yourself. Requests
are authenticated with the same header (X-API-Key: iond_…):
# Create an objectcurl -X POST http://localhost:3000/api/v1/schema/objects \ -H 'content-type: application/json' -H 'x-api-key: iond_…' \ -d '{ "name": "contacts", "displayName": "Contacts", "fields": [ { "name": "full_name", "displayName": "Full Name", "columnType": "text", "isRequired": true }, { "name": "email", "displayName": "Email", "columnType": "email" }, { "name": "status", "displayName": "Status", "columnType": "enum" } ] }'
# Insert a recordcurl -X POST http://localhost:3000/api/v1/data/contacts \ -H 'content-type: application/json' -H 'x-api-key: iond_…' \ -d '{ "full_name": "Ada Lovelace", "email": "ada@example.com", "status": "active" }'
# Query: full-text search plus per-property operators, sorting, paginationcurl -H 'x-api-key: iond_…' \ "http://localhost:3000/api/v1/data/contacts?search=example&sort=-created_at&page=1"curl -H 'x-api-key: iond_…' \ "http://localhost:3000/api/v1/data/contacts?status[neq]=archived&created_at[gt]=2020-10-10"See the Querying guide for the full operator list.
Guest (anonymous) users
Section titled “Guest (anonymous) users”Building a play-first / try-first app? Set ION_ANONYMOUS_AUTH=true and
visitors can start with zero sign-up friction — POST /api/auth/sign-in/anonymous (SDK: ion.auth.signInAnonymously()) mints a
real, RBAC-governed guest user that can later upgrade to an email account and
keep its data. See Authentication & guest users.
Calling the API from scripts
Section titled “Calling the API from scripts”For anything scripted (seed scripts, CI, cron), use an API key — send it as
X-API-Key: iond_… like the examples above. API keys skip cookies and CSRF
entirely; that is the recommended path. Mint one in the admin console under
API Keys (assign a role — a key with no role has no permissions).
The cookie-session auth endpoints (/api/auth/*) are different: they are
protected by Better Auth’s CSRF check, which requires an Origin header that
matches the server’s base URL (ION_PUBLIC_URL, or http://localhost:3000 by
default) or another trusted origin. Browsers send it automatically; a script or
curl must add it explicitly or the request fails with
403 {"code":"MISSING_OR_NULL_ORIGIN"}:
# Sign in from a script: the Origin header satisfies the CSRF check.curl -c cookies.txt -X POST http://localhost:3000/api/auth/sign-in/email \ -H 'content-type: application/json' \ -H 'Origin: http://localhost:3000' \ -d '{ "email": "admin@example.com", "password": "…" }'That one sign-in is all a script ever needs the Origin header for — including minting the first API key without touching the admin console. With the session cookie, list roles and create a role-bound key (the plaintext key is returned exactly once):
curl -b cookies.txt http://localhost:3000/api/v1/roles # find the admin role idcurl -b cookies.txt -X POST http://localhost:3000/api/v1/api-keys \ -H 'content-type: application/json' \ -d '{ "name": "scripts", "roleId": "<role-id>" }'From then on, use the key and forget cookies.
6. Talk to it from code
Section titled “6. Talk to it from code”Install the zero-dependency client SDK and use the fluent query builder:
npm install @ion-drive/clientimport { IonDriveClient } from '@ion-drive/client';
const ion = new IonDriveClient({ baseUrl: 'http://localhost:3000', apiKey: 'iond_…',});
// Awaiting the fluent chain executes it (Supabase-style):const { data, pagination } = await ion.from('contacts') .select('id, full_name, email') .search('example') .neq('status', 'archived') .gt('created_at', '2020-10-10') .order('created_at', { ascending: false }) .range(0, 24);
console.log(data, pagination.totalCount);