Self-Hosting
Run Reliant Locally
Reliant has no embedded/library mode — it is always a client talking to an HTTP server. Running it "locally, off the network" means hosting the api/ service yourself and pointing the SDK's baseUrl at it. Nothing leaves your machine.
How local mode works
The SDK (reliant-js) only speaks HTTP. The whole engine — schema validation (AJV), intelligent retry, provider fallback, Guard, Contracts, Agents — lives in the api/Fastify service. To run without the hosted API you start that service on localhost and set baseUrl accordingly.
Set DATA_BACKEND=postgres and the api runs entirely against one local PostgreSQL database — no Supabase project, no SUPABASE_* variables. Every feature route runs through Prisma. This is the on-prem / air-gapped path.
Scope: only the api/ service runs fully local on Postgres. The web/ dashboard still expects Supabase for auth and a few server routes — if you only need the API and SDK, you can ignore web/.
Prerequisites
| Requirement | | | Notes |
|---|
| Node.js | ≥ 22 | required | The api targets Node 22. |
| PostgreSQL | 14+ | required | Local Docker container or a native install. One database is enough. |
| LLM provider key | string | required | An Anthropic / OpenAI / Gemini / Groq / Mistral key. There is no env-var fallback — it is read from the database (step 5). |
1
Configure api/.env
Copy api/.env.example to api/.env and set the values below. With DATA_BACKEND=postgres no Supabase variables are needed —lib/supabase.ts is null-safe and the server boots without them.
DATABASE_URL="postgresql://user:pass@localhost:5432/reliant?schema=public"
DATA_BACKEND=postgres
# 32+ characters each
PROVIDER_KEY_ENCRYPTION_SECRET="a-string-of-32-plus-characters-xxxxxxxx"
INTERNAL_API_SECRET="another-32-plus-character-string-xxxxxxx"
PORT=3100
HOST=127.0.0.1
NODE_ENV=development
2
Install, migrate, run
npm run dev does not run migrations (only npm start does), so apply them once by hand first.
cd api
npm install
npx prisma migrate deploy # applies all migrations, incl. unify_data_layer
npm run dev # tsx watch — serves on http://localhost:3100
curl http://localhost:3100/health
# { "status": "ok", "version": "1.0.1", ... }
3
Create the account row
Pick a fixed UUID for your local user. An accounts row is required —provider_keys.user_id has a foreign key to it, and the plan is read from here.
INSERT INTO accounts (id, plan, created_at, updated_at)
VALUES ('11111111-1111-1111-1111-111111111111', 'pro', now(), now());
4
Create a project and API key
Use the internal route — it stores the key hashed and returns the raw key once. Pass the same UUID asuser_id so /execute can resolve the project owner.
curl -X POST http://localhost:3100/projects \
-H "Content-Type: application/json" \
-H "x-internal-secret: <INTERNAL_API_SECRET from .env>" \
-d '{"name":"Local","user_id":"11111111-1111-1111-1111-111111111111"}'
{
"id": "clxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "Local",
"api_key": "rel_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"message": "Store your API key securely — it will not be shown again."
}
Do not use npm run db:seed for this. The seed script stores the API key in plain text, but auth looks it up by SHA-256 hash, so a seeded project will not authenticate. Always mint keys through POST /projects.
5
Add a provider key
/execute reads the LLM key from the provider_keystable (there is no env-var fallback). For local use you may store it in plain text —decrypt() returns the value unchanged when it is not iniv:tag:ciphertext form. To store it encrypted, runencrypt() from api/src/lib/crypto.ts with the samePROVIDER_KEY_ENCRYPTION_SECRET.
INSERT INTO provider_keys (id, user_id, provider, encrypted_key, is_active, created_at, updated_at)
VALUES (
gen_random_uuid(),
'11111111-1111-1111-1111-111111111111',
'anthropic',
'sk-ant-your-real-key',
true, now(), now()
);
6
Create a schema and execute
From here everything is the normal API, just against localhost. Authenticate feature routes with the X-Reliant-Key header.
# schema
curl -X POST http://localhost:3100/schemas \
-H "X-Reliant-Key: rel_..." -H "Content-Type: application/json" \
-d '{
"name": "Contact",
"slug": "contact",
"definition": {
"type": "object",
"required": ["name", "email"],
"properties": {
"name": { "type": "string" },
"email": { "type": "string", "format": "email" }
},
"additionalProperties": false
}
}'
# execute
curl -X POST http://localhost:3100/execute \
-H "X-Reliant-Key: rel_..." -H "Content-Type: application/json" \
-d '{
"prompt": "John Smith, john@acme.com",
"schema_id": "<id from previous call>",
"provider": "anthropic",
"model": "claude-sonnet-4-20250514",
"user_id": "11111111-1111-1111-1111-111111111111"
}'
7
Point the SDK at your local server
The only change from the hosted setup is baseUrl. Without it the SDK defaults to https://reliant.api.br.
import { Reliant } from 'reliant-js'
const reliant = new Reliant({
apiKey: 'rel_...',
userId: '11111111-1111-1111-1111-111111111111',
baseUrl: 'http://localhost:3100', // <- local, off the network
})
const result = await reliant.execute({
prompt: 'John Smith, john@acme.com',
schemaId: '<id>',
provider: 'anthropic',
model: 'claude-sonnet-4-20250514',
})
Environment variables
| Variable | | | Description |
|---|
| DATABASE_URL | string | required | PostgreSQL connection string used by Prisma. |
| DATA_BACKEND | enum | optional | supabase (default, cloud) or postgres (fully local). Set to postgres for self-hosting. |
| PROVIDER_KEY_ENCRYPTION_SECRET | string | required | 32+ chars. Used to encrypt/decrypt stored provider keys. |
| INTERNAL_API_SECRET | string | required | 32+ chars. Guards POST /projects and other internal routes. |
| PORT | number | optional | Default 3100. |
| HOST | string | optional | Default 0.0.0.0. Use 127.0.0.1 to bind loopback only. |
| NODE_ENV | string | optional | development returns full error messages; production hides them. |
| RATE_LIMIT_MAX | number | optional | Requests per window per key/IP. Default 100. |
| RATE_LIMIT_WINDOW_MS | number | optional | Rate-limit window in ms. Default 60000. |
| SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY | string | not used | Ignored when DATA_BACKEND=postgres. |
Notes
Usage limits. If the Plan table is empty the monthly cap falls back to 1000 executions — enough for local development. Populating Plan enforces the real per-plan limits (free is 7/month).
Agents. The Agents feature works identically in local mode — same server, same reliant.agents / reliant.agentRun() calls against http://localhost:3100. Its migrations are included in prisma migrate deploy.
Encryption. crypto.ts currently derives its key by truncating/padding the secret. It is fine for local development but should be hardened before any production or enterprise deployment.