API Endpoints ​

All endpoints are under /api/v1/. Table routes are at /api/v1/table/{table}/.

For JavaScript examples of every operation, see the Frontend Guide. Your running server also has interactive docs at /api/v1/doc/ui (Swagger UI).

Database Level ​

EndpointMethodDescription
/healthGETHealth check
/docGETOpenAPI 3.1.0 JSON spec (requires OpenApiExtension; default in teeny init)
/doc/uiGETSwagger UI (requires OpenApiExtension; default in teeny init)
/pocket/GETAdmin panel UI (requires PocketUIExtension; default in teeny init)
/settingsGETDatabase settings (admin)
/setup-dbPOSTInitialize database (superadmin)
/migrationsGET/POSTList or apply migrations (superadmin)
/files/{table}/{rid}/{path}GETDownload file from R2
/action/{name}POSTExecute an action
/auth/logoutPOSTClear the auth cookie (top-level; distinct from per-table /table/{t}/auth/logout)
/explain/*GETAdmin dry-run for any API route. Requires admin auth + RESPOND_WITH_QUERY_LOG env var enabled. Routes the path under /explain/ as if executing but in dryRunMode, returning {logs, result} where logs is the captured SQL/storage actions. Used by teeny exec --explain.

Table CRUD ​

All CRUD endpoints respect row-level security rules defined in your config.

INSERT — POST /table/{t}/insert ​

json
{
  "values": { "title": "Hello", "body": "World" },
  "returning": "*"
}
FieldTypeRequiredDescription
valuesobject or object[]YesField values. Array for batch insert; every row must set the same fields (use null where a row has no value), otherwise 400 naming the row and field.
returningstring or string[]NoFields to return. "*" for all. Without this, response is empty.
orstringNoConflict handling: "IGNORE", "REPLACE", "ABORT", "FAIL", "ROLLBACK". "REPLACE" is admin-only on tables with rules (see below).

Common mistake: Sending {"title": "x"} without the values wrapper: the request fails with 400 Error parsing INSERT data (with RESPOND_WITH_ERRORS, the detail says Insert query must have either values or expr).

SELECT — GET/POST /table/{t}/select ​

GET with query params:

/table/{t}/select?where=published == true&order=-created&limit=10&select=id,title

POST with JSON body:

json
{
  "where": "published == true",
  "order": "-created",
  "limit": 10,
  "select": "id,title"
}
FieldTypeRequiredDescription
wherestringNoFilter expression (e.g., author_id == 'abc', published == true & title != null)
orderstring or string[]NoSort: "-created" (DESC), "+title" (ASC), "created DESC". Comma-separated or array for multiple.
sortstring or string[]NoAlias for order (common API convention)
limitnumberNoMax records to return
offsetnumberNoSkip N records (pagination)
selectstring or string[]NoFields to return: "id,title,created" or ["id", "title"]
distinctbooleanNoDeduplicate results
groupstring or string[]NoGroup by fields

Returns: array of records.

LIST — GET/POST /table/{t}/list ​

Same parameters as SELECT. Returns { items: [...], total: number }.

VIEW — GET /table/{t}/view/{id} ​

No body. Returns single record or 404.

Optional query params: select (field filter), where (additional filter).

UPDATE — POST /table/{t}/update ​

json
{
  "where": "author_id == 'abc123'",
  "setValues": { "published": true },
  "returning": "*"
}
FieldTypeRequiredDescription
wherestringYesFilter expression — which rows to update
setValuesobjectNoField→value pairs
setobjectNoField→SQL expression pairs (e.g., { "count": "count + 1" })
returningstring or string[]NoFields to return
orstringNoConflict handling, same values as INSERT. "REPLACE" is admin-only on tables with rules.

REPLACE and rules: SQLite resolves an OR REPLACE conflict (primary key or unique column) by deleting the existing row, and that delete never goes through deleteRule or updateRule. So on a table with a rules extension, a non-admin request that uses REPLACE (including EDIT with or=INSERT) gets 403, unless deleteRule is 'true' (no trigger on that event, never on auth tables). Admin requests and public: true tables are not affected. For a rule-safe "create or update", see User Preferences (Upsert).

EDIT — POST /table/{t}/edit/{id} ​

json
{ "title": "Updated Title" }

Body is bare fields — NOT wrapped in setValues. The endpoint wraps it internally.

ParamWhereDescription
Body fieldsJSON bodyField→value pairs to update
returningQuery paramFields to return (defaults to UID field)
orQuery param"INSERT": insert the record with this ID if it doesn't exist, replace it if it does (INSERT OR REPLACE). Admin-only on tables with rules unless deleteRule is 'true' (no trigger on that event, never on auth tables) (403 otherwise).

DELETE — POST /table/{t}/delete ​

json
{
  "where": "id == 'abc123'",
  "returning": "*"
}
FieldTypeRequiredDescription
wherestringYesFilter expression — which rows to delete
returningstring or string[]NoReturn deleted records

Authentication ​

Available when a table has the auth extension. See the Frontend Guide for complete auth flow examples.

Sign Up — POST /table/{t}/auth/sign-up ​

json
{
  "username": "testuser",
  "email": "test@example.com",
  "password": "mypassword",
  "passwordConfirm": "mypassword",
  "name": "Test User"
}

Body is bare fields matching the auth table schema. Returns { token, refresh_token, record }. passwordConfirm is required (and must match) when the auth extension sets passwordConfirmSuffix: 'Confirm', as the with-auth scaffold does; without that option, omit it.

Login — POST /table/{t}/auth/login-password ​

json
{
  "identity": "test@example.com",
  "password": "mypassword"
}

The identity field accepts email or username. Returns { token, refresh_token, record }.

Other Auth Endpoints ​

EndpointMethodBodyDescription
/auth/refresh-tokenPOST{ "refresh_token": "..." } + Authorization headerRefresh an expired token
/auth/change-passwordPOST{ "password": "new", "passwordCurrent": "old" } (plus passwordConfirm when passwordConfirmSuffix is set)Change password (requires auth with a verified email). Only the password is written; any other field in the body is ignored.
/auth/request-password-resetPOST{ "email": "..." }Request password reset email
/auth/confirm-password-resetPOST{ "token": "...", "password": "new" } (plus passwordConfirm when passwordConfirmSuffix is set)Confirm reset with token. Signs the user in and ends their other sessions.
/auth/request-verificationPOST{ "email": "..." }Request email verification
/auth/confirm-verificationPOST{ "token": "..." }Confirm email with token
/auth/login-tokenPOST— (uses Authorization header)Login with external JWT
/auth/google-loginPOSTform-encoded; see Google One Tap docsGoogle One Tap callback. Verifies the Google credential + CSRF, creates a session. Called by the Google JS library, not by application code.
/auth/oauth/{provider}GET—Start OAuth flow (Google, GitHub, Discord, LinkedIn)
/auth/oauth/{provider}/callbackGET—OAuth callback
/auth/logoutPOST— (uses Authorization header)Logout (invalidates session)

Authenticated Requests ​

Add Authorization: Bearer <token> header to any request that needs auth context (for rules like auth.uid == id).