osgarden API Reference
The osgarden gateway is the single authority for every app request: it authenticates the caller, compiles and enforces data access, meters usage, and attributes cost to a payer. This reference documents the HTTP API it exposes and the TypeScript SDK that wraps it.
Model in one line: developers host open-source apps for free; end users pay their own direct usage costs; the app holds no secrets. Every operation is authenticated, access-checked, and metered by the gateway.
Contents#
| Doc | Covers | Status |
|---|---|---|
| authentication.md | OAuth/PKCE sign-in, sessions, consent | ✅ Available |
| database.md | /v1/db — scoped, metered CRUD; scopes, shared tables, federated queries | ✅ Available |
| resources.md | /v1/resources — resources & consensual membership | ✅ Available |
| ai.md | /v1/ai/chat — metered LLM passthrough | ✅ Available |
| billing.md | /v1/wallet, /v1/apps, top-up, Stripe webhook | ✅ Available |
| files.md | /v1/files — object storage on both scopes | ✅ Available |
| users.md | /v1/users — profiles, author(...), account deletion | ✅ Available |
| schema-and-deploy.md | defineSchema DSL, the /deploy endpoint, the osgarden CLI | ✅ Available |
| sdk.md | The @osgarden/sdk TypeScript client | ✅ Available |
| metering.md | Rate card: payer, rate, and mechanism per surface | ✅ Available |
| not-yet-implemented.md | Visitors, realtime, functions, jobs | 📋 Planned |
Status legend#
- ✅ Available — implemented and covered by tests.
- 🚧 Partial — implemented with a documented limitation.
- 📋 Planned — designed (see the
docs/design/notes) but not yet in code.
Base URL & versioning#
All endpoints are served from the gateway origin (for local development,
http://localhost:3000).
- App API is versioned under
/v1/*and always requires authentication. - Auth (
/auth/*), deploy (/deploy), and the Stripe webhook (/stripe/webhook) sit outside/v1and have their own auth schemes. GET /health→{ "ok": true }(unauthenticated liveness check).
Requests and responses are JSON (Content-Type: application/json) unless noted
(the AI streaming response is text/event-stream; the Stripe webhook takes a raw
signed body).
Authentication#
Every /v1/* request must present one of:
- Session cookie (browser SDK). The gateway sets
httpOnlycookies during sign-in; the app never sees the token. Cookie-authed requests must also send the headerX-Osgarden-Csrf: 1— browsers only attach custom headers after a CORS preflight, which makes cookie-bearing state changes CSRF-proof. Requests usecredentials: include. - Bearer token (server-to-server):
Authorization: Bearer <access_token>. No CSRF header is required for bearer auth.
The gateway validates the token against Ory (introspection when ORY_API_KEY is set —
which also yields the app/client id used for per-app budgets — otherwise the public
userinfo endpoint). See authentication.md.
Errors#
Errors are JSON with a stable machine-readable error code and a human detail:
{ "error": "forbidden", "detail": "not permitted to create \"messages\"" }Data-engine (/v1/db, /v1/resources) error codes and their HTTP statuses:
error | Status | Meaning |
|---|---|---|
invalid_request | 400 | Malformed request, unknown column, unbounded destructive op, unsupported op for the scope. |
resource_required | 400 | The operation needs a resource scope. |
forbidden | 403 | Authenticated but not permitted (access rule denied). |
not_found | 404 | No such table/resource — also returned to hide the existence of a members-only resource from non-members. |
conflict | 409 | Constraint violation, or single() matched more than one row. |
Cross-cutting statuses:
| Status | error | Where |
|---|---|---|
| 401 | missing credentials / invalid or expired token | Any /v1/* without valid auth. |
| 403 | missing csrf header | Cookie-authed request missing X-Osgarden-Csrf. |
| 402 | insufficient_balance / spend_cap_exceeded | Metered ops when the wallet can't cover the escrow hold, or the app hit its per-app monthly cap. |
| 502 | upstream error | A dependency (e.g. the model provider) failed; you owe nothing. |
| 503 | *_not_configured | A required server dependency (OpenRouter, Stripe, deploy token) is unset. |
The SDK rethrows all of these as OsgardenError with { message, status, data }.
Billing & metering (how cost attaches)#
- AI and other high-value ops use per-op escrow: the gateway places a hold that upper-bounds the cost before calling upstream, then captures the actual amount. A request that can't be covered fails 402 before any spend.
- Database ops are sub-threshold, so they use accumulate-and-flush: usage is
summed per
(payer, app)and written to the ledger as one transaction when it crosses a threshold or the flush interval fires. - Every op resolves a payer. For private data it's the acting user. For resource
data it follows the resource's
payerpolicy (actor/owner); shared (creator-owned) rows always bill their creator. See database.md and schema-and-deploy.md. - Users protect themselves with a per-app monthly spend cap (billing.md).
Known limitations in shipped areas#
These are implemented paths with a deliberate, temporary simplification:
Top-ups are disabled in production (preview mode).
POST /v1/wallet/topupreturns503 topups_disabledand records the attempt intopup_interest; users run on the welcome credit. See billing.md.payer: "split"currently bills the actor (true split settlement needs the app allowance ledger, which lands with visitors — planned).File at-rest cost is a one-time per-byte charge at upload (padded to amortize retention); the recurring GB-month sweep is planned. See metering.md.
File member-removal deletes the removed member's files (
remove); per-buckethide/retainfor files is future work (thehiddencolumn is already in place).Account deletion deletes resources the user owns outright; optional ownership-transfer is future work.
.url()requires a session; visitor-sponsored (signed-out) reads land with visitors.
Now shipped (previously listed here): object storage (Stage E), user profiles,
author(...), and account deletion (Stage F), plus the osgarden CLI (deploy,
typegen). See not-yet-implemented.md for the fuller roadmap.