# alia.help — agent onboarding guide

You are (probably) an AI agent. This guide gets you from zero to running
read-only SQL against your human's data and publishing live charts they can
share. Most of it you can do yourself; your human is needed for the API
token, and possibly for database-side changes (firewall, TLS, roles — see
the checklist in section 3).

## 1. Get an API token (needs your human)

Ask your human to:

1. Sign in at https://alia.help/users/log-in (email magic link — creating an account
   and signing in are the same flow).
2. Create an API token at https://alia.help/tokens. Tokens look like `alh_...` and
   are shown once.

If your MCP client supports OAuth 2.1 instead, skip the token: add the
server and complete the browser consent flow when prompted (we support
dynamic client registration at `POST https://alia.help/oauth/register`).

### No browser? Use the device flow

Fully headless agents can onboard via the OAuth device flow (RFC 8628) —
the human approves from any browser, on any device:

```sh
# 1. Register a client once (device flow never uses the redirect;
#    a loopback placeholder satisfies registration).
curl -s -X POST https://alia.help/oauth/register -H 'content-type: application/json' \
  -d '{"client_name":"My Agent","redirect_uris":["http://127.0.0.1:1/unused"]}'
# → {"client_id":"cid_...", ...}

# 2. Start a device authorization.
curl -s -X POST https://alia.help/oauth/device_authorization -d 'client_id=cid_...'
# → {"device_code":"alhd_...","user_code":"XXXX-XXXX",
#    "verification_uri_complete":"https://alia.help/activate?code=XXXX-XXXX",
#    "expires_in":900,"interval":5}

# 3. Tell your human: "Open <verification_uri_complete> and approve,
#    or enter code XXXX-XXXX at https://alia.help/activate."

# 4. Poll every `interval` seconds until approved (15 min limit).
curl -s -X POST https://alia.help/oauth/token \
  -d 'grant_type=urn:ietf:params:oauth:grant-type:device_code' \
  -d 'device_code=alhd_...' -d 'client_id=cid_...'
# → {"error":"authorization_pending"} while waiting (back off on
#   "slow_down"), then {"access_token":"alh_...", ...} once approved.
```

The resulting `alh_` token is a normal API token — use it as the bearer
token below; the human can revoke it at https://alia.help/tokens.

## 2. Register the MCP server

Streamable HTTP endpoint: `POST https://api.alia.help/mcp` with header
`Authorization: Bearer alh_...`.

Claude Code:

```sh
claude mcp add --transport http aliahlp https://api.alia.help/mcp \
  --header "Authorization: Bearer alh_YOUR_TOKEN"
```

After adding the server, restart your MCP client (e.g. start a new Claude
Code session) — most clients load the tool list only at startup. If the
aliahlp tools don't appear, a restart is almost always the fix.

## 3. Give the platform data

Two independent paths — pick whichever fits.

### Option A: connect a live Postgres

Before calling `create_connection`, walk this checklist — every item is a
real onboarding failure we have seen:

1. **Public reachability.** The database must accept TCP connections from
   the public internet. Our stable egress IP is `5.78.219.67` — allowlist it in the database firewall. Managed Postgres (Supabase, Neon, RDS
   with a public endpoint) usually is reachable; Postgres inside Docker on
   a VPS usually is NOT until you publish the port — see "Common setups"
   below.
2. **TLS.** The default is `ssl_mode: "require"` (encrypted, certificate
   chain not verified — a self-signed certificate works). Managed providers
   support TLS, but a stock Postgres Docker image ships with `ssl = off`
   and will refuse the handshake. Either enable TLS on the server (see
   "Common setups") or pass `ssl_mode: "disable"` — only over a network
   path you trust.
3. **A read-only role.** Strongly prefer a dedicated role with SELECT only:

   ```sql
   CREATE ROLE aliahlp_ro LOGIN PASSWORD '<generate a strong password>';
   GRANT CONNECT ON DATABASE yourdb TO aliahlp_ro;
   GRANT USAGE ON SCHEMA public TO aliahlp_ro;
   GRANT SELECT ON ALL TABLES IN SCHEMA public TO aliahlp_ro;
   ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO aliahlp_ro;
   ```

4. **Row-level security policies.** If tables have RLS enabled, a
   brand-new role sees ZERO rows from them — silently, with no error —
   until a policy grants it rows. If the role should read everything, run
   per RLS table:

   ```sql
   CREATE POLICY aliahlp_read ON <schema>.<table> FOR SELECT TO aliahlp_ro USING (true);
   ```

   `create_connection` and `get_schema` warn about affected tables.

If anything misbehaves after connecting — errors, or queries returning 0
rows you did not expect — call the `diagnose_connection` tool: it checks
reachability, TLS, credentials, read-only status, table access and RLS in
one shot and tells you the fix.

### Option B: CSV upload

Call `import_csv` (inline content up to ~2MB), or POST bigger files to
`https://api.alia.help/api/tables/<table_name>` with the same bearer header
(`curl --data-binary @file.csv`). The data becomes a queryable table in a
hosted workspace.

### Common setups

**Postgres in Docker (or a Kamal accessory) on a VPS** — stock image,
`ssl = off`, port unpublished:

```sh
# 1. Publish the port (docker run: -p 5432:5432 / compose: ports: ["5432:5432"]
#    / Kamal accessory: port: 5432), then restrict it to our egress IP:
ufw allow from 5.78.219.67 to any port 5432 proto tcp

# 2. Enable TLS — a self-signed cert is enough for ssl_mode "require":
docker exec <container> bash -c 'cd /var/lib/postgresql/data &&
  openssl req -new -x509 -days 3650 -nodes -subj "/CN=postgres" \
    -out server.crt -keyout server.key &&
  chmod 600 server.key && chown postgres:postgres server.crt server.key'
docker exec -u postgres <container> psql -c "ALTER SYSTEM SET ssl = on"
docker restart <container>

# 3. Create the read-only role from checklist item 3, then call
#    create_connection with the VPS IP. (Skipping step 2 works with
#    ssl_mode: "disable", but then the firewall rule is your only
#    protection.)
```

## 4. Explore, query, chart

Suggested first-run sequence:

1. `list_connections` — everything else takes a `connection_id` from here.
2. `get_schema` — tables, columns, types, row estimates.
3. `get_catalog` — AI-generated plain-language description of the schema
   (appears a few minutes after a connection is created; check back if not
   ready). `suggest_insights` proposes questions worth charting.
4. `run_query` — one read-only SQL statement per call. 15s statement
   timeout, row and ~2MB result caps. Aggregate in SQL; don't pull raw
   tables. If a query returns 0 rows you did not expect, call
   `diagnose_connection`.
5. `create_chart` — SQL + a Vega-Lite spec (omit the `data` property; rows
   are injected server-side). Returns a live hosted URL `/c/<slug>`.
   Charts re-run their SQL on a `refresh_seconds` interval, so published
   charts stay live. A spec-level `title` is suppressed at render time
   (the chart's `title` field is shown instead), and a query returning
   exactly one row with one numeric column renders as a big-number KPI
   stat tile — don't build text-mark specs for single values.
6. `create_dashboard` / `add_chart_to_dashboard` / `add_text_to_dashboard`
   — compose charts and markdown narrative into a report at `/d/<slug>`.

## Rules and limits

- All SQL is read-only, single-statement, 15s timeout, capped result size.
  Writes/DDL are rejected at multiple layers — don't try.
- Chart/dashboard URLs are unguessable slugs. `visibility: "public"` (the
  default) means anyone with the link can view; `"private"` (owner only)
  requires the Pro plan.
- Your token is scoped to its owner's account. Keep it out of logs, repos,
  and shell history where possible.

## Policies

Privacy policy: https://alia.help/privacy · Terms: https://alia.help/terms · Security:
https://alia.help/security · Sub-processors: https://alia.help/sub-processors. Only schema
metadata (not table rows) is sent to our AI sub-processor; customer SQL is
strictly read-only. All four pages return raw markdown to agents.
