What is Forgeyn Base
Forgeyn Base is an open backend platform. You create a project, and the platform provisions a dedicated, isolated Postgres database (on Neon), a storage layer, and API credentials for it. You talk to it with the JavaScript SDK, plain REST, the CLI — or let AI agents use it through the MCP server.
There are two doors into the platform. The management API is for you, the developer: create projects, run SQL, upload files, wire webhooks. The BaaS gateway is for your software: your website or app gets scoped, parameterized access to its own database — with end-user accounts living inside it.
| Plane | Who uses it | Credentials | Base URL |
|---|---|---|---|
| Management API | Your servers, scripts, CLI | X-Api-Key: fbk_live_… | https://forgebase-api-z6xv.onrender.com |
| BaaS gateway | Your website / app / agents | app id + pk_ (or sk_) | https://pupagsqvhhshecxmbgay.supabase.co/functions/v1/gateway |
NEXT_PUBLIC_API_URL the console calls). A custom API domain — e.g. CNAME api.db.forgeyn.com.ng to the API service — becomes the printed URL the moment the env var is updated.Quickstart
From zero to a working database-backed website in about five minutes.
- 1
Create a free account
Sign up in the console. No credit card.
- 2
Create a project
Pick a name and a region — a private Postgres database is provisioned for you on Neon.
- 3
Create an API key
Open your project → API keys → Create key. Copy the
fbk_live_…value — it authenticates the management API via theX-Api-Keyheader. - 4
Create a table
Use the console SQL runner, or run it from anywhere:
SQL create table todos ( id uuid primary key default gen_random_uuid(), user_id uuid, title text not null, done boolean not null default false, created_at timestamptz not null default now() );
- 5
Make your first request
Shell curl -X POST https://forgebase-api-z6xv.onrender.com/api/projects/<project-id>/sql \ -H "X-Api-Key: fbk_live_…" \ -H "Content-Type: application/json" \ -d '{"query": "select count(*) from todos"}'
The two planes
The management API and the BaaS gateway are deliberately separate. Secrets that can spend or destroy live only on the management side; the gateway hands out scoped, publishable credentials that are safe to embed in browsers.
| Management API | BaaS gateway | |
|---|---|---|
| Raw SQL | Yes — you are the admin | Never. Structured JSON actions only |
| Auth model | Account API key (fbk_live_…) | App id + publishable pk_ / server sk_ + optional end-user token |
| Scoping | Full access to your projects | Rows on user_id tables are forced to the signed-in user |
| Ideal caller | Your backend, CI, the CLI | Your website, mobile app, AI agents |
JavaScript SDK
forgeyn-sdk is the official client. It has zero dependencies and runs in browsers, Node, Bun, Deno and edge runtimes.
npm install forgeyn-sdk
import { createClient } from "forgeyn-sdk";
const fb = createClient({
url: "https://pupagsqvhhshecxmbgay.supabase.co/functions/v1/gateway",
appId: "app_xxxx", // console → project → API keys
clientKey: "pk_…", // publishable — safe in the browser
});
// The end-user session token is held in memory by default. To survive page
// reloads, hand the SDK a persistence hook backed by YOUR backend (an
// httpOnly cookie) — never stash raw tokens in localStorage.// end-user auth — accounts live in YOUR project's own database
const { user, token } = await fb.auth.signUp("[email protected]", "password123");
// parameterized database access — no SQL, no injection
const row = await fb.db.insert("todos", { title: "Ship the docs", done: false });
const { rows } = await fb.db.select("todos", { orderBy: "-created_at", limit: 20 });
await fb.db.update("todos", { where: { id: row.id }, values: { done: true } });
await fb.db.remove("todos", { where: { id: row.id } });
const open = await fb.db.count("todos", { where: { done: false } });On tables that have a user_id column, every write is stamped with the signed-in user and every read is filtered to them — the SDK cannot opt out of this, even if it tries to send a different user_id.
import { createAdminClient } from "forgeyn-sdk";
// SERVER-SIDE ONLY — bypasses per-user row scoping
const admin = createAdminClient({
url: "https://pupagsqvhhshecxmbgay.supabase.co/functions/v1/gateway",
appId: "app_xxxx",
adminKey: "sk_…",
});sk_…) bypasses per-user row scoping. Ship pk_ to clients; keep sk_ on your server only.Data plane REST
Prefer raw HTTP? The gateway speaks the same JSON the SDK sends. Every request carries your app id and the publishable client key.
curl -X POST "https://pupagsqvhhshecxmbgay.supabase.co/functions/v1/gateway/data" \
-H "x-app-id: app_xxxx" \
-H "x-client-key: pk_…" \
-H "Content-Type: application/json" \
-d '{
"action": "select",
"table": "todos",
"columns": ["id", "title", "done"],
"where": { "done": false },
"orderBy": [{ "column": "created_at", "direction": "desc" }],
"limit": 20
}'Filters accept an equality shorthand or an operator object:
// equality shorthand
{ "where": { "done": false } }
// operator object
{ "where": { "title": { "ilike": "%ship%" } } }
{ "where": { "created_at": { "gte": "2026-01-01" } } }
{ "where": { "status": { "in": ["new", "open"] } } }| Operator | Meaning |
|---|---|
eq | equal to |
ne | not equal to |
gt / gte | greater than / greater or equal |
lt / lte | less than / less or equal |
like | case-sensitive pattern match (% wildcards) |
ilike | case-insensitive pattern match |
in | matches any value in a list |
Responses return { rows, rowCount } for reads and writes, or { count } for counts. Supported actions: select, insert, update, delete, count.
End-user auth
Your end users are first-class: accounts are created inside your project's own Postgres (baas_users), sessions are tokens, and every scoped query is forced to the token's identity at the execution layer — not in client code.
curl -X POST "https://pupagsqvhhshecxmbgay.supabase.co/functions/v1/gateway/auth/signup" \
-H "x-app-id: app_xxxx" -H "x-client-key: pk_…" \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "password123"}'
# → { "user": { "uid": "<uuid>", "email": "[email protected]" }, "token": "…" }
# subsequent scoped calls:
curl -X POST "https://pupagsqvhhshecxmbgay.supabase.co/functions/v1/gateway/data" \
-H "x-app-id: app_xxxx" -H "x-client-key: pk_…" \
-H "Authorization: Bearer <user-token>" \
-H "Content-Type: application/json" \
-d '{"action": "select", "table": "todos"}'| Endpoint | Purpose |
|---|---|
POST /auth/signup | Create an account (password ≥ 8 chars) → user + token |
POST /auth/signin | Exchange credentials for a session token |
GET /auth/me | Fetch the signed-in user (requires Bearer token) |
GET /health | Gateway + backend probe |
user_id column: inserts are stamped with the caller's uid, updates and deletes only touch rows the caller owns, and user_id can never be reassigned. The sk_ admin key is the only bypass — for trusted server jobs.Storage & image transforms
Buckets hold files on S3-compatible storage. Uploads stream through antivirus scanning, and any stored image can be resized and re-encoded on the fly — no batch jobs, no CDN plugin.
# 1. create the bucket (once per bucket)
curl -X POST https://forgebase-api-z6xv.onrender.com/api/buckets \
-H "X-Api-Key: fbk_live_…" \
-H "Content-Type: application/json" \
-d '{"name": "media"}'
# 2. upload (multipart, AV-scanned)
curl -X POST https://forgebase-api-z6xv.onrender.com/api/files/upload \
-H "X-Api-Key: fbk_live_…" \
-F "bucket=media" -F "[email protected]"
# → { "file": { "id": "<uuid>", "bucket": "media",
# "scanStatus": "clean", "isImage": true } }# serve a 400×300 cover-cropped WebP, transformed on the fly curl "https://forgebase-api-z6xv.onrender.com/api/files/<file-id>/raw?w=400&h=300&fit=cover&fmt=webp" \ -o hero_400.webp
| Param | Detail |
|---|---|
w / h | Target width / height in px (1–4096) |
fit | cover (default), contain or inside |
fmt | webp (default), avif, jpeg or png |
q | Encoder quality 1–100 (default 80) |
Also available: GET /api/files/:id/download for Content-Disposition downloads, POST /api/files/zip to stream a ZIP of many files, and POST /api/files/presign for short-lived direct upload URLs.
Webhooks
Subscribe to platform events and receive deliveries signed with an HMAC-SHA256 signature so your server can trust them.
curl -X POST https://forgebase-api-z6xv.onrender.com/api/webhooks \
-H "X-Api-Key: fbk_live_…" \
-H "Content-Type: application/json" \
-d '{"url": "https://yourapp.com/hooks/forgeyn", "events": ["file.uploaded", "file.deleted"]}'The signing secret is shown exactly once at creation. Verify deliveries by hashing the raw body:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyForgeynSignature(rawBody: string, header: string, secret: string) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
return (
header.length === expected.length &&
timingSafeEqual(Buffer.from(header), Buffer.from(expected))
);
}Use POST /api/webhooks/:id/test to fire a signed ping event at your endpoint and inspect the delivery.
Management API
Base URL https://forgebase-api-z6xv.onrender.com. Authenticate every call with your key in the X-Api-Key header. Keys are scoped — grant the minimum:
| Scope | Allows |
|---|---|
projects:read | List and inspect projects, tables, rows, activity and usage |
projects:write | Create, rename and delete projects |
sql:run | Run SQL and insert / update / delete rows through the management API |
files:read | List, download and transform stored files |
files:write | Upload and delete files, create presigned URLs |
buckets:read | List buckets and read the storage config |
buckets:write | Create and delete buckets |
keys:write | Mint and revoke gateway app credentials (the CLI / MCP path) |
webhooks:read | List webhooks and inspect deliveries |
webhooks:write | Create, test and delete webhooks |
Full endpoint map:
| Method | Path | Description |
|---|---|---|
| POST | /api/orgs | Create an organization |
| GET | /api/orgs | List your organizations |
| GET | /api/orgs/:slug/projects | List an org's projects |
| POST | /api/projects | Provision a new Postgres project |
| GET | /api/projects | List your projects |
| GET | /api/projects/:id | Project detail |
| PATCH | /api/projects/:id | Rename a project |
| DELETE | /api/projects/:id | Delete a project (async teardown) |
| GET | /api/projects/:id/connection | Direct Postgres connection string |
| POST | /api/projects/:id/sql | Run a SQL statement |
| GET | /api/projects/:id/tables | List tables and columns |
| GET | /api/projects/:id/tables/:table/rows | Paginate rows of a table |
| POST | /api/projects/:id/tables/:table/rows | Insert a row |
| PATCH | /api/projects/:id/tables/:table/rows | Update matching rows |
| DELETE | /api/projects/:id/tables/:table/rows | Delete matching rows |
| GET | /api/projects/:id/activity | Project audit trail |
| GET | /api/projects/:id/usage | DB size, storage, key counts |
| GET | /api/projects/:id/keys | List gateway apps (dual-key credentials) |
| POST | /api/projects/:id/keys | Create an app → app id + pk_ + sk_ |
| DELETE | /api/projects/:id/keys/:appId | Revoke a gateway app |
| POST | /api/buckets | Create a storage bucket |
| GET | /api/buckets | List buckets |
| DELETE | /api/buckets/:name | Delete an empty bucket |
| POST | /api/files/upload | Upload a file (multipart, AV-scanned) |
| GET | /api/files | List files in a bucket |
| GET | /api/files/:id/raw | Stream raw bytes or an on-the-fly transform |
| GET | /api/files/:id/download | Download with Content-Disposition |
| POST | /api/files/zip | Stream a ZIP of many files |
| POST | /api/files/presign | Short-lived direct upload URL |
| DELETE | /api/files | Delete files by id |
| POST | /api/webhooks | Register a signed webhook |
| GET | /api/webhooks | List webhooks |
| POST | /api/webhooks/:id/test | Send a signed test ping |
| DELETE | /api/webhooks/:id | Delete a webhook |
| GET | /api/audit | Account-wide audit log |
| GET | /api/meta/regions | Provisionable regions |
| GET | /api/health | Service health |
Errors use one envelope everywhere: { error: { code, message, details } }.
CLI
The CLI is the fastest way to drive the management API from a terminal or CI.
npm install -g forgeyn-cli forgeyn login # email + password, or: forgeyn login --api-key fbk_live_… forgeyn projects create my-app --region aws-eu-central-1 forgeyn db sql my-app "create table notes (id serial primary key, body text)" forgeyn apps create my-app web # → app_xxxx + pk_… + sk_…
| Command | What it does |
|---|---|
forgeyn login | Authenticate with email + password, --api-key <fbk_live_…> or --token <session token> |
forgeyn whoami | Show the signed-in account and stored API URL |
forgeyn projects create <name> | Provision a new Postgres project |
forgeyn projects list | List your projects |
forgeyn projects connection <project> | Print the direct Postgres URI |
forgeyn db tables <project> | List tables and columns |
forgeyn db rows <project> <table> | Paginate rows |
forgeyn db sql <project> <query> | Run raw SQL from the terminal |
forgeyn apps create <project> <name> | Mint dual-key gateway credentials |
forgeyn keys create <name> | Create an account API key (fbk_live_…) |
forgeyn orgs list | List organizations |
forgeyn regions | Show provisionable regions |
forgeyn mcp | Print the config snippet for the MCP server |
MCP for AI agents
forgeyn-mcp is a Model Context Protocol server that lets AI agents (Claude, Cursor, Zed, …) operate your Forgeyn Base account directly — authenticated with a scoped access token, Supabase-style. Email and password are never exposed to the agent.
1. Create a token: forgeyn keys create agent -s projects:read -s projects:write -s sql:run -s keys:write. 2. Connect — pick whichever fits your agent:
URL: https://forgebase-api-z6xv.onrender.com/mcp
Header: Authorization: Bearer fbk_live_…
# It is plain JSON-RPC over HTTP — from any agent:
curl -X POST https://forgebase-api-z6xv.onrender.com/mcp \
-H "Authorization: Bearer $FORGEYN_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'{
"mcpServers": {
"forgeyn": {
"command": "npx",
"args": ["-y", "forgeyn-mcp"],
"env": {
"FORGEYN_ACCESS_TOKEN": "fbk_live_…",
"FORGEYN_API_URL": "https://forgebase-api-z6xv.onrender.com"
}
}
}
}| Tool | Purpose |
|---|---|
forgeyn_whoami | Current account + auth mode |
forgeyn_list_projects | List projects |
forgeyn_create_project | Provision a project |
forgeyn_delete_project | Delete a project (confirm first) |
forgeyn_get_connection_string | Direct Postgres URI for a project |
forgeyn_run_sql | Execute SQL against a project |
forgeyn_list_tables | Tables and columns |
forgeyn_list_rows | Read rows from a table |
forgeyn_insert_row | Insert a row |
forgeyn_list_orgs | List organizations |
forgeyn_create_org | Create an organization |
forgeyn_delete_org | Delete an organization (confirm first) |
forgeyn_list_apps | Gateway apps for a project |
forgeyn_create_app | Mint dual-key gateway credentials |
forgeyn_revoke_app | Revoke a gateway app (confirm first) |
forgeyn_list_api_keys | Account API keys |
Add --read-only to expose only safe tools, and note the destructive tools (delete_project, revoke_app) should always be user-confirmed.
Errors
Every failure — both planes — returns the same envelope with a stable, machine-readable code:
{
"error": {
"code": "invalid_client_key",
"message": "Invalid BaaS Client Credentials: expired token",
"details": null
}
}| Code | Meaning |
|---|---|
validation_error | A request failed validation — e.g. Postgres rejected a SQL statement, or a body field was malformed |
unauthorized | No valid credential — send X-Api-Key, a Bearer session token, or sign in |
forbidden | Authenticated, but not allowed — e.g. a key is missing a required scope, or reassigning user_id |
not_found | The resource does not exist (or belongs to another account) |
quota_exceeded | A platform quota was hit (projects, storage, keys, webhooks) — HTTP 402 |
missing_app_id | x-app-id header is required (data plane) |
invalid_client_key | The pk_ client key is missing, tampered or expired (data plane) |
app_key_mismatch | The key's signed app id does not match x-app-id — cross-tenant guard (data plane) |
forbidden_origin | Data-plane calls must come through the Forgeyn Base gateway (direct backend calls are refused) |
auth_required | An end-user Authorization token is required for scoped access (data plane) |
invalid_action | Unknown action — use select, insert, update, delete or count (data plane) |
invalid_filter | A where operator or value is malformed (data plane) |
invalid_query | Missing values, empty where, or a bad column (data plane) |
unknown_table | The table does not exist in this project's database (data plane) |
reserved_table | Internal tables (baas_users) cannot be touched from the data plane |
invalid_identifier | Table or column names must be plain identifiers (data plane) |
query_failed | The data-plane query could not be executed — inspect message details |
Quotas & limits
Current managed defaults — generous for production apps, enforced server-side per account:
| What | Limit |
|---|---|
| Projects per account | 3 (private Postgres each) |
| Buckets per account | 5 |
| File storage | 512 MB total, 200 MB max per file |
| API keys | 10 |
| Webhooks | 5 |
| API rate limit | 300 requests / minute / identity |
Rate limiting is per identity (API key or gateway app), not per IP, and returns 429 with the standard error envelope. Need more? The limits are configuration — talk to us about the Scale plan.
Scale: Stripe-class platforms
Why the data model fits: payment platforms live on ACID transactions, unique constraints for idempotency keys, double-entry balance tables and append-only ledgers — exactly what Postgres enforces natively. And because every Forgeyn Base project gets a dedicated Postgres (no shared schema, no noisy neighbors), a fast-growing product scales its database without touching anyone else's.
| Dimension | What you have today | What Stripe-class scale adds |
|---|---|---|
| Storage | Every project is a dedicated Neon Postgres that grows independently; file storage is quota-capped per account. | Neon scales databases to terabytes — raise the platform quotas and storage grows with the workload, not with a shared disk. |
| Transaction throughput | One hardened Fastify API fronts all tenants; per-tenant pools cap at 5 connections per instance. | Run more stateless API replicas behind the gateway and add Neon's pooled connection endpoint — the same horizontal path Stripe-style workloads take. |
| Latency & regions | Single-region deployment; regions are selectable at project creation. | Add Neon read replicas near users and API replicas per region; the data plane is already region-aware per project. |
| Data integrity | Full Postgres ACID: transactions, constraints, unique indexes, JSONB, parameterized writes with forced row scoping. | Nothing to change — ledgers, idempotency keys and double-entry balances are exactly what Postgres constraints are for. |
| Security & compliance | TLS in transit, AES-256-GCM encrypted tenant credentials, HMAC-signed webhooks, audit logs, per-identity rate limits. | A payments platform must earn its own PCI DSS certification — the platform gives you the primitives, the certification is application-level. |
Bottom line: the same Postgres primitives Stripe runs on — full ACID, constraints, JSONB, parameterized access through one hardened API — are what Forgeyn Base hands you on day one. Today's managed quotas are tuned for indie-to-SaaS scale; the architecture (isolated Postgres per project behind a stateless API) is deliberately the pattern you'd keep as you grow.