chore: import upstream snapshot with attribution
Lint and Format Check / lint-and-format (push) Failing after 0s
Check Migrations / Check for duplicate migration numbers (push) Failing after 1s
CI Pre-merge Check / CI Pre-merge Check (push) Failing after 2m17s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:23:40 +08:00
commit 3a28426bf4
1399 changed files with 257375 additions and 0 deletions
+86
View File
@@ -0,0 +1,86 @@
---
title: "Model Gateway"
sidebarTitle: "Overview"
description: "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.
<Frame caption="One OpenAI-compatible endpoint with per-provider access, ready-to-copy code, and usage tracking.">
<img src="/images/model-gateway-overview.png" alt="InsForge dashboard Model Gateway overview showing code samples, provider chips, and usage charts" />
</Frame>
<Note>
**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.
</Note>
```mermaid
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://<project>.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
<CardGroup cols={2}>
<Card title="TypeScript SDK" icon="js" href="/sdks/typescript/ai">
Chat, stream, and embed from Node, browser, and edge runtimes.
</Card>
<Card title="Swift SDK" icon="swift" href="/sdks/swift/ai">
Native Swift AI client for iOS and macOS.
</Card>
<Card title="Kotlin SDK" icon="android" href="/sdks/kotlin/ai">
Coroutines-first AI client for Android and JVM.
</Card>
<Card title="REST API" icon="code" href="/sdks/rest/ai">
Plain HTTP AI endpoints, callable from any language.
</Card>
</CardGroup>
## 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.
+109
View File
@@ -0,0 +1,109 @@
---
title: "Analytics"
sidebarTitle: "Overview"
description: "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.
<Frame caption="Analytics dashboard: KPIs over time plus breakdowns by page, country, and device.">
<img src="/images/dashboard-analytics.png" alt="InsForge Analytics dashboard showing visitor KPIs, a visitor trend chart, and top pages, countries, and devices breakdowns" />
</Frame>
<Note>
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.
</Note>
```mermaid
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
<CardGroup cols={2}>
<Card title="PostHog product analytics" icon="chart-mixed" href="https://posthog.com/docs/product-analytics">
Events, autocapture, insights, and dashboards behind the Analytics pages.
</Card>
<Card title="PostHog session replay" icon="circle-play" href="https://posthog.com/docs/session-replay">
How recordings are captured, redacted, and played back.
</Card>
</CardGroup>
## Build with it
<CardGroup cols={2}>
<Card title="PostHog wizard" icon="wand-magic-sparkles" href="https://posthog.com/docs/libraries/wizard">
Auto-detects your framework, installs the right PostHog SDK, and adds initialization code.
</Card>
<Card title="PostHog JavaScript SDK" icon="js" href="https://posthog.com/docs/libraries/js">
Capture custom events on top of what the wizard sets up.
</Card>
<Card title="InsForge CLI" icon="terminal" href="/quickstart">
`npx @insforge/cli posthog setup` links your InsForge project to PostHog, then prints the wizard command.
</Card>
</CardGroup>
## 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.
@@ -0,0 +1,98 @@
---
title: "Authentication"
sidebarTitle: "Overview"
description: "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.
<Frame caption="Configured sign-in methods: email and password, Google, and GitHub OAuth.">
<img src="/images/authentication-methods.png" alt="InsForge dashboard Auth Methods showing email and password, Google OAuth, and GitHub OAuth" />
</Frame>
<Note>
**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.
</Note>
```mermaid
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
<CardGroup cols={2}>
<Card title="TypeScript SDK" icon="js" href="/sdks/typescript/auth">
Sign up, log in, and manage sessions from Node, browser, and edge.
</Card>
<Card title="Swift SDK" icon="swift" href="/sdks/swift/auth">
Native Swift auth client for iOS and macOS.
</Card>
<Card title="Kotlin SDK" icon="android" href="/sdks/kotlin/auth">
Coroutines-first auth client for Android and JVM.
</Card>
<Card title="REST API" icon="code" href="/sdks/rest/auth">
Plain HTTP auth endpoints, callable from any language.
</Card>
</CardGroup>
## 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.
+58
View File
@@ -0,0 +1,58 @@
---
title: "Custom Compute"
sidebarTitle: "Overview"
description: "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.
<Note>
**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.
</Note>
```mermaid
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.
+34
View File
@@ -0,0 +1,34 @@
---
title: "Database backups and restore"
description: "Create manual database backups, restore snapshots from the InsForge dashboard, and use automated daily backups on InsForge Cloud with 7-day retention."
---
Back up and restore your database from **Database → Backup & Restore** in the dashboard. InsForge Cloud also backs up your project automatically every day, so you always have a recent copy to roll back to.
Backups cover the database only — files uploaded to Storage are not included.
<Frame caption="Here is where it lives in the UI: Database → Backup & Restore.">
<img src="/images/dashboard-backup-restore.png" alt="InsForge dashboard Backup & Restore page showing manual backups and scheduled backups with a Restore button" />
</Frame>
## Scheduled backups
InsForge Cloud takes a full backup of every active project on a paid plan once a day, retained for 7 days. The **Scheduled Backups** list shows when each backup expires, and any of them can be restored at any time. Manual backups don't affect the daily schedule.
## Manual backups
Click **Create a Backup** before a risky change and optionally give it a name. The backup runs in the background; the list shows its status, size, and creation time, and lets you rename or delete it. The Free plan includes 1 manual backup slot, paid plans include 5, and manual backups are kept until you delete them.
## Restore
Click **Restore** next to any completed backup — scheduled or manual. Restoring replaces the current database with the backup: the project is offline during the restore, data created after the backup is lost, and the action can't be undone, so take a manual backup first if you're unsure.
## Self-hosting
Self-hosted deployments have the same **Backup & Restore** page for manual backups and restores. Backups are stored alongside your project's storage (local disk, or your S3 bucket when configured) and kept until you delete them. The official Docker image needs no extra setup.
## More resources
- [Database overview](/core-concepts/database/overview) for how the database is exposed to your app.
- [Database migrations](/core-concepts/database/migrations) to version schema changes instead of restoring to undo them.
- [Database branching](/agent-native/branching) to rehearse risky changes on a copy.
@@ -0,0 +1,52 @@
---
title: "Database migrations"
description: "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: `<YYYYMMDDHHmmss>_<name>.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
npx @insforge/cli login
npx @insforge/cli link
npx @insforge/cli db migrations new create-employees-table
```
Write the SQL.
```sql
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
npx @insforge/cli db migrations up --all
npx @insforge/cli db migrations list
```
Target a single file with `up <version>`, or apply everything pending up to and including a target with `up --to <version>`.
## 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.
+100
View File
@@ -0,0 +1,100 @@
---
title: "Database"
sidebarTitle: "Overview"
description: "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.
<Frame caption="The table editor: typed columns, inline editing, CSV import, and a SQL studio.">
<img src="/images/database-table-editor.png" alt="InsForge dashboard table editor showing a messages table with typed columns" />
</Frame>
<Note>
**Looking for file storage?** Use [Storage](/core-concepts/storage/overview) for images, PDFs, and other binary content. The database stores rows; storage stores objects.
</Note>
```mermaid
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
<CardGroup cols={2}>
<Card title="Migrations" icon="layer-group" href="/core-concepts/database/migrations">
Apply SQL changes in order, safely.
</Card>
<Card title="Branching" icon="code-branch" href="/agent-native/branching">
Isolated databases for preview and risky changes.
</Card>
<Card title="pgvector" icon="brain" href="/core-concepts/database/pgvector">
Vector search for embeddings.
</Card>
</CardGroup>
## Build with it
<CardGroup cols={2}>
<Card title="TypeScript SDK" icon="js" href="/sdks/typescript/database">
Typed queries, inserts, and updates from Node, browser, and edge.
</Card>
<Card title="Swift SDK" icon="swift" href="/sdks/swift/database">
Native Swift database client for iOS and macOS.
</Card>
<Card title="Kotlin SDK" icon="android" href="/sdks/kotlin/database">
Coroutines-first database client for Android and JVM.
</Card>
<Card title="REST API" icon="code" href="/sdks/rest/database">
Plain HTTP database endpoints, callable from any language.
</Card>
</CardGroup>
## 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.
+93
View File
@@ -0,0 +1,93 @@
---
title: "pgvector"
description: "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
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
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
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
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
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.
+91
View File
@@ -0,0 +1,91 @@
---
title: "Edge Functions"
sidebarTitle: "Overview"
description: "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.
<Note>
**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.
</Note>
```mermaid
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://<project>.insforge.dev/functions/<name>`. 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
<CardGroup cols={2}>
<Card title="Schedules" icon="clock" href="/core-concepts/functions/schedules">
Run a function on a cron expression instead of in response to a request.
</Card>
</CardGroup>
## Build with it
<CardGroup cols={2}>
<Card title="TypeScript SDK" icon="js" href="/sdks/typescript/functions">
Invoke and stream functions from Node, browser, and edge.
</Card>
<Card title="Swift SDK" icon="swift" href="/sdks/swift/functions">
Invoke functions from iOS and macOS apps.
</Card>
<Card title="Kotlin SDK" icon="android" href="/sdks/kotlin/functions">
Invoke functions from Android and JVM apps.
</Card>
<Card title="REST API" icon="code" href="/sdks/rest/functions">
Plain HTTP function endpoints, callable from any language.
</Card>
</CardGroup>
## 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.
@@ -0,0 +1,43 @@
---
title: "Schedules: cron-triggered functions"
description: "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
*/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
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.
@@ -0,0 +1,56 @@
---
title: Custom SMTP
description: 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**.
<Steps>
<Step title="Enable custom SMTP">
Flip the switch on the **SMTP Provider** card.
</Step>
<Step title="Enter credentials">
Host, port (`25`, `465`, `587`, `2525`), username, password, sender email, sender name. Private IPs and self-signed certs are rejected.
</Step>
<Step title="Save">
InsForge runs an SMTP handshake before persisting. Bad credentials fail fast.
</Step>
<Step title="Edit templates (optional)">
The **Email Templates** card unlocks the four auth templates.
</Step>
</Steps>
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.
+97
View File
@@ -0,0 +1,97 @@
---
title: "Messaging"
sidebarTitle: "Overview"
description: "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.
<Note>
**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.
</Note>
```mermaid
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
<CardGroup cols={3}>
<Card title="Email" icon="envelope" href="/core-concepts/messaging/custom-smtp">
Managed SMTP or bring your own provider. Templates, delivery tracking, and webhook events.
</Card>
<Card title="SMS" icon="message">
Coming soon. Same API, Twilio or Sinch on the back.
</Card>
<Card title="Push" icon="bell">
Coming soon. APNs and FCM via a single endpoint.
</Card>
</CardGroup>
## 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
<CardGroup cols={2}>
<Card title="Custom SMTP" icon="envelope" href="/core-concepts/messaging/custom-smtp">
Bring your own SMTP provider (SendGrid, Postmark, AWS SES, etc.).
</Card>
</CardGroup>
## Build with it
<CardGroup cols={2}>
<Card title="TypeScript SDK" icon="js" href="/sdks/typescript/email">
Send mail from Node, browser, and edge runtimes.
</Card>
<Card title="REST API" icon="code" href="/sdks/rest/overview">
Plain HTTP messaging endpoints, callable from any language.
</Card>
</CardGroup>
## 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.
+93
View File
@@ -0,0 +1,93 @@
---
title: "Payments"
sidebarTitle: "Overview"
description: "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.
<Frame caption="Payments dashboard: provider connection, catalog, subscriptions, and transactions.">
<img src="/images/dashboard-payments.png" alt="InsForge Payments dashboard" />
</Frame>
<Note>
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.
</Note>
## Choose a provider
<CardGroup cols={2}>
<Card title="Stripe Payments" icon="stripe" href="/core-concepts/payments/stripe">
Use Stripe Checkout, Products, Prices, Subscriptions, and Billing Portal.
</Card>
<Card title="Razorpay Payments" icon="credit-card" href="/core-concepts/payments/razorpay">
Use Razorpay Orders, Items, Plans, Subscriptions, and Razorpay Checkout.
</Card>
</CardGroup>
## 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
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.
<Warning>
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`.
</Warning>
## Build with it
<CardGroup cols={2}>
<Card title="TypeScript payments guide" icon="js" href="/sdks/typescript/payments">
Pick the Stripe or Razorpay provider module for app code.
</Card>
<Card title="REST patterns" icon="code" href="/sdks/rest/overview">
Review provider-specific Payments API routes and webhook routes.
</Card>
</CardGroup>
## 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`.
+177
View File
@@ -0,0 +1,177 @@
---
title: "Razorpay Payments"
sidebarTitle: "Razorpay"
description: "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
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
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
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
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
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
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)
+210
View File
@@ -0,0 +1,210 @@
---
title: "Stripe Payments"
sidebarTitle: "Stripe"
description: "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
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
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
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
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
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
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)
+188
View File
@@ -0,0 +1,188 @@
---
title: "Realtime"
sidebarTitle: "Overview"
description: "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.
<Frame caption="Realtime dashboard: channel patterns, message history, permissions, and retention settings.">
<img src="/images/dashboard-realtime.png" alt="InsForge Realtime dashboard" />
</Frame>
<Note>
**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.
</Note>
```mermaid
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
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
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
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
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:<id>` channels only when the order belongs to them:
```sql
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
<CardGroup cols={2}>
<Card title="TypeScript SDK" icon="js" href="/sdks/typescript/realtime">
Subscribe to channels, publish events, and track presence from Node, browser, and edge.
</Card>
<Card title="Swift SDK" icon="swift" href="/sdks/swift/realtime">
Native Swift realtime client for iOS and macOS.
</Card>
<Card title="Kotlin SDK" icon="android" href="/sdks/kotlin/realtime">
Coroutines-first realtime client for Android and JVM.
</Card>
<Card title="REST and WebSocket API" icon="code" href="/sdks/rest/realtime">
Use the raw Socket.IO contract from any language.
</Card>
</CardGroup>
## 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.
+93
View File
@@ -0,0 +1,93 @@
---
title: "Sites"
sidebarTitle: "Overview"
description: "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.
<Frame caption="Sites dashboard: status, domains, env vars, and deployment history.">
<img src="/images/dashboard-sites.png" alt="InsForge Sites dashboard" />
</Frame>
<Note>
**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.
</Note>
```mermaid
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
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
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
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://<appkey>.insforge.site`. You can also set an InsForge-managed slug at `https://<slug>.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
<CardGroup cols={2}>
<Card title="CLI quickstart" icon="terminal" href="/quickstart">
Connect your project and run InsForge CLI commands from your app directory.
</Card>
</CardGroup>
## 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`.
+95
View File
@@ -0,0 +1,95 @@
---
title: "Storage"
sidebarTitle: "Overview"
description: "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.
<Frame caption="The storage browser: buckets, file listing, and uploads, all behind the same RLS as the database.">
<img src="/images/storage-browser.png" alt="InsForge dashboard storage browser showing a photos bucket and the file listing" />
</Frame>
<Note>
**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.
</Note>
```mermaid
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
<CardGroup cols={2}>
<Card title="S3 compatibility" icon="bucket" href="/core-concepts/storage/s3-compatibility">
Point any S3 client at your project's bucket with native credentials.
</Card>
</CardGroup>
## Build with it
<CardGroup cols={2}>
<Card title="TypeScript SDK" icon="js" href="/sdks/typescript/storage">
Upload, download, list, and manage objects from Node, browser, and edge.
</Card>
<Card title="Swift SDK" icon="swift" href="/sdks/swift/storage">
Native Swift storage client for iOS and macOS.
</Card>
<Card title="Kotlin SDK" icon="android" href="/sdks/kotlin/storage">
Coroutines-first storage client for Android and JVM.
</Card>
<Card title="REST API" icon="code" href="/sdks/rest/storage">
Plain HTTP storage endpoints, callable from any language.
</Card>
</CardGroup>
## 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.
@@ -0,0 +1,60 @@
---
title: S3-compatible gateway
description: 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
# ~/.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
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
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.
+114
View File
@@ -0,0 +1,114 @@
---
title: "Web Scraper overview: Apify integration for InsForge"
sidebarTitle: "Overview"
description: "Connect Apify to InsForge to let your coding agent run scrapers on demand, manage actors and runs, and land scraped datasets in your app."
---
Use InsForge Web Scraper to give your coding agent live access to external data: connect your own Apify account once, and your agent can run scrapers (Apify calls them actors) on demand, while the dashboard shows your actors, run history, and scraped datasets without leaving InsForge.
Connect Apify with one click, then paste the scrape prompt into your coding agent. The agent authenticates with your InsForge-managed Apify token, picks the right actor for the job, and returns the results.
<Frame caption="Web Scraper dashboard: connected actors with their last run and total run counts.">
<img src="/images/dashboard-webscraper.png" alt="InsForge Web Scraper dashboard showing the Actors tab with connected Apify actors, their last run times, and run counts" />
</Frame>
<Note>
Apify remains the source of truth for actors, runs, and datasets. InsForge surfaces a focused subset for everyday checks, then deep-links into the Apify console for anything beyond it. The Web Scraper integration is available on InsForge Cloud; self-hosted deployments return `501 Not Implemented` on these routes.
</Note>
```mermaid
flowchart TB
Agent["Coding Agent"] --> Skill["Apify Skill + CLI"]
Admin["Dashboard"] --> ScraperAPI["Web Scraper API"]
Skill --> Apify["Apify"]
ScraperAPI --> Apify
Apify --> Actors["Actors"]
Apify --> Runs["Runs"]
Apify --> Datasets["Datasets"]
Actors --> Pages["Web Scraper pages"]
Runs --> Pages
Datasets --> Pages
style Admin fill:#1e293b,stroke:#475569,color:#e2e8f0
style Agent fill:#1e293b,stroke:#475569,color:#e2e8f0
style Skill fill:#1e40af,stroke:#3b82f6,color:#dbeafe
style ScraperAPI fill:#166534,stroke:#22c55e,color:#dcfce7
style Apify fill:#c2410c,stroke:#fb923c,color:#fed7aa
style Actors fill:#0e7490,stroke:#06b6d4,color:#cffafe
style Runs fill:#0e7490,stroke:#06b6d4,color:#cffafe
style Datasets fill:#0e7490,stroke:#06b6d4,color:#cffafe
style Pages fill:#6b21a8,stroke:#a855f7,color:#f3e8ff
```
## Features
### One-click Apify connection
Connect Apify from the Web Scraper page in the dashboard. InsForge walks you through the Apify OAuth flow, stores credentials server-side, and keeps the access token refreshed for you. The raw token never lands in your repo or your frontend; agents and functions fetch a live token from the backend when they need one.
### Scrape via your coding agent
After connecting, the empty state ships a scrape prompt you can paste into your coding agent:
```
Use the insforge webscraper apify skill to scrape <what you want> and return the results.
```
Behind the prompt, `npx @insforge/cli webscraper apify login` fetches your InsForge-managed Apify token, authenticates the local Apify CLI headlessly (no browser OAuth), and installs the Apify agent skills. From there the agent picks an actor from the Apify Store, starts runs, and reads the results back.
### Actors
The actors you have used or created recently, with their last run time and total run count. Each row deep-links into the Apify console for full actor configuration.
### Runs
Recent scraper executions with status (succeeded, failed, running), start time, and cost in USD. Useful for a quick "did last night's scrape work and what did it cost" check without opening Apify.
### Dataset
Datasets produced by your runs, with item counts, creation time, and the actor that produced them. Deep-links into Apify storage where you can inspect or export the items.
### Landing scraped data in your database
Scraped results live in Apify datasets by default; nothing is written to your project's Postgres unless you want it there. For small scrapes, your agent can just return the results. For anything you want to keep or refresh on a schedule, have the agent deploy an [edge function](/core-concepts/functions/overview) or [compute service](/core-concepts/compute/overview) that fetches the dataset from Apify and upserts rows into a table.
### Settings and disconnect
The Web Scraper Config dialog (the gear icon in the sidebar) shows the connected Apify account, plan, and data retention, links into the Apify console, and lets admins disconnect. Disconnecting only stops InsForge from using your Apify credentials; your Apify account, actors, and datasets stay intact, and you can reconnect anytime.
## Concepts
<CardGroup cols={2}>
<Card title="Apify actors" icon="robot" href="https://docs.apify.com/platform/actors">
The serverless scrapers behind every run, from ready-made Store actors to your own.
</Card>
<Card title="Apify storage" icon="database" href="https://docs.apify.com/platform/storage/dataset">
How datasets store scraped items and how to export or fetch them via API.
</Card>
</CardGroup>
## Build with it
<CardGroup cols={2}>
<Card title="InsForge CLI" icon="terminal" href="/quickstart">
`npx @insforge/cli webscraper apify connect` links your project to Apify, then logs your local agent in.
</Card>
<Card title="Apify Store" icon="store" href="https://apify.com/store">
Thousands of ready-made actors for common targets, from Google Maps to social platforms.
</Card>
<Card title="Apify API client" icon="js" href="https://docs.apify.com/api/client/js/">
Call actors and read datasets from your edge functions or compute services.
</Card>
</CardGroup>
## Next steps
- Open the Web Scraper page in the dashboard and click **Connect Apify**.
- Paste the scrape prompt into your coding agent and tell it what you want to scrape.
- When a scrape is worth keeping, ask your agent to land the dataset in a table via an [edge function](/core-concepts/functions/overview) or a [schedule](/core-concepts/functions/schedules).