# Backend branching Source: https://docs.insforge.dev/agent-native/branching Spin up an isolated copy of your project to test schema and config changes A branch is a child project with its own Postgres, auth config, storage, edge functions, email templates, realtime channels, and schedules. Merge back when ready or reset and retry. Available on InsForge OSS 2.1.0+. ## Concepts Each branch runs on its own EC2 instance, restored from the parent at create time. `merge` runs a three-way diff against the parent's create-time state. The branch shares the parent's `JWT_SECRET` but gets its own `API_KEY`. Compute services and frontend deployments do not branch. ## Usage Create a branch with `full` (schema + data) or `schema-only` (faster, empty user tables). ```bash theme={null} npx @insforge/cli branch create feat-billing --mode full npx @insforge/cli branch list ``` Preview the merge SQL before applying. ```bash theme={null} npx @insforge/cli branch merge feat-billing --dry-run --save-sql ./preview.sql npx @insforge/cli branch merge feat-billing ``` Roll back to the create-time snapshot, or delete the branch. ```bash theme={null} npx @insforge/cli branch reset feat-billing npx @insforge/cli branch delete feat-billing ``` ## Specific usage cases Use a branch for risky schema migrations, RLS rewrites, OAuth provider swaps, and edge function refactors. Skip it for trivial changes and data backfills (user-data rows are not auto-merged). Merges block on conflicts. Resolve on the branch or reset and retry. Quotas: 3 parent projects per org, 2 active branches per parent, no nesting. A successful merge does not auto-delete the branch. ## More resources * [Database migrations](/core-concepts/database/migrations) for forward-only SQL files. * [Database overview](/core-concepts/database/overview) for what runs under each branch. * [CLI reference](https://github.com/InsForge/InsForge) for the full `branch` flag set. # CLI harness Source: https://docs.insforge.dev/agent-native/cli-harness The InsForge CLI is the agent's hands: one terminal interface for schema, config, deploys, and diagnostics. The `@insforge/cli` is the interface a coding agent uses to operate your backend. Where a human reaches for the dashboard, the agent reaches for the terminal: it runs a command, reads the output, and decides what to do next. Every command speaks `--json`, so the agent works from structured data instead of scraping a screen. Run the CLI with `npx @insforge/cli`. Do not install it globally, so the agent always uses the version pinned to the project. ## Why a CLI for agents A dashboard is built for a human pointer; a CLI is built for anything that can write text. Pass `--json` to any command and the agent gets a structured result it can parse; pass `--yes` and it runs without stopping for a confirmation prompt. Schema, auth config, storage, functions, deploys, branches, and diagnostics are all subcommands of the same tool, so there is one surface to learn instead of a dashboard to navigate. It runs in any terminal, any editor, or CI, with no integration to set up. And after a change, the agent can run [`npx @insforge/cli diagnose`](/agent-native/diagnostics) and read back exactly what broke. ## Command surface | Area | Commands | | -------------- | -------------------------------------------------------------------------- | | Auth & context | `login`, `logout`, `whoami`, `current`, `list` | | Project | `create`, `link` | | Schema | `db migrations new`, `db migrations up`, `db migrations list` | | Config as code | `config plan`, `config apply`, `config export` | | Branching | `branch create`, `branch merge`, `branch reset`, `branch delete` | | Build | `functions`, `storage`, `deployments`, `secrets`, `schedules`, `ai` | | Diagnose | `diagnose`, `diagnose advisor`, `diagnose db`, `diagnose logs`, `metadata` | Run `npx @insforge/cli help ` for the flags on any of these. ## A typical agent run ```bash theme={null} # connect npx @insforge/cli login npx @insforge/cli link # read current state npx @insforge/cli --json metadata # change schema, safely npx @insforge/cli db migrations new add-orders-table npx @insforge/cli db migrations up --all # check the result npx @insforge/cli diagnose --json ``` ## Let the agent interpret the output Diagnostics ships an AI flag so the agent can hand its own backend data to a model and get back an explanation: ```bash theme={null} npx @insforge/cli diagnose --ai "why are auth requests failing after the last migration?" ``` This pairs the raw signals (advisor findings, DB health, error logs) with a plain-language read, which is what turns "here is a stack trace" into "here is the fix." See [Diagnostics & advisor](/agent-native/diagnostics). ## Next steps * Put auth, SMTP, storage, retention, and deployment settings in version control with [config as code](/agent-native/config-as-code). * Test risky changes on a [backend branch](/agent-native/branching) before touching production. # Config as code Source: https://docs.insforge.dev/agent-native/config-as-code Manage InsForge auth, SMTP, storage, retention, and deployment settings declaratively from a single insforge.toml using the CLI's plan, apply, and export workflow. ## Overview `insforge.toml` is a declarative, version-controlled snapshot of a subset of your project's config: auth policy, allowed redirect URLs, password rules, SMTP, storage upload size, realtime and schedule retention, and the deployment subdomain. The CLI provides three commands that work against this file: * **`config plan`**: diff `insforge.toml` against the linked project. Shows what would change. * **`config apply`**: push the diff to the live project. Per-change capability gating, env-resolved secrets, dry-run mode. * **`config export`**: pull current project state and write a fresh `insforge.toml`. Useful for bootstrapping from an existing project. You keep **one** `insforge.toml` in your repo. To apply it to a different environment (staging, prod, a teammate's local backend, or a self-hosted instance), point the CLI at a different project (re-link or use `--project-id`). Secrets are read from environment variables via `env(...)` references, so the file itself stays free of credentials and can be committed safely. This works the same way on InsForge Cloud projects and on self-hosted OSS deployments. All examples use `npx @insforge/cli`. Do not install the CLI globally. ## Command summary | Command | Purpose | | ----------------------------- | -------------------------------------------------------------------- | | `config plan` | Show diff between `insforge.toml` and live project state | | `config apply` | Apply `insforge.toml` to the live project | | `config apply --dry-run` | Print the plan without applying | | `config apply --auto-approve` | Skip the interactive confirmation prompt (required in `--json` mode) | | `config export` | Pull live config and write `insforge.toml` | | `config export --force` | Overwrite an existing `insforge.toml` without confirmation | `config plan` and `config apply` read `insforge.toml`; pass `--file ` if it lives somewhere other than `./insforge.toml`. `config export` writes the file; pass `--out ` to write it to a custom location. ## Recommended workflow If you already configured the project through the dashboard, export it once to get a working file: ```bash theme={null} npx @insforge/cli config export ``` This writes `insforge.toml` reflecting the current backend state. Check `insforge.toml` into version control. Secrets are referenced via `env(...)`, so the file is safe to commit. Change the TOML, then preview the diff before applying: ```bash theme={null} npx @insforge/cli config plan ``` ```bash theme={null} npx @insforge/cli config apply ``` Review the rendered plan and confirm. In CI, pass `--auto-approve` and `--json`. To push the same config to staging or a self-hosted backend, point the CLI at the other project and re-run apply: ```bash theme={null} npx @insforge/cli --project-id config apply ``` Secrets that differ between environments (SMTP password, etc.) are resolved per-environment from the local shell, so the TOML doesn't need to change. ## What `insforge.toml` covers The file mirrors a curated subset of project config, the parts that are useful to manage declaratively. Anything not in this list still lives on the dashboard and the API. | Section | Keys | | ----------------- | ----------------------------------------------------------------------------------------------------------------------- | | `[auth]` | `allowed_redirect_urls`, `require_email_verification`, `verify_email_method`, `reset_password_method`, `disable_signup` | | `[auth.password]` | `min_length`, `require_number`, `require_lowercase`, `require_uppercase`, `require_special_char` | | `[auth.smtp]` | `enabled`, `host`, `port`, `username`, `password`, `sender_email`, `sender_name`, `min_interval_seconds` | | `[storage]` | `max_file_size_mb` | | `[realtime]` | `retention_days` | | `[schedules]` | `retention_days` | | `[deployments]` | `subdomain` | Because TOML has no `null` literal, use `retention_days = 0` to disable retention cleanup for realtime messages or schedule execution logs; `config apply` sends `null` to the backend for that value. Email templates, OAuth provider app credentials, buckets, realtime channels, functions, deployment environment variables, and secrets are not managed through this file. A complete example: ```toml theme={null} [auth] require_email_verification = true verify_email_method = "code" reset_password_method = "code" disable_signup = false allowed_redirect_urls = [ "https://app.example.com/auth/callback", "http://localhost:3000/auth/callback", ] [auth.password] min_length = 12 require_number = true require_lowercase = true require_uppercase = true require_special_char = false [auth.smtp] enabled = true host = "smtp.sendgrid.net" port = 587 username = "apikey" password = "env(SENDGRID_API_KEY)" sender_email = "noreply@example.com" sender_name = "Acme" [storage] max_file_size_mb = 100 [realtime] retention_days = 7 [schedules] retention_days = 0 [deployments] subdomain = "acme-prod" ``` ## `config plan` ```bash theme={null} npx @insforge/cli config plan npx @insforge/cli config plan --file ./config/insforge.toml npx @insforge/cli --json config plan ``` `plan` reads `insforge.toml`, fetches live state via `/api/metadata` and the optional config endpoints for storage, realtime, and schedules, then prints a rendered diff. It also tags any section the live backend doesn't expose yet (older self-hosted versions, etc.). Apply will skip those instead of failing the whole run. Use `plan` before every `apply` in interactive sessions, and as a CI gate to catch unintended drift. ## `config apply` ```bash theme={null} npx @insforge/cli config apply npx @insforge/cli config apply --dry-run npx @insforge/cli config apply --auto-approve npx @insforge/cli --json config apply --auto-approve ``` `apply` runs the same diff as `plan`, then walks the change set: 1. **Per-change capability gate.** Each change is checked against the backend's metadata or the section's config endpoint. If the backend doesn't support a section (e.g. an older self-hosted instance without SMTP exposed), that section is skipped with a named warning, and the rest of the changes still apply. 2. **Secret resolution.** `env(...)` references in the TOML are resolved at apply time from the local environment. If a referenced variable is missing, the command aborts before sending any update, so the backend isn't left half-configured. 3. **Per-section dispatch.** Each change is sent to the appropriate backend endpoint (`/api/auth/config`, `/api/auth/smtp-config`, `/api/storage/config`, `/api/realtime/config`, `/api/schedules/config`, `/api/deployments/slug`, etc.). Changes are independent, so a failure on one section won't roll back earlier successful sections. Flags: * `--dry-run` prints the plan and exits without applying. * `--auto-approve` skips the interactive confirmation. Required when `--json` is set, since there's no TTY for the prompt. * `--file ` overrides the default `./insforge.toml` location. ## `config export` ```bash theme={null} npx @insforge/cli config export npx @insforge/cli config export --out ./config/insforge.toml npx @insforge/cli config export --force ``` `export` pulls the live project's configurable surface and writes it to a TOML file. Use it to: * Bootstrap an `insforge.toml` from a project you've been configuring through the dashboard. * Diff hand-edits against current backend state by exporting to a temporary file and comparing. * Snapshot config before a risky change so you can re-apply the snapshot if you need to roll back. The file written by `export` is the same shape the CLI expects from `apply`, so round-tripping is supported. Without `--force`, `export` refuses to overwrite an existing file in interactive mode and surfaces an `OUTPUT_EXISTS` error in `--json` mode. ## Secret references `auth.smtp.password` and any other sensitive field can be expressed as `env(VAR_NAME)` instead of a literal value: ```toml theme={null} [auth.smtp] password = "env(SENDGRID_API_KEY)" ``` At apply time the CLI reads `SENDGRID_API_KEY` from the local environment, validates it's present, and sends the resolved value to the backend. The TOML itself never contains the secret, so it can be committed. This is what lets one `insforge.toml` apply cleanly to multiple environments: the dev and prod backends differ only in *which* `SENDGRID_API_KEY` is in scope when you run `apply`. `env(...)` refs in a TOML that's the target of `apply` re-send the resolved password on every run. That is the only way the CLI can tell the backend "the secret may have rotated, please update." Fields without an `env(...)` ref are treated as preserve-existing. ## When to use this * **Version control for project config.** Redirect URLs, sign-up policy, password policy, email verification mode, SMTP, storage upload size, and retention windows live in a file your team reviews via PR. * **Multi-environment parity.** One TOML, applied to dev, staging, and prod, keeps the supported project settings aligned everywhere. Environment-specific values (subdomain, SMTP credentials) flow through `--project-id` overrides and `env(...)` refs. * **CI-driven config changes.** Run `config apply --auto-approve --json` from your deploy pipeline. Combine with `config plan` as a PR check so reviewers see what the merge will change in prod. * **Disaster recovery.** A committed `insforge.toml` is a known-good config snapshot. Re-apply it after restoring a project to bring auth, SMTP, storage, retention, and deployment settings back to the expected shape in seconds. * **Self-hosted and local OSS development.** Run `npx @insforge/cli link --api-base-url http://localhost:7130 --api-key ` against a docker-compose stack, then point the CLI's config commands at your local OSS instance the same way you'd point at cloud. ## Troubleshooting **`Refusing to apply in --json mode without --auto-approve or --yes`.** The CLI never silently applies changes in non-interactive runs. Pass `--auto-approve` (or `-y`) explicitly. **`your backend doesn't expose
`.** The linked backend is on a version that doesn't have the relevant API yet. The rest of your changes still applied. Upgrade the backend (or wait for the next release) to apply that section. **`env(...)` reference resolves to nothing.** The CLI aborts before any API call when a referenced env var is missing. Set the variable in your shell or your CI's secret store and re-run. **`Slug is already taken`.** `deployments.subdomain` conflicts with another project's subdomain on the same backend. Pick a different value. ## Related * [CLI harness](/agent-native/cli-harness): the full command surface an agent drives * [Deployment security guide](/deployment/deployment-security-guide): hardening a self-hosted backend after deploy # Diagnostics & advisor Source: https://docs.insforge.dev/agent-native/diagnostics The agent reads backend health, advisor findings, and error logs, then applies the fix. When something breaks, an agent should not have to guess. `npx @insforge/cli diagnose` turns the backend's own signals into something an agent can fetch and act on directly: advisor findings with remediations, database health checks, instance metrics, and aggregated error logs. Each finding comes with a fix, so "what is wrong" and "what to do about it" arrive together. The agent pulls all of this itself, with no dashboard in the loop, which is what lets it close security gaps like a permissive RLS policy before they leak data. Backend Advisor finding: a permissive RLS policy on public.messages with a Copy Remediation button ## Full health report Run `diagnose` with no subcommand for everything at once: ```bash theme={null} npx @insforge/cli diagnose npx @insforge/cli diagnose --json ``` The report bundles the advisor scan, database health, instance metrics, and recent error logs into one output. In `--json` mode the agent gets it all as structured data. ## Backend Advisor The advisor scans for security, performance, and health issues and writes a remediation for each one. The dashboard shows the findings with a "Copy Remediation" button, but the agent does not need the dashboard: it fetches the same scan directly with `--json` and applies the fix itself, so a security issue gets closed without waiting for a human to notice it. ```bash theme={null} npx @insforge/cli diagnose advisor npx @insforge/cli diagnose advisor --json ``` A typical finding is a permissive RLS policy that exposes a table to anonymous users. The advisor names the table, the severity, and the SQL to fix it. The agent reads the finding, applies the remediation as a [migration](/core-concepts/database/migrations), and re-scans to confirm it cleared. ## Targeted checks Each part of the report is also available on its own: | Command | What it checks | | ------------------ | ------------------------------------------------------------- | | `diagnose advisor` | Latest advisor scan: security, performance, and health issues | | `diagnose db` | Database health: connections, table bloat, index usage | | `diagnose metrics` | Instance metrics: CPU, memory, disk, and network | | `diagnose logs` | Error-level logs aggregated across every backend source | For raw logs from a single source, use the top-level command: ```bash theme={null} npx @insforge/cli logs function.logs npx @insforge/cli logs postgres.logs ``` Sources include `insforge.logs`, `postgREST.logs`, `postgres.logs`, `function.logs`, and `function-deploy.logs`. ## Let the agent interpret it `diagnose --ai` hands the collected diagnostic data to a model and returns a plain-language analysis: ```bash theme={null} npx @insforge/cli diagnose --ai "why did write latency spike after the last deploy?" ``` This is the difference between dumping a stack trace and explaining the fix. The agent asks a question about the live backend and gets an answer grounded in the actual signals. ## Next steps * Apply advisor remediations as [migrations](/core-concepts/database/migrations) so the fix is versioned. * Rehearse a risky fix on a [backend branch](/agent-native/branching) before it touches production. * Read the [CLI harness](/agent-native/cli-harness) for the rest of the command surface. # Agent-Native Initiatives Source: https://docs.insforge.dev/agent-native/overview The primitives that let a coding agent operate your backend, not just a human clicking a dashboard. Most backends assume a human in a dashboard. InsForge assumes a coding agent at a terminal. The products (Database, Auth, Storage, and the rest) are the building blocks; the primitives on this page are how an agent operates them: as files it can edit, branches it can test on, and a backend it can diagnose and fix on its own. New here? Start with [Connect via CLI](/quickstart) to link your project. This section is about how an agent *works* with the backend once it is connected. ## The primitives The agent's hands. One terminal interface for login, schema, deploys, config, and diagnostics. Pull advisor findings, DB health, metrics, and error logs the agent can read and fix. Auth, SMTP, storage, retention, and deployment settings live in `insforge.toml`. Plan and apply like infrastructure. Clone the whole backend into an isolated branch to test risky changes, then merge or reset. ## Why it matters A person and an agent want different things from a backend. A person wants a UI to click. An agent wants a stable text interface it can drive, read back, and reason about. That shows up everywhere in InsForge. Schema changes are [migrations](/core-concepts/database/migrations) in your repo, and project config is an [`insforge.toml`](/agent-native/config-as-code) file, so the agent edits text, commits it, and reviews it in a PR instead of clicking through forms. When it wants to try something risky, it spins up a [backend branch](/agent-native/branching), runs the change against a copy, and throws the branch away if it goes wrong. When something looks off, it fetches [diagnostics and advisor findings](/agent-native/diagnostics) directly with `npx @insforge/cli diagnose`, no dashboard in the loop, and applies the fix itself. That last part is also how the backend gets more secure: the agent reads a security finding like a permissive RLS policy and remediates it on its own, instead of waiting for a human to remember to check. And it reads current schemas, logs, and metadata straight from the backend with `npx @insforge/cli metadata`, so it works from real state rather than guessing. ## The loop These chain together. A session usually goes: 1. Read the current state with `npx @insforge/cli metadata`. 2. Branch the backend, write a [migration](/core-concepts/database/migrations) and check what is pending with `npx @insforge/cli db migrations list`, or edit `insforge.toml` and preview the config diff with `npx @insforge/cli config plan`. 3. Apply it with `npx @insforge/cli db migrations up --all` or `config apply`, against the branch first, then the parent. 4. Run `npx @insforge/cli diagnose` to check advisor findings and error logs, and ask `diagnose --ai` to interpret them. 5. Apply the remediation, re-run diagnose, and merge the branch. ## Next steps * Read the [CLI harness](/agent-native/cli-harness) to see the full command surface an agent drives. * Set up [config as code](/agent-native/config-as-code) so project settings live in version control. * Use [diagnostics](/agent-native/diagnostics) to let the agent find and fix backend issues. # Admin login Source: https://docs.insforge.dev/api-reference/admin/admin-login https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/admin/sessions Authenticates admin user for dashboard access # Clear logs Source: https://docs.insforge.dev/api-reference/admin/clear-logs https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/logs.yaml delete /api/logs # Create a new secret Source: https://docs.insforge.dev/api-reference/admin/create-a-new-secret https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/secrets.yaml post /api/secrets Create a new encrypted secret with a unique key # Create and Execute Database Migration Source: https://docs.insforge.dev/api-reference/admin/create-and-execute-database-migration https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/tables.yaml post /api/database/migrations # Create custom OAuth configuration Source: https://docs.insforge.dev/api-reference/admin/create-custom-oauth-configuration https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/oauth/custom/configs Create a new custom OAuth provider configuration (admin only) # Create New Bucket Source: https://docs.insforge.dev/api-reference/admin/create-new-bucket https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml post /api/storage/buckets # Create new function Source: https://docs.insforge.dev/api-reference/admin/create-new-function https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/functions.yaml post /api/functions Create a new function with code that runs in Deno runtime # Create OAuth configuration Source: https://docs.insforge.dev/api-reference/admin/create-oauth-configuration https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/oauth/configs Create a new OAuth provider configuration (admin only) # Create Table Source: https://docs.insforge.dev/api-reference/admin/create-table https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/tables.yaml post /api/database/tables # Delete Bucket Source: https://docs.insforge.dev/api-reference/admin/delete-bucket https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml delete /api/storage/buckets/{bucketName} Delete an entire bucket and all its objects # Delete custom OAuth configuration Source: https://docs.insforge.dev/api-reference/admin/delete-custom-oauth-configuration https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml delete /api/auth/oauth/custom/{key}/config Delete a custom OAuth provider configuration (admin only) # Delete function Source: https://docs.insforge.dev/api-reference/admin/delete-function https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/functions.yaml delete /api/functions/{slug} Permanently delete a function # Delete OAuth configuration Source: https://docs.insforge.dev/api-reference/admin/delete-oauth-configuration https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml delete /api/auth/oauth/{provider}/config Delete OAuth provider configuration (admin only) # Delete secret Source: https://docs.insforge.dev/api-reference/admin/delete-secret https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/secrets.yaml delete /api/secrets/{key} Mark a secret as inactive (soft delete). Cannot delete reserved secrets. # Delete Table Source: https://docs.insforge.dev/api-reference/admin/delete-table https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/tables.yaml delete /api/database/tables/{tableName} # Delete users (admin only) Source: https://docs.insforge.dev/api-reference/admin/delete-users-admin-only https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml delete /api/auth/users Delete multiple users by their IDs # Exchange cloud provider authorization code for admin session Source: https://docs.insforge.dev/api-reference/admin/exchange-cloud-provider-authorization-code-for-admin-session https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/admin/sessions/exchange Verifies an authorization code/JWT from from Insforge Cloud platform and issues an internal admin session token with project_admin role # Get active OpenRouter API key Source: https://docs.insforge.dev/api-reference/admin/get-active-openrouter-api-key https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/ai.yaml get /api/ai/openrouter/api-key Returns the active OpenRouter API key details for admin copy workflows. # Get all available AI models Source: https://docs.insforge.dev/api-reference/admin/get-all-available-ai-models https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/ai.yaml get /api/ai/models Returns the normalized OpenRouter model catalog fetched from OpenRouter with output_modalities=all. # Get anon key Source: https://docs.insforge.dev/api-reference/admin/get-anon-key https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/metadata.yaml get /api/metadata/anon-key Admin-only endpoint to retrieve the project's opaque anon key (`anon_...`). The anon key is a non-secret client identifier that maps requests to the `anon` role - safe to embed in frontend bundles; row level security is the security boundary. Rotatable via POST /api/secrets/anon-key/rotate. # Get anon key (deprecated) Source: https://docs.insforge.dev/api-reference/admin/get-anon-key-deprecated https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/tokens/anon Deprecated - use GET /api/metadata/anon-key instead. Returns the project's opaque anon key (`anon_...`) under the legacy `accessToken` field for backward compatibility. The anon key is a non-secret client identifier that maps requests to the `anon` role; it replaces the former non-expiring anonymous JWT and can be rotated via POST /api/secrets/anon-key/rotate (admin only). # Get API key Source: https://docs.insforge.dev/api-reference/admin/get-api-key https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/metadata.yaml get /api/metadata/api-key Admin-only endpoint to retrieve the API key # Get app metadata Source: https://docs.insforge.dev/api-reference/admin/get-app-metadata https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/metadata.yaml get /api/metadata Returns aggregated application metadata (auth, database, storage, functions, realtime, deployments). Use the `format` query parameter to choose between JSON and Markdown output. # Get authentication configuration Source: https://docs.insforge.dev/api-reference/admin/get-authentication-configuration https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/config Get current authentication settings including all configuration options (admin only) # Get custom OAuth configuration Source: https://docs.insforge.dev/api-reference/admin/get-custom-oauth-configuration https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/oauth/custom/{key}/config Get a custom OAuth configuration including client secret (admin only) # Get database metadata Source: https://docs.insforge.dev/api-reference/admin/get-database-metadata https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/metadata.yaml get /api/metadata/database Get database statistics and table information for dashboard # Get function details Source: https://docs.insforge.dev/api-reference/admin/get-function-details https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/functions.yaml get /api/functions/{slug} Get a specific function including its code # Get logs statistics Source: https://docs.insforge.dev/api-reference/admin/get-logs-statistics https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/logs.yaml get /api/logs/stats # Get Model Gateway overview Source: https://docs.insforge.dev/api-reference/admin/get-model-gateway-overview https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/ai.yaml get /api/ai/overview Returns key-level OpenRouter usage and activity time series when the key has activity access. # Get OAuth configuration for specific provider Source: https://docs.insforge.dev/api-reference/admin/get-oauth-configuration-for-specific-provider https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/oauth/{provider}/config Get OAuth configuration including client secret (admin only) # Get secret value Source: https://docs.insforge.dev/api-reference/admin/get-secret-value https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/secrets.yaml get /api/secrets/{key} Retrieve the decrypted value of a specific secret by its key # Get specific user Source: https://docs.insforge.dev/api-reference/admin/get-specific-user https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/users/{userId} Get user details by ID (admin only) # Get Table Schema Source: https://docs.insforge.dev/api-reference/admin/get-table-schema https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/tables.yaml get /api/database/tables/{tableName}/schema # List activity logs Source: https://docs.insforge.dev/api-reference/admin/list-activity-logs https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/logs.yaml get /api/logs # List All Buckets Source: https://docs.insforge.dev/api-reference/admin/list-all-buckets https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml get /api/storage/buckets # List all custom OAuth configurations Source: https://docs.insforge.dev/api-reference/admin/list-all-custom-oauth-configurations https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/oauth/custom/configs Get all configured custom OAuth providers (admin only) # List all functions Source: https://docs.insforge.dev/api-reference/admin/list-all-functions https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/functions.yaml get /api/functions Get all functions with their metadata # List all OAuth configurations Source: https://docs.insforge.dev/api-reference/admin/list-all-oauth-configurations https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/oauth/configs Get all configured OAuth providers (admin only) # List all secrets Source: https://docs.insforge.dev/api-reference/admin/list-all-secrets https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/secrets.yaml get /api/secrets Returns metadata for all secrets (without values) # List all users (admin only) Source: https://docs.insforge.dev/api-reference/admin/list-all-users-admin-only https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/users Returns paginated list of users # List Database Migrations Source: https://docs.insforge.dev/api-reference/admin/list-database-migrations https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/tables.yaml get /api/database/migrations # List Objects in Bucket Source: https://docs.insforge.dev/api-reference/admin/list-objects-in-bucket https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml get /api/storage/buckets/{bucketName}/objects # List Tables Source: https://docs.insforge.dev/api-reference/admin/list-tables https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/tables.yaml get /api/database/tables # Logout admin dashboard session Source: https://docs.insforge.dev/api-reference/admin/logout-admin-dashboard-session https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/admin/logout Clears the dashboard-only `insforge_admin_refresh_token` cookie. # Refresh admin dashboard access token Source: https://docs.insforge.dev/api-reference/admin/refresh-admin-dashboard-access-token https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/admin/refresh Uses the dashboard-only httpOnly `insforge_admin_refresh_token` cookie with an `X-CSRF-Token` header. # Rotate anon key Source: https://docs.insforge.dev/api-reference/admin/rotate-anon-key https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/secrets.yaml post /api/secrets/anon-key/rotate Rotate the project's opaque anon key (`anon_...`). A new key is generated and returned; the old key stays valid for the grace period (default 168 hours / 7 days, max 720) so already-deployed frontends and mobile binaries keep working while the new key ships. Admin only. # Update authentication configuration Source: https://docs.insforge.dev/api-reference/admin/update-authentication-configuration https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml put /api/auth/config Update authentication settings (admin only) # Update Bucket Source: https://docs.insforge.dev/api-reference/admin/update-bucket https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml patch /api/storage/buckets/{bucketName} # Update custom OAuth configuration Source: https://docs.insforge.dev/api-reference/admin/update-custom-oauth-configuration https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml put /api/auth/oauth/custom/{key}/config Update a custom OAuth provider configuration (admin only) # Update function Source: https://docs.insforge.dev/api-reference/admin/update-function https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/functions.yaml put /api/functions/{slug} Update an existing function's code or metadata # Update OAuth configuration Source: https://docs.insforge.dev/api-reference/admin/update-oauth-configuration https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml put /api/auth/oauth/{provider}/config Update OAuth provider configuration (admin only) # Update secret Source: https://docs.insforge.dev/api-reference/admin/update-secret https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/secrets.yaml put /api/secrets/{key} Update an existing secret's value or metadata # Update Table Schema Source: https://docs.insforge.dev/api-reference/admin/update-table-schema https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/tables.yaml patch /api/database/tables/{tableName}/schema # Create Channel Source: https://docs.insforge.dev/api-reference/channels/create-channel https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/realtime.yaml post /api/realtime/channels Create a new realtime channel with a pattern for subscription matching # Delete Channel Source: https://docs.insforge.dev/api-reference/channels/delete-channel https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/realtime.yaml delete /api/realtime/channels/{id} Delete a channel definition. Existing message history is preserved with null channelId values. # Get Channel by ID Source: https://docs.insforge.dev/api-reference/channels/get-channel-by-id https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/realtime.yaml get /api/realtime/channels/{id} Retrieve a specific channel by its UUID # List All Channels Source: https://docs.insforge.dev/api-reference/channels/list-all-channels https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/realtime.yaml get /api/realtime/channels Retrieve all configured realtime channels # Update Channel Source: https://docs.insforge.dev/api-reference/channels/update-channel https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/realtime.yaml put /api/realtime/channels/{id} Update an existing channel's configuration # Confirm Presigned Upload Source: https://docs.insforge.dev/api-reference/client/confirm-presigned-upload https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml post /api/storage/buckets/{bucketName}/objects/{objectKey}/confirm-upload Confirms that a file was successfully uploaded to S3 using presigned URL # Create Records Source: https://docs.insforge.dev/api-reference/client/create-records https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/records.yaml post /api/database/records/{tableName} Create one or more records. Request body MUST be an array. # Custom OAuth callback (GET) Source: https://docs.insforge.dev/api-reference/client/custom-oauth-callback-get https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/oauth/custom/{key}/callback OAuth callback endpoint for custom OAuth providers # Delete Object Source: https://docs.insforge.dev/api-reference/client/delete-object https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml delete /api/storage/buckets/{bucketName}/objects/{objectKey} # Delete Records Source: https://docs.insforge.dev/api-reference/client/delete-records https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/records.yaml delete /api/database/records/{tableName} Delete records matching query filters # Download Object Source: https://docs.insforge.dev/api-reference/client/download-object https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml get /api/storage/buckets/{bucketName}/objects/{objectKey} # Exchange OAuth code for tokens (PKCE) Source: https://docs.insforge.dev/api-reference/client/exchange-oauth-code-for-tokens-pkce https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/oauth/exchange Exchange the insforge_code (received from OAuth callback) for access and refresh tokens. This endpoint is used for PKCE flow in mobile/desktop/server clients: 1. After OAuth callback, your redirect_uri receives `insforge_code` parameter 2. Call this endpoint with the code and your original code_verifier 3. Receive access token and refresh token in response The code_verifier must match the code_challenge sent during OAuth initiation. # Exchange reset password code for reset token Source: https://docs.insforge.dev/api-reference/client/exchange-reset-password-code-for-reset-token https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/email/exchange-reset-password-token Step 1 of two-step password reset flow (only used when resetPasswordMethod is 'code'): 1. Verify the 6-digit code sent to user's email 2. Return a reset token that can be used to actually reset the password This endpoint is not used when resetPasswordMethod is 'link', because the browser reset-link flow uses the emailed link token directly. # Execute function (DELETE) Source: https://docs.insforge.dev/api-reference/client/execute-function-delete https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/functions.yaml delete /functions/{slug} Execute a function using DELETE method. Proxied to Deno runtime. # Execute function (GET) Source: https://docs.insforge.dev/api-reference/client/execute-function-get https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/functions.yaml get /functions/{slug} Execute a function using GET method. Proxied to Deno runtime. # Execute function (PATCH) Source: https://docs.insforge.dev/api-reference/client/execute-function-patch https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/functions.yaml patch /functions/{slug} Execute a function using PATCH method. Proxied to Deno runtime. # Execute function (POST) Source: https://docs.insforge.dev/api-reference/client/execute-function-post https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/functions.yaml post /functions/{slug} Execute a function using POST method with JSON body. Proxied to Deno runtime. # Execute function (PUT) Source: https://docs.insforge.dev/api-reference/client/execute-function-put https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/functions.yaml put /functions/{slug} Execute a function using PUT method. Proxied to Deno runtime. # Generate chat completion (deprecated) Source: https://docs.insforge.dev/api-reference/client/generate-chat-completion-deprecated https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/ai.yaml post /api/ai/chat/completion Deprecated compatibility proxy. New integrations should call https://openrouter.ai/api/v1/chat/completions directly with the provisioned OpenRouter key. # Generate embeddings (deprecated) Source: https://docs.insforge.dev/api-reference/client/generate-embeddings-deprecated https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/ai.yaml post /api/ai/embeddings Deprecated compatibility proxy. New integrations should call https://openrouter.ai/api/v1/embeddings directly with the provisioned OpenRouter key. # Generate images (deprecated) Source: https://docs.insforge.dev/api-reference/client/generate-images-deprecated https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/ai.yaml post /api/ai/image/generation Deprecated compatibility proxy. New integrations should use OpenRouter image-capable models directly with the provisioned OpenRouter key. # Get current session Source: https://docs.insforge.dev/api-reference/client/get-current-session https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/sessions/current Returns the currently authenticated user's basic info from the access token. Project admin tokens return a projectAdmin session object instead of an auth.users row. This endpoint does not refresh expired access tokens by itself. For browser apps using the TypeScript SDK, call `auth.getCurrentUser()` during startup. The SDK will use the httpOnly refresh cookie automatically when it can refresh the session. Server, mobile, and other non-browser clients should call `/api/auth/refresh` explicitly. # Get Download Strategy (Deprecated — use GET on the canonical path) Source: https://docs.insforge.dev/api-reference/client/get-download-strategy-deprecated-—-use-get-on-the-canonical-path https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml post /api/storage/buckets/{bucketName}/objects/{objectKey}/download-strategy Deprecated alias of `GET /api/storage/buckets/{bucketName}/download-strategy/objects/{objectKey}`. Retained at the original path/method for backward compatibility with SDK releases that already shipped against this POST endpoint. Any request body is ignored. # Get Download Strategy (Direct or Presigned URL) Source: https://docs.insforge.dev/api-reference/client/get-download-strategy-direct-or-presigned-url https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml get /api/storage/buckets/{bucketName}/download-strategy/objects/{objectKey} Returns download strategy based on storage backend and bucket visibility. - S3 with public bucket: Direct URLs (no presigning, better performance) - S3 with private bucket: Presigned URLs with expiration - Local storage: Always direct endpoints Expiry (when presigned) is auto-calculated server-side from bucket visibility; no request body is accepted. Object keys may contain `/` (pseudo-folders); the path is matched with a wildcard, so callers should NOT percent-encode `/` inside `objectKey`. # Get public authentication configuration Source: https://docs.insforge.dev/api-reference/client/get-public-authentication-configuration https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/public-config Get all public authentication configuration including OAuth providers and email auth settings (public endpoint) # Get Upload Strategy (Direct or Presigned URL) Source: https://docs.insforge.dev/api-reference/client/get-upload-strategy-direct-or-presigned-url https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml post /api/storage/buckets/{bucketName}/upload-strategy Returns upload strategy based on storage backend (S3 returns presigned URLs, local returns direct upload endpoints) # Get user profile by ID Source: https://docs.insforge.dev/api-reference/client/get-user-profile-by-id https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/profiles/{userId} Get public profile information for a user by their ID (public endpoint) # Health check Source: https://docs.insforge.dev/api-reference/client/health-check https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/health.yaml get /api/health Check if the API is running and healthy # Initiate custom OAuth flow (PKCE) Source: https://docs.insforge.dev/api-reference/client/initiate-custom-oauth-flow-pkce https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/oauth/custom/{key} Generate OAuth authorization URL for a configured custom OAuth provider using PKCE flow # Initiate OAuth flow (PKCE) Source: https://docs.insforge.dev/api-reference/client/initiate-oauth-flow-pkce https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/oauth/{provider} Generate OAuth authorization URL for any supported provider using PKCE flow. For mobile/desktop/server clients using PKCE: 1. Generate code_verifier (random string, 43-128 chars) 2. Generate code_challenge = Base64URL(SHA256(code_verifier)) 3. Call this endpoint with code_challenge 4. After OAuth callback, use /api/auth/oauth/exchange with code_verifier # Logout user Source: https://docs.insforge.dev/api-reference/client/logout-user https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/logout Logout and clear refresh token cookie # Open password reset flow from browser link click Source: https://docs.insforge.dev/api-reference/client/open-password-reset-flow-from-browser-link-click https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/email/reset-password-link Browser-oriented password reset link flow. This endpoint is intended for users clicking password reset links in email. It validates the token on the backend and redirects the browser to the stored, validated `redirectTo` URL with the reset token in the query string. Redirect query params: - Ready: `token=...&insforge_status=ready&insforge_type=reset_password` - Error: `insforge_status=error&insforge_type=reset_password&insforge_error=...` - `token`: present only when `insforge_status=ready` - `insforge_status`: `ready` or `error` - `insforge_type`: always `reset_password` - `insforge_error`: present only on error, human-readable message Your app should render the reset-password form only when `insforge_status=ready` and `token` is present. # Provider-specific OAuth callback (GET) Source: https://docs.insforge.dev/api-reference/client/provider-specific-oauth-callback-get https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/oauth/{provider}/callback OAuth callback endpoint for provider-specific flows (most providers use GET). Response varies based on the original OAuth initiation: - With code_challenge (PKCE): Redirects with `insforge_code` for exchange endpoint - Without code_challenge (web): Redirects with `access_token` and sets httpOnly cookie # Provider-specific OAuth callback (POST) Source: https://docs.insforge.dev/api-reference/client/provider-specific-oauth-callback-post https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/oauth/{provider}/callback OAuth callback endpoint for providers that use POST (e.g., Apple with form_post response mode). Response varies based on the original OAuth initiation: - With code_challenge (PKCE): Redirects with `insforge_code` for exchange endpoint - Without code_challenge (web): Redirects with `access_token` and sets httpOnly cookie # Query Records Source: https://docs.insforge.dev/api-reference/client/query-records https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/records.yaml get /api/database/records/{tableName} Query records from a table with filtering, sorting, and pagination # Refresh access token Source: https://docs.insforge.dev/api-reference/client/refresh-access-token https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/refresh Refresh access token using refresh token. - Web clients: Use httpOnly refresh token cookie with X-CSRF-Token header - Mobile/Desktop/Server clients: Send refreshToken in request body # Register new user Source: https://docs.insforge.dev/api-reference/client/register-new-user https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/users Creates a new user account # Reset password with token Source: https://docs.insforge.dev/api-reference/client/reset-password-with-token https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/email/reset-password Reset user password with a token. The token can be: - Magic link token (64-character hex token from send-reset-password when method is 'link') - Reset token (from exchange-reset-password-token after code verification when method is 'code') Both token types use RESET_PASSWORD purpose and are verified the same way. Flow summary: - Code method: send-reset-password → exchange-reset-password-token → reset-password (with resetToken) - Link method: send-reset-password → GET /api/auth/email/reset-password-link → reset-password # Send email verification (code or link based on config) Source: https://docs.insforge.dev/api-reference/client/send-email-verification-code-or-link-based-on-config https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/email/send-verification Send email verification using the method configured in auth settings (verifyEmailMethod). When method is 'code', sends a 6-digit numeric code. When method is 'link', sends a browser verification link that goes through an InsForge backend endpoint first. Prevents user enumeration by returning success even if email doesn't exist. # Send password reset (code or link based on config) Source: https://docs.insforge.dev/api-reference/client/send-password-reset-code-or-link-based-on-config https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/email/send-reset-password Send password reset email using the method configured in auth settings (resetPasswordMethod). When method is 'code', sends a 6-digit numeric code for two-step flow. When method is 'link', sends a browser reset link that goes through an InsForge backend endpoint first. Prevents user enumeration by returning success even if email doesn't exist. # Send raw HTML email Source: https://docs.insforge.dev/api-reference/client/send-raw-html-email https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/email.yaml post /api/email/send-raw Send a custom HTML email to one or more recipients. Requires user authentication. # Shared OAuth callback handler Source: https://docs.insforge.dev/api-reference/client/shared-oauth-callback-handler https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/oauth/shared/callback/{state} Handles OAuth callbacks from InsForge Cloud shared OAuth # Update current user's profile Source: https://docs.insforge.dev/api-reference/client/update-current-users-profile https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml patch /api/auth/profiles/current Update the profile of the currently authenticated user # Update Records Source: https://docs.insforge.dev/api-reference/client/update-records https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/records.yaml patch /api/database/records/{tableName} Update records matching query filters # Upload Object Source: https://docs.insforge.dev/api-reference/client/upload-object https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml put /api/storage/buckets/{bucketName}/objects/{objectKey} # Upload Object with Auto-Generated Key Source: https://docs.insforge.dev/api-reference/client/upload-object-with-auto-generated-key https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml post /api/storage/buckets/{bucketName}/objects # User login Source: https://docs.insforge.dev/api-reference/client/user-login https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/sessions Authenticates user and returns access token. For web clients, sets httpOnly refresh token cookie. For mobile/desktop/server clients, returns refreshToken in response body. # Verify email from browser link click Source: https://docs.insforge.dev/api-reference/client/verify-email-from-browser-link-click https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml get /api/auth/email/verify-link Browser-oriented link verification flow. This endpoint is intended for users clicking verification links in email. It validates the token on the backend and redirects the browser to the stored, validated `redirectTo` URL with the verification result. Redirect query params: - Success: `insforge_status=success&insforge_type=verify_email` - Error: `insforge_status=error&insforge_type=verify_email&insforge_error=...` - `insforge_status`: `success` or `error` - `insforge_type`: always `verify_email` - `insforge_error`: present only on error, human-readable message Recommended handling: use your sign-in page as `redirectTo`. When the redirect arrives with `insforge_status=success`, show a confirmation message and ask the user to sign in with their email and password. # Verify email with code Source: https://docs.insforge.dev/api-reference/client/verify-email-with-code https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/auth.yaml post /api/auth/email/verify Verify email address with a 6-digit code. Successfully verified users will receive a session token. Browser email clicks should use `GET /api/auth/email/verify-link`. `POST /api/auth/email/verify` is the JSON API for 6-digit code submission. # Get Realtime Config Source: https://docs.insforge.dev/api-reference/configuration/get-realtime-config https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/realtime.yaml get /api/realtime/config Retrieve realtime message retention configuration # Update Realtime Config Source: https://docs.insforge.dev/api-reference/configuration/update-realtime-config https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/realtime.yaml patch /api/realtime/config Update realtime message retention configuration # Clear Messages Source: https://docs.insforge.dev/api-reference/messages/clear-messages https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/realtime.yaml delete /api/realtime/messages Permanently delete all stored realtime messages # Get Message Statistics Source: https://docs.insforge.dev/api-reference/messages/get-message-statistics https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/realtime.yaml get /api/realtime/messages/stats Retrieve aggregated statistics about messages # List Messages Source: https://docs.insforge.dev/api-reference/messages/list-messages https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/realtime.yaml get /api/realtime/messages Retrieve message history with optional filters # Receive Razorpay Webhook Source: https://docs.insforge.dev/api-reference/payment-webhooks/receive-razorpay-webhook https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/webhooks/razorpay/{environment} Receive Razorpay events for one environment. The request body must be the raw Razorpay JSON body and must include the Razorpay signature header. # Receive Stripe Webhook Source: https://docs.insforge.dev/api-reference/payment-webhooks/receive-stripe-webhook https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/webhooks/stripe/{environment} Receive Stripe events for one environment. The request body must be the raw Stripe JSON body and must include the Stripe signature header. # Get Realtime Permissions Source: https://docs.insforge.dev/api-reference/permissions/get-realtime-permissions https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/realtime.yaml get /api/realtime/permissions Retrieve RLS policies for subscribe (channels) and publish (messages) operations # Cancel Razorpay Subscription Source: https://docs.insforge.dev/api-reference/razorpay-payments/cancel-razorpay-subscription https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/razorpay/{environment}/subscriptions/{subscriptionId}/cancel Cancel a Razorpay subscription after evaluating the caller's UPDATE policy on payments.razorpay_subscriptions. # Configure Razorpay Keys Source: https://docs.insforge.dev/api-reference/razorpay-payments/configure-razorpay-keys https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml put /api/payments/razorpay/{environment}/config Validate and store Razorpay API keys for one environment. A webhook secret is generated automatically when one does not already exist. # Create Razorpay Item Source: https://docs.insforge.dev/api-reference/razorpay-payments/create-razorpay-item https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/razorpay/{environment}/catalog/items Create a Razorpay Item, then mirror it locally after Razorpay succeeds. # Create Razorpay Order Source: https://docs.insforge.dev/api-reference/razorpay-payments/create-razorpay-order https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/razorpay/{environment}/orders Create a local Razorpay Order record with the caller's user context, create a Razorpay Order, and return Checkout options for the client-side Razorpay Checkout script. # Create Razorpay Plan Source: https://docs.insforge.dev/api-reference/razorpay-payments/create-razorpay-plan https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/razorpay/{environment}/catalog/plans Create a Razorpay Plan with its amount-bearing item definition, then mirror it locally after Razorpay succeeds. # Create Razorpay Subscription Source: https://docs.insforge.dev/api-reference/razorpay-payments/create-razorpay-subscription https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/razorpay/{environment}/subscriptions Create a Razorpay Subscription and mirror it locally, then return Checkout options for authorization. The backend first evaluates the caller's INSERT policy on payments.razorpay_subscriptions so apps can restrict which subjects can start subscriptions. # Get Razorpay Key Configuration Source: https://docs.insforge.dev/api-reference/razorpay-payments/get-razorpay-key-configuration https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/razorpay/config Return masked Razorpay key configuration for test and live environments. # Get Razorpay Payments Status Source: https://docs.insforge.dev/api-reference/razorpay-payments/get-razorpay-payments-status https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/razorpay/status Return Razorpay connection and sync status for each environment. # Get Razorpay Webhook Setup Values Source: https://docs.insforge.dev/api-reference/razorpay-payments/get-razorpay-webhook-setup-values https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/razorpay/{environment}/webhook Return the Razorpay webhook URL and signing secret that must be copied into the Razorpay Dashboard for manual webhook setup. # List Razorpay Customers Source: https://docs.insforge.dev/api-reference/razorpay-payments/list-razorpay-customers https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/razorpay/{environment}/customers Admin/debug read for mirrored Razorpay customers. This is a display mirror only and should not replace app-owned billing tables. # List Razorpay Payment Catalog Source: https://docs.insforge.dev/api-reference/razorpay-payments/list-razorpay-payment-catalog https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/razorpay/{environment}/catalog Return mirrored Razorpay items and plans for one environment. # List Razorpay Subscriptions Source: https://docs.insforge.dev/api-reference/razorpay-payments/list-razorpay-subscriptions https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/razorpay/{environment}/subscriptions Admin/debug read for mirrored Razorpay subscriptions. Use app-owned tables with RLS for end-user payment state. # List Razorpay Transactions Source: https://docs.insforge.dev/api-reference/razorpay-payments/list-razorpay-transactions https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/razorpay/{environment}/transactions Admin/debug read for InsForge's Razorpay transaction projection. Use app-owned fulfillment tables with RLS for end-user order, credit, or entitlement state. # Pause Razorpay Subscription Source: https://docs.insforge.dev/api-reference/razorpay-payments/pause-razorpay-subscription https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/razorpay/{environment}/subscriptions/{subscriptionId}/pause Pause a Razorpay subscription immediately after evaluating the caller's UPDATE policy on payments.razorpay_subscriptions. # Remove Razorpay Keys Source: https://docs.insforge.dev/api-reference/razorpay-payments/remove-razorpay-keys https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml delete /api/payments/razorpay/{environment}/config Remove the configured Razorpay keys for one environment. # Resume Razorpay Subscription Source: https://docs.insforge.dev/api-reference/razorpay-payments/resume-razorpay-subscription https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/razorpay/{environment}/subscriptions/{subscriptionId}/resume Resume a paused Razorpay subscription immediately after evaluating the caller's UPDATE policy on payments.razorpay_subscriptions. # Rotate Razorpay Webhook Secret Source: https://docs.insforge.dev/api-reference/razorpay-payments/rotate-razorpay-webhook-secret https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/razorpay/{environment}/webhook/rotate-secret Generate a new Razorpay webhook signing secret. Update the webhook secret in the Razorpay Dashboard after calling this endpoint. # Sync Razorpay Payments State Source: https://docs.insforge.dev/api-reference/razorpay-payments/sync-razorpay-payments-state https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/razorpay/sync Sync items, plans, customers, subscriptions, invoices, and payments for every configured Razorpay environment. Razorpay remains the source of truth. # Sync Razorpay Payments State For One Environment Source: https://docs.insforge.dev/api-reference/razorpay-payments/sync-razorpay-payments-state-for-one-environment https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/razorpay/{environment}/sync Sync items, plans, customers, subscriptions, invoices, and payments for one Razorpay environment. Razorpay remains the source of truth. # Update Razorpay Item Source: https://docs.insforge.dev/api-reference/razorpay-payments/update-razorpay-item https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml patch /api/payments/razorpay/{environment}/catalog/items/{itemId} Update mutable Razorpay Item fields, then mirror the item locally after Razorpay succeeds. # Verify Razorpay Order Payment Source: https://docs.insforge.dev/api-reference/razorpay-payments/verify-razorpay-order-payment https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/razorpay/{environment}/orders/verify Verify the Razorpay Checkout signature for an order payment before recording the verified payment ID locally. # Verify Razorpay Subscription Authorization Source: https://docs.insforge.dev/api-reference/razorpay-payments/verify-razorpay-subscription-authorization https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/razorpay/{environment}/subscriptions/verify Verify the Razorpay Checkout signature for the subscription authorization payment before recording the authorization payment ID locally. # Create S3 Access Key Source: https://docs.insforge.dev/api-reference/s3-access-keys/create-s3-access-key https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml post /api/storage/s3/access-keys Mint a new S3 credential pair usable against the `/storage/v1/s3` protocol gateway. The plaintext `secretAccessKey` in the response is returned **exactly once** — it is encrypted at rest and can never be retrieved again. If you lose it, revoke and re-create. Limits: - 50 keys per project (hard cap, enforced transactionally). - Rate-limited to 20 management requests per 15 min per IP. # Get S3 Gateway Config Source: https://docs.insforge.dev/api-reference/s3-access-keys/get-s3-gateway-config https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml get /api/storage/s3/config Return the externally-reachable endpoint URL and the SigV4 signing region for the S3 protocol gateway. The endpoint is assembled from the server's `VITE_API_BASE_URL` env var plus `/storage/v1/s3`, and the region is `AWS_REGION` (default `us-east-2`). Intended for Dashboard display so the UI doesn't make any backend-topology assumptions client-side. # List S3 Access Keys Source: https://docs.insforge.dev/api-reference/s3-access-keys/list-s3-access-keys https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml get /api/storage/s3/access-keys Return every S3 access key configured for this project. Plaintext secrets are **never** returned here — the secret is only shown once in the response of `POST /api/storage/s3/access-keys`. # Revoke S3 Access Key Source: https://docs.insforge.dev/api-reference/s3-access-keys/revoke-s3-access-key https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml delete /api/storage/s3/access-keys/{id} Revoke an S3 access key by its id. The in-memory LRU cache is invalidated synchronously so subsequent S3 protocol requests with this credential return `403 InvalidAccessKeyId`. # S3 protocol (DELETE) Source: https://docs.insforge.dev/api-reference/s3-protocol/s3-protocol-delete https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml delete /storage/v1/s3/{path} Dispatched to DeleteObject / DeleteBucket / AbortMultipartUpload based on path and query. # S3 protocol (GET) Source: https://docs.insforge.dev/api-reference/s3-protocol/s3-protocol-get https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml get /storage/v1/s3/{path} Dispatched to ListBuckets / ListObjectsV2 / GetObject / HeadObject / ListParts / GetBucketLocation / GetBucketVersioning based on path shape and query. # S3 protocol (HEAD) Source: https://docs.insforge.dev/api-reference/s3-protocol/s3-protocol-head https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml head /storage/v1/s3/{path} Dispatched to HeadBucket / HeadObject based on path shape. # S3 protocol (POST) Source: https://docs.insforge.dev/api-reference/s3-protocol/s3-protocol-post https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml post /storage/v1/s3/{path} Dispatched to CreateMultipartUpload / CompleteMultipartUpload / DeleteObjects based on query. # S3 protocol (PUT) Source: https://docs.insforge.dev/api-reference/s3-protocol/s3-protocol-put https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/storage.yaml put /storage/v1/s3/{path} Dispatched to PutObject / UploadPart / CopyObject / CreateBucket based on path shape, query, and headers. # Archive Stripe Price Source: https://docs.insforge.dev/api-reference/stripe-payments/archive-stripe-price https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml delete /api/payments/stripe/{environment}/catalog/prices/{priceId} Archive a Stripe price in one environment, then mirror the archived state locally. # Configure Managed Stripe Webhook Source: https://docs.insforge.dev/api-reference/stripe-payments/configure-managed-stripe-webhook https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/stripe/{environment}/webhook Create or recreate the InsForge-managed Stripe webhook endpoint for one environment. # Configure Stripe Secret Key Source: https://docs.insforge.dev/api-reference/stripe-payments/configure-stripe-secret-key https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml put /api/payments/stripe/{environment}/config Validate and store a Stripe secret key. New Stripe accounts attempt managed webhook setup and then run a unified sync. # Create Stripe Checkout Session Source: https://docs.insforge.dev/api-reference/stripe-payments/create-stripe-checkout-session https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/stripe/{environment}/checkout-sessions Create a local checkout attempt with the caller's user context and then create a Stripe Checkout Session. Subscription mode requires a billing subject. # Create Stripe Customer Portal Session Source: https://docs.insforge.dev/api-reference/stripe-payments/create-stripe-customer-portal-session https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/stripe/{environment}/customer-portal-sessions Create a Stripe Billing Portal Session for an authenticated user's mapped billing subject. # Create Stripe Price Source: https://docs.insforge.dev/api-reference/stripe-payments/create-stripe-price https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/stripe/{environment}/catalog/prices Create a Stripe price in the requested environment, then mirror it locally after Stripe succeeds. # Create Stripe Product Source: https://docs.insforge.dev/api-reference/stripe-payments/create-stripe-product https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/stripe/{environment}/catalog/products Create a Stripe product in the requested environment, then mirror it locally after Stripe succeeds. # Delete Stripe Product Source: https://docs.insforge.dev/api-reference/stripe-payments/delete-stripe-product https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml delete /api/payments/stripe/{environment}/catalog/products/{productId} Delete a Stripe product from one environment, then remove it from the local mirror. # Get Stripe Key Configuration Source: https://docs.insforge.dev/api-reference/stripe-payments/get-stripe-key-configuration https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/stripe/config Return masked Stripe key configuration for test and live environments. # Get Stripe Payments Status Source: https://docs.insforge.dev/api-reference/stripe-payments/get-stripe-payments-status https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/stripe/status Return Stripe connection, sync, and managed webhook status for each environment. # Get Stripe Price Source: https://docs.insforge.dev/api-reference/stripe-payments/get-stripe-price https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/stripe/{environment}/catalog/prices/{priceId} Get one mirrored Stripe price. # Get Stripe Product Source: https://docs.insforge.dev/api-reference/stripe-payments/get-stripe-product https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/stripe/{environment}/catalog/products/{productId} Get one mirrored Stripe product and its associated prices. # List Stripe Catalog Source: https://docs.insforge.dev/api-reference/stripe-payments/list-stripe-catalog https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/stripe/{environment}/catalog Return mirrored Stripe products and prices for one environment. # List Stripe Customers Source: https://docs.insforge.dev/api-reference/stripe-payments/list-stripe-customers https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/stripe/{environment}/customers Admin/debug read for mirrored Stripe customers. This is a display mirror only and should not replace app-owned billing tables. # List Stripe Prices Source: https://docs.insforge.dev/api-reference/stripe-payments/list-stripe-prices https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/stripe/{environment}/catalog/prices List mirrored Stripe prices for one environment, optionally filtered by product. # List Stripe Products Source: https://docs.insforge.dev/api-reference/stripe-payments/list-stripe-products https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/stripe/{environment}/catalog/products List mirrored Stripe products for one environment. # List Stripe Subscriptions Source: https://docs.insforge.dev/api-reference/stripe-payments/list-stripe-subscriptions https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/stripe/{environment}/subscriptions Admin/debug read for mirrored Stripe subscriptions. Use app-owned tables with RLS for end-user payment state. # List Stripe Transactions Source: https://docs.insforge.dev/api-reference/stripe-payments/list-stripe-transactions https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml get /api/payments/stripe/{environment}/transactions Admin/debug read for InsForge's Stripe transaction projection. Use app-owned fulfillment tables with RLS for end-user order, credit, or entitlement state. # Remove Stripe Secret Key Source: https://docs.insforge.dev/api-reference/stripe-payments/remove-stripe-secret-key https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml delete /api/payments/stripe/{environment}/config Remove the configured Stripe secret key for one environment and best-effort remove managed webhook endpoints. # Sync Stripe Payments State Source: https://docs.insforge.dev/api-reference/stripe-payments/sync-stripe-payments-state https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/stripe/sync Sync products, prices, customers, and subscriptions for every configured Stripe environment. Stripe remains the source of truth. # Sync Stripe Payments State For One Environment Source: https://docs.insforge.dev/api-reference/stripe-payments/sync-stripe-payments-state-for-one-environment https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml post /api/payments/stripe/{environment}/sync Sync products, prices, customers, and subscriptions for one Stripe environment. Stripe remains the source of truth. # Update Stripe Price Source: https://docs.insforge.dev/api-reference/stripe-payments/update-stripe-price https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml patch /api/payments/stripe/{environment}/catalog/prices/{priceId} Update mutable Stripe price fields, then mirror the price locally after Stripe succeeds. # Update Stripe Product Source: https://docs.insforge.dev/api-reference/stripe-payments/update-stripe-product https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/payments.yaml patch /api/payments/stripe/{environment}/catalog/products/{productId} Update a Stripe product, then mirror it locally after Stripe succeeds. # Model Gateway Source: https://docs.insforge.dev/core-concepts/ai/overview Call any LLM through one InsForge-managed key, with per-project quotas. Use the Model Gateway to call chat, streaming, and embedding models through one OpenAI-compatible endpoint. InsForge holds the provider keys, tracks usage per project, and routes traffic through [OpenRouter](https://openrouter.ai), so your application code never sees Anthropic, OpenAI, or Mistral credentials directly. InsForge dashboard Model Gateway overview showing code samples, provider chips, and usage charts **Want to run AI code, not call a model?** Use [Edge Functions](/core-concepts/functions/overview) to orchestrate prompts, retrieval, and tools. The Model Gateway is the call; functions are the program around it. ```mermaid theme={null} graph TB Dashboard[InsForge Dashboard] --> Key[Active OpenRouter Key] Dashboard --> Catalog[OpenRouter Model Catalog] Dashboard --> Metrics[OpenRouter Usage Overview] App[Application Backend or Server Route] --> SDK[OpenAI SDK] SDK --> OpenRouter[OpenRouter API] OpenRouter --> OpenAI[OpenAI] OpenRouter --> Anthropic[Anthropic] OpenRouter --> Google[Google] OpenRouter --> More[Other Providers] style Dashboard fill:#1e293b,stroke:#475569,color:#e2e8f0 style App fill:#166534,stroke:#22c55e,color:#dcfce7 style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style OpenRouter fill:#c2410c,stroke:#fb923c,color:#fed7aa ``` ## Features ### OpenAI-compatible API Point any OpenAI SDK or `openai`-compatible library at `https://.insforge.dev/v1` and it works. `/v1/chat/completions`, `/v1/embeddings`, and `/v1/models` all behave like the upstream spec. ### Streaming Server-sent events for chat completions. Use the streaming endpoint the same way you would with OpenAI; the gateway forwards tokens as they arrive from the provider. ### Embeddings Generate dense vectors from any embedding model OpenRouter supports. Store the result in Postgres with [pgvector](/core-concepts/database/pgvector) for semantic search. ### Per-project quotas Each project carries its own rate limit and spend cap. Hit it, and the gateway returns a clean 429 instead of leaking provider quota state into your app. ### Usage tracking Every request is logged with model, token count, and cost. Query usage from the dashboard, CLI, or MCP — billing reconciles to OpenRouter's invoice automatically. ### Multi-provider routing Switch between Anthropic, OpenAI, Mistral, Llama, Gemini, and dozens more by changing the model name in the request. Application code does not change. ## Build with it Chat, stream, and embed from Node, browser, and edge runtimes. Native Swift AI client for iOS and macOS. Coroutines-first AI client for Android and JVM. Plain HTTP AI endpoints, callable from any language. ## Next steps * Set up the [CLI](/quickstart) to link your project (the recommended path). * Browse the [TypeScript SDK reference](/sdks/typescript/ai) for chat and embedding patterns. # Analytics Source: https://docs.insforge.dev/core-concepts/analytics/overview Product analytics for your app, powered by PostHog. Use InsForge Analytics to understand how people actually use your app: page traffic, retention, and session replays, all wired up by connecting a PostHog project to your InsForge backend. Once connected, the dashboard renders Traffic, User Retention, and Session Replay pages on top of your PostHog data without leaving InsForge. Connect PostHog once with one click, drop the setup prompt into your coding agent so it runs the PostHog wizard and installs the PostHog SDK on your frontend, and the Analytics pages start filling in. InsForge Analytics dashboard showing visitor KPIs, a visitor trend chart, and top pages, countries, and devices breakdowns PostHog remains the source of truth for events, dashboards, insights, and recordings. InsForge surfaces a focused subset for everyday checks, then deep-links into PostHog for anything beyond it. ```mermaid theme={null} flowchart TB Admin["Dashboard"] --> AnalyticsAPI["Analytics API"] App["Frontend App"] --> SDK["PostHog SDK"] SDK --> PostHog["PostHog"] AnalyticsAPI --> PostHog PostHog --> Traffic["Traffic / KPIs"] PostHog --> Retention["User Retention"] PostHog --> Replay["Session Replay"] Traffic --> Pages["Analytics pages"] Retention --> Pages Replay --> Pages style Admin fill:#1e293b,stroke:#475569,color:#e2e8f0 style App fill:#1e293b,stroke:#475569,color:#e2e8f0 style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style AnalyticsAPI fill:#166534,stroke:#22c55e,color:#dcfce7 style PostHog fill:#c2410c,stroke:#fb923c,color:#fed7aa style Traffic fill:#0e7490,stroke:#06b6d4,color:#cffafe style Retention fill:#0e7490,stroke:#06b6d4,color:#cffafe style Replay fill:#0e7490,stroke:#06b6d4,color:#cffafe style Pages fill:#6b21a8,stroke:#a855f7,color:#f3e8ff ``` ## Features ### One-click PostHog connection Connect PostHog from the Analytics page in the dashboard. InsForge provisions or links a PostHog project for you, stores credentials server-side, and unlocks the Traffic, Retention, and Session Replay pages once the connection succeeds. ### SDK setup via PostHog wizard After connecting, the empty state ships a setup prompt you can paste into your coding agent: ``` I want to add product analytics to this project. Read the current directory and use the InsForge skill to set up PostHog analytics by running `npx @insforge/cli posthog setup`. ``` `@insforge/cli posthog setup` links your InsForge project to PostHog, then prints the official [PostHog wizard](https://posthog.com/docs/libraries/wizard) command (`npx -y @posthog/wizard@latest`) for you (or your agent) to run next. The wizard detects your framework, installs the right PostHog SDK, and drops in initialization code so pageviews, autocapture events, and session recordings start flowing. ### Traffic KPIs over your selected time range (visitors, pageviews, sessions, bounce rate, and trend), plus breakdowns by Page, Country, and Device Type. Useful for the first "how is the app doing this week" pass without opening PostHog. ### User retention Cohort retention chart built from your PostHog events. Pick a time range and see how many users come back over the following days or weeks. ### Session replay A paginated list of recent session recordings with duration, person, and a deep-link into PostHog's full replay player. Helps you watch what users actually did right after spotting something odd in Traffic or Retention. ### Settings and disconnect The Analytics Config dialog (the gear icon in the sidebar) lets admins review the linked PostHog project, jump straight into PostHog, and disconnect when needed. Disconnecting only severs the InsForge ↔ PostHog link; your PostHog project, events, and recordings stay intact. ## Concepts Events, autocapture, insights, and dashboards behind the Analytics pages. How recordings are captured, redacted, and played back. ## Build with it Auto-detects your framework, installs the right PostHog SDK, and adds initialization code. Capture custom events on top of what the wizard sets up. `npx @insforge/cli posthog setup` links your InsForge project to PostHog, then prints the wizard command. ## Next steps * Open the Analytics page in the dashboard and click **Connect PostHog**. * Paste the setup prompt into your coding agent, then run the `@posthog/wizard` command it prints to wire the SDK into your app. * Set up the [CLI](/quickstart) if you want to manage the connection from the terminal. # Authentication Source: https://docs.insforge.dev/core-concepts/authentication/overview Use InsForge to authenticate and authorize your users. Use InsForge Authentication to handle sign-up, login, sessions, and identity for your app. Users can sign in with email and password, magic link, one-time code, OAuth providers (Google, GitHub, Apple, and others), or any OIDC-compliant identity provider you bring. InsForge issues JSON Web Tokens on login, and every other product on the platform consumes the same token. InsForge dashboard Auth Methods showing email and password, Google OAuth, and GitHub OAuth **Authentication** is checking that a user is who they say they are. **Authorization** is checking what they can do. InsForge handles the first directly and powers the second through [row-level security](/core-concepts/database/overview) policies that read the auth JWT. ```mermaid theme={null} graph TB Client[Client Application] --> SDK[InsForge SDK] SDK --> AuthAPI[Auth API] AuthAPI --> JWT[JWT Service] AuthAPI --> OAuth[OAuth Providers] AuthAPI --> DB[(PostgreSQL)] OAuth --> Google[Google OAuth 2.0] OAuth --> GitHub[GitHub OAuth] JWT --> Secret[Shared Secret] JWT --> Validation[Token Validation] DB --> Users[auth.users Table] DB --> Providers[auth.user_providers] style Client fill:#1e293b,stroke:#475569,color:#e2e8f0 style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style AuthAPI fill:#166534,stroke:#22c55e,color:#dcfce7 style JWT fill:#c2410c,stroke:#fb923c,color:#fed7aa style OAuth fill:#6b21a8,stroke:#a855f7,color:#f3e8ff style DB fill:#0e7490,stroke:#06b6d4,color:#cffafe style Secret fill:#991b1b,stroke:#ef4444,color:#fee2e2 style Google fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe style GitHub fill:#1e293b,stroke:#64748b,color:#f1f5f9 style Validation fill:#991b1b,stroke:#ef4444,color:#fee2e2 style Users fill:#0e7490,stroke:#22d3ee,color:#cffafe style Providers fill:#0e7490,stroke:#22d3ee,color:#cffafe ``` ## Features ### Email and password The default. New users sign up with an email and password, get a confirmation email, and receive a session JWT on login. Password reset, email verification, and brute-force throttling are built in. ### Magic link and OTP Send a one-time link or six-digit code to the user's email. Passwordless sign-in, account recovery, and step-up auth all use the same primitive. ### OAuth providers First-class support for Google, GitHub, Apple, Microsoft, GitLab, Discord, and more. Add custom OAuth 2.0 / OIDC providers (Keycloak, Okta, Auth0, your own IdP) by URL without writing provider-specific code. ### OAuth server mode Run InsForge itself as an OAuth 2.0 / OIDC identity provider for your own downstream apps. See the [OAuth Server guide](/oauth-server) for the full setup. ### Row-level security The auth JWT flows through every InsForge SDK call automatically. Postgres RLS policies read claims from the token and decide, row by row, what the user can read and write. The same identity and the same policies apply whether the request hits the database, storage, or a realtime channel. ### `auth.users` in your database User state lives in your project's Postgres database in the `auth` schema. Join `auth.users` to your application tables via foreign keys, react to identity changes with triggers, and back the whole thing up the same way you back up everything else. ## Build with it Sign up, log in, and manage sessions from Node, browser, and edge. Native Swift auth client for iOS and macOS. Coroutines-first auth client for Android and JVM. Plain HTTP auth endpoints, callable from any language. ## Next steps * Set up the [CLI](/quickstart) to link your project (the recommended path). * Browse the [TypeScript SDK reference](/sdks/typescript/auth) for sign-in patterns. # Custom Compute Source: https://docs.insforge.dev/core-concepts/compute/overview Run long-lived containers next to your InsForge project. Use InsForge Custom Compute to run long-lived containers next to your project: queue workers, background processors, AI inference loops, websocket servers, scrapers, anything that needs to stay up. Containers attach to your project's database, storage, and auth with the same credentials a function would use. **Just need to handle a request?** Use [Edge Functions](/core-concepts/functions/overview) for request/response work and short jobs. Custom Compute is for processes that need to run continuously. ```mermaid theme={null} graph TB Dashboard[InsForge Dashboard] --> Service[Compute Service] CLI[InsForge CLI] --> Service Service --> Container[Long-lived Container] Container --> DB[(Database)] Container --> Storage[Storage] Container --> Auth[Auth] style Dashboard fill:#1e293b,stroke:#475569,color:#e2e8f0 style CLI fill:#1e40af,stroke:#3b82f6,color:#dbeafe style Service fill:#166534,stroke:#22c55e,color:#dcfce7 style Container fill:#c2410c,stroke:#fb923c,color:#fed7aa style DB fill:#0e7490,stroke:#06b6d4,color:#cffafe style Storage fill:#0e7490,stroke:#06b6d4,color:#cffafe style Auth fill:#0e7490,stroke:#06b6d4,color:#cffafe ``` ## Features ### Container deploys Push any Docker image to InsForge and it runs. Use a `Dockerfile` from your repo or point at a pre-built image on a registry. No proprietary build pipeline to learn. ### Project-linked credentials Containers receive the InsForge project URL, service-role JWT, and S3 storage credentials as environment variables. Connect to Postgres, call the SDK, and read objects without provisioning anything. ### Scaling Run one instance for a singleton worker, or scale horizontally for stateless workloads. Memory, CPU, and replica count are configurable per service. ### Logs Structured logs per container, queryable by service and time range. Tail in the dashboard, CLI, or MCP without `kubectl exec`-ing into anything. ### Secrets and env vars Set environment variables and secrets per service, separately from your edge-function secrets. Rotate without redeploying. ## Next steps * Set up the [CLI](/quickstart) to link your project (the recommended path). * See [Edge Functions](/core-concepts/functions/overview) if request/response is all you need. # Database migrations Source: https://docs.insforge.dev/core-concepts/database/migrations Track schema changes in git and apply them with the InsForge CLI Migrations are versioned SQL files in `migrations/` applied with `@insforge/cli`. Each successful run is recorded in `system.custom_migrations`. The workflow is forward-only. ## Concepts A migration is one SQL file prefixed with a 14-digit UTC timestamp: `_.sql`. The CLI applies pending files in order inside a transaction, sets `search_path` to `public`, and records history only on success. PostgREST reloads schema metadata automatically. `BEGIN`/`COMMIT`/`ROLLBACK` inside a file are rejected. ## Usage Link the backend, then create a file. ```bash theme={null} npx @insforge/cli login npx @insforge/cli link npx @insforge/cli db migrations new create-employees-table ``` Write the SQL. ```sql theme={null} create table if not exists public.employees ( id bigint primary key generated always as identity, name text not null, email text, created_at timestamptz default now() ); ``` Apply pending migrations and check history. ```bash theme={null} npx @insforge/cli db migrations up --all npx @insforge/cli db migrations list ``` Target a single file with `up `, or apply everything pending up to and including a target with `up --to `. ## Specific usage cases Adopting migrations on an existing project: run `db migrations fetch` first to materialize remote history into local files. Once applied remotely, never edit a migration in place. Write a forward migration instead. Once you opt in, route all schema changes through files. Ad hoc dashboard edits cause drift between git and `system.custom_migrations`. ## More resources * [Database branching](/agent-native/branching) to rehearse a migration on a copy. * [Database overview](/core-concepts/database/overview) for how PostgREST picks up schema changes. * [PostgreSQL DDL docs](https://www.postgresql.org/docs/15/ddl.html) for the SQL you write. # Database Source: https://docs.insforge.dev/core-concepts/database/overview Use InsForge to manage your data. Every InsForge project comes with a full [Postgres](https://www.postgresql.org/) database. Every table is automatically a typed REST and SDK endpoint. Auth tokens scope every read and write through row-level security. The same Postgres handles relational queries, semantic search via pgvector, and realtime change feeds. InsForge dashboard table editor showing a messages table with typed columns **Looking for file storage?** Use [Storage](/core-concepts/storage/overview) for images, PDFs, and other binary content. The database stores rows; storage stores objects. ```mermaid theme={null} graph TB Client[Client Application] --> SDK[InsForge SDK] SDK --> API[InsForge API] API --> PostgREST[PostgREST v12.2] PostgREST --> PG[(PostgreSQL 15)] API --> PG PG --> RLS[Row Level Security] PG --> Triggers[Database Triggers] PG --> Functions[Stored Functions] PG --> Schemas[Multiple Schemas] style Client fill:#1e293b,stroke:#475569,color:#e2e8f0 style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style API fill:#166534,stroke:#22c55e,color:#dcfce7 style PostgREST fill:#c2410c,stroke:#fb923c,color:#fed7aa style PG fill:#0e7490,stroke:#06b6d4,color:#cffafe style RLS fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe style Triggers fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe style Functions fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe style Schemas fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe ``` ## Features ### Tables as APIs Define a table and you immediately get REST endpoints plus a typed SDK client for it. No code generation step. The auth JWT scopes every query through RLS. ### Migrations Track and apply SQL changes in order. [Migrations](/core-concepts/database/migrations) ship as plain `.sql` files in your repo, applied with `npx @insforge/cli db migrations up --all` or via the MCP tool. ### Branching Spin up an isolated database branch to test risky schema changes against a copy of production data. See [Branching](/agent-native/branching). ### pgvector Native vector search for embeddings, with HNSW and IVFFlat indexes. See [pgvector](/core-concepts/database/pgvector). ### Row-level security Postgres RLS policies enforce access at the row level. Policies read the auth JWT, so the same rule applies to REST queries, SDK calls, realtime subscriptions, and storage requests. ## Concepts Apply SQL changes in order, safely. Isolated databases for preview and risky changes. Vector search for embeddings. ## Build with it Typed queries, inserts, and updates from Node, browser, and edge. Native Swift database client for iOS and macOS. Coroutines-first database client for Android and JVM. Plain HTTP database endpoints, callable from any language. ## Next steps * Set up the [CLI](/quickstart) to link your project (the recommended path). * Browse the [TypeScript SDK reference](/sdks/typescript/database) for typed queries. # pgvector Source: https://docs.insforge.dev/core-concepts/database/pgvector Store embeddings and run similarity search inside Postgres [pgvector](https://github.com/pgvector/pgvector) ships on every InsForge project. Use it for semantic search, recommendations, and [RAG](https://www.pinecone.io/learn/retrieval-augmented-generation/). ## Prompt your agent > Add pgvector to my project. Create a `documents` table with `content` and a 1536-dim `embedding` column, plus an HNSW cosine index. When I insert content, embed it with OpenRouter's `text-embedding-3-small` from a server-side route. Expose a `match_documents(query, count, threshold)` RPC that returns top similarity matches. ## Concepts A vector is a list of numbers representing an item. Two vectors are similar if they sit close in vector space. Store the vector next to its row, embed the user query the same way, and pgvector ranks by distance. ## Usage Enable the extension and create a vector column. Match the dimension to your model (`text-embedding-3-small` is 1536). ```sql theme={null} create extension if not exists vector; create table documents ( id bigserial primary key, content text, embedding vector(1536) ); ``` Generate an embedding server-side and insert it. ```typescript theme={null} import OpenAI from 'openai'; import { createClient } from '@insforge/sdk'; const openai = new OpenAI({ baseURL: 'https://openrouter.ai/api/v1', apiKey: process.env.OPENROUTER_API_KEY, }); const insforge = createClient({ projectId: process.env.INSFORGE_PROJECT_ID }); const { data } = await openai.embeddings.create({ model: 'openai/text-embedding-3-small', input: 'hello world', }); await insforge.database.from('documents').insert({ content: 'hello world', embedding: data[0].embedding, }); ``` Query by cosine distance (`<=>`). L2 (`<->`) and inner product (`<#>`) are also available. ```sql theme={null} select id, content from documents order by embedding <=> $1 limit 5; ``` ## Specific usage cases Wrap search in a Postgres function and call it via `rpc()` to keep the math server-side: ```sql theme={null} create or replace function match_documents( query_embedding vector(1536), match_count int default 5, match_threshold float default 0 ) returns table (id bigint, content text, similarity float) language sql stable as $$ select id, content, 1 - (embedding <=> query_embedding) as similarity from documents where 1 - (embedding <=> query_embedding) > match_threshold order by embedding <=> query_embedding limit match_count; $$; ``` Past \~10k rows, add an HNSW index: ```sql theme={null} create index on documents using hnsw (embedding vector_cosine_ops); ``` ## More resources * [pgvector on GitHub](https://github.com/pgvector/pgvector) for operators and indexes. * [OpenRouter embeddings](https://openrouter.ai/docs/features/multimodal/embeddings) for the model catalog. * [Model Gateway overview](/core-concepts/ai/overview) for the InsForge side of OpenRouter. # Edge Functions Source: https://docs.insforge.dev/core-concepts/functions/overview Deno-powered serverless TypeScript with first-class schedules. Use InsForge edge functions to run TypeScript on [Deno](https://deno.com), deployed close to your users for low latency. Functions can be invoked on-demand from any client, chained from database triggers, or scheduled to run on a cron expression. The runtime ships standard fetch, streaming responses, and ESM imports out of the box. **Need a process that stays up?** Use [Compute](/core-concepts/compute/overview) for queue workers, AI inference loops, and anything stateful. Edge Functions are for request/response and short-lived jobs. ```mermaid theme={null} graph TB HTTP[HTTP Request] --> Fn[Edge Function on Deno] Schedule[Cron Schedule] --> Fn Trigger[Database Trigger] --> Fn Fn --> SDK[InsForge SDK] SDK --> DB[(Database)] SDK --> Storage[Storage] SDK --> Gateway[Model Gateway] style HTTP fill:#1e293b,stroke:#475569,color:#e2e8f0 style Schedule fill:#1e293b,stroke:#475569,color:#e2e8f0 style Trigger fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe style Fn fill:#c2410c,stroke:#fb923c,color:#fed7aa style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style DB fill:#0e7490,stroke:#06b6d4,color:#cffafe style Storage fill:#166534,stroke:#22c55e,color:#dcfce7 style Gateway fill:#166534,stroke:#22c55e,color:#dcfce7 ``` ## Features ### HTTP triggers Every function is reachable at `https://.insforge.dev/functions/`. Standard fetch in, standard `Response` out. Streaming, JSON, redirects, and websockets all work. ### Schedules Attach a cron expression to a function and InsForge invokes it on time, with retry on failure. See [Schedules](/core-concepts/functions/schedules) for the cron syntax and execution model. ### Database triggers Wire a function to fire on `INSERT`, `UPDATE`, or `DELETE` against a table. The function receives the row payload and runs with a service-role JWT so it can perform privileged follow-up writes. ### Secrets and environment variables Set env vars and secrets per function. The dashboard, CLI, and MCP all read and write the same store; secrets never round-trip through your repo. ### Logs Structured logs are captured per invocation, queryable by status, duration, and function name. The InsForge MCP `get-function-logs` tool lets your agent diagnose failures without leaving the editor. ### Deno standard library Use the [Deno standard library](https://jsr.io/@std) and any ESM module from `jsr.io`, `esm.sh`, or `npm:` specifiers. You don't run a bundler, and there's no `node_modules` directory to ship. ## Concepts Run a function on a cron expression instead of in response to a request. ## Build with it Invoke and stream functions from Node, browser, and edge. Invoke functions from iOS and macOS apps. Invoke functions from Android and JVM apps. Plain HTTP function endpoints, callable from any language. ## Next steps * Set up the [CLI](/quickstart) to link your project (the recommended path). * Browse the [TypeScript SDK reference](/sdks/typescript/functions) for invocation patterns. # Schedules: cron-triggered functions Source: https://docs.insforge.dev/core-concepts/functions/schedules Run a function on a cron schedule using pg_cron Schedules invoke functions on a recurring cron expression. [pg\_cron](https://github.com/citusdata/pg_cron) fires an HTTP request to the function URL at each tick and logs the result. ## Concepts A schedule is a cron expression, a target URL, and headers. On creation, `${{secrets.KEY}}` placeholders in headers are resolved and encrypted with `pgcrypto`. At each tick, `execute_job()` decrypts headers, calls the function, and writes status and duration to `schedules.job_logs`. ## Usage Standard 5-field cron (no seconds). Reference secrets in headers instead of hardcoding keys. ```text theme={null} */5 * * * * every 5 minutes 0 * * * * every hour 0 0 * * * daily at midnight 0 9 * * 1 every Monday at 9am 0 0 1 * * first of every month ``` Create via dashboard or SQL: ```sql theme={null} select schedules.create_job( name => 'daily-cleanup', schedule => '0 0 * * *', url => 'https://myapp.functions.insforge.app/cleanup', headers => jsonb_build_object('Authorization', 'Bearer ${{secrets.CRON_TOKEN}}') ); ``` ## Limits Minimum interval is 1 minute (pg\_cron). Failed runs are logged but not retried, so the function must be idempotent. Deleting a referenced secret breaks every job using it until you update or disable the schedule. ## More resources * [pg\_cron docs](https://github.com/citusdata/pg_cron) for cron syntax. * [Functions overview](/core-concepts/functions/overview) for the runtime. * [crontab.guru](https://crontab.guru) to check an expression. # Custom SMTP Source: https://docs.insforge.dev/core-concepts/messaging/custom-smtp Route every outgoing email through your own SMTP server When enabled, every email (auth flows and `emails.send()` calls) routes through your SMTP server. Toggle off to revert; credentials are preserved. ## Concepts Provider is resolved on every send, so saves take effect on the next request. InsForge runs `transporter.verify()` before saving, so a persisted config always works. Passwords are encrypted at rest with AES-256-GCM and never returned by the API. ## Usage Configure SMTP under **Authentication → Email**. Flip the switch on the **SMTP Provider** card. Host, port (`25`, `465`, `587`, `2525`), username, password, sender email, sender name. Private IPs and self-signed certs are rejected. InsForge runs an SMTP handshake before persisting. Bad credentials fail fast. The **Email Templates** card unlocks the four auth templates. The `From:` header is always your configured sender. SDK callers cannot spoof it. ## Email templates Templates render locally from `email.templates`. Variables use `{{ variable }}` and are HTML-escaped. | Template | When it sends | | ------------------------- | ------------------------------------------- | | `email-verification-code` | New-user verification with a 6-digit code | | `email-verification-link` | New-user verification with a clickable link | | `reset-password-code` | Password reset with a 6-digit code | | `reset-password-link` | Password reset with a clickable link | Variables: `{{ token }}` (code templates), `{{ link }}` (link templates, must start with `http://` or `https://`), `{{ name }}` and `{{ email }}` (all templates). ## Considerations * **Rate limiting.** **Min interval (seconds)** caps per-recipient frequency. Sends within the cooldown return HTTP `429`. Defaults to `60`; `0` disables. * **SSRF protection.** Private, loopback, link-local, and carrier-NAT ranges are rejected. * **Audit log.** Config saves log `UPDATE_SMTP_CONFIG`; template edits log `UPDATE_EMAIL_TEMPLATE`. ## More resources * [Messaging overview](/core-concepts/messaging/overview) for the routing model. * [nodemailer SMTP transport](https://nodemailer.com/smtp/) for connection options. * [Authentication overview](/core-concepts/authentication/overview) for the flows that emit these emails. # Messaging Source: https://docs.insforge.dev/core-concepts/messaging/overview Send transactional messages from your project. Email today, SMS and push on the roadmap. InsForge Messaging sends transactional notifications from your project: receipts, digests, password-reset codes, notification roll-ups, anything you would otherwise wire SendGrid, Postmark, or Twilio in for. Email is the first channel; SMS and push are on the roadmap and will share the same API surface. **Just sending auth emails?** Magic links, verification codes, and password resets are wired into [Authentication](/core-concepts/authentication/overview) out of the box. You only need this product for transactional messages beyond auth. ```mermaid theme={null} graph TB Client[Client Application] --> SDK[InsForge SDK] SDK --> API[Email API] API --> Service[EmailService] Service --> Decision{SMTP enabled?} Decision -->|No| Cloud[InsForge Cloud] Cloud --> SES[AWS SES] Decision -->|Yes| SMTP[Your SMTP server] SES --> Inbox[Recipient Inbox] SMTP --> Inbox style Client fill:#1e293b,stroke:#475569,color:#e2e8f0 style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style API fill:#166534,stroke:#22c55e,color:#dcfce7 style Service fill:#166534,stroke:#22c55e,color:#dcfce7 style Decision fill:#7c3aed,stroke:#a78bfa,color:#ede9fe style Cloud fill:#7c3aed,stroke:#a78bfa,color:#ede9fe style SES fill:#ea580c,stroke:#f97316,color:#fed7aa style SMTP fill:#0e7490,stroke:#06b6d4,color:#cffafe style Inbox fill:#0e7490,stroke:#06b6d4,color:#cffafe ``` ## Channels Managed SMTP or bring your own provider. Templates, delivery tracking, and webhook events. Coming soon. Same API, Twilio or Sinch on the back. Coming soon. APNs and FCM via a single endpoint. ## Features ### One API, every channel Same `emails.send()` shape for email today, with SMS and push to follow when they land. Switching channels is a field change, not a rewrite. ### Managed delivery or bring your own Send through InsForge Cloud (AWS SES for email today) for zero setup, or plug in your own provider when you need to control deliverability and sender reputation. See [Custom SMTP](/core-concepts/messaging/custom-smtp). ### Templates Pick a template by name, pass the variables, and InsForge renders and sends. Templates are editable per project; the four auth templates (`email-verification-*`, `reset-password-*`) ship with sensible defaults. ### Delivery tracking Send events (`accepted`, `delivered`, `bounced`, `complained`) are recorded per message. Query the audit table in Postgres, subscribe over webhooks, or watch the dashboard. ### Rate limits Per-project and per-plan limits keep stray loops from melting deliverability. Configurable from the dashboard, enforced at the gateway. ## Concepts Bring your own SMTP provider (SendGrid, Postmark, AWS SES, etc.). ## Build with it Send mail from Node, browser, and edge runtimes. Plain HTTP messaging endpoints, callable from any language. ## Next steps * Set up the [CLI](/quickstart) to link your project (the recommended path). * Browse the [TypeScript SDK reference](/sdks/typescript/email) for send patterns. # Payments Source: https://docs.insforge.dev/core-concepts/payments/overview Choose a provider-specific payments integration for Stripe or Razorpay. InsForge Payments lets your app collect money with your own Stripe or Razorpay account. The two providers use different payment models, so the docs are split by provider instead of describing one generic payment flow. InsForge Payments dashboard Stripe or Razorpay remains the source of truth for charges, invoices, refunds, disputes, taxes, and account-level financial operations. InsForge is not a payment processor or merchant of record, and it does not replace the provider dashboard. ## Choose a provider Use Stripe Checkout, Products, Prices, Subscriptions, and Billing Portal. Use Razorpay Orders, Items, Plans, Subscriptions, and Razorpay Checkout. ## Architecture Provider-native tables keep provider concepts intact: | Provider | Runtime tables | Catalog tables | Subscription tables | | -------- | ------------------------------------------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------- | | Stripe | `payments.stripe_checkout_sessions`, `payments.stripe_customer_portal_sessions` | `payments.stripe_products`, `payments.stripe_prices` | `payments.stripe_subscriptions`, `payments.stripe_subscription_items` | | Razorpay | `payments.razorpay_orders` | `payments.razorpay_items`, `payments.razorpay_plans` | `payments.razorpay_subscriptions` | Shared tables are used only where the durable shape is useful across providers: | Table | Purpose | | ------------------------------- | ---------------------------------------------------------------------------------------------- | | `payments.provider_connections` | Provider key, account, sync, and webhook setup status by `provider` and `environment`. | | `payments.customer_mappings` | App billing subject to provider customer ID mapping. | | `payments.customers` | Admin/customer mirror for dashboard visibility. | | `payments.webhook_events` | Verified provider webhook event ledger. Use this for durable fulfillment triggers. | | `payments.transactions` | InsForge dashboard/reporting projection for successful, failed, and refunded payment activity. | `payments.transactions` is not the fulfillment contract. It is a projection built from provider events and sync. For business logic, create app-owned tables such as `public.orders`, `public.credit_ledger`, or `public.team_entitlements`, then populate them from verified rows in `payments.webhook_events`. ## Fulfillment Do not fulfill from a Stripe success URL or a Razorpay Checkout callback alone. Those are user experience signals. Durable fulfillment should run from verified provider webhook events. ```sql theme={null} CREATE TRIGGER fulfill_from_payment_webhook AFTER INSERT OR UPDATE ON payments.webhook_events FOR EACH ROW EXECUTE FUNCTION public.fulfill_payment_event(); ``` If your app accepts multiple providers, keep the trigger idempotent and branch on `NEW.provider` and `NEW.event_type`. Protect your app-owned fulfillment tables with your own RLS policies. Webhook events are processed independently and providers give no ordering guarantee across events. Rows derived from an event are committed before that event is marked `processed`, but rows owned by other events — such as `payments.customer_mappings`, which checkout completion creates — may not exist yet when your trigger fires. Resolve billing subjects from the event payload first and treat lookups into other tables as fallbacks. See the provider guides for subscription fulfillment examples. Older Stripe-only `payments.payment_history` rows are migrated into `payments.transactions` for dashboard and reporting. Triggers on `payment_history` are not rewritten automatically. Move fulfillment logic to `payments.webhook_events`. ## Build with it Pick the Stripe or Razorpay provider module for app code. Review provider-specific Payments API routes and webhook routes. ## Next steps * Read [Stripe Payments](/core-concepts/payments/stripe) if you are using Stripe Checkout or Billing Portal. * Read [Razorpay Payments](/core-concepts/payments/razorpay) if you are using Razorpay Orders or Subscriptions. * Configure provider keys in Dashboard -> Payments -> Settings. * Add app-specific RLS or server-side membership checks for billing subjects. * Add trigger-backed fulfillment from `payments.webhook_events`. # Razorpay Payments Source: https://docs.insforge.dev/core-concepts/payments/razorpay Integrate Razorpay Orders, Subscriptions, manual webhooks, and webhook fulfillment with InsForge. Use the Razorpay integration when you want Razorpay Orders, Razorpay Checkout, Items, Plans, and Subscriptions. Razorpay is not a hosted redirect flow like Stripe Checkout. Your backend creates the provider object, your frontend opens the Razorpay Checkout script, and your backend verifies the returned signature. ## Razorpay model | Razorpay concept | Meaning in InsForge | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | Item | Amount-bearing sellable unit. Mirrored in `payments.razorpay_items`. | | Plan | Recurring subscription definition around an item. Mirrored in `payments.razorpay_plans`. | | Order | One-time payment object. Created through `POST /api/payments/razorpay/{environment}/orders` and mirrored in `payments.razorpay_orders`. | | Payment | Provider payment result. Projected into `payments.transactions` from webhooks and sync. | | Subscription | Razorpay subscription tied to a Plan. Mirrored in `payments.razorpay_subscriptions`. | | Webhook event | Verified provider event in `payments.webhook_events` with `provider = 'razorpay'`. | Razorpay does not have a Stripe Billing Portal equivalent. InsForge exposes backend routes to cancel, pause, and resume subscriptions after checking the caller against `payments.razorpay_subscriptions` RLS `UPDATE` policies. For one-time products, Razorpay Orders can be created with only an amount, currency, and receipt. Still, prefer creating Razorpay Items for sellable products so the catalog is visible in InsForge and Razorpay after sync. Treat Orders as payment attempts, not as your product catalog. ## Setup Configure `test` and `live` Razorpay Key ID and Key Secret in Dashboard -> Payments -> Settings or through the admin API. InsForge generates a webhook signing secret if one does not already exist. Razorpay webhooks must be created manually in the Razorpay Dashboard. Razorpay normal merchant API keys do not support automatic webhook registration. 1. Open Dashboard -> Payments -> Settings -> Webhooks. 2. Copy the Razorpay Webhook URL and Webhook Secret. 3. In the Razorpay Dashboard, create a webhook with the copied URL and secret. 4. Select the events InsForge handles. 5. Save the webhook and complete a test payment to confirm delivery. Razorpay can only deliver webhooks to a public HTTPS URL. Localhost URLs need a public tunnel or a deployed backend. Handled events: * `payment.authorized` * `payment.captured` * `payment.failed` * `order.paid` * `invoice.paid` * `invoice.expired` * `refund.created` * `refund.processed` * `refund.failed` * `subscription.created` * `subscription.activated` * `subscription.charged` * `subscription.updated` * `subscription.cancelled` * `subscription.paused` * `subscription.resumed` * `subscription.halted` * `subscription.completed` * `subscription.expired` ## One-time orders Create an app-owned pending order first. Then create a Razorpay Order with the current InsForge user token. ```typescript theme={null} const { data, error } = await insforge.payments.razorpay.createOrder('test', { amount: 50000, currency: 'INR', receipt: 'order_123', subject: { type: 'team', id: 'team_123' }, customerEmail: 'buyer@example.com', notes: { order_id: 'order_123' } }); if (error) throw error; ``` The SDK wraps `POST /api/payments/razorpay/{environment}/orders`. The response includes `checkoutOptions` with Razorpay Checkout-native fields such as `key` and `order_id`. Load `https://checkout.razorpay.com/v1/checkout.js` in the frontend and pass those options to `new Razorpay(options).open()`. If your fulfillment trigger reads `notes.order_id`, pass `notes: { order_id: ... }` when creating the Order or Subscription. After Razorpay Checkout returns `razorpay_order_id`, `razorpay_payment_id`, and `razorpay_signature`, verify the signature on the backend: ```typescript theme={null} await insforge.payments.razorpay.verifyOrder('test', { orderId: response.razorpay_order_id, paymentId: response.razorpay_payment_id, signature: response.razorpay_signature }); ``` Signature verification proves the immediate Checkout callback came from Razorpay. Durable order fulfillment should still run from verified Razorpay webhook events. ## Subscriptions Create or sync a Razorpay Plan before creating subscriptions. A Plan is not the same thing as a Stripe Price. It is a recurring definition around a Razorpay Item. ```typescript theme={null} const { data, error } = await insforge.payments.razorpay.createSubscription('test', { planId: 'plan_123', totalCount: 12, subject: { type: 'team', id: 'team_123' }, customerEmail: 'buyer@example.com' }); if (error) throw error; ``` The SDK wraps `POST /api/payments/razorpay/{environment}/subscriptions`. The response includes `checkoutOptions.subscription_id`. Open Razorpay Checkout with that subscription ID. After Checkout returns `razorpay_subscription_id`, `razorpay_payment_id`, and `razorpay_signature`, verify the authorization payment: ```typescript theme={null} await insforge.payments.razorpay.verifySubscription('test', { subscriptionId: response.razorpay_subscription_id, paymentId: response.razorpay_payment_id, signature: response.razorpay_signature }); ``` Manage subscriptions through backend routes: ```typescript theme={null} await insforge.payments.razorpay.cancelSubscription('test', 'sub_123', { cancelAtCycleEnd: false }); await insforge.payments.razorpay.pauseSubscription('test', 'sub_123'); await insforge.payments.razorpay.resumeSubscription('test', 'sub_123'); ``` Subscription creation evaluates `INSERT` policies on `payments.razorpay_subscriptions`. Cancel, pause, and resume evaluate `UPDATE` policies on the same table. PostgreSQL also applies `SELECT` policies to rows returned by `INSERT/UPDATE ... RETURNING`, so make the same subject visible to the caller when a policy probe needs to return the row. Grant users only the table access needed for policy checks; provider mutations still run through the backend. ## Webhooks and fulfillment Razorpay's Checkout callback and signature verification are not a replacement for webhooks. Use `payments.webhook_events` for fulfillment triggers. Do not attach fulfillment triggers to provider mirror tables such as `payments.razorpay_subscriptions`; sync and webhook projection can update those rows independently of provider event delivery. ```sql theme={null} CREATE OR REPLACE FUNCTION public.fulfill_razorpay_order() RETURNS TRIGGER AS $$ BEGIN IF NEW.provider = 'razorpay' AND NEW.event_type IN ('payment.captured', 'order.paid', 'invoice.paid') AND NEW.processing_status = 'processed' AND COALESCE( NEW.payload -> 'payload' -> 'payment' -> 'entity' -> 'notes' ->> 'order_id', NEW.payload -> 'payload' -> 'invoice' -> 'entity' -> 'notes' ->> 'order_id' ) IS NOT NULL THEN UPDATE public.orders SET status = 'paid', paid_at = COALESCE(NEW.processed_at, NOW()) WHERE id::text = COALESCE( NEW.payload -> 'payload' -> 'payment' -> 'entity' -> 'notes' ->> 'order_id', NEW.payload -> 'payload' -> 'invoice' -> 'entity' -> 'notes' ->> 'order_id' ) AND status = 'pending'; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER fulfill_razorpay_order_from_webhook AFTER INSERT OR UPDATE ON payments.webhook_events FOR EACH ROW EXECUTE FUNCTION public.fulfill_razorpay_order(); ``` ## Sync and dashboard state Razorpay sync mirrors Items, Plans, Customers, Subscriptions, Invoices, and Payments. Invoices and payments feed the `payments.transactions` dashboard/reporting projection. Transactions include provider reference IDs such as payment, invoice, order, subscription, and refund IDs so you can inspect the source record in the Razorpay Dashboard. Keep user-facing order, credit, and entitlement state in your own app tables. Treat `payments.transactions` as dashboard/reporting state, not as the primary business workflow. ## References * [Razorpay Standard Checkout integration](https://razorpay.com/docs/payments/payment-gateway/web-integration/standard/integration-steps/) * [Razorpay Subscriptions integration](https://razorpay.com/docs/payments/subscriptions/integration-guide/) * [Razorpay Webhooks setup](https://razorpay.com/docs/payments/dashboard/account-settings/webhooks/) * [TypeScript Razorpay payments guide](/sdks/typescript/payments-razorpay) # Stripe Payments Source: https://docs.insforge.dev/core-concepts/payments/stripe Integrate Stripe Checkout, Billing Portal, catalog sync, and webhook fulfillment with InsForge. Use the Stripe integration when you want Stripe-hosted Checkout, Stripe Products and Prices, Stripe Subscriptions, and the hosted Billing Portal. InsForge stores Stripe secret keys server-side, creates Checkout and Billing Portal sessions from your app, automatically manages the Stripe webhook endpoint when your backend is reachable, mirrors Stripe state into the `payments` schema, and records verified webhook events. ## Stripe model | Stripe concept | InsForge table or API | | ------------------------- | ----------------------------------------------------------------------------------------------------------------- | | Product | `payments.stripe_products` | | Price | `payments.stripe_prices` | | Checkout Session | `POST /api/payments/stripe/{environment}/checkout-sessions` and `payments.stripe_checkout_sessions` | | Billing Portal Session | `POST /api/payments/stripe/{environment}/customer-portal-sessions` and `payments.stripe_customer_portal_sessions` | | Subscription | `payments.stripe_subscriptions` and `payments.stripe_subscription_items` | | Customer mapping | `payments.customer_mappings` with `provider = 'stripe'` | | Webhook event | `payments.webhook_events` with `provider = 'stripe'` | | Dashboard transaction row | `payments.transactions` with `provider = 'stripe'` | ## Setup Configure `test` and `live` Stripe secret keys in Dashboard -> Payments -> Settings, the CLI, or the admin API. ```bash theme={null} npx @insforge/cli payments stripe status npx @insforge/cli payments stripe config set --environment test sk_test_xxx npx @insforge/cli payments stripe sync --environment test npx @insforge/cli payments stripe webhooks configure --environment test ``` After a key is connected, InsForge validates the account, stores the key in the secret store, tries to create the managed Stripe webhook endpoint, and runs sync for Products, Prices, Customers, and Subscriptions. ## Checkout Create Checkout Sessions from frontend code with the current InsForge user token. ```typescript theme={null} const { data, error } = await insforge.payments.stripe.createCheckoutSession('test', { mode: 'payment', lineItems: [{ priceId: 'price_123', quantity: 1 }], successUrl: `${window.location.origin}/checkout/success`, cancelUrl: `${window.location.origin}/pricing`, customerEmail: user?.email ?? null, metadata: { order_id: orderId }, idempotencyKey: `order:${orderId}` }); if (error) throw error; if (data?.checkoutSession.url) { window.location.assign(data.checkoutSession.url); } ``` For subscription Checkout, pass a billing subject. The subject is your app-owned billing owner, such as a user, team, workspace, organization, tenant, or group. ```typescript theme={null} const { data, error } = await insforge.payments.stripe.createCheckoutSession('test', { mode: 'subscription', subject: { type: 'team', id: teamId }, lineItems: [{ priceId: 'price_monthly_123', quantity: 1 }], successUrl: `${window.location.origin}/billing/success`, cancelUrl: `${window.location.origin}/billing`, customerEmail: user.email, idempotencyKey: `team:${teamId}:pro-monthly` }); if (error) throw error; if (data?.checkoutSession.url) { window.location.assign(data.checkoutSession.url); } ``` Checkout inserts a row in `payments.stripe_checkout_sessions` using the caller's InsForge token. Add RLS policies so users can only create sessions for subjects they are allowed to bill. PostgreSQL applies `SELECT` policies to rows returned by `INSERT ... RETURNING` and idempotent lookups, so retries also need a matching `SELECT` policy for the same subject and idempotency key. ## Billing Portal Use the hosted Billing Portal for an existing Stripe customer mapping. ```typescript theme={null} const { data, error } = await insforge.payments.stripe.createCustomerPortalSession('test', { subject: { type: 'team', id: teamId }, returnUrl: `${window.location.origin}/billing` }); if (error) { if ('statusCode' in error && error.statusCode === 404) { return; } throw error; } if (data?.customerPortalSession.url) { window.location.assign(data.customerPortalSession.url); } ``` Portal creation requires an authenticated user and an existing `payments.customer_mappings` row for the subject. Protect portal creation with RLS or a server-side membership check so users cannot open billing settings for a team or organization they do not manage. ## Webhooks and fulfillment Stripe webhooks are managed automatically when the backend has a public URL. InsForge listens for the events needed to keep checkout attempts, customers, subscriptions, refunds, and transaction projections current. Stripe also recommends fulfilling Checkout orders from webhooks instead of the success URL. In InsForge, attach fulfillment triggers to `payments.webhook_events`. ```sql theme={null} CREATE OR REPLACE FUNCTION public.fulfill_stripe_order() RETURNS TRIGGER AS $$ BEGIN IF NEW.provider = 'stripe' AND NEW.event_type = 'checkout.session.completed' AND NEW.processing_status = 'processed' AND (NEW.payload -> 'data' -> 'object' -> 'metadata' ->> 'order_id') IS NOT NULL THEN UPDATE public.orders SET status = 'paid', paid_at = COALESCE(NEW.processed_at, NOW()) WHERE id::text = NEW.payload -> 'data' -> 'object' -> 'metadata' ->> 'order_id' AND status = 'pending'; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER fulfill_stripe_order_from_webhook AFTER INSERT OR UPDATE ON payments.webhook_events FOR EACH ROW EXECUTE FUNCTION public.fulfill_stripe_order(); ``` ### Event ordering Webhook events are verified and processed independently. InsForge commits every row derived from an event before marking that event `processed`, but Stripe gives no ordering guarantee across events: `invoice.paid` can be processed before `checkout.session.completed`, so rows created by another event (such as `payments.customer_mappings`) may not exist yet when your trigger fires. For subscription events, resolve the billing subject from the event payload first — InsForge stamps `insforge_subject_type` and `insforge_subject_id` into subscription metadata at checkout, and Stripe snapshots it onto subscription-generated invoices as `parent.subscription_details.metadata`. Check `invoice.metadata` next, then fall back to `payments.customer_mappings` (the same order InsForge uses internally): ```sql theme={null} CREATE OR REPLACE FUNCTION public.grant_subscription_access() RETURNS TRIGGER AS $$ DECLARE v_subject_type TEXT; v_subject_id TEXT; BEGIN IF NEW.provider = 'stripe' AND NEW.event_type = 'invoice.paid' AND NEW.processing_status = 'processed' THEN v_subject_type := COALESCE( NEW.payload -> 'data' -> 'object' -> 'parent' -> 'subscription_details' -> 'metadata' ->> 'insforge_subject_type', NEW.payload -> 'data' -> 'object' -> 'metadata' ->> 'insforge_subject_type' ); v_subject_id := COALESCE( NEW.payload -> 'data' -> 'object' -> 'parent' -> 'subscription_details' -> 'metadata' ->> 'insforge_subject_id', NEW.payload -> 'data' -> 'object' -> 'metadata' ->> 'insforge_subject_id' ); IF v_subject_id IS NULL THEN SELECT m.subject_type, m.subject_id INTO v_subject_type, v_subject_id FROM payments.customer_mappings m WHERE m.provider = NEW.provider AND m.environment = NEW.environment AND m.provider_customer_id = NEW.payload -> 'data' -> 'object' ->> 'customer'; END IF; IF v_subject_id IS NULL THEN RAISE WARNING 'Stripe event % has no resolvable billing subject', NEW.provider_event_id; RETURN NEW; END IF; -- Branch on the subject type sent at checkout; team_id is a UUID here, -- so the type check also guards the cast. IF v_subject_type = 'team' THEN INSERT INTO public.team_entitlements (team_id, plan, active, updated_at) VALUES (v_subject_id::uuid, 'pro', true, NOW()) ON CONFLICT (team_id) DO UPDATE SET plan = EXCLUDED.plan, active = true, updated_at = NOW(); END IF; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER grant_subscription_access_from_stripe_webhook AFTER INSERT OR UPDATE ON payments.webhook_events FOR EACH ROW EXECUTE FUNCTION public.grant_subscription_access(); ``` Never let fulfillment skip silently — log or dead-letter events you cannot resolve so they can be replayed. ## Sync and dashboard state Stripe sync mirrors Products, Prices, Customers, and Subscriptions. Webhooks maintain session, subscription, customer, refund, and transaction state as Stripe emits events. `payments.transactions` is a reporting projection for the dashboard. It gives you provider reference IDs such as payment intent, charge, invoice, checkout session, and refund IDs so you can look up details in the Stripe Dashboard. Keep user-facing order, credit, or entitlement state in your own tables. ## References * [Stripe Checkout fulfillment](https://docs.stripe.com/checkout/fulfillment) * [Stripe Billing Portal Sessions API](https://docs.stripe.com/api/customer_portal/sessions/create) * [TypeScript Stripe payments guide](/sdks/typescript/payments-stripe) # Realtime Source: https://docs.insforge.dev/core-concepts/realtime/overview Send database changes, broadcasts, presence, and webhook fan-out through realtime channels. Use InsForge Realtime when your app needs to update without a page refresh. Clients subscribe to channels such as `order:123` or `chat:room-1`, then receive database changes, broadcasts, and presence updates over WebSockets. Channels can also fan out the same messages to webhook URLs when another service should receive the event. InsForge Realtime dashboard **Need server-side code to run after a database change?** Put that business logic in an [Edge Function](/core-concepts/functions/overview) and invoke it from a database trigger. Use Realtime when the change should be delivered to connected clients or configured webhook endpoints. ```mermaid theme={null} graph TB App[Client application] --> SDK[InsForge SDK] SDK --> Channel[Realtime channel] Database[(Postgres)] --> Trigger[Database trigger] Trigger --> Channel Channel --> WebSocket[WebSocket subscribers] Channel --> Presence[Presence state] Channel --> Webhook[Webhook URLs] Channel --> History[(Message history)] Auth[Auth token and RLS] --> Channel style App fill:#1e293b,stroke:#475569,color:#e2e8f0 style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style Channel fill:#166534,stroke:#22c55e,color:#dcfce7 style Database fill:#0e7490,stroke:#06b6d4,color:#cffafe style Trigger fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe style WebSocket fill:#c2410c,stroke:#fb923c,color:#fed7aa style Presence fill:#c2410c,stroke:#fb923c,color:#fed7aa style Webhook fill:#c2410c,stroke:#fb923c,color:#fed7aa style History fill:#0e7490,stroke:#06b6d4,color:#cffafe style Auth fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe ``` ## Features ### Channels Channels are named topics that clients can join. Use exact names for shared rooms, or patterns like `order:%` when every record needs its own live stream. ### Database changes Use database changes when a table write should become a live app event. Create a trigger on the table you want to watch. In its trigger function, call the predefined `realtime.publish(channel, event, payload)` function to decide which channel receives the message, which event name clients handle, and what payload they receive. For a channel pattern such as `order:%`, a trigger can publish one event per order: ```sql theme={null} CREATE OR REPLACE FUNCTION public.notify_order_status() RETURNS TRIGGER AS $$ BEGIN PERFORM realtime.publish( 'order:' || NEW.id::text, 'status_changed', jsonb_build_object( 'id', NEW.id, 'status', NEW.status, 'updatedAt', NEW.updated_at ) ); RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER order_status_realtime AFTER UPDATE OF status ON public.orders FOR EACH ROW WHEN (OLD.status IS DISTINCT FROM NEW.status) EXECUTE FUNCTION public.notify_order_status(); ``` Then subscribe from the app with the SDK: ```typescript theme={null} const channel = `order:${orderId}`; await insforge.realtime.connect(); const subscription = await insforge.realtime.subscribe(channel); if (!subscription.ok) { throw new Error(subscription.error.message); } insforge.realtime.on('status_changed', (message) => { renderOrderStatus(message.status); }); ``` ### Client broadcasts Clients can publish messages to channels they have already joined. Use this for chat, typing indicators, cursors, collaborative editing signals, and other user-to-user updates that do not need to start from a database write. ```typescript theme={null} await insforge.realtime.publish(`chat:${roomId}`, 'typing', { userId, isTyping: true }); ``` ### Webhooks Attach webhook URLs to a channel when another service should receive each message. InsForge posts the event payload to every configured URL, includes headers for the event name, channel, and message ID, retries transient network failures, and records webhook delivery counts in message history. ### Presence Presence tracks who is online in a channel. Clients receive the current member snapshot when they subscribe, then `presence:join` and `presence:leave` events as members come and go. Store durable room membership, roles, and permissions in your own tables; presence is only online state. ```typescript theme={null} const response = await insforge.realtime.subscribe(`chat:${roomId}`); if (response.ok) { renderOnlineMembers(response.presence.members); } insforge.realtime.on('presence:join', (message) => { addOnlineMember(message.member); }); insforge.realtime.on('presence:leave', (message) => { removeOnlineMember(message.member.presenceId); }); ``` ### Row-level security Realtime can be open while prototyping, then locked down with Postgres RLS. Use `SELECT` policies on `realtime.channels` to control who can subscribe, and `INSERT` policies on `realtime.messages` to control who can publish from a client. This policy lets authenticated users subscribe to `order:` channels only when the order belongs to them: ```sql theme={null} ALTER TABLE realtime.channels ENABLE ROW LEVEL SECURITY; CREATE POLICY "users_subscribe_own_orders" ON realtime.channels FOR SELECT TO authenticated USING ( pattern = 'order:%' AND EXISTS ( SELECT 1 FROM public.orders WHERE id = NULLIF(split_part(realtime.channel_name(), ':', 2), '')::uuid AND user_id = auth.uid() ) ); ``` Use `realtime.channel_name()` in subscribe policies because clients subscribe to resolved channels such as `order:123`, while `realtime.channels` stores patterns such as `order:%`. ### Message history Every delivered event is recorded with WebSocket and webhook delivery counts. The dashboard can inspect recent messages, delivery stats, and retention settings when you need to debug live behavior. ## Build with it Subscribe to channels, publish events, and track presence from Node, browser, and edge. Native Swift realtime client for iOS and macOS. Coroutines-first realtime client for Android and JVM. Use the raw Socket.IO contract from any language. ## Next steps * Set up the [CLI](/quickstart) to link your project. * Create channels in the Realtime dashboard. * Use the [TypeScript SDK reference](/sdks/typescript/realtime) for client subscriptions. * Add webhook URLs to a channel when another service needs the same event stream. # Sites Source: https://docs.insforge.dev/core-concepts/sites/overview Deploy frontend apps from your project, powered by Vercel. Use InsForge Sites to ship the browser-facing app that belongs to your project. The InsForge CLI uploads your frontend source through InsForge, which creates a Vercel production deployment. The dashboard tracks the URL, status, deployment history, environment variables, and domains. InsForge Sites dashboard **Need to deploy a container or backend service?** Use [Compute](/core-concepts/compute/overview) for workers, queues, WebSocket servers, and long-running services. Sites are for frontend websites and framework builds that produce a hosted web app. ```mermaid theme={null} flowchart TB CLI[InsForge CLI] --> API[InsForge deployment API] Dashboard[Dashboard] --> API API --> Source[Frontend source upload] API --> Config[Environment variables and domains] Source --> Vercel[Vercel production build] Config --> Vercel Vercel --> App[Frontend app] App --> URL[Public URL] App --> Status[Status and deployment history] style CLI fill:#1e293b,stroke:#475569,color:#e2e8f0 style Dashboard fill:#1e293b,stroke:#475569,color:#e2e8f0 style API fill:#166534,stroke:#22c55e,color:#dcfce7 style Source fill:#0e7490,stroke:#06b6d4,color:#cffafe style Config fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe style Vercel fill:#c2410c,stroke:#fb923c,color:#fed7aa style App fill:#166534,stroke:#22c55e,color:#dcfce7 style URL fill:#166534,stroke:#22c55e,color:#dcfce7 style Status fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe ``` ## Features ### CLI deploys Deploy from your app's source directory. The CLI uploads the source tree, skips local-only files such as `node_modules`, `.git`, build output, and `.env` files, then starts the Vercel build through InsForge. ```bash theme={null} npx @insforge/cli deployments deploy ./frontend ``` ### Framework builds Deploy React, Vue, Svelte, Next.js, static sites, and other frontend projects. InsForge sends the source files to Vercel, where framework detection and project files such as `package.json` and `vercel.json` decide how the app builds. ### Environment variables Manage provider environment variables from the dashboard. Use public prefixes such as `VITE_` or `NEXT_PUBLIC_` only for values that are safe to expose in browser code. ```bash theme={null} npx @insforge/cli deployments env list npx @insforge/cli deployments env set VITE_INSFORGE_URL https://your-project.region.insforge.app npx @insforge/cli deployments env set VITE_INSFORGE_ANON_KEY ik_xxx ``` ### Deployment history Review previous runs, sync Vercel status, inspect metadata, and cancel in-progress deployments from the Deployment Logs page. ```bash theme={null} npx @insforge/cli deployments list npx @insforge/cli deployments status deployment_123 --sync npx @insforge/cli deployments cancel deployment_123 ``` ### Domains Every ready deployment gets a default URL at `https://.insforge.site`. You can also set an InsForge-managed slug at `https://.insforge.site`. For a custom domain, add the domain in the dashboard and configure the DNS record it returns, usually a CNAME for subdomains. ## Deploy with it Connect your project and run InsForge CLI commands from your app directory. ## Next steps * Set up the [CLI](/quickstart) and connect your project. * Add browser-safe environment variables from the dashboard or with `npx @insforge/cli deployments env set`. * Run `npx @insforge/cli deployments deploy ./frontend`. # Storage Source: https://docs.insforge.dev/core-concepts/storage/overview Store and serve large binary files. Use InsForge to store and serve large binary files: images, videos, PDFs, audio, backups, anything you would not put in a database row. Every project gets an S3-compatible bucket. Files are served behind signed URLs, access policies follow the same row-level security model as the database, and the S3 API works with rclone, the AWS CLI, Terraform, and SDKs in any language. InsForge dashboard storage browser showing a photos bucket and the file listing **Looking for structured data?** Use [Database](/core-concepts/database/overview) for rows, relations, and queries. Storage holds objects; the database holds rows. Keep file metadata (owner, name, size, content type) in a database table and the bytes in storage. ```mermaid theme={null} graph TB Client[Client Application] --> SDK[InsForge SDK] SDK --> StorageAPI[Storage API] StorageAPI --> S3[AWS S3] StorageAPI --> DB[(PostgreSQL)] DB --> Metadata[File Metadata] DB --> Buckets[Bucket Configuration] S3 --> DirectUpload[Presigned URLs] S3 --> SecureAccess[IAM Policies] style Client fill:#1e293b,stroke:#475569,color:#e2e8f0 style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style StorageAPI fill:#166534,stroke:#22c55e,color:#dcfce7 style S3 fill:#ea580c,stroke:#f97316,color:#fed7aa style DB fill:#0e7490,stroke:#06b6d4,color:#cffafe style Metadata fill:#0e7490,stroke:#22d3ee,color:#cffafe style Buckets fill:#0e7490,stroke:#22d3ee,color:#cffafe style DirectUpload fill:#ea580c,stroke:#fb923c,color:#fed7aa style SecureAccess fill:#ea580c,stroke:#fb923c,color:#fed7aa ``` ## Features ### S3-compatible API Point any S3 client at your project's bucket. Native AWS credentials, native multipart uploads, native presigned URLs. See [S3 compatibility](/core-concepts/storage/s3-compatibility). ### Signed URLs Generate time-limited URLs to share private objects without exposing your credentials. The SDK and REST API both issue signed URLs for upload and download. ### Row-level security Storage policies read the same auth JWT as database queries. The same user who can `SELECT` a row can `GET` the file the row references, so you never maintain a separate set of storage permissions. ### Buckets Group objects into buckets with separate access policies. Public buckets serve files directly over HTTPS; private buckets require a signed URL or an authenticated request. ### Direct uploads Browser and mobile clients upload straight to storage with a presigned URL. The backend never proxies bytes. ## Concepts Point any S3 client at your project's bucket with native credentials. ## Build with it Upload, download, list, and manage objects from Node, browser, and edge. Native Swift storage client for iOS and macOS. Coroutines-first storage client for Android and JVM. Plain HTTP storage endpoints, callable from any language. ## Next steps * Set up the [CLI](/quickstart) to link your project (the recommended path). * Browse the [TypeScript SDK reference](/sdks/typescript/storage) for uploads and downloads. # S3-compatible gateway Source: https://docs.insforge.dev/core-concepts/storage/s3-compatibility Use any AWS SigV4 client against InsForge Storage InsForge Storage speaks the [AWS S3 protocol](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html) at `/storage/v1/s3`. Cloud projects only. ## Concepts Long-lived access keys signed with SigV4. Project-admin scope across every bucket, path-style URLs only. S3 uploads appear immediately in the REST API and Dashboard. Generate keys in **Storage → Settings → S3 Configuration**. ## Usage Fetch endpoint and region from the Dashboard or `GET /api/storage/s3/config`. ```ini theme={null} # ~/.aws/credentials [insforge] aws_access_key_id = your_access_key_id aws_secret_access_key = your_secret_access_key # ~/.aws/config [profile insforge] region = us-east-2 endpoint_url = https://project_ref.region.insforge.app/storage/v1/s3 s3 = addressing_style = path ``` ```bash theme={null} aws --profile insforge s3 cp ./photo.jpg s3://my-bucket/photo.jpg aws --profile insforge s3 sync ./dist s3://my-bucket/dist ``` In code, set `forcePathStyle: true` and point `endpoint` at `/storage/v1/s3`. ```ts theme={null} import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; const client = new S3Client({ forcePathStyle: true, region: 'us-east-2', endpoint: 'https://project_ref.region.insforge.app/storage/v1/s3', credentials: { accessKeyId: '...', secretAccessKey: '...' }, }); await client.send(new PutObjectCommand({ Bucket: 'my-bucket', Key: 'hello.txt', Body: 'hello' })); ``` ## Limits `PutObject` caps at 5 GB, multipart at 5 TB. 50 keys per project, 15-minute clock skew. Secret keys show once on creation. Not supported: presigned URLs (use `POST /api/storage/buckets/:bucket/upload-strategy`), session tokens, virtual-hosted URLs. Versioning, SSE-C/KMS, ACLs, object lock, tagging, lifecycle, and CORS return `501 NotImplemented`. ## More resources * [Storage overview](/core-concepts/storage/overview) for the gateway internals. * [TypeScript storage SDK](/sdks/typescript/storage) for browser uploads. * [AWS SigV4 reference](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-signing.html) for signing details. # Deploy to aws ec2 Source: https://docs.insforge.dev/deployment/deploy-to-aws-ec2 # Deploy InsForge to AWS EC2 This guide will walk you through deploying InsForge on an AWS EC2 instance using Docker Compose. This cloud walkthrough is community-maintained and can lag the latest InsForge release. The canonical, always-current setup is the `deploy/docker-compose/` directory in the [InsForge repo](https://github.com/InsForge/InsForge). ## 📋 Prerequisites * AWS Account with EC2 access * Basic knowledge of SSH and command-line operations * Domain name (optional, for custom domain setup) ## 🚀 Deployment Steps ### 1. Create and Configure EC2 Instance #### 1.1 Launch EC2 Instance 1. **Log into AWS Console** and navigate to EC2 Dashboard 2. **Click "Launch Instance"** 3. **Configure Instance:** * **Name**: `insforge-server` (or your preferred name) * **AMI**: Ubuntu Server 24.04 LTS (HVM), SSD Volume Type * **Instance Type**: `t3.medium` or larger (minimum 2 vCPU, 4 GB RAM) * For production: `t3.large` (2 vCPU, 8 GB RAM) recommended * For testing: `t3.small` (2 vCPU, 2 GB RAM) minimum * **Key Pair**: Create new or select existing key pair (download and save the `.pem` file) * **Storage**: 30 GB gp3 (minimum 20 GB recommended) #### 1.2 Configure Security Group Create or configure security group with the following inbound rules: | Type | Protocol | Port Range | Source | Description | | ---------- | -------- | ---------- | --------- | --------------------- | | SSH | TCP | 22 | My IP | SSH access | | HTTP | TCP | 80 | 0.0.0.0/0 | HTTP access | | HTTPS | TCP | 443 | 0.0.0.0/0 | HTTPS access | | Custom TCP | TCP | 7130 | 0.0.0.0/0 | Dashboard + API | | Custom TCP | TCP | 5432 | 0.0.0.0/0 | PostgreSQL (optional) | > ⚠️ **Security Note**: For production, restrict PostgreSQL (5432) to specific IP addresses or remove external access entirely. Consider using a reverse proxy (nginx) and exposing only ports 80/443. #### 1.3 Allocate Elastic IP (Recommended) 1. Navigate to **Elastic IPs** in EC2 Dashboard 2. Click **Allocate Elastic IP address** 3. Associate the Elastic IP with your instance This ensures your instance keeps the same IP address even after restarts. ### 2. Connect to Your EC2 Instance ```bash theme={null} # Set correct permissions for your key file chmod 400 your-key-pair.pem # Connect via SSH ssh -i your-key-pair.pem ubuntu@your-ec2-public-ip ``` ### 3. Install Dependencies #### 3.1 Update System Packages ```bash theme={null} sudo apt update && sudo apt upgrade -y ``` #### 3.2 Install Docker ```text theme={null} Follow the instructions of the link below to install and verify docker on your new ubuntu ec2 instance: https://docs.docker.com/engine/install/ubuntu/ ``` #### 3.3 Add Your User to Docker Group After installing Docker, you need to add your user to the `docker` group to run Docker commands without `sudo`: ```bash theme={null} # Add your user to the docker group sudo usermod -aG docker $USER # Apply the group changes newgrp docker ``` **Verify it works:** ```bash theme={null} # This should now work without sudo docker ps ``` > 💡 **Note**: If `docker ps` doesn't work immediately, log out and log back in via SSH, then try again. > ⚠️ **Security Note**: Adding a user to the `docker` group grants them root-equivalent privileges on the system. This is acceptable for single-user environments like your EC2 instance, but be cautious on shared systems. #### 3.4 Install Git ```bash theme={null} sudo apt install git -y ``` ### 4. Deploy InsForge #### 4.1 Clone Repository ```bash theme={null} cd ~ git clone https://github.com/insforge/insforge.git cd insforge/deploy/docker-compose ``` #### 4.2 Create Environment Configuration Copy the example template to create your `.env` file: ```bash theme={null} cp .env.example .env nano .env ``` The full template lives at `deploy/docker-compose/.env.example`. These are the variables you must set: ```env theme={null} # Required JWT_SECRET=your-secret-key-here-must-be-32-char-or-above ROOT_ADMIN_USERNAME=admin ROOT_ADMIN_PASSWORD=change-this-password POSTGRES_PASSWORD=change-this-password # Optional: falls back to JWT_SECRET if left blank ENCRYPTION_KEY= # Optional: enables AI features OPENROUTER_API_KEY= # Optional: enables site deployments VERCEL_TOKEN= VERCEL_TEAM_ID= VERCEL_PROJECT_ID= # Optional: OAuth providers GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= ``` The `.env.example` template carries the remaining variables and their defaults, so editing the copied file is enough. **Generate Secure Secrets:** ```bash theme={null} # Generate JWT_SECRET (32+ characters) openssl rand -base64 32 # Generate ENCRYPTION_KEY (must be exactly 32 characters) openssl rand -base64 24 ``` > 💡 **Important**: Save these secrets securely. You'll need them if you ever migrate or restore your instance. #### 4.3 Start InsForge Services ```bash theme={null} # Pull Docker images and start services docker compose up -d # View logs to ensure everything started correctly docker compose logs -f ``` Press `Ctrl+C` to exit log view. #### 4.4 Verify Services ```bash theme={null} # Check running containers docker compose ps # You should see 4 running services: # - postgres # - postgrest # - insforge # - deno ``` ### 5. Access Your InsForge Instance #### 5.1 Test Backend API ```bash theme={null} curl http://your-ec2-ip:7130/api/health ``` Expected response: ```json theme={null} { "status": "ok", "version": "2.1.7", "service": "Insforge OSS Backend", "timestamp": "2025-10-17T..." } ``` #### 5.2 Access Dashboard Open your browser and navigate to: ```text theme={null} http://your-ec2-ip:7130 ``` Log in with the `ROOT_ADMIN_USERNAME` and `ROOT_ADMIN_PASSWORD` you set in `.env`. ### 6. Configure Domain (Optional but Recommended) #### 6.1 Update DNS Records Add DNS A records pointing to your EC2 Elastic IP: ```text theme={null} api.yourdomain.com → your-ec2-ip app.yourdomain.com → your-ec2-ip ``` #### 6.2 Install Nginx Reverse Proxy ```bash theme={null} sudo apt install nginx -y ``` Create Nginx configuration: ```bash theme={null} sudo nano /etc/nginx/sites-available/insforge ``` Add the following configuration: ```nginx theme={null} # Backend API server { listen 80; server_name api.yourdomain.com; location / { proxy_pass http://localhost:7130; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } } # Dashboard (served by the backend on the same port as the API) server { listen 80; server_name app.yourdomain.com; location / { proxy_pass http://localhost:7130; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } } ``` Enable the configuration: ```bash theme={null} sudo ln -s /etc/nginx/sites-available/insforge /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx ``` #### 6.3 Install SSL Certificate (Recommended) ```bash theme={null} # Install Certbot sudo apt install certbot python3-certbot-nginx -y # Obtain SSL certificates sudo certbot --nginx -d api.yourdomain.com -d app.yourdomain.com # Follow the prompts to complete setup ``` Update your `.env` file with HTTPS URLs: ```bash theme={null} cd ~/insforge/deploy/docker-compose nano .env ``` Change: ```env theme={null} API_BASE_URL=https://api.yourdomain.com VITE_API_BASE_URL=https://api.yourdomain.com ``` Restart services: ```bash theme={null} docker compose down docker compose up -d ``` ## 🔧 Management & Maintenance ### View Logs ```bash theme={null} # All services docker compose logs -f # Specific service docker compose logs -f insforge docker compose logs -f postgres docker compose logs -f deno ``` ### Stop Services ```bash theme={null} docker compose down ``` ### Restart Services ```bash theme={null} docker compose restart ``` ### Update InsForge InsForge ships prebuilt images, so an update is a pull and restart. Run this from `~/insforge/deploy/docker-compose`: ```bash theme={null} cd ~/insforge/deploy/docker-compose git pull origin main docker compose pull && docker compose up -d ``` ### Backup Database Run these from `~/insforge/deploy/docker-compose`: ```bash theme={null} # Create backup docker compose exec postgres pg_dump -U postgres insforge > backup_$(date +%Y%m%d_%H%M%S).sql # Restore from backup cat backup_file.sql | docker compose exec -T postgres psql -U postgres -d insforge ``` ### Monitor Resources ```bash theme={null} # Check disk usage df -h # Check memory usage free -h # Check Docker stats docker stats ``` ## 🐛 Troubleshooting ### Services Won't Start ```bash theme={null} # Check logs for errors docker compose logs # Check disk space df -h # Check memory free -h # Restart Docker daemon sudo systemctl restart docker docker compose up -d ``` ### Cannot Connect to Database ```bash theme={null} # Check if PostgreSQL is running docker compose ps postgres # Check PostgreSQL logs docker compose logs postgres # Verify credentials in .env file cat .env | grep POSTGRES ``` ### Port Already in Use ```bash theme={null} # Check what's using the port sudo netstat -tulpn | grep :7130 # Kill the process or change port in docker-compose.yml ``` ### Out of Memory Consider upgrading to a larger instance type: ```text theme={null} - Current: t3.medium (4 GB RAM) - Upgrade to: t3.large (8 GB RAM) ``` ### SSL Certificate Issues ```bash theme={null} # Renew certificates sudo certbot renew # Test renewal sudo certbot renew --dry-run ``` ## 📊 Performance Optimization ### For Production Workloads 1. **Upgrade Instance Type**: Use `t3.large` or `t3.xlarge` 2. **Enable Auto-scaling**: Set up Application Load Balancer with auto-scaling groups 3. **Use RDS**: Migrate from containerized PostgreSQL to AWS RDS for better reliability 4. **Enable CloudWatch**: Monitor metrics and set up alarms 5. **Configure Backups**: Set up automated daily backups 6. **Use S3 for Storage**: Configure S3 bucket for file uploads instead of local storage ### Database Optimization ```conf theme={null} # Increase PostgreSQL shared_buffers (edit postgresql.conf in deploy/docker-init/db/) # Recommended: 25% of available RAM shared_buffers = 1GB effective_cache_size = 3GB ``` ## 🔒 Security Best Practices 1. **Change Default Passwords**: Update admin and database passwords 2. **Enable Firewall**: Use AWS Security Groups effectively 3. **Regular Updates**: Keep system and Docker images updated 4. **SSL/TLS**: Always use HTTPS in production 5. **Backup Regularly**: Automate database backups 6. **Monitor Logs**: Set up log monitoring and alerts 7. **Limit SSH Access**: Restrict SSH to specific IP addresses 8. **Use IAM Roles**: Instead of AWS access keys where possible ## 🆘 Support & Resources * **Documentation**: [https://docs.insforge.dev](https://docs.insforge.dev) * **GitHub Issues**: [https://github.com/insforge/insforge/issues](https://github.com/insforge/insforge/issues) * **Discord Community**: [https://discord.com/invite/MPxwj5xVvW](https://discord.com/invite/MPxwj5xVvW) ## 📝 Cost Estimation **Monthly AWS Costs (approximate):** | Component | Type | Monthly Cost | | --------------- | ----------------- | ---------------- | | EC2 Instance | t3.medium | \~\$30 | | Storage (30 GB) | EBS gp3 | \~\$3 | | Elastic IP | (if running 24/7) | \$0 | | Data Transfer | First 100GB free | Variable | | **Total** | | **\~\$33/month** | > 💡 **Cost Optimization**: Use AWS Savings Plans or Reserved Instances for long-term deployments to save up to 70%. *** **Congratulations! 🎉** Your InsForge instance is now running on AWS EC2. You can start building applications by connecting AI agents to your backend platform. For other production deployment strategies, check out our [deployment guides](./README.md). # Deploy to azure virtual machines Source: https://docs.insforge.dev/deployment/deploy-to-azure-virtual-machines # 📖 Deploying InsForge to Azure Virtual Machines (Extended Guide) This guide provides comprehensive, step-by-step instructions for deploying, managing, and securing InsForge on an Azure Virtual Machine (VM) using Docker Compose. This cloud walkthrough is community-maintained and can lag the latest InsForge release. The canonical, always-current setup is the `deploy/docker-compose/` directory in the [InsForge repo](https://github.com/InsForge/InsForge). ## Prerequisites * An active **Azure account**. * An **SSH client** to connect to the virtual machine. * Basic familiarity with the **Linux command line**. *** ## Step 1: 🖥️ Create an Azure Virtual Machine 1. **Log in to the [Azure Portal](https://portal.azure.com/)** and navigate to **Virtual machines**. 2. Click **+ Create** > **Azure virtual machine**. 3. **Basics Tab:** * **Resource Group:** Create a new one (e.g., `insforge-rg`). * **Virtual machine name:** `insforge-vm`. * **Image:** **Ubuntu Server 22.04 LTS** or newer. * **Size:** `Standard_B2s` (2 vCPUs, 4 GiB memory) is a good start. For production, consider `Standard_B4ms` (4 vCPUs, 16 GiB memory). * **Authentication type:** **SSH public key**. * **SSH public key source:** **Generate new key pair**. Name it `insforge-key`. 4. **Networking Tab:** * In the **Network security group** section, click **Create new**. * Add the following **inbound port rules** to allow traffic: * `22` (SSH) * `80` (HTTP for Nginx) * `443` (HTTPS for Nginx/SSL) * `7130` (InsForge API and dashboard) 5. **Review and Create:** * Click **Review + create**, then **Create**. * When prompted, **Download private key and create resource**. Save the `.pem` file securely. * Once deployed, find and copy your VM's **Public IP address**. *** ## Step 2: ⚙️ Connect and Set Up the Server 1. **Connect via SSH:** Open your terminal, give your key the correct permissions, and connect to the VM. ```bash theme={null} chmod 400 /path/to/your/insforge-key.pem ssh -i /path/to/your/insforge-key.pem azureuser@ ``` 2. **Update System Packages:** ```bash theme={null} sudo apt update && sudo apt upgrade -y ``` 3. **Install Docker:** Follow the official, up-to-date instructions on the Docker website to install Docker Engine on Ubuntu: **[https://docs.docker.com/engine/install/ubuntu/](https://docs.docker.com/engine/install/ubuntu/)** 4. **Add Your User to the Docker Group:** This step allows you to run Docker commands without `sudo`. ```bash theme={null} # Add your user to the docker group sudo usermod -aG docker $USER # Apply the group changes newgrp docker ``` Verify it works. This command should now run without `sudo`: ```bash theme={null} docker ps ``` > 💡 **Note:** If `docker ps` doesn't work, log out of your SSH session and log back in, then try again. > > ⚠️ **Security Note:** Adding a user to the `docker` group grants them root-equivalent privileges. This is acceptable for a single-user VM but be cautious on shared systems. 5. **Install Git:** ```bash theme={null} sudo apt install git -y ``` *** ## Step 3: 🚀 Deploy InsForge 1. **Clone the Repository:** Navigate to your home directory and clone the InsForge project. ```bash theme={null} cd ~ git clone https://github.com/InsForge/InsForge.git cd InsForge/deploy/docker-compose ``` 2. **Create Environment Configuration:** Create your `.env` file from the example and open it for editing. ```bash theme={null} cp .env.example .env nano .env ``` `.env.example` lists every supported variable with comments. For a basic deployment you only need to set a few. Set these values and update the API URLs to your VM's public IP: ```ini theme={null} # Required JWT_SECRET=your-secret-key-here-must-be-32-char-or-above ROOT_ADMIN_USERNAME=admin ROOT_ADMIN_PASSWORD=change-this-password POSTGRES_PASSWORD=change-this-password # API URLs (replace with your VM public IP or domain) API_BASE_URL=http://:7130 VITE_API_BASE_URL=http://:7130 # Optional # ENCRYPTION_KEY falls back to JWT_SECRET if left empty ENCRYPTION_KEY= # OPENROUTER_API_KEY= # VERCEL_TOKEN= # GOOGLE_CLIENT_ID= ``` The rest of `.env.example` covers optional features (OpenRouter, Vercel deployments, OAuth providers). Leave those blank unless you need them. > **Generate a Secure JWT Secret:** Run this on your VM and paste the result into `JWT_SECRET`: > > ```bash theme={null} > openssl rand -base64 32 > ``` 3. **Start InsForge Services:** Pull the Docker images and start all services in the background. ```bash theme={null} docker compose up -d ``` 4. **Verify Services:** Check that all four containers are running. ```bash theme={null} docker compose ps ``` You should see the `postgres`, `postgrest`, `insforge`, and `deno` services running. *** ## Step 4: 🔑 Access Your InsForge Instance 1. **Test Backend API:** Use `curl` to check the health endpoint. ```bash theme={null} curl http://:7130/api/health ``` You should see a response like: `{"status":"ok", ...}` 2. **Access Dashboard:** Open your browser and navigate to: `http://:7130` Log in with the `ROOT_ADMIN_USERNAME` and `ROOT_ADMIN_PASSWORD` you set in your `.env` file. *** ## Step 5: 🌐 Configure Domain (Optional but Recommended) 1. **Update DNS Records:** In your domain provider's DNS settings, add two **A records** pointing to your VM's Public IP address: * `api.yourdomain.com` → `` * `app.yourdomain.com` → `` 2. **Install and Configure Nginx as a Reverse Proxy:** ```bash theme={null} sudo apt install nginx -y sudo nano /etc/nginx/sites-available/insforge ``` Paste the following configuration: ```nginx theme={null} # Backend API server { listen 80; server_name api.yourdomain.com; location / { proxy_pass http://localhost:7130; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } # Frontend Dashboard (served by the same port as the API) server { listen 80; server_name app.yourdomain.com; location / { proxy_pass http://localhost:7130; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; } } ``` Enable the configuration and reload Nginx: ```bash theme={null} sudo ln -s /etc/nginx/sites-available/insforge /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx ``` 3. **Install SSL Certificate with Certbot:** ```bash theme={null} # Install Certbot for Nginx sudo apt install certbot python3-certbot-nginx -y # Obtain SSL certificates and configure Nginx automatically sudo certbot --nginx -d api.yourdomain.com -d app.yourdomain.com ``` Follow the prompts. Certbot will handle the rest. 4. **Update `.env` with HTTPS URLs:** Edit your `.env` file and update the URLs. ```bash theme={null} cd ~/InsForge nano .env ``` Change the URLs to `https`: ```ini theme={null} API_BASE_URL=https://api.yourdomain.com VITE_API_BASE_URL=https://api.yourdomain.com ``` Restart the services for the changes to take effect: ```bash theme={null} docker compose down && docker compose up -d ``` *** ## 🔧 Management & Maintenance * **View Logs:** `docker compose logs -f` (all services) or `docker compose logs -f insforge` (specific service). * **Stop Services:** `docker compose down` * **Restart Services:** `docker compose restart` * **Update InsForge:** Run these from `~/InsForge/deploy/docker-compose`. The images are prebuilt, so pull the latest tags instead of rebuilding. ```bash theme={null} cd ~/InsForge/deploy/docker-compose git -C ~/InsForge pull origin main docker compose pull && docker compose up -d ``` * **Backup Database:** Run from `~/InsForge/deploy/docker-compose`. ```bash theme={null} docker compose exec postgres pg_dump -U postgres insforge > backup_$(date +%Y%m%d_%H%M%S).sql ``` ## 🐛 Troubleshooting * **Services Won't Start:** Check `docker compose logs` for errors. Ensure you have enough disk space (`df -h`) and memory (`free -h`). * **Port Already in Use:** Check which process is using the port with `sudo netstat -tulpn | grep :7130`. * **Out of Memory:** Consider upgrading your Azure VM to a size with more RAM. ## 📊 Cost Estimation > **Disclaimer:** Prices are estimates based on Pay-As-You-Go rates in a common region (e.g., East US) and can vary. Always check the official [Azure Pricing Calculator](https://azure.microsoft.com/en-us/pricing/calculator/) for the most accurate information. On Azure, you pay for the VM's resources (CPU, RAM, Storage), which are shared by all the Docker services you run on it. ### Free Tier (for Testing) * **Cost:** **\~\$0/month** for the first 12 months. * **Resources:** Azure provides a free tier that includes 750 hours/month of a `B1s` burstable VM. * **Limitations:** This VM has very limited resources (1 vCPU, 1 GiB RAM) and may run slowly. It's suitable only for basic testing and familiarization, not for active development or production. ### Starter Setup (for Development & Small Projects) * **Cost:** **\~$30 - $40/month** * **Resources:** This estimate is for a `Standard_B2s` VM (2 vCPU, 4 GiB RAM) running all the InsForge Docker containers. * **Breakdown:** The cost primarily consists of the VM compute hours. It also includes the OS disk storage and a static public IP address. This single VM runs your database, backend, Deno, and all other services. ### Production Setup (for Scalability & Reliability) For production, you can choose between an all-in-one, larger VM or a more robust setup using managed services. * **Option A: All-in-One Larger VM** * **Cost:** **\~$150 - $170/month** * **Resources:** A more powerful `Standard_B4ms` VM (4 vCPU, 16 GiB RAM) to handle higher traffic and all services. * **Pros:** Simple to manage, consolidated cost. * **Cons:** Database and application share resources, which can create performance bottlenecks. Scaling requires upgrading the entire VM. * **Option B: Managed Services (Recommended for Production)** * **Cost:** **\~\$120+/month** (highly variable) * **Resources:** * **Application VM:** A `Standard_B2s` VM for the app services (InsForge, PostgREST, Deno). `(~$30/month)` * **Managed Database:** Use **Azure Database for PostgreSQL** for reliability, automated backups, and scaling. `(~$40+/month for a starter tier)` * **Pros:** Highly reliable and scalable. Database performance is isolated and guaranteed. Managed backups and security. * **Cons:** More complex setup, costs are distributed across multiple services. ## 🔒 Security Best Practices * **Change Default Passwords:** Always update admin and database passwords. * **Enable Firewall:** Use Azure **Network Security Groups (NSGs)** to restrict access to necessary ports and IP addresses. * **Regular Updates:** Periodically run `sudo apt update && sudo apt upgrade -y` and update InsForge. * **Backup Regularly:** Automate database and configuration backups. # Deploy to google cloud compute engine Source: https://docs.insforge.dev/deployment/deploy-to-google-cloud-compute-engine # Deploy InsForge to Google Cloud Compute Engine This guide will walk you through deploying InsForge on Google Cloud Compute Engine using Docker Compose. This cloud walkthrough is community-maintained and can lag the latest InsForge release. The canonical, always-current setup is the `deploy/docker-compose/` directory in the [InsForge repo](https://github.com/InsForge/InsForge). ## 📋 Prerequisites * Google Cloud Account with billing enabled * Basic knowledge of SSH and command-line operations * Domain name (optional, for custom domain setup) ## 🚀 Deployment Steps ### 1. Create and Configure Compute Engine Instance #### 1.1 Create Google Cloud Project 1. **Log into Google Cloud Console** at [console.cloud.google.com](https://console.cloud.google.com) 2. **Click "Select a project"** in the top navigation bar 3. **Click "New Project"** 4. **Enter project name** (e.g., `insforge-deployment`) 5. **Click "Create"** 6. **Wait for project creation to complete** #### 1.2 Enable Required APIs 1. In your project, navigate to **APIs & Services** → **Library** 2. Search for and enable these APIs: * **Compute Engine API** * **Cloud Storage API** (if using for backups) * **Cloud SQL Admin API** (if using Cloud SQL) #### 1.3 Create Compute Engine Instance 1. Navigate to **Compute Engine** → **VM instances** 2. Click **"Create Instance"** 3. Configure your instance: * **Name**: `insforge-server` (or your preferred name) * **Region**: Choose a region close to your users * **Zone**: Select an availability zone (e.g., us-central1-a) * **Machine configuration**: * **Series**: N2 or E2 * **Machine type**: `e2-medium` or larger (minimum 2 vCPU, 4 GB RAM) * For production: `e2-standard-2` (2 vCPU, 8 GB RAM) recommended * For testing: `e2-small` (2 vCPU, 2 GB RAM) minimum * **Boot disk**: * **Operating system**: Ubuntu LTS (Ubuntu 22.04 LTS or newer) * **Boot disk type**: Balanced persistent disk * **Size**: 30 GB (minimum 20 GB recommended) * **Firewall**: * Allow HTTP traffic: **Checked** * Allow HTTPS traffic: **Checked** #### 1.4 Configure Firewall Rules 1. Navigate to **VPC network** → **Firewall** 2. Create or modify firewall rules to allow the following ports: | Name | Direction | Targets | Protocols/ports | Source filters | | ------------------ | --------- | --------------- | --------------- | ------------------------------------- | | insforge-ssh | Ingress | insforge-server | tcp:22 | Your IP address | | insforge-http | Ingress | insforge-server | tcp:80 | 0.0.0.0/0 | | insforge-https | Ingress | insforge-server | tcp:443 | 0.0.0.0/0 | | insforge-app | Ingress | insforge-server | tcp:7130 | 0.0.0.0/0 | | insforge-deno | Ingress | insforge-server | tcp:7133 | 0.0.0.0/0 | | insforge-postgrest | Ingress | insforge-server | tcp:5430 | 0.0.0.0/0 | | insforge-postgres | Ingress | insforge-server | tcp:5432 | 0.0.0.0/0 (only if needed externally) | > ⚠️ **Security Note**: For production, restrict PostgreSQL (5432) to specific IP addresses or remove external access entirely. Consider using a reverse proxy (nginx) and exposing only ports 80/443. ### 2. Connect to Your Compute Engine Instance 1. In the Google Cloud Console, go to **Compute Engine** → **VM instances** 2. Find your instance and click the **SSH** button in the same row, or: ```bash theme={null} # Use gcloud CLI to SSH (if you have gcloud SDK installed locally) gcloud compute ssh insforge-server --zone=your-zone ``` ### 3. Install Dependencies #### 3.1 Update System Packages ```bash theme={null} sudo apt update && sudo apt upgrade -y ``` #### 3.2 Install Docker ```bash theme={null} # Add Docker's official GPG key sudo apt-get update sudo apt-get install ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # Add Docker repository echo \ "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # Install Docker sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin ``` #### 3.3 Add Your User to Docker Group After installing Docker, you need to add your user to the `docker` group to run Docker commands without `sudo`: ```bash theme={null} # Add your user to the docker group sudo usermod -aG docker $USER # Apply the group changes newgrp docker ``` **Verify it works:** ```bash theme={null} # This should now work without sudo docker ps ``` > 💡 **Note**: If `docker ps` doesn't work immediately, log out and log back in via SSH, then try again. > ⚠️ **Security Note**: Adding a user to the `docker` group grants them root-equivalent privileges on the system. This is acceptable for single-user environments like your Compute Engine instance, but be cautious on shared systems. #### 3.4 Install Git ```bash theme={null} sudo apt install git -y ``` ### 4. Deploy InsForge #### 4.1 Clone Repository ```bash theme={null} cd ~ git clone https://github.com/insforge/insforge.git cd insforge/deploy/docker-compose ``` #### 4.2 Create Environment Configuration Create your `.env` file with production settings: ```bash theme={null} nano .env ``` The repo ships a template at `deploy/docker-compose/.env.example`. Copy it and edit the values: ```bash theme={null} cp .env.example .env nano .env ``` At a minimum, set these values: ```env theme={null} # Authentication (required) # IMPORTANT: Generate a strong random secret for production (32+ characters) JWT_SECRET=your-secret-key-here-must-be-32-char-or-above # Admin account (used for initial setup) ROOT_ADMIN_USERNAME=admin ROOT_ADMIN_PASSWORD=change-this-password # Database (required) POSTGRES_PASSWORD=your-secure-postgres-password ``` Optional values you may want to set: ```env theme={null} # Encryption key for secrets and database encryption. # Falls back to JWT_SECRET if left empty. ENCRYPTION_KEY= # AI/LLM (get a key from https://openrouter.ai/keys) OPENROUTER_API_KEY= # Site deployments and custom domains VERCEL_TOKEN= VERCEL_TEAM_ID= VERCEL_PROJECT_ID= # OAuth providers (Google, GitHub, etc.) GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= ``` See `deploy/docker-compose/.env.example` for the full list of supported variables. **Generate Secure Secrets:** ```bash theme={null} # Generate JWT_SECRET (32+ characters) openssl rand -base64 32 # Generate ENCRYPTION_KEY (32 characters) openssl rand -base64 24 ``` > 💡 **Important**: Save these secrets securely. You'll need them if you ever migrate or restore your instance. #### 4.3 Start InsForge Services ```bash theme={null} # Pull Docker images and start services docker compose up -d # View logs to ensure everything started correctly docker compose logs -f ``` Press `Ctrl+C` to exit log view. #### 4.4 Verify Services ```bash theme={null} # Check running containers docker compose ps # You should see 4 running services: # - postgres # - postgrest # - insforge # - deno ``` ### 5. Access Your InsForge Instance #### 5.1 Test Backend API ```bash theme={null} curl http://your-external-ip:7130/api/health ``` Expected response: ```json theme={null} { "status": "ok", "version": "2.1.7", "service": "Insforge OSS Backend", "timestamp": "2025-10-17T..." } ``` #### 5.2 Access Dashboard Open your browser and navigate to: ```text theme={null} http://your-external-ip:7130 ``` ### 6. Configure Domain (Optional but Recommended) #### 6.1 Reserve a Static External IP 1. In Google Cloud Console, go to **VPC network** → **External IP addresses** 2. Click **Reserve Static Address** 3. **Name**: `insforge-ip` 4. **Type**: Regional or Global (Regional for VM instances) 5. **Region**: Same as your VM instance 6. **Click Reserve** #### 6.2 Update DNS Records Point your domain's DNS records to the reserved static IP: ```text theme={null} api.yourdomain.com → your-static-external-ip app.yourdomain.com → your-static-external-ip ``` #### 6.3 Install Nginx Reverse Proxy ```bash theme={null} sudo apt install nginx -y ``` Create Nginx configuration: ```bash theme={null} sudo nano /etc/nginx/sites-available/insforge ``` Add the following configuration: ```nginx theme={null} # Backend API server { listen 80; server_name api.yourdomain.com; location / { proxy_pass http://localhost:7130; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } } # Dashboard server { listen 80; server_name app.yourdomain.com; location / { proxy_pass http://localhost:7130; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } } ``` Enable the configuration: ```bash theme={null} sudo ln -s /etc/nginx/sites-available/insforge /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx ``` #### 6.4 Install SSL Certificate (Recommended) ```bash theme={null} # Install Certbot sudo apt install certbot python3-certbot-nginx -y # Obtain SSL certificates sudo certbot --nginx -d api.yourdomain.com -d app.yourdomain.com # Follow the prompts to complete setup ``` Update your `.env` file with HTTPS URLs: ```bash theme={null} cd ~/insforge/deploy/docker-compose nano .env ``` Change: ```env theme={null} API_BASE_URL=https://api.yourdomain.com VITE_API_BASE_URL=https://api.yourdomain.com ``` Restart services: ```bash theme={null} docker compose down docker compose up -d ``` ## 🔧 Management & Maintenance ### View Logs ```bash theme={null} # All services docker compose logs -f # Specific service docker compose logs -f insforge docker compose logs -f postgres docker compose logs -f deno ``` ### Stop Services ```bash theme={null} docker compose down ``` ### Restart Services ```bash theme={null} docker compose restart ``` ### Update InsForge ```bash theme={null} cd ~/insforge/deploy/docker-compose git pull origin main docker compose pull && docker compose up -d ``` ### Backup Database ```bash theme={null} # Create backup (run from deploy/docker-compose/) docker compose exec postgres pg_dump -U postgres insforge > backup_$(date +%Y%m%d_%H%M%S).sql # Store backup in Google Cloud Storage (optional) # First, install Google Cloud CLI and authenticate # Then: gsutil cp backup_$(date +%Y%m%d_%H%M%S).sql gs://your-backup-bucket/ ``` ### Monitor Resources ```bash theme={null} # Check disk usage df -h # Check memory usage free -h # Check Docker stats docker stats ``` ## 🐛 Troubleshooting ### Services Won't Start ```bash theme={null} # Check logs for errors docker compose logs # Check disk space df -h # Check memory free -h # Restart Docker daemon sudo systemctl restart docker docker compose up -d ``` ### Cannot Connect to Database ```bash theme={null} # Check if PostgreSQL is running docker compose ps postgres # Check PostgreSQL logs docker compose logs postgres # Verify credentials in .env file cat .env | grep POSTGRES ``` ### Port Already in Use ```bash theme={null} # Check what's using the port sudo netstat -tulpn | grep :7130 # Kill the process or change port in docker-compose.yml ``` ### Out of Memory Consider upgrading to a larger instance type: ```text theme={null} - Current: e2-small (2 vCPU, 2 GB RAM) - Upgrade to: e2-standard-2 (2 vCPU, 8 GB RAM) ``` ### SSL Certificate Issues ```bash theme={null} # Renew certificates sudo certbot renew # Test renewal sudo certbot renew --dry-run ``` ## 📊 Performance Optimization ### For Production Workloads 1. **Upgrade Instance Type**: Use `e2-standard-2` or `e2-standard-4` 2. **Use Cloud SQL**: Migrate from containerized PostgreSQL to Google Cloud SQL for better reliability 3. **Enable Cloud Monitoring**: Monitor metrics and set up alerts 4. **Configure Backups**: Set up automated daily backups 5. **Use Cloud Storage**: Configure Google Cloud Storage for file uploads instead of local storage ### Database Optimization ```conf theme={null} # Increase PostgreSQL shared_buffers (edit postgresql.conf in deploy/docker-init/db/) # Recommended: 25% of available RAM shared_buffers = 1GB effective_cache_size = 3GB ``` ## 🔒 Security Best Practices 1. **Change Default Passwords**: Update admin and database passwords 2. **Enable Firewall**: Use Google Cloud Firewall rules effectively 3. **Regular Updates**: Keep system and Docker images updated 4. **SSL/TLS**: Always use HTTPS in production 5. **Backup Regularly**: Automate database backups 6. **Monitor Logs**: Set up log monitoring and alerts 7. **Limit SSH Access**: Restrict SSH to specific IP addresses 8. **Use Service Accounts**: Instead of API keys where possible ## 🆘 Support & Resources * **Documentation**: [https://docs.insforge.dev](https://docs.insforge.dev) * **GitHub Issues**: [https://github.com/insforge/insforge/issues](https://github.com/insforge/insforge/issues) * **Discord Community**: [https://discord.com/invite/MPxwj5xVvW](https://discord.com/invite/MPxwj5xVvW) ## 📝 Cost Estimation **Monthly Google Cloud Costs (approximate):** | Component | Type | Monthly Cost | | ----------------------- | ---------------------------- | ---------------- | | Compute Engine | e2-medium (2 vCPU, 4 GB RAM) | \~\$29 | | Persistent Disk (30 GB) | Standard | \~\$3 | | Network Egress | First 1GB free | Variable | | **Total** | | **\~\$32/month** | > 💡 **Cost Optimization**: Use sustained use discounts for 24/7 running instances to save up to 30%. Consider preemptible instances for development/testing environments. *** **Congratulations! 🎉** Your InsForge instance is now running on Google Cloud Compute Engine. You can start building applications by connecting AI agents to your backend platform. For other production deployment strategies, check out our [deployment guides](./README.md). # Deployment security guide Source: https://docs.insforge.dev/deployment/deployment-security-guide # Deployment & Security Guide for VPS Installation This comprehensive guide covers deploying InsForge on a generic VPS (Virtual Private Server) for production, hardening your instance with security best practices, and maintaining it over time with safe updates and rollback procedures. > **Scope**: This guide is provider-agnostic. It works on any Linux VPS — Ubuntu/Debian recommended — from providers such as DigitalOcean, Hetzner, Linode, Vultr, OVH, or a bare-metal server. For cloud-specific guides (AWS EC2, GCP, Azure, Render), see the [deployment directory](./README.md). *** ## 📋 Table of Contents * [Prerequisites](#prerequisites) * [Part 1 — Deployment](#part-1--deployment) * [Server Requirements](#1-server-requirements) * [Initial Server Setup](#2-initial-server-setup) * [Install Docker & Docker Compose](#3-install-docker--docker-compose) * [Deploy InsForge with Docker Compose](#4-deploy-insforge-with-docker-compose) * [Environment Variable Configuration](#5-environment-variable-configuration) * [Reverse Proxy Setup](#6-reverse-proxy-setup) * [HTTPS / TLS Setup](#7-https--tls-setup) * [Part 2 — Security](#part-2--security) * [Port Management](#8-port-management) * [Firewall Setup (UFW)](#9-firewall-setup-ufw) * [Run Services as a Non-Root User](#10-run-services-as-a-non-root-user) * [SSH Hardening](#11-ssh-hardening) * [Docker Security](#12-docker-security) * [Secrets Management](#13-secrets-management) * [Part 3 — Updating & Maintenance](#part-3--updating--maintenance) * [Pre-Update Backup](#14-pre-update-backup) * [Updating InsForge](#15-updating-insforge) * [Rollback Procedure](#16-rollback-procedure) * [Automated Backups](#17-automated-backups) * [Monitoring & Health Checks](#18-monitoring--health-checks) * [Quick Reference](#quick-reference) * [Troubleshooting](#troubleshooting) *** ## Prerequisites Before starting, ensure you have: * A VPS running **Ubuntu 22.04 LTS** or **Ubuntu 24.04 LTS** (Debian 12 also works) * **Root or sudo access** to the server * A registered **domain name** (recommended for production) * Basic familiarity with the Linux command line and SSH *** ## Part 1 — Deployment ### 1. Server Requirements | Resource | Minimum | Recommended | | ----------- | ------------- | ------------------ | | **CPU** | 2 vCPU | 4 vCPU | | **RAM** | 2 GB | 4 GB+ | | **Storage** | 20 GB SSD | 40 GB+ SSD | | **OS** | Ubuntu 22.04+ | Ubuntu 24.04 LTS | | **Network** | Public IPv4 | Public IPv4 + IPv6 | > 💡 **Tip**: For production workloads with multiple users, start with 4 GB RAM. Monitor usage with `docker stats` and scale vertically as needed. InsForge consists of **4 services** that run together: | Service | Description | Internal Port | | -------------- | ----------------------------- | --------------------- | | **PostgreSQL** | Primary database | 5432 | | **PostgREST** | Auto-generated REST API layer | 3000 (mapped to 5430) | | **InsForge** | Node.js backend + dashboard | 7130 | | **Deno** | Serverless functions runtime | 7133 | *** ### 2. Initial Server Setup #### 2.1 Connect to Your VPS ```bash theme={null} ssh root@your-server-ip ``` #### 2.2 Update System Packages ```bash theme={null} apt update && apt upgrade -y ``` #### 2.3 Create a Deploy User (Non-Root) Never run production services as root. Create a dedicated user: ```bash theme={null} # Create the deploy user and add to sudo group adduser deploy usermod -aG sudo deploy # Switch to the deploy user su - deploy ``` #### 2.4 Set the Timezone ```bash theme={null} sudo timedatectl set-timezone UTC ``` #### 2.5 Enable Automatic Security Updates ```bash theme={null} sudo apt install unattended-upgrades -y sudo dpkg-reconfigure -plow unattended-upgrades ``` *** ### 3. Install Docker & Docker Compose #### 3.1 Install Docker Engine ```bash theme={null} # Add Docker's official GPG key sudo apt install ca-certificates curl gnupg -y sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # Add the Docker repository echo \ "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # Install Docker sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y ``` #### 3.2 Add Deploy User to the Docker Group ```bash theme={null} sudo usermod -aG docker deploy newgrp docker ``` #### 3.3 Verify Docker Installation ```bash theme={null} docker --version docker compose version docker run hello-world ``` > ⚠️ **Security Note**: Adding a user to the `docker` group grants root-equivalent privileges on the host. This is acceptable for a dedicated deploy user but should not be done for general-purpose accounts on shared servers. *** ### 4. Deploy InsForge with Docker Compose #### 4.1 Download the Production Docker Compose File ```bash theme={null} mkdir -p ~/insforge && cd ~/insforge # Download the production-ready Docker Compose file and environment template wget https://raw.githubusercontent.com/insforge/insforge/main/deploy/docker-compose/docker-compose.yml wget https://raw.githubusercontent.com/insforge/insforge/main/deploy/docker-compose/.env.example # Create your environment file cp .env.example .env ``` #### 4.2 Start InsForge ```bash theme={null} docker compose up -d ``` #### 4.3 Verify All Services Are Running ```bash theme={null} docker compose ps ``` You should see 4 containers in a `running` or `healthy` state: ```text theme={null} NAME SERVICE STATUS insforge insforge running postgres postgres healthy postgrest postgrest healthy deno deno running ``` #### 4.4 Test the Health Endpoint ```bash theme={null} curl http://localhost:7130/api/health ``` Expected response: ```json theme={null} { "status": "ok", "version": "1.x.x", "service": "Insforge OSS Backend", "timestamp": "2026-..." } ``` *** ### 5. Environment Variable Configuration Edit your `.env` file to configure InsForge for production: ```bash theme={null} nano ~/insforge/.env ``` #### 5.1 Required Variables These **must** be changed from defaults before going to production: ```env theme={null} # ── Security (CRITICAL — generate unique values) ────────────── JWT_SECRET= ENCRYPTION_KEY= ROOT_ADMIN_USERNAME=admin ROOT_ADMIN_PASSWORD= # ── Public URL (must match your domain/IP) ──────────────────── API_BASE_URL=https://insforge.yourdomain.com VITE_API_BASE_URL=https://insforge.yourdomain.com ``` Generate secure secrets right from the terminal: ```bash theme={null} # JWT secret (32+ characters) openssl rand -base64 32 # Encryption key (separate from JWT_SECRET) openssl rand -base64 24 # Admin password openssl rand -base64 18 ``` > ⚠️ **Important**: `JWT_SECRET` and `ENCRYPTION_KEY` should be **different** values. If `ENCRYPTION_KEY` is not set, InsForge falls back to `JWT_SECRET` — but rotating `JWT_SECRET` later will permanently corrupt all stored secrets (API keys, OAuth tokens, etc.). #### 5.2 Database Variables ```env theme={null} POSTGRES_USER=postgres POSTGRES_PASSWORD= POSTGRES_DB=insforge ``` #### 5.3 Port Variables Default ports used by InsForge: ```env theme={null} POSTGRES_PORT=5432 POSTGREST_PORT=5430 APP_PORT=7130 AUTH_PORT=7131 DENO_PORT=7133 ``` > 💡 You can change these if they conflict with other services on your VPS. #### 5.4 Required for Deployments These variables are only needed if you plan to use InsForge's **deployment features** (deploying projects via the dashboard). If you don't need deployments, skip this section. > ⚠️ **Note**: These variables (`AWS_S3_BUCKET`, `AWS_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `PROJECT_ID`, `MAX_FILE_SIZE`) come from the root `.env.example` setup. They are **not** present in `deploy/docker-compose/.env.example`, and the `deploy/docker-compose/docker-compose.yml` does **not** pass them through to the `insforge` container, so setting them in your `.env` has no effect on that production compose. To use them, add each one to the `insforge` service's `environment` block in your `docker-compose.yml`. ```env theme={null} # ── Deployments ────────────────────────────────────────────── # S3 bucket for legacy zip deployment uploads. # Direct uploads use the backend proxy, but POST /api/deployments still requires S3. AWS_S3_BUCKET=your-deployment-bucket AWS_REGION=us-east-2 AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= # Project ID used by OpenRouter AI token renewal and Vercel deployments PROJECT_ID=your-project-id ``` #### 5.5 Optional Variables ```env theme={null} # ── OAuth Providers ─────────────────────────────────────────── GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= MICROSOFT_CLIENT_ID= MICROSOFT_CLIENT_SECRET= DISCORD_CLIENT_ID= DISCORD_CLIENT_SECRET= LINKEDIN_CLIENT_ID= LINKEDIN_CLIENT_SECRET= X_CLIENT_ID= X_CLIENT_SECRET= APPLE_CLIENT_ID= APPLE_CLIENT_SECRET= # ── AI / LLM ───────────────────────────────────────────────── OPENROUTER_API_KEY= # ── Storage (S3-compatible — leave empty for local storage) ── # For general file storage only (not deployments). If omitted, local # filesystem storage is used automatically. AWS_S3_BUCKET= AWS_REGION= AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= # ── Deno Functions ──────────────────────────────────────────── WORKER_TIMEOUT_MS=60000 ``` After editing, restart services to apply changes: ```bash theme={null} cd ~/insforge docker compose down docker compose up -d ``` *** ### 6. Reverse Proxy Setup A reverse proxy sits in front of InsForge, providing TLS termination, HTTP/2, and a clean URL without port numbers. #### Option A: Nginx (Recommended) ##### 6.1 Install Nginx ```bash theme={null} sudo apt install nginx -y ``` ##### 6.2 Create the Site Configuration ```bash theme={null} sudo nano /etc/nginx/sites-available/insforge ``` Paste the following configuration — replace `insforge.yourdomain.com` with your actual domain: ```nginx theme={null} # ── InsForge Backend + Dashboard ────────────────────────────── server { listen 80; listen [::]:80; server_name insforge.yourdomain.com; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Max upload size (match MAX_FILE_SIZE in .env, default 50 MB) client_max_body_size 50M; location / { proxy_pass http://127.0.0.1:7130; proxy_http_version 1.1; # WebSocket support (required for Realtime features) proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; # Timeouts for long-running requests (e.g., AI completions) proxy_read_timeout 120s; proxy_send_timeout 120s; } } ``` ##### 6.3 Enable the Site ```bash theme={null} sudo ln -s /etc/nginx/sites-available/insforge /etc/nginx/sites-enabled/ # Remove the default site (optional) sudo rm -f /etc/nginx/sites-enabled/default # Test and reload sudo nginx -t sudo systemctl reload nginx ``` #### Option B: Caddy (Automatic HTTPS) Caddy is a simpler alternative that handles TLS certificates automatically. ##### Install Caddy ```bash theme={null} sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list sudo apt update sudo apt install caddy -y ``` ##### Configure Caddy ```bash theme={null} sudo nano /etc/caddy/Caddyfile ``` ```caddyfile theme={null} insforge.yourdomain.com { reverse_proxy localhost:7130 header { X-Frame-Options "SAMEORIGIN" X-Content-Type-Options "nosniff" X-XSS-Protection "1; mode=block" Referrer-Policy "strict-origin-when-cross-origin" } request_body { max_size 50MB } } ``` ```bash theme={null} sudo systemctl reload caddy ``` Caddy will automatically obtain and renew Let's Encrypt certificates — no extra steps needed. *** ### 7. HTTPS / TLS Setup > If you chose **Caddy** in Step 6, TLS is already handled automatically. Skip to [Part 2](#part-2--security). #### 7.1 Install Certbot (for Nginx) ```bash theme={null} sudo apt install certbot python3-certbot-nginx -y ``` #### 7.2 Obtain SSL Certificates ```bash theme={null} sudo certbot --nginx -d insforge.yourdomain.com ``` Follow the interactive prompts. Certbot will: 1. Verify domain ownership via HTTP challenge 2. Obtain a signed certificate from Let's Encrypt 3. Automatically update your Nginx configuration to serve HTTPS 4. Set up HTTP → HTTPS redirect #### 7.3 Verify Auto-Renewal Let's Encrypt certificates expire every 90 days. Certbot installs a systemd timer for automatic renewal: ```bash theme={null} # Test renewal (dry run — no actual renewal) sudo certbot renew --dry-run # Check the timer is active sudo systemctl status certbot.timer ``` #### 7.4 Update InsForge Environment for HTTPS After obtaining your certificate, update your `.env` to use HTTPS URLs: ```bash theme={null} cd ~/insforge nano .env ``` ```env theme={null} API_BASE_URL=https://insforge.yourdomain.com VITE_API_BASE_URL=https://insforge.yourdomain.com ``` Restart InsForge to apply: ```bash theme={null} docker compose down docker compose up -d ``` *** ## Part 2 — Security ### 8. Port Management #### Ports That Should Be Open (via Reverse Proxy) | Port | Protocol | Purpose | | ---- | -------- | ------------------------ | | 22 | TCP | SSH (restrict source IP) | | 80 | TCP | HTTP → HTTPS redirect | | 443 | TCP | HTTPS (reverse proxy) | #### Ports That Should Be Closed to the Public These ports are used **only** for internal Docker service-to-service communication. They should **never** be exposed to the internet: | Port | Service | Why Close It | | ---- | ---------- | ---------------------------------------------------------------- | | 5432 | PostgreSQL | Direct DB access — use `docker exec` instead | | 5430 | PostgREST | Internal REST layer — proxied through InsForge | | 7130 | InsForge | API + dashboard, accessed via reverse proxy on 443, not directly | | 7131 | (unused) | Published by compose (`AUTH_PORT`), but no process listens on it | | 7133 | Deno | Internal serverless runtime | > ⚠️ **Critical**: The default `docker-compose.yml` binds ports to `0.0.0.0` (all interfaces), **not** `127.0.0.1`. This means Docker will expose services directly to the internet, **bypassing UFW entirely** (Docker manipulates iptables directly). You **MUST** add the `127.0.0.1:` prefix to every published port in your `docker-compose.yml`: > > ```yaml theme={null} > ports: > - "127.0.0.1:${POSTGRES_PORT:-5432}:5432" # PostgreSQL > - "127.0.0.1:${POSTGREST_PORT:-5430}:3000" # PostgREST > - "127.0.0.1:${APP_PORT:-7130}:7130" # InsForge (API + dashboard) > - "127.0.0.1:${AUTH_PORT:-7131}:7131" # AUTH_PORT (published by compose, unused) > - "127.0.0.1:${DENO_PORT:-7133}:7133" # Deno > ``` > > Without this prefix, anyone on the internet can reach these services directly — including PostgreSQL with default credentials. See [Section 9.2](#92-docker-and-ufw-caveat) for details. *** ### 9. Firewall Setup (UFW) UFW (Uncomplicated Firewall) is the simplest way to manage iptables on Ubuntu. #### 9.1 Install and Configure UFW ```bash theme={null} # Install UFW (usually pre-installed on Ubuntu) sudo apt install ufw -y # Default policy: deny all incoming, allow all outgoing sudo ufw default deny incoming sudo ufw default allow outgoing # Allow SSH (CRITICAL — do this BEFORE enabling UFW!) sudo ufw allow OpenSSH # Allow HTTP and HTTPS (for reverse proxy) sudo ufw allow 80/tcp sudo ufw allow 443/tcp # Enable the firewall sudo ufw enable # Verify rules sudo ufw status verbose ``` Expected output: ```text theme={null} Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere 80/tcp ALLOW Anywhere 443/tcp ALLOW Anywhere ``` > ⚠️ **Critical**: Always allow SSH **before** enabling UFW, or you will lock yourself out of the server. #### 9.2 Docker and UFW Caveat Docker manipulates iptables directly, which can **bypass UFW rules**. To prevent this: **Option 1 — Bind ports to localhost** (recommended): In your `docker-compose.yml`, prefix ports with `127.0.0.1:`: ```yaml theme={null} ports: - "127.0.0.1:7130:7130" - "127.0.0.1:7131:7131" ``` **Option 2 — Disable Docker's iptables management**: ```bash theme={null} sudo nano /etc/docker/daemon.json ``` ```json theme={null} { "iptables": false } ``` ```bash theme={null} sudo systemctl restart docker ``` > ⚠️ Disabling Docker iptables requires manual network configuration. **Option 1 is preferred** for most setups. #### 9.3 Restrict SSH to Your IP (Optional) For maximum security, restrict SSH access to a known IP address: ```bash theme={null} # Remove the broad SSH rule sudo ufw delete allow OpenSSH # Allow SSH only from your IP sudo ufw allow from YOUR_IP_ADDRESS to any port 22 proto tcp # Verify sudo ufw status ``` *** ### 10. Run Services as a Non-Root User InsForge's Docker image already follows non-root best practices: * The production Dockerfile sets `USER node` (UID 1000), so the application process inside the container runs as a non-root user. * System-level Docker operations are managed by the `deploy` user (created in [Step 2.3](#23-create-a-deploy-user-non-root)), which has access to the Docker socket via the `docker` group. **Verify the container user:** ```bash theme={null} docker compose exec insforge whoami # Expected output: node ``` **Additional hardening:** Add `security_opt` to each service in your `docker-compose.yml` to prevent privilege escalation: ```yaml theme={null} # Add to each service in docker-compose.yml security_opt: - no-new-privileges:true ``` *** ### 11. SSH Hardening #### 11.1 Use SSH Key Authentication ```bash theme={null} # On your LOCAL machine — generate a key pair if you don't have one ssh-keygen -t ed25519 -C "deploy@insforge" # Copy the public key to your server ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@your-server-ip ``` #### 11.2 Disable Password Authentication Once key-based auth is confirmed working: ```bash theme={null} sudo nano /etc/ssh/sshd_config ``` Set the following: ```ini theme={null} PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes MaxAuthTries 3 ``` Restart SSH: ```bash theme={null} sudo systemctl restart sshd ``` #### 11.3 Install Fail2Ban Fail2Ban automatically bans IPs that show malicious activity (e.g., brute-force SSH): ```bash theme={null} sudo apt install fail2ban -y # Create a local config (survives updates) sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local sudo nano /etc/fail2ban/jail.local ``` Add or ensure these settings are present: ```ini theme={null} [sshd] enabled = true port = ssh filter = sshd maxretry = 5 bantime = 3600 findtime = 600 ``` ```bash theme={null} sudo systemctl enable fail2ban sudo systemctl restart fail2ban # Check banned IPs sudo fail2ban-client status sshd ``` *** ### 12. Docker Security #### 12.1 Keep Docker Updated ```bash theme={null} sudo apt update sudo apt upgrade docker-ce docker-ce-cli containerd.io -y ``` #### 12.2 Limit Container Resources (Optional) Prevent a single container from consuming all resources: ```yaml theme={null} # Add to any service in docker-compose.yml deploy: resources: limits: memory: 2G cpus: '1.0' reservations: memory: 512M ``` #### 12.3 Read-Only Root Filesystem (Advanced) For extra hardening, mount the container filesystem as read-only where possible: ```yaml theme={null} read_only: true tmpfs: - /tmp ``` > ⚠️ This requires testing — some services need writable directories for caches or temporary files. #### 12.4 Restrict CORS Origins By default the backend allows all origins. It reflects the request's `Origin` header back in the response and, for function proxy responses, sets `Access-Control-Allow-Origin: *`. This is convenient for local development but too permissive for production. For a production deployment, restrict the allowed origins to the domains you actually serve (for example your dashboard and app domains), so other sites cannot make credentialed cross-origin requests to your API. *** ### 13. Secrets Management #### Do ✅ * Store secrets in the `.env` file with `chmod 600 ~/insforge/.env` * Use separate values for `JWT_SECRET` and `ENCRYPTION_KEY` * Generate secrets with `openssl rand -base64 32` * Back up your `.env` file to a secure, offline location #### Don't ❌ * Commit `.env` to version control * Reuse the same secret for multiple variables * Use default passwords (`change-this-password`, `postgres`) in production * Share secrets over unencrypted channels *** ## Part 3 — Updating & Maintenance ### 14. Pre-Update Backup **Always back up before updating.** This gives you a recovery path if anything goes wrong. #### 14.1 Back Up the Database ```bash theme={null} cd ~/insforge source .env # Create a timestamped database backup docker compose exec -T postgres pg_dump \ -U "${POSTGRES_USER:-postgres}" "${POSTGRES_DB:-insforge}" \ > backup_$(date +%Y%m%d_%H%M%S).sql # Verify size is reasonable ls -lh backup_*.sql ``` #### 14.2 Back Up Environment and Volumes ```bash theme={null} # Back up .env file cp .env .env.backup_$(date +%Y%m%d) # Back up Docker volumes (optional but recommended) docker run --rm \ -v insforge_postgres-data:/data \ -v $(pwd):/backup \ alpine tar czf /backup/volumes_postgres_$(date +%Y%m%d_%H%M%S).tar.gz /data ``` #### 14.3 Record Current Version ```bash theme={null} # Note the current image versions before updating docker compose images ``` *** ### 15. Updating InsForge #### 15.1 Pull the Latest Images ```bash theme={null} cd ~/insforge # Pull the latest versions docker compose pull ``` #### 15.2 Apply the Update ```bash theme={null} # Stop current services, start with new images docker compose down docker compose up -d # Watch logs for errors during startup docker compose logs -f --tail=50 ``` Press `Ctrl+C` to stop following logs. #### 15.3 Verify the Update ```bash theme={null} # Check all services are healthy docker compose ps # Test the health endpoint curl http://localhost:7130/api/health # Check the version in the response ``` #### 15.4 Update the Docker Compose File (If Needed) Occasionally, new releases may include changes to `docker-compose.yml`. To pick up these changes: ```bash theme={null} cd ~/insforge # Download the updated compose file wget -O docker-compose.yml.new \ https://raw.githubusercontent.com/insforge/insforge/main/deploy/docker-compose/docker-compose.yml # Compare with your current file diff docker-compose.yml docker-compose.yml.new # If changes look safe, apply them mv docker-compose.yml docker-compose.yml.old mv docker-compose.yml.new docker-compose.yml # Restart with the new configuration docker compose down docker compose up -d ``` *** ### 16. Rollback Procedure If an update causes issues, follow these steps to revert: #### 16.1 Stop the Broken Services ```bash theme={null} cd ~/insforge docker compose down ``` #### 16.2 Restore the Previous Docker Compose File ```bash theme={null} # If you saved the old file mv docker-compose.yml.old docker-compose.yml ``` #### 16.3 Pin to a Specific Image Version Edit `docker-compose.yml` and replace `latest` tags with the previous version: ```yaml theme={null} # Example: pin to a known-good version (replace with your previous tag) image: ghcr.io/insforge/insforge-oss:v1.5.0 ``` > Note: the current `deploy/docker-compose` pins `v1.5.0`, and the project is now on the 2.x line. Pin to whatever version you were running before the update. #### 16.4 Restore the Database (If Needed) Only restore the database if the update included a database migration that caused issues: ```bash theme={null} cd ~/insforge source .env # Start only PostgreSQL docker compose up -d postgres # Wait for it to be healthy docker compose exec postgres pg_isready -U "${POSTGRES_USER:-postgres}" # Restore from backup cat backup_YYYYMMDD_HHMMSS.sql | \ docker compose exec -T postgres psql \ -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-insforge}" # Start remaining services docker compose up -d ``` #### 16.5 Restore Environment File (If Changed) ```bash theme={null} cp .env.backup_YYYYMMDD .env docker compose down docker compose up -d ``` *** ### 17. Automated Backups Set up a cron job for daily automated backups: #### 17.1 Create a Backup Script ```bash theme={null} nano ~/insforge/backup.sh ``` ```bash theme={null} #!/bin/bash set -euo pipefail # InsForge Automated Backup Script # Load .env so POSTGRES_USER / POSTGRES_DB are available outside Docker Compose set -a source "$HOME/insforge/.env" set +a BACKUP_DIR="$HOME/insforge/backups" RETENTION_DAYS=14 TIMESTAMP=$(date +%Y%m%d_%H%M%S) trap 'echo "[$(date)] ERROR: Backup failed at line $LINENO" >&2; exit 1' ERR mkdir -p "$BACKUP_DIR" # Dump the database docker compose -f "$HOME/insforge/docker-compose.yml" exec -T postgres \ pg_dump -U "${POSTGRES_USER:-postgres}" "${POSTGRES_DB:-insforge}" \ > "$BACKUP_DIR/db_$TIMESTAMP.sql" # Copy the environment file cp "$HOME/insforge/.env" "$BACKUP_DIR/env_$TIMESTAMP.bak" # Remove backups older than retention period find "$BACKUP_DIR" -name "db_*.sql" -mtime +$RETENTION_DAYS -delete find "$BACKUP_DIR" -name "env_*.bak" -mtime +$RETENTION_DAYS -delete echo "[$(date)] Backup completed successfully: db_$TIMESTAMP.sql" ``` ```bash theme={null} chmod +x ~/insforge/backup.sh ``` #### 17.2 Schedule with Cron ```bash theme={null} crontab -e ``` Add this line for daily backups at 3:00 AM: ```cron theme={null} 0 3 * * * /home/deploy/insforge/backup.sh >> /home/deploy/insforge/backups/cron.log 2>&1 ``` #### 17.3 Off-Site Backups (Recommended) For disaster recovery, copy backups to an external location: ```bash theme={null} # Example: sync backups to S3-compatible storage aws s3 sync ~/insforge/backups s3://your-backup-bucket/insforge/ # Example: sync to a remote server rsync -avz ~/insforge/backups/ user@backup-server:/backups/insforge/ ``` *** ### 18. Monitoring & Health Checks #### 18.1 Check Service Status ```bash theme={null} # Container status docker compose ps # Resource usage per container docker stats --no-stream # Disk usage df -h # Memory usage free -h ``` #### 18.2 View Logs ```bash theme={null} # All services docker compose logs -f --tail=100 # Specific service docker compose logs -f insforge docker compose logs -f postgres docker compose logs -f deno ``` #### 18.3 Health Check Endpoint Monitor the health endpoint externally. A simple cron-based check: ```bash theme={null} # Add to crontab for monitoring */5 * * * * curl -sf https://insforge.yourdomain.com/api/health > /dev/null || echo "InsForge is DOWN" | mail -s "InsForge Alert" you@example.com ``` Or use a free uptime monitoring service like [UptimeRobot](https://uptimerobot.com) or [Betterstack](https://betterstack.com) to monitor `https://insforge.yourdomain.com/api/health`. *** ## Quick Reference ### Essential Commands ```bash theme={null} # ── Lifecycle ───────────────────────────────── docker compose up -d # Start all services docker compose down # Stop all services docker compose restart # Restart all services docker compose pull # Pull latest images # ── Diagnostics ─────────────────────────────── docker compose ps # Service status docker compose logs -f # Follow all logs docker compose logs -f insforge # Follow specific service docker stats --no-stream # Resource usage # ── Database (source .env first for vars) ──── source ~/insforge/.env docker compose exec -T postgres pg_dump -U "${POSTGRES_USER:-postgres}" "${POSTGRES_DB:-insforge}" > backup.sql # Backup cat backup.sql | docker compose exec -T postgres psql -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-insforge}" # Restore # ── Updates ─────────────────────────────────── docker compose pull # Pull new images docker compose down && docker compose up -d # Apply update ``` ### Security Checklist * [ ] Deploy user created (non-root) * [ ] SSH key authentication enabled * [ ] SSH password authentication disabled * [ ] Root login disabled * [ ] UFW firewall enabled (ports 22, 80, 443 only) * [ ] Docker ports bound to `127.0.0.1` * [ ] Fail2Ban installed and active * [ ] `JWT_SECRET` changed from default (32+ chars) * [ ] `ENCRYPTION_KEY` set (separate from `JWT_SECRET`) * [ ] `ROOT_ADMIN_PASSWORD` changed from default * [ ] `POSTGRES_PASSWORD` changed from default * [ ] `.env` file permissions set to `600` * [ ] HTTPS enabled via Certbot or Caddy * [ ] Automated daily backups configured * [ ] Unattended security updates enabled *** ## Troubleshooting ### Cannot Connect After Enabling UFW If you're locked out, use your VPS provider's **web console** (out-of-band access) to: ```bash theme={null} sudo ufw allow OpenSSH sudo ufw enable ``` ### Docker Bypasses UFW Docker directly manipulates iptables. Bind ports to `127.0.0.1` in `docker-compose.yml` as described in [Section 9.2](#92-docker-and-ufw-caveat). ### Services Fail to Start ```bash theme={null} # Check logs for the failing service docker compose logs postgres docker compose logs insforge # Verify disk space df -h # Verify memory free -h # Restart Docker daemon sudo systemctl restart docker docker compose up -d ``` ### SSL Certificate Won't Renew ```bash theme={null} # Check Certbot timer sudo systemctl status certbot.timer # Manual renewal sudo certbot renew # Test renewal sudo certbot renew --dry-run ``` ### Port Conflicts ```bash theme={null} # Find what's using a port sudo ss -tlnp | grep :7130 # Change the port in .env APP_PORT=7140 ``` ### Database Connection Issues ```bash theme={null} # Check PostgreSQL is healthy docker compose ps postgres # View PostgreSQL logs docker compose logs postgres # Connect to the database directly docker compose exec postgres psql -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-insforge}" ``` *** ## 🆘 Need Help? * **Documentation**: [https://docs.insforge.dev](https://docs.insforge.dev) * **Discord Community**: [https://discord.com/invite/MPxwj5xVvW](https://discord.com/invite/MPxwj5xVvW) * **GitHub Issues**: [https://github.com/insforge/insforge/issues](https://github.com/insforge/insforge/issues) # Next.js Source: https://docs.insforge.dev/examples/framework-guides/nextjs Learn how to create an InsForge project and build a Next.js app using AI Learn how to create an InsForge project and build a Next.js app using AI tools like Cursor. ## 1. Create an InsForge project Create a new InsForge project at [insforge.dev](https://insforge.dev). ## 2. Connect InsForge Two pieces: * **CLI link** — see the [Quickstart](/quickstart) to run `npx @insforge/cli link --project-id ` so the agent can read your project ID and keys. * **MCP setup** — see [MCP Setup](/mcp-setup) for the per-editor config (Cursor, Claude Code, Windsurf, Codex, VS Code). With both wired up the agent can read schemas, run queries, and deploy code from your editor. ## 3. Build your app with one prompt In Cursor or your AI assistant, use this prompt: ``` Create a new Next.js app with TypeScript and Tailwind CSS 3.4. Install the InsForge SDK and set up the client configuration. In my InsForge database, create a sports table with id and name columns. Add sample data: basketball, soccer, and tennis. Make it publicly readable. Create a page at /sports that fetches and displays all sports from the database. ``` Your AI will generate the complete application including database setup and UI. No need to write code manually - the AI creates everything for you. ## 4. What the AI generates Your AI assistant will automatically create files like these. You don't need to touch them manually. **InsForge client** at `lib/insforge.ts`: ```typescript lib/insforge.ts theme={null} import { createClient } from '@insforge/sdk'; export const insforge = createClient({ baseUrl: 'https://your-project.us-east.insforge.app', anonKey: 'your-anon-key', }); ``` **Sports page** at `app/sports/page.tsx`: ```typescript app/sports/page.tsx theme={null} import { insforge } from '@/lib/insforge'; export default async function SportsPage() { const { data: sports, error } = await insforge.database .from('sports') .select(); if (error) { return (

Error

{error.message}

); } return (

Sports

{sports && sports.length > 0 ? (
{sports.map((sport: { id: string; name: string }) => (

{sport.name}

ID: {sport.id}

))}
) : (

No sports found.

)}
); } ``` ## 5. Start the app Run the development server, go to [http://localhost:3000/sports](http://localhost:3000/sports) in a browser and you should see the list of sports. ```bash theme={null} npm run dev ``` ## Next: Extend your app with more prompts Try these prompts to add more features to your app: ``` Add a form to create new sports and save them to the database. Include validation and error handling. ``` ``` Add user authentication with sign up and login pages. Only allow authenticated users to add new sports. ``` ``` Add a favorites feature where users can mark their favorite sports. Store favorites in a user_favorites table with user_id and sport_id. ``` ``` Add images to each sport using InsForge Storage. Allow users to upload sport images and display them in the grid. ``` ``` Add an AI chat feature that can answer questions about sports. Use InsForge AI with streaming responses. ``` # Nuxt Source: https://docs.insforge.dev/examples/framework-guides/nuxt Learn how to create an InsForge project and build a Nuxt app using AI Learn how to create an InsForge project and build a Nuxt app using AI tools like Cursor. ## 1. Create an InsForge project Create a new InsForge project at [insforge.dev](https://insforge.dev). ## 2. Connect InsForge Two pieces: * **CLI link** — see the [Quickstart](/quickstart) to run `npx @insforge/cli link --project-id ` so the agent can read your project ID and keys. * **MCP setup** — see [MCP Setup](/mcp-setup) for the per-editor config (Cursor, Claude Code, Windsurf, Codex, VS Code). With both wired up the agent can read schemas, run queries, and deploy code from your editor. ## 3. Build your app with one prompt In Cursor or your AI assistant, use this prompt: ``` Create a new Nuxt app with TypeScript. Add Tailwind CSS 3.4 for styling. Install the InsForge SDK and set up the client configuration. In my InsForge database, create a sports table with id and name columns. Add sample data: basketball, soccer, and tennis. Make it publicly readable. Create a page that fetches and displays all sports from the database. ``` Your AI will generate the complete application including database setup and UI. No need to write code manually - the AI creates everything for you. ## 4. What the AI generates Your AI assistant will automatically create files like these. You don't need to touch them manually. **Runtime config** at `nuxt.config.ts`: ```typescript nuxt.config.ts theme={null} export default defineNuxtConfig({ runtimeConfig: { public: { insforgeBaseUrl: process.env.NUXT_PUBLIC_INSFORGE_BASE_URL, insforgeAnonKey: process.env.NUXT_PUBLIC_INSFORGE_ANON_KEY } } }) ``` **Server API route** at `server/api/sports.get.ts`: ```typescript server/api/sports.get.ts theme={null} import { createClient } from '@insforge/sdk'; export default defineEventHandler(async (event) => { const config = useRuntimeConfig() const client = createClient({ baseUrl: config.public.insforgeBaseUrl, anonKey: config.public.insforgeAnonKey }) const { data, error } = await client.database .from('sports') .select('*') if (error) { throw createError({ statusCode: 500, statusMessage: error.message || 'Failed to fetch sports' }) } return data }) ``` **Sports page** at `pages/sports.vue`: ```vue pages/sports.vue theme={null} ``` ## 5. Start the app Run the development server, go to [http://localhost:3000/sports](http://localhost:3000/sports) in a browser and you should see the list of sports. ```bash theme={null} npm run dev ``` ## Next: Extend your app with more prompts Try these prompts to add more features to your app: ``` Add a form to create new sports and save them to the database. Include validation and error handling. ``` ``` Add user authentication with sign up and login pages. Only allow authenticated users to add new sports. ``` ``` Add a favorites feature where users can mark their favorite sports. Store favorites in a user_favorites table with user_id and sport_id. ``` ``` Add images to each sport using InsForge Storage. Allow users to upload sport images and display them in the grid. ``` ``` Add an AI chat feature that can answer questions about sports. Use InsForge AI with streaming responses. ``` # React Source: https://docs.insforge.dev/examples/framework-guides/react Learn how to create an InsForge project and build a React app using AI Learn how to create an InsForge project and build a React app using AI tools like Cursor. ## 1. Create an InsForge project Create a new InsForge project at [insforge.dev](https://insforge.dev). ## 2. Connect InsForge Two pieces: * **CLI link** — see the [Quickstart](/quickstart) to run `npx @insforge/cli link --project-id ` so the agent can read your project ID and keys. * **MCP setup** — see [MCP Setup](/mcp-setup) for the per-editor config (Cursor, Claude Code, Windsurf, Codex, VS Code). With both wired up the agent can read schemas, run queries, and deploy code from your editor. ## 3. Build your app with one prompt In Cursor or your AI assistant, use this prompt: ``` Create a new React app with TypeScript and Vite. Add Tailwind CSS 3.4 for styling. Install the InsForge SDK and set up the client configuration. In my InsForge database, create a sports table with id and name columns. Add sample data: basketball, soccer, and tennis. Make it publicly readable. Create a component that fetches and displays all sports from the database. ``` Your AI will generate the complete application including database setup and UI. No need to write code manually - the AI creates everything for you. ## 4. What the AI generates Your AI assistant will automatically create files like these. You don't need to touch them manually. **InsForge client** at `src/lib/insforge.ts`: ```typescript src/lib/insforge.ts theme={null} import { createClient } from '@insforge/sdk'; export const insforge = createClient({ baseUrl: 'https://your-project.us-east.insforge.app', anonKey: 'your-anon-key', }); ``` **Sports component** at `src/components/Sports.tsx`: ```typescript src/components/Sports.tsx theme={null} import { useEffect, useState } from 'react'; import { insforge } from '../lib/insforge'; interface Sport { id: string; name: string; } export function Sports() { const [sports, setSports] = useState([]); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { async function fetchSports() { const { data, error } = await insforge.database .from('sports') .select(); if (error) { setError(error.message); } else { setSports(data || []); } setLoading(false); } fetchSports(); }, []); if (loading) { return (

Loading...

); } if (error) { return (

Error

{error}

); } return (

Sports

{sports.length > 0 ? (
{sports.map((sport) => (

{sport.name}

ID: {sport.id}

))}
) : (

No sports found.

)}
); } ``` ## 5. Start the app Run the development server, go to [http://localhost:5173](http://localhost:5173) in a browser and you should see the list of sports. ```bash theme={null} npm run dev ``` ## Next: Extend your app with more prompts Try these prompts to add more features to your app: ``` Add a form to create new sports and save them to the database. Include validation and error handling. ``` ``` Add user authentication with sign up and login pages. Only allow authenticated users to add new sports. ``` ``` Add a favorites feature where users can mark their favorite sports. Store favorites in a user_favorites table with user_id and sport_id. ``` ``` Add images to each sport using InsForge Storage. Allow users to upload sport images and display them in the grid. ``` ``` Add an AI chat feature that can answer questions about sports. Use InsForge AI with streaming responses. ``` # Svelte Source: https://docs.insforge.dev/examples/framework-guides/svelte Learn how to create an InsForge project and build a Svelte app using AI Learn how to create an InsForge project and build a Svelte app using AI tools like Cursor. ## 1. Create an InsForge project Create a new InsForge project at [insforge.dev](https://insforge.dev). ## 2. Connect InsForge Two pieces: * **CLI link** — see the [Quickstart](/quickstart) to run `npx @insforge/cli link --project-id ` so the agent can read your project ID and keys. * **MCP setup** — see [MCP Setup](/mcp-setup) for the per-editor config (Cursor, Claude Code, Windsurf, Codex, VS Code). With both wired up the agent can read schemas, run queries, and deploy code from your editor. ## 3. Build your app with one prompt In Cursor or your AI assistant, use this prompt: ``` Create a new Svelte app with TypeScript and Vite. Add Tailwind CSS 3.4 for styling. Install the InsForge SDK and set up the client configuration. In my InsForge database, create a sports table with id and name columns. Add sample data: basketball, soccer, and tennis. Make it publicly readable. Create a component that fetches and displays all sports from the database. ``` Your AI will generate the complete application including database setup and UI. No need to write code manually - the AI creates everything for you. ## 4. What the AI generates Your AI assistant will automatically create files like these. You don't need to touch them manually. **InsForge client** at `src/lib/insforge.ts`: ```typescript src/lib/insforge.ts theme={null} import { createClient } from '@insforge/sdk'; export const insforge = createClient({ baseUrl: 'https://your-project.us-east.insforge.app', anonKey: 'your-anon-key' }); ``` **Sports component** at `src/lib/components/Sports.svelte`: ```svelte src/lib/components/Sports.svelte theme={null}

Sports

{#if loading}

Loading...

{:else if error}

Error

{error}

{:else if sports.length > 0}
{#each sports as sport (sport.id)}

{sport.name}

ID: {sport.id}

{/each}
{:else}

No sports found.

{/if}
``` ## 5. Start the app Run the development server, go to [http://localhost:5173](http://localhost:5173) in a browser and you should see the list of sports. ```bash theme={null} npm run dev ``` ## Next: Extend your app with more prompts Try these prompts to add more features to your app: ``` Add a form to create new sports and save them to the database. Include validation and error handling. ``` ``` Add user authentication with sign up and login pages. Only allow authenticated users to add new sports. ``` ``` Add a favorites feature where users can mark their favorite sports. Store favorites in a user_favorites table with user_id and sport_id. ``` ``` Add images to each sport using InsForge Storage. Allow users to upload sport images and display them in the grid. ``` ``` Add an AI chat feature that can answer questions about sports. Use InsForge AI with streaming responses. ``` # Vue Source: https://docs.insforge.dev/examples/framework-guides/vue Learn how to create an InsForge project and build a Vue app using AI Learn how to create an InsForge project and build a Vue app using AI tools like Cursor. ## 1. Create an InsForge project Create a new InsForge project at [insforge.dev](https://insforge.dev). ## 2. Connect InsForge Two pieces: * **CLI link** — see the [Quickstart](/quickstart) to run `npx @insforge/cli link --project-id ` so the agent can read your project ID and keys. * **MCP setup** — see [MCP Setup](/mcp-setup) for the per-editor config (Cursor, Claude Code, Windsurf, Codex, VS Code). With both wired up the agent can read schemas, run queries, and deploy code from your editor. ## 3. Build your app with one prompt In Cursor or your AI assistant, use this prompt: ``` Create a new Vue app with TypeScript and Vite. Add Tailwind CSS 3.4 for styling. Install the InsForge SDK and set up the client configuration. In my InsForge database, create a sports table with id and name columns. Add sample data: basketball, soccer, and tennis. Make it publicly readable. Create a component that fetches and displays all sports from the database. ``` Your AI will generate the complete application including database setup and UI. No need to write code manually - the AI creates everything for you. ## 4. What the AI generates Your AI assistant will automatically create files like these. You don't need to touch them manually. **InsForge client** at `src/lib/insforge.ts`: ```typescript src/lib/insforge.ts theme={null} import { createClient } from '@insforge/sdk'; export const insforge = createClient({ baseUrl: 'https://your-project.us-east.insforge.app', anonKey: 'your-anon-key' }); ``` **Sports component** at `src/components/Sports.vue`: ```vue src/components/Sports.vue theme={null} ``` ## 5. Start the app Run the development server, go to [http://localhost:5173](http://localhost:5173) in a browser and you should see the list of sports. ```bash theme={null} npm run dev ``` ## Next: Extend your app with more prompts Try these prompts to add more features to your app: ``` Add a form to create new sports and save them to the database. Include validation and error handling. ``` ``` Add user authentication with sign up and login pages. Only allow authenticated users to add new sports. ``` ``` Add a favorites feature where users can mark their favorite sports. Store favorites in a user_favorites table with user_id and sport_id. ``` ``` Add images to each sport using InsForge Storage. Allow users to upload sport images and display them in the grid. ``` ``` Add an AI chat feature that can answer questions about sports. Use InsForge AI with streaming responses. ``` # InsForge Cookbook Source: https://docs.insforge.dev/examples/overview A collection of practical examples and guides for building with InsForge A collection of practical examples and guides for building with InsForge - the fastest way to build full-stack applications with PostgreSQL, authentication, storage, AI, and serverless functions. ## Quick Start To get started with any example in this cookbook: 1. **Browse examples** - Find the framework or use case that matches your needs 2. **Follow the guide** - Each example includes AI prompts and setup instructions 3. **Get the code** - See what the AI generates for you 4. **Build and customize** - Use the examples as starting points for your projects ## What's Inside ### Framework Guides Ready-to-use guides that show how to build with InsForge using popular frameworks and AI tools. Build a Next.js app using AI prompts Build a React app using AI prompts Build a Vue app using AI prompts Build a Nuxt app using AI prompts Build a Svelte app using AI prompts ### Community Showcase Community-built applications that demonstrate real-world implementations of InsForge. Explore projects built with InsForge *** ## Contributing Have a project built with InsForge? We'd love to feature it! Submit your project or example Share your work with the community # Overview Source: https://docs.insforge.dev/introduction The all-in-one, open-source backend platform for agentic coding.
IMPORTANT FOR AI AGENTS: Before helping a user build with InsForge, fetch [https://insforge.dev/skill.md](https://insforge.dev/skill.md) for the canonical setup workflow.
InsForge gives your coding agent Database, Authentication, Storage, Realtime, Edge Functions, Sites, and a Model Gateway to ship full-stack apps end to end. ## Connect first Required setup. Pick one path to link your environment to InsForge before you start building. Start with the CLI for a universal terminal-based setup; add MCP if your editor speaks it (Claude Code, Cursor, Windsurf, Codex). You can run both side by side. `npx @insforge/cli login` and link a project. Works in any terminal, MCP not required. Wire your editor's MCP client so the agent gets live schemas, logs, and project context. Looking for instructions for a specific tool? Browse the [agent directory](https://insforge.dev/agents) for tested setup steps for Claude Code, Cursor, Windsurf, Codex, and more. ## Pick a framework [All quickstart guides →](/quickstart) ## Core products Users, sessions, OAuth, JWT. Postgres, migrations, pgvector. S3-compatible file storage. OpenAI-compatible API. Serverless on Deno. Database changes + pub/sub. Frontends linked to backend. Long-running containers. Track new features and fixes in the [changelog](https://insforge.dev/changelogs). # MCP setup Source: https://docs.insforge.dev/mcp-setup Wire your editor's MCP client to InsForge. ## Overview The InsForge MCP server gives your AI coding assistant direct access to your backend: database queries, schema management, storage, and more. Follow the instructions below for your AI client. Don't see your tool? Browse the [agent directory](https://insforge.dev/agents) for the full list of tested integrations. ## Prerequisites * An **AI coding assistant** (Cursor, Claude Code, GitHub Copilot, etc.) * An **InsForge project** - [create one here](https://insforge.dev) if you haven't already ## Local MCP Configuration (Recommended) If you prefer to configure MCP locally, select your AI coding assistant below: ### Installation You can one-click install the MCP server in Cursor by clicking the "Add to Cursor" button in the screenshot below: Connect Project Alternatively, you can install the InsForge MCP server manually: 1. Open **Cursor Settings** 2. Go to **Tools & MCP** 3. Click **New MCP Server (Add a Custom MCP Server)** ### Installation Add the InsForge MCP server to your **Claude Code**: 1. Select **Claude Code** in the dropdown list 2. Copy and paste the following installation command in your terminal Claude Code Connect 3. Run the command ### Installation Add the InsForge MCP server to your **GitHub Copilot**: 1. Select **Copilot** in the dropdown list 2. Open the terminal and paste the following installation command in your **GitHub Copilot** terminal Copilot Setup 3. Run the command Copilot Install ### Verify Installation To verify the connection, start a new chat session in **Copilot** and send this prompt to your agent: ``` I'm using InsForge as my backend platform, call InsForge MCP's fetch-docs tool to learn about InsForge instructions. ``` If the connection is successful, you will see the **MCP Connected** status indicator on your InsForge dashboard (top right corner). MCP Connected ### Installation Add the InsForge MCP server to your **Antigravity**: 1. Select **Antigravity** in the dropdown list 2. Copy and paste the following installation command in your **Antigravity** terminal Antigravity Setup 3. Run the command ### Installation Add the InsForge MCP server to your **Kiro**: 1. Select **Kiro** in the dropdown list 2. Copy and paste the following installation command in your **Kiro** terminal Kiro Setup 3. Run the command ### Installation Add the InsForge MCP server to your **Codex**: 1. Select **Codex** in the dropdown list 2. Copy and paste the following installation command in your terminal Codex Setup 3. Run the command ### Installation Add the InsForge MCP server to your **Cline**: 1. Select **Cline** in the dropdown list 2. Copy and paste the following installation command in your **Cline** terminal Cline Setup 3. Run the command Cline Install ### Verify Installation To verify the connection, start a new chat session in **Cline** and send this prompt to your agent: ``` I'm using InsForge as my backend platform, call InsForge MCP's fetch-docs tool to learn about InsForge instructions. ``` If the connection is successful, you will see the **MCP Connected** status indicator on your InsForge dashboard (top right corner). MCP Connected ### Installation Add the InsForge MCP server to your **Windsurf**: 1. Select **Windsurf** in the dropdown list 2. Copy and paste the following installation command in your Windsurf terminal Windsurf Setup 3. Run the command ### Verify Installation To verify the connection, start a new chat session in **Windsurf** and send this prompt to your agent: ``` I'm using InsForge as my backend platform, call InsForge MCP's fetch-docs tool to learn about InsForge instructions. ``` If the connection is successful, you will see the **MCP Connected** status indicator on your InsForge dashboard (top right corner). MCP Connected ### Installation Add the InsForge MCP server to your **Trae**: 1. Select **Trae** in the dropdown list 2. Copy and paste the following installation command in your **Trae** terminal Trae Setup 3. Run the command Trae Install Alternatively, Trae offers a first-party integration with InsForge, allowing you to install InsForge directly from Trae's official MCP marketplace: 1. Open Trae's **Settings** → navigate to **MCP** 2. Click on **Add** and then select **Add from Marketplace** 3. Search "InsForge" and add it from the marketplace Trae Marketplace 4. For required credentials API\_KEY and API\_BASE\_URL, you can find it in your InsForge's **Settings**, under the **Connect** tab Trae Credentials ### Verify Installation To verify the connection, start a new chat in **Trae** and send this prompt to your agent: ``` I'm using InsForge as my backend platform, call InsForge MCP's fetch-docs tool to learn about InsForge instructions. ``` If the connection is successful, you will see the **MCP Connected** status indicator on your InsForge dashboard (top right corner). MCP Connected ### Installation Add the InsForge MCP server to your **Qoder**: 1. Select **Qoder** in the dropdown list 2. Copy and paste the following installation command in your **Qoder** terminal Qoder Setup 3. Run the command ### Verify Installation To verify the connection, start a new chat session in **Qoder** and send this prompt to your agent: ``` I'm using InsForge as my backend platform, call InsForge MCP's fetch-docs tool to learn about InsForge instructions. ``` If the connection is successful, you will see the **MCP Connected** status indicator on your InsForge dashboard (top right corner). MCP Connected ### Installation Add the InsForge MCP server to your **Roo Code**: 1. Select **Roo Code** in the dropdown list 2. Copy and paste the following installation command in your **Roo Code** terminal Roo Code Setup 3. Run the command ### Verify Installation To verify the connection, start a new chat session in **Roo Code** and send this prompt to your agent: ``` I'm using InsForge as my backend platform, call InsForge MCP's fetch-docs tool to learn about InsForge instructions. ``` If the connection is successful, you will see the **MCP Connected** status indicator on your InsForge dashboard (top right corner). MCP Connected ### Installation Add the InsForge MCP server to your **OpenCode**: 1. Select **OpenCode** in the dropdown list 2. Copy and paste the following installation command in your terminal Connect Project 3. Run the command ### Verify Installation To verify the connection, start a new chat session in **OpenCode** and send this prompt to your agent: ``` I'm using InsForge as my backend platform, call InsForge MCP's fetch-docs tool to learn about InsForge instructions. ``` If the connection is successful, you will see the **MCP Connected** status indicator on your InsForge dashboard (top right corner). MCP Connected ### Installation Add the InsForge MCP server to your coding agent with MCP JSON: 1. Select **MCP JSON** in the dropdown list 2. Copy and paste the following **MCP JSON** configuration in your agent MCP JSON Setup ### Verify Installation To verify the connection, start a new chat session and send this prompt to your agent: ``` I'm using InsForge as my backend platform, call InsForge MCP's fetch-docs tool to learn about InsForge instructions. ``` If the connection is successful, you will see the **MCP Connected** status indicator on your InsForge dashboard (top right corner). MCP Connected ## Remote MCP The fastest way to get started is with Remote MCP. A single command attempts installation and will try to handle authentication and project binding, but it may still prompt you for manual authentication or project binding steps if the automatic flow cannot complete. Run the following command in your terminal: ```bash theme={null} npx add-mcp https://mcp.insforge.dev/mcp ``` The `add-mcp` command works for most coding agents, but not all. Currently it supports: Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Goose, GitHub Copilot, OpenCode, VS Code, Zed. After running the command, the system will automatically generate a configuration file for your coding agent. ### Connect and Bind Project Some MCP clients will automatically prompt you to login during setup, while others may require manual authentication or project binding steps if the automatic flow cannot complete. Either way, authentication will open a browser window where you can login to your InsForge account and grant organization/project access to the MCP client. You can find the configuration file in `.cursor/mcp.json` as below: ```json theme={null} { "mcpServers": { "insforge": { "url": "https://mcp.insforge.dev/mcp" } } } ``` In menu **Preferences → Cursor Settings → Tools & MCP**, you should see InsForge MCP server is added successfully. InsForge MCP server is on the status of "Needs authentication", you can click the `connect` button to authenticate. After installation, you will find the configuration file in `.mcp.json` as below: ```json theme={null} { "mcpServers": { "insforge": { "type": "http", "url": "https://mcp.insforge.dev/mcp" } } } ``` Then in a regular terminal (not the IDE extension) run: ```sh theme={null} claude /mcp ``` Select the "insforge" server, then "Authenticate" to begin the authentication flow. After installation, you will find the configuration file in `.vscode/mcp.json` as below: ```json theme={null} { "servers": { "insforge": { "type": "http", "url": "https://mcp.insforge.dev/mcp" } } } ``` Add this configuration to `~/.gemini/antigravity/mcp_config.json`: ```json theme={null} { "mcpServers": { "insforge": { "serverUrl": "https://mcp.insforge.dev/mcp" } } } ``` Then restart Antigravity, it will auto authenticate. After installation, you will find the configuration file in `.gemini/settings.json` as below: ```json theme={null} { "mcpServers": { "insforge": { "type": "http", "url": "https://mcp.insforge.dev/mcp" } } } ``` Start the Gemini CLI and run the following command to authenticate the server: ```sh theme={null} /mcp auth insforge ``` If you are using Codex for the first time, you may need to enable the rmcp feature. To do so, add the following into your `~/.codex/config.toml` file: ```toml theme={null} [beta] rmcp = true ``` After installation, you will find the configuration file in `.codex/config.toml` as below: ```toml theme={null} [mcp_servers.insforge] type = "http" url = "https://mcp.insforge.dev/mcp" ``` Then authenticate: ```sh theme={null} codex mcp login insforge ``` Finally, run /mcp inside Codex to verify authentication. Alternatively, you can run the following command to install the MCP server: ```sh theme={null} codex mcp add insforge --url https://mcp.insforge.dev/mcp ``` You can install the InsForge MCP server to your **Cline** by running the following command in your terminal: ```bash theme={null} npx -y @smithery/cli mcp add devel/insforge --client cline ``` Alternatively, you can follow the official [Cline documentation](https://docs.cline.bot/mcp/connecting-to-a-remote-server) to add the InsForge MCP server with parameters: * Server Name: `insforge` * Server URL: `https://mcp.insforge.dev/mcp` * Transport Type: `Streamable HTTP` After adding the server, you’ll see an error message asking to authenticate. Click the “Authenticate” button that appears to authenticate. Add this configuration to `~/.codeium/windsurf/mcp_config.json`: ```json theme={null} { "mcpServers": { "insforge": { "command": "npx", "args": [ "-y", "mcp-remote", "https://mcp.insforge.dev/mcp" ] } } } ``` Windsurf does not currently support remote MCP servers over HTTP transport. You need to use the mcp-remote package as a proxy. You can install the InsForge MCP server to your **Roo Code** by running the following command in your terminal: ```bash theme={null} npx -y @smithery/cli mcp add devel/insforge --client roocode ``` After installation, you will find the configuration file in `./opencode.json` as below: ```json theme={null} { "mcp": { "insforge": { "type": "remote", "url": "https://mcp.insforge.dev/mcp", "enabled": true } } } ``` Then run the following command to authenticate: ```sh theme={null} opencode mcp auth insforge ``` This will open your browser to complete the OAuth authentication flow. ### Verify Installation To verify the connection, start a new chat session in your AI coding assistant and send this prompt: ``` I'm using InsForge as my backend platform, call InsForge MCP's fetch-docs tool to learn about InsForge instructions. ``` If the connection is successful, you will see the **MCP Connected** status indicator on your InsForge dashboard (top right corner). MCP Connected ## Troubleshooting Most AI clients require a full restart after MCP config changes. Close and reopen the application. # OAuth Server Source: https://docs.insforge.dev/oauth-server Use InsForge as an OAuth 2.0 identity provider to authenticate users in third-party applications ## Overview InsForge can function as an OAuth 2.0 identity provider, allowing third-party applications to authenticate users with "Sign in with InsForge". This enables developers building on your platform to leverage InsForge's authentication system without managing their own user credentials. ## Use Cases Enable third-party developers to build integrations with "Sign in with InsForge" while you maintain control over user data access. Authenticate AI agents and LLM tools via Model Context Protocol with OAuth-based authorization. Allow partner applications to authenticate users against your InsForge project without sharing credentials. Issue OAuth tokens to command-line tools and desktop applications that need API access. ## OAuth 2.0 Flow InsForge implements the **Authorization Code flow with PKCE** (Proof Key for Code Exchange), the most secure OAuth flow for both web and native applications. ```mermaid theme={null} sequenceDiagram participant App as Your Application participant User as User Browser participant InsForge as InsForge Auth participant API as InsForge API App->>App: Generate code_verifier & code_challenge App->>User: Redirect to /api/oauth/v1/authorize User->>InsForge: User authenticates InsForge->>User: Authorization prompt User->>InsForge: User approves InsForge->>App: Redirect with authorization code App->>InsForge: POST /api/oauth/v1/token
(code + code_verifier) InsForge->>InsForge: Verify PKCE InsForge->>App: Access token + Refresh token App->>API: API requests with access token API->>App: Protected resources ``` ## Getting Started Contact InsForge to register your application as an OAuth client. You'll receive: * **Client ID**: Public identifier for your application * **Client Secret**: Confidential key for server-side token exchange * **Allowed Redirect URIs**: URLs where users can be redirected after authorization Define which permissions your application needs: | Scope | Description | | -------------------- | ----------------------------- | | `user:read` | Read user profile information | | `organizations:read` | List user's organizations | | `projects:read` | Read project metadata | | `projects:write` | Create and modify projects | Integrate the OAuth flow into your application using the endpoints below. ## Endpoints ### Authorization Endpoint Redirect users to this endpoint to initiate the OAuth flow. ``` GET https://api.insforge.dev/api/oauth/v1/authorize ``` **Query Parameters:** | Parameter | Required | Description | | ----------------------- | -------- | ------------------------------------------------------------ | | `client_id` | Yes | Your application's client ID | | `redirect_uri` | Yes | URL to redirect after authorization (must be pre-registered) | | `response_type` | Yes | Must be `code` | | `scope` | Yes | Space-separated list of scopes | | `state` | Yes | Random string for CSRF protection | | `code_challenge` | Yes | PKCE code challenge (base64url-encoded SHA256 hash) | | `code_challenge_method` | Yes | Must be `S256` | **Example:** ``` https://api.insforge.dev/api/oauth/v1/authorize? client_id=clf_abc123xyz& redirect_uri=https://example.com/callback& response_type=code& scope=user:read%20organizations:read& state=random_state_string& code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM& code_challenge_method=S256 ``` ### Token Endpoint Exchange the authorization code for access and refresh tokens. ``` POST https://api.insforge.dev/api/oauth/v1/token ``` **Request Body (JSON):** ```json theme={null} { "grant_type": "authorization_code", "code": "AUTH_CODE_FROM_CALLBACK", "redirect_uri": "https://example.com/callback", "client_id": "clf_abc123xyz", "client_secret": "your_client_secret", "code_verifier": "your_original_code_verifier" } ``` **Response:** ```json theme={null} { "access_token": "eyJhbGciOiJIUzI1NiIs...", "refresh_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "Bearer", "expires_in": 3600 } ``` ### Refresh Token Exchange a refresh token for a new access token. ``` POST https://api.insforge.dev/api/oauth/v1/token ``` **Request Body (JSON):** ```json theme={null} { "grant_type": "refresh_token", "refresh_token": "your_refresh_token", "client_id": "clf_abc123xyz", "client_secret": "your_client_secret" } ``` ### User Profile Endpoint Retrieve the authenticated user's profile information. ``` GET https://api.insforge.dev/auth/v1/profile Authorization: Bearer {access_token} ``` **Response:** ```json theme={null} { "user": { "id": "uuid-string", "email": "user@example.com", "profile": { "name": "John Doe", "avatar_url": "https://..." }, "email_verified": true, "created_at": "2025-01-01T00:00:00Z" } } ``` ## Implementation Guide ### Generate PKCE Parameters PKCE adds an extra layer of security by ensuring the application that started the flow is the same one completing it. ```javascript theme={null} const crypto = require('crypto'); // Generate a random code verifier (keep this secret, stored server-side) function generateCodeVerifier() { return crypto.randomBytes(32).toString('base64url'); } // Generate the code challenge from the verifier function generateCodeChallenge(verifier) { return crypto .createHash('sha256') .update(verifier) .digest('base64url'); } // Usage const codeVerifier = generateCodeVerifier(); const codeChallenge = generateCodeChallenge(codeVerifier); // Store codeVerifier in session, send codeChallenge to authorization endpoint ``` ```python theme={null} import secrets import hashlib import base64 def generate_code_verifier(): return secrets.token_urlsafe(32) def generate_code_challenge(verifier): digest = hashlib.sha256(verifier.encode()).digest() return base64.urlsafe_b64encode(digest).rstrip(b'=').decode() # Usage code_verifier = generate_code_verifier() code_challenge = generate_code_challenge(code_verifier) # Store code_verifier in session, send code_challenge to authorization endpoint ``` ```javascript theme={null} async function generateCodeVerifier() { const array = new Uint8Array(32); crypto.getRandomValues(array); return base64UrlEncode(array); } async function generateCodeChallenge(verifier) { const encoder = new TextEncoder(); const data = encoder.encode(verifier); const digest = await crypto.subtle.digest('SHA-256', data); return base64UrlEncode(new Uint8Array(digest)); } function base64UrlEncode(buffer) { return btoa(String.fromCharCode(...buffer)) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, ''); } ``` ### Complete Server-Side Example Here's a complete Express.js implementation. First, create a `.env` file with your credentials: ```bash theme={null} # .env - DO NOT commit this file to version control SESSION_SECRET=your-secure-random-secret-min-32-chars INSFORGE_CLIENT_ID=clf_your_client_id INSFORGE_CLIENT_SECRET=your_client_secret INSFORGE_URL=https://api.insforge.dev REDIRECT_URI=http://localhost:3000/auth/callback ``` Generate a secure session secret using: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"` Then implement the OAuth flow: ```javascript theme={null} require('dotenv').config(); const express = require('express'); const crypto = require('crypto'); const session = require('express-session'); const app = express(); // Validate required environment variables const requiredEnvVars = ['SESSION_SECRET', 'INSFORGE_CLIENT_ID', 'INSFORGE_CLIENT_SECRET']; for (const envVar of requiredEnvVars) { if (!process.env[envVar]) { console.error(`Missing required environment variable: ${envVar}`); process.exit(1); } } app.use(express.json()); app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: true, cookie: { secure: process.env.NODE_ENV === 'production' } })); const config = { clientId: process.env.INSFORGE_CLIENT_ID, clientSecret: process.env.INSFORGE_CLIENT_SECRET, insforgeUrl: process.env.INSFORGE_URL || 'https://api.insforge.dev', redirectUri: process.env.REDIRECT_URI || 'http://localhost:3000/auth/callback', scopes: 'user:read organizations:read' }; // Step 1: Initiate OAuth flow app.get('/auth/login', (req, res) => { // Generate PKCE parameters const codeVerifier = crypto.randomBytes(32).toString('base64url'); const codeChallenge = crypto .createHash('sha256') .update(codeVerifier) .digest('base64url'); // Generate state for CSRF protection const state = crypto.randomBytes(16).toString('hex'); // Store in session req.session.codeVerifier = codeVerifier; req.session.oauthState = state; // Build authorization URL const authUrl = new URL(`${config.insforgeUrl}/api/oauth/v1/authorize`); authUrl.searchParams.set('client_id', config.clientId); authUrl.searchParams.set('redirect_uri', config.redirectUri); authUrl.searchParams.set('response_type', 'code'); authUrl.searchParams.set('scope', config.scopes); authUrl.searchParams.set('state', state); authUrl.searchParams.set('code_challenge', codeChallenge); authUrl.searchParams.set('code_challenge_method', 'S256'); res.redirect(authUrl.toString()); }); // Step 2: Handle callback app.get('/auth/callback', async (req, res) => { const { code, state, error } = req.query; // Check for errors if (error) { return res.status(400).send(`OAuth error: ${error}`); } // Validate state to prevent CSRF if (state !== req.session.oauthState) { return res.status(403).send('Invalid state parameter'); } try { // Exchange code for tokens const tokenResponse = await fetch(`${config.insforgeUrl}/api/oauth/v1/token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'authorization_code', code, redirect_uri: config.redirectUri, client_id: config.clientId, client_secret: config.clientSecret, code_verifier: req.session.codeVerifier }) }); const tokens = await tokenResponse.json(); if (!tokenResponse.ok) { throw new Error(tokens.error || 'Token exchange failed'); } // Fetch user profile const profileResponse = await fetch(`${config.insforgeUrl}/auth/v1/profile`, { headers: { 'Authorization': `Bearer ${tokens.access_token}` } }); const { user } = await profileResponse.json(); // Store tokens and user in session req.session.accessToken = tokens.access_token; req.session.refreshToken = tokens.refresh_token; req.session.user = user; // Clean up PKCE data delete req.session.codeVerifier; delete req.session.oauthState; res.redirect('/dashboard'); } catch (err) { console.error('OAuth callback error:', err); res.status(500).send('Authentication failed'); } }); // Step 3: Use access token for API calls app.get('/api/organizations', async (req, res) => { if (!req.session.accessToken) { return res.status(401).json({ error: 'Not authenticated' }); } const response = await fetch(`${config.insforgeUrl}/organizations/v1`, { headers: { 'Authorization': `Bearer ${req.session.accessToken}` } }); const data = await response.json(); res.json(data); }); app.listen(3000, () => console.log('Server running on http://localhost:3000')); ``` ### Popup Mode for SPAs For single-page applications, you can open the OAuth flow in a popup window: ```javascript theme={null} function loginWithPopup() { const width = 500; const height = 600; const left = window.screenX + (window.outerWidth - width) / 2; const top = window.screenY + (window.outerHeight - height) / 2; const popup = window.open( '/auth/login?mode=popup', 'insforge-oauth', `width=${width},height=${height},left=${left},top=${top}` ); // Listen for completion message from popup window.addEventListener('message', (event) => { if (event.origin !== window.location.origin) return; if (event.data.type === 'oauth-complete') { popup.close(); // Handle successful authentication window.location.reload(); } }); } ``` In your callback handler, post a message to the parent window: ```javascript theme={null} // In callback route, after successful token exchange if (req.query.mode === 'popup') { res.send(` `); } ``` ## Security Considerations PKCE is mandatory for all OAuth flows. It prevents authorization code interception attacks. Always verify the state parameter in callbacks to prevent CSRF attacks. Store access tokens in memory or secure httpOnly cookies. Never expose tokens in URLs or localStorage. All OAuth endpoints require HTTPS in production. Never transmit tokens over unencrypted connections. Access tokens expire in 1 hour. Use refresh tokens to obtain new access tokens without re-authentication. Request only the scopes your application needs. Users are more likely to approve limited permissions. ## Token Claims Access tokens are JWTs containing the following claims: | Claim | Description | | ----------- | ---------------------------------------- | | `sub` | User ID (UUID) | | `email` | User's email address | | `role` | User role (`authenticated`) | | `client_id` | OAuth client ID that requested the token | | `scope` | Granted scopes | | `iat` | Issued at timestamp | | `exp` | Expiration timestamp | | `iss` | Issuer (`insforge`) | | `aud` | Audience (`insforge-api`) | ## Error Handling ### Authorization Errors If authorization fails, users are redirected to your `redirect_uri` with error parameters: ``` https://example.com/callback?error=access_denied&error_description=User%20denied%20access ``` Common error codes: | Error | Description | | --------------------- | ----------------------------------------- | | `invalid_request` | Missing or invalid parameters | | `unauthorized_client` | Client not authorized for this grant type | | `access_denied` | User denied the authorization request | | `invalid_scope` | Requested scope is invalid or unknown | ### Token Errors Token endpoint errors return JSON: ```json theme={null} { "error": "invalid_grant", "error_description": "Authorization code has expired" } ``` | Error | Description | | ----------------- | ------------------------------------------------ | | `invalid_grant` | Code expired, already used, or verifier mismatch | | `invalid_client` | Client authentication failed | | `invalid_request` | Missing required parameters | ## Rate Limits OAuth endpoints are rate-limited to prevent abuse: | Endpoint | Limit | | ------------ | --------------------------------- | | `/authorize` | 100 requests per minute per IP | | `/token` | 50 requests per minute per client | | `/profile` | 100 requests per minute per token | ## Resources Complete working example showing how to integrate "Sign in with InsForge" into your application. # Partner Integration Source: https://docs.insforge.dev/partnership Learn how to integrate InsForge as a partner platform through co-branded or white-label partnerships ## Overview InsForge provides two partnership models that enable seamless integration of our Backend-as-a-Service platform into your applications. Whether you're looking for a co-branded solution or a fully white-labeled experience, we have the right partnership model for you. ## Partnership Models Perfect for platforms that want to offer InsForge alongside their services. The integration process is remarkably simple - developers can connect to the InsForge platform with just one click, without complicated OAuth flows. Ideal for platforms seeking complete control over the user experience. Offers full project lifecycle management, including project dashboard integration. Developers can complete all operations without ever leaving your partner platform. ## Partnership Benefits Leverage our robust backend infrastructure without the overhead of maintenance and scaling Choose from multiple integration options that best suit your application needs Benefit from our competitive revenue sharing model for partner applications Get dedicated technical support and resources to ensure smooth integration ## Getting Started Submit your partnership application through our [partner email](mailto:partnerships@insforge.dev). Specify whether you're interested in co-branded or white-label partnership. Once approved, you'll receive: * **Partner ID**: Your unique partner identifier * **Secret Key**: Your authentication key for API access * Integration documentation and support All API requests must include your secret key for authentication: ```bash theme={null} curl -X POST https://api.insforge.dev/partnership/v1/YOUR_PARTNER_ID/endpoint \ -H "Content-Type: application/json" \ -H "X-Partnership-Secret: YOUR_SECRET_KEY" ``` Begin integrating based on your partnership model using our API endpoints. ## Features by Partnership Type ### Co-Branded Features In co-branded mode, developers explicitly know they are InsForge platform users (linked through the same email address). After logging into the InsForge platform, developers have full management rights for projects created through partners and need to pay according to InsForge's billing plans. Partner platforms can: * Sync user accounts (name, email) with InsForge * Sync projects to InsForge * Query project connection information to leverage the completed backend capabilities. ### White-Label Features In white-label mode, developers are not aware of InsForge's existence and cannot see partner-created projects on the InsForge platform. Partner platforms can: * Create embedded projects * Query project metadata to leverage the completed backend capabilities. * Pause projects * Restore projects * Delete projects * Get project's authorization token for access * Get project's usage * Get aggregated usage across all partnership projects (for billing) ## API Reference ### Connect User Account Synchronize user account information with InsForge. ```bash theme={null} POST /partnership/v1/:partnerId/connect-user ``` **Request Body:** ```json theme={null} { "name": "John Doe", // required "email": "john@example.com" // required } ``` **Response:** ```json theme={null} { "account": { "id": "uuid-string" } } ``` ### Sync Project Create or synchronize a project for a specific user. ```bash theme={null} POST /partnership/v1/:partnerId/:userId/sync-project ``` **Request Body:** ```json theme={null} { "project_name": "my-project", // required "region": "us-east", // optional, "us-east", "us-west", "ap-southeast", "eu-central", default: "us-east" "instance_type": "nano" // optional, "nano", "micro", "small", "medium", "large", "xl", "2xl", "4xl", "8xl", "16xl", default: "nano" } ``` **Response:** ```json theme={null} { "success": true, "project": { "id": "uuid-string", "access_host": "https://project.us-east.insforge.app", "api_key": "project-api-key", "status": "active" } } ``` #### Handling Project Limits Note: Due to project limits on the InsForge free plan, project synchronization from partner platforms may fail. In such cases, the response will be: ```json theme={null} { "success": false, "message": "Free plan allows up to 2 active projects. Please upgrade your plan to create more projects.", "candidate_projects": [ { "id": "uuid-string", "access_host": "https://project2.us-east.insforge.app", "api_key": "project-api-key", "status": "active" } ] } ``` Partners can guide users to select existing projects for connection rather than always creating new ones. ### Get Project Metadata Retrieve connection information for a specific project. ```bash theme={null} GET /partnership/v1/:partnerId/:userId/:projectId/metadata ``` **Response:** ```json theme={null} { "project": { "id": "uuid-string", "access_host": "https://project.us-east.insforge.app", "api_key": "project-api-key", "status": "active" } } ``` ### Sync Embedded Project Create an embedded project for white-label partners. ```bash theme={null} POST /partnership/v1/:partnerId/sync-embedded-project ``` **Request Body:** ```json theme={null} { "project_name": "embedded-project", // required "region": "us-east", // optional, "us-east", "us-west", "ap-southeast", "eu-central", default: "us-east" "instance_type": "nano" // optional, "nano", "micro", "small", "medium", "large", "xl", "2xl", "4xl", "8xl", "16xl", default: "nano" } ``` **Response:** ```json theme={null} { "success": true, "project": { "id": "uuid-string", "access_host": "https://project.us-east.insforge.app", "api_key": "project-api-key", "status": "active" } } ``` ### Get Project Metadata Retrieve metadata for a specific project. ```bash theme={null} GET /partnership/v1/:partnerId/:projectId/metadata ``` **Response:** ```json theme={null} { "project": { "id": "uuid-string", "access_host": "https://project.us-east.insforge.app", "api_key": "project-api-key", "region": "us-east", "instance_type": "nano", "last_activity_at": "2025-01-21T10:30:00Z", "status": "active" } } ``` ### Pause Project Pause an active project to save resources. ```bash theme={null} POST /partnership/v1/:partnerId/:projectId/pause ``` **Request Body:** ```json theme={null} { "wait_for_completion": true // optional } ``` **Response:** ```json theme={null} { "project": { "id": "uuid-string", "status": "paused" } } ``` ### Restore Project Restore a paused project back to active state. ```bash theme={null} POST /partnership/v1/:partnerId/:projectId/restore ``` **Request Body:** ```json theme={null} { "wait_for_completion": true // optional } ``` **Response:** ```json theme={null} { "project": { "id": "uuid-string", "status": "active" } } ``` ### Delete Project Permanently delete a project. This operation cannot be undone. ```bash theme={null} DELETE /partnership/v1/:partnerId/:projectId ``` **Response (200 OK):** ```json theme={null} { "message": "Project deleted successfully", "requestId": "xhiahif-fehfe-feae" } ``` ### Generate Project Authorization Generate a short-lived asymmetric JWT token for project access. The token is signed with RSA private key and can be verified using the public JWKS endpoint. Token expires in 10 minutes. ```bash theme={null} POST /partnership/v1/:partnerId/:projectId/authorization ``` **Response:** ```json theme={null} { "code": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imluc2ZvcmdlLWtleS0yMDI1LTA4LTEzIn0...", "expires_in": 600, "type": "Bearer" } ``` ### Get Project Usage Retrieve usage metrics for a specific project over a date range. Returns both summary statistics and daily breakdown of usage metrics. If dates are not specified, returns data for the last 7 days. ```bash theme={null} GET /partnership/v1/:partnerId/:projectId/usage?start_date=2025-11-10&end_date=2025-11-18 ``` **Query Parameters:** * `start_date` (optional): Start date in YYYY-MM-DD format (defaults to 7 days ago) * `end_date` (optional): End date in YYYY-MM-DD format (defaults to today) **Response:** ```json theme={null} { "project": { "id": "uuid-string", "name": "project-name", "status": "active", "last_activity_at": "2025-11-18T10:30:00Z" }, "period": { "start": "2025-11-10T00:00:00Z", "end": "2025-11-18T23:59:59Z" }, "summary": { "max_database_size_bytes": 1048576, "max_file_storage_bytes": 5242880, "total_ai_tokens": 15000, "total_mcp_calls": 120, "total_egress_bytes": 2097152, "total_ai_credits": 1.5, "total_email_requests": 50, "total_function_calls": 300, "total_ec2_compute": 3600 }, "daily_usage": [ { "usage_date": "2025-11-10", "database_size_bytes": 1048576, "file_storage_bytes": 5242880, "ai_tokens": 1500, "mcp_calls": 12, "egress_bytes": 204800, "ai_credits": 0.15, "email_requests": 5, "function_calls": 30, "ec2_compute": 3600 } ] } ``` ### Get Partnership Total Usage Retrieve aggregated usage metrics across all projects under your partnership for a date range. This endpoint is designed for billing and reporting purposes, providing a consolidated view of resource consumption. ```bash theme={null} GET /partnership/v1/:partnerId/usage?start_date=2025-11-01&end_date=2025-11-30 ``` **Query Parameters:** * `start_date` (optional): Start date in YYYY-MM-DD format (defaults to 7 days ago) * `end_date` (optional): End date in YYYY-MM-DD format (defaults to today) **Response:** ```json theme={null} { "partnership": { "id": "ps_abc123xyz", "name": "Partner Name" }, "period": { "start": "2025-11-01T00:00:00Z", "end": "2025-11-30T23:59:59Z" }, "summary": { "database_bytes": 10485760, "storage_bytes": 52428800, "ai_tokens": 150000, "mcp_calls": 1200, "egress_bytes": 20971520, "ai_credits": 15.5, "email_requests": 500, "function_calls": 3000, "ec2_compute": 36000 } } ``` **Usage Metric Types:** | Metric | Type | Description | | ---------------- | ---------- | ------------------------------------------------------------ | | `database_bytes` | Peak | Peak total database footprint (sum per day, max across days) | | `storage_bytes` | Peak | Peak total storage footprint (sum per day, max across days) | | `ai_tokens` | Cumulative | Total AI tokens consumed | | `mcp_calls` | Cumulative | Total MCP/tool calls made | | `egress_bytes` | Cumulative | Total data transfer (origin + CDN) | | `ai_credits` | Cumulative | Total AI credits consumed | | `email_requests` | Cumulative | Total emails sent | | `function_calls` | Cumulative | Total serverless function invocations | | `ec2_compute` | Cumulative | Total compute seconds used | This endpoint includes usage from deleted projects within the specified date range, ensuring complete billing accuracy. ## Integration Examples ### Co-Branded Integration Flow The following sequence diagram illustrates the integration flow for co-branded partnerships: ```mermaid theme={null} sequenceDiagram participant User as End User participant Partner as Partner Platform participant InsForge as InsForge API participant Dashboard as InsForge Dashboard User->>Partner: Sign up/Login Partner->>InsForge: POST /connect-user
(name, email) InsForge-->>Partner: Return user account ID User->>Partner: Create new project Partner->>InsForge: POST /sync-project
(userId, project_name) alt Success InsForge-->>Partner: Return project details
(id, access_host, api_key) Partner->>Partner: Store project credentials Partner->>User: Show project ready else Project Limit Reached InsForge-->>Partner: Return error + candidate projects Partner->>User: Show existing projects to choose User->>Partner: Select existing project Partner->>InsForge: GET /metadata
(projectId) InsForge-->>Partner: Return project details end User->>Partner: Use application features Partner->>InsForge: API calls using project credentials InsForge-->>Partner: Return data Partner-->>User: Display results Note over User,Dashboard: User can also access InsForge Dashboard directly User->>Dashboard: Login with same email Dashboard->>User: Full project management access ``` ```typescript theme={null} // 1. Connect user account const connectUser = async (name: string, email: string) => { const response = await fetch( `https://api.insforge.dev/partnership/v1/${PARTNER_ID}/connect-user`, { method: 'POST', headers: { 'X-Partnership-Secret': `${SECRET_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email }) } ); const { account } = await response.json(); return account.id; }; // 2. Create project for user const createProject = async (userId: string, projectName: string) => { const response = await fetch( `https://api.insforge.dev/partnership/v1/${PARTNER_ID}/${userId}/sync-project`, { method: 'POST', headers: { 'X-Partnership-Secret': `${SECRET_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ project_name: projectName, region: 'us-east', instance_type: 'nano' }) } ); const data = await response.json(); if (data.success) { return data.project; } throw new Error(data.message); }; // 3. Use project credentials console.log(project.access_host); console.log(project.api_key); ``` ### White-Label Integration Flow The following sequence diagram illustrates the integration flow for white-label partnerships: ```mermaid theme={null} sequenceDiagram participant User as End User participant Partner as Partner Platform participant InsForge as InsForge API Note over User,InsForge: User never interacts with InsForge directly User->>Partner: Sign up/Login to partner platform User->>Partner: Create new project Partner->>InsForge: POST /sync-embedded-project
(project_name, region, instance_type) InsForge-->>Partner: Return project details
(id, access_host, api_key, status) Partner->>Partner: Store project credentials Partner->>User: Show project ready User->>Partner: Use application features Partner->>InsForge: API calls using project credentials InsForge-->>Partner: Return data Partner-->>User: Display results User->>Partner: Request to pause project Partner->>InsForge: POST /pause
(projectId) InsForge-->>Partner: Return status: paused Partner->>User: Confirm project paused User->>Partner: Request to restore project Partner->>InsForge: POST /restore
(projectId) InsForge-->>Partner: Return status: active Partner->>User: Confirm project active Partner->>InsForge: GET /metadata
(projectId) InsForge-->>Partner: Return current project status ``` ```typescript theme={null} // 1. Create embedded project const createEmbeddedProject = async (projectName: string) => { const response = await fetch( `https://api.insforge.dev/partnership/v1/${PARTNER_ID}/sync-embedded-project`, { method: 'POST', headers: { 'X-Partnership-Secret': `${SECRET_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ project_name: projectName, region: 'eu-west', instance_type: 'small' }) } ); const data = await response.json(); if (data.success) { return data.project; } throw new Error(data.message); }; // 2. Manage project lifecycle const pauseProject = async (projectId: string) => { const response = await fetch( `https://api.insforge.dev/partnership/v1/${PARTNER_ID}/${projectId}/pause`, { method: 'POST', headers: { 'X-Partnership-Secret': `${SECRET_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ wait_for_completion: true }) } ); const { project } = await response.json(); console.log(`Project ${project.id} status: ${project.status}`); }; const restoreProject = async (projectId: string) => { const response = await fetch( `https://api.insforge.dev/partnership/v1/${PARTNER_ID}/${projectId}/restore`, { method: 'POST', headers: { 'X-Partnership-Secret': `${SECRET_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ wait_for_completion: true }) } ); const { project } = await response.json(); console.log(`Project ${project.id} status: ${project.status}`); }; // 3. Delete project const deleteProject = async (projectId: string) => { const response = await fetch( `https://api.insforge.dev/partnership/v1/${PARTNER_ID}/${projectId}`, { method: 'DELETE', headers: { 'X-Partnership-Secret': `${SECRET_KEY}` } } ); if (response.ok) { const data = await response.json(); console.log(data.message); // "Project deleted successfully" console.log(`Request ID: ${data.requestId}`); } else { throw new Error(`Failed to delete project: ${response.status}`); } }; // 4. Get partnership total usage (for billing) const getPartnershipUsage = async (startDate: string, endDate: string) => { const params = new URLSearchParams({ start_date: startDate, end_date: endDate }); const response = await fetch( `https://api.insforge.dev/partnership/v1/${PARTNER_ID}/usage?${params}`, { method: 'GET', headers: { 'X-Partnership-Secret': `${SECRET_KEY}` } } ); const data = await response.json(); console.log(`Period: ${data.period.start} to ${data.period.end}`); console.log(`Database: ${data.summary.database_bytes} bytes (peak)`); console.log(`Storage: ${data.summary.storage_bytes} bytes (peak)`); console.log(`AI Credits: ${data.summary.ai_credits}`); console.log(`Egress: ${data.summary.egress_bytes} bytes`); return data; }; ``` ## Parameter and Response Reference ### Project Region Values InsForge provides global deployment capabilities. Each project can be deployed to different regions: * `us-east` - United States East Coast * `us-west` - United States West Coast * `ap-southeast` - Asia Pacific Southeast * `eu-central` - Europe Central Additional regions will be added based on demand. ### Project Instance Type Values Based on your project's business requirements, InsForge offers the following resource types: * `nano` - Minimal resources for development * `micro` - Basic resources for testing and development * `small` - Light workloads * `medium` - Standard applications * `large` - Production workloads * `xl` - High-performance applications * `2xl` - Enterprise applications * `4xl` - Large-scale operations * `8xl` - Mission-critical systems * `16xl` - Maximum performance ### Project Status Values Projects can have the following status values: * **active**: Project is running and accessible * **paused**: Project is paused (white-label only) ## Error Handling All API endpoints return consistent error responses: ```json theme={null} { "success": false, "message": "Detailed error message describing what went wrong" } ``` Common error scenarios: * Invalid authentication credentials * Project not found * Insufficient permissions for requested operation * Invalid request parameters ## Next Steps * Schedule a [technical review](https://calendly.com/tony-chang-insforge/45min) with our team # CLI setup Source: https://docs.insforge.dev/quickstart Connect your project from the terminal in 5 minutes. ## Step 1: Create your project Visit [insforge.dev](https://insforge.dev) and create a free account Once logged in: Click **"Create New Project"** Your backend will be ready in \~3 seconds You'll be redirected to the project console — copy the **Project ID** from the browser URL: ``` https://insforge.dev/dashboard/project/ ``` You'll need this Project ID in Step 2. ## Step 2: Link the CLI From your project directory, link your local code to your InsForge project. Replace `` with the ID from Step 1. ```bash theme={null} npx @insforge/cli link --project-id ``` Running through `npx` keeps the CLI out of your global install path. The link file ends up in the current directory and tells subsequent commands which project they belong to. ## Step 3: Verify Installation Send the following prompt to your AI coding agent to verify the setup: `I'm using InsForge as my backend platform. Read the current directory, make sure InsForge skills are installed, and use InsForge CLI for backend tasks.` ## Learn More To explore the full capabilities of the InsForge CLI: * Check the [npm package documentation](https://www.npmjs.com/package/@insforge/cli) * Run the built-in help command: ```bash theme={null} npx @insforge/cli --help ``` # Model Gateway Source: https://docs.insforge.dev/sdks/kotlin/ai Build AI features from Kotlin with the OpenRouter key provisioned by InsForge ## Overview InsForge provisions an OpenRouter API key for Model Gateway projects. New Kotlin applications should call OpenRouter from trusted server-side code, a backend API, or another secure boundary. Do not embed the OpenRouter key in Android or desktop client binaries. The previous InsForge Kotlin AI SDK methods are deprecated compatibility wrappers. Use the InsForge SDK for database, auth, storage, functions, and realtime; use OpenRouter for model calls. ## Recommended Architecture ```mermaid theme={null} sequenceDiagram participant App as Kotlin App participant API as Your Backend participant OR as OpenRouter App->>API: User prompt or app request API->>OR: OpenRouter request with server-side key OR-->>API: Model response API-->>App: App-specific response ``` ## Server-Side OpenRouter Call Use the OpenAI SDK or REST from your backend. For TypeScript backends: ```typescript theme={null} import OpenAI from 'openai'; const openai = new OpenAI({ baseURL: 'https://openrouter.ai/api/v1', apiKey: process.env.OPENROUTER_API_KEY, }); const completion = await openai.chat.completions.create({ model: 'openai/gpt-4o-mini', messages: [{ role: 'user', content: 'Summarize this note.' }], }); ``` ## Calling Your Backend from Kotlin ```kotlin theme={null} import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.request.header import io.ktor.client.request.post import io.ktor.client.request.setBody import io.ktor.http.ContentType import io.ktor.http.contentType import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.Serializable @Serializable data class ChatRequest(val prompt: String) @Serializable data class ChatResponse(val text: String) val http = HttpClient { install(ContentNegotiation) { json() } } suspend fun sendPrompt(prompt: String, sessionToken: String): ChatResponse { return http.post("https://your-app.example/api/chat") { header("Authorization", "Bearer $sessionToken") contentType(ContentType.Application.Json) setBody(ChatRequest(prompt)) }.body() } ``` Use an app session token or another user-scoped credential for your backend route. Never send the OpenRouter key from a Kotlin client. ## Legacy InsForge AI Methods These Kotlin SDK methods are deprecated for new AI integrations: * `client.ai.listModels()` * `client.ai.chatCompletion(...)` * `client.ai.chatCompletionStream(...)` * `client.ai.generateEmbeddings(...)` * `client.ai.generateImage(...)` They target the deprecated InsForge AI proxy. New integrations should use the OpenRouter key from the dashboard and follow OpenRouter's current API docs for model parameters and capabilities. # Authentication SDK Reference Source: https://docs.insforge.dev/sdks/kotlin/auth User authentication and profile management with the InsForge Kotlin SDK ## Installation 1. Add InsForge dependencies to your project build.gradle.kts: ```kotlin theme={null} repositories { mavenLocal() // For local development mavenCentral() } dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` First, create a GitHub Personal Access Token: * Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) * Select permission: `read:packages` Then configure your project using one of the following methods: settings.gradle.kts (or build.gradle.kts): ```kotlin theme={null} repositories { mavenCentral() maven { url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin") credentials { username = System.getenv("GITHUB_USER") ?: "" password = System.getenv("GITHUB_TOKEN") ?: "" } } } ``` build.gradle.kts: ```kotlin theme={null} dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` Set environment variables before building: ```bash theme={null} export GITHUB_USER="your-github-username" export GITHUB_TOKEN="your-personal-access-token" ``` Add credentials to your global Gradle properties file: \~/.gradle/gradle.properties: ```properties theme={null} gpr.user=your-github-username gpr.token=ghp_xxxxxxxxxxxx ``` settings.gradle.kts: ```kotlin theme={null} repositories { mavenCentral() maven { url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin") credentials { username = providers.gradleProperty("gpr.user").orNull ?: "" password = providers.gradleProperty("gpr.token").orNull ?: "" } } } ``` build.gradle.kts: ```kotlin theme={null} dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` The `~/.gradle/gradle.properties` file is stored outside your project, so credentials won't be accidentally committed to version control. 2. Initialize InsForge SDK ```kotlin theme={null} import dev.insforge.createInsforgeClient import dev.insforge.auth.Auth import dev.insforge.database.Database import dev.insforge.storage.Storage import dev.insforge.functions.Functions import dev.insforge.realtime.Realtime import dev.insforge.ai.AI val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-api-key" ) { install(Auth) install(Database) install(Storage) install(Functions) install(Realtime) { autoReconnect = true reconnectDelay = 5000 } install(AI) } ``` 3. Enable Logging (Optional) For debugging, you can configure the SDK log level: ```kotlin theme={null} import dev.insforge.InsforgeLogLevel val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-api-key" ) { // DEBUG: logs request method/URL and response status // VERBOSE: logs full headers and request/response bodies logLevel = InsforgeLogLevel.DEBUG install(Auth) install(Database) // ... other modules } ``` | Log Level | Description | | --------- | ------------------------------------------------- | | `NONE` | No logging (default, recommended for production) | | `ERROR` | Only errors | | `WARN` | Warnings and errors | | `INFO` | Informational messages | | `DEBUG` | Debug info (request method, URL, response status) | | `VERBOSE` | Full details (headers, request/response bodies) | Use `NONE` or `ERROR` in production to avoid exposing sensitive data in logs. ### Android Initialization 1. Add Chrome Custom Tabs dependency to your `build.gradle.kts`: ```kotlin theme={null} dependencies { implementation("androidx.browser:browser:1.9.0") } ``` 2. Initialize InsForge SDK (With Chrome Custom Tabs and Session Storage) ```kotlin theme={null} import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri import androidx.browser.customtabs.CustomTabsIntent import dev.insforge.createInsforgeClient import dev.insforge.ai.AI import dev.insforge.auth.Auth import dev.insforge.auth.BrowserLauncher import dev.insforge.auth.ClientType import dev.insforge.auth.SessionStorage import dev.insforge.database.Database import dev.insforge.functions.Functions import dev.insforge.realtime.Realtime import dev.insforge.storage.Storage class InsforgeManager(private val context: Context) { val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-anon-key" ) { install(Auth) { // 1. Chrome Custom Tabs - opens in-app, similar to iOS ASWebAuthenticationSession browserLauncher = BrowserLauncher { url -> val customTabsIntent = CustomTabsIntent.Builder() .setShowTitle(true) .build() // Handle non-Activity context safely if (context is Activity) { customTabsIntent.launchUrl(context, Uri.parse(url)) } else { customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) customTabsIntent.launchUrl(context, Uri.parse(url)) } } // 2. enable session persistence persistSession = true // 3. config SessionStorage (use SharedPreferences) sessionStorage = object : SessionStorage { private val prefs = context.getSharedPreferences( "insforge_auth", Context.MODE_PRIVATE ) override suspend fun save(key: String, value: String) { prefs.edit().putString(key, value).apply() } override suspend fun get(key: String): String? { return prefs.getString(key, null) } override suspend fun remove(key: String) { prefs.edit().remove(key).apply() } } // 4. set client type for mobile clientType = ClientType.MOBILE } // Install Database module install(Database) // Install Realtime module for real-time subscriptions install(Realtime) { debug = true } // Install other modules install(Storage) install(Functions) install(AI) } } ``` 3. Use Jetpack DataStore for Session Storage (Optional) ```kotlin theme={null} import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map val Context.authDataStore: DataStore by preferencesDataStore(name = "insforge_auth") class DataStoreSessionStorage(private val context: Context) : SessionStorage { override suspend fun save(key: String, value: String) { context.authDataStore.edit { prefs -> prefs[stringPreferencesKey(key)] = value } } override suspend fun get(key: String): String? { return context.authDataStore.data.map { prefs -> prefs[stringPreferencesKey(key)] }.first() } override suspend fun remove(key: String) { context.authDataStore.edit { prefs -> prefs.remove(stringPreferencesKey(key)) } } } // Then use it in your InsForge client install(Auth) { browserLauncher = ... persistSession = true sessionStorage = DataStoreSessionStorage(context) } ``` ## signUp() Create a new user account with email and password. ### Parameters * `email` (String) - User's email address * `password` (String) - User's password * `name` (String?, optional) - User's display name ### Returns ```kotlin theme={null} SignUpResponse ``` ### SignUpResponse ```kotlin theme={null} data class SignUpResponse( /** User object (null when email verification is required) */ val user: User? = null, /** Access token (null when email verification is required) */ val accessToken: String? = null, /** Indicates if email verification is required before sign-in */ val requireEmailVerification: Boolean = false, /** Redirect URL (if applicable) */ val redirectTo: String? = null, /** CSRF token (if applicable) */ val csrfToken: String? = null, /** Refresh token (null when email verification is required) */ val refreshToken: String? = null ) ``` ### Example (Complete Flow with Verification) ```kotlin theme={null} class AuthViewModel : ViewModel() { // Sign up and handle verification requirement suspend fun signUp(email: String, password: String, name: String?) { try { val result = client.auth.signUp( email = email, password = password, name = name ) if (result.requireEmailVerification) { // Show verification code input screen // User will receive a 6-digit code via email _uiState.value = AuthUiState.RequiresVerification(email) } else if (result.accessToken != null) { // Registration complete, user is signed in _uiState.value = AuthUiState.Authenticated } } catch (e: InsforgeHttpException) { _uiState.value = AuthUiState.Error(e.message ?: "Sign up failed") } } // Verify email with 6-digit code suspend fun verifyEmail(email: String, code: String) { try { client.auth.verifyEmail(email = email, code = code) // Verification successful, user can now sign in _uiState.value = AuthUiState.VerificationSuccess } catch (e: InsforgeHttpException) { _uiState.value = AuthUiState.Error("Invalid verification code") } } // Resend verification email suspend fun resendVerificationEmail(email: String) { try { client.auth.resendVerificationEmail(email = email) // Show success message } catch (e: InsforgeHttpException) { _uiState.value = AuthUiState.Error("Failed to resend verification email") } } } sealed class AuthUiState { object Initial : AuthUiState() object Authenticated : AuthUiState() object VerificationSuccess : AuthUiState() data class RequiresVerification(val email: String) : AuthUiState() data class Error(val message: String) : AuthUiState() } ``` ### Email Verification For users who register with email, the InsForge backend provides three options: 1. **No email verification** - Users can sign in immediately after registration. `SignUpResponse` will have `accessToken != null`. 2. **Link-based verification** - Users must open their email and click the verification link before they can sign in. 3. **Code-based verification** - The InsForge backend sends a 6-digit verification code to the user's email. The client app needs to display a verification screen where users can enter the code, then call `verifyEmail(email, code)` to complete verification. Only after this can users sign in with email + password. When `requireEmailVerification` is `true`, the response will have: * `accessToken = null` * `user = null` * `requireEmailVerification = true` This indicates that verification via option 2 or 3 is required before the user can sign in. ### Related Methods | Method | Description | | -------------------------------- | ------------------------------ | | `verifyEmail(email, code)` | Verify email with 6-digit code | | `resendVerificationEmail(email)` | Resend verification email | *** ## signIn() Sign in an existing user with email and password. ### Example ```kotlin theme={null} try { val result = client.auth.signIn( email = "user@example.com", password = "secure_password123" ) result.user?.let { user -> Log.d("Auth", "Welcome back, ${user.profile?.name ?: user.email}") } } catch (e: InsforgeHttpException) { Log.e("Auth", "Sign in failed: ${e.message}") } ``` ### Email Verification If the sign in response is: ```json theme={null} {"error":"FORBIDDEN","message":"Email verification required","statusCode":403,"nextActions":"Please verify your email address before logging in"} ``` This indicates that verification via option 2 or 3 (link or code, see [signUp()](#email-verification)) is required before the user can sign in. *** ## signOut() Sign out the current user. ### Example ```kotlin theme={null} try { client.auth.signOut() Log.d("Auth", "User signed out") } catch (e: InsforgeException) { Log.e("Auth", "Sign out failed: ${e.message}") } ``` *** ## signInWithOAuthPage() Sign in directly with a specific OAuth provider. This method opens the OAuth provider's authentication page directly in the system browser. ### Supported Providers ```kotlin theme={null} enum class OAuthProvider(val value: String) { GOOGLE("google"), GITHUB("github"), DISCORD("discord"), LINKEDIN("linkedin"), FACEBOOK("facebook"), INSTAGRAM("instagram"), TIKTOK("tiktok"), APPLE("apple"), X("x"), SPOTIFY("spotify"), MICROSOFT("microsoft") } ``` ### Parameters * `provider` (`OAuthProvider`) - The OAuth provider to authenticate with * `redirectUri` (`String`) - Callback URL where InsForge will redirect after authentication ### Returns ```kotlin theme={null} String // The OAuth authorization URL (also opens in browser automatically) ``` ### Example 1. Configure BrowserLauncher When creating the InsForge client, configure the `browserLauncher` to handle opening URLs: ```kotlin theme={null} val client = createInsforgeClient(baseURL, anonKey) { install(Auth) { browserLauncher = BrowserLauncher { url -> val customTabsIntent = CustomTabsIntent.Builder() .setShowTitle(true) .build() // Handle non-Activity context safely if (context is Activity) { customTabsIntent.launchUrl(context, Uri.parse(url)) } else { customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) customTabsIntent.launchUrl(context, Uri.parse(url)) } } persistSession = true sessionStorage = mySessionStorage } } ``` 2. Configure App Link / Deep Link Callback Configure your callback Activity in AndroidManifest.xml: * Option A: Custom URL Scheme (for Development) ```xml theme={null} ``` * Option B: App Links (for Production) ```xml theme={null} ``` 3. Initiate OAuth Login with Specific Provider ```kotlin theme={null} // Start OAuth flow with Google fun startGoogleLogin() { lifecycleScope.launch { val authUrl = client.auth.signInWithOAuthPage( OAuthProvider.GOOGLE, "yourapp://auth/callback" ) // Browser opens automatically via browserLauncher } } ``` 4. Handle OAuth Callback ```kotlin theme={null} class AuthCallbackActivity : AppCompatActivity() { // Example: read the shared InsForge client from your Application class. private val client by lazy { (application as MyApp).insforgeClient } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) handleIntent(intent) } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) intent?.let { handleIntent(it) } } private fun handleIntent(intent: Intent) { intent.data?.let { uri -> lifecycleScope.launch { try { val result = client.auth.handleAuthCallback(uri.toString()) Toast.makeText( this@AuthCallbackActivity, "Success: ${result.email}", Toast.LENGTH_SHORT ).show() startActivity(Intent(this@AuthCallbackActivity, MainActivity::class.java)) finish() } catch (e: Exception) { Toast.makeText( this@AuthCallbackActivity, "Failed: ${e.message}", Toast.LENGTH_LONG ).show() finish() } } } } } ``` `client` should come from the same shared InsForge client instance you use elsewhere in the app, such as an `Application` singleton or your DI container. *** ## getCurrentUser() Fetch the current authenticated user from the server. This is a suspend function that makes a network request. ### Returns ```kotlin theme={null} CurrentUserResponse // Contains user data from server ``` ### Example ```kotlin theme={null} try { val response = client.auth.getCurrentUser() Log.d("Auth", "Email: ${response.email}") Log.d("Auth", "Name: ${response.profile?.name ?: "N/A"}") } catch (e: InsforgeHttpException) { Log.e("Auth", "Failed to get user: ${e.message}") } ``` This method makes a network request to fetch user data. For accessing locally cached user state, use `currentUser` StateFlow instead. *** ## updateProfile() Update current user's profile. ### Parameters * `profile` (`Map\`) - Profile fields to update ### Returns ```kotlin theme={null} ProfileResponse // Updated profile data ``` ### Example ```kotlin theme={null} try { val result = client.auth.updateProfile( mapOf( "name" to "JohnDev", "bio" to "Android Developer", "avatar_url" to "https://example.com/avatar.jpg" ) ) Log.d("Auth", "Profile updated: ${result.name}") } catch (e: InsforgeHttpException) { Log.e("Auth", "Update failed: ${e.message}") } ``` *** ## Password Reset InsForge supports two password reset methods, configured in the backend: * **Code method**: User receives a 6-digit code via email, verifies it to get a reset token, then resets password * **Link method**: User receives a magic link via email containing the reset token, then resets password directly ### sendPasswordReset() Send a password reset email to the user. The email will contain either a 6-digit code or a magic link depending on the backend configuration. #### Parameters * `email` (String) - User's email address #### Example ```kotlin theme={null} try { client.auth.sendPasswordReset(email = "user@example.com") // Show message to check email showMessage("If your email is registered, you will receive a password reset email.") } catch (e: InsforgeHttpException) { Log.e("Auth", "Failed to send reset email: ${e.message}") } ``` *** ### exchangeResetPasswordToken() Exchange a 6-digit reset code for a reset token. **This method is only used with the code-based reset flow.** #### Parameters * `email` (String) - User's email address * `code` (String) - 6-digit numeric code received via email #### Returns ```kotlin theme={null} ResetTokenResponse ``` #### ResetTokenResponse ```kotlin theme={null} data class ResetTokenResponse( /** Reset token to use with resetPassword() */ val token: String, /** Token expiration timestamp */ val expiresAt: String? ) ``` #### Example ```kotlin theme={null} try { val response = client.auth.exchangeResetPasswordToken( email = "user@example.com", code = "123456" ) // Store the token and proceed to password reset screen val resetToken = response.token showPasswordResetScreen(token = resetToken) } catch (e: InsforgeHttpException) { Log.e("Auth", "Invalid or expired code: ${e.message}") } ``` *** ### resetPassword() Reset the user's password using a reset token. #### Parameters * `newPassword` (String) - New password meeting the configured requirements * `otp` (String) - Reset token (from `exchangeResetPasswordToken()` for code flow, or from magic link URL for link flow) #### Example ```kotlin theme={null} try { client.auth.resetPassword( newPassword = "newSecurePassword123", otp = resetToken ) // Password reset successful showMessage("Password reset successfully. You can now sign in.") navigateToSignIn() } catch (e: InsforgeHttpException) { Log.e("Auth", "Password reset failed: ${e.message}") } ``` *** ## Error Handling ```kotlin theme={null} import dev.insforge.exceptions.InsforgeHttpException import dev.insforge.exceptions.InsforgeException try { val result = client.auth.signIn(email, password) } catch (e: InsforgeHttpException) { // HTTP errors from API with error codes when (e.error) { "INVALID_CREDENTIALS" -> showError("Invalid email or password") "USER_NOT_FOUND" -> showError("User not found") "EMAIL_NOT_VERIFIED" -> showError("Please verify your email") "INVALID_EMAIL" -> showError("Invalid email format") "WEAK_PASSWORD" -> showError("Password is too weak") else -> showError("Error: ${e.message}") } } catch (e: InsforgeException) { // Other SDK errors (network, parsing, etc.) showError("Error: ${e.message}") } ``` ### Common Error Codes | Error Code | Description | | --------------------- | -------------------------------------- | | `INVALID_CREDENTIALS` | Email or password is incorrect | | `USER_NOT_FOUND` | No user with this email exists | | `EMAIL_NOT_VERIFIED` | Email verification required | | `INVALID_EMAIL` | Email format is invalid | | `WEAK_PASSWORD` | Password doesn't meet requirements | | `USER_ALREADY_EXISTS` | Email is already registered | | `SESSION_EXPIRED` | Session has expired, re-login required | # Database SDK Reference Source: https://docs.insforge.dev/sdks/kotlin/database Type-safe database operations using the InsForge Kotlin SDK ## Installation 1. Add InsForge dependencies to your project build.gradle.kts: ```kotlin theme={null} repositories { mavenLocal() // For local development mavenCentral() } dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` First, create a GitHub Personal Access Token: * Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) * Select permission: `read:packages` Then configure your project using one of the following methods: settings.gradle.kts (or build.gradle.kts): ```kotlin theme={null} repositories { mavenCentral() maven { url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin") credentials { username = System.getenv("GITHUB_USER") ?: "" password = System.getenv("GITHUB_TOKEN") ?: "" } } } ``` build.gradle.kts: ```kotlin theme={null} dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` Set environment variables before building: ```bash theme={null} export GITHUB_USER="your-github-username" export GITHUB_TOKEN="your-personal-access-token" ``` Add credentials to your global Gradle properties file: \~/.gradle/gradle.properties: ```properties theme={null} gpr.user=your-github-username gpr.token=ghp_xxxxxxxxxxxx ``` settings.gradle.kts: ```kotlin theme={null} repositories { mavenCentral() maven { url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin") credentials { username = providers.gradleProperty("gpr.user").orNull ?: "" password = providers.gradleProperty("gpr.token").orNull ?: "" } } } ``` build.gradle.kts: ```kotlin theme={null} dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` The `~/.gradle/gradle.properties` file is stored outside your project, so credentials won't be accidentally committed to version control. 2. Initialize InsForge SDK ```kotlin theme={null} import dev.insforge.createInsforgeClient import dev.insforge.auth.Auth import dev.insforge.database.Database import dev.insforge.storage.Storage import dev.insforge.functions.Functions import dev.insforge.realtime.Realtime import dev.insforge.ai.AI val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-api-key" ) { install(Auth) install(Database) install(Storage) install(Functions) install(Realtime) { autoReconnect = true reconnectDelay = 5000 } install(AI) } ``` 3. Enable Logging (Optional) For debugging, you can configure the SDK log level: ```kotlin theme={null} import dev.insforge.InsforgeLogLevel val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-api-key" ) { // DEBUG: logs request method/URL and response status // VERBOSE: logs full headers and request/response bodies logLevel = InsforgeLogLevel.DEBUG install(Auth) install(Database) // ... other modules } ``` | Log Level | Description | | --------- | ------------------------------------------------- | | `NONE` | No logging (default, recommended for production) | | `ERROR` | Only errors | | `WARN` | Warnings and errors | | `INFO` | Informational messages | | `DEBUG` | Debug info (request method, URL, response status) | | `VERBOSE` | Full details (headers, request/response bodies) | Use `NONE` or `ERROR` in production to avoid exposing sensitive data in logs. ### Android Initialization 1. Add Chrome Custom Tabs dependency to your `build.gradle.kts`: ```kotlin theme={null} dependencies { implementation("androidx.browser:browser:1.9.0") } ``` 2. Initialize InsForge SDK (With Chrome Custom Tabs and Session Storage) ```kotlin theme={null} import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri import androidx.browser.customtabs.CustomTabsIntent import dev.insforge.createInsforgeClient import dev.insforge.ai.AI import dev.insforge.auth.Auth import dev.insforge.auth.BrowserLauncher import dev.insforge.auth.ClientType import dev.insforge.auth.SessionStorage import dev.insforge.database.Database import dev.insforge.functions.Functions import dev.insforge.realtime.Realtime import dev.insforge.storage.Storage class InsforgeManager(private val context: Context) { val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-anon-key" ) { install(Auth) { // 1. Chrome Custom Tabs - opens in-app, similar to iOS ASWebAuthenticationSession browserLauncher = BrowserLauncher { url -> val customTabsIntent = CustomTabsIntent.Builder() .setShowTitle(true) .build() // Handle non-Activity context safely if (context is Activity) { customTabsIntent.launchUrl(context, Uri.parse(url)) } else { customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) customTabsIntent.launchUrl(context, Uri.parse(url)) } } // 2. enable session persistence persistSession = true // 3. config SessionStorage (use SharedPreferences) sessionStorage = object : SessionStorage { private val prefs = context.getSharedPreferences( "insforge_auth", Context.MODE_PRIVATE ) override suspend fun save(key: String, value: String) { prefs.edit().putString(key, value).apply() } override suspend fun get(key: String): String? { return prefs.getString(key, null) } override suspend fun remove(key: String) { prefs.edit().remove(key).apply() } } // 4. set client type for mobile clientType = ClientType.MOBILE } // Install Database module install(Database) // Install Realtime module for real-time subscriptions install(Realtime) { debug = true } // Install other modules install(Storage) install(Functions) install(AI) } } ``` 3. Use Jetpack DataStore for Session Storage (Optional) ```kotlin theme={null} import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map val Context.authDataStore: DataStore by preferencesDataStore(name = "insforge_auth") class DataStoreSessionStorage(private val context: Context) : SessionStorage { override suspend fun save(key: String, value: String) { context.authDataStore.edit { prefs -> prefs[stringPreferencesKey(key)] = value } } override suspend fun get(key: String): String? { return context.authDataStore.data.map { prefs -> prefs[stringPreferencesKey(key)] }.first() } override suspend fun remove(key: String) { context.authDataStore.edit { prefs -> prefs.remove(stringPreferencesKey(key)) } } } // Then use it in your InsForge client install(Auth) { browserLauncher = ... persistSession = true sessionStorage = DataStoreSessionStorage(context) } ``` ## insert() Insert new records into a table. ### Examples ```kotlin theme={null} // Define your data class @Serializable data class Post( val id: String? = null, val title: String, val content: String, @SerialName("created_at") val createdAt: String? = null ) // Single insert (must wrap in list) val post = Post(title = "Hello World", content = "My first post!") val result = insforge.database .from("posts") .insertTyped(listOf(post)) .returning() .execute() // Returns List // Bulk insert val posts = listOf( Post(title = "First Post", content = "Hello everyone!"), Post(title = "Second Post", content = "Another update.") ) val result = insforge.database .from("posts") .insertTyped(posts) .returning() .execute() // Returns List ``` *** ## update() Update existing records in a table. ### Examples ```kotlin theme={null} import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put // Update by ID (using JsonPrimitive) val result = insforge.database .from("posts") .update(mapOf("title" to JsonPrimitive("Updated Title"))) .eq("id", postId) .returning() .execute() // Returns List // Update by ID (using buildJsonObject) val result = insforge.database .from("posts") .update(buildJsonObject { put("title", "Updated Title") }) .eq("id", postId) .returning() .execute() // Returns List // Update multiple val result = insforge.database .from("tasks") .update(buildJsonObject { put("status", "completed") }) .`in`("id", listOf("task-1", "task-2")) .returning() .execute() // Returns List ``` *** ## delete() Delete records from a table. ### Examples ```kotlin theme={null} // Delete by ID insforge.database .from("posts") .delete() .eq("id", postId) .execute() // Delete with filter insforge.database .from("sessions") .delete() .lt("expires_at", Date()) .execute() ``` *** ## select() Query records from a table. ### Examples ```kotlin theme={null} // Get all posts val posts = insforge.database .from("posts") .select() .execute() // Returns List // Specific columns val posts = insforge.database .from("posts") .select("id, title, content") .execute() // Returns List // With relationships (typed) @Serializable data class PostWithComments( val id: String, val title: String, val content: String, val comments: List ) val posts = insforge.database .from("posts") .select("*, comments(id, content)") .execute() // Returns List ``` *** ## executeRaw() Execute SELECT query and return raw JSON array. Use this method when you need to work with dynamic/untyped data, such as queries with joins that return nested objects. ### Examples ```kotlin theme={null} import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive // Query with relationships - raw JSON access val result = insforge.database .from("tweets") .select("id, content, profiles!tweets_user_id_fkey(username)") .executeRaw() // Returns JsonArray result.forEach { element -> val obj = element.jsonObject val id = obj["id"]?.jsonPrimitive?.content val content = obj["content"]?.jsonPrimitive?.content val profile = obj["profiles"]?.jsonObject val username = profile?.get("username")?.jsonPrimitive?.content println("Tweet by $username: $content") } // Dynamic queries where structure is unknown val rawData = insforge.database .from("dynamic_table") .select() .executeRaw() // Process raw JSON as needed rawData.forEach { element -> val obj = element.jsonObject obj.keys.forEach { key -> println("$key: ${obj[key]}") } } ``` *** ## rpc() Call PostgreSQL stored functions (RPC - Remote Procedure Call). This method allows you to directly invoke SQL functions defined in your database. ### Signature ```kotlin theme={null} // Typed RPC call - deserializes response to specified type suspend inline fun rpc( functionName: String, args: Map? = null ): T // Raw RPC call - returns JsonElement for dynamic processing suspend fun rpcRaw( functionName: String, args: Map? = null ): JsonElement ``` ### Parameters * `functionName` (String) - Name of the PostgreSQL function to call * `args` (`Map\?`, optional) - Arguments to pass to the function ### Implementation Details * **No arguments**: Uses GET request to `/api/database/rpc/{functionName}` * **With arguments**: Uses POST request with JSON body to `/api/database/rpc/{functionName}` ### Examples ```kotlin theme={null} // Define response data classes @Serializable data class UserStats( val totalPosts: Int, val totalLikes: Int, val joinedAt: String ) @Serializable data class User( val id: String, val name: String, val email: String ) // Call function with parameters val stats = insforge.database.rpc( "get_user_stats", mapOf("user_id" to 123) ) println("Total posts: ${stats.totalPosts}") // Call function without parameters val users = insforge.database.rpc>("get_all_active_users") users.forEach { user -> println("User: ${user.name}") } // Call function returning a single value val count = insforge.database.rpc("count_active_posts") println("Active posts: $count") // Call function with multiple parameters val result = insforge.database.rpc>( "search_posts", mapOf( "search_term" to "kotlin", "limit" to 10, "offset" to 0 ) ) ``` ### rpcRaw() Examples Use `rpcRaw()` when the return type is dynamic or unknown at compile time. ```kotlin theme={null} import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive // Get raw JSON response val result = insforge.database.rpcRaw( "some_dynamic_function", mapOf("param" to "value") ) // Process based on actual response structure when (result) { is JsonArray -> { result.forEach { element -> val obj = element.jsonObject println("Item: ${obj["name"]?.jsonPrimitive?.content}") } } is JsonObject -> { println("Single result: ${result["data"]}") } is JsonPrimitive -> { println("Value: ${result.content}") } } // Handle complex nested structures val complexResult = insforge.database.rpcRaw("get_dashboard_data") val dashboard = complexResult.jsonObject val userCount = dashboard["user_count"]?.jsonPrimitive?.int val recentPosts = dashboard["recent_posts"]?.jsonArray ``` *** ## Filters | Filter | Description | Example | | ------------------------- | ------------------------ | -------------------------------------------- | | `.eq(column, value)` | Equals | `.eq("status", "active")` | | `.neq(column, value)` | Not equals | `.neq("status", "banned")` | | `.gt(column, value)` | Greater than | `.gt("age", 18)` | | `.gte(column, value)` | Greater than or equal | `.gte("price", 100)` | | `.lt(column, value)` | Less than | `.lt("stock", 10)` | | `.lte(column, value)` | Less than or equal | `.lte("priority", 3)` | | `.like(column, pattern)` | Case-sensitive pattern | `.like("name", "%Widget%")` | | `.ilike(column, pattern)` | Case-insensitive pattern | `.ilike("email", "%@gmail.com")` | | `.in(column, list)` | Value in list | `.in("status", listOf("pending", "active"))` | | `.isNull(column)` | Is null | `.isNull("deleted_at")` | ```kotlin theme={null} // Chain multiple filters val products = insforge.database .from("products") .select() .eq("category", "electronics") .gte("price", 50) .lte("price", 500) .execute() // Returns List ``` *** ## Modifiers | Modifier | Description | Example | | --------------------------- | ------------ | ----------------------------------------- | | `.order(column, ascending)` | Sort results | `.order("created_at", ascending = false)` | | `.limit(count)` | Limit rows | `.limit(10)` | | `.range(from, to)` | Pagination | `.range(0, 9)` | ```kotlin theme={null} // Pagination with sorting val posts = insforge.database .from("posts") .select() .order("created_at", ascending = false) .range(0, 9) .limit(10) .execute() // Returns List ``` # Functions SDK Reference Source: https://docs.insforge.dev/sdks/kotlin/functions Invoke serverless functions with the InsForge Kotlin SDK ## Installation 1. Add InsForge dependencies to your project build.gradle.kts: ```kotlin theme={null} repositories { mavenLocal() // For local development mavenCentral() } dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` First, create a GitHub Personal Access Token: * Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) * Select permission: `read:packages` Then configure your project using one of the following methods: settings.gradle.kts (or build.gradle.kts): ```kotlin theme={null} repositories { mavenCentral() maven { url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin") credentials { username = System.getenv("GITHUB_USER") ?: "" password = System.getenv("GITHUB_TOKEN") ?: "" } } } ``` build.gradle.kts: ```kotlin theme={null} dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` Set environment variables before building: ```bash theme={null} export GITHUB_USER="your-github-username" export GITHUB_TOKEN="your-personal-access-token" ``` Add credentials to your global Gradle properties file: \~/.gradle/gradle.properties: ```properties theme={null} gpr.user=your-github-username gpr.token=ghp_xxxxxxxxxxxx ``` settings.gradle.kts: ```kotlin theme={null} repositories { mavenCentral() maven { url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin") credentials { username = providers.gradleProperty("gpr.user").orNull ?: "" password = providers.gradleProperty("gpr.token").orNull ?: "" } } } ``` build.gradle.kts: ```kotlin theme={null} dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` The `~/.gradle/gradle.properties` file is stored outside your project, so credentials won't be accidentally committed to version control. 2. Initialize InsForge SDK ```kotlin theme={null} import dev.insforge.createInsforgeClient import dev.insforge.auth.Auth import dev.insforge.database.Database import dev.insforge.storage.Storage import dev.insforge.functions.Functions import dev.insforge.realtime.Realtime import dev.insforge.ai.AI val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-api-key" ) { install(Auth) install(Database) install(Storage) install(Functions) install(Realtime) { autoReconnect = true reconnectDelay = 5000 } install(AI) } ``` 3. Enable Logging (Optional) For debugging, you can configure the SDK log level: ```kotlin theme={null} import dev.insforge.InsforgeLogLevel val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-api-key" ) { // DEBUG: logs request method/URL and response status // VERBOSE: logs full headers and request/response bodies logLevel = InsforgeLogLevel.DEBUG install(Auth) install(Database) // ... other modules } ``` | Log Level | Description | | --------- | ------------------------------------------------- | | `NONE` | No logging (default, recommended for production) | | `ERROR` | Only errors | | `WARN` | Warnings and errors | | `INFO` | Informational messages | | `DEBUG` | Debug info (request method, URL, response status) | | `VERBOSE` | Full details (headers, request/response bodies) | Use `NONE` or `ERROR` in production to avoid exposing sensitive data in logs. ### Android Initialization 1. Add Chrome Custom Tabs dependency to your `build.gradle.kts`: ```kotlin theme={null} dependencies { implementation("androidx.browser:browser:1.9.0") } ``` 2. Initialize InsForge SDK (With Chrome Custom Tabs and Session Storage) ```kotlin theme={null} import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri import androidx.browser.customtabs.CustomTabsIntent import dev.insforge.createInsforgeClient import dev.insforge.ai.AI import dev.insforge.auth.Auth import dev.insforge.auth.BrowserLauncher import dev.insforge.auth.ClientType import dev.insforge.auth.SessionStorage import dev.insforge.database.Database import dev.insforge.functions.Functions import dev.insforge.realtime.Realtime import dev.insforge.storage.Storage class InsforgeManager(private val context: Context) { val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-anon-key" ) { install(Auth) { // 1. Chrome Custom Tabs - opens in-app, similar to iOS ASWebAuthenticationSession browserLauncher = BrowserLauncher { url -> val customTabsIntent = CustomTabsIntent.Builder() .setShowTitle(true) .build() // Handle non-Activity context safely if (context is Activity) { customTabsIntent.launchUrl(context, Uri.parse(url)) } else { customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) customTabsIntent.launchUrl(context, Uri.parse(url)) } } // 2. enable session persistence persistSession = true // 3. config SessionStorage (use SharedPreferences) sessionStorage = object : SessionStorage { private val prefs = context.getSharedPreferences( "insforge_auth", Context.MODE_PRIVATE ) override suspend fun save(key: String, value: String) { prefs.edit().putString(key, value).apply() } override suspend fun get(key: String): String? { return prefs.getString(key, null) } override suspend fun remove(key: String) { prefs.edit().remove(key).apply() } } // 4. set client type for mobile clientType = ClientType.MOBILE } // Install Database module install(Database) // Install Realtime module for real-time subscriptions install(Realtime) { debug = true } // Install other modules install(Storage) install(Functions) install(AI) } } ``` 3. Use Jetpack DataStore for Session Storage (Optional) ```kotlin theme={null} import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map val Context.authDataStore: DataStore by preferencesDataStore(name = "insforge_auth") class DataStoreSessionStorage(private val context: Context) : SessionStorage { override suspend fun save(key: String, value: String) { context.authDataStore.edit { prefs -> prefs[stringPreferencesKey(key)] = value } } override suspend fun get(key: String): String? { return context.authDataStore.data.map { prefs -> prefs[stringPreferencesKey(key)] }.first() } override suspend fun remove(key: String) { context.authDataStore.edit { prefs -> prefs.remove(stringPreferencesKey(key)) } } } // Then use it in your InsForge client install(Auth) { browserLauncher = ... persistSession = true sessionStorage = DataStoreSessionStorage(context) } ``` *** ## invoke() Invoke a serverless function by slug. ### Parameters * `slug` (String) - Function slug identifier * `body` (Any?, optional) - Request body (will be JSON serialized) ### Returns ```kotlin theme={null} T // Typed response (reified generic) ``` ### Examples ```kotlin theme={null} import kotlinx.serialization.Serializable import kotlinx.serialization.SerialName // Define response data class @Serializable data class HelloResponse( val message: String, val timestamp: String ) // Invoke function with typed response val response = client.functions.invoke( slug = "hello-world", body = mapOf("name" to "World") ) println(response.message) // "Hello, World!" ``` ### Example with Typed Request ```kotlin theme={null} @Serializable data class GreetingRequest( val name: String, val greeting: String ) @Serializable data class GreetingResponse( val message: String, val timestamp: String ) val response = client.functions.invoke( slug = "hello-world", body = GreetingRequest(name = "Kotlin", greeting = "Hello") ) ``` *** ## invokeRaw() Invoke a function and get the raw HTTP response. ### Example ```kotlin theme={null} val response = client.functions.invokeRaw( slug = "generate-pdf", body = mapOf("documentId" to "doc-123") ) // Access raw response val bytes = response.readBytes() val contentType = response.contentType() ``` *** ## Admin Functions The following methods require admin/service role authentication. These methods require the client to be initialized with a **service role key** instead of the anon key. Service role keys have elevated privileges and should only be used in secure server-side environments, never in client-side code. ```kotlin theme={null} // Initialize client with service role key for admin operations val adminClient = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-service-role-key" // Use service role key or api key, not anon key ) { install(Functions) } ``` ### listFunctions() List all functions. ```kotlin theme={null} val functions = client.functions.listFunctions() functions.forEach { fn -> println("${fn.name} (${fn.slug}) - ${fn.status}") } ``` ### getFunction() Get specific function details. ```kotlin theme={null} val details = client.functions.getFunction("hello-world") println("Name: ${details.name}") println("Slug: ${details.slug}") println("Status: ${details.status}") println("Code: ${details.code}") ``` ### createFunction() Currently, InsForge only supports JavaScript/TypeScript functions running in a Deno environment. Create a new function. ```kotlin theme={null} val result = client.functions.createFunction( name = "Hello World", code = """ export default async function(req) { const body = await req.json() return new Response(JSON.stringify({ message: `Hello, ${'$'}{body.name}!`, timestamp: new Date().toISOString() }), { headers: { "Content-Type": "application/json" } }) } """.trimIndent(), slug = "hello-world", // Optional, auto-generated from name if not provided description = "A simple greeting function", status = "active" // "draft" or "active" ) println("Created function: ${result.slug}") ``` ### updateFunction() Update an existing function. ```kotlin theme={null} val result = client.functions.updateFunction( slug = "hello-world", name = "Hello World v2", code = """ export default async function(req) { const body = await req.json() return new Response(JSON.stringify({ message: `Hello, ${'$'}{body.name}! Welcome to v2.`, version: 2 }), { headers: { "Content-Type": "application/json" } }) } """.trimIndent(), status = "active" ) ``` ### deleteFunction() Delete a function. ```kotlin theme={null} client.functions.deleteFunction("old-function") ``` *** ## Error Handling ```kotlin theme={null} import dev.insforge.exceptions.InsforgeHttpException try { val response = client.functions.invoke("my-function") println("Success: $response") } catch (e: InsforgeHttpException) { println("HTTP Error ${e.statusCode}: ${e.message}") println("Error code: ${e.error}") e.nextActions?.let { actions -> println("Suggested actions: $actions") } } catch (e: Exception) { println("Unexpected error: ${e.message}") } ``` *** ## Models Reference ### FunctionMetadata ```kotlin theme={null} @Serializable data class FunctionMetadata( val id: String, val slug: String, val name: String, val description: String? = null, val status: String, // "draft", "active", "error" @SerialName("created_at") val createdAt: String? = null, @SerialName("updated_at") val updatedAt: String? = null, @SerialName("deployed_at") val deployedAt: String? = null ) ``` ### FunctionDetails ```kotlin theme={null} @Serializable data class FunctionDetails( val id: String, val slug: String, val name: String, val description: String? = null, val code: String, val status: String, // "draft", "active", "error" @SerialName("created_at") val createdAt: String, @SerialName("updated_at") val updatedAt: String? = null, @SerialName("deployed_at") val deployedAt: String? = null ) ``` # Kotlin SDK Source: https://docs.insforge.dev/sdks/kotlin/overview Official Kotlin SDK for InsForge - Android and Kotlin Multiplatform The InsForge Kotlin SDK provides a native Kotlin client for interacting with InsForge services from Android applications and Kotlin Multiplatform projects. ## Installation 1. Add InsForge dependencies to your project build.gradle.kts: ```kotlin theme={null} repositories { mavenLocal() // For local development mavenCentral() } dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` First, create a GitHub Personal Access Token: * Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) * Select permission: `read:packages` Then configure your project using one of the following methods: settings.gradle.kts (or build.gradle.kts): ```kotlin theme={null} repositories { mavenCentral() maven { url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin") credentials { username = System.getenv("GITHUB_USER") ?: "" password = System.getenv("GITHUB_TOKEN") ?: "" } } } ``` build.gradle.kts: ```kotlin theme={null} dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` Set environment variables before building: ```bash theme={null} export GITHUB_USER="your-github-username" export GITHUB_TOKEN="your-personal-access-token" ``` Add credentials to your global Gradle properties file: \~/.gradle/gradle.properties: ```properties theme={null} gpr.user=your-github-username gpr.token=ghp_xxxxxxxxxxxx ``` settings.gradle.kts: ```kotlin theme={null} repositories { mavenCentral() maven { url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin") credentials { username = providers.gradleProperty("gpr.user").orNull ?: "" password = providers.gradleProperty("gpr.token").orNull ?: "" } } } ``` build.gradle.kts: ```kotlin theme={null} dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` The `~/.gradle/gradle.properties` file is stored outside your project, so credentials won't be accidentally committed to version control. 2. Initialize InsForge SDK ```kotlin theme={null} import dev.insforge.createInsforgeClient import dev.insforge.auth.Auth import dev.insforge.database.Database import dev.insforge.storage.Storage import dev.insforge.functions.Functions import dev.insforge.realtime.Realtime import dev.insforge.ai.AI val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-api-key" ) { install(Auth) install(Database) install(Storage) install(Functions) install(Realtime) { autoReconnect = true reconnectDelay = 5000 } install(AI) } ``` 3. Enable Logging (Optional) For debugging, you can configure the SDK log level: ```kotlin theme={null} import dev.insforge.InsforgeLogLevel val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-api-key" ) { // DEBUG: logs request method/URL and response status // VERBOSE: logs full headers and request/response bodies logLevel = InsforgeLogLevel.DEBUG install(Auth) install(Database) // ... other modules } ``` | Log Level | Description | | --------- | ------------------------------------------------- | | `NONE` | No logging (default, recommended for production) | | `ERROR` | Only errors | | `WARN` | Warnings and errors | | `INFO` | Informational messages | | `DEBUG` | Debug info (request method, URL, response status) | | `VERBOSE` | Full details (headers, request/response bodies) | Use `NONE` or `ERROR` in production to avoid exposing sensitive data in logs. ### Android Initialization 1. Add Chrome Custom Tabs dependency to your `build.gradle.kts`: ```kotlin theme={null} dependencies { implementation("androidx.browser:browser:1.9.0") } ``` 2. Initialize InsForge SDK (With Chrome Custom Tabs and Session Storage) ```kotlin theme={null} import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri import androidx.browser.customtabs.CustomTabsIntent import dev.insforge.createInsforgeClient import dev.insforge.ai.AI import dev.insforge.auth.Auth import dev.insforge.auth.BrowserLauncher import dev.insforge.auth.ClientType import dev.insforge.auth.SessionStorage import dev.insforge.database.Database import dev.insforge.functions.Functions import dev.insforge.realtime.Realtime import dev.insforge.storage.Storage class InsforgeManager(private val context: Context) { val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-anon-key" ) { install(Auth) { // 1. Chrome Custom Tabs - opens in-app, similar to iOS ASWebAuthenticationSession browserLauncher = BrowserLauncher { url -> val customTabsIntent = CustomTabsIntent.Builder() .setShowTitle(true) .build() // Handle non-Activity context safely if (context is Activity) { customTabsIntent.launchUrl(context, Uri.parse(url)) } else { customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) customTabsIntent.launchUrl(context, Uri.parse(url)) } } // 2. enable session persistence persistSession = true // 3. config SessionStorage (use SharedPreferences) sessionStorage = object : SessionStorage { private val prefs = context.getSharedPreferences( "insforge_auth", Context.MODE_PRIVATE ) override suspend fun save(key: String, value: String) { prefs.edit().putString(key, value).apply() } override suspend fun get(key: String): String? { return prefs.getString(key, null) } override suspend fun remove(key: String) { prefs.edit().remove(key).apply() } } // 4. set client type for mobile clientType = ClientType.MOBILE } // Install Database module install(Database) // Install Realtime module for real-time subscriptions install(Realtime) { debug = true } // Install other modules install(Storage) install(Functions) install(AI) } } ``` 3. Use Jetpack DataStore for Session Storage (Optional) ```kotlin theme={null} import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map val Context.authDataStore: DataStore by preferencesDataStore(name = "insforge_auth") class DataStoreSessionStorage(private val context: Context) : SessionStorage { override suspend fun save(key: String, value: String) { context.authDataStore.edit { prefs -> prefs[stringPreferencesKey(key)] = value } } override suspend fun get(key: String): String? { return context.authDataStore.data.map { prefs -> prefs[stringPreferencesKey(key)] }.first() } override suspend fun remove(key: String) { context.authDataStore.edit { prefs -> prefs.remove(stringPreferencesKey(key)) } } } // Then use it in your InsForge client install(Auth) { browserLauncher = ... persistSession = true sessionStorage = DataStoreSessionStorage(context) } ``` ## Features * **Database** - Type-safe CRUD operations with Kotlin Serialization * **Authentication** - Email/password and OAuth authentication * **Storage** - File upload, download, and management * **AI** - Chat completions and image generation * **Realtime** - WebSocket-based pub/sub messaging * **Coroutines** - Full Kotlin Coroutines support * **Jetpack Compose** - Composable functions and state management ## Platform Support | Platform | Support | | ---------------------------------- | ----------------------------- | | Android | 5.0+ (API 21+) | | JVM | Java 11+ | | Kotlin Multiplatform (coming soon) | iOS, macOS, JS (experimental) | ## SDK Reference CRUD operations with Kotlin data classes Sign up, sign in, OAuth, and user management File upload, download, and management Chat completions and image generation WebSocket pub/sub messaging # Realtime SDK Reference Source: https://docs.insforge.dev/sdks/kotlin/realtime Subscribe to channels, publish events, and track presence with the InsForge Kotlin SDK. ## Installation 1. Add InsForge dependencies to your project build.gradle.kts: ```kotlin theme={null} repositories { mavenLocal() // For local development mavenCentral() } dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` First, create a GitHub Personal Access Token: * Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) * Select permission: `read:packages` Then configure your project using one of the following methods: settings.gradle.kts (or build.gradle.kts): ```kotlin theme={null} repositories { mavenCentral() maven { url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin") credentials { username = System.getenv("GITHUB_USER") ?: "" password = System.getenv("GITHUB_TOKEN") ?: "" } } } ``` build.gradle.kts: ```kotlin theme={null} dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` Set environment variables before building: ```bash theme={null} export GITHUB_USER="your-github-username" export GITHUB_TOKEN="your-personal-access-token" ``` Add credentials to your global Gradle properties file: \~/.gradle/gradle.properties: ```properties theme={null} gpr.user=your-github-username gpr.token=ghp_xxxxxxxxxxxx ``` settings.gradle.kts: ```kotlin theme={null} repositories { mavenCentral() maven { url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin") credentials { username = providers.gradleProperty("gpr.user").orNull ?: "" password = providers.gradleProperty("gpr.token").orNull ?: "" } } } ``` build.gradle.kts: ```kotlin theme={null} dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` The `~/.gradle/gradle.properties` file is stored outside your project, so credentials won't be accidentally committed to version control. 2. Initialize InsForge SDK ```kotlin theme={null} import dev.insforge.createInsforgeClient import dev.insforge.auth.Auth import dev.insforge.database.Database import dev.insforge.storage.Storage import dev.insforge.functions.Functions import dev.insforge.realtime.Realtime import dev.insforge.ai.AI val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-api-key" ) { install(Auth) install(Database) install(Storage) install(Functions) install(Realtime) { autoReconnect = true reconnectDelay = 5000 } install(AI) } ``` 3. Enable Logging (Optional) For debugging, you can configure the SDK log level: ```kotlin theme={null} import dev.insforge.InsforgeLogLevel val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-api-key" ) { // DEBUG: logs request method/URL and response status // VERBOSE: logs full headers and request/response bodies logLevel = InsforgeLogLevel.DEBUG install(Auth) install(Database) // ... other modules } ``` | Log Level | Description | | --------- | ------------------------------------------------- | | `NONE` | No logging (default, recommended for production) | | `ERROR` | Only errors | | `WARN` | Warnings and errors | | `INFO` | Informational messages | | `DEBUG` | Debug info (request method, URL, response status) | | `VERBOSE` | Full details (headers, request/response bodies) | Use `NONE` or `ERROR` in production to avoid exposing sensitive data in logs. ### Android Initialization 1. Add Chrome Custom Tabs dependency to your `build.gradle.kts`: ```kotlin theme={null} dependencies { implementation("androidx.browser:browser:1.9.0") } ``` 2. Initialize InsForge SDK (With Chrome Custom Tabs and Session Storage) ```kotlin theme={null} import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri import androidx.browser.customtabs.CustomTabsIntent import dev.insforge.createInsforgeClient import dev.insforge.ai.AI import dev.insforge.auth.Auth import dev.insforge.auth.BrowserLauncher import dev.insforge.auth.ClientType import dev.insforge.auth.SessionStorage import dev.insforge.database.Database import dev.insforge.functions.Functions import dev.insforge.realtime.Realtime import dev.insforge.storage.Storage class InsforgeManager(private val context: Context) { val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-anon-key" ) { install(Auth) { // 1. Chrome Custom Tabs - opens in-app, similar to iOS ASWebAuthenticationSession browserLauncher = BrowserLauncher { url -> val customTabsIntent = CustomTabsIntent.Builder() .setShowTitle(true) .build() // Handle non-Activity context safely if (context is Activity) { customTabsIntent.launchUrl(context, Uri.parse(url)) } else { customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) customTabsIntent.launchUrl(context, Uri.parse(url)) } } // 2. enable session persistence persistSession = true // 3. config SessionStorage (use SharedPreferences) sessionStorage = object : SessionStorage { private val prefs = context.getSharedPreferences( "insforge_auth", Context.MODE_PRIVATE ) override suspend fun save(key: String, value: String) { prefs.edit().putString(key, value).apply() } override suspend fun get(key: String): String? { return prefs.getString(key, null) } override suspend fun remove(key: String) { prefs.edit().remove(key).apply() } } // 4. set client type for mobile clientType = ClientType.MOBILE } // Install Database module install(Database) // Install Realtime module for real-time subscriptions install(Realtime) { debug = true } // Install other modules install(Storage) install(Functions) install(AI) } } ``` 3. Use Jetpack DataStore for Session Storage (Optional) ```kotlin theme={null} import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map val Context.authDataStore: DataStore by preferencesDataStore(name = "insforge_auth") class DataStoreSessionStorage(private val context: Context) : SessionStorage { override suspend fun save(key: String, value: String) { context.authDataStore.edit { prefs -> prefs[stringPreferencesKey(key)] = value } } override suspend fun get(key: String): String? { return context.authDataStore.data.map { prefs -> prefs[stringPreferencesKey(key)] }.first() } override suspend fun remove(key: String) { context.authDataStore.edit { prefs -> prefs.remove(stringPreferencesKey(key)) } } } // Then use it in your InsForge client install(Auth) { browserLauncher = ... persistSession = true sessionStorage = DataStoreSessionStorage(context) } ``` ## Mental Model The Kotlin SDK connects to InsForge Realtime over Socket.IO. Subscribe to channel names such as `order:123` or `chat:room-1`, then listen for event names published to those channels. Events are published by database triggers that call `realtime.publish(...)` or by clients that publish to a channel they have already joined. See [Realtime overview](/core-concepts/realtime/overview) for the channel and RLS model. ## Quick Start ```kotlin theme={null} import dev.insforge.realtime.models.SocketMessage client.realtime.connect() val response = client.realtime.subscribe("order:$orderId") if (!response.ok) { println("Subscribe failed: ${response.error?.message}") return } client.realtime.on("status_changed") { message -> message?.let { println("Status changed: ${it.payload}") } } ``` ## connect() Establish a realtime connection. ```kotlin theme={null} client.realtime.connect() println("Connected: ${client.realtime.isConnected}") println("Socket ID: ${client.realtime.socketId}") ``` Monitor connection state: ```kotlin theme={null} client.realtime.connectionState.collect { state -> when (state) { is Realtime.ConnectionState.Connected -> println("Connected") is Realtime.ConnectionState.Connecting -> println("Connecting") is Realtime.ConnectionState.Disconnected -> println("Disconnected") is Realtime.ConnectionState.Error -> println("Error: ${state.message}") } } ``` ## subscribe() Subscribe to a channel and receive the current presence snapshot. ```kotlin theme={null} val response = client.realtime.subscribe("chat:room-1") if (response.ok) { println("Subscribed to: ${response.channel}") println("Members: ${response.presence?.members}") } else { println("Error: ${response.error?.message}") } ``` ## publish() Publish an event to a channel. ```kotlin theme={null} client.realtime.publish( channel = "chat:room-1", event = "new_message", payload = mapOf( "text" to "Hello from Kotlin", "sentAt" to System.currentTimeMillis() ) ) ``` The client must subscribe to a channel before publishing to that same channel. If RLS is enabled on `realtime.messages`, publish is also checked against `INSERT` policies. ## on() Register an event listener. ```kotlin theme={null} client.realtime.on("new_message") { message -> message?.let { println("Message: ${it.payload}") } } ``` Connection and system events: | Event | Description | | ---------------- | ----------------------------------------------------- | | `presence:join` | A logical member became present in the channel. | | `presence:leave` | A logical member is no longer present in the channel. | | `realtime:error` | Subscribe or publish failed. | ## once() Listen for an event once. ```kotlin theme={null} client.realtime.once("checkout_completed") { message -> println("Checkout completed: ${message?.payload}") } ``` ## off() Remove an event listener. ```kotlin theme={null} val callback = Realtime.EventCallback { message -> println("Event received: $message") } client.realtime.on("status_changed", callback) client.realtime.off("status_changed", callback) ``` ## unsubscribe() Leave a channel. ```kotlin theme={null} client.realtime.unsubscribe("chat:room-1") ``` ## disconnect() Close the realtime connection. ```kotlin theme={null} client.realtime.disconnect() ``` ## getSubscribedChannels() Return locally subscribed channels. ```kotlin theme={null} val channels = client.realtime.getSubscribedChannels() println(channels) ``` ## Presence Successful subscriptions return a presence snapshot. Listen for `presence:join` and `presence:leave` to update local online state. Presence is ephemeral. It is not a durable room membership table. # Storage SDK Reference Source: https://docs.insforge.dev/sdks/kotlin/storage File upload, download, and management with the InsForge Kotlin SDK ## Installation 1. Add InsForge dependencies to your project build.gradle.kts: ```kotlin theme={null} repositories { mavenLocal() // For local development mavenCentral() } dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` First, create a GitHub Personal Access Token: * Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) * Select permission: `read:packages` Then configure your project using one of the following methods: settings.gradle.kts (or build.gradle.kts): ```kotlin theme={null} repositories { mavenCentral() maven { url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin") credentials { username = System.getenv("GITHUB_USER") ?: "" password = System.getenv("GITHUB_TOKEN") ?: "" } } } ``` build.gradle.kts: ```kotlin theme={null} dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` Set environment variables before building: ```bash theme={null} export GITHUB_USER="your-github-username" export GITHUB_TOKEN="your-personal-access-token" ``` Add credentials to your global Gradle properties file: \~/.gradle/gradle.properties: ```properties theme={null} gpr.user=your-github-username gpr.token=ghp_xxxxxxxxxxxx ``` settings.gradle.kts: ```kotlin theme={null} repositories { mavenCentral() maven { url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin") credentials { username = providers.gradleProperty("gpr.user").orNull ?: "" password = providers.gradleProperty("gpr.token").orNull ?: "" } } } ``` build.gradle.kts: ```kotlin theme={null} dependencies { implementation("dev.insforge:insforge-kotlin:0.1.6") } ``` The `~/.gradle/gradle.properties` file is stored outside your project, so credentials won't be accidentally committed to version control. 2. Initialize InsForge SDK ```kotlin theme={null} import dev.insforge.createInsforgeClient import dev.insforge.auth.Auth import dev.insforge.database.Database import dev.insforge.storage.Storage import dev.insforge.functions.Functions import dev.insforge.realtime.Realtime import dev.insforge.ai.AI val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-api-key" ) { install(Auth) install(Database) install(Storage) install(Functions) install(Realtime) { autoReconnect = true reconnectDelay = 5000 } install(AI) } ``` 3. Enable Logging (Optional) For debugging, you can configure the SDK log level: ```kotlin theme={null} import dev.insforge.InsforgeLogLevel val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-api-key" ) { // DEBUG: logs request method/URL and response status // VERBOSE: logs full headers and request/response bodies logLevel = InsforgeLogLevel.DEBUG install(Auth) install(Database) // ... other modules } ``` | Log Level | Description | | --------- | ------------------------------------------------- | | `NONE` | No logging (default, recommended for production) | | `ERROR` | Only errors | | `WARN` | Warnings and errors | | `INFO` | Informational messages | | `DEBUG` | Debug info (request method, URL, response status) | | `VERBOSE` | Full details (headers, request/response bodies) | Use `NONE` or `ERROR` in production to avoid exposing sensitive data in logs. ### Android Initialization 1. Add Chrome Custom Tabs dependency to your `build.gradle.kts`: ```kotlin theme={null} dependencies { implementation("androidx.browser:browser:1.9.0") } ``` 2. Initialize InsForge SDK (With Chrome Custom Tabs and Session Storage) ```kotlin theme={null} import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri import androidx.browser.customtabs.CustomTabsIntent import dev.insforge.createInsforgeClient import dev.insforge.ai.AI import dev.insforge.auth.Auth import dev.insforge.auth.BrowserLauncher import dev.insforge.auth.ClientType import dev.insforge.auth.SessionStorage import dev.insforge.database.Database import dev.insforge.functions.Functions import dev.insforge.realtime.Realtime import dev.insforge.storage.Storage class InsforgeManager(private val context: Context) { val client = createInsforgeClient( baseUrl = "https://your-app.insforge.app", anonKey = "your-anon-key" ) { install(Auth) { // 1. Chrome Custom Tabs - opens in-app, similar to iOS ASWebAuthenticationSession browserLauncher = BrowserLauncher { url -> val customTabsIntent = CustomTabsIntent.Builder() .setShowTitle(true) .build() // Handle non-Activity context safely if (context is Activity) { customTabsIntent.launchUrl(context, Uri.parse(url)) } else { customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) customTabsIntent.launchUrl(context, Uri.parse(url)) } } // 2. enable session persistence persistSession = true // 3. config SessionStorage (use SharedPreferences) sessionStorage = object : SessionStorage { private val prefs = context.getSharedPreferences( "insforge_auth", Context.MODE_PRIVATE ) override suspend fun save(key: String, value: String) { prefs.edit().putString(key, value).apply() } override suspend fun get(key: String): String? { return prefs.getString(key, null) } override suspend fun remove(key: String) { prefs.edit().remove(key).apply() } } // 4. set client type for mobile clientType = ClientType.MOBILE } // Install Database module install(Database) // Install Realtime module for real-time subscriptions install(Realtime) { debug = true } // Install other modules install(Storage) install(Functions) install(AI) } } ``` 3. Use Jetpack DataStore for Session Storage (Optional) ```kotlin theme={null} import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map val Context.authDataStore: DataStore by preferencesDataStore(name = "insforge_auth") class DataStoreSessionStorage(private val context: Context) : SessionStorage { override suspend fun save(key: String, value: String) { context.authDataStore.edit { prefs -> prefs[stringPreferencesKey(key)] = value } } override suspend fun get(key: String): String? { return context.authDataStore.data.map { prefs -> prefs[stringPreferencesKey(key)] }.first() } override suspend fun remove(key: String) { context.authDataStore.edit { prefs -> prefs.remove(stringPreferencesKey(key)) } } } // Then use it in your InsForge client install(Auth) { browserLauncher = ... persistSession = true sessionStorage = DataStoreSessionStorage(context) } ``` ## from() Get a bucket instance for file operations. ### Example ```kotlin theme={null} val bucket = insforge.storage.from("images") ``` *** ## upload() Upload a file with a specific path/key. ### Parameters * `path` (String) - File path/key in the bucket * `data` (ByteArray) - File content as bytes * `options` (DSL block, optional) - Upload options ### UploadOptions | Option | Type | Description | | ------------- | ------------------------ | ----------------------------------------- | | `contentType` | String? | MIME type (e.g., "image/jpeg") | | `upsert` | Boolean | Overwrite if file exists (default: false) | | `metadata` | `Map\?` | Custom metadata | ### Example ```kotlin theme={null} // Basic upload val imageData = bitmap.toByteArray() val result = insforge.storage .from("images") .upload("posts/post-123/cover.jpg", imageData) Log.d("Storage", "Uploaded: ${result.url}") // Upload with options val result = insforge.storage .from("images") .upload("posts/post-123/cover.jpg", imageData) { contentType = "image/jpeg" upsert = true // Overwrite if exists metadata = mapOf("userId" to "123") } // Using bracket syntax val result = insforge.storage["images"] .upload("avatars/user-123.jpg", imageData) { contentType = "image/jpeg" } ``` ### Upload from Android Uri Developers can use the following function to upload files from an Android Uri: ```kotlin theme={null} // Android Uri Upload sample: suspend fun uploadFromUri( bucket: BucketApi, uri: Uri, context: Context, path: String ): FileUploadResponse { val data = context.contentResolver.openInputStream(uri)?.use { it.readBytes() } ?: throw IllegalArgumentException("Cannot read Uri") val contentType = context.contentResolver.getType(uri) return bucket.upload(path, data) { this.contentType = contentType } } ``` *** ## uploadWithAutoKey() Upload a file with auto-generated unique key. ### Parameters * `filename` (String) - Original filename (used for extension detection) * `data` (ByteArray) - File content as bytes * `options` (DSL block, optional) - Upload options ### Example ```kotlin theme={null} val imageData = bitmap.toByteArray() val result = insforge.storage .from("uploads") .uploadWithAutoKey("photo.jpg", imageData) { contentType = "image/jpeg" } Log.d("Storage", "Auto-generated key: ${result.key}") Log.d("Storage", "URL: ${result.url}") // Save to database insforge.database .from("posts") .insertTyped(listOf( Post( imageUrl = result.url, imageKey = result.key, userId = userId ) )) .returning() .execute() ``` *** ## download() Download a file as ByteArray. ### Example ```kotlin theme={null} // Download file val data = insforge.storage .from("images") .download(path = "posts/post-123/cover.jpg") // Convert to Bitmap val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size) imageView.setImageBitmap(bitmap) ``` *** ## delete() Delete a file from storage. ### Example ```kotlin theme={null} // Get the file key from database val posts = insforge.database .from("posts") .select("image_key") .eq("id", "post-123") .execute() // Returns List val post = posts.firstOrNull() ?: return // Delete from storage insforge.storage .from("images") .delete(post.imageKey) // Clear database reference insforge.database .from("posts") .update(buildJsonObject { put("image_url", JsonNull) put("image_key", JsonNull) }) .eq("id", "post-123") .execute() ``` *** ## createSignedUrl() Create a signed URL for temporary access to a file. ### Parameters * `path` (String) - File path in the bucket * `expiresIn` (Int) - Expiration time in seconds ### Example ```kotlin theme={null} // Create a signed URL (expires in 1 hour) val url = insforge.storage .from("images") .createSignedUrl("posts/post-123/cover.jpg", expiresIn = 3600) Log.d("Storage", "Signed URL: $url") ``` *** ## getDownloadUrl() Get a download URL for a file. ### Example ```kotlin theme={null} val strategy = insforge.storage .from("images") .getDownloadUrl("posts/post-123/cover.jpg") Log.d("Storage", "Download URL: ${strategy.url}") ``` *** ## list() List files in a bucket with optional filtering. ### BucketListFilter Options | Option | Type | Description | | ----------- | --------- | ------------------------------ | | `prefix` | String? | Filter by path prefix | | `limit` | Int | Maximum results (default: 100) | | `offset` | Int | Pagination offset (default: 0) | | `sortBy` | String? | Sort field | | `sortOrder` | SortOrder | ASC or DESC (default: ASC) | ### Example ```kotlin theme={null} // List all files in bucket val files = insforge.storage .from("images") .list() files.forEach { file -> Log.d("Storage", "File: ${file.name}, Size: ${file.metadata?.size}") } // List with filters val files = insforge.storage .from("images") .list { prefix = "posts/" limit = 50 offset = 0 sortBy = "created_at" sortOrder = SortOrder.DESC } ``` *** ## Bucket Management ### listBuckets() List all storage buckets. ```kotlin theme={null} val buckets = insforge.storage.listBuckets() buckets.forEach { bucket -> Log.d("Storage", "Bucket: ${bucket.id}, Public: ${bucket.isPublic}") } ``` ### createBucket() Create a new storage bucket. ```kotlin theme={null} val bucket = insforge.storage.createBucket("my-bucket") { isPublic = true } Log.d("Storage", "Created bucket: ${bucket.id}") ``` ### updateBucket() Update bucket settings. ```kotlin theme={null} insforge.storage.updateBucket("my-bucket") { isPublic = false } ``` ### deleteBucket() Delete a storage bucket. ```kotlin theme={null} insforge.storage.deleteBucket("my-bucket") ``` *** ## Advanced Upload ### getUploadStrategy() Get upload strategy for large files or resumable uploads. ```kotlin theme={null} val strategy = insforge.storage .from("videos") .getUploadStrategy("videos/large-file.mp4", fileSize = 100_000_000) // Use strategy.url for upload Log.d("Storage", "Upload URL: ${strategy.url}") ``` ### confirmUpload() Confirm a completed upload. ```kotlin theme={null} insforge.storage .from("videos") .confirmUpload("videos/large-file.mp4") ``` # AI REST Reference Source: https://docs.insforge.dev/sdks/rest/ai Direct OpenRouter HTTP integration and InsForge Model Gateway helper endpoints ## Overview New AI integrations should call OpenRouter directly with the OpenRouter key provisioned by InsForge. The InsForge AI proxy routes for chat, image generation, and embeddings are deprecated compatibility endpoints. Use the dashboard to copy the active OpenRouter key into a server-only `OPENROUTER_API_KEY` environment variable. ## Direct OpenRouter REST ### Headers ```bash theme={null} Authorization: Bearer $OPENROUTER_API_KEY Content-Type: application/json ``` ### Chat Completion ```bash theme={null} curl -X POST https://openrouter.ai/api/v1/chat/completions \ -H "Authorization: Bearer $OPENROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-4o", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' ``` ### Streaming ```bash theme={null} curl -X POST https://openrouter.ai/api/v1/chat/completions \ -H "Authorization: Bearer $OPENROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-3.5-haiku", "stream": true, "messages": [ { "role": "user", "content": "Write a short product update." } ] }' ``` ### Embeddings ```bash theme={null} curl -X POST https://openrouter.ai/api/v1/embeddings \ -H "Authorization: Bearer $OPENROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/text-embedding-3-small", "input": "Hello world" }' ``` ### Models ```bash theme={null} curl "https://openrouter.ai/api/v1/models?output_modalities=all" ``` OpenRouter owns the current request and response shape for AI model calls. Check OpenRouter's API docs for model-specific parameters, supported modalities, plugins, and video generation endpoints. ## InsForge Admin Helper Endpoints These endpoints help the dashboard display the active Model Gateway key, model catalog, and usage overview. They require admin authentication. Use either an admin JWT bearer token or an InsForge API key bearer token in the `Authorization` header. ### Get Available Models ``` GET /api/ai/models ``` ```bash theme={null} curl "https://your-app.insforge.app/api/ai/models" \ -H "Authorization: Bearer " ``` ```bash theme={null} curl "https://your-app.insforge.app/api/ai/models" \ -H "Authorization: Bearer " ``` `GET /api/ai/models` now returns a flat array of model objects. Older AI proxy docs described a categorized object such as `{ "text": [...], "image": [...] }`; update existing callers that read category properties to filter by `inputModality` or `outputModality` instead. Returns the normalized OpenRouter model catalog as an array: ```json theme={null} [ { "id": "openai/gpt-4o", "modelId": "openai/gpt-4o", "provider": "openrouter", "inputModality": ["text", "image"], "outputModality": ["text"], "inputPrice": 2.5, "outputPrice": 10, "inputPriceLabel": "$2.50 / 1M tokens", "outputPriceLabel": "$10.00 / 1M tokens" } ] ``` ### Get Active OpenRouter Key ``` GET /api/ai/openrouter/api-key ``` ```bash theme={null} curl "https://your-app.insforge.app/api/ai/openrouter/api-key" \ -H "Authorization: Bearer " ``` ```bash theme={null} curl "https://your-app.insforge.app/api/ai/openrouter/api-key" \ -H "Authorization: Bearer " ``` ```json theme={null} { "apiKey": "sk-or-...", "maskedKey": "sk-or-abcd••••••••1234" } ``` `apiKey` is a raw secret. Do not log it, commit it, store it in browser storage, or return it from public endpoints. Only `maskedKey` is safe to display in client UI. ### Get Usage Overview ``` GET /api/ai/overview ``` ```bash theme={null} curl "https://your-app.insforge.app/api/ai/overview" \ -H "Authorization: Bearer " ``` ```bash theme={null} curl "https://your-app.insforge.app/api/ai/overview" \ -H "Authorization: Bearer " ``` Returns key-level OpenRouter usage and activity time series when the active key has activity access. ## Deprecated InsForge Proxy Routes The following routes still exist for existing applications, but new code should use OpenRouter directly. | Method | Endpoint | Replacement | | ------ | -------------------------- | ------------------------------------------------------------------------------------ | | POST | `/api/ai/chat/completion` | `POST https://openrouter.ai/api/v1/chat/completions` | | POST | `/api/ai/image/generation` | OpenRouter chat completions with image output or OpenRouter image-capable model docs | | POST | `/api/ai/embeddings` | `POST https://openrouter.ai/api/v1/embeddings` | ### Legacy Chat Proxy ``` POST /api/ai/chat/completion ``` This route validates an InsForge-shaped request, forwards it to OpenRouter, and returns an InsForge-shaped response. It does not expose the complete OpenRouter API surface. ### Legacy Image Proxy ``` POST /api/ai/image/generation ``` This route forwards an InsForge-shaped image request to OpenRouter chat completions with image output. It is retained for compatibility only. ### Legacy Embeddings Proxy ``` POST /api/ai/embeddings ``` This route forwards an InsForge-shaped embeddings request to OpenRouter. Prefer the direct OpenRouter embeddings endpoint for new code. # Authentication API Reference Source: https://docs.insforge.dev/sdks/rest/auth User authentication and session management via REST API ## Overview The Authentication API provides endpoints for user registration, login, email verification, password reset, and OAuth integration. ## Headers For authenticated function invocations: ```bash theme={null} Authorization: Bearer your-jwt-token-or-anon-key Content-Type: application/json ``` For admin endpoints: ```bash theme={null} Authorization: Bearer admin-jwt-token-Or-API-Key Content-Type: application/json ``` *** ## Register User Create a new user account. ``` POST /api/auth/users ``` ### Query Parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | -------------------------------------------------------------- | | `client_type` | string | No | Client type: `web` (default), `mobile`, `desktop`, or `server` | ### Request Body | Field | Type | Required | Description | | ------------ | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `email` | string | Yes | User email address | | `password` | string | Yes | Password (must meet configured requirements) | | `name` | string | No | User display name | | `redirectTo` | string | No | Used for link-based email verification. The email link always opens an InsForge backend endpoint first; after the token is verified, InsForge redirects the browser to this URL with the verification result. Required when `verifyEmailMethod` is `link`. This URL must be included in `allowedRedirectUrls`. Recommended: use your app's sign-in page. | ### Example (Web Client) ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/users" \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "password": "securepassword123", "name": "John Doe", "redirectTo": "http://localhost:3000/sign-in" }' ``` ### Example (Non-Web Client) ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/users?client_type=mobile" \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "password": "securepassword123", "name": "John Doe", "redirectTo": "myapp://sign-in" }' ``` ### Response (Web Client) ```json theme={null} { "user": { "id": "123e4567-e89b-12d3-a456-426614174000", "email": "user@example.com", "emailVerified": false, "providers": ["email"], "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" }, "accessToken": "eyJhbGciOiJIUzI1NiIs...", "csrfToken": "abc123...", "requireEmailVerification": false } ``` ### Response (Non-Web Client) ```json theme={null} { "user": { "id": "123e4567-e89b-12d3-a456-426614174000", "email": "user@example.com", "emailVerified": false, "providers": ["email"], "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" }, "accessToken": "eyJhbGciOiJIUzI1NiIs...", "refreshToken": "eyJhbGciOiJIUzI1NiIs...", "requireEmailVerification": false } ``` * For **web clients**: A `csrfToken` is returned and the refresh token is stored in an httpOnly cookie. * For **non-web clients** (`mobile`, `desktop`, `server`): A `refreshToken` is returned directly in the response. Store it securely in your client or server runtime. * Use **`server`** for trusted server-side callers such as SSR apps, BFFs, or CLIs that cannot rely on browser cookies. * If `requireEmailVerification` is `true`, `accessToken` and tokens will be `null` and the user must verify their email before logging in. *** ## Sign In Authenticate user and get access token. ``` POST /api/auth/sessions ``` ### Query Parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | -------------------------------------------------------------- | | `client_type` | string | No | Client type: `web` (default), `mobile`, `desktop`, or `server` | ### Request Body | Field | Type | Required | Description | | ---------- | ------ | -------- | ------------------ | | `email` | string | Yes | User email address | | `password` | string | Yes | User password | ### Example (Web Client) ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/sessions" \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "password": "securepassword123" }' ``` ### Example (Non-Web Client) ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/sessions?client_type=mobile" \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "password": "securepassword123" }' ``` ### Response (Web Client) ```json theme={null} { "user": { "id": "123e4567-e89b-12d3-a456-426614174000", "email": "user@example.com", "emailVerified": true, "providers": ["email"], "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" }, "accessToken": "eyJhbGciOiJIUzI1NiIs...", "csrfToken": "abc123..." } ``` ### Response (Non-Web Client) ```json theme={null} { "user": { "id": "123e4567-e89b-12d3-a456-426614174000", "email": "user@example.com", "emailVerified": true, "providers": ["email"], "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" }, "accessToken": "eyJhbGciOiJIUzI1NiIs...", "refreshToken": "eyJhbGciOiJIUzI1NiIs..." } ``` * For **web clients**: A `csrfToken` is returned and the refresh token is stored in an httpOnly cookie. Include the `csrfToken` in the `X-CSRF-Token` header when calling `/api/auth/refresh`. * For **non-web clients** (`mobile`, `desktop`, `server`): A `refreshToken` is returned directly. Store it securely and include it in the request body when calling `/api/auth/refresh`. *** ## Refresh Token Refresh access token using refresh token. ``` POST /api/auth/refresh ``` ### Query Parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | -------------------------------------------------------------- | | `client_type` | string | No | Client type: `web` (default), `mobile`, `desktop`, or `server` | ### Headers (Web Client) | Header | Type | Required | Description | | -------------- | ------ | -------- | ------------------------------------------------ | | `X-CSRF-Token` | string | Yes | CSRF token received from login/register response | ### Request Body (Non-Web Client) | Field | Type | Required | Description | | -------------- | ------ | -------- | --------------------------------------------------- | | `refreshToken` | string | Yes | Refresh token received from login/register response | ### Example (Web Client) ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/refresh" \ -H "X-CSRF-Token: abc123..." \ --cookie "refresh_token=..." ``` ### Example (Non-Web Client) ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/refresh?client_type=mobile" \ -H "Content-Type: application/json" \ -d '{ "refreshToken": "eyJhbGciOiJIUzI1NiIs..." }' ``` ### Response (Web Client) ```json theme={null} { "user": { "id": "123e4567-e89b-12d3-a456-426614174000", "email": "user@example.com", "emailVerified": true, "providers": ["email"], "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" }, "accessToken": "eyJhbGciOiJIUzI1NiIs...", "csrfToken": "def456..." } ``` ### Response (Non-Web Client) ```json theme={null} { "user": { "id": "123e4567-e89b-12d3-a456-426614174000", "email": "user@example.com", "emailVerified": true, "providers": ["email"], "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" }, "accessToken": "eyJhbGciOiJIUzI1NiIs...", "refreshToken": "eyJhbGciOiJIUzI1NiIs..." } ``` Token rotation is implemented for security: * **Web clients**: Each refresh returns a new `csrfToken` that must be used for subsequent refresh requests. * **Non-web clients** (`mobile`, `desktop`, `server`): Each refresh returns a new `refreshToken`. You must persist this new token and use it for the next refresh. Update the `accessToken` in memory. *** ## Logout Logout and clear refresh token cookie. ``` POST /api/auth/logout ``` ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/logout" ``` ### Response ```json theme={null} { "success": true, "message": "Logged out successfully" } ``` *** ## Get Current User Get the currently authenticated user's info from JWT token. This REST endpoint does not refresh expired access tokens by itself. * For raw REST clients, call `POST /api/auth/refresh` when needed. * For browser apps using the TypeScript SDK, call `auth.getCurrentUser()` during startup. The SDK will use the httpOnly refresh cookie automatically when it can refresh the session. * This automatic refresh behavior is browser-only. Server, mobile, and other non-browser clients should refresh explicitly. ``` GET /api/auth/sessions/current ``` ### Example ```bash theme={null} curl "https://your-app.insforge.app/api/auth/sessions/current" \ -H "Authorization: Bearer your-jwt-token" ``` ### Response ```json theme={null} { "user": { "id": "123e4567-e89b-12d3-a456-426614174000", "email": "user@example.com", "role": "authenticated" } } ``` *** ## Update Profile Update the current user's profile. ``` PATCH /api/auth/profiles/current ``` ### Request Body | Field | Type | Required | Description | | --------- | ------ | -------- | ----------------------------------------------- | | `profile` | object | Yes | Profile data (name, avatar\_url, custom fields) | ### Example ```bash theme={null} curl -X PATCH "https://your-app.insforge.app/api/auth/profiles/current" \ -H "Authorization: Bearer your-jwt-token" \ -H "Content-Type: application/json" \ -d '{ "profile": { "name": "John Doe", "avatar_url": "https://example.com/avatar.jpg" } }' ``` ### Response ```json theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "profile": { "name": "John Doe", "avatar_url": "https://example.com/avatar.jpg" } } ``` *** ## Get User Profile Get public profile information for a user by ID. ``` GET /api/auth/profiles/{userId} ``` ### Example ```bash theme={null} curl "https://your-app.insforge.app/api/auth/profiles/123e4567-e89b-12d3-a456-426614174000" ``` ### Response ```json theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "profile": { "name": "John Doe", "avatar_url": "https://example.com/avatar.jpg" } } ``` *** ## Email Verification ### Send Verification Email ``` POST /api/auth/email/send-verification ``` ### Request Body | Field | Type | Required | Description | | ------------ | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `email` | string | Yes | User email address | | `redirectTo` | string | No | Used for link-based email verification. The email link always opens an InsForge backend endpoint first; after the token is verified, InsForge redirects the browser to this URL with the verification result. Required when `verifyEmailMethod` is `link`. This URL must be included in `allowedRedirectUrls`. Recommended: use your app's sign-in page. | ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/email/send-verification" \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "redirectTo": "http://localhost:3000/sign-in" }' ``` ### Response ```json theme={null} { "success": true, "message": "If your email is registered, we have sent you a verification code/link." } ``` ### Verify Email ``` POST /api/auth/email/verify ``` ### Query Parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | -------------------------------------------------------------- | | `client_type` | string | No | Client type: `web` (default), `mobile`, `desktop`, or `server` | ### Request Body | Field | Type | Required | Description | | ------- | ------ | -------- | ------------------------- | | `email` | string | Yes | User email address | | `otp` | string | Yes | 6-digit verification code | For link-based verification, email clicks use: ``` GET /api/auth/email/verify-link?token=... ``` That browser-oriented GET flow verifies the token on the backend and redirects to the stored `redirectTo` URL. `POST /api/auth/email/verify` is the JSON API for 6-digit code submission. Handle the browser redirect like this: * Success: `?insforge_status=success&insforge_type=verify_email` * Error: `?insforge_status=error&insforge_type=verify_email&insforge_error=...` * `insforge_status`: Result of the browser link flow. For verification, values are `success` or `error`. * `insforge_type`: Flow identifier. For verification links this is always `verify_email`. * `insforge_error`: Present only when `insforge_status=error`. Human-readable error message for display or logging. * Recommended handling: use your sign-in page as `redirectTo`. When `insforge_status=success`, show a confirmation message and ask the user to sign in with their email and password. * If `redirectTo` is not allowlisted, InsForge returns a `400` error whose message includes the rejected URL and whose `nextActions` tells you to add it to `allowedRedirectUrls`. ### Example (Web Client) ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/email/verify" \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "otp": "123456" }' ``` ### Example (Non-Web Client) ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/email/verify?client_type=mobile" \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "otp": "123456" }' ``` ### Response (Web Client) ```json theme={null} { "user": { "id": "123e4567-e89b-12d3-a456-426614174000", "email": "user@example.com", "emailVerified": true }, "accessToken": "eyJhbGciOiJIUzI1NiIs...", "csrfToken": "abc123..." } ``` ### Response (Non-Web Client) ```json theme={null} { "user": { "id": "123e4567-e89b-12d3-a456-426614174000", "email": "user@example.com", "emailVerified": true }, "accessToken": "eyJhbGciOiJIUzI1NiIs...", "refreshToken": "eyJhbGciOiJIUzI1NiIs..." } ``` *** ## Password Reset ### Send Reset Email ``` POST /api/auth/email/send-reset-password ``` ### Request Body | Field | Type | Required | Description | | ------------ | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `email` | string | Yes | User email address | | `redirectTo` | string | No | Used for link-based password reset. The email link always opens an InsForge backend endpoint first; InsForge then redirects the browser to this URL with the reset `token` in the query string so your app can render its own reset-password page. Required when `resetPasswordMethod` is `link`. This URL must be included in `allowedRedirectUrls`. Recommended: use your app's dedicated reset-password page. | ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/email/send-reset-password" \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "redirectTo": "http://localhost:3000/reset-password" }' ``` ### Exchange Code for Token (Code Method Only) ``` POST /api/auth/email/exchange-reset-password-token ``` ### Request Body | Field | Type | Required | Description | | ------- | ------ | -------- | ----------------------- | | `email` | string | Yes | User email address | | `code` | string | Yes | 6-digit code from email | ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/email/exchange-reset-password-token" \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "code": "123456" }' ``` ### Response ```json theme={null} { "token": "abc123...", "expiresAt": "2024-01-15T11:00:00Z" } ``` ### Reset Password ``` POST /api/auth/email/reset-password ``` For link-based password reset, email clicks use: ``` GET /api/auth/email/reset-password-link?token=... ``` That browser-oriented GET flow validates the token on the backend and redirects to the stored `redirectTo` URL with the reset token in the query string. `POST /api/auth/email/reset-password` remains the JSON API that accepts the new password. Handle the browser redirect like this: * Ready to reset: `?token=...&insforge_status=ready&insforge_type=reset_password` * Error: `?insforge_status=error&insforge_type=reset_password&insforge_error=...` * `token`: Present only when `insforge_status=ready`. Pass this value to `POST /api/auth/email/reset-password` as `otp`. * `insforge_status`: Result of the browser link flow. For reset links, values are `ready` or `error`. * `insforge_type`: Flow identifier. For reset links this is always `reset_password`. * `insforge_error`: Present only when `insforge_status=error`. Human-readable error message for display or logging. * Your app should only render the reset-password form when `insforge_status=ready` and `token` is present. ### Request Body | Field | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------------------------------ | | `newPassword` | string | Yes | New password | | `otp` | string | Yes | Reset token from either the code exchange endpoint or the magic link URL | ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/email/reset-password" \ -H "Content-Type: application/json" \ -d '{ "newPassword": "newSecurePassword123", "otp": "abc123..." }' ``` ### Response ```json theme={null} { "message": "Password reset successfully" } ``` *** ## OAuth Authentication OAuth authentication now uses the PKCE (Proof Key for Code Exchange) flow for enhanced security. Instead of returning tokens directly in the redirect URL, an authorization code is returned which must be exchanged for tokens. ### Initiate OAuth Flow ``` GET /api/auth/oauth/{provider} ``` For custom providers configured in the dashboard, use: ``` GET /api/auth/oauth/custom/{key} ``` ### Query Parameters | Parameter | Type | Required | Description | | ------------------------- | ------ | -------- | ----------------------------------------------------------------------- | | `redirect_uri` | string | Yes | URL to redirect after authentication | | `code_challenge` | string | Yes | PKCE code challenge (Base64 URL-encoded SHA256 hash of code\_verifier) | | Other string query params | string | No | Provider-specific OAuth hints, such as Google's `prompt=select_account` | Extra query params are forwarded as provider-specific hints only when they do not collide with server-owned OAuth fields. Do not pass `client_id`, `redirect_uri`, `code_challenge`, `state`, `response_type`, or `scope`; InsForge/provider-generated values win and colliding client values are ignored. ### Supported Providers * `google` * `github` * `discord` * `linkedin` * `facebook` * `apple` * `microsoft` * `x` * `spotify` * Any custom provider key returned by `GET /api/auth/public-config` in `customOAuthProviders` ### Example ```bash theme={null} # Generate code_verifier (random string, 43-128 characters) CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=' | tr '/+' '_-') # Generate code_challenge (SHA256 hash of code_verifier, Base64 URL-encoded) CODE_CHALLENGE=$(echo -n $CODE_VERIFIER | openssl dgst -sha256 -binary | base64 | tr -d '=' | tr '/+' '_-') curl "https://your-app.insforge.app/api/auth/oauth/google?redirect_uri=https://myapp.com/callback&code_challenge=$CODE_CHALLENGE&prompt=select_account" ``` ```bash theme={null} # Custom provider example curl "https://your-app.insforge.app/api/auth/oauth/custom/acme?redirect_uri=https://myapp.com/callback&code_challenge=$CODE_CHALLENGE" ``` ### Response ```json theme={null} { "authUrl": "https://accounts.google.com/o/oauth2/v2/auth?client_id=..." } ``` ### OAuth Callback After the user authenticates with the provider, they will be redirected to your `redirect_uri` with an authorization code: ``` https://myapp.com/callback?insforge_code=abc123... ``` The `insforge_code` is a temporary authorization code that must be exchanged for tokens using the `/api/auth/oauth/exchange` endpoint. *** ### Exchange Code for Tokens Exchange the authorization code for access and refresh tokens. ``` POST /api/auth/oauth/exchange ``` ### Query Parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | -------------------------------------------------------------- | | `client_type` | string | No | Client type: `web` (default), `mobile`, `desktop`, or `server` | ### Request Body | Field | Type | Required | Description | | --------------- | ------ | -------- | ---------------------------------------------------------------- | | `code` | string | Yes | The `insforge_code` received in the callback | | `code_verifier` | string | Yes | The original code\_verifier used to generate the code\_challenge | ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/oauth/exchange?client_type=mobile" \ -H "Content-Type: application/json" \ -d '{ "code": "abc123...", "code_verifier": "your-original-code-verifier" }' ``` ### Response ```json theme={null} { "user": { "id": "123e4567-e89b-12d3-a456-426614174000", "email": "user@example.com", "emailVerified": true, "providers": ["google"], "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" }, "accessToken": "eyJhbGciOiJIUzI1NiIs...", "refreshToken": "eyJhbGciOiJIUzI1NiIs..." } ``` * For **web clients**: The `refreshToken` will be `null` and a `csrfToken` is returned instead. The refresh token is stored in an httpOnly cookie. * For **non-web clients** (`mobile`, `desktop`, `server`): A `refreshToken` is returned directly. Store it securely. *** ### Complete OAuth Flow Example (Non-Web) ```swift theme={null} // 1. Generate PKCE code verifier and challenge let codeVerifier = generateRandomString(length: 43) let codeChallenge = sha256(codeVerifier).base64URLEncoded() // 2. Initiate OAuth flow let authURL = "https://your-app.insforge.app/api/auth/oauth/google" + "?redirect_uri=myapp://callback" + "&code_challenge=\(codeChallenge)" // 3. Open browser/WebView and wait for callback // User completes authentication... // 4. Handle callback with insforge_code // myapp://callback?insforge_code=abc123... // 5. Exchange code for tokens let response = POST("/api/auth/oauth/exchange?client_type=mobile", body: { "code": insforgeCode, "code_verifier": codeVerifier }) // 6. Store tokens accessToken = response.accessToken refreshToken = response.refreshToken // Persist securely ``` *** ## Public Configuration Get public authentication settings (no auth required). ``` GET /api/auth/public-config ``` ### Example ```bash theme={null} curl "https://your-app.insforge.app/api/auth/public-config" ``` ### Response ```json theme={null} { "oAuthProviders": ["google", "github"], "customOAuthProviders": ["acme"], "requireEmailVerification": true, "passwordMinLength": 8, "requireNumber": true, "requireLowercase": true, "requireUppercase": false, "requireSpecialChar": false, "verifyEmailMethod": "code", "resetPasswordMethod": "link" } ``` *** ## Admin Endpoints These endpoints require `project_admin` role. ### List All Users ``` GET /api/auth/users?offset=0&limit=10&search=john ``` ### Get User by ID ``` GET /api/auth/users/{userId} ``` ### Delete Users ```bash theme={null} curl -X DELETE "https://your-app.insforge.app/api/auth/users" \ -H "Authorization: Bearer admin-jwt-token" \ -H "Content-Type: application/json" \ -d '{"userIds": ["user-id-1", "user-id-2"]}' ``` ### Generate Anonymous Token ``` POST /api/auth/tokens/anon ``` ### Get Auth Configuration Get current authentication settings (admin only). ``` GET /api/auth/config ``` #### Example ```bash theme={null} curl "https://your-app.insforge.app/api/auth/config" \ -H "Authorization: Bearer admin-jwt-token" ``` #### Response ```json theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "requireEmailVerification": true, "passwordMinLength": 8, "requireNumber": true, "requireLowercase": true, "requireUppercase": false, "requireSpecialChar": false, "verifyEmailMethod": "code", "resetPasswordMethod": "link", "allowedRedirectUrls": ["https://myapp.com/dashboard", "https://*.myapp.com"], "createdAt": "2024-01-15T10:30:00Z", "updatedAt": "2024-01-15T10:30:00Z" } ``` `allowedRedirectUrls` entries are matched against the full `redirectTo` value, including scheme, host, optional port, and path. * Exact entries must match exactly, such as `https://myapp.com/dashboard`. * Wildcards are supported only in the host portion, such as `https://*.myapp.com/callback`. * Deep links are allowed when explicitly listed, such as `com.example.app:/oauth2redirect` or `myapp://auth/callback`. * If `allowedRedirectUrls` is empty, InsForge allows all redirects for developer convenience. This is insecure for production and should be avoided outside local development. ### Update Auth Configuration Update authentication settings (admin only). ``` PUT /api/auth/config ``` #### Request Body | Field | Type | Required | Description | | -------------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `requireEmailVerification` | boolean | No | Whether email verification is required | | `passwordMinLength` | integer | No | Minimum password length (4-128) | | `requireNumber` | boolean | No | Require numbers in password | | `requireLowercase` | boolean | No | Require lowercase in password | | `requireUppercase` | boolean | No | Require uppercase in password | | `requireSpecialChar` | boolean | No | Require special characters in password | | `verifyEmailMethod` | string | No | Email verification method (`code` or `link`) | | `resetPasswordMethod` | string | No | Password reset method (`code` or `link`) | | `allowedRedirectUrls` | array | No | List of allowed redirect URLs. Entries are matched against the full `redirectTo` value. Exact URLs must match exactly, host wildcards such as `https://*.domain.com/callback` are supported, and custom deep links such as `com.example.app:/oauth2redirect` are allowed when explicitly listed. If empty, all redirects are allowed, which is insecure for production. | #### Example ```bash theme={null} curl -X PUT "https://your-app.insforge.app/api/auth/config" \ -H "Authorization: Bearer admin-jwt-token" \ -H "Content-Type: application/json" \ -d '{ "requireEmailVerification": true, "passwordMinLength": 10, "verifyEmailMethod": "link" }' ``` ### Exchange Admin Session Exchange a cloud provider authorization code for an admin session. ``` POST /api/auth/admin/sessions/exchange ``` #### Request Body | Field | Type | Required | Description | | ------ | ------ | -------- | --------------------------------------------- | | `code` | string | Yes | Authorization code or JWT from Insforge Cloud | #### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/admin/sessions/exchange" \ -H "Content-Type: application/json" \ -d '{"code": "eyJhbGciOiJIUzI1NiIs..."}' ``` #### Response ```json theme={null} { "admin": { "sub": "cloud:user_123" }, "accessToken": "eyJhbGciOiJIUzI1NiIs...", "csrfToken": "abc123..." } ``` ### Get Current Admin Session Get the current dashboard admin session from a project admin access token. ``` GET /api/auth/admin/sessions/current ``` #### Response ```json theme={null} { "admin": { "sub": "local:admin" } } ``` ### Refresh Admin Session Refresh a dashboard admin access token. This endpoint uses the dashboard-only `insforge_admin_refresh_token` httpOnly cookie and does not share the app/user refresh cookie. ``` POST /api/auth/admin/refresh ``` ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/auth/admin/refresh" \ -H "X-CSRF-Token: abc123..." \ --cookie "insforge_admin_refresh_token=..." ``` ### Logout Admin Session ``` POST /api/auth/admin/logout ``` *** ## Error Responses ### Invalid Credentials (401) ```json theme={null} { "error": "INVALID_CREDENTIALS", "message": "Invalid email or password", "statusCode": 401, "nextActions": "Check your email and password" } ``` ### User Already Exists (409) ```json theme={null} { "error": "USER_EXISTS", "message": "User with this email already exists", "statusCode": 409, "nextActions": "Use a different email or sign in" } ``` ### Email Not Verified (403) ```json theme={null} { "error": "EMAIL_NOT_VERIFIED", "message": "Please verify your email before signing in", "statusCode": 403, "nextActions": "Check your inbox for verification email" } ``` # Database API Reference Source: https://docs.insforge.dev/sdks/rest/database PostgREST-style database operations via REST API ## Overview The Database API provides PostgREST-style CRUD operations for your database tables. All requests require authentication headers. ## Headers ```bash theme={null} Authorization: Bearer your-jwt-token-or-anon-key Content-Type: application/json ``` *** ## Query Records Retrieve records from a table with filtering, sorting, and pagination. ``` GET /api/database/records/{tableName} ``` ### Query Parameters | Parameter | Type | Description | | --------- | ------- | ------------------------------------------------ | | `limit` | integer | Maximum records to return (1-1000, default: 100) | | `offset` | integer | Records to skip for pagination (default: 0) | | `order` | string | Sort order (e.g., `createdAt.desc`, `name.asc`) | | `select` | string | Comma-separated columns to return | | `{field}` | string | PostgREST filter (e.g., `status=eq.active`) | ### Filter Operators | Operator | Description | Example | | -------- | -------------------------------- | ---------------------------- | | `eq` | Equal | `status=eq.active` | | `neq` | Not equal | `status=neq.deleted` | | `gt` | Greater than | `age=gt.18` | | `gte` | Greater than or equal | `price=gte.100` | | `lt` | Less than | `quantity=lt.10` | | `lte` | Less than or equal | `score=lte.50` | | `like` | Pattern match (case-sensitive) | `name=like.*john*` | | `ilike` | Pattern match (case-insensitive) | `email=ilike.*@gmail.com` | | `in` | In list | `status=in.(active,pending)` | | `is` | Is null/true/false | `deleted_at=is.null` | ### Example ```bash theme={null} # Get all posts curl "https://your-app.insforge.app/api/database/records/posts" \ -H "Authorization: Bearer your-jwt-token" # Get posts with filters curl "https://your-app.insforge.app/api/database/records/posts?status=eq.published&order=createdAt.desc&limit=10" \ -H "Authorization: Bearer your-jwt-token" # Select specific columns curl "https://your-app.insforge.app/api/database/records/posts?select=id,title,author" \ -H "Authorization: Bearer your-jwt-token" ``` ### Response ```json theme={null} [ { "id": "248373e1-0aea-45ce-8844-5ef259203749", "title": "Getting Started with InsForge", "content": "This is a guide to help you get started...", "createdAt": "2025-07-18T05:37:24.338Z", "updatedAt": "2025-07-18T05:37:24.338Z" } ] ``` ### Response Headers | Header | Description | | --------------- | --------------------------------------------- | | `X-Total-Count` | Total records matching the query | | `Content-Range` | Range of records returned (e.g., `0-99/1234`) | *** ## Create Records Create one or more records in a table. ``` POST /api/database/records/{tableName} ``` **Important**: Request body MUST be an array, even for single records. ### Headers | Header | Value | Description | | -------- | ----------------------- | --------------------------------- | | `Prefer` | `return=representation` | Include to return created records | ### Example ```bash theme={null} # Create a single record curl -X POST "https://your-app.insforge.app/api/database/records/posts" \ -H "Authorization: Bearer your-jwt-token" \ -H "Content-Type: application/json" \ -H "Prefer: return=representation" \ -d '[{ "title": "My First Post", "content": "Hello world!", "published": true }]' # Create multiple records curl -X POST "https://your-app.insforge.app/api/database/records/posts" \ -H "Authorization: Bearer your-jwt-token" \ -H "Content-Type: application/json" \ -H "Prefer: return=representation" \ -d '[ {"title": "Post 1", "content": "Content 1"}, {"title": "Post 2", "content": "Content 2"} ]' ``` ### Response Without `Prefer` header: ```json theme={null} [] ``` With `Prefer: return=representation`: ```json theme={null} [ { "id": "248373e1-0aea-45ce-8844-5ef259203749", "title": "My First Post", "content": "Hello world!", "published": true, "createdAt": "2025-07-18T05:37:24.338Z", "updatedAt": "2025-07-18T05:37:24.338Z" } ] ``` *** ## Update Records Update records matching query filters. ``` PATCH /api/database/records/{tableName} ``` ### Query Parameters Use filter operators to specify which records to update. ### Headers | Header | Value | Description | | -------- | ----------------------- | --------------------------------- | | `Prefer` | `return=representation` | Include to return updated records | ### Example ```bash theme={null} # Update a single record by ID curl -X PATCH "https://your-app.insforge.app/api/database/records/posts?id=eq.248373e1-0aea-45ce-8844-5ef259203749" \ -H "Authorization: Bearer your-jwt-token" \ -H "Content-Type: application/json" \ -H "Prefer: return=representation" \ -d '{ "title": "Updated Post Title", "content": "This content has been updated." }' # Update multiple records curl -X PATCH "https://your-app.insforge.app/api/database/records/posts?status=eq.draft" \ -H "Authorization: Bearer your-jwt-token" \ -H "Content-Type: application/json" \ -d '{"status": "archived"}' ``` ### Response ```json theme={null} [ { "id": "248373e1-0aea-45ce-8844-5ef259203749", "title": "Updated Post Title", "content": "This content has been updated.", "createdAt": "2025-01-01T00:00:00Z", "updatedAt": "2025-01-21T11:00:00Z" } ] ``` *** ## Delete Records Delete records matching query filters. ``` DELETE /api/database/records/{tableName} ``` ### Query Parameters Use filter operators to specify which records to delete. ### Headers | Header | Value | Description | | -------- | ----------------------- | --------------------------------- | | `Prefer` | `return=representation` | Include to return deleted records | ### Example ```bash theme={null} # Delete by ID curl -X DELETE "https://your-app.insforge.app/api/database/records/posts?id=eq.248373e1-0aea-45ce-8844-5ef259203749" \ -H "Authorization: Bearer your-jwt-token" # Delete with Prefer header to see deleted records curl -X DELETE "https://your-app.insforge.app/api/database/records/posts?status=eq.archived" \ -H "Authorization: Bearer your-jwt-token" \ -H "Prefer: return=representation" ``` ### Response Without `Prefer` header: `204 No Content` With `Prefer: return=representation`: ```json theme={null} [ { "id": "248373e1-0aea-45ce-8844-5ef259203749", "title": "Deleted Post", "createdAt": "2025-01-01T00:00:00Z" } ] ``` *** ## Error Responses ### Table Not Found (404) ```json theme={null} { "error": "TABLE_NOT_FOUND", "message": "Table 'nonexistent' does not exist", "statusCode": 404, "nextActions": "Check table name and try again" } ``` ### Invalid Query (400) ```json theme={null} { "error": "INVALID_QUERY", "message": "Invalid filter syntax", "statusCode": 400, "nextActions": "Check PostgREST filter documentation" } ``` ### Validation Error (400) ```json theme={null} { "error": "VALIDATION_ERROR", "message": "Invalid field type: expected boolean for 'published'", "statusCode": 400, "nextActions": "Ensure field types match the table schema" } ``` *** ## Examples ### Pagination ```bash theme={null} # Page 1 (first 20 records) curl "https://your-app.insforge.app/api/database/records/posts?limit=20&offset=0" # Page 2 (next 20 records) curl "https://your-app.insforge.app/api/database/records/posts?limit=20&offset=20" ``` ### Complex Filters ```bash theme={null} # Multiple conditions curl "https://your-app.insforge.app/api/database/records/posts?status=eq.published&author_id=eq.123&order=createdAt.desc" # Search with pattern curl "https://your-app.insforge.app/api/database/records/users?email=ilike.*@company.com" # In list curl "https://your-app.insforge.app/api/database/records/orders?status=in.(pending,processing)" # Null check curl "https://your-app.insforge.app/api/database/records/tasks?completed_at=is.null" ``` ### Upsert (Insert or Update) Insert a record or update it if a conflict occurs on a unique constraint. ``` POST /api/database/records/{tableName} ``` #### Headers | Header | Value | Description | | -------- | ------------------------------ | ---------------------------------- | | `Prefer` | `resolution=merge-duplicates` | Update existing record on conflict | | `Prefer` | `resolution=ignore-duplicates` | Ignore insert if record exists | #### Example ```bash theme={null} # Upsert: insert or update on conflict curl -X POST "https://your-app.insforge.app/api/database/records/user_settings" \ -H "Authorization: Bearer your-jwt-token" \ -H "Content-Type: application/json" \ -H "Prefer: resolution=merge-duplicates,return=representation" \ -d '[{ "user_id": "123e4567-e89b-12d3-a456-426614174000", "theme": "dark", "notifications": true }]' ``` #### Response ```json theme={null} [ { "id": "550e8400-e29b-41d4-a716-446655440000", "user_id": "123e4567-e89b-12d3-a456-426614174000", "theme": "dark", "notifications": true, "createdAt": "2025-01-01T00:00:00Z", "updatedAt": "2025-01-21T11:00:00Z" } ] ``` Upsert requires a unique constraint (e.g., primary key or unique index) on the table. The conflict resolution is based on this constraint. *** ## Call RPC Function Execute a database function via PostgREST RPC. Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE). ``` POST /api/database/rpc/{functionName} ``` ### Example ```bash theme={null} # Call a function with parameters curl -X POST "https://your-app.insforge.app/api/database/rpc/get_user_stats" \ -H "Authorization: Bearer your-jwt-token" \ -H "Content-Type: application/json" \ -d '{"user_id": "123e4567-e89b-12d3-a456-426614174000"}' # Call a function without parameters curl -X POST "https://your-app.insforge.app/api/database/rpc/get_total_count" \ -H "Authorization: Bearer your-jwt-token" ``` ### Response ```json theme={null} { "total_posts": 42, "total_comments": 128, "last_activity": "2025-01-15T10:30:00Z" } ``` *** ## Admin Endpoints The following endpoints require admin authentication. ### Headers for Admin Endpoints ```bash theme={null} Authorization: Bearer admin-jwt-token-Or-API-Key Content-Type: application/json ``` *** ## List Database Functions Get all database functions in the public schema. **Requires admin authentication.** ``` GET /api/database/functions ``` ### Example ```bash theme={null} curl "https://your-app.insforge.app/api/database/functions" \ -H "Authorization: Bearer admin-jwt-token-Or-API-Key" ``` ### Response ```json theme={null} [ { "name": "get_user_stats", "schema": "public", "language": "plpgsql", "returnType": "json", "arguments": "user_id uuid", "definition": "BEGIN ... END;" } ] ``` *** ## List Database Indexes Get all database indexes. **Requires admin authentication.** ``` GET /api/database/indexes ``` ### Example ```bash theme={null} curl "https://your-app.insforge.app/api/database/indexes" \ -H "Authorization: Bearer admin-jwt-token-Or-API-Key" ``` ### Response ```json theme={null} [ { "name": "posts_pkey", "tableName": "posts", "columns": ["id"], "isUnique": true, "isPrimary": true, "definition": "CREATE UNIQUE INDEX posts_pkey ON public.posts USING btree (id)" } ] ``` *** ## List RLS Policies Get all Row Level Security policies. **Requires admin authentication.** ``` GET /api/database/policies ``` ### Example ```bash theme={null} curl "https://your-app.insforge.app/api/database/policies" \ -H "Authorization: Bearer admin-jwt-token-Or-API-Key" ``` ### Response ```json theme={null} [ { "name": "Users can view own posts", "tableName": "posts", "command": "SELECT", "roles": ["authenticated"], "using": "(auth.uid() = user_id)", "withCheck": null } ] ``` *** ## List Database Triggers Get all database triggers. **Requires admin authentication.** ``` GET /api/database/triggers ``` ### Example ```bash theme={null} curl "https://your-app.insforge.app/api/database/triggers" \ -H "Authorization: Bearer admin-jwt-token-Or-API-Key" ``` ### Response ```json theme={null} [ { "name": "update_timestamp", "tableName": "posts", "timing": "BEFORE", "events": ["UPDATE"], "functionName": "update_updated_at_column", "enabled": true } ] ``` *** ## List Database Migrations Get the history of successful custom migrations. InsForge stores these in `system.custom_migrations`, but the endpoint returns only the migration metadata and executed statements. **Requires admin authentication.** ``` GET /api/database/migrations ``` ### Example ```bash theme={null} curl "https://your-app.insforge.app/api/database/migrations" \ -H "Authorization: Bearer admin-jwt-token-Or-API-Key" ``` ### Response ```json theme={null} { "migrations": [ { "version": "20260416170500", "name": "create-posts-table", "statements": [ "CREATE TABLE posts (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title TEXT NOT NULL);" ], "createdAt": "2026-04-16T17:05:00.000Z" } ] } ``` *** ## Create Database Migration Create and immediately execute a custom migration against the database. The migration is recorded only if every statement succeeds. **Requires admin authentication.** ``` POST /api/database/migrations ``` ### Request Body | Field | Type | Required | Description | | --------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `version` | string | Yes | Numeric migration version (up to 64 digits). Accepts Drizzle-style sequential prefixes (e.g. `0001`) or a `YYYYMMDDHHmmss` timestamp. Versions are compared numerically. The InsForge CLI generates 14-digit UTC timestamps for local migration filenames. | | `name` | string | Yes | Migration name using lowercase letters, numbers, and hyphens only | | `sql` | string | Yes | SQL text to parse and execute | ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/database/migrations" \ -H "Authorization: Bearer admin-jwt-token-Or-API-Key" \ -H "Content-Type: application/json" \ -d '{ "version": "20260416170500", "name": "create-posts-table", "sql": "CREATE TABLE posts (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title TEXT NOT NULL);" }' ``` ### Response ```json theme={null} { "version": "20260416170500", "name": "create-posts-table", "statements": [ "CREATE TABLE posts (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title TEXT NOT NULL);" ], "createdAt": "2026-04-16T17:05:00.000Z", "message": "Migration executed successfully" } ``` Do not include `BEGIN`, `COMMIT`, or `ROLLBACK` inside a custom migration. InsForge executes the migration inside its own transaction. *** ## Execute Raw SQL (Strict Mode) Execute raw SQL query with strict sanitization. Blocks access to system tables and auth.users. **Requires admin authentication.** ``` POST /api/database/advance/rawsql ``` ### Request Body | Field | Type | Required | Description | | -------- | ------ | -------- | ------------------------------------------ | | `query` | string | Yes | SQL query to execute | | `params` | array | No | Query parameters for parameterized queries | ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/database/advance/rawsql" \ -H "Authorization: Bearer admin-jwt-token-Or-API-Key" \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT * FROM posts WHERE published = $1", "params": [true] }' ``` ### Response ```json theme={null} { "rows": [ { "id": "248373e1-0aea-45ce-8844-5ef259203749", "title": "My Post", "published": true } ], "rowCount": 1, "command": "SELECT" } ``` *** ## Execute Raw SQL (Relaxed Mode) Execute raw SQL query with relaxed sanitization. Allows SELECT and INSERT into system tables. **Use with caution. Requires admin authentication.** ``` POST /api/database/advance/rawsql/unrestricted ``` ### Request Body | Field | Type | Required | Description | | -------- | ------ | -------- | ------------------------------------------ | | `query` | string | Yes | SQL query to execute | | `params` | array | No | Query parameters for parameterized queries | ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/database/advance/rawsql/unrestricted" \ -H "Authorization: Bearer admin-jwt-token-Or-API-Key" \ -H "Content-Type: application/json" \ -d '{ "query": "SELECT * FROM auth.users LIMIT 10" }' ``` ### Response ```json theme={null} { "rows": [...], "rowCount": 10, "command": "SELECT" } ``` This endpoint has relaxed restrictions and can access system tables. Use only when necessary and ensure proper authorization. *** ## Export Database Export database schema and/or data in SQL or JSON format. **Requires admin authentication.** ``` POST /api/database/advance/export ``` ### Request Body | Field | Type | Required | Default | Description | | ------------------ | ------- | -------- | ------- | ------------------------------- | | `tables` | array | No | all | List of tables to export | | `format` | string | No | sql | Export format (`sql` or `json`) | | `includeData` | boolean | No | true | Include table data | | `includeFunctions` | boolean | No | false | Include database functions | | `includeSequences` | boolean | No | false | Include sequences | | `includeViews` | boolean | No | false | Include views | | `rowLimit` | integer | No | - | Maximum rows per table | ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/database/advance/export" \ -H "Authorization: Bearer admin-jwt-token-Or-API-Key" \ -H "Content-Type: application/json" \ -d '{ "tables": ["posts", "comments"], "format": "sql", "includeData": true, "rowLimit": 1000 }' ``` ### Response ```json theme={null} { "format": "sql", "content": "CREATE TABLE posts (...); INSERT INTO posts VALUES (...);", "tables": ["posts", "comments"] } ``` *** ## Import Database Import database from SQL file. **Requires admin authentication.** ``` POST /api/database/advance/import ``` ### Request (multipart/form-data) | Field | Type | Required | Default | Description | | ---------- | ------- | -------- | ------- | -------------------------------------- | | `file` | file | Yes | - | SQL file to import | | `truncate` | boolean | No | false | Truncate existing tables before import | ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/database/advance/import" \ -H "Authorization: Bearer admin-jwt-token-Or-API-Key" \ -F "file=@backup.sql" \ -F "truncate=false" ``` ### Response ```json theme={null} { "filename": "backup.sql", "fileSize": 102400, "tables": ["posts", "comments", "users"], "rowsImported": 1500 } ``` *** ## Bulk Upsert Bulk insert or update data from CSV or JSON file. **Requires admin authentication.** ``` POST /api/database/advance/bulk-upsert ``` ### Request (multipart/form-data) | Field | Type | Required | Description | | ----------- | ------ | -------- | ------------------------------------------ | | `file` | file | Yes | CSV or JSON file containing data | | `table` | string | Yes | Target table name | | `upsertKey` | string | No | Column name for upsert conflict resolution | ### Example ```bash theme={null} # Import from CSV curl -X POST "https://your-app.insforge.app/api/database/advance/bulk-upsert" \ -H "Authorization: Bearer admin-jwt-token-Or-API-Key" \ -F "file=@data.csv" \ -F "table=posts" \ -F "upsertKey=id" # Import from JSON curl -X POST "https://your-app.insforge.app/api/database/advance/bulk-upsert" \ -H "Authorization: Bearer admin-jwt-token-Or-API-Key" \ -F "file=@data.json" \ -F "table=posts" ``` ### Response ```json theme={null} { "rowsAffected": 150, "totalRecords": 150, "table": "posts" } ``` # Functions API Reference Source: https://docs.insforge.dev/sdks/rest/functions Serverless edge functions via REST API ## Overview The Functions API provides endpoints for invoking and managing serverless functions. In cloud environments, functions run on Deno Subhosting at the edge. In self-hosted (Docker) environments, functions run in a local Deno runtime. ## Headers ```bash theme={null} Content-Type: application/json ``` For authenticated function invocations: ```bash theme={null} Authorization: Bearer your-jwt-token-or-anon-key ``` For admin endpoints: ```bash theme={null} Authorization: Bearer admin-jwt-token-or-api-key ``` *** ## Invoke Function Execute a deployed function using any HTTP method (GET, POST, PUT, PATCH, DELETE, etc.). The server preserves and forwards the caller's original method to the function runtime. ``` ANY /functions/{slug} ``` Note: Function invocation uses `/functions/{slug}` (without `/api` prefix), not `/api/functions/{slug}`. ### Path Parameters | Parameter | Type | Description | | --------- | ------ | ------------------------ | | `slug` | string | Function slug identifier | ### Request Body Any JSON payload that the function expects. ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/functions/hello-world" \ -H "Content-Type: application/json" \ -d '{ "name": "John" }' ``` ### Response The response depends on what the function returns: ```json theme={null} { "message": "Hello, John!" } ``` *** ## Admin Endpoints These endpoints require admin authentication. ### List All Functions ``` GET /api/functions ``` ### Example ```bash theme={null} curl "https://your-app.insforge.app/api/functions" \ -H "Authorization: Bearer admin-jwt-token-or-api-key" ``` ### Response ```json theme={null} [ { "id": "123e4567-e89b-12d3-a456-426614174000", "slug": "hello-world", "name": "Hello World Function", "description": "Returns a greeting message", "status": "active", "created_at": "2024-01-21T10:30:00Z", "updated_at": "2024-01-21T10:35:00Z", "deployed_at": "2024-01-21T10:35:00Z" }, { "id": "223e4567-e89b-12d3-a456-426614174001", "slug": "process-webhook", "name": "Webhook Processor", "description": "Processes incoming webhooks", "status": "draft", "created_at": "2024-01-22T14:20:00Z", "updated_at": "2024-01-22T14:20:00Z", "deployed_at": null } ] ``` *** ### Get Function Details ``` GET /api/functions/{slug} ``` ### Example ```bash theme={null} curl "https://your-app.insforge.app/api/functions/hello-world" \ -H "Authorization: Bearer admin-jwt-token-or-api-key" ``` ### Response ```json theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "slug": "hello-world", "name": "Hello World Function", "description": "Returns a greeting message", "code": "export default async function(request) {\n const { name = 'World' } = await request.json();\n return new Response(\n JSON.stringify({ message: `Hello, ${name}!` }),\n { headers: { 'Content-Type': 'application/json' } }\n );\n}", "status": "active", "created_at": "2024-01-21T10:30:00Z", "updated_at": "2024-01-21T10:35:00Z", "deployed_at": "2024-01-21T10:35:00Z" } ``` *** ### Create Function Currently, InsForge only supports JavaScript/TypeScript functions running in a Deno environment. ``` POST /api/functions ``` ### Request Body | Field | Type | Required | Description | | ------------- | ------ | -------- | -------------------------------------------------------- | | `name` | string | Yes | Display name for the function | | `code` | string | Yes | JavaScript/TypeScript code | | `slug` | string | No | URL-friendly identifier (auto-generated if not provided) | | `description` | string | No | Description of the function | | `status` | string | No | `draft` or `active` (default: `active`) | ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/functions" \ -H "Authorization: Bearer admin-jwt-token-or-api-key" \ -H "Content-Type: application/json" \ -d '{ "name": "Hello World Function", "slug": "hello-world", "description": "Returns a personalized greeting", "code": "export default async function(request) {\n const { name = \"World\" } = await request.json();\n return new Response(\n JSON.stringify({ message: `Hello, ${name}!` }),\n { headers: { \"Content-Type\": \"application/json\" } }\n );\n}", "status": "active" }' ``` ### Response ```json theme={null} { "success": true, "function": { "id": "123e4567-e89b-12d3-a456-426614174000", "slug": "hello-world", "name": "Hello World Function", "description": "Returns a personalized greeting", "status": "active", "created_at": "2024-01-21T10:30:00Z" } } ``` *** ### Update Function ``` PUT /api/functions/{slug} ``` ### Request Body | Field | Type | Required | Description | | ------------- | ------ | -------- | ----------------------------- | | `name` | string | No | Updated display name | | `code` | string | No | Updated function code | | `description` | string | No | Updated description | | `status` | string | No | `draft`, `active`, or `error` | ### Example ```bash theme={null} curl -X PUT "https://your-app.insforge.app/api/functions/hello-world" \ -H "Authorization: Bearer admin-jwt-token-or-api-key" \ -H "Content-Type: application/json" \ -d '{ "name": "Hello World Function v2", "code": "export default async function(request) {\n const { name = \"World\" } = await request.json();\n return new Response(\n JSON.stringify({ message: `Hello, ${name}! Welcome to v2.`, version: 2 }),\n { headers: { \"Content-Type\": \"application/json\" } }\n );\n}" }' ``` ### Response ```json theme={null} { "success": true, "function": { "id": "123e4567-e89b-12d3-a456-426614174000", "slug": "hello-world", "name": "Hello World Function v2", "description": "Returns a personalized greeting", "status": "active", "updated_at": "2024-01-21T11:00:00Z" } } ``` *** ### Delete Function ``` DELETE /api/functions/{slug} ``` ### Example ```bash theme={null} curl -X DELETE "https://your-app.insforge.app/api/functions/old-function" \ -H "Authorization: Bearer admin-jwt-token-or-api-key" ``` ### Response ```json theme={null} { "success": true, "message": "Function old-function deleted successfully" } ``` *** ## Function Code Structure Functions must export a default async function that receives a `Request` object and returns a `Response`: ```javascript theme={null} export default async function(request) { // Parse request body const body = await request.json(); // Process request const result = { message: `Hello, ${body.name}!` }; // Return response return new Response( JSON.stringify(result), { headers: { 'Content-Type': 'application/json' }, status: 200 } ); } ``` ### Accessing Request Data ```javascript theme={null} export default async function(request) { // Get JSON body const body = await request.json(); // Get headers const authHeader = request.headers.get('Authorization'); // Get query parameters const url = new URL(request.url); const param = url.searchParams.get('param'); // Get request method const method = request.method; return new Response(JSON.stringify({ body, authHeader, param, method })); } ``` *** ## Function Status | Status | Description | | -------- | --------------------------------------- | | `draft` | Function is saved but not deployed | | `active` | Function is deployed and can be invoked | | `error` | Function has a deployment error | *** ## Error Responses ### Function Not Found (404) ```json theme={null} { "error": "Function not found" } ``` ### Function Not Active (404) ```json theme={null} { "error": "Function not found or not active" } ``` ### Execution Error (502) When the function runtime is unreachable (self-hosted: local Deno runtime down; cloud: subhosting proxy failure): ```json theme={null} { "error": "Failed to proxy function" } ``` ### Function Runtime Error (500) When the function code throws an error: ```json theme={null} { "error": "Function execution failed", "message": "TypeError: Cannot read property 'name' of undefined" } ``` ### Slug Already Exists (409) ```json theme={null} { "error": "Function with this slug already exists", "details": "duplicate key value violates unique constraint" } ``` ### Dangerous Code Detected (400) ```json theme={null} { "error": "Code contains potentially dangerous patterns", "pattern": "/Deno\\.run/i" } ``` # REST API Source: https://docs.insforge.dev/sdks/rest/overview Direct HTTP API access to InsForge services The InsForge REST API provides direct HTTP access to all InsForge services. Use this when you need to integrate with languages or platforms without an official SDK. ## Base URL ``` https://your-app.insforge.app ``` ## Authentication All API requests require authentication via the `Authorization` header: ### Bearer Token ```bash theme={null} Authorization: Bearer your-jwt-token-or-anon-key ``` ## Quick Start ```bash theme={null} # Register a new user curl -X POST https://your-app.insforge.app/api/auth/users \ -H "Content-Type: application/json" \ -d '{"email": "user@example.com", "password": "securepassword123"}' # Sign in and get token curl -X POST https://your-app.insforge.app/api/auth/sessions \ -H "Content-Type: application/json" \ -d '{"email": "user@example.com", "password": "securepassword123"}' # Use the token for authenticated requests curl https://your-app.insforge.app/api/database/records/posts \ -H "Authorization: Bearer your-jwt-token" ``` ## Response Format ### Success Response Data is returned directly in the response body: ```json theme={null} { "id": "123e4567-e89b-12d3-a456-426614174000", "email": "user@example.com", "createdAt": "2024-01-15T10:30:00Z" } ``` ### Error Response ```json theme={null} { "error": "ERROR_CODE", "message": "Human-readable error message", "statusCode": 400, "nextActions": "Suggested action to resolve the error" } ``` ## Common HTTP Status Codes | Status | Description | | ------ | --------------------------------------- | | `200` | Success | | `201` | Created | | `204` | No Content (for DELETE) | | `400` | Bad Request - Invalid input | | `401` | Unauthorized - Invalid or missing token | | `403` | Forbidden - Insufficient permissions | | `404` | Not Found | | `409` | Conflict - Resource already exists | | `500` | Internal Server Error | ## Features * **Database** - PostgREST-style CRUD operations * **Authentication** - Email/password and OAuth authentication * **Storage** - File upload, download, and management * **Functions** - Invoke serverless edge functions * **AI** - Chat completions and image generation * **Realtime** - Channel management and message history (WebSocket for real-time) ## API Reference CRUD operations, filters, and queries Sign up, sign in, OAuth, and user management File upload, download, and management Invoke serverless edge functions Chat completions and image generation Channel management and message history # Realtime API Reference Source: https://docs.insforge.dev/sdks/rest/realtime Manage realtime channels, inspect message history, and use raw Socket.IO pub/sub. ## Overview Realtime has two surfaces: * **REST API** for channel management, message history, delivery stats, permissions, and retention settings. * **Socket.IO** for live subscribe, publish, presence, and delivered events. The REST API does not stream messages. Use the Socket.IO connection, or the [TypeScript SDK](/sdks/typescript/realtime), for live events. ## Authentication Admin REST endpoints require a project-admin token or API key. ```bash theme={null} Authorization: Bearer Content-Type: application/json ``` You can also pass API keys with: ```bash theme={null} X-API-Key: ``` Socket.IO connections authenticate through the Socket.IO `auth` object: ```javascript theme={null} const socket = io('https://your-app.insforge.app', { auth: { token: '' } }); ``` Server/admin clients may pass an API key: ```javascript theme={null} const socket = io('https://your-app.insforge.app', { auth: { apiKey: '' } }); ``` ## Socket.IO Pub/Sub Install the Socket.IO client when you are not using the InsForge SDK: ```bash theme={null} npm install socket.io-client ``` ### Connect And Subscribe ```javascript theme={null} import { io } from 'socket.io-client'; const socket = io('https://your-app.insforge.app', { auth: { token: '' } }); socket.emit('realtime:subscribe', { channel: 'order:123' }, (response) => { if (response.ok) { console.log('Subscribed:', response.channel); console.log('Presence:', response.presence.members); } else { console.error(response.error.code, response.error.message); } }); ``` ### Listen For Events ```javascript theme={null} socket.on('status_changed', (message) => { console.log(message.status); console.log(message.meta.messageId); console.log(message.meta.senderType); }); socket.on('presence:join', (message) => { console.log('Joined:', message.member); }); socket.on('presence:leave', (message) => { console.log('Left:', message.member); }); socket.on('realtime:error', (error) => { console.error(error.channel, error.code, error.message); }); ``` ### Publish ```javascript theme={null} socket.emit('realtime:publish', { channel: 'order:123', event: 'customer_viewed', payload: { viewedAt: new Date().toISOString() } }); ``` The socket must successfully subscribe to a channel before it can publish to that channel. ### Unsubscribe ```javascript theme={null} socket.emit('realtime:unsubscribe', { channel: 'order:123' }); socket.disconnect(); ``` ### Socket Events | Event | Direction | Description | | ---------------------- | ---------------- | ------------------------------------------------------------------------------------------ | | `realtime:subscribe` | Client to server | Subscribe to a channel. The acknowledgement returns success/error and a presence snapshot. | | `realtime:unsubscribe` | Client to server | Leave a channel. | | `realtime:publish` | Client to server | Insert a user message into the realtime pipeline. | | Custom event name | Server to client | Delivered message, emitted under the message `eventName`. | | `presence:join` | Server to client | A logical member became present in the channel. | | `presence:leave` | Server to client | A logical member is no longer present in the channel. | | `realtime:error` | Server to client | Subscribe or publish failed. | ## Channel Patterns Create channel definitions before clients subscribe. | Pattern | Matches | | ----------------- | ------------------------ | | `orders` | `orders` | | `order:%` | `order:123`, `order:456` | | `chat:%:messages` | `chat:room-1:messages` | Pattern matching uses SQL `LIKE`, so `%` is the wildcard. `_` is not accepted in channel patterns because it is also a SQL wildcard. ## Channels ### List Channels ```http theme={null} GET /api/realtime/channels ``` ```bash theme={null} curl "https://your-app.insforge.app/api/realtime/channels" \ -H "Authorization: Bearer " ``` Response: ```json theme={null} [ { "id": "550e8400-e29b-41d4-a716-446655440000", "pattern": "order:%", "description": "Order updates", "webhookUrls": ["https://example.com/webhook"], "enabled": true, "createdAt": "2026-04-25T17:00:00.000Z", "updatedAt": "2026-04-25T17:00:00.000Z" } ] ``` ### Create Channel ```http theme={null} POST /api/realtime/channels ``` Request body: | Field | Type | Required | Description | | ------------- | ---------- | -------- | ----------------------------------------------------------------------- | | `pattern` | `string` | Yes | Channel pattern. | | `description` | `string` | No | Human-readable description. | | `webhookUrls` | `string[]` | No | Webhook URLs for every delivered message. | | `enabled` | `boolean` | No | Defaults to `true`. Disabled channels cannot be joined or delivered to. | ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/realtime/channels" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "pattern": "chat:%", "description": "Chat rooms", "webhookUrls": ["https://example.com/realtime-webhook"], "enabled": true }' ``` ### Get Channel ```http theme={null} GET /api/realtime/channels/{id} ``` ### Update Channel ```http theme={null} PUT /api/realtime/channels/{id} ``` ```bash theme={null} curl -X PUT "https://your-app.insforge.app/api/realtime/channels/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "description": "Active order updates", "enabled": true }' ``` ### Delete Channel ```http theme={null} DELETE /api/realtime/channels/{id} ``` ```json theme={null} { "message": "Channel deleted" } ``` Message history is preserved. Deleted channels set existing message `channelId` values to `null`. ## Messages ### List Messages ```http theme={null} GET /api/realtime/messages ``` Query parameters: | Parameter | Type | Description | | ----------- | --------- | --------------------------- | | `channelId` | `uuid` | Filter by channel ID. | | `eventName` | `string` | Filter by event name. | | `limit` | `integer` | 1 to 1000. Defaults to 100. | | `offset` | `integer` | Defaults to 0. | ```bash theme={null} curl "https://your-app.insforge.app/api/realtime/messages?eventName=status_changed&limit=50" \ -H "Authorization: Bearer " ``` Response: ```json theme={null} [ { "id": "660e8400-e29b-41d4-a716-446655440000", "eventName": "status_changed", "channelId": "550e8400-e29b-41d4-a716-446655440000", "channelName": "order:123", "payload": { "id": "123", "status": "shipped" }, "senderType": "system", "senderId": null, "wsAudienceCount": 5, "whAudienceCount": 1, "whDeliveredCount": 1, "createdAt": "2026-04-25T17:00:00.000Z" } ] ``` ### Message Stats ```http theme={null} GET /api/realtime/messages/stats ``` Query parameters: | Parameter | Type | Description | | ----------- | ----------- | --------------------------------------------------- | | `channelId` | `uuid` | Filter stats by channel ID. | | `since` | `date-time` | Include only messages created after this timestamp. | ```json theme={null} { "totalMessages": 1250, "whDeliveryRate": 0.98, "topEvents": [ { "eventName": "status_changed", "count": 450 } ], "retentionDays": null } ``` `retentionDays: null` means messages are retained indefinitely. ## Permissions ```http theme={null} GET /api/realtime/permissions ``` Returns user-defined RLS policies for: * Subscribe checks on `realtime.channels`. * Publish checks on `realtime.messages`. ```json theme={null} { "subscribe": { "policies": [ { "policyName": "users_subscribe_own_orders", "tableName": "channels", "command": "SELECT", "roles": ["authenticated"], "using": "pattern = 'order:%'", "withCheck": null } ] }, "publish": { "policies": [] } } ``` ## Configuration ### Get Realtime Config ```http theme={null} GET /api/realtime/config ``` ```json theme={null} { "retentionDays": null } ``` ### Update Realtime Config ```http theme={null} PATCH /api/realtime/config ``` ```bash theme={null} curl -X PATCH "https://your-app.insforge.app/api/realtime/config" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "retentionDays": 90 }' ``` Use `null` to keep messages indefinitely. Positive integers retain messages for that many days. ## Webhooks When a channel has `webhookUrls`, each message delivered through that channel is posted to each URL. The request body is the original message payload. Headers identify the delivery: | Header | Value | | ----------------------- | --------------------- | | `X-InsForge-Event` | Event name | | `X-InsForge-Channel` | Resolved channel name | | `X-InsForge-Message-Id` | Message UUID | Delivery attempts are reflected in `whAudienceCount` and `whDeliveredCount` on message history. # Storage API Reference Source: https://docs.insforge.dev/sdks/rest/storage File storage and bucket management via REST API ## Overview The Storage API provides bucket-based file storage similar to S3. Upload, download, and manage files with support for both local and S3-compatible storage backends. ## Headers For authenticated function invocations: ```bash theme={null} Authorization: Bearer your-jwt-token-or-anon-key Content-Type: application/json ``` For admin endpoints: ```bash theme={null} Authorization: Bearer admin-jwt-token-Or-API-Key Content-Type: application/json ``` *** ## Upload Object with upload strategy InsForge supports two types of storage backends: 1. **Local Storage**: Files are stored on the local filesystem. Use for development or low-volume production. 2. **S3-Compatible**: Files are stored on S3-compatible object storage. Use for high-volume production. The steps to upload a file are: 1. Get upload strategy 2. Upload file 3. Confirm upload (S3 only) *** ### Step 1: Get Upload Strategy Get the optimal upload strategy (direct or presigned URL) based on storage backend. The upload strategy API returns the optimal upload method based on the storage backend: * **Local Storage**: Direct upload to InsForge API * **S3-Compatible**: Presigned URL for direct upload to S3 ``` POST /api/storage/buckets/{bucketName}/upload-strategy ``` #### Request Body | Field | Type | Required | Description | | ------------- | ------- | -------- | --------------------- | | `filename` | string | Yes | Original filename | | `contentType` | string | No | MIME type of the file | | `size` | integer | No | File size in bytes | #### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/storage/buckets/avatars/upload-strategy" \ -H "Authorization: Bearer your-jwt-token" \ -H "Content-Type: application/json" \ -d '{ "filename": "profile-photo.jpg", "contentType": "image/jpeg", "size": 102400 }' ``` #### Response (S3 Backend) ```json theme={null} { "method": "presigned", "uploadUrl": "https://s3-bucket.amazonaws.com/", "fields": { "bucket": "my-s3-bucket", "key": "app-key/avatars/profile-photo-1234567890-abc123.jpg", "X-Amz-Algorithm": "AWS4-HMAC-SHA256", "X-Amz-Credential": "...", "Policy": "...", "X-Amz-Signature": "..." }, "key": "profile-photo-1234567890-abc123.jpg", "confirmRequired": true, "confirmUrl": "/api/storage/buckets/avatars/objects/profile-photo-1234567890-abc123.jpg/confirm-upload", "expiresAt": "2025-09-05T01:00:00Z" } ``` #### Response (Local Storage) ```json theme={null} { "method": "direct", "uploadUrl": "/api/storage/buckets/avatars/objects/profile-photo-1234567890-abc123.jpg", "key": "profile-photo-1234567890-abc123.jpg", "confirmRequired": false } ``` *** ### Step 2: Upload File Upload the file to the specified URL using the provided method. * **Local Storage**: Use a PUT request to `uploadUrl` with `multipart/form-data` and a `file` field. * **S3-Compatible**: Use a POST request to `uploadUrl` with `multipart/form-data` and a `file` field. Include all fields from the `fields` object in the request. ### Step 3: Confirm Presigned Upload (S3 Only) Confirm that a file was successfully uploaded to S3 using presigned URL. ``` POST /api/storage/buckets/{bucketName}/objects/{objectKey}/confirm-upload ``` #### Request Body | Field | Type | Required | Description | | ------------- | ------- | -------- | ------------------------------ | | `size` | integer | Yes | File size in bytes | | `contentType` | string | No | MIME type of the file | | `etag` | string | No | S3 ETag of the uploaded object | #### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/storage/buckets/avatars/objects/profile-photo-123.jpg/confirm-upload" \ -H "Authorization: Bearer your-jwt-token" \ -H "Content-Type: application/json" \ -d '{ "size": 102400, "contentType": "image/jpeg" }' ``` #### Response ```json theme={null} { "bucket": "avatars", "key": "profile-photo-123.jpg", "size": 102400, "mimeType": "image/jpeg", "uploadedAt": "2024-01-21T10:30:00Z", "url": "/api/storage/buckets/avatars/objects/profile-photo-123.jpg" } ``` *** ## Upload Object (Deprecated) Upload a file to a bucket with a specific key. ``` PUT /api/storage/buckets/{bucketName}/objects/{objectKey} ``` ### Path Parameters | Parameter | Type | Description | | ------------ | ------ | ----------------------------------------------- | | `bucketName` | string | Name of the bucket | | `objectKey` | string | Object key (can include `/` for pseudo-folders) | ### Request Body `multipart/form-data` with a `file` field. ### Example ```bash theme={null} curl -X PUT "https://your-app.insforge.app/api/storage/buckets/avatars/objects/users/profile.jpg" \ -H "Authorization: Bearer your-jwt-token" \ -F "file=@/path/to/image.jpg" ``` ### Response ```json theme={null} { "bucket": "avatars", "key": "users/profile.jpg", "size": 102400, "mimeType": "image/jpeg", "uploadedAt": "2024-01-15T10:30:00Z", "url": "/api/storage/buckets/avatars/objects/users/profile.jpg" } ``` *** ## Upload with Auto-Generated Key (Deprecated) Upload a file with an auto-generated unique key. ``` POST /api/storage/buckets/{bucketName}/objects ``` ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/storage/buckets/uploads/objects" \ -H "Authorization: Bearer your-jwt-token" \ -F "file=@/path/to/document.pdf" ``` ### Response ```json theme={null} { "bucket": "uploads", "key": "document-1737546841234-a3f2b1.pdf", "size": 204800, "mimeType": "application/pdf", "uploadedAt": "2024-01-21T10:30:00Z", "url": "/api/storage/buckets/uploads/objects/document-1737546841234-a3f2b1.pdf" } ``` *** ## Download Object with download strategy InsForge supports two types of storage backends: 1. **Local Storage**: Files are stored on the local filesystem. Use for development or low-volume production. 2. **S3-Compatible**: Files are stored on S3-compatible object storage. Use for high-volume production. The steps to download a file are: 1. Get download strategy 2. Download file from returned URL ### Step 1: Get Download Strategy Get the optimal download strategy (direct URL or presigned URL) based on storage backend and bucket visibility. The download strategy API returns the optimal download method based on the storage backend and bucket visibility: * **Local Storage**: Direct download from InsForge API * **S3-Compatible**: Presigned URL for S3. ``` GET /api/storage/buckets/{bucketName}/download-strategy/objects/{objectKey} ``` Expiry (for presigned URLs) is auto-calculated server-side from bucket visibility. The endpoint takes no request body. `objectKey` may contain `/` (pseudo-folders) — do not percent-encode the separators. `POST /api/storage/buckets/{bucketName}/objects/{objectKey}/download-strategy` is retained as a deprecated alias at the original path for older SDK releases and will be removed in a future major version. Migrate to `GET`. #### Example ```bash theme={null} curl "https://your-app.insforge.app/api/storage/buckets/avatars/download-strategy/objects/profile.jpg" \ -H "Authorization: Bearer your-jwt-token" ``` #### Response (S3 Public Bucket) ```json theme={null} { "method": "direct", "url": "https://s3-bucket.s3.us-east-2.amazonaws.com/app-key/avatars/profile.jpg" } ``` #### Response (S3 Private Bucket) ```json theme={null} { "method": "presigned", "url": "https://s3-bucket.s3.us-east-2.amazonaws.com/app-key/avatars/profile.jpg?X-Amz-Algorithm=...", "expiresAt": "2025-09-05T01:00:00Z" } ``` ### Step 2: Download File Download the file from the returned URL, using the appropriate method. *** ## Download Object (Deprecated) Download a file from a bucket. ``` GET /api/storage/buckets/{bucketName}/objects/{objectKey} ``` ### Example ```bash theme={null} curl "https://your-app.insforge.app/api/storage/buckets/avatars/objects/users/profile.jpg" \ -H "Authorization: Bearer your-jwt-token" \ -o profile.jpg ``` ### Response Binary file content with appropriate `Content-Type` and `Content-Length` headers. *** ## Delete Object Delete a file from a bucket. ``` DELETE /api/storage/buckets/{bucketName}/objects/{objectKey} ``` ### Example ```bash theme={null} curl -X DELETE "https://your-app.insforge.app/api/storage/buckets/avatars/objects/users/profile.jpg" \ -H "Authorization: Bearer your-jwt-token" ``` ### Response ```json theme={null} { "message": "Object deleted successfully" } ``` *** ## List Objects in Bucket List all objects in a bucket with optional filtering. ``` GET /api/storage/buckets/{bucketName}/objects ``` ### Query Parameters | Parameter | Type | Description | | --------- | ------- | ------------------------------------------------ | | `prefix` | string | Filter by key prefix (e.g., `users/`) | | `search` | string | Search objects by key (partial match) | | `limit` | integer | Maximum objects to return (1-1000, default: 100) | | `offset` | integer | Number of objects to skip | ### Example ```bash theme={null} # Filter by prefix curl "https://your-app.insforge.app/api/storage/buckets/avatars/objects?prefix=users/&limit=50" \ -H "Authorization: Bearer your-jwt-token" # Search by key curl "https://your-app.insforge.app/api/storage/buckets/avatars/objects?search=profile" \ -H "Authorization: Bearer your-jwt-token" ``` ### Response ```json theme={null} { "data": [ { "bucket": "avatars", "key": "users/user123.jpg", "size": 102400, "mimeType": "image/jpeg", "uploadedAt": "2024-01-15T10:30:00Z", "url": "/api/storage/buckets/avatars/objects/users/user123.jpg" }, { "bucket": "avatars", "key": "users/user456.png", "size": 204800, "mimeType": "image/png", "uploadedAt": "2024-01-16T11:00:00Z", "url": "/api/storage/buckets/avatars/objects/users/user456.png" } ], "pagination": { "offset": 0, "limit": 100, "total": 2 } } ``` *** ## Bucket Management (Admin) ### List All Buckets ``` GET /api/storage/buckets ``` ### Example ```bash theme={null} curl "https://your-app.insforge.app/api/storage/buckets" \ -H "Authorization: Bearer admin-jwt-token" ``` ### Response ```json theme={null} { "buckets": ["avatars", "documents", "uploads", "public"] } ``` ### Create Bucket ``` POST /api/storage/buckets ``` ### Request Body | Field | Type | Required | Description | | ------------ | ------- | -------- | ----------------------------------------------------- | | `bucketName` | string | Yes | Bucket name (alphanumeric, underscore, hyphen) | | `isPublic` | boolean | No | Whether bucket is publicly accessible (default: true) | ### Example ```bash theme={null} curl -X POST "https://your-app.insforge.app/api/storage/buckets" \ -H "Authorization: Bearer admin-jwt-token" \ -H "Content-Type: application/json" \ -d '{ "bucketName": "user-uploads", "isPublic": false }' ``` ### Response ```json theme={null} { "message": "Bucket created successfully", "bucket": "user-uploads" } ``` ### Update Bucket ``` PATCH /api/storage/buckets/{bucketName} ``` ### Request Body | Field | Type | Required | Description | | ---------- | ------- | -------- | ------------------------------------- | | `isPublic` | boolean | Yes | Whether bucket is publicly accessible | ### Example ```bash theme={null} curl -X PATCH "https://your-app.insforge.app/api/storage/buckets/user-uploads" \ -H "Authorization: Bearer admin-jwt-token" \ -H "Content-Type: application/json" \ -d '{"isPublic": true}' ``` ### Response ```json theme={null} { "message": "Bucket visibility updated", "bucket": "user-uploads", "isPublic": true } ``` ### Delete Bucket ``` DELETE /api/storage/buckets/{bucketName} ``` ### Example ```bash theme={null} curl -X DELETE "https://your-app.insforge.app/api/storage/buckets/old-uploads" \ -H "Authorization: Bearer admin-jwt-token" ``` ### Response ```json theme={null} { "message": "Bucket deleted successfully" } ``` *** ## Error Responses ### Bucket Not Found (404) ```json theme={null} { "error": "BUCKET_NOT_FOUND", "message": "Bucket 'nonexistent' does not exist", "statusCode": 404, "nextActions": "Create the bucket first" } ``` ### Object Not Found (404) ```json theme={null} { "error": "OBJECT_NOT_FOUND", "message": "Object 'missing.jpg' not found in bucket 'avatars'", "statusCode": 404, "nextActions": "Check the bucket and key combination" } ``` ### Invalid File (400) ```json theme={null} { "error": "INVALID_FILE", "message": "No file provided in the request", "statusCode": 400, "nextActions": "Include a file in the multipart form data" } ``` ### Bucket Already Exists (409) ```json theme={null} { "error": "BUCKET_EXISTS", "message": "Bucket 'avatars' already exists", "statusCode": 409, "nextActions": "Choose a different bucket name" } ``` # Model Gateway Source: https://docs.insforge.dev/sdks/swift/ai Build AI features from Swift with the OpenRouter key provisioned by InsForge ## Overview InsForge provisions an OpenRouter API key for Model Gateway projects. New Swift applications should call OpenRouter directly from trusted server-side code, a backend API, or another secure boundary. Do not embed the OpenRouter key in an iOS, macOS, tvOS, or watchOS app binary. The previous InsForge Swift AI SDK methods are deprecated compatibility wrappers. Use the InsForge SDK for database, auth, storage, functions, and realtime; use OpenRouter for model calls. ## Recommended Architecture ```mermaid theme={null} sequenceDiagram participant App as Swift App participant API as Your Backend participant OR as OpenRouter App->>API: User prompt or app request API->>OR: OpenRouter request with server-side key OR-->>API: Model response API-->>App: App-specific response ``` ## Server-Side OpenRouter Call Use the OpenAI SDK or REST from your backend. For TypeScript backends: ```typescript theme={null} import OpenAI from 'openai'; const openai = new OpenAI({ baseURL: 'https://openrouter.ai/api/v1', apiKey: process.env.OPENROUTER_API_KEY, }); const completion = await openai.chat.completions.create({ model: 'openai/gpt-4o-mini', messages: [{ role: 'user', content: 'Summarize this note.' }], }); ``` ## Calling Your Backend from Swift ```swift theme={null} struct ChatRequest: Encodable { let prompt: String } struct ChatResponse: Decodable { let text: String } func sendPrompt(_ prompt: String, sessionToken: String) async throws -> ChatResponse { let url = URL(string: "https://your-app.example/api/chat")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("Bearer \(sessionToken)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try JSONEncoder().encode(ChatRequest(prompt: prompt)) let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse, (200..<300).contains(httpResponse.statusCode) else { throw URLError(.badServerResponse) } return try JSONDecoder().decode(ChatResponse.self, from: data) } ``` Use an app session token or another user-scoped credential for your backend route. Never send the OpenRouter key from a Swift client. ## Legacy InsForge AI Methods These Swift SDK methods are deprecated for new AI integrations: * `insforge.ai.chatCompletion(...)` * `insforge.ai.generateEmbeddings(...)` * `insforge.ai.generateImage(...)` * `insforge.ai.listModels()` They target the deprecated InsForge AI proxy. New integrations should use the OpenRouter key from the dashboard and follow OpenRouter's current API docs for model parameters and capabilities. # Authentication SDK Reference Source: https://docs.insforge.dev/sdks/swift/auth User authentication and profile management with the InsForge Swift SDK ## Installation Add InsForge to your Swift Package Manager dependencies: ```swift theme={null} dependencies: [ .package(url: "https://github.com/insforge/insforge-swift.git", from: "0.0.9") ] ``` ```swift theme={null} import InsForge let insforge = InsForgeClient( baseURL: URL(string: "https://your-app.insforge.app")!, anonKey: "your-anon-key" ) ``` ### Enable Logging (Optional) For debugging, you can configure the SDK log level and destination: ```swift theme={null} let options = InsForgeClientOptions( global: .init( logLevel: .debug, logDestination: .osLog, logSubsystem: "com.example.MyApp" ) ) let insforge = InsForgeClient( baseURL: URL(string: "https://your-app.insforge.app")!, anonKey: "your-anon-key", options: options ) ``` **Log Levels:** | Level | Description | | ----------- | ------------------------------------------- | | `.trace` | Most verbose, includes all internal details | | `.debug` | Detailed information for debugging | | `.info` | General operational information (default) | | `.warning` | Warnings that don't prevent operation | | `.error` | Errors that affect functionality | | `.critical` | Critical failures | **Log Destinations:** | Destination | Description | | ----------- | ---------------------------------------------------------- | | `.console` | Standard output (print) | | `.osLog` | Apple's unified logging system (recommended for iOS/macOS) | | `.none` | Disable logging | | `.custom` | Provide your own LogHandler factory | Use `.info` or `.error` in production to avoid exposing sensitive data in logs. ## signUp() Create a new user account with email and password. ### Parameters * `email` (String) - User's email address * `password` (String) - User's password * `name` (String, optional) - User's display name ### Returns ```swift theme={null} SignUpResponse ``` ### SignUpResponse ```swift theme={null} public struct SignUpResponse: Codable, Sendable { /// User object (nil when email verification is required) public let user: User? /// Access token (nil when email verification is required) public let accessToken: String? /// Refresh token (nil when email verification is required) public let refreshToken: String? /// Indicates if email verification is required before sign-in public let requireEmailVerification: Bool? /// Check if email verification is required public var needsEmailVerification: Bool /// Check if sign up completed with session (no verification required) public var hasSession: Bool } ``` ### Example (Complete Flow with Verification) ```swift theme={null} func handleSignUp(email: String, password: String, name: String?) async { do { let result = try await insforge.auth.signUp( email: email, password: password, name: name ) if result.needsEmailVerification { // Show verification code input screen // User will receive a 6-digit code via email showEmailVerificationScreen(email: email) } else if result.hasSession { // Registration complete, user is signed in navigateToDashboard() } } catch { showError(error.localizedDescription) } } // Called when user enters the verification code func handleVerifyEmail(email: String, code: String) async { do { try await insforge.auth.verifyEmail(email: email, code: code) // Verification successful, user can now sign in navigateToSignIn() } catch { showError("Invalid verification code") } } // Called when user requests a new verification email func handleResendCode(email: String) async { do { try await insforge.auth.resendVerificationEmail(email: email) showMessage("Verification email sent to \(email)") } catch { showError("Failed to resend verification email") } } ``` ### Email Verification For users who register with email, the InsForge backend provides three options: 1. **No email verification** - Users can sign in immediately after registration. `SignUpResponse` will have `hasSession = true`. 2. **Link-based verification** - Users must open their email and click the verification link before they can sign in. 3. **Code-based verification** - The InsForge backend sends a 6-digit verification code to the user's email. The client app needs to display a verification screen where users can enter the code, then call `verifyEmail(email:code:)` to complete verification. Only after this can users sign in with email + password. When `requireEmailVerification` is `true`, the response will have: * `accessToken = nil` * `user = nil` * `needsEmailVerification = true` This indicates that verification via option 2 or 3 is required before the user can sign in. ### Related Methods | Method | Description | | --------------------------------- | ------------------------------ | | `verifyEmail(email:code:)` | Verify email with 6-digit code | | `resendVerificationEmail(email:)` | Resend verification email | *** ## signIn() Sign in an existing user with email and password. ### Parameters * `email` (String) - User's email address * `password` (String) - User's password ### Example ```swift theme={null} do { let result = try await insforge.auth.signIn( email: "user@example.com", password: "secure_password123" ) if let user = result.user { print("Welcome back, \(user.profile?.name ?? user.email)") } } catch { print("Sign in failed: \(error)") } ``` ### Email Verification If the sign in response is: ```json theme={null} {"error":"FORBIDDEN","message":"Email verification required","statusCode":403,"nextActions":"Please verify your email address before logging in"} ``` This indicates that verification via option 2 or 3 (link or code, see [signUp()](#email-verification)) is required before the user can sign in. *** ## signOut() Sign out the current user. ### Example ```swift theme={null} do { try await insforge.auth.signOut() print("User signed out") } catch { print("Sign out failed: \(error)") } ``` *** ## getCurrentUser() Get authenticated user with profile data. ### Example ```swift theme={null} do { if let user = try await insforge.auth.getCurrentUser() { print("Email: \(user.email)") print("Name: \(user.profile?.name ?? "N/A")") } else { print("No user signed in") } } catch { print("Error: \(error)") } ``` *** ## setProfile() Update current user's profile. ### Example ```swift theme={null} do { let result = try await insforge.auth.setProfile([ "name": "JohnDev", "bio": "iOS Developer", "avatar_url": "https://example.com/avatar.jpg" ]) print("Profile updated") } catch { print("Update failed: \(error)") } ``` *** ## Password Reset InsForge supports two password reset methods, configured in the backend: * **Code method**: User receives a 6-digit code via email, verifies it to get a reset token, then resets password * **Link method**: User receives a magic link via email containing the reset token, then resets password directly ### sendPasswordReset() Send a password reset email to the user. The email will contain either a 6-digit code or a magic link depending on the backend configuration. #### Parameters * `email` (String) - User's email address #### Example ```swift theme={null} do { try await insforge.auth.sendPasswordReset(email: "user@example.com") // Show message to check email showMessage("If your email is registered, you will receive a password reset email.") } catch { print("Failed to send reset email: \(error)") } ``` *** ### exchangeResetPasswordToken() Exchange a 6-digit reset code for a reset token. **This method is only used with the code-based reset flow.** #### Parameters * `email` (String) - User's email address * `code` (String) - 6-digit numeric code received via email #### Returns ```swift theme={null} ResetPasswordTokenResponse ``` #### ResetPasswordTokenResponse ```swift theme={null} public struct ResetPasswordTokenResponse: Codable, Sendable { /// Reset token to use with resetPassword() public let token: String /// Token expiration timestamp public let expiresAt: Date? } ``` #### Example ```swift theme={null} do { let response = try await insforge.auth.exchangeResetPasswordToken( email: "user@example.com", code: "123456" ) // Store the token and proceed to password reset screen let resetToken = response.token showPasswordResetScreen(token: resetToken) } catch { print("Invalid or expired code: \(error)") } ``` *** ### resetPassword() Reset the user's password using a reset token. #### Parameters * `otp` (String) - Reset token (from `exchangeResetPasswordToken()` for code flow, or from magic link URL for link flow) * `newPassword` (String) - New password meeting the configured requirements #### Example ```swift theme={null} do { try await insforge.auth.resetPassword( otp: resetToken, newPassword: "newSecurePassword123" ) // Password reset successful showMessage("Password reset successfully. You can now sign in.") navigateToSignIn() } catch { print("Password reset failed: \(error)") } ``` *** ## signInWithOAuthView() Sign in directly with a specific OAuth provider. This method opens the OAuth provider's authentication page directly. On iOS 12+ and macOS 10.15+, the SDK uses `ASWebAuthenticationSession` to present an in-app authentication browser with automatic callback handling. ### Supported Providers ```swift theme={null} public enum OAuthProvider: String, Sendable { case google case github case discord case linkedin case facebook case instagram case tiktok case apple case x case spotify case microsoft } ``` ### Parameters * `provider` (`OAuthProvider`) - The OAuth provider to authenticate with * `redirectTo` (`String`) - Callback URL where InsForge will redirect after authentication ### Returns ```swift theme={null} AuthResponse ``` Returns `AuthResponse` containing the authenticated user and session tokens. ### Authentication Flow ``` 1. App calls signInWithOAuthView(provider:redirectTo:) 2. SDK fetches the OAuth authorization URL from InsForge 3. SDK opens in-app authentication browser (ASWebAuthenticationSession) 4. User authenticates with the provider (Google, GitHub, etc.) 5. SDK automatically handles callback and creates session 6. Method returns AuthResponse with user data ``` ### Example ```swift theme={null} import SwiftUI import InsForge struct LoginView: View { let client: InsForgeClient @State private var currentUser: User? @State private var errorMessage: String? var body: some View { VStack(spacing: 16) { Text("Sign in with") .font(.headline) // Google Sign In Button { Task { do { let response = try await client.auth.signInWithOAuthView( provider: .google, redirectTo: "yourapp://auth/callback" ) currentUser = response.user } catch { errorMessage = error.localizedDescription } } } label: { HStack { Image(systemName: "g.circle.fill") Text("Continue with Google") } .frame(maxWidth: .infinity) } .buttonStyle(.bordered) if let error = errorMessage { Text(error) .foregroundColor(.red) .font(.caption) } } .padding() } } ``` ### URL Scheme Configuration Register a callback URL for your app, pass that exact value to `redirectTo`, and add the same value to `allowedRedirectUrls` in Auth Settings. For example, you can use a deep link such as `yourapp://auth/callback` or a claimed HTTPS callback such as `https://app.example.com/auth/callback`. Requests fail with HTTP 400 if `redirectTo` is not allowlisted. *** ## Error Handling ```swift theme={null} do { let result = try await insforge.auth.signIn( email: email, password: password ) } catch let error as InsForgeAuthError { switch error { case .invalidCredentials: print("Invalid email or password") case .userNotFound: print("User not found") case .emailNotVerified: print("Please verify your email") case .networkError(let underlying): print("Network error: \(underlying)") default: print("Auth error: \(error.localizedDescription)") } } ``` # Database SDK Reference Source: https://docs.insforge.dev/sdks/swift/database Type-safe database operations using the InsForge Swift SDK ## Installation Add InsForge to your Swift Package Manager dependencies: ```swift theme={null} dependencies: [ .package(url: "https://github.com/insforge/insforge-swift.git", from: "0.0.9") ] ``` ```swift theme={null} import InsForge let insforge = InsForgeClient( baseURL: URL(string: "https://your-app.insforge.app")!, anonKey: "your-anon-key" ) ``` ### Enable Logging (Optional) For debugging, you can configure the SDK log level and destination: ```swift theme={null} let options = InsForgeClientOptions( global: .init( logLevel: .debug, logDestination: .osLog, logSubsystem: "com.example.MyApp" ) ) let insforge = InsForgeClient( baseURL: URL(string: "https://your-app.insforge.app")!, anonKey: "your-anon-key", options: options ) ``` **Log Levels:** | Level | Description | | ----------- | ------------------------------------------- | | `.trace` | Most verbose, includes all internal details | | `.debug` | Detailed information for debugging | | `.info` | General operational information (default) | | `.warning` | Warnings that don't prevent operation | | `.error` | Errors that affect functionality | | `.critical` | Critical failures | **Log Destinations:** | Destination | Description | | ----------- | ---------------------------------------------------------- | | `.console` | Standard output (print) | | `.osLog` | Apple's unified logging system (recommended for iOS/macOS) | | `.none` | Disable logging | | `.custom` | Provide your own LogHandler factory | Use `.info` or `.error` in production to avoid exposing sensitive data in logs. ## Define Models Define your data models using Swift's `Codable` protocol: ```swift theme={null} struct Post: Codable { let id: String? let title: String let content: String let userId: String let createdAt: Date? } ``` *** ## insert() Insert new records into a table. ### Parameters * `values` - Single Codable object or array of objects to insert ### Examples ```swift theme={null} // Single insert let post = Post( id: nil, title: "Hello World", content: "My first post!", userId: currentUserId, createdAt: nil ) let inserted = try await insforge.database .from("posts") .insert(post) // Bulk insert let posts = [ Post(id: nil, title: "First Post", content: "Hello everyone!", userId: currentUserId, createdAt: nil), Post(id: nil, title: "Second Post", content: "Another update.", userId: currentUserId, createdAt: nil) ] let inserted = try await insforge.database .from("posts") .insert(posts) ``` *** ## update() Update existing records in a table. ### Examples ```swift theme={null} // Define update model struct PostUpdate: Codable { let title: String } // Update by ID let updated: [Post] = try await insforge.database .from("posts") .eq("id", value: postId) .update(PostUpdate(title: "Updated Title")) // Update multiple records struct TaskUpdate: Codable { let status: String } let updated: [Task] = try await insforge.database .from("tasks") .in("id", values: ["task-1", "task-2"]) .update(TaskUpdate(status: "completed")) ``` *** ## delete() Delete records from a table. ### Examples ```swift theme={null} // Delete by ID try await insforge.database .from("posts") .eq("id", value: postId) .delete() // Delete with filter try await insforge.database .from("sessions") .lt("expires_at", value: Date()) .delete() ``` *** ## select() Query records from a table. ### Examples ```swift theme={null} // Get all posts let posts: [Post] = try await insforge.database .from("posts") .select() .execute() // Filter and sort let recentPosts: [Post] = try await insforge.database .from("posts") .select() .eq("userId", value: currentUserId) .order("createdAt", ascending: false) .limit(20) .execute() // Multiple filters let activePosts: [Post] = try await insforge.database .from("posts") .select() .eq("userId", value: currentUserId) .eq("status", value: "published") .execute() // Specific columns let posts = try await insforge.database .from("posts") .select("id, title, content") .execute() // With relationships let posts = try await insforge.database .from("posts") .select("*, comments(id, content)") .execute() ``` *** ## rpc() Call PostgreSQL stored functions (RPC - Remote Procedure Call). This method allows you to directly invoke SQL functions defined in your database. ### Parameters * `functionName` (String) - Name of the PostgreSQL function to call * `args` (\[String: Any]?, optional) - Arguments to pass to the function ### Examples ```swift theme={null} // Define response models struct UserStats: Decodable { let totalPosts: Int let totalComments: Int } struct User: Decodable { let id: String let name: String let email: String } // Call function with parameters, returning array let stats: [UserStats] = try await insforge.database .rpc("get_user_stats", args: ["user_id": "123"]) .execute() // Call function with parameters, returning single result let stat: UserStats = try await insforge.database .rpc("get_user_stats", args: ["user_id": "123"]) .executeSingle() // Call function without parameters let users: [User] = try await insforge.database .rpc("get_all_active_users") .execute() // Call function with multiple parameters let results: [Post] = try await insforge.database .rpc("search_posts", args: [ "search_term": "swift", "limit": 10, "offset": 0 ]) .execute() // Call function with no return value (void function) try await insforge.database .rpc("cleanup_old_records", args: ["days": 30]) .execute() // Call function returning a single primitive value let count: Int = try await insforge.database .rpc("count_active_posts") .executeSingle() ``` ### Implementation Details * **No arguments**: Uses GET request to `/api/database/rpc/{functionName}` * **With arguments**: Uses POST request with JSON body to `/api/database/rpc/{functionName}` * Use `execute()` for functions returning arrays or void * Use `executeSingle()` for functions returning a single object or value *** ## Filters | Filter | Description | Example | | -------------------------- | ----------------------------- | ---------------------------------------------- | | `.eq(column, value:)` | Equals | `.eq("status", value: "active")` | | `.neq(column, value:)` | Not equals | `.neq("status", value: "banned")` | | `.gt(column, value:)` | Greater than | `.gt("age", value: 18)` | | `.gte(column, value:)` | Greater than or equal | `.gte("price", value: 100)` | | `.lt(column, value:)` | Less than | `.lt("stock", value: 10)` | | `.lte(column, value:)` | Less than or equal | `.lte("priority", value: 3)` | | `.like(column, pattern:)` | Case-sensitive pattern | `.like("name", pattern: "%Widget%")` | | `.ilike(column, pattern:)` | Case-insensitive pattern | `.ilike("email", pattern: "%@gmail.com")` | | `.in(column, values:)` | Value in array | `.in("status", values: ["pending", "active"])` | | `.is(column, value:)` | Exactly equals (for nil/bool) | `.is("deleted_at", value: nil)` | ```swift theme={null} // Chain multiple filters let products: [Product] = try await insforge.database .from("products") .select() .eq("category", value: "electronics") .gte("price", value: 50) .lte("price", value: 500) .execute() // Pattern matching let posts: [Post] = try await insforge.database .from("posts") .select() .ilike("title", pattern: "%swift%") .execute() // Filter by multiple values let tasks: [Task] = try await insforge.database .from("tasks") .select() .in("status", values: ["pending", "in_progress"]) .execute() // Check for null or boolean let activePosts: [Post] = try await insforge.database .from("posts") .select() .is("deleted_at", value: nil) .is("published", value: true) .execute() ``` *** ## Modifiers | Modifier | Description | Example | | ---------------------------- | ---------------- | --------------------------------------- | | `.order(column, ascending:)` | Sort results | `.order("createdAt", ascending: false)` | | `.limit(count)` | Limit rows | `.limit(10)` | | `.offset(count)` | Skip rows | `.offset(20)` | | `.range(from:, to:)` | Range pagination | `.range(from: 0, to: 9)` | ```swift theme={null} // Pagination with sorting let posts: [Post] = try await insforge.database .from("posts") .select() .order("createdAt", ascending: false) .limit(10) .offset(20) .execute() // Range pagination (get items 10-19) let posts: [Post] = try await insforge.database .from("posts") .select() .range(from: 10, to: 19) .execute() ``` # Functions SDK Reference Source: https://docs.insforge.dev/sdks/swift/functions Invoke serverless functions with the InsForge Swift SDK ## Installation Add InsForge to your Swift Package Manager dependencies: ```swift theme={null} dependencies: [ .package(url: "https://github.com/insforge/insforge-swift.git", from: "0.0.9") ] ``` ```swift theme={null} import InsForge let insforge = InsForgeClient( baseURL: URL(string: "https://your-app.insforge.app")!, anonKey: "your-anon-key" ) ``` ### Enable Logging (Optional) For debugging, you can configure the SDK log level and destination: ```swift theme={null} let options = InsForgeClientOptions( global: .init( logLevel: .debug, logDestination: .osLog, logSubsystem: "com.example.MyApp" ) ) let insforge = InsForgeClient( baseURL: URL(string: "https://your-app.insforge.app")!, anonKey: "your-anon-key", options: options ) ``` **Log Levels:** | Level | Description | | ----------- | ------------------------------------------- | | `.trace` | Most verbose, includes all internal details | | `.debug` | Detailed information for debugging | | `.info` | General operational information (default) | | `.warning` | Warnings that don't prevent operation | | `.error` | Errors that affect functionality | | `.critical` | Critical failures | **Log Destinations:** | Destination | Description | | ----------- | ---------------------------------------------------------- | | `.console` | Standard output (print) | | `.osLog` | Apple's unified logging system (recommended for iOS/macOS) | | `.none` | Disable logging | | `.custom` | Provide your own LogHandler factory | Use `.info` or `.error` in production to avoid exposing sensitive data in logs. Currently, InsForge only supports JavaScript/TypeScript functions running in a Deno environment. *** ## invoke() Invoke a serverless function by slug. ### Parameters * `slug` (String) - Function slug/name * `body` (\[String: Any], optional) - Request body as dictionary ### Overloads The `invoke` method has three overloads: 1. **With dictionary body, returning typed response** ```swift theme={null} func invoke(_ slug: String, body: [String: Any]?) async throws -> T ``` 2. **With Encodable body, returning typed response** ```swift theme={null} func invoke(_ slug: String, body: I) async throws -> O ``` 3. **Without expecting response body** ```swift theme={null} func invoke(_ slug: String, body: [String: Any]?) async throws ``` SDK automatically includes authentication token from logged-in user. *** ## Examples ### Example: Basic Invocation with Typed Response ```swift theme={null} // Define response model struct HelloResponse: Codable { let message: String let timestamp: String } // Invoke function with dictionary body let response: HelloResponse = try await insforge.functions.invoke( "hello-world", body: ["name": "World", "greeting": "Hello"] ) print(response.message) // "Hello, World!" ``` ### Example: With Encodable Request Body ```swift theme={null} // Define request and response models struct GreetingRequest: Codable { let name: String let greeting: String } struct GreetingResponse: Codable { let message: String let timestamp: String } // Invoke with typed request let request = GreetingRequest(name: "World", greeting: "Hello") let response: GreetingResponse = try await insforge.functions.invoke( "hello-world", body: request ) print(response.message) ``` *** ## Error Handling ```swift theme={null} do { let response: MyResponse = try await insforge.functions.invoke( "my-function", body: ["key": "value"] ) print("Success: \(response)") } catch let error as InsForgeError { switch error { case .httpError(let statusCode, let message): print("HTTP Error \(statusCode): \(message)") case .decodingError(let error): print("Failed to decode response: \(error)") case .networkError(let error): print("Network error: \(error)") default: print("Error: \(error)") } } catch { print("Unexpected error: \(error)") } ``` # Swift SDK Source: https://docs.insforge.dev/sdks/swift/overview Official Swift SDK for InsForge - iOS, macOS, tvOS, and watchOS The InsForge Swift SDK provides a native Swift client for interacting with InsForge services from iOS, macOS, tvOS, and watchOS applications. ## Installation ### Swift Package Manager Add InsForge to your Swift Package Manager dependencies: ```swift theme={null} dependencies: [ .package(url: "https://github.com/insforge/insforge-swift.git", from: "0.0.9") ] ``` ```swift theme={null} import InsForge let insforge = InsForgeClient( baseURL: URL(string: "https://your-app.insforge.app")!, anonKey: "your-anon-key" ) ``` ### Enable Logging (Optional) For debugging, you can configure the SDK log level and destination: ```swift theme={null} let options = InsForgeClientOptions( global: .init( logLevel: .debug, logDestination: .osLog, logSubsystem: "com.example.MyApp" ) ) let insforge = InsForgeClient( baseURL: URL(string: "https://your-app.insforge.app")!, anonKey: "your-anon-key", options: options ) ``` **Log Levels:** | Level | Description | | ----------- | ------------------------------------------- | | `.trace` | Most verbose, includes all internal details | | `.debug` | Detailed information for debugging | | `.info` | General operational information (default) | | `.warning` | Warnings that don't prevent operation | | `.error` | Errors that affect functionality | | `.critical` | Critical failures | **Log Destinations:** | Destination | Description | | ----------- | ---------------------------------------------------------- | | `.console` | Standard output (print) | | `.osLog` | Apple's unified logging system (recommended for iOS/macOS) | | `.none` | Disable logging | | `.custom` | Provide your own LogHandler factory | Use `.info` or `.error` in production to avoid exposing sensitive data in logs. Or add it via Xcode: 1. File → Add Packages... 2. Enter: `https://github.com/insforge/insforge-swift.git` ### CocoaPods(coming soon) ```ruby theme={null} pod 'InsForge', '~> 1.0' ``` ## Quick Start ```swift theme={null} import InsForge let insforge = InsForgeClient( baseUrl: "https://your-app.insforge.app", anonKey: "your-anon-key" ) ``` ## Features * **Database** - Type-safe CRUD operations with Codable support * **Authentication** - Email/password and OAuth authentication * **Storage** - File upload, download, and management * **AI** - Chat completions and image generation * **Realtime** - WebSocket-based pub/sub messaging * **SwiftUI Integration** - Property wrappers and environment values ## Platform Support | Platform | Minimum Version | | -------- | --------------- | | iOS | 15.0+ | | macOS | 12.0+ | | tvOS | 15.0+ | | watchOS | 8.0+ | ## SDK Reference CRUD operations with Codable models Sign up, sign in, OAuth, and user management File upload, download, and management Chat completions and image generation WebSocket pub/sub messaging # Realtime SDK Reference Source: https://docs.insforge.dev/sdks/swift/realtime Subscribe to channels, publish events, and track presence with the InsForge Swift SDK. ## Installation Add InsForge to your Swift Package Manager dependencies: ```swift theme={null} dependencies: [ .package(url: "https://github.com/insforge/insforge-swift.git", from: "0.0.9") ] ``` ```swift theme={null} import InsForge let insforge = InsForgeClient( baseURL: URL(string: "https://your-app.insforge.app")!, anonKey: "your-anon-key" ) ``` ### Enable Logging (Optional) For debugging, you can configure the SDK log level and destination: ```swift theme={null} let options = InsForgeClientOptions( global: .init( logLevel: .debug, logDestination: .osLog, logSubsystem: "com.example.MyApp" ) ) let insforge = InsForgeClient( baseURL: URL(string: "https://your-app.insforge.app")!, anonKey: "your-anon-key", options: options ) ``` **Log Levels:** | Level | Description | | ----------- | ------------------------------------------- | | `.trace` | Most verbose, includes all internal details | | `.debug` | Detailed information for debugging | | `.info` | General operational information (default) | | `.warning` | Warnings that don't prevent operation | | `.error` | Errors that affect functionality | | `.critical` | Critical failures | **Log Destinations:** | Destination | Description | | ----------- | ---------------------------------------------------------- | | `.console` | Standard output (print) | | `.osLog` | Apple's unified logging system (recommended for iOS/macOS) | | `.none` | Disable logging | | `.custom` | Provide your own LogHandler factory | Use `.info` or `.error` in production to avoid exposing sensitive data in logs. ## Mental Model The Swift SDK connects to InsForge Realtime over Socket.IO. Subscribe to channel names such as `order:123` or `chat:room-1`, then listen for event names published to those channels. Events are published by database triggers that call `realtime.publish(...)` or by clients that publish to a channel they have already joined. See [Realtime overview](/core-concepts/realtime/overview) for the channel and RLS model. ## Quick Start ```swift theme={null} try await insforge.realtime.connect() await insforge.realtime.subscribe(to: "order:\(orderId)") { message in if message.eventName == "status_changed" { print("Payload: \(String(describing: message.payload))") } } ``` ## connect() Establish a realtime connection. ```swift theme={null} do { try await insforge.realtime.connect() print("Connected to realtime") } catch { print("Connection failed: \(error)") } ``` ## subscribe() Subscribe to a channel and receive messages published to it. ```swift theme={null} await insforge.realtime.subscribe(to: "chat:room-1") { message in print("Event: \(message.eventName ?? "")") print("Channel: \(message.channelName ?? "")") if let payload = message.payload { print("Payload: \(payload)") } } ``` If RLS is enabled on `realtime.channels`, subscription is checked against `SELECT` policies. ## publish() Publish an event to a channel. ```swift theme={null} try await insforge.realtime.publish( to: "chat:room-1", event: "new_message", payload: [ "text": "Hello from Swift", "sentAt": ISO8601DateFormatter().string(from: Date()) ] ) ``` The client must subscribe to a channel before publishing to that same channel. If RLS is enabled on `realtime.messages`, publish is also checked against `INSERT` policies. ## unsubscribe() Leave a channel. ```swift theme={null} await insforge.realtime.unsubscribe(from: "chat:room-1") ``` ## disconnect() Close the realtime connection. ```swift theme={null} await insforge.realtime.disconnect() ``` ## Channel API The channel API groups operations for one channel name. ```swift theme={null} let channel = await insforge.realtime.channel("chat:room-1") try await channel.subscribe() ``` ### Broadcast Messages Listen for an event: ```swift theme={null} let channel = await insforge.realtime.channel("chat:room-1") try await channel.subscribe() for await message in await channel.broadcast(event: "new_message") { print("Payload: \(message.payload)") } ``` Publish through the channel: ```swift theme={null} struct ChatMessage: Codable { let text: String let author: String } try await channel.broadcast( event: "new_message", message: ChatMessage(text: "Hello", author: currentUser.name) ) ``` Remove a channel object when it is no longer needed: ```swift theme={null} await insforge.realtime.removeChannel("chat:room-1") ``` ## Presence Successful subscriptions return the current presence snapshot where supported by the SDK API. Listen for presence updates through the channel or lower-level event APIs exposed by the SDK. Presence is ephemeral online state. It is not stored in Postgres. # Storage SDK Reference Source: https://docs.insforge.dev/sdks/swift/storage File upload, download, and management with the InsForge Swift SDK ## Installation Add InsForge to your Swift Package Manager dependencies: ```swift theme={null} dependencies: [ .package(url: "https://github.com/insforge/insforge-swift.git", from: "0.0.9") ] ``` ```swift theme={null} import InsForge let insforge = InsForgeClient( baseURL: URL(string: "https://your-app.insforge.app")!, anonKey: "your-anon-key" ) ``` ### Enable Logging (Optional) For debugging, you can configure the SDK log level and destination: ```swift theme={null} let options = InsForgeClientOptions( global: .init( logLevel: .debug, logDestination: .osLog, logSubsystem: "com.example.MyApp" ) ) let insforge = InsForgeClient( baseURL: URL(string: "https://your-app.insforge.app")!, anonKey: "your-anon-key", options: options ) ``` **Log Levels:** | Level | Description | | ----------- | ------------------------------------------- | | `.trace` | Most verbose, includes all internal details | | `.debug` | Detailed information for debugging | | `.info` | General operational information (default) | | `.warning` | Warnings that don't prevent operation | | `.error` | Errors that affect functionality | | `.critical` | Critical failures | **Log Destinations:** | Destination | Description | | ----------- | ---------------------------------------------------------- | | `.console` | Standard output (print) | | `.osLog` | Apple's unified logging system (recommended for iOS/macOS) | | `.none` | Disable logging | | `.custom` | Provide your own LogHandler factory | Use `.info` or `.error` in production to avoid exposing sensitive data in logs. ## from() Get a file API reference for a bucket. ### Example ```swift theme={null} let bucket = insforge.storage.from("images") ``` *** ## Bucket Operations ### createBucket() Creates a new storage bucket. ```swift theme={null} // Create a public bucket try await insforge.storage.createBucket("avatars") // Create a private bucket try await insforge.storage.createBucket( "documents", options: BucketOptions(isPublic: false) ) ``` ### listBuckets() List all bucket names. ```swift theme={null} let buckets = try await insforge.storage.listBuckets() // ["avatars", "documents", "uploads"] ``` ### updateBucket() Update a bucket's visibility. ```swift theme={null} try await insforge.storage.updateBucket( "documents", options: BucketOptions(isPublic: true) ) ``` ### deleteBucket() Delete a bucket. ```swift theme={null} try await insforge.storage.deleteBucket("old-bucket") ``` *** ## upload() Upload a file to the bucket. ### Parameters * `path` (String) - The object key/path for the file * `data` (Data) - File data to upload * `options` (FileOptions, optional) - Upload options including contentType ### Examples ```swift theme={null} // Upload with specific path let imageData = UIImage(named: "photo")?.jpegData(compressionQuality: 0.8) let file = try await insforge.storage .from("images") .upload( path: "posts/post-123/cover.jpg", data: imageData!, options: FileOptions(contentType: "image/jpeg") ) print("Uploaded: \(file.url)") print("Key: \(file.key)") // Upload from file URL let fileURL = URL(fileURLWithPath: "/path/to/document.pdf") let file = try await insforge.storage .from("documents") .upload( path: "reports/annual-2024.pdf", fileURL: fileURL ) // Upload with auto-generated key let file = try await insforge.storage .from("uploads") .upload( data: imageData!, fileName: "profile.jpg", options: FileOptions(contentType: "image/jpeg") ) ``` *** ## download() Download a file as Data. ### Example ```swift theme={null} // Download file let data = try await insforge.storage .from("images") .download(path: "posts/post-123/cover.jpg") // Convert to UIImage if let image = UIImage(data: data) { imageView.image = image } ``` *** ## list() List files in a bucket. ### Parameters * `options` (ListOptions, optional) - Options including prefix, limit, offset ### Examples ```swift theme={null} // List all files let files = try await insforge.storage .from("images") .list() // List with prefix filter let userFiles = try await insforge.storage .from("images") .list(options: ListOptions(prefix: "users/", limit: 50)) // Shorthand with prefix let files = try await insforge.storage .from("images") .list(prefix: "posts/", limit: 20, offset: 0) // Iterate results for file in files { print("Key: \(file.key), Size: \(file.size)") } ``` *** ## delete() Delete a file from storage. ### Example ```swift theme={null} // Delete a file try await insforge.storage .from("images") .delete(path: "posts/post-123/cover.jpg") // Delete with database cleanup let posts: [Post] = try await insforge.database .from("posts") .select("id, image_key") .eq("id", value: "post-123") .execute() if let post = posts.first, let imageKey = post.imageKey { // Delete from storage try await insforge.storage .from("images") .delete(path: imageKey) // Clear database reference struct PostUpdate: Codable { let imageUrl: String? let imageKey: String? } let _: [Post] = try await insforge.database .from("posts") .eq("id", value: "post-123") .update(PostUpdate(imageUrl: nil, imageKey: nil)) } ``` *** ## getPublicURL() Get a public URL for a file in a public bucket. ### Example ```swift theme={null} let url = insforge.storage .from("images") .getPublicURL(path: "posts/post-123/cover.jpg") print("Public URL: \(url)") ``` *** ## Upload Strategy Get upload strategy for large files (direct or presigned URL). ### getUploadStrategy() ```swift theme={null} let strategy = try await insforge.storage .from("videos") .getUploadStrategy( filename: "large-video.mp4", contentType: "video/mp4", size: 104857600 // 100MB ) if strategy.method == "presigned" { // Upload directly to S3 using presigned URL print("Upload URL: \(strategy.uploadUrl)") print("Key: \(strategy.key)") // After uploading to presigned URL, confirm the upload if strategy.confirmRequired { let file = try await insforge.storage .from("videos") .confirmUpload( path: strategy.key, size: 104857600, contentType: "video/mp4" ) } } ``` ### confirmUpload() Confirm a presigned upload after uploading directly to S3. ```swift theme={null} let file = try await insforge.storage .from("videos") .confirmUpload( path: "videos/large-video.mp4", size: 104857600, contentType: "video/mp4", etag: "\"abc123\"" // Optional S3 ETag ) ``` *** ## Download Strategy Get download strategy for private files (direct or presigned URL). ### getDownloadStrategy() ```swift theme={null} let strategy = try await insforge.storage .from("documents") .getDownloadStrategy( path: "private/confidential.pdf", expiresIn: 3600 // URL expires in 1 hour ) if strategy.method == "presigned" { // Use presigned URL to download print("Download URL: \(strategy.url)") print("Expires: \(strategy.expiresAt ?? "N/A")") } ``` *** ## Options Reference ### FileOptions Options for file upload operations. ```swift theme={null} public struct FileOptions { /// The Content-Type header value (auto-inferred if not specified) var contentType: String? /// Optional extra headers for the request var headers: [String: String]? } ``` ### BucketOptions Options for bucket creation/update. ```swift theme={null} public struct BucketOptions { /// Whether the bucket is publicly accessible (default: true) var isPublic: Bool } ``` ### ListOptions Options for listing files. ```swift theme={null} public struct ListOptions { /// Filter objects by key prefix var prefix: String? /// Maximum number of results (1-1000, default: 100) var limit: Int /// Offset for pagination (default: 0) var offset: Int } ``` *** ## StoredFile Model The response model for storage operations. ```swift theme={null} public struct StoredFile { let bucket: String // Bucket name let key: String // Object key/path let size: Int // File size in bytes let mimeType: String? // MIME type let uploadedAt: Date // Upload timestamp let url: String // Public or signed URL } ``` # Model Gateway Source: https://docs.insforge.dev/sdks/typescript/ai Build AI features with the OpenAI SDK and the OpenRouter key provisioned by InsForge ## Overview InsForge provisions an OpenRouter API key for Model Gateway projects. For new AI features, use that key directly with OpenRouter's OpenAI-compatible API instead of the deprecated InsForge AI proxy methods. Use the InsForge dashboard to copy the active OpenRouter key, store it in `OPENROUTER_API_KEY`, and call OpenRouter from trusted server-side code. Do not expose `OPENROUTER_API_KEY` in browser bundles. In React, Next.js, Vue, Svelte, and similar apps, put OpenRouter calls behind a backend route, server action, function, or other server-only boundary. ## Installation ```bash theme={null} npm install openai ``` For local scripts, you may also want: ```bash theme={null} npm install dotenv ``` ## Environment ```bash theme={null} OPENROUTER_API_KEY= ``` ## Create a Client ```typescript theme={null} import OpenAI from 'openai'; export const openai = new OpenAI({ baseURL: 'https://openrouter.ai/api/v1', apiKey: process.env.OPENROUTER_API_KEY, defaultHeaders: { 'HTTP-Referer': 'https://your-app.example', 'X-Title': 'Your App', }, }); ``` The `HTTP-Referer` and `X-Title` headers are optional OpenRouter metadata headers. Keep them if you want OpenRouter attribution and rankings for your app. ## Chat Completion ```typescript theme={null} const completion = await openai.chat.completions.create({ model: 'openai/gpt-4o', messages: [ { role: 'system', content: 'You are a concise assistant.' }, { role: 'user', content: 'What is the capital of France?' }, ], }); console.log(completion.choices[0]?.message?.content); ``` ## Streaming ```typescript theme={null} const stream = await openai.chat.completions.create({ model: 'anthropic/claude-3.5-haiku', messages: [{ role: 'user', content: 'Write a short product update.' }], stream: true, }); for await (const chunk of stream) { const text = chunk.choices[0]?.delta?.content; if (text) process.stdout.write(text); } ``` ## Tool Calling ```typescript theme={null} const completion = await openai.chat.completions.create({ model: 'openai/gpt-4o-mini', messages: [{ role: 'user', content: 'Look up order 123 and summarize it.' }], tools: [ { type: 'function', function: { name: 'get_order', description: 'Fetch an order by ID', parameters: { type: 'object', properties: { orderId: { type: 'string' }, }, required: ['orderId'], }, }, }, ], }); console.log(completion.choices[0]?.message?.tool_calls); ``` ## Image Input ```typescript theme={null} const completion = await openai.chat.completions.create({ model: 'openai/gpt-4o', messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe this image.' }, { type: 'image_url', image_url: { url: 'https://example.com/photo.jpg', }, }, ], }, ], }); console.log(completion.choices[0]?.message?.content); ``` ## Image Generation Use a model that supports image output and request image modality. OpenRouter supports fields that may not be in the OpenAI SDK type surface yet, so extend the request and response types locally. ```typescript theme={null} type OpenRouterImageRequest = OpenAI.Chat.ChatCompletionCreateParamsNonStreaming & { modalities?: Array<'image' | 'text'>; }; type OpenRouterImage = { type: 'image_url'; image_url: { url: string; }; }; type OpenRouterImageMessage = OpenAI.Chat.ChatCompletionMessage & { images?: OpenRouterImage[]; }; const request: OpenRouterImageRequest = { model: 'google/gemini-2.5-flash-image', modalities: ['image', 'text'], messages: [ { role: 'user', content: 'Generate a clean product mockup on a white background.' }, ], }; const completion = await openai.chat.completions.create(request); const message = completion.choices[0]?.message as OpenRouterImageMessage | undefined; console.log(message?.content); console.log(message?.images?.[0]?.image_url?.url); ``` ## Embeddings ```typescript theme={null} const response = await openai.embeddings.create({ model: 'openai/text-embedding-3-small', input: 'Your text here', }); console.log(response.data[0].embedding); ``` ## Video Generation OpenRouter video generation uses OpenRouter HTTP endpoints directly. ```typescript theme={null} const response = await fetch('https://openrouter.ai/api/v1/videos', { method: 'POST', headers: { Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ model: 'google/veo-3.1', prompt: 'A golden retriever playing fetch on a sunny beach.', }), }); if (!response.ok) { const body = await response.text(); throw new Error(`OpenRouter video request failed: ${response.status} ${body}`); } const job = await response.json(); console.log(job); ``` ## Next.js Route Example Replace `verifySessionToken` with your app's auth provider or session verifier. ```typescript theme={null} // app/api/chat/route.ts import OpenAI from 'openai'; const openai = new OpenAI({ baseURL: 'https://openrouter.ai/api/v1', apiKey: process.env.OPENROUTER_API_KEY, }); declare function verifySessionToken(token: string): Promise; export async function POST(request: Request) { const authorization = request.headers.get('Authorization'); const sessionToken = authorization?.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined; if (!sessionToken || !(await verifySessionToken(sessionToken))) { return Response.json({ error: 'Unauthorized' }, { status: 401 }); } const body = await request.json().catch(() => ({})); const prompt = typeof body.prompt === 'string' ? body.prompt.trim() : ''; if (!prompt || prompt.length > 4000) { return Response.json( { error: 'Prompt must be between 1 and 4000 characters.' }, { status: 400 } ); } const completion = await openai.chat.completions.create({ model: 'openai/gpt-4o-mini', messages: [{ role: 'user', content: prompt }], }); return Response.json({ text: completion.choices[0]?.message?.content ?? '', }); } ``` ## Using InsForge Data with AI Use the InsForge SDK for database, auth, storage, functions, and realtime. Use OpenRouter directly for model calls. ```typescript theme={null} import { createClient } from '@insforge/sdk'; import OpenAI from 'openai'; const insforge = createClient({ baseUrl: process.env.INSFORGE_BASE_URL!, anonKey: process.env.INSFORGE_ANON_KEY!, }); const openai = new OpenAI({ baseURL: 'https://openrouter.ai/api/v1', apiKey: process.env.OPENROUTER_API_KEY, }); const { data: documents } = await insforge.database .from('documents') .select() .limit(5); const completion = await openai.chat.completions.create({ model: 'openai/gpt-4o-mini', messages: [ { role: 'user', content: `Summarize these records:\n${JSON.stringify(documents)}`, }, ], }); console.log(completion.choices[0]?.message?.content); ``` ## Legacy InsForge AI Proxy The previous InsForge AI SDK methods are deprecated: * `insforge.ai.chat.completions.create()` * `insforge.ai.images.generate()` * `insforge.ai.embeddings.create()` They map to backend proxy routes that still exist for compatibility, but new integrations should use OpenRouter directly. The direct path gives access to OpenRouter's complete and evolving API surface without waiting for InsForge SDK wrapper updates. ## Model Catalog Use the InsForge dashboard or OpenRouter model catalog to choose a model. Model IDs use the `provider/model` format, such as `openai/gpt-4o`, `anthropic/claude-3.5-haiku`, and `google/gemini-2.5-flash-image`. # Authentication SDK Reference Source: https://docs.insforge.dev/sdks/typescript/auth User authentication and profile management with the InsForge TypeScript SDK ## Installation ```bash npm theme={null} npm install @insforge/sdk@latest ``` ```bash yarn theme={null} yarn add @insforge/sdk@latest ``` ```bash pnpm theme={null} pnpm add @insforge/sdk@latest ``` ```javascript theme={null} import { createClient } from '@insforge/sdk'; const insforge = createClient({ baseUrl: 'https://your-app.insforge.app', anonKey: 'your-anon-key' // Optional: for public/unauthenticated requests }); ``` ## signUp() Create a new user account with email and password. ### Parameters * `email` (string, required) - User's email address * `password` (string, required) - User's password * `name` (string, optional) - User's display name * `redirectTo` (string, optional) - Used for link-based email verification. The email link always opens an InsForge backend endpoint first; after the token is verified, InsForge redirects the browser to this URL with the verification result. Required when `verifyEmailMethod` is set to `link`. This URL must be included in `allowedRedirectUrls`. Recommended: use your app's sign-in page. ### Returns ```typescript theme={null} { data: { user?: { id, email, emailVerified, providers, createdAt, updatedAt, profile, metadata }, accessToken: string | null, requireEmailVerification?: boolean, csrfToken?: string | null } | null, error: Error | null } ``` When `requireEmailVerification` is true, `accessToken` will be null until the user verifies their email. InsForge sends a verification email with either a link or a 6-digit code, based on your dashboard configuration (`verifyEmailMethod`). For code verification, implement a page that prompts the user to enter the code (see [verifyEmail()](#verifyemail)). For link verification, provide a `redirectTo` URL that should receive the browser after InsForge verifies the token. Recommended: use your sign-in page as `redirectTo`, then show a success message and ask the user to sign in with their email and password. ### Example ```javascript theme={null} const { data, error } = await insforge.auth.signUp({ email: 'user@example.com', password: 'secure_password123', name: 'John Doe', redirectTo: 'http://localhost:3000/sign-in', }); if (data?.requireEmailVerification) { // For code verification: redirect to page where user enters the 6-digit code // For link verification: wait for the user to click the email link console.log('Please verify your email'); } else if (data?.accessToken) { // User is signed in (email verification disabled) console.log('Welcome!', data.user.email); } ``` ### Output ```json theme={null} { "data": { "user": { "id": "usr_abc123", "email": "user@example.com", "emailVerified": false, "providers": ["email"], "createdAt": "2024-01-15T10:00:00Z", "updatedAt": "2024-01-15T10:00:00Z", "profile": { "name": "John Doe", "avatar_url": null }, "metadata": {} }, "requireEmailVerification": true, "accessToken": null, "csrfToken": null }, "error": null } ``` *** ## signInWithPassword() Sign in an existing user with email and password. ### Parameters * `email` (string, required) - User's email address * `password` (string, required) - User's password ### Returns ```typescript theme={null} { data: { user: { id, email, emailVerified, providers, createdAt, updatedAt, profile, metadata }, accessToken: string, csrfToken?: string | null } | null, error: Error | null } ``` ### Example ```javascript theme={null} const { data, error } = await insforge.auth.signInWithPassword({ email: 'user@example.com', password: 'secure_password123', }); if (data) { console.log('Signed in as:', data.user.email); } ``` ### Output ```json theme={null} { "data": { "user": { "id": "usr_abc123", "email": "user@example.com", "emailVerified": true, "providers": ["email"], "createdAt": "2024-01-15T10:00:00Z", "updatedAt": "2024-01-15T10:00:00Z", "profile": { "name": "John Doe", "avatar_url": "https://example.com/avatar.jpg" }, "metadata": {} }, "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "csrfToken": "5758d38259fb..." }, "error": null } ``` *** ## signInWithOAuth() Start OAuth authentication flow with configured providers (built-in providers like Google/GitHub, plus any custom provider key configured from the dashboard). ### Parameters * `provider` (string, required) - OAuth provider key (e.g., `google`, `github`, or custom provider key like `okta-company`) * `redirectTo` (string, required) - URL to redirect after authentication * `additionalParams` (`Record`, optional) - Provider-specific OAuth hints such as Google's `prompt=select_account` * `skipBrowserRedirect` (boolean, optional) - If true, returns OAuth URL without auto-redirecting (for server-rendered or mobile flows) ### Returns ```typescript theme={null} { data: { url?: string, provider?: string, codeVerifier?: string }, error: Error | null } ``` After OAuth redirect, SDK automatically detects the callback `insforge_code`, exchanges it for a session, and saves the session automatically. ### Example ```javascript theme={null} // Default: auto-redirect await insforge.auth.signInWithOAuth('google', { redirectTo: 'http://localhost:3000/dashboard', additionalParams: { prompt: 'select_account' }, }); // Browser immediately redirects to Google // skipBrowserRedirect: get URL for manual handling const { data } = await insforge.auth.signInWithOAuth('google', { redirectTo: 'http://localhost:3000/dashboard', skipBrowserRedirect: true, }); window.location.href = data.url; // Redirect when ready ``` `additionalParams` is for provider-specific optional hints only. Do not pass server-owned OAuth fields such as `client_id`, `redirect_uri`, `code_challenge`, `state`, `response_type`, or `scope`; InsForge sets those values on the server and ignores colliding client-provided keys. Custom providers must be configured first in the InsForge dashboard under `Auth Methods` with client credentials and an OIDC discovery URL. ### Output ```json theme={null} { "data": { "url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...", "provider": "google" }, "error": null } ``` *** ## signOut() Sign out the current user and clear session. ### Parameters None ### Returns ```typescript theme={null} { error: Error | null; } ``` ### Example ```javascript theme={null} const { error } = await insforge.auth.signOut(); ``` ### Output ```json theme={null} { "error": null } ``` *** ## getCurrentUser() Get the currently signed-in user. For browser apps, call `auth.getCurrentUser()` during startup. If a valid httpOnly refresh cookie is present, the SDK will refresh the session automatically before returning the user. For server mode, call `refreshSession({ refreshToken })` explicitly when you need to refresh an expired access token. ### Parameters None ### Returns ```typescript theme={null} { data: { user: { id, email, emailVerified, providers, createdAt, updatedAt, profile, metadata } | null }, error: Error | null } ``` ### Example ```javascript theme={null} const { data, error } = await insforge.auth.getCurrentUser(); if (data.user) { console.log('User:', data.user.email); } ``` ### Output ```json theme={null} { "data": { "user": { "id": "usr_abc123", "email": "user@example.com", "emailVerified": true, "createdAt": "2024-01-15T10:00:00Z", "updatedAt": "2024-01-15T10:00:00Z", "providers": ["email"], "profile": { "name": "John Doe", "avatar_url": "https://example.com/avatar.jpg" }, "metadata": {} } }, "error": null } ``` *** ## getProfile() Get any user's public profile by ID. Returns a flat profile object with all fields at the top level. ### Parameters * `userId` (string, required) - User ID to fetch profile for ### Returns ```typescript theme={null} { data: { id: string, name?: string, avatar_url?: string, createdAt?: string, updatedAt?: string, ...customFields } | null, error: Error | null } ``` ### Example ```javascript theme={null} const { data, error } = await insforge.auth.getProfile('usr_xyz789'); if (data) { console.log(data.name); console.log(data.avatar_url); } ``` ### Output ```json theme={null} { "data": { "id": "usr_xyz789", "name": "John Doe", "avatar_url": "https://example.com/avatar.jpg", "bio": "Full-stack developer", "createdAt": "2024-01-15T10:00:00Z", "updatedAt": "2024-01-15T10:00:00Z" }, "error": null } ``` *** ## setProfile() Update current user's profile in users table. Supports any dynamic fields and returns the updated profile as a flat object. ### Parameters * `profile` (object) - A key-value map of profile fields to update. Any fields are accepted. **Common fields:** * `name` (predefined, string) - User's display name * `avatar_url` (predefined, string) - Profile picture URL ### Returns ```typescript theme={null} { data: { id: string, name?: string, avatar_url?: string, createdAt?: string, updatedAt?: string, ...customFields } | null, error: Error | null } ``` ### Example ```javascript theme={null} const { data, error } = await insforge.auth.setProfile({ name: 'JohnDev', bio: 'Full-stack developer', avatar_url: 'https://example.com/avatar.jpg', custom_field: 'any value', // Any custom fields are supported }); ``` ### Output ```json theme={null} { "data": { "id": "usr_abc123", "name": "JohnDev", "avatar_url": "https://example.com/avatar.jpg", "bio": "Full-stack developer", "custom_field": "any value", "createdAt": "2024-01-15T10:00:00Z", "updatedAt": "2024-01-15T12:30:00Z" }, "error": null } ``` *** ## resendVerificationEmail() Resend email verification when the previous OTP has expired or was not received. Uses the method configured in auth settings (`verifyEmailMethod`). When method is `code`, sends a 6-digit numeric code. When method is `link`, sends a browser verification link that goes through an InsForge backend endpoint first. This endpoint prevents user enumeration by returning success even if the email doesn't exist. ### Parameters * `email` (string, required) - User's email address * `redirectTo` (string, optional) - Used for link-based email verification. The email link always opens an InsForge backend endpoint first; after the token is verified, InsForge redirects the browser to this URL with the verification result. Required when `verifyEmailMethod` is set to `link`. This URL must be included in `allowedRedirectUrls`. Recommended: use your app's sign-in page. ### Returns ```typescript theme={null} { data: { success: boolean, message: string } | null, error: Error | null } ``` ### Example ```javascript theme={null} const { data, error } = await insforge.auth.resendVerificationEmail({ email: 'user@example.com', redirectTo: 'http://localhost:3000/sign-in', }); if (data?.success) { console.log('Verification email sent!'); } ``` ### Output ```json theme={null} { "data": { "success": true, "message": "Verification email sent" }, "error": null } ``` *** ## verifyEmail() Verify an email address with a 6-digit code. For link-based verification, users should click the email link, which opens `GET /api/auth/email/verify-link` in the browser. Successfully verified users who use this code endpoint will receive a session token. For link-based verification, your frontend should handle the browser redirect like this: * Success: `?insforge_status=success&insforge_type=verify_email` * Error: `?insforge_status=error&insforge_type=verify_email&insforge_error=...` * `insforge_status`: Result of the browser link flow. For verification, values are `success` or `error`. * `insforge_type`: Flow identifier. For verification links this is always `verify_email`. * `insforge_error`: Present only when `insforge_status=error`. Human-readable error message for display or logging. Recommended handling: use your sign-in page as `redirectTo`. When `insforge_status=success`, show a confirmation message and ask the user to sign in with their email and password. ### Parameters * `email` (string, required) - User's email address * `otp` (string, required) - 6-digit verification code ### Returns ```typescript theme={null} { data: { accessToken: string, user: { id, email, emailVerified, ... } } | null, error: Error | null } ``` ### Example ```javascript theme={null} const { data, error } = await insforge.auth.verifyEmail({ email: 'user@example.com', otp: '123456', }); if (data) { console.log('Email verified!', data.accessToken); } ``` ### Output ```json theme={null} { "data": { "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "user": { "id": "usr_abc123", "email": "user@example.com", "emailVerified": true } }, "error": null } ``` *** ## sendResetPasswordEmail() Send password reset email using the method configured in auth settings (`resetPasswordMethod`). When method is `code`, sends a 6-digit numeric code for two-step flow. When method is `link`, sends a browser reset link that goes through an InsForge backend endpoint first. This endpoint prevents user enumeration by returning success even if the email doesn't exist. ### Parameters * `email` (string, required) - User's email address * `redirectTo` (string, optional) - Used for link-based password reset. The email link always opens an InsForge backend endpoint first; InsForge then redirects the browser to this URL with the reset `token` in the query string so your app can render its own reset-password page. Required when `resetPasswordMethod` is set to `link`. This URL must be included in `allowedRedirectUrls`. Recommended: use your app's dedicated reset-password page. ### Returns ```typescript theme={null} { data: { success: boolean, message: string } | null, error: Error | null } ``` ### Example ```javascript theme={null} const { data, error } = await insforge.auth.sendResetPasswordEmail({ email: 'user@example.com', redirectTo: 'http://localhost:3000/reset-password', }); if (data?.success) { console.log('Password reset email sent!'); } ``` ### Output ```json theme={null} { "data": { "success": true, "message": "Password reset email sent" }, "error": null } ``` *** ## exchangeResetPasswordToken() Exchange a 6-digit reset password code for a reset token. This is step 1 of the two-step password reset flow (only used when `resetPasswordMethod` is `code`). This endpoint is not used when `resetPasswordMethod` is `link`, because the browser reset-link flow uses the emailed link token directly. ### Parameters * `email` (string, required) - User's email address * `code` (string, required) - 6-digit code from the email ### Returns ```typescript theme={null} { data: { token: string, expiresAt: string } | null, error: Error | null } ``` ### Example ```javascript theme={null} const { data, error } = await insforge.auth.exchangeResetPasswordToken({ email: 'user@example.com', code: '123456', }); if (data) { // Use token to reset password await insforge.auth.resetPassword({ newPassword: 'newSecurePassword123', otp: data.token, }); } ``` ### Output ```json theme={null} { "data": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expiresAt": "2024-01-15T11:00:00Z" }, "error": null } ``` *** ## resetPassword() Reset user password with a token. The token can be: * **Magic link token**: Provided in the reset page URL from `sendResetPasswordEmail` when method is `link` * **Reset token**: From `exchangeResetPasswordToken` after code verification when method is `code` ### Parameters * `newPassword` (string, required) - New password for the user * `otp` (string, required) - Reset token or magic link token For link-based password reset, your frontend should handle the browser redirect like this: * Ready to reset: `?token=...&insforge_status=ready&insforge_type=reset_password` * Error: `?insforge_status=error&insforge_type=reset_password&insforge_error=...` * `token`: Present only when `insforge_status=ready`. Pass this value to `resetPassword({ otp })`. * `insforge_status`: Result of the browser link flow. For reset links, values are `ready` or `error`. * `insforge_type`: Flow identifier. For reset links this is always `reset_password`. * `insforge_error`: Present only when `insforge_status=error`. Human-readable error message for display or logging. Only render the reset-password form when `insforge_status=ready` and `token` is present. ### Returns ```typescript theme={null} { data: { message: string } | null, error: Error | null } ``` ### Example ```javascript theme={null} // Code method flow: after exchangeResetPasswordToken const { data, error } = await insforge.auth.resetPassword({ newPassword: 'newSecurePassword123', otp: resetToken, // Token from exchangeResetPasswordToken }); // Link method flow: token from the reset page URL const { data, error } = await insforge.auth.resetPassword({ newPassword: 'newSecurePassword123', otp: 'a1b2c3d4e5f6...', // Token from the magic link URL }); if (data) { console.log('Password reset successful!'); } ``` ### Output ```json theme={null} { "data": { "message": "Password reset successfully. Please login with your new password." }, "error": null } ``` *** ## Error Handling All auth methods return structured errors: ```javascript theme={null} const { data, error } = await insforge.auth.signInWithPassword({ email: 'user@example.com', password: 'wrong_password', }); if (error) { console.error(error.statusCode); // 401 console.error(error.error); // 'INVALID_CREDENTIALS' console.error(error.message); // 'Invalid login credentials' console.error(error.nextActions); // 'Check email and password' } ``` # Database SDK Reference Source: https://docs.insforge.dev/sdks/typescript/database Type-safe database operations using the InsForge TypeScript SDK ## Installation ```bash npm theme={null} npm install @insforge/sdk@latest ``` ```bash yarn theme={null} yarn add @insforge/sdk@latest ``` ```bash pnpm theme={null} pnpm add @insforge/sdk@latest ``` ```javascript theme={null} import { createClient } from '@insforge/sdk'; const insforge = createClient({ baseUrl: 'https://your-app.insforge.app', anonKey: 'your-anon-key' // Optional: for public/unauthenticated requests }); ``` ## insert() Insert new records into a table. ### Parameters * `values` (object | Array, required) - Data to insert. Single object or array for bulk insert * `options.count` ('exact' | 'planned' | 'estimated', optional) - Include count of inserted rows ### Returns ```typescript theme={null} { data: Array | null, error: Error | null, count?: number } ``` Chain `.select()` after `.insert()` to return the inserted data ### Examples ```javascript Single insert theme={null} const { data, error } = await insforge.database .from('posts') .insert({ title: 'Hello World', content: 'My first post!' }) .select() ``` ```javascript Bulk insert theme={null} const { data, error } = await insforge.database .from('posts') .insert([ { title: 'First Post', content: 'Hello everyone!' }, { title: 'Second Post', content: 'Another update.' } ]) .select() ``` ### Output Example ```json theme={null} { "data": [ { "id": "789", "title": "Hello World", "content": "My first post!", "created_at": "2024-01-15T10:30:00Z" } ], "error": null } ``` *** ## update() Update existing records in a table. Must use filters to target specific rows. ### Parameters * `values` (object, required) - Fields to update * `options.count` ('exact' | 'planned' | 'estimated', optional) - Include count of updated rows ### Returns ```typescript theme={null} { data: Array | null, error: Error | null, count?: number } ``` Always use filters like `.eq()` or `.in()` to specify which rows to update ### Examples ```javascript Update by ID theme={null} const { data, error } = await insforge.database .from('posts') .update({ title: 'Updated Title' }) .eq('id', postId) .select() ``` ```javascript Update multiple theme={null} const { data, error } = await insforge.database .from('tasks') .update({ status: 'completed' }) .in('id', ['task-1', 'task-2']) .select() ``` ### Output Example ```json theme={null} { "data": [ { "id": "123", "title": "Updated Title", "content": "My first post!", "updated_at": "2024-01-15T11:00:00Z" } ], "error": null } ``` *** ## delete() Delete records from a table. Must use filters to target specific rows. ### Parameters * `options.count` ('exact' | 'planned' | 'estimated', optional) - Include count of deleted rows ### Returns ```typescript theme={null} { data: Array | null, error: Error | null, count?: number } ``` Always use filters to specify which rows to delete ### Examples ```javascript Delete by ID theme={null} const { error } = await insforge.database .from('posts') .delete() .eq('id', postId) ``` ```javascript Delete with filter theme={null} const { error } = await insforge.database .from('sessions') .delete() .lt('expires_at', new Date()) ``` ### Output Example ```json theme={null} { "data": null, "error": null } ``` *** ## select() Query records from a table or view. ### Parameters * `columns` (string, optional) - Comma-separated column names. Use `*` for all columns * `options.count` ('exact' | 'planned' | 'estimated', optional) - Include total row count * `options.head` (boolean, optional) - Return only count, no data ### Returns ```typescript theme={null} { data: Array | null, error: Error | null, count?: number } ``` ### Examples ```javascript Get all theme={null} const { data, error } = await insforge.database .from('posts') .select() ``` ```javascript Specific columns theme={null} const { data, error } = await insforge.database .from('posts') .select('id, title, content') ``` ```javascript With relationships theme={null} const { data, error } = await insforge.database .from('posts') .select('*, comments(id, content)') ``` ### Output Example ```json theme={null} { "data": [ { "id": "123", "title": "Hello World", "content": "My first post!" }, { "id": "456", "title": "Second Post", "content": "Another update." } ], "error": null } ``` *** ## rpc() Call PostgreSQL stored functions (RPC - Remote Procedure Call). ### Parameters * `functionName` (string, required) - Name of the PostgreSQL function to call * `args` (object, optional) - Arguments to pass to the function ### Returns ```typescript theme={null} { data: T | T[] | null, error: Error | null } ``` ### Examples ```javascript With parameters theme={null} const { data, error } = await insforge.database .rpc('get_user_stats', { user_id: '123' }) ``` ```javascript Without parameters theme={null} const { data, error } = await insforge.database .rpc('get_all_active_users') ``` *** ## Filters Chain filters to narrow down query results. All filters take `(column, value)` as parameters. | Filter | Description | Example | | ------------------------- | ------------------------------------------- | -------------------------------------- | | `.eq(column, value)` | Equals | `.eq('status', 'active')` | | `.neq(column, value)` | Not equals | `.neq('status', 'banned')` | | `.gt(column, value)` | Greater than | `.gt('age', 18)` | | `.gte(column, value)` | Greater than or equal | `.gte('price', 100)` | | `.lt(column, value)` | Less than | `.lt('stock', 10)` | | `.lte(column, value)` | Less than or equal | `.lte('priority', 3)` | | `.like(column, pattern)` | Case-sensitive pattern (use `%` wildcard) | `.like('name', '%Widget%')` | | `.ilike(column, pattern)` | Case-insensitive pattern (use `%` wildcard) | `.ilike('email', '%@gmail.com')` | | `.in(column, array)` | Value in array | `.in('status', ['pending', 'active'])` | | `.is(column, value)` | Exactly equals (for null checks) | `.is('deleted_at', null)` | ```javascript theme={null} // Example: Chain multiple filters const { data } = await insforge.database .from('products') .select() .eq('category', 'electronics') .gte('price', 50) .lte('price', 500) .is('in_stock', true) ``` *** ## Modifiers Control how query results are returned. | Modifier | Description | Example | | ------------------------- | -------------------------------------------------------------------------- | -------------------------------------------- | | `.order(column, options)` | Sort results. Options: `{ ascending: true/false, nullsFirst: true/false }` | `.order('created_at', { ascending: false })` | | `.limit(count)` | Limit number of rows | `.limit(10)` | | `.range(from, to)` | Get rows between indices (pagination) | `.range(0, 9)` | | `.single()` | Return object instead of array (throws if multiple) | `.single()` | | `.maybeSingle()` | Return object or null (no error) | `.maybeSingle()` | ```javascript theme={null} // Example: Pagination with sorting const { data } = await insforge.database .from('posts') .select() .order('created_at', { ascending: false }) .range(0, 9) ``` *** ## Common Patterns ### Pagination with Count ```javascript theme={null} const page = 1; const pageSize = 10; const from = (page - 1) * pageSize; const to = from + pageSize - 1; const { data, count } = await insforge.database .from('posts') .select('*', { count: 'exact' }) .range(from, to) .order('created_at', { ascending: false }) console.log(`Page ${page}: ${data.length} of ${count} total`) ``` **Output:** ```json theme={null} { "data": [ { "id": "1", "title": "Post 1", "created_at": "2024-01-15T10:00:00Z" }, { "id": "2", "title": "Post 2", "created_at": "2024-01-14T10:00:00Z" } ], "count": 50, "error": null } ``` ### Filtered Search ```javascript theme={null} const { data } = await insforge.database .from('products') .select('id, name, price, category') .eq('category', 'electronics') .gte('price', 50) .lte('price', 500) .order('price', { ascending: true }) .limit(20) ``` **Output:** ```json theme={null} { "data": [ { "id": "101", "name": "USB Cable", "price": 59.99, "category": "electronics" }, { "id": "102", "name": "Keyboard", "price": 89.99, "category": "electronics" } ], "error": null } ``` ### Using with Authentication Hooks Combine database queries with `useUser()` or `useAuth()` hooks to fetch user-specific data: ```tsx theme={null} import { useUser } from '@insforge/react'; // or '@insforge/react-router' import { insforge } from './lib/insforge'; import { useEffect, useState } from 'react'; function MyProfile() { const { user, isLoaded } = useUser(); const [posts, setPosts] = useState([]); useEffect(() => { if (user) { // Fetch user's posts from database insforge.database .from('posts') .select('*') .eq('user_id', user.id) .then(({ data }) => setPosts(data)); } }, [user]); if (!isLoaded) return
Loading...
; if (!user) return
Not signed in
; return (

{user.profile?.name}

{user.email}

My Posts: {posts.length}

{posts.map(post => (
{post.title}
))}
); } ``` **Key points:** * Use `user.id` to filter data for the authenticated user * Check `isLoaded` before accessing `user` to avoid race conditions * Check `!user` to handle unauthenticated state # Email SDK Reference Source: https://docs.insforge.dev/sdks/typescript/email Send custom transactional emails with the InsForge SDK ```bash npm theme={null} npm install @insforge/sdk@latest ``` ```bash yarn theme={null} yarn add @insforge/sdk@latest ``` ```bash pnpm theme={null} pnpm add @insforge/sdk@latest ``` ```javascript theme={null} import { createClient } from '@insforge/sdk'; const insforge = createClient({ baseUrl: 'https://your-app.insforge.app', anonKey: 'your-anon-key' // Optional: for public/unauthenticated requests }); ``` ## emails.send() Send custom HTML emails to one or more recipients. ### Parameters * `to` (string | string\[], required) - Recipient email(s), max 50 recipients * `subject` (string, required) - Email subject line, max 500 characters * `html` (string, required) - HTML content of the email * `cc` (string | string\[], optional) - CC recipient(s), max 50 recipients * `bcc` (string | string\[], optional) - BCC recipient(s), max 50 recipients * `from` (string, optional) - Custom sender name, max 100 characters * `replyTo` (string, optional) - Reply-to email address, must be a valid email address When [Custom SMTP](/core-concepts/messaging/custom-smtp) is enabled for your project, the `from` field is ignored. The sender name and email are always taken from your SMTP configuration to prevent spoofing through your server. ### Returns ```typescript theme={null} { data: {} | null, // Empty object on success error: Error | null } ``` ### Example ```javascript theme={null} const { data, error } = await insforge.emails.send({ to: ['user1@example.com', 'user2@example.com'], cc: 'manager@example.com', from: 'Acme Support Team', subject: 'Team Update', html: '

Weekly Update

Here are this week\'s highlights...

', replyTo: 'support@example.com' }); if (error) { console.error('Failed to send email:', error.message); return; } console.log('Email sent successfully'); ``` # Functions SDK Reference Source: https://docs.insforge.dev/sdks/typescript/functions Invoke serverless functions with the InsForge TypeScript SDK ## Installation ```bash npm theme={null} npm install @insforge/sdk@latest ``` ```bash yarn theme={null} yarn add @insforge/sdk@latest ``` ```bash pnpm theme={null} pnpm add @insforge/sdk@latest ``` ```javascript theme={null} import { createClient } from '@insforge/sdk'; const insforge = createClient({ baseUrl: 'https://your-app.insforge.app', anonKey: 'your-anon-key' // Optional: for public/unauthenticated requests }); ``` ## invoke() Invoke a serverless function by slug. ### Parameters * `slug` (string, required) - Function slug/name * `body` (any, optional) - Request body (JSON-serializable) * `headers` (object, optional) - Custom headers * `method` ('GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', optional) - HTTP method (default: POST) ### Returns ```typescript theme={null} { data: any | null, // Response from function error: Error | null } ``` SDK automatically includes authentication token from logged-in user. ### Example (POST with body) ```javascript theme={null} const { data, error } = await insforge.functions.invoke('hello-world', { body: { name: 'World', greeting: 'Hello' } }) console.log(data) ``` ### Output (POST with body) ```json theme={null} { "data": { "message": "Hello, World!", "timestamp": "2024-01-15T10:30:00Z" }, "error": null } ``` ### Example (GET request) ```javascript theme={null} const { data, error } = await insforge.functions.invoke('get-stats', { method: 'GET' }) console.log(data) ``` ### Output (GET request) ```json theme={null} { "data": { "posts": 500, "comments": 1200 }, "error": null } ``` ### Example (With custom headers) ```javascript theme={null} const { data, error } = await insforge.functions.invoke('api-endpoint', { method: 'PUT', body: { id: '123', status: 'active' }, headers: { 'X-Custom-Header': 'value' } }) ``` ### Output (With custom headers) ```json theme={null} { "data": { "updated": true, "id": "123" }, "error": null } ``` ## Complete Serverless Function Examples ### Example 1: Public Function (No Authentication Required) ```typescript theme={null} import { createClient } from 'npm:@insforge/sdk'; export default async function(req: Request): Promise { const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization' }; if (req.method === 'OPTIONS') { return new Response(null, { status: 204, headers: corsHeaders }); } // Create client with anon token - no authentication needed const client = createClient({ baseUrl: Deno.env.get('INSFORGE_BASE_URL'), anonKey: Deno.env.get('ANON_KEY') }); // Access public data const { data, error } = await client.database .from('public_posts') .select('*') .limit(10); return new Response(JSON.stringify({ data }), { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } ``` ### Example 2: Authenticated Function (Access User Data) ```typescript theme={null} import { createClient } from 'npm:@insforge/sdk'; export default async function(req: Request): Promise { const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization' }; if (req.method === 'OPTIONS') { return new Response(null, { status: 204, headers: corsHeaders }); } // Extract token from request headers const authHeader = req.headers.get('Authorization'); const userToken = authHeader ? authHeader.replace('Bearer ', '') : null; // Create client with user's token for authenticated access const client = createClient({ baseUrl: Deno.env.get('INSFORGE_BASE_URL'), edgeFunctionToken: userToken }); // Get authenticated user const { data: userData } = await client.auth.getCurrentUser(); if (!userData?.user?.id) { return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } // Access user's private data or create records with user_id await client.database.from('user_posts').insert([{ user_id: userData.user.id, content: 'My post' }]); return new Response(JSON.stringify({ success: true }), { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } ``` # TypeScript SDK Source: https://docs.insforge.dev/sdks/typescript/overview Official TypeScript/JavaScript SDK for InsForge The InsForge TypeScript SDK provides a type-safe client for interacting with InsForge services from JavaScript and TypeScript applications. ## Installation ```bash npm theme={null} npm install @insforge/sdk@latest ``` ```bash yarn theme={null} yarn add @insforge/sdk@latest ``` ```bash pnpm theme={null} pnpm add @insforge/sdk@latest ``` ```javascript theme={null} import { createClient } from '@insforge/sdk'; const insforge = createClient({ baseUrl: 'https://your-app.insforge.app', anonKey: 'your-anon-key' // Optional: for public/unauthenticated requests }); ``` ## Quick Start ```typescript theme={null} import { createClient } from '@insforge/sdk'; const insforge = createClient({ baseUrl: 'https://your-app.insforge.app', anonKey: 'your-anon-key' // Optional: for public/unauthenticated requests }); ``` ## Features * **Database** - Type-safe CRUD operations with PostgREST * **Authentication** - Email/password and OAuth authentication * **Storage** - File upload, download, and management * **Functions** - Invoke serverless edge functions * **AI** - Chat completions and image generation * **Realtime** - WebSocket-based pub/sub messaging * **Payments** - Provider-scoped Stripe and Razorpay payment helpers ## SDK Reference CRUD operations, filters, and queries Sign up, sign in, OAuth, and user management File upload, download, and management Invoke serverless edge functions Chat completions and image generation WebSocket pub/sub messaging Provider-scoped Stripe and Razorpay payment helpers # Payments Source: https://docs.insforge.dev/sdks/typescript/payments Choose the Stripe or Razorpay payments helpers for TypeScript apps ```bash npm theme={null} npm install @insforge/sdk@latest ``` ```bash yarn theme={null} yarn add @insforge/sdk@latest ``` ```bash pnpm theme={null} pnpm add @insforge/sdk@latest ``` ```javascript theme={null} import { createClient } from '@insforge/sdk'; const insforge = createClient({ baseUrl: 'https://your-app.insforge.app', anonKey: 'your-anon-key' // Optional: for public/unauthenticated requests }); ``` InsForge Payments is provider-specific. Stripe uses hosted Checkout and Billing Portal. Razorpay uses provider-native Orders and Subscriptions plus Razorpay Checkout. The TypeScript SDK exposes separate provider modules for both. Create Checkout and Billing Portal sessions with `insforge.payments.stripe`. Create Orders or Subscriptions with `insforge.payments.razorpay`, then open Razorpay Checkout. ## Shared setup ```typescript theme={null} import { createClient } from '@insforge/sdk'; const insforge = createClient({ baseUrl: 'https://your-project.insforge.app', anonKey: 'your-anon-key' }); ``` Payment runtime calls require an InsForge user token. Guest one-time checkout can use an anonymous InsForge token. Provider secret keys must stay server-side and should be configured through Dashboard -> Payments -> Settings, the CLI, or admin APIs. ## Fulfillment For both providers, create app-owned tables such as `public.orders`, `public.credit_ledger`, or `public.team_entitlements`, then populate them from verified provider webhook events in `payments.webhook_events`. `payments.transactions` is a dashboard/reporting projection. Do not use it as the primary business workflow. ## Next steps * Use [Stripe Payments](/sdks/typescript/payments-stripe) for `insforge.payments.stripe.createCheckoutSession(...)` and `insforge.payments.stripe.createCustomerPortalSession(...)`. * Use [Razorpay Payments](/sdks/typescript/payments-razorpay) for `insforge.payments.razorpay.createOrder(...)`, `createSubscription(...)`, verification, and subscription management. * Read [Payments overview](/core-concepts/payments/overview) for the shared database architecture and fulfillment model. # Razorpay Payments SDK Source: https://docs.insforge.dev/sdks/typescript/payments-razorpay Create Razorpay Orders and Subscriptions from a TypeScript app ```bash npm theme={null} npm install @insforge/sdk@latest ``` ```bash yarn theme={null} yarn add @insforge/sdk@latest ``` ```bash pnpm theme={null} pnpm add @insforge/sdk@latest ``` ```javascript theme={null} import { createClient } from '@insforge/sdk'; const insforge = createClient({ baseUrl: 'https://your-app.insforge.app', anonKey: 'your-anon-key' // Optional: for public/unauthenticated requests }); ``` ## Overview The TypeScript SDK exposes Razorpay runtime helpers for generated app frontends: * `insforge.payments.razorpay.createOrder(...)` * `insforge.payments.razorpay.verifyOrder(...)` * `insforge.payments.razorpay.createSubscription(...)` * `insforge.payments.razorpay.verifySubscription(...)` * `insforge.payments.razorpay.cancelSubscription(...)` * `insforge.payments.razorpay.pauseSubscription(...)` * `insforge.payments.razorpay.resumeSubscription(...)` Razorpay does not return a hosted Checkout URL. The backend creates a Razorpay Order or Subscription and returns `checkoutOptions`. The browser loads Razorpay Checkout and opens it with those options. See [Razorpay Payments](/core-concepts/payments/razorpay) for provider setup, manual webhooks, database tables, and fulfillment patterns. ## SDK setup ```typescript theme={null} import { createClient } from '@insforge/sdk'; const insforge = createClient({ baseUrl: 'https://your-project.insforge.app', anonKey: 'your-anon-key' }); declare global { interface Window { Razorpay?: new (options: Record) => { open: () => void }; } } ``` The SDK calls Razorpay runtime routes with the current InsForge user token. Do not call these routes with provider secret keys. ## Load Razorpay Checkout ```typescript theme={null} function loadRazorpayCheckout(): Promise { return new Promise((resolve, reject) => { if (window.Razorpay) { resolve(); return; } const script = document.createElement('script'); script.src = 'https://checkout.razorpay.com/v1/checkout.js'; script.onload = () => resolve(); script.onerror = () => reject(new Error('Failed to load Razorpay Checkout')); document.body.appendChild(script); }); } ``` ## One-time order Create an app-owned pending order first. Then create the Razorpay Order through InsForge: Razorpay Orders can be amount-only, but creating Razorpay Items for one-time sellable products is a good practice because synced Items make the catalog visible in InsForge and Razorpay. Treat Orders as payment attempts. ```typescript theme={null} type CreateRazorpayOrderResponse = { order: { id: string; orderId: string | null; status: string; }; checkoutOptions: { key: string; amount: number; currency: string; order_id: string; name?: string | null; description?: string | null; callback_url?: string | null; prefill: { name?: string | null; email?: string | null; contact?: string | null; }; }; }; const { data: orderResponse, error } = await insforge.payments.razorpay.createOrder('test', { amount: 50000, currency: 'INR', receipt: order.id, subject: { type: 'team', id: teamId }, customerEmail: user.email, notes: { order_id: order.id } }); if (error) throw error; if (!orderResponse) throw new Error('Razorpay order creation returned no data'); ``` If your webhook trigger reads `notes.order_id`, pass `notes: { order_id: ... }` when creating the Order. Open Razorpay Checkout and verify the returned signature: ```typescript theme={null} await loadRazorpayCheckout(); const RazorpayCheckout = window.Razorpay; if (!RazorpayCheckout) { throw new Error('Razorpay Checkout failed to load'); } const checkout = new RazorpayCheckout({ ...orderResponse.checkoutOptions, handler: async (response: { razorpay_order_id: string; razorpay_payment_id: string; razorpay_signature: string; }) => { const { error } = await insforge.payments.razorpay.verifyOrder('test', { orderId: response.razorpay_order_id, paymentId: response.razorpay_payment_id, signature: response.razorpay_signature }); if (error) throw error; } }); checkout.open(); ``` Signature verification protects the immediate browser callback. Durable fulfillment should still come from verified Razorpay webhook events. ## Subscription Create or sync a Razorpay Plan first. Then create the subscription through InsForge: ```typescript theme={null} type CreateRazorpaySubscriptionResponse = { subscription: { subscriptionId: string; status: string; }; checkoutOptions: { key: string; subscription_id: string; name?: string | null; description?: string | null; callback_url?: string | null; prefill: { name?: string | null; email?: string | null; contact?: string | null; }; }; }; const { data: subscriptionResponse, error } = await insforge.payments.razorpay.createSubscription('test', { planId: 'plan_123', totalCount: 12, subject: { type: 'team', id: teamId }, customerEmail: user.email }); if (error) throw error; if (!subscriptionResponse) throw new Error('Razorpay subscription creation returned no data'); ``` Pass Razorpay subscription `notes` when webhook triggers need app identifiers later. Open Razorpay Checkout with the returned subscription ID: ```typescript theme={null} await loadRazorpayCheckout(); const RazorpayCheckout = window.Razorpay; if (!RazorpayCheckout) { throw new Error('Razorpay Checkout failed to load'); } const checkout = new RazorpayCheckout({ ...subscriptionResponse.checkoutOptions, handler: async (response: { razorpay_subscription_id: string; razorpay_payment_id: string; razorpay_signature: string; }) => { const { error } = await insforge.payments.razorpay.verifySubscription('test', { subscriptionId: response.razorpay_subscription_id, paymentId: response.razorpay_payment_id, signature: response.razorpay_signature }); if (error) throw error; } }); checkout.open(); ``` ## Manage subscriptions Razorpay does not provide a Stripe Billing Portal equivalent. Use backend routes for subscription management: ```typescript theme={null} await insforge.payments.razorpay.cancelSubscription('test', 'sub_123', { cancelAtCycleEnd: false }); await insforge.payments.razorpay.pauseSubscription('test', 'sub_123'); await insforge.payments.razorpay.resumeSubscription('test', 'sub_123'); ``` Subscription creation checks `INSERT` policies on `payments.razorpay_subscriptions`. Cancel, pause, and resume check `UPDATE` policies on the same table. PostgreSQL also applies `SELECT` policies to rows returned by `INSERT/UPDATE ... RETURNING`, so add matching `SELECT` visibility for the same billing subject when a policy probe needs to return the row. Add app-specific RLS or server-side membership checks before exposing these controls for shared subjects. ## Fulfillment Do not mark orders paid, grant credits, or activate subscriptions from the Checkout handler alone. Use verified Razorpay webhook events in `payments.webhook_events`. Do not attach fulfillment triggers to provider mirror tables such as `payments.razorpay_subscriptions`. Create app-owned fulfillment tables with RLS, then update them from webhook triggers. See [Razorpay Payments](/core-concepts/payments/razorpay) for a trigger example. ## Live/test environment Pass `'test'` as the first SDK argument while developing. Only switch to `'live'` after the developer explicitly approves production Razorpay changes and live Items, Plans, and webhooks are configured. Never put Razorpay Key Secret or webhook secret in frontend code. The frontend only receives the public Razorpay Key ID as `checkoutOptions.key`. # Stripe Payments SDK Source: https://docs.insforge.dev/sdks/typescript/payments-stripe Create Stripe Checkout and Billing Portal sessions with the InsForge TypeScript SDK ```bash npm theme={null} npm install @insforge/sdk@latest ``` ```bash yarn theme={null} yarn add @insforge/sdk@latest ``` ```bash pnpm theme={null} pnpm add @insforge/sdk@latest ``` ```javascript theme={null} import { createClient } from '@insforge/sdk'; const insforge = createClient({ baseUrl: 'https://your-app.insforge.app', anonKey: 'your-anon-key' // Optional: for public/unauthenticated requests }); ``` ## Overview The TypeScript SDK exposes Stripe runtime helpers for generated app frontends: * `insforge.payments.stripe.createCheckoutSession(...)` * `insforge.payments.stripe.createCustomerPortalSession(...)` Stripe secret keys, catalog management, webhook setup, and payment monitoring are admin operations. Configure them from the dashboard or CLI before using the SDK. ```bash theme={null} npx @insforge/cli payments stripe status npx @insforge/cli payments stripe catalog --environment test ``` See [Stripe Payments](/core-concepts/payments/stripe) for setup, database tables, webhook projections, and fulfillment patterns. The SDK methods require the current InsForge user token. Anonymous InsForge tokens can be used for guest one-time checkout, but an InsForge API key is not a substitute because the backend needs user context for local session rows. ## createCheckoutSession() Create a Stripe Checkout Session through the InsForge backend. ### Parameters | Parameter | Type | Required | Description | | ---------------- | ------------------------------------------ | -------------------------- | --------------------------------------------------------- | | first argument | `'test' \| 'live'` | Yes | Stripe environment to target | | `mode` | `'payment' \| 'subscription'` | Yes | Checkout mode | | `lineItems` | `{ priceId: string; quantity?: number }[]` | Yes | Stripe Price IDs and quantities. Quantity defaults to `1` | | `successUrl` | `string` | Yes | URL Stripe redirects to after Checkout completes | | `cancelUrl` | `string` | Yes | URL Stripe redirects to if the customer cancels | | `subject` | `{ type: string; id: string }` | Required for subscriptions | App-defined billing owner | | `customerEmail` | `string \| null` | No | Email hint for Checkout customer creation | | `metadata` | `Record` | No | App metadata copied to Stripe Checkout | | `idempotencyKey` | `string` | No | Stable key for retry-safe Checkout creation | Metadata keys starting with `insforge_` are reserved. ### Returns ```typescript theme={null} { data: { checkoutSession: { id: string environment: 'test' | 'live' mode: 'payment' | 'subscription' status: 'initialized' | 'open' | 'completed' | 'expired' | 'failed' paymentStatus: 'paid' | 'unpaid' | 'no_payment_required' | null subjectType: string | null subjectId: string | null customerEmail: string | null checkoutSessionId: string | null customerId: string | null paymentIntentId: string | null subscriptionId: string | null url: string | null lastError: string | null createdAt: string updatedAt: string } } | null, error: Error | null } ``` ### One-time checkout ```typescript theme={null} const { data, error } = await insforge.payments.stripe.createCheckoutSession('test', { mode: 'payment', lineItems: [{ priceId: 'price_123', quantity: 1 }], successUrl: `${window.location.origin}/checkout/success`, cancelUrl: `${window.location.origin}/pricing`, customerEmail: user?.email ?? null, metadata: { order_id: orderId }, idempotencyKey: `order:${orderId}` }); if (error) { console.error('Checkout failed:', error.message); return; } if (data?.checkoutSession.url) { window.location.assign(data.checkoutSession.url); } ``` For anonymous one-time purchases, omit `subject` and pass `customerEmail` when available. If one-time checkout includes a `subject` and there is no existing Stripe customer mapping yet, InsForge lets Stripe create the customer during Checkout and backfills the subject mapping from the completion webhook. ### Subscription checkout ```typescript theme={null} const { data, error } = await insforge.payments.stripe.createCheckoutSession('test', { mode: 'subscription', subject: { type: 'team', id: teamId }, lineItems: [{ priceId: 'price_monthly_123', quantity: 1 }], successUrl: `${window.location.origin}/billing/success`, cancelUrl: `${window.location.origin}/billing`, customerEmail: user.email, idempotencyKey: `team:${teamId}:pro-monthly` }); if (error) throw error; if (data?.checkoutSession.url) { window.location.assign(data.checkoutSession.url); } ``` Subscription checkout requires `subject` because recurring access belongs to an app-defined billing owner, such as a user, team, organization, workspace, tenant, or group. Do not treat the success URL as proof of payment. Use verified webhook events to fulfill orders and grant subscription access. ## createCustomerPortalSession() Create a Stripe Billing Portal Session for a mapped billing subject. ### Parameters | Parameter | Type | Required | Description | | --------------- | ------------------------------ | -------- | ----------------------------------------------------------- | | first argument | `'test' \| 'live'` | Yes | Stripe environment to target | | `subject` | `{ type: string; id: string }` | Yes | App-defined billing owner | | `returnUrl` | `string` | No | URL Stripe redirects to when the customer leaves the portal | | `configuration` | `string` | No | Stripe Billing Portal configuration ID | ### Returns ```typescript theme={null} { data: { customerPortalSession: { id: string environment: 'test' | 'live' status: 'initialized' | 'created' | 'failed' subjectType: string subjectId: string customerId: string | null returnUrl: string | null configuration: string | null url: string | null lastError: string | null createdAt: string updatedAt: string } } | null, error: Error | null } ``` ### Example ```typescript theme={null} const { data, error } = await insforge.payments.stripe.createCustomerPortalSession('test', { subject: { type: 'team', id: teamId }, returnUrl: `${window.location.origin}/billing` }); if (error) { if ('statusCode' in error && error.statusCode === 404) { return; } throw error; } if (data?.customerPortalSession.url) { window.location.assign(data.customerPortalSession.url); } ``` Customer portal sessions require an authenticated user and an existing customer mapping for the subject. The mapping is usually created after a Checkout Session completes and Stripe returns a customer. ## Authorization and RLS The SDK methods call runtime routes using the current InsForge token. The backend inserts local session rows using the caller context: * `payments.stripe_checkout_sessions` for Checkout attempts * `payments.stripe_customer_portal_sessions` for Billing Portal attempts If users can pass shared subjects such as teams or organizations, add an authorization boundary before exposing checkout, subscription, or customer portal UI. For example, enable RLS on the Stripe runtime tables with policies that check team membership, or call Payments through your own server endpoint that checks membership first. PostgreSQL applies `SELECT` policies to rows returned by `INSERT ... RETURNING` and to idempotent retry lookups. If an RLS error appears even though an `INSERT` policy exists, add a matching `SELECT` policy for the same billing subject and idempotency key. Do not let users submit arbitrary `subject.type` and `subject.id` values unless your app checks that they can manage that billing subject. ## Reading payment state The Payments SDK does not expose generic end-user reads for `payments.customers`, `payments.stripe_subscriptions`, or `payments.transactions`. Those tables are operational records used for dashboard visibility and reporting. For user-facing billing state, create app-owned tables with RLS and populate them from provider webhook event triggers: * `public.orders` * `public.credit_ledger` * `public.user_entitlements` * `public.team_billing_status` See [Stripe Payments](/core-concepts/payments/stripe) for fulfillment examples. ## Live/test environment Pass `'test'` as the first SDK argument while developing. Only switch to `'live'` after the developer explicitly approves production Stripe changes and live Prices are configured. Never put Stripe secret keys in frontend code or public deployment environment variables. Configure Stripe keys through the InsForge dashboard or CLI. # Realtime SDK Reference Source: https://docs.insforge.dev/sdks/typescript/realtime Subscribe to channels, publish events, and track presence with the InsForge TypeScript SDK. ## Installation ```bash npm theme={null} npm install @insforge/sdk@latest ``` ```bash yarn theme={null} yarn add @insforge/sdk@latest ``` ```bash pnpm theme={null} pnpm add @insforge/sdk@latest ``` ```javascript theme={null} import { createClient } from '@insforge/sdk'; const insforge = createClient({ baseUrl: 'https://your-app.insforge.app', anonKey: 'your-anon-key' // Optional: for public/unauthenticated requests }); ``` ## Mental Model The TypeScript SDK opens one Socket.IO connection to your InsForge backend. You subscribe to named channels, listen for event names, and optionally publish events back to channels you have joined. Events can come from two places: * Database triggers that call `realtime.publish(channel, event, payload)`. * Clients that call `insforge.realtime.publish(channel, event, payload)`. For the backend channel and RLS model, see [Realtime overview](/core-concepts/realtime/overview). ## Quick Start ```typescript theme={null} import { createClient } from '@insforge/sdk'; const insforge = createClient({ baseUrl: 'https://your-app.insforge.app', anonKey: 'your-anon-key' }); insforge.realtime.on('connect', () => { console.log('Connected:', insforge.realtime.socketId); }); insforge.realtime.on('error', (error) => { console.error(error.code, error.message); }); await insforge.realtime.connect(); const subscription = await insforge.realtime.subscribe('order:123'); if (!subscription.ok) { throw new Error(subscription.error.message); } insforge.realtime.on('status_changed', (message) => { console.log(message.status); console.log(message.meta.messageId); }); ``` Register `connect`, `disconnect`, `connect_error`, and `error` handlers before calling `connect()` so early connection failures are visible. ## connect() Establish a WebSocket connection. ```typescript theme={null} await insforge.realtime.connect(); ``` Returns: ```typescript theme={null} Promise ``` Notes: * The SDK includes the current auth token when one exists. If there is no signed-in user, it can use the configured anon key. * Multiple `connect()` calls while a connection is already in progress reuse the same connection promise. * The connection attempt times out after 10 seconds. ## subscribe() Subscribe to a channel and receive the current presence snapshot. ```typescript theme={null} const response = await insforge.realtime.subscribe('chat:room-1'); if (response.ok) { console.log(response.channel); console.log(response.presence.members); } else { console.error(response.error.code, response.error.message); } ``` Parameters: | Parameter | Type | Description | | --------- | -------- | ----------------------------------------------------------------------- | | `channel` | `string` | Resolved channel name, such as `orders`, `order:123`, or `chat:room-1`. | Returns: ```typescript theme={null} type SubscribeResponse = | { ok: true; channel: string; presence: { members: PresenceMember[]; }; } | { ok: false; channel: string; error: { code: string; message: string; }; }; ``` `subscribe()` auto-connects if needed. Calling `connect()` explicitly is still recommended so connection event handlers are already attached. ## publish() Publish an event to a channel. ```typescript theme={null} await insforge.realtime.publish('chat:room-1', 'new_message', { text: 'Hello', sentAt: new Date().toISOString() }); ``` Parameters: | Parameter | Type | Description | | --------- | ------------------------- | ------------------------------------------------------------- | | `channel` | `string` | Channel to publish to. The client must already be subscribed. | | `event` | `string` | Event name that subscribers listen for. | | `payload` | `Record` | JSON-serializable message payload. | Publishing requires a prior successful subscription to the same channel. If RLS is enabled on `realtime.messages`, publish is also checked against `INSERT` policies. Publish failures are emitted through the `error` event. ## on() Listen for custom events, connection events, presence events, and realtime errors. ```typescript theme={null} insforge.realtime.on('new_message', (message) => { console.log(message.text); console.log(message.meta.senderType); }); ``` Reserved events: | Event | Payload | Description | | ---------------- | ---------------------- | --------------------------------------------------- | | `connect` | None | The WebSocket connected. | | `connect_error` | `Error` | Initial connection or reconnect failed. | | `disconnect` | `string` | The WebSocket disconnected. | | `error` | `RealtimeErrorPayload` | Subscribe or publish failed. | | `presence:join` | `PresenceJoinMessage` | A logical member became present in a channel. | | `presence:leave` | `PresenceLeaveMessage` | A logical member is no longer present in a channel. | ## once() Listen for an event once, then remove the listener automatically. ```typescript theme={null} insforge.realtime.once('checkout_completed', (message) => { console.log('Completed:', message.orderId); }); ``` ## off() Remove an event listener. ```typescript theme={null} function handleStatus(message: OrderStatusMessage) { console.log(message.status); } insforge.realtime.on('status_changed', handleStatus); insforge.realtime.off('status_changed', handleStatus); ``` ## unsubscribe() Leave a channel. ```typescript theme={null} insforge.realtime.unsubscribe('chat:room-1'); ``` `unsubscribe()` is fire-and-forget. If this was the final socket for a logical member, other subscribers receive `presence:leave`. ## disconnect() Close the WebSocket and clear local subscriptions. ```typescript theme={null} insforge.realtime.disconnect(); ``` ## Message Shape Delivered messages include your payload fields plus server metadata. ```typescript theme={null} import type { SocketMessage } from '@insforge/sdk'; interface OrderStatusMessage extends SocketMessage { id: string; status: string; } insforge.realtime.on('status_changed', (message) => { console.log(message.id); console.log(message.status); console.log(message.meta.messageId); console.log(message.meta.senderType); console.log(message.meta.senderId); console.log(message.meta.timestamp); }); ``` Metadata: ```typescript theme={null} interface SocketMessageMeta { channel?: string; messageId: string; senderType: 'system' | 'user'; senderId?: string; timestamp: string; } ``` `senderType` is `system` for database-triggered messages and `user` for client-published messages. ## Presence A successful subscription returns the current presence snapshot. ```typescript theme={null} const response = await insforge.realtime.subscribe('chat:room-1'); if (response.ok) { for (const member of response.presence.members) { console.log(member.type, member.presenceId, member.joinedAt); } } ``` Presence member: ```typescript theme={null} type PresenceMember = | { type: 'user'; presenceId: string; joinedAt: string; } | { type: 'anonymous'; presenceId: string; joinedAt: string; }; ``` Listen for changes: ```typescript theme={null} insforge.realtime.on('presence:join', (message) => { console.log('Joined:', message.member.presenceId); }); insforge.realtime.on('presence:leave', (message) => { console.log('Left:', message.member.presenceId); }); ``` ## Properties | Property | Type | Description | | ------------------------- | ----------------------------------------------- | ------------------------------------------ | | `isConnected` | `boolean` | Whether the socket is currently connected. | | `connectionState` | `'disconnected' \| 'connecting' \| 'connected'` | Current SDK connection state. | | `socketId` | `string \| undefined` | Socket.IO ID when connected. | | `getSubscribedChannels()` | `() => string[]` | Local list of subscribed channels. | ## Error Handling ```typescript theme={null} insforge.realtime.on('connect_error', (error) => { console.error('Connection failed:', error.message); }); insforge.realtime.on('disconnect', (reason) => { console.log('Disconnected:', reason); }); insforge.realtime.on('error', (error) => { console.error(error.channel, error.code, error.message); }); ``` Common realtime error codes: | Code | Meaning | | ------------------------- | -------------------------------------------------------------- | | `REALTIME_UNAUTHORIZED` | RLS or channel matching denied subscribe or publish. | | `REALTIME_NOT_SUBSCRIBED` | The client tried to publish before subscribing to the channel. | | `INTERNAL_ERROR` | The backend could not complete the realtime operation. | ## Complete Example ```typescript theme={null} const channel = `order:${orderId}`; insforge.realtime.on('error', (error) => { console.error(error.code, error.message); }); await insforge.realtime.connect(); const response = await insforge.realtime.subscribe(channel); if (!response.ok) { throw new Error(response.error.message); } insforge.realtime.on('status_changed', (message) => { renderOrderStatus(message.status); }); await insforge.realtime.publish(channel, 'customer_viewed', { orderId, viewedAt: new Date().toISOString() }); function cleanup() { insforge.realtime.unsubscribe(channel); insforge.realtime.disconnect(); } ``` # Storage SDK Reference Source: https://docs.insforge.dev/sdks/typescript/storage File upload, download, and management with the InsForge TypeScript SDK ## Installation ```bash npm theme={null} npm install @insforge/sdk@latest ``` ```bash yarn theme={null} yarn add @insforge/sdk@latest ``` ```bash pnpm theme={null} pnpm add @insforge/sdk@latest ``` ```javascript theme={null} import { createClient } from '@insforge/sdk'; const insforge = createClient({ baseUrl: 'https://your-app.insforge.app', anonKey: 'your-anon-key' // Optional: for public/unauthenticated requests }); ``` ## from() Get a bucket instance for file operations. ### Parameters * `bucketName` (string, required) - Name of the storage bucket ### Returns ```typescript theme={null} StorageBucket // Instance with upload, uploadAuto, download, remove methods ``` ### Example ```javascript theme={null} const bucket = insforge.storage.from('images') ``` *** ## upload() Upload a file with a specific path/key. ### Parameters * `path` (string, required) - The object key/path for the file * `file` (File | Blob, required) - File or Blob to upload ### Returns ```typescript theme={null} { data: { bucket: string, key: string, size: number, mimeType: string, uploadedAt: string, url: string } | null, error: Error | null } ``` If a file with the same key exists, backend auto-renames it. Always use the returned `key` and `url`. ### Example ```javascript theme={null} const { data, error } = await insforge.storage .from('images') .upload('posts/post-123/cover.jpg', fileObject) // Save BOTH url and key to database await insforge.database .from('posts') .update({ image_url: data.url, image_key: data.key // Save key for download/delete operations }) .eq('id', 'post-123') ``` ### Output ```json theme={null} { "data": { "bucket": "images", "key": "posts/post-123/cover.jpg", "size": 45678, "mimeType": "image/jpeg", "uploadedAt": "2024-01-15T10:30:00Z", "url": "https://your-app.region.insforge.app/api/storage/buckets/images/objects/posts%2Fpost-123%2Fcover.jpg" }, "error": null } ``` *** ## uploadAuto() Upload a file with auto-generated unique key. ### Parameters * `file` (File | Blob, required) - File or Blob to upload ### Returns ```typescript theme={null} { data: { bucket: string, key: string, size: number, mimeType: string, uploadedAt: string, url: string } | null, error: Error | null } ``` ### Example ```javascript theme={null} const { data, error } = await insforge.storage .from('uploads') .uploadAuto(fileObject) // Save url and key to database await insforge.database .from('posts') .insert([{ image_url: data.url, image_key: data.key, // Save key for download/delete operations user_id: userId }]) ``` ### Output ```json theme={null} { "data": { "bucket": "uploads", "key": "myfile-1705315200000-abc123.jpg", "size": 45678, "mimeType": "image/jpeg", "uploadedAt": "2024-01-15T10:30:00Z", "url": "https://your-app.region.insforge.app/api/storage/buckets/uploads/objects/myfile-1705315200000-abc123.jpg" }, "error": null } ``` *** ## download() Download a file as Blob. ### Parameters * `path` (string, required) - The object key/path to download ### Returns ```typescript theme={null} { data: Blob | null, error: Error | null } ``` ### Example ```javascript theme={null} // 1. Get the file key from your database const { data: post, error: dbError } = await insforge.database .from('posts') .select('image_key') .eq('id', 'post-123') .single() // 2. Download the file using the key const { data: blob, error } = await insforge.storage .from('images') .download(post.image_key) // 3. Create download link or display image const url = URL.createObjectURL(blob) const img = document.querySelector('img') img.src = url ``` ### Output ```json theme={null} { "data": "Blob { size: 45678, type: 'image/jpeg' }", "error": null } ``` *** ## remove() Delete a file from storage. ### Parameters * `path` (string, required) - The object key/path to delete ### Returns ```typescript theme={null} { data: { message: string } | null, error: Error | null } ``` ### Example ```javascript theme={null} // 1. Get the file key from your database const { data: post, error: dbError } = await insforge.database .from('posts') .select('image_key') .eq('id', 'post-123') .single() // 2. Delete the file from storage const { data, error } = await insforge.storage .from('images') .remove(post.image_key) // 3. Clear the database reference await insforge.database .from('posts') .update({ image_url: null, image_key: null }) .eq('id', 'post-123') ``` ### Output ```json theme={null} { "data": { "message": "Object deleted successfully" }, "error": null } ``` # Showcase Source: https://docs.insforge.dev/showcase Production applications built with InsForge See what developers are building with InsForge - from AI-powered SaaS to social platforms and beyond. ## Community Projects
National Flags Leaderboard showcase
Learn national flags through an interactive database and take on challenges with real-time leaderboard rankings.
Pokemon Vibe showcase
Form your own Pokemon teams and compete against others. AI-powered battles let two teams fight on top of AI-generated drawings.
2048 Arena showcase
Classic 2048 puzzle game with multiplayer arena and competitive leaderboards.
Line Connect Race showcase
Interactive line-drawing racing game with real-time multiplayer and competitive gameplay.