# For AI agents
Source: https://developers.zite.com/ai-skill
How agents get the context they need to build Zite apps well: the framework guide, llms.txt, and tips for great results.
Zite is built for AI agents. External agents (Claude, ChatGPT, Cursor) have no training data on `zitejs`, so
Zite delivers the context they need, when they need it.
## The framework guide
Every `create_sandbox` response includes a **framework guide**: a concise spec of how Zite apps are
structured (monorepo layout, data access, `zite.sql()` identifier rules, available imports and libraries,
build workflow). Connecting the MCP and booting a sandbox is enough to build correctly, with no memorizing
conventions in advance. The same conventions are documented for humans in the
[Framework reference](/framework/project-structure).
## llms.txt
This site publishes an `llms.txt` index and a full `llms-full.txt` so agents and IDEs can pull the docs into
context. Point your tool at:
```text theme={"dark"}
https://developers.zite.com/llms-full.txt
```
## Give your agent Zite context
When an agent works **outside** a Zite sandbox, building against the [Database API](/api/overview) or
planning in your IDE before it connects, drop this into your project's `AGENTS.md` (or a Cursor rule /
`CLAUDE.md`):
```markdown theme={"dark"}
# Building on Zite
Apps live in a `zitejs` monorepo under `apps/
/`: React frontend in `src/`, backend workflows in
`src/api/` (one file = one workflow).
## Data access (backend only — the frontend never hits the DB directly)
- Each `src/api/.ts` default-exports `createEndpoint({ inputSchema, outputSchema, execute })` from `zitejs/backend`.
- Query with the generated client: `import { zite } from 'zitejs/db'` — `zite.
.findAll({ filter })`,
plus `findOne/create/update/delete/bulkCreate`. Read `.zite/db.ts` for exact table/field SDK names.
- Aggregates & joins: `zite.sql({ query, params })` — double-quote `"Table"`/`"field"` SDK names, bind values
via `$1`, SELECT-only, join linked records through the link table.
- Frontend calls workflows via the generated typed client: `import { listTickets } from 'zitejs/api'`.
- Auth: `useAuth()` from `zitejs/auth` (frontend); `context.user` in workflows when `authenticated: true`.
## Conventions
- Imports: platform SDKs via `zitejs/*` (never relative into `.zite/`). Shared UI via `@project/components/ui/*`.
Your own files via relative paths. Never edit `.zite/` or `src/main.tsx` (generated).
- Available libs: react, tailwindcss, react-router-dom, recharts, lucide-react, framer-motion, zod, date-fns,
react-hook-form, @tanstack/react-table, @dnd-kit/*.
- After adding/renaming workflows, run `npx zitejs generate` from the workspace root.
```
For the full corpus, point your tool at this site's `llms-full.txt`, and link recipes directly (e.g.
`/recipes/stripe-payments`) for task-specific guidance.
## Getting great results
* **Be specific about data.** Name the tables and fields; the agent designs the schema from your description.
* **Ask it to verify at runtime.** A clean type-check isn't proof. See [the build loop](/mcp/build-loop).
* **Let it read `.zite/db.ts`.** It is the source of truth for table and field names.
* **Connect integrations first.** Connect Slack or Stripe when the agent hands you a `setup_integration`
link, so the token is available while it builds.
# Create Database
Source: https://developers.zite.com/api/databases/create-database
api/openapi.json POST /bases
Creates a new database with tables and fields.
* Each table requires at least one field
* Table names must be unique within your database
* Field names must be unique within each table
* Field types must match the [Field Types Reference](/api/field-types)
# Delete Database
Source: https://developers.zite.com/api/databases/delete-database
api/openapi.json DELETE /bases/{databaseId}
Permanently deletes a database and all its data.
Deleting a database will permanently remove the entire database including all tables, fields, views, and records. This action cannot be undone.
# Get Database by ID
Source: https://developers.zite.com/api/databases/get-database-by-id
api/openapi.json GET /bases/{databaseId}
Retrieves a specific database by ID with complete table definitions.
* Returns complete database structure including table schemas
* Includes field configurations and view definitions
* Use this endpoint to understand database structure before making changes
# Get Databases
Source: https://developers.zite.com/api/databases/get-databases
api/openapi.json GET /bases
Lists all databases for your organization.
* Returns all databases accessible to your API key
* Use [Get Database by ID](/api/databases/get-database-by-id) to retrieve full table and field structure for a specific database
# Zite Database Field Types
Source: https://developers.zite.com/api/field-types
Complete reference for all available field types and their configurations.
The 21 field types you can create through the REST API: their `type` string, value shape, and key settings (defaults in parentheses).
Computed and system types (`rich_text`, `rollup`, `created_at`, `updated_at`, `updated_by`, `user`) exist inside Zite apps but are platform-managed and can't be created or configured through this API.
| Type | Value | Key settings (defaults) |
| ------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| **Text** `single_line_text` | `string` | — |
| **Long Text** `long_text` | `string` (multi-line) | — |
| **Email** `email` | `string` (validated) | — |
| **URL** `url` | `string` (validated) | — |
| **Phone** `phone_number` | `string` (any formatting) | — |
| **Number** `number` | `number \| null` | `decimalPlaces` 0–10 (1), `numberFormat` (local) ¹ |
| **Currency** `currency` | `number \| null` (raw number) | `currencySymbol` (\$), `decimalPlaces` 0–10 (2), `numberFormat` (local) ¹ |
| **Percent** `percent` | `number \| null` (decimal; `0.5` = 50%) | `decimalPlaces` 0+ (0), `numberFormat` (local) ¹, `showProgressBar` (false), `allowNegative` (false) |
| **Rating** `rating` | `number \| null` (int 0–maxRating) | `maxRating` 1–10 (5) |
| **Duration** `duration` | `number \| null` (total seconds) | `format` (h:mm) ² |
| **Single Select** `single_select` | `string \| null` (option label) | `options[]` of `{ label, color? }` ³ |
| **Multiple Select** `multiple_select` | `string[]` (labels) | `options[]` of `{ label, color? }` ³ |
| **Checkbox** `checkbox` | `boolean` | `color` hex (#a8a4ac) |
| **Date** `date` | `string \| null` (ISO date, UTC) | `dateFormat` (local) ⁴ |
| **DateTime** `datetime` | `string \| null` (ISO datetime) | `displayTimeZone` (false), `dateFormat` (local) ⁴, `timeFormat` (12h) ⁵, `timezone` (browser) |
| **Attachments** `attachments` | `Array<{ url: string, filename?: string }>` | None (URL must be accessible; `filename` is display-only) |
| **Linked Record** `linked_record` | `string[]` (record IDs) | `tableId` (required), `allowMultiple` (true), `inverseFieldId?`, `isInverse?` ⁶ |
| **Lookup** `lookup` | `any \| any[] \| null` (looked-up values) ⁷ | `linkedRecordFieldId` (required), `lookupFieldId` (required) ⁸ |
| **Formula** `formula` | `string \| number \| Date \| boolean \| any[]`, shape follows `resultType` (read-only) | `expression` (required), `resultType` (required), `formatting?`. See [Formula fields](#formula-fields) |
| **Autonumber** `autonumber` | `number \| null` (auto-assigned, read-only) | — |
| **Source** `source` | JSONB union, system-set (read-only) ⁹ | — |
**Notes**
1. `numberFormat`: `local` (default) · `comma_period` · `period_comma` · `space_comma` · `space_period`.
2. Duration `format`: `h:mm` (default) · `h:mm:ss` · `h:mm:ss.s` · `h:mm:ss.ss` · `h:mm:ss.sss`.
3. `options[]`: `label` required; `color` optional (auto-assigned if omitted); `value` optional, so omit when creating, use in writes to reference existing options (reorder, or keep old + add new). Colors: purple, orange, blue, gray, red, yellow, green, pink, lime, tangerine, emerald, sky, teal, indigo, cyan, violet, fuchsia.
4. `dateFormat`: `local` (default) · `long` · `us` · `european` · `iso`.
5. `timeFormat`: `12h` (default) · `24h`.
6. `tableId` accepts a table ID or name during base creation; creating the field auto-connects both tables in both directions.
7. Single value if the linked-record field has `allowMultiple: false`, array if `true`; element type matches the looked-up field.
8. Cannot be created during initial base setup. Add it later via the field-creation endpoint.
9. `{ type: "public_api", apiKeyId? }` | `{ type: "form_submission", flowPublicId, sessionId }` | `{ type: "workflow", workflowId, executionId }` | `{ type: "manual", reason?, userId? }` | `null`.
## Formula fields
Formula fields compute their value from other fields using a formula expression. They are read-only and automatically recalculate when referenced fields change. See the [Formulas guide](https://zite.com/help/database/configure-database/formulas) for the complete list of supported functions.
```json theme={"dark"}
{
"name": "Total Price",
"type": "formula",
"template": {
"expression": "SUM({Price}, {Tax})",
"resultType": "number",
"formatting": {
"numberDisplayType": "currency",
"decimalPlaces": 2
}
}
}
```
| Property | Type | Description |
| ------------ | ------ | -------------------------------------------------------------------------- |
| `expression` | string | The formula expression, e.g. `"SUM({Price}, {Tax})"` |
| `resultType` | string | The output type: `"text"`, `"number"`, `"date"`, `"boolean"`, or `"array"` |
| `formatting` | object | Optional display formatting (see below) |
**Formatting options**
* Numbers use `numberDisplayType`: `"number"` (plain) · `"currency"` (use with `decimalPlaces`, `numberFormat`) · `"percent"` · `"duration"`.
* Dates use `dateFormat`, `timeFormat` (`"12h"` or `"24h"`), `timezone`, `displayTimeZone` (whether to show the timezone).
## Primary field
The first field in each table is the **primary field** and must be one of: `single_line_text`, `long_text`, `date`, `phone_number`, `email`, `url`, `number`, `currency`, `percent`, `duration`, `autonumber`, `formula`.
## Empty values
| Value kind | Empty value |
| ---------- | ----------- |
| Text | `""` |
| Number | `null` |
| Boolean | `false` |
| Array | `[]` |
| Object | `null` |
Changing a field's type after data exists can cause data loss if the new type is incompatible with existing values.
# Create Field
Source: https://developers.zite.com/api/fields/create-field
api/openapi.json POST /bases/{databaseId}/tables/{tableId}/fields
Adds a new field to an existing table using either table ID or table name.
Both `type` and `name` are required. The `template` object structure varies by field type - see [Field Types Reference](/api/field-types) for complete details
## Example Field Creation
Here's how to create a single select field:
```json theme={"dark"}
{
"type": "single_select",
"name": "Status",
"template": {
"options": [
{"label": "Active", "color": "#10b981"},
{"label": "Inactive", "color": "#6b7280"},
{"label": "Pending", "color": "#f59e0b"},
{"label": "Archived", "color": "#ef4444"}
]
}
}
```
# Delete Field
Source: https://developers.zite.com/api/fields/delete-field
api/openapi.json DELETE /bases/{databaseId}/tables/{tableId}/fields/{fieldId}
Permanently removes a field from a table.
Deleting a field will permanently remove all data stored in that field across all records. This action cannot be undone.
# List Fields
Source: https://developers.zite.com/api/fields/list-fields
api/openapi.json GET /bases/{databaseId}/tables/{tableId}/fields
Retrieve all fields for a table using either table ID or table name.
Fields are returned in display order.
Each field includes its `id`, `name`, `type`, `order`, and a `template` object with field-specific configuration. See the [Field Types Reference](/api/field-types) for the template structure of each field type.
# Update Field
Source: https://developers.zite.com/api/fields/update-field
api/openapi.json PATCH /bases/{databaseId}/tables/{tableId}/fields/{fieldId}
Updates field properties and configuration using either field ID or field name.
The `template` object structure varies by field type - see [Field Types Reference](/api/field-types) for complete details
# Overview
Source: https://developers.zite.com/api/mcp/overview
Connect an AI tool to Zite via MCP to read, write, and query your data in natural language.
The Zite MCP is a single server at `https://mcp.zite.com/mcp`, reachable over the
[Model Context Protocol](https://modelcontextprotocol.io). It exposes a **Data** tool group (below) plus a
**Build** tool group for [building apps](/mcp/overview).
Connect it with the same OAuth flow as the build tools (see [Connect the Zite MCP](/mcp/connect)), then
browse the [data tools](/api/mcp/tools).
# Supported MCP Tools
Source: https://developers.zite.com/api/mcp/tools
What you can do with our MCP service
The Zite MCP's **Data** tool group, for querying and editing databases, tables, fields, and records in
natural language. App-building is the separate [Build](/mcp/overview) tool group. One connection:
`https://mcp.zite.com/mcp`.
| Tool | Description | Example Prompts |
| --------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| get\_table\_schema | Get the schema (fields) for a specific table in a database. | "What fields are in the Customers table?" / "Show me the columns in my Orders table" |
| query\_records | Query records from a table with optional filtering, sorting, and pagination. | "Show me all orders over \$500" / "Find customers in California sorted by signup date" |
| get\_record | Get a single record by its ID. | "Show me the details for order ABC123" / "Get the customer record with ID xyz" |
| get\_database\_schema | Get the full schema for a database, all of its tables and their fields, in a single call. | "Show me the structure of my Sales database" / "What tables and fields are in my CRM database?" |
| execute\_sql | Run a read-only SQL query over a database using human-readable table and field names. | "Run a SQL query for total revenue by month" / "Query the top 10 customers by number of orders" |
| create\_table | Create a new table in an existing database with specified fields. | "Add an Invoices table to my Sales database" / "Create a Tags table with name and color fields" |
| update\_table | Update a table's name. | "Rename the Clients table to Customers" / "Change the Products table name to Inventory" |
| delete\_table | Permanently delete a table and all its records. | "Delete the old Archived Orders table" / "Remove the Test table from my database" |
| create\_field | Add a new field (column) to an existing table. | "Add a phone number field to the Customers table" / "Add a priority dropdown to the Tasks table" |
| update\_field | Update a field's name, type, or configuration. | "Rename the 'price' field to 'unit\_price'" / "Change the status field options to include 'On Hold'" |
| delete\_field | Permanently delete a field and its data from all records. | "Remove the fax number field from Contacts" / "Delete the unused 'legacy\_id' column" |
| create\_record | Create a new record (row) in a table. | "Add a new customer named John Smith" / "Create a task to review the Q4 report" |
| update\_record | Update an existing record's fields. | "Mark order #1234 as shipped" / "Change the priority of the design task to high" |
| delete\_record | Delete a record from a table. | "Delete the duplicate customer entry" / "Remove the canceled order from the list" |
| bulk\_create\_records | Create multiple records at once (more efficient than one at a time). | "Add these 50 products to my inventory" / "Import this list of contacts into my CRM" |
| bulk\_update\_records | Update multiple records at once. | "Mark all overdue tasks as urgent" / "Update the status of these 10 orders to 'Processing'" |
# Zite Database REST API
Source: https://developers.zite.com/api/overview
Read and manage your Zite databases, tables, and records over a REST API.
The Zite Database API gives you programmatic access to your databases, tables, fields, and records: the
same data your apps use. Base URL:
```text theme={"dark"}
https://tables.zite.com/api/v1
```
If your workspace uses a custom deployment, use the base URL shown in your API dashboard instead.
Prefer a client library? Use the [JavaScript SDK](/api/sdk). For value shapes, see [Field types](/api/field-types).
## Authentication
Generate an API key in the [Developer settings](https://app.zite.com/home/settings/developer) (revoke or
regenerate any time), and send it as a Bearer token:
```bash theme={"dark"}
Authorization: Bearer
```
The same API key works across all Zite REST APIs, including the [Build API](/build-api/overview).
## Finding database & table IDs
Open a database in the [dashboard](https://app.zite.com); the IDs are in the URL:
```text theme={"dark"}
app.zite.com/database/{databaseId}/{tableId}/...
```
For example, in `app.zite.com/database/67ef4d500c50cce9/t7nUTgYUjzF/vkuDPV6ZqBb` the database ID is
`67ef4d500c50cce9` and the table ID is `t7nUTgYUjzF`.
Accessing a database embedded in a Zite app? Open the database on its own and use that URL, not the editor URL.
## Rate limits
30 requests/second per database; responses carry `RateLimit-*` headers. Contact
[support@zite.com](mailto:support@zite.com) to raise it.
## Errors
All errors return `{ "error": { "code", "message" } }`:
| Code | Meaning |
| ----------------------- | ------------------------------------------ |
| `INVALID_RECORD_ID` | Record ID is not a valid UUID |
| `NOT_FOUND` | Resource doesn't exist |
| `BAD_REQUEST` | Invalid request data or validation failure |
| `UNAUTHORIZED` | Invalid or missing API key |
| `NOT_IMPLEMENTED` | Feature not yet implemented |
| `INTERNAL_SERVER_ERROR` | Server-side error |
# Aggregate Records
Source: https://developers.zite.com/api/records/aggregate-records
api/openapi.json POST /bases/{databaseId}/tables/{tableId}/records/aggregate
Compute a sum, average, min, max, or count over a field, optionally filtered and grouped.
## Operations
| Operation | Description | Field requirement |
| ---------------- | -------------------------- | ----------------- |
| `sum` | Total of all values | Numeric field |
| `avg` | Average of all values | Numeric field |
| `min` | Smallest value | Any field |
| `max` | Largest value | Any field |
| `count` | Number of non-empty values | Any field |
| `count_distinct` | Number of distinct values | Any field |
`fieldId` (a field ID or field name) is required for every operation. `sum` and `avg` require a numeric field (`number`, `currency`, `percent`, `rating`, or `duration`).
## Filtering
Use the optional `filter` to restrict which records are aggregated. It supports the same nested AND/OR logic as [List Records](/api/records/list-records#filtering).
## Grouping
When `groupBy` is supplied, the response returns `groupedResults` (one entry per group with its `value` and `groupValue`) instead of a single `result`.
```json theme={"dark"}
{
"fieldId": "Amount",
"operation": "sum",
"filter": { "field": "Status", "equals": "Paid" },
"groupBy": "Region"
}
```
# Bulk Create Records
Source: https://developers.zite.com/api/records/bulk-create-records
api/openapi.json POST /bases/{databaseId}/tables/{tableId}/records/bulk
Create or upsert up to 100 records in a single request using either table ID or table name.
Use field names or field IDs as keys and field values as data (e.g., `"Email": "user@example.com"` or `"fwtJyga6dso": "user@example.com"`).
## Upsert with `matchOn`
Provide `matchOn` with one or more **field IDs** to upsert instead of always creating. Incoming records that match an existing record on **all** of the `matchOn` fields update that record; the rest are created.
```json theme={"dark"}
{
"records": [
{ "Email": "john@example.com", "Name": "John Doe" },
{ "Email": "jane@example.com", "Name": "Jane Roe" }
],
"matchOn": ["k8mNp2xQ9rL"]
}
```
Unlike the keys inside each record, `matchOn` accepts **field IDs only**, and field names are rejected. A record's field IDs are the keys of the `data` object returned by any record endpoint (for example, [List records](/api/records/list-records)).
A pure create (no `matchOn`) returns `201 Created`. An upsert (with `matchOn`) returns `200 OK`. In both cases the response contains the resulting records.
Any `id` field included in a record is ignored. Record IDs are always generated by the database.
# Bulk Delete Records
Source: https://developers.zite.com/api/records/bulk-delete-records
api/openapi.json DELETE /bases/{databaseId}/tables/{tableId}/records/bulk
Permanently delete up to 100 records in a single request using either table ID or table name.
This action cannot be undone.
Record IDs that do not exist are skipped rather than causing the request to fail, so `deletedCount` in the response can be lower than the number of IDs you sent.
```json theme={"dark"}
{
"recordIds": [
"d4b3c2a3-c46b-46a1-a8ec-81b664bb41cb",
"5f0895cb-8f2b-4a2a-9d15-3f3a3f6f0f1e"
]
}
```
# Bulk Update Records
Source: https://developers.zite.com/api/records/bulk-update-records
api/openapi.json PUT /bases/{databaseId}/tables/{tableId}/records/bulk
Update up to 100 existing records in a single request using either table ID or table name.
Each record requires a `recordId` (a valid UUID). All other keys are treated as field updates, using field names or field IDs as keys. Only include the fields you want to change.
```json theme={"dark"}
{
"records": [
{
"recordId": "d4b3c2a3-c46b-46a1-a8ec-81b664bb41cb",
"Priority": "low",
"Email": "john.doe@newcompany.com"
}
]
}
```
# Create Record
Source: https://developers.zite.com/api/records/create-record
api/openapi.json POST /bases/{databaseId}/tables/{tableId}/records
Creates a new record in a table using either table ID or table name.
Use field names or field IDs as keys and field values as data (e.g., `"Email": "user@example.com"` or `"fwtJyga6dso": "user@example.com"`)
# Delete Record
Source: https://developers.zite.com/api/records/delete-record
api/openapi.json DELETE /bases/{databaseId}/tables/{tableId}/records/{recordId}
Permanently removes a record from a table using either table ID or table name.
This action cannot be undone.
# Get Record by ID
Source: https://developers.zite.com/api/records/get-record-by-id
api/openapi.json GET /bases/{databaseId}/tables/{tableId}/records/{recordId}
Retrieves a specific record by UUID using either table ID or table name.
# List Records
Source: https://developers.zite.com/api/records/list-records
api/openapi.json POST /bases/{databaseId}/tables/{tableId}/records/list
Retrieves records from a table with filtering, sorting, and pagination using either table ID or table name.
## Pagination
| Parameter | Type | Default | Max |
| --------- | ------ | ------- | ---- |
| `limit` | number | 500 | 2000 |
| `offset` | number | 0 | - |
Use `hasMore` to determine if additional pages exist. Increment `offset` by `limit` for each subsequent request.
## Sorting
When no `sort` parameter is provided, records are returned in ascending order by creation time (`createdAt ASC`).
```json theme={"dark"}
{
"sort": [
{ "field": "name", "direction": "asc" },
{ "field": "createdAt", "direction": "desc" }
]
}
```
| Property | Type | Required | Description |
| ----------- | ------ | -------- | ---------------------------------------------------------------------- |
| `field` | string | Yes | Field name, field ID, or system field (`id`, `createdAt`, `updatedAt`) |
| `direction` | string | No | `"asc"` (default) or `"desc"` |
`fieldId` is also accepted for backward compatibility, but `field` is preferred.
## Filtering
Use the `filter` parameter to query records. Filters support nested AND/OR logic for complex queries.
```json theme={"dark"}
{
"filter": {
"field": "Status",
"equals": "Active"
}
}
```
Use `and` or `or` to combine multiple conditions:
```json theme={"dark"}
{
"filter": {
"and": [
{ "field": "Status", "equals": "Active" },
{ "field": "Amount", "greater_than": 100 }
]
}
}
```
```json theme={"dark"}
{
"filter": {
"or": [
{ "field": "Status", "equals": "Urgent" },
{ "field": "Priority", "equals": "High" }
]
}
}
```
Filters can be nested for complex logic:
```json theme={"dark"}
{
"filter": {
"or": [
{ "field": "Status", "equals": "Urgent" },
{
"and": [
{ "field": "Priority", "equals": "High" },
{ "field": "Completed", "equals": false }
]
}
]
}
}
```
| Operator | Description | Example Value |
| -------------------------- | ----------------------------------------------------- | ------------------------- |
| `equals` | Exact match | `"Active"` |
| `does_not_equal` | Not equal to | `"Archived"` |
| `contains` | Contains substring (text) or has value (multi-select) | `"john"` |
| `does_not_contain` | Does not contain | `"test"` |
| `starts_with` | Starts with string | `"Mr."` |
| `ends_with` | Ends with string | `"@gmail.com"` |
| `is_empty` | Field has no value | `true` |
| `is_not_empty` | Field has a value | `true` |
| `in` | Value is in array | `["Active", "Pending"]` |
| `not_in` | Value is not in array | `["Archived", "Deleted"]` |
| `greater_than` | Greater than (numbers/dates) | `100` or `"2024-01-01"` |
| `greater_than_or_equal_to` | Greater than or equal | `100` |
| `less_than` | Less than | `50` |
| `less_than_or_equal_to` | Less than or equal | `50` |
| Field Type | Supported Operators |
| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Text (`single_line_text`, `long_text`, `email`, `url`, `phone_number`) | `equals`, `does_not_equal`, `contains`, `does_not_contain`, `starts_with`, `ends_with`, `is_empty`, `is_not_empty`, `in`, `not_in` |
| Number (`number`, `currency`, `percent`, `rating`, `duration`) | `equals`, `does_not_equal`, `greater_than`, `greater_than_or_equal_to`, `less_than`, `less_than_or_equal_to`, `is_empty`, `is_not_empty`, `in`, `not_in` |
| Date (`date`, `datetime`) | `equals`, `does_not_equal`, `greater_than`, `greater_than_or_equal_to`, `less_than`, `less_than_or_equal_to`, `is_empty`, `is_not_empty` |
| Selection (`single_select`, `multiple_select`) | `equals`, `does_not_equal`, `contains`, `does_not_contain`, `is_empty`, `is_not_empty`, `in`, `not_in` |
| Checkbox | `equals`, `does_not_equal` |
| Attachments (`attachments`) | `is_empty`, `is_not_empty` |
| Linked Record | `contains`, `does_not_contain`, `is_empty`, `is_not_empty`, `in`, `not_in` |
**Filter by single select:**
```json theme={"dark"}
{ "filter": { "field": "Status", "equals": "Active" } }
```
**Filter by number range:**
```json theme={"dark"}
{
"filter": {
"and": [
{ "field": "Price", "greater_than_or_equal_to": 10 },
{ "field": "Price", "less_than": 100 }
]
}
}
```
**Filter by date:**
```json theme={"dark"}
{ "filter": { "field": "CreatedAt", "greater_than": "2024-01-01" } }
```
**Filter by multiple values:**
```json theme={"dark"}
{ "filter": { "field": "Status", "in": ["Active", "Pending", "Review"] } }
```
**Filter for empty/non-empty:**
```json theme={"dark"}
{ "filter": { "field": "AssignedTo", "is_not_empty": true } }
```
# Update Record
Source: https://developers.zite.com/api/records/update-record
api/openapi.json PATCH /bases/{databaseId}/tables/{tableId}/records/{recordId}
Updates an existing record's field values using either table ID or table name.
Only provided fields will be updated
# JavaScript SDK
Source: https://developers.zite.com/api/sdk
The official zitejs client for calling the Zite Database API from your own Node.js or TypeScript code.
The `zitejs` package ships an official **`Zite` client**: a typed wrapper over the
[Database REST API](/api/overview) for use from your own Node.js or TypeScript code.
Don't confuse the two clients. **`Zite`** (below), imported from the package root, is for calling the
Database API from *outside* Zite with an API key. The **`zite`** client from
[`zitejs/db`](/framework/database-client) is the one generated *inside* a Zite app. Same data, different
entry points.
## Install
```bash theme={"dark"}
npm install zitejs
```
## Initialize
Create a client with your API key (generate one in the
[Developer settings](https://app.zite.com/home/settings/developer)):
```typescript theme={"dark"}
import { Zite } from 'zitejs';
const zite = new Zite(process.env.ZITE_API_KEY!);
```
## Records
The most common operations. Methods take positional ids (`databaseId`, `tableId`, `recordId`) followed by
the payload:
```typescript theme={"dark"}
// Create
const ticket = await zite.record.create(databaseId, tableId, {
subject: 'Printer is down',
status: 'open',
});
// Get / update / delete by id
await zite.record.get(databaseId, tableId, ticket.id);
await zite.record.update(databaseId, tableId, ticket.id, { status: 'closed' });
await zite.record.delete(databaseId, tableId, ticket.id);
// List with filter, sort, and pagination
const { records, total, hasMore } = await zite.record.list(databaseId, tableId, {
filter: { status: 'open' },
sort: [{ field: 'createdAt', direction: 'desc' }],
limit: 100,
offset: 0,
});
// Bulk create (optionally upsert on matching fields) and bulk update
await zite.record.bulkCreate(databaseId, tableId, [{ email: 'a@b.com' }], ['email']);
await zite.record.bulkUpdate(databaseId, tableId, [{ recordId: ticket.id, status: 'closed' }]);
```
| Method | Signature |
| ------------------- | ---------------------------------------------------------------------------- |
| `record.create` | `(databaseId, tableId, record)` |
| `record.get` | `(databaseId, tableId, recordId)` |
| `record.update` | `(databaseId, tableId, recordId, record)` |
| `record.delete` | `(databaseId, tableId, recordId)` |
| `record.list` | `(databaseId, tableId, options?)` with `{ limit?, offset?, sort?, filter? }` |
| `record.bulkCreate` | `(databaseId, tableId, records, matchOn?)` |
| `record.bulkUpdate` | `(databaseId, tableId, records)`, where each item includes `recordId` |
## Schema: fields, tables, databases
Manage the schema with the same positional style:
```typescript theme={"dark"}
// Databases
const db = await zite.database.create({ name: 'Helpdesk', tables: [/* … */] });
await zite.database.list();
await zite.database.get(databaseId);
await zite.database.delete(databaseId);
// Tables
await zite.table.create(databaseId, { name: 'Tickets', fields: [/* … */] });
await zite.table.update(databaseId, tableId, { name: 'SupportTickets' });
await zite.table.delete(databaseId, tableId);
// Fields
await zite.field.create(databaseId, tableId, { type: 'single_select', name: 'Status', template: { /* … */ } });
await zite.field.update(databaseId, tableId, fieldId, { name: 'State' });
await zite.field.delete(databaseId, tableId, fieldId);
```
See [Field types](/api/field-types) for the field `type` and `template` shapes.
## Aggregates and SQL
For counts, sums, group-bys, and read-only SQL over a base, use the Database API's
[aggregate](/api/records/aggregate-records) and SQL endpoints directly. These run in the database rather than
pulling rows to the client.
Finding your `databaseId` and `tableId`: open the database in the [dashboard](https://app.zite.com) and
read them from the URL (`app.zite.com/database/{databaseId}/{tableId}/…`). See the
[API overview](/api/overview#finding-database-table-ids).
# Get SQL Schema
Source: https://developers.zite.com/api/sql/get-schema
api/openapi.json GET /bases/{databaseId}/sql/schema
Describe a database's tables, fields, and link tables for composing SQL queries.
Returns the database schema (tables, their fields, and the link tables that connect them) using human-readable names. Use this to discover the exact table and field names to reference from [Run SQL](/api/sql/run-sql).
Names in the response match the human-readable names used in your SQL queries and the Zite SDK.
## Query parameters
| Parameter | Type | Description |
| --------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tables` | string\[] | Optional whitelist of table names to include. Repeat the parameter for multiple tables (e.g. `?tables=Orders&tables=Customers`) or pass a single comma-separated string. Omit to return all tables. |
| `includeFields` | boolean | Whether to include field-level detail. Defaults to `true`. Set to `false` for a cheap overview of just table names. |
## Link tables
`linkTables` describes the junction tables that connect two tables for many-to-many relationships. Each entry exposes the source and target tables along with the columns to join on, so you can traverse linked records in SQL.
# Run SQL
Source: https://developers.zite.com/api/sql/run-sql
api/openapi.json POST /bases/{databaseId}/sql
Execute a read-only SQL query against a database and its linked tables.
Queries are **read-only**. Only `SELECT` statements are permitted. Any write, DDL, or transaction-control statement is rejected.
## Writing queries
* **Human-readable names.** Reference tables and fields by the same names you see in the Zite SDK, not physical column names (e.g. `SELECT "Name" FROM "Orders"`).
* **Bind parameters.** Pass user-supplied values in `params` and reference them with positional placeholders (`$1`, `$2`, ...). This is safer than interpolating values into the SQL string.
* **Linked records.** Join through link tables to traverse relationships. Use [Get SQL Schema](/api/sql/get-schema) to discover table names, field names, and link tables.
```json theme={"dark"}
{
"sql": "SELECT \"Name\", \"Amount\" FROM \"Orders\" WHERE \"Status\" = $1 ORDER BY \"Amount\" DESC",
"params": ["Paid"]
}
```
## Limits
| Behavior | Detail |
| ---------- | -------------------------------------------------------------------------------------------------------------------- |
| Row cap | Results are capped at \~2000 rows. When more match, `truncated` is `true` and only the first 2000 rows are returned. |
| Timeout | `statementTimeoutMs` defaults to `10000` (10 seconds) and is capped at `60000` (60 seconds). |
| Query size | The `sql` string may be up to 50,000 characters; `params` accepts up to 1000 values. |
If `truncated` is `true`, narrow the query with a `WHERE` clause or add your own `LIMIT`/`OFFSET` to page through results.
# Create Table
Source: https://developers.zite.com/api/tables/create-table
api/openapi.json POST /bases/{databaseId}/tables
Adds a new table to an existing database.
At least one field must be specified. The first field becomes the primary field and must be a supported primary field type. Field names must be unique within the table.
# Delete Table
Source: https://developers.zite.com/api/tables/delete-table
api/openapi.json DELETE /bases/{databaseId}/tables/{tableId}
Permanently deletes a table and all its records using either table ID or table name.
Deleting a table will permanently remove the table, all its fields, views, and all records stored in the table. This action cannot be undone.
# Update Table
Source: https://developers.zite.com/api/tables/update-table
api/openapi.json PATCH /bases/{databaseId}/tables/{tableId}
Updates table properties like name and order using either table ID or table name.
* Only provided fields will be updated
* Omitted fields retain their current values
* Order determines table position in database navigation
# File Uploads
Source: https://developers.zite.com/api/uploads
Upload attachments and import CSV/Excel files via multipart/form-data.
Uploads are sent as `multipart/form-data` to three dedicated endpoints. Unlike the JSON REST API, these routes live at the **root host**, not under `/api/v1`:
```
https://tables.zite.com
```
Every upload uses the same `Authorization: Bearer YOUR_API_KEY` header as the rest of the API, and the file must be sent as a multipart form field named **`filepond`**.
## Example
`POST /fileupload` uploads a file for an **attachment** field; write the returned `url` to that field via [Create Record](/api/records/create-record) or [Update Record](/api/records/update-record).
```bash theme={"dark"}
curl -X POST https://tables.zite.com/fileupload \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "base-public-id: YOUR_BASE_ID" \
-H "table-id: YOUR_TABLE_ID" \
-H "field-id: YOUR_FIELD_ID" \
-F "filepond=@/path/to/photo.png"
```
```json theme={"dark"}
{
"url": "https://.../photo.png",
"fileName": "photo.png",
"size": 20480,
"mimeType": "image/png"
}
```
## Endpoints
| Endpoint | Purpose | Max size | Required headers | Response |
| ------------------ | ------------------------------------- | -------- | --------------------------------------------------------- | ----------------------------------- |
| `POST /fileupload` | Upload a file for an attachment field | 20 MB | `Authorization`, `base-public-id`, `table-id`, `field-id` | `{ url, fileName, size, mimeType }` |
| `POST /csvupload` | Upload a CSV to import as data | 50 MB | `Authorization` | `{ url, fileName, s3Key }` |
| `POST /xlsxupload` | Upload an `.xlsx` to import as data | 50 MB | `Authorization` | `{ url, fileName, s3Key }` |
`s3Key` is the storage key the import flow uses to reference the uploaded file.
## Validation
A request that fails any of these returns `400 Bad Request`:
| Endpoint | Must satisfy |
| ------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `POST /fileupload` | All three of `base-public-id`, `table-id`, `field-id` are present, and the file is a supported attachment MIME type |
| `POST /csvupload` | MIME type is `text/csv`, or the filename ends in `.csv` |
| `POST /xlsxupload` | The `.xlsx` MIME type, a `.xlsx` filename, and a valid Excel (ZIP) signature |
# Create Webhook
Source: https://developers.zite.com/api/webhooks/create-webhook
api/openapi.json POST /bases/{databaseId}/webhooks
Creates a new webhook subscription to receive notifications when database events occur.
## Event Types
Subscribe to any combination of these events:
| Event | Description |
| ---------------- | -------------------------------------------- |
| `record.created` | Triggered when new records are added |
| `record.updated` | Triggered when existing records are modified |
| `record.deleted` | Triggered when records are deleted |
| `table.created` | Triggered when new tables are added |
| `table.updated` | Triggered when table properties change |
| `table.deleted` | Triggered when tables are deleted |
| `field.created` | Triggered when new fields are added |
| `field.updated` | Triggered when field properties change |
| `field.deleted` | Triggered when fields are deleted |
## Filtering by Table
Use the optional `tableId` parameter to receive events only for a specific table:
```json theme={"dark"}
{
"url": "https://your-server.com/webhook",
"events": ["record.created", "record.updated"],
"tableId": "tbl_abc123"
}
```
Omit `tableId` to receive events from all tables in the database.
## Example: Subscribe to Record Changes
```bash theme={"dark"}
curl -X POST "https://tables.zite.com/api/v1/bases/{databaseId}/webhooks" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/zite",
"events": ["record.created", "record.updated", "record.deleted"]
}'
```
**Response:**
```json theme={"dark"}
{
"id": 123,
"secret": "a1b2c3d4e5f6..."
}
```
Store the `secret` securely - it is only returned once on creation and cannot be retrieved later. You'll need this secret to verify webhook signatures.
# Delete Webhook
Source: https://developers.zite.com/api/webhooks/delete-webhook
api/openapi.json DELETE /bases/{databaseId}/webhooks/{webhookId}
Permanently removes a webhook subscription.
This action cannot be undone. If you need to temporarily stop receiving webhooks, consider updating the webhook to set `active: false` instead.
## Example Request
```bash theme={"dark"}
curl -X DELETE "https://tables.zite.com/api/v1/bases/{databaseId}/webhooks/123" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Example Response
```json theme={"dark"}
{
"success": true,
"message": "Webhook deleted successfully"
}
```
## Error Responses
### Webhook Not Found (404)
```json theme={"dark"}
{
"error": {
"code": "NOT_FOUND",
"message": "Webhook not found"
}
}
```
This error occurs when:
* The webhook ID doesn't exist
* The webhook belongs to a different database
* You don't have access to this database
# List Webhooks
Source: https://developers.zite.com/api/webhooks/list-webhooks
api/openapi.json GET /bases/{databaseId}/webhooks
Lists all webhook subscriptions for a database.
## Example Request
```bash theme={"dark"}
curl "https://tables.zite.com/api/v1/bases/{databaseId}/webhooks" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Example Response
```json theme={"dark"}
{
"webhooks": [
{
"id": 123,
"url": "https://your-server.com/webhooks/zite",
"events": ["record.created", "record.updated", "record.deleted"],
"tableIds": null,
"active": true
},
{
"id": 124,
"url": "https://another-service.com/webhook",
"events": ["table.created", "field.created"],
"tableIds": ["tbl_abc123"],
"active": true
}
]
}
```
## Response Fields
| Field | Type | Description |
| ---------- | ----------------- | ------------------------------------ |
| `id` | number | Unique webhook ID |
| `url` | string | The webhook endpoint URL |
| `events` | string\[] | Array of subscribed event types |
| `tableIds` | string\[] \| null | Table filter (null means all tables) |
| `active` | boolean | Whether the webhook is active |
# Webhooks overview
Source: https://developers.zite.com/api/webhooks/webhooks
Receive real-time notifications when database events occur
Subscribe to database events and receive an HTTP POST at your URL instead of polling. To create a subscription, see [Create Webhook](/api/webhooks/create-webhook).
## Event types
| Event | Fires when |
| ---------------- | -------------------------------------------- |
| `record.created` | A record is added to a table |
| `record.updated` | An existing record is modified |
| `record.deleted` | A record is deleted |
| `table.created` | A table is added |
| `table.updated` | A table's properties (e.g. name) change |
| `table.deleted` | A table is deleted |
| `field.created` | A field is added to a table |
| `field.updated` | A field's properties or configuration change |
| `field.deleted` | A field is removed |
## Payload
Each delivery is a JSON POST body:
```json theme={"dark"}
{
"id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "record.created",
"timestamp": "2025-01-15T10:30:00.000Z",
"data": {
"tableId": "tbl_abc123",
"recordIds": ["rec_xyz789"],
"records": [
{ "id": "rec_xyz789", "data": { "Name": "John Doe", "Email": "john@example.com" } }
]
},
"metadata": {
"webhookId": 123,
"organizationId": 456,
"baseId": "base_def456",
"attempt": 1,
"source": "ui"
}
}
```
| Field | Type | Description |
| ------------------------- | ------ | ------------------------------------------------------ |
| `id` | string | Unique event ID, use it for idempotency |
| `type` | string | Event type (e.g. `record.created`) |
| `timestamp` | string | ISO 8601 time the event occurred |
| `data` | object | Event-specific data (see below) |
| `metadata.webhookId` | number | ID of the webhook subscription |
| `metadata.organizationId` | number | Your organization ID |
| `metadata.baseId` | string | The database ID |
| `metadata.attempt` | number | Delivery attempt (1 = first) |
| `metadata.source` | string | Origin: `ui`, `public_api`, `zapier`, `make`, `import` |
The `data` object varies by event family:
| Event family | `data` fields | Also present on `*.updated` |
| ------------ | --------------------------------------------------------- | --------------------------- |
| Record | `tableId`, `recordIds`, `records[]` | `previousRecords[]` |
| Table | `tableId`, `table` `{ id, name, order, primaryFieldId }` | `previousTable` |
| Field | `tableId`, `fieldId`, `field` `{ id, name, type, order }` | `previousField` |
## Request headers
| Header | Value |
| --------------------- | ------------------------------------ |
| `Content-Type` | `application/json` |
| `X-Webhook-Signature` | HMAC-SHA256 signature of the payload |
| `X-Webhook-Event` | The event type |
| `X-Webhook-ID` | The unique event ID |
| `User-Agent` | `Zite-Webhooks/1.0` |
## Verifying signatures
Every request carries an `X-Webhook-Signature` header: an HMAC-SHA256 of the payload keyed with your webhook secret (returned once on [creation](/api/webhooks/create-webhook)). Verify it before processing:
```javascript theme={"dark"}
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// In your webhook handler
app.post('/webhooks/zite', (req, res) => {
const signature = req.headers['x-webhook-signature'];
if (!verifyWebhookSignature(req.body, signature, YOUR_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
res.status(200).send('OK'); // then process asynchronously
});
```
## Delivery, retries & limits
* Return a **2xx within 30 s** to acknowledge; process asynchronously if needed. Use HTTPS.
* Non-2xx or timeout is retried up to **5 times** with exponential backoff: 5s, 10s, 20s, 40s, 80s.
* Deduplicate on the event `id`, because retries reuse it.
| Limit | Value |
| ------------------------- | ---------------- |
| Max webhooks per database | 100 |
| Max events per webhook | 20 |
| Max URL length | 2,048 characters |
| Max payload size | 256 KB |
| Delivery timeout | 30 seconds |
# Create Zite via API
Source: https://developers.zite.com/build-api/create-zite
Programmatically create Zites from a prompt using the REST API.
The Create Zite API lets you programmatically generate a Zite from a text prompt, optionally including files, a theme, and a name. The Zite is created instantly and a **claim URL** is returned so a user can take ownership of it.
No authentication is required to call this endpoint.
## Use cases
* **Embed app creation in your product.** Add a "Create an app" button to your website or tool that generates a Zite behind the scenes
* **Share prompts that generate apps.** Create shareable links or workflows that produce a Zite from a predefined prompt
* **Automate app generation.** Integrate Zite creation into scripts, pipelines, or automation workflows
* **Rapid prototyping.** Quickly spin up Zites from the command line or CI/CD
## How it works
1. Send a `POST` request with a prompt (and optional files, theme, or name)
2. A new Zite is generated instantly
3. The response includes a `claimUrl`. Share it with a user so they can claim the Zite into their own account
The base URL is `https://api.zite.com/v1/api`.
## API reference
### Endpoint
```text theme={"dark"}
POST /zite/create
```
### Request body
| Field | Type | Required | Description |
| ------------------ | ------ | -------------------- | --------------------------------------------------------------------------------------------- |
| `prompt` | string | Yes | The prompt to generate the Zite from. 1–10,000 characters, trimmed. |
| `files` | array | No | Files to include. Max 10 items. |
| `files[].url` | string | Yes (within `files`) | A valid URL pointing to the file. |
| `files[].fileName` | string | No | A display name for the file. |
| `theme` | string | No | A theme for the Zite. If omitted, auto-selected from the prompt (when no files are attached). |
| `name` | string | No | A name for the Zite. Max 250 characters. If omitted, auto-generated from the prompt. |
### Response body
| Field | Type | Description |
| ---------------------- | ------ | --------------------------------------------------- |
| `flowPublicIdentifier` | string | Unique identifier for the created Zite. |
| `claimUrl` | string | URL the user can visit to claim and own the Zite. |
| `appName` | string | The resolved app name (provided or auto-generated). |
If no `theme` is provided and no files are attached, a theme is automatically selected using AI based on the prompt.
## Example
### Request
```bash theme={"dark"}
curl -X POST https://api.zite.com/v1/api/zite/create \
-H "Content-Type: application/json" \
-d '{
"prompt": "A project management dashboard with task boards, deadlines, and team assignments"
}'
```
### Response
```json theme={"dark"}
{
"claimUrl": "https://www.zite.com/claim/abc123def456",
"appName": "Project Management Dashboard"
}
```
# Publish a deployment
Source: https://developers.zite.com/build-api/deployments/publish-deployment
build-api/openapi.json POST /apps/{appId}/deployments
Publish an app's latest successful build to make it live.
Publishes the app's latest **successful** build.
* **External** apps deploy to `https://.zite.so` (or a custom domain).
* **Internal** apps become available to your organization; `customSubdomain` is `null`.
Publishing requires a **successful** build. If the latest build is still `building` or failed, the
call returns a `400` explaining why. Poll [`GET /apps/{appId}`](/build-api/workspaces/get-app) until
`buildStatus` is `success`, or fix errors with [check](/build-api/sessions/check) / logs and
[commit](/build-api/sessions/commit) again.
# Read logs
Source: https://developers.zite.com/build-api/deployments/read-logs
build-api/openapi.json GET /apps/{appId}/logs
Read workflow execution logs: inputs, outputs, console output, and stack traces.
Returns [workflow](/concepts/workflows) execution logs: inputs, outputs, console output, and
stack traces. Logs are retained for 90 days. See [Troubleshooting](/troubleshooting) for common runtime
errors and fixes.
# Build API
Source: https://developers.zite.com/build-api/overview
Create, build, and deploy Zite apps programmatically, from your own code, CI, or your own agent. The REST equivalent of the Zite MCP build tools.
## Base URL and auth
```text theme={"dark"}
https://api.zite.com/v1
```
Authenticate with a Bearer API key from your
[Developer settings](https://app.zite.com/home/settings/developer):
```bash theme={"dark"}
Authorization: Bearer
```
The key acts as **you**, so it can only see and change workspaces you have access to, and every change is
attributed to you in the app's [version history](/deploy/publishing).
The build endpoints are free for 14 days, starting on your first build call. After that they're on the
Business plan and above, and an unentitled key gets a `402`.
## The lifecycle
A database plus the apps built on it. See [Create a workspace](/build-api/workspaces/create-workspace).
An isolated checkout where you create apps and edit files. See [Open a session](/build-api/sessions/open-session).
Scaffold, edit, check, iterate. See [Create an app](/build-api/sessions/create-app).
Builds a versioned snapshot in the background. See [Commit](/build-api/sessions/commit).
Publish the build live and pull logs. See [Publish a deployment](/build-api/deployments/publish-deployment).
## Relationship to the Zite MCP
Same operations and semantics as the [Zite MCP build tools](/mcp/build-tools), where a session maps to a
sandbox. Use the MCP for interactive agents, the Build API for CI, scripts, and automation.
## Conventions
### Errors
Every error returns the same envelope:
```json theme={"dark"}
{ "statusCode": 404, "error": "Not Found", "message": "App not found" }
```
| Status | Meaning |
| ------ | -------------------------------------------------------------------------------------------------------- |
| `401` | Invalid or missing API key |
| `402` | The build tools aren't active: the free trial has ended, or your plan doesn't include them |
| `404` | Resource doesn't exist |
| `409` | The workspace has newer commits than your session. Pull and rebase inside the session, then commit again |
| `400` | Invalid request data, or publishing while the latest build was still `building` or had failed |
### Async builds
`commit` and publish kick off builds in the background. Poll the app's `buildStatus` (see
[Get an app](/build-api/workspaces/get-app)) and wait for a green build before publishing.
Publishing early returns a `400` explaining why.
# Check
Source: https://developers.zite.com/build-api/sessions/check
build-api/openapi.json POST /sessions/{sessionId}/check
Regenerate types and run the type-check and config validation.
Regenerates types and runs the type-check and config validation. `appId` is optional and scopes workflow
validation. Fix everything it reports before [committing](/build-api/sessions/commit).
# Commit
Source: https://developers.zite.com/build-api/sessions/commit
build-api/openapi.json POST /sessions/{sessionId}/commit
Commit and push session changes, then build and snapshot in the background.
`commit` regenerates types, commits and pushes, then **builds and snapshots in the background**. The
returned `changedApps` report `buildStatus: "building"`. Poll
[`GET /apps/{appId}`](/build-api/workspaces/get-app) until the build is `success`, then
[publish](/build-api/deployments/publish-deployment).
If the workspace has newer commits than your session, `commit` returns `CONFLICT`. Pull and rebase
inside the session ([`POST /bash`](/build-api/sessions/run-command) with `git pull --rebase origin main`),
resolve, and commit again.
# Create an app
Source: https://developers.zite.com/build-api/sessions/create-app
build-api/openapi.json POST /sessions/{sessionId}/apps
Scaffold an empty app inside a build session.
Scaffolds an empty app at `apps//`. This is a **free scaffold**, so no AI build runs. `accessMode` is
`internal` (default) or `external`. The app lands in the repo with your first
[commit](/build-api/sessions/commit).
# Edit a file
Source: https://developers.zite.com/build-api/sessions/edit-file
build-api/openapi.json POST /sessions/{sessionId}/files/edit
Replace an exact snippet in an existing file.
Replaces `oldText` with `newText` in a file.
`files/edit` replaces `oldText` where it occurs **exactly once**. If it matches zero or more than one
place, the call returns `BAD_REQUEST`. Include more surrounding context so the match is unique. Use
[Write a file](/build-api/sessions/write-file) for new files or full rewrites.
# List files
Source: https://developers.zite.com/build-api/sessions/list-files
build-api/openapi.json POST /sessions/{sessionId}/files/list
List or glob files in a build session.
Lists the session's file tree. Paths are relative
to the workspace root (e.g. `apps/agent-console/src/api/listTickets.ts`).
# Open a session
Source: https://developers.zite.com/build-api/sessions/open-session
build-api/openapi.json POST /workspaces/{workspaceId}/sessions
Open an isolated checkout of a workspace's monorepo to create apps and edit files.
A **build session** is an isolated checkout of a workspace's monorepo, the same concept as the Zite MCP
[sandbox](/mcp/build-loop#sandboxes). You create apps and edit files inside a session, then
[commit](/build-api/sessions/commit) to build and version your changes.
The response includes the workspace's file tree, its apps, and the **framework guide**: the same spec
agents receive, describing the [project layout](/framework/project-structure) and conventions.
* Every file and commit call takes the `sessionId` returned here.
* Use **one session per concurrent build** so parallel jobs don't clobber each other.
* Sessions auto-stop after \~15 minutes idle and re-boot transparently on the next call.
# Read a file
Source: https://developers.zite.com/build-api/sessions/read-file
build-api/openapi.json POST /sessions/{sessionId}/files/read
Read a file in a build session, with optional paging for long files.
Reads a file in the session.
# Run a command
Source: https://developers.zite.com/build-api/sessions/run-command
build-api/openapi.json POST /sessions/{sessionId}/bash
Run a shell command inside a build session checkout.
Runs a shell command inside the session checkout, for example `git pull --rebase origin main` to
resolve a [commit conflict](/build-api/sessions/commit).
# Search files
Source: https://developers.zite.com/build-api/sessions/search-files
build-api/openapi.json POST /sessions/{sessionId}/grep
Search file contents in a build session with a regular expression.
Searches file contents with a regular expression. Pass `context` to include surrounding lines with each
match.
# Write a file
Source: https://developers.zite.com/build-api/sessions/write-file
build-api/openapi.json PUT /sessions/{sessionId}/files
Create a new file or fully rewrite an existing one in a build session.
Creates a new file or fully rewrites an existing one. For surgical changes to an existing file, use
[Edit a file](/build-api/sessions/edit-file) instead.
# Create a workspace
Source: https://developers.zite.com/build-api/workspaces/create-workspace
build-api/openapi.json POST /workspaces
Create a new workspace: a database plus the apps built on it.
A [workspace](/concepts/workspaces) is a database plus the apps built on it. The new workspace starts
empty (a database with no tables). Add tables with the [Database API](/api/overview), or let your code
create them once you're in a [build session](/build-api/sessions/open-session).
# Get an app
Source: https://developers.zite.com/build-api/workspaces/get-app
build-api/openapi.json GET /apps/{appId}
Return one app, including its build status and editor URL.
Returns one app. `buildStatus` is one of `none`, `building`, `success`, or `failure`. Poll it after a
[commit](/build-api/sessions/commit) before you [publish](/build-api/deployments/publish-deployment).
`published` flips once a snapshot is published.
# Get a workspace
Source: https://developers.zite.com/build-api/workspaces/get-workspace
build-api/openapi.json GET /workspaces/{workspaceId}
Return one workspace with its apps and forms.
Returns the same shape as an item in [List workspaces](/build-api/workspaces/list-workspaces).
# List apps in a workspace
Source: https://developers.zite.com/build-api/workspaces/list-apps
build-api/openapi.json GET /workspaces/{workspaceId}/apps
List the apps in a workspace.
Returns the apps in a workspace. For an app's build state, poll [Get an app](/build-api/workspaces/get-app).
# List workspaces
Source: https://developers.zite.com/build-api/workspaces/list-workspaces
build-api/openapi.json GET /workspaces
List every workspace your key can access, each with its apps and forms.
# Apps
Source: https://developers.zite.com/concepts/apps
Apps are React applications built on a workspace's database. A workspace can have many, all sharing the same data, auth, and design system.
Each app is a React + Vite single-page app in its own directory (`apps//`) in the workspace monorepo:
* **Frontend:** `src/`, styled with Tailwind and the shared shadcn/ui design system (`@project/components`, which you own).
* **Backend:** one file per [workflow](/concepts/workflows) in `src/api/`.
* **Generated types:** a never-edited `.zite/` directory (typed DB client + workflow callers).
* **Config:** `zite.config.json` (access mode, integrations, settings).
See [Project structure](/framework/project-structure) for the full layout.
## Access modes
Every app is **internal** (org members only, auto signed-in, and the default) or **external** (public, with a
Zite-hosted sign-in). Access mode drives auth and how the app is served. See
[Authentication](/concepts/authentication).
## Built and served by agents
Agents build apps through the [Zite MCP](/mcp/overview) in a cloud sandbox, then compile to a static build.
No dev server, no local setup. Internal apps are served to your team; external apps deploy to
`https://.zite.so` or a custom domain. See [Publishing](/deploy/publishing).
# Authentication
Source: https://developers.zite.com/concepts/authentication
Internal apps sign in your team automatically; external apps add public sign-in. User sync links a signed-in user to a row in your database.
How users sign in to an app is determined by its **access mode**.
## Access modes
Only members of your Zite organization can access the app. They're signed in automatically, so there's
no sign-in screen, and `context.user` is always populated. The default for new apps.
Anyone can access the app. Users sign in through a Zite-hosted page. The auth SDK is available but
optional: a public marketing page needs no sign-in, a customer portal does.
Changing access mode is a deliberate, confirmed action. An agent will ask before switching an app between
internal and external.
## Signing in
On the frontend, use `useAuth` from `zitejs/auth`:
```tsx theme={"dark"}
import { useAuth } from 'zitejs/auth';
function Header() {
const { user, isLoading, loginWithRedirect, logout } = useAuth();
if (isLoading) return null;
return user
?
: ;
}
```
In backend [workflows](/concepts/workflows), set `authenticated: true` and read `context.user`.
See the [auth reference](/framework/auth) for the full API.
## Sign-in methods
External apps can offer **email magic link**, **Google sign-in**, and **SSO**. You also control signups:
open, disabled, or restricted to specific email domains. Some options depend on your plan:
| Setting | Requires |
| ----------------------------------- | --------------- |
| SSO sign-in | Enterprise plan |
| Custom auth email sender & branding | Business plan |
| Sign-in screen customization | Business plan |
These are configured per app (in `zite.config.json` under `authentication`); an agent sets them up for you.
## User sync
**User sync** links a signed-in user to a row in your workspace's database, typically a `Users` table.
Once enabled, the record's fields are merged into `context.user`, so you can:
* Read app-specific profile data (role, team, plan) alongside the user's identity.
* Reference those fields in row-level [permission](/concepts/permissions) filters via `userField`.
User sync is now based on the **workspace database**. Syncing users from Airtable, and the older
internal-app user sync, are deprecated in favor of a users table in the workspace's own database.
# Databases
Source: https://developers.zite.com/concepts/databases
Every workspace is backed by one Zite database: tables, fields, records, and views that all its apps share.
The database is relational, with a spreadsheet-friendly field model: tables of records, where each field has
a type (text, number, select, date, linked record, formula, and more).
## Tables, fields, and records
* **Tables** hold records of one kind (Customers, Tickets, Invoices).
* **Fields** are the typed columns on a table. The first field is the table's **primary field**.
* **Records** are the rows.
* **Views** are saved filters, sorts, and field arrangements over a table.
Zite supports rich field types: text, number, currency, percent, rating, duration, single/multiple
select, date, datetime, checkbox, attachments, email, url, phone, **linked records** (relationships
between tables), **lookups** and **rollups** (values pulled or aggregated across a relationship), and
**formulas** (computed values). See the full [Field types](/api/field-types) reference.
Some field types are computed or system-managed: `formula`, `rollup`, `created_at`, `updated_at`,
`updated_by`, and `user`. They're maintained by the platform, so they're read-only from your code
and can't be set directly.
## SDK names
In your apps, tables and fields are addressed by **SDK names**, not their display labels:
* **Tables** use PascalCase (`Tickets`, `SupportTickets`).
* **Fields** use camelCase (`dueDate`, `assigneeEmail`).
SDK names are stable (renaming a table or field in the UI does not change its SDK name), so your code
keeps working. You'll see them in the generated `.zite/db.ts` for each workspace. See the
[database client](/framework/database-client).
## How apps use the database
The frontend never talks to the database directly. Reads and writes run inside backend
[workflows](/concepts/workflows), which use a typed client generated from the schema:
```typescript theme={"dark"}
// in a workflow — apps//src/api/listOpenTickets.ts
import { zite } from 'zitejs/db';
const open = await zite.Tickets.findAll({ filter: { status: 'open' } });
```
The React frontend reaches this data by calling that workflow through a generated, typed caller. It
never queries the database itself. For counts, joins, and aggregates, workflows use read-only SQL with
human-readable names via `zite.sql()`. Both the client and `zite.sql()` are covered in the
[database client](/framework/database-client) reference.
## Editing the schema
You can change the schema from the database UI in the [dashboard](https://app.zite.com), or ask an agent
to do it through the [Zite MCP](/mcp/overview) (`create_table`, `create_field`, and friends). You can also
manage tables, fields, and records programmatically with the [Database API](/api/overview).
# Integrations & secrets
Source: https://developers.zite.com/concepts/integrations-secrets
How Zite apps talk to third-party services, and where their credentials live.
Beyond its own database, a Zite app can call third-party services: Slack, OpenAI, Stripe, Google
Workspace, HubSpot, and more. Integrations run in your backend [workflows](/concepts/workflows),
never in the browser, so credentials stay secret.
## How integrations work
Everything your app can call falls into one of three buckets:
| What | How you call it | Import | Credential |
| ---------------------- | ----------------------------------------------- | --------------------------------------- | ----------------------------------------- |
| The workspace database | Typed client, permissions applied on every call | `zitejs/db` | None, it's the workspace itself |
| Airtable | Managed, generated client | `zitejs/integrations` | Managed for you |
| Every other service | The real npm library, called directly | `@slack/web-api`, `openai`, `stripe`, … | `process.env.ZITE__ACCESS_TOKEN` |
The database is not an "integration" you configure. It's the workspace itself, reached through the runtime.
## Supported services
Zite supports 24 connectable third-party services:
| Category | Services |
| ------------------- | ----------------------------------------------------- |
| AI | OpenAI, Anthropic, Gemini |
| Messaging | Slack, Microsoft Teams, Twilio, Intercom |
| Email & marketing | Gmail, Outlook, Mailchimp |
| Docs & spreadsheets | Google Sheets, Google Docs, Google Calendar, Airtable |
| Files & storage | Google Drive, OneDrive |
| CRM & payments | HubSpot, Salesforce, Stripe |
| Project tracking | Linear, Notion, Trello |
| Maps & analytics | Google Maps, Google Analytics |
## Secrets
Secrets are **never written in code**. They're provided to your workflows as environment variables:
* Connected integrations expose a token like `ZITE_SLACK_ACCESS_TOKEN` or `ZITE_OPENAI_ACCESS_TOKEN`.
* Your own secrets, added in the editor, are prefixed `ZITE_` and read as `process.env.ZITE_`.
```typescript theme={"dark"}
import { WebClient } from '@slack/web-api';
const slack = new WebClient(process.env.ZITE_SLACK_ACCESS_TOKEN);
```
Connecting an integration requires an OAuth sign-in, which can't be done by an agent headlessly. The agent
hands you a link (`setup_integration`) to connect the service in Zite; once connected, the token is
available to your workflows.
## Environments
The `ZITE_ENV` variable selects the environment your workflows run against:
| `ZITE_ENV` | Runtime |
| ---------------------- | ----------------------------------- |
| `production` (default) | `https://workflows.zite.com` |
| `staging` | `https://workflows.zitestaging.com` |
| `local` | `http://localhost:2506` |
`process.env.ZITE_APP_URL` is always set to your app's public URL. Use it for building links instead of
hardcoding a domain.
For result-set caps, execution timeouts, and payload sizes, see [Platform limits](/limits).
# Roles & permissions
Source: https://developers.zite.com/concepts/permissions
Define roles and row-level rules once, on the workspace database. Every app and workflow enforces them.
You create roles, assign them to workspace members, and write rules for which roles can
read/create/update/delete records in each table, optionally filtered to specific rows. Because the rules
live on the database rather than in any one app, every app and workflow enforces the same policy.
## Roles
* **Assigned manually** to workspace members (not condition-based).
* **`All team members`** is built in, and every internal member has it.
* **Roles govern the central database only.** App access is controlled by workspace membership, not roles.
**External app users bypass roles.** A public app must expose only what its users should see, so scope every
query to the signed-in user. See [Authentication](/concepts/authentication).
## Rules
Rules live in `zite.permissions.json` at the workspace root. Each rule grants (or denies, via
`effect: "deny"`) a set of `operations` to one or more `roles`, with an optional **`rowFilter`** comparing a
record field to a `userField` (from the signed-in user) or a `staticValue`. See the
[permissions file reference](/framework/permissions-file) for the full schema.
## How access is decided
For a user, operation, and table:
1. **No rules** → the workspace `defaultPolicy` (`allow` by default, or `deny` to lock down).
2. Only rules matching the operation **and** one of the user's roles count; none matching → **denied**.
3. An unscoped **deny** rule blocks access and beats any allow.
4. Otherwise matching allow filters are OR'd (deny filters AND'd) into the visible row set.
Permissions are **table- and row-level** (no field-level). Set them in the
[dashboard](https://app.zite.com) roles UI or let an agent manage `zite.permissions.json`; they're enforced
on every database call, including the [Database API](/api/overview).
# Workflows
Source: https://developers.zite.com/concepts/workflows
Workflows are an app's backend: typed server-side logic triggered by the app, a schedule, or a webhook.
A **workflow** is server-side logic that runs securely away from the browser. Anything the client shouldn't
do (elevated database access, calling a third-party API with a secret key, a nightly job) belongs in a
workflow.
Each workflow is one file in an app's `src/api/` directory, defined with `createEndpoint` and typed
Zod input/output. The frontend calls it through a generated, tRPC-like client. See the
[workflow reference](/framework/workflows) for the full API.
## Three triggers
| Trigger | Config key | Invoked by | `context.user` | Reference |
| --------------- | ----------- | --------------------------------------------------------------------------- | ------------------ | ------------------------------------------ |
| HTTP / RPC | — (default) | The app via `zitejs/api`, or publicly at `POST /public/{flowId}/api/{name}` | The signed-in user | [Workflow reference](/framework/workflows) |
| Scheduled | `schedule` | A cadence, or once at a future time | `null` | [Schedules](/framework/schedules) |
| Inbound webhook | `webhook` | An external POST to a secret URL | `null` | [Webhooks](/framework/webhooks) |
Workflows run in an isolated cloud runtime. They use the database client, send email, call integrations
with securely-stored [secrets](/concepts/integrations-secrets), and enforce [permissions](/concepts/permissions)
on every database call.
# Workspaces
Source: https://developers.zite.com/concepts/workspaces
A workspace is one database plus the apps and workflows built on it. Roles and permissions are defined once, at the workspace level.
A **workspace** is the top-level container in Zite. It ties together:
* **One database:** the tables, fields, and records your system runs on.
* **Roles and permissions:** defined once, on the database, and shared by everything in the workspace.
* **Many apps and workflows:** each built on that same database.
This is the core idea: you model your data and access rules once, then build as many apps on top as you need.
A workspace has a single stable id. You'll see the same id referred to as the **workspace id**, the
**base id** (`baseId`), or the **base public identifier** depending on context. They're the same thing.
It addresses the workspace, its database, and its underlying code repository.
## What's in a workspace
A single Zite database with tables, fields, and records. Every app reads and writes it through a
typed client.
React apps built on the database. A workspace can have many (a customer portal, an internal admin
tool, a dashboard), all sharing the same data.
Backend workflows that run business logic: called from an app, on a schedule, or from an inbound webhook.
Roles and row-level rules defined on the database and enforced everywhere.
Under the hood, a workspace is a git-backed monorepo: one repository holding every app and workflow,
plus the generated types and shared design system. You rarely touch this directly; agents build inside it.
See [Project structure](/framework/project-structure) for the layout.
## Why one database, many apps
Because everything in a workspace shares one database:
* **Data stays consistent.** An internal ops tool and a customer-facing portal read and write the same records.
* **Permissions are defined once.** You set roles and row-level rules on the database, and every app inherits them.
* **Building is fast.** New apps start with the schema, types, auth, and design system already in place.
## Creating a workspace
You can create a workspace from the [dashboard](https://app.zite.com), or ask an agent to do it through
the [Zite MCP](/mcp/overview) with `create_workspace`. A workspace can start as **database-only** (no apps yet), and
apps are added later, in the same workspace.
# Publishing
Source: https://developers.zite.com/deploy/publishing
How Zite apps go live: publishing, versions and rollback, custom domains, and audit logs.
Publishing takes an app's latest successful build live. Every commit produces a versioned build and
snapshot, so shipping is safe and reversible.
## From commit to live
A `commit` type-checks, pushes to the workspace repo, and builds a **snapshot** in the background. Each
commit is a version.
`publish_app` pins the latest **successful** build as the published version. If the build is still
running or failed, publishing is blocked until it's green.
External apps deploy to an edge worker at a public URL; internal apps become available to your
organization.
You publish by asking an agent (`publish_app`) or from the editor.
## Internal vs external
Served to members of your Zite organization only. No public URL.
Deployed to a public edge worker at `https://.zite.so` (or your custom domain), with the
app's sign-in for its users.
Changing an app's [access mode](/concepts/authentication) between internal and external is a confirmed
action.
## Versions & rollback
Every version is backed by a commit and its snapshot. From the app's **History** you can:
* Browse previous versions and preview them.
* **Restore** an earlier version to roll back.
Because each build carries its commit, you always know exactly what's live.
## Custom domains
External apps can be served from:
* A **custom Zite subdomain**, such as `yourname.zite.so`.
* A **custom domain** you own. Connect it with a CNAME record; TLS is handled automatically.
## Audit logs
Zite records an audit trail at the account level covering deploys, permission changes, and administrative
actions, so you can see who changed what and when.
# Auth
Source: https://developers.zite.com/framework/auth
useAuth on the frontend and context.user in workflows: reading the signed-in user, signing in, and signing out.
The `zitejs/auth` module gives your frontend the signed-in user and sign-in/out actions. In backend
[workflows](/framework/workflows), the same user is available as `context.user`. See
[Authentication](/concepts/authentication) for the concepts (access modes and user sync).
## useAuth (frontend)
```tsx theme={"dark"}
import { useAuth } from 'zitejs/auth';
function Account() {
const { user, isLoading, loginWithRedirect, logout } = useAuth();
if (isLoading) return null;
if (!user) return ;
return (
Signed in as {user.email}
);
}
```
`useAuth()` returns:
| Field | Type | Notes |
| ------------------- | ------------------- | ------------------------------------------------ |
| `user` | `User \| undefined` | The signed-in user; `undefined` while unresolved |
| `isLoading` | `boolean` | `true` until auth resolves |
| `loginWithRedirect` | `(opts?) => void` | Redirect to the sign-in page |
| `logout` | `(opts?) => void` | Sign out |
Both functions take an optional `opts` object:
| Function | Option | Type | Notes |
| ------------------- | ------------- | --------------------- | ------------------------------------ |
| `loginWithRedirect` | `redirectUrl` | `string` | Where to land after signing in |
| `loginWithRedirect` | `initialView` | `'login' \| 'signup'` | Open the sign-in or the sign-up view |
| `logout` | `returnTo` | `string` | Where to land after signing out |
The `User` type is `{ id, email, firstName?, lastName?, … }`. With [user sync](/concepts/authentication#user-sync)
enabled, your users-table fields are merged in and typed, so `user.plan` or `user.team` are available.
In an **internal** app, users are signed in automatically, so `loginWithRedirect` and `logout` are not
used (they throw if called). Use them in **external** apps.
## context.user (backend)
Set `authenticated: true` on a workflow and read `context.user`:
```typescript theme={"dark"}
import { createEndpoint } from 'zitejs/backend';
import { zite } from 'zitejs/db';
export default createEndpoint({
authenticated: true,
execute: async ({ context }) => {
// scope the query to the signed-in user
return zite.Orders.findAll({ filter: { customerEmail: context.user.email } });
},
});
```
The same synced fields available on the frontend `User` are available on `context.user`, and can be
referenced in row-level [permission](/concepts/permissions) filters via `userField`.
# Context
Source: https://developers.zite.com/framework/context
Documents you write for the agent: data model notes, policies, specs, and brand guidelines, stored in context/ at the workspace root.
`context/` is a directory at the root of a [workspace monorepo](/framework/project-structure) that holds
documents you write for the agent. Data model notes, refund policies, brand guidelines, meeting decisions:
the business knowledge behind the code, in a place the agent will read it, instead of something you
re-explain in every chat.
Context belongs to the workspace, so every app, workflow, and agent session in that workspace sees it.
## Layout
```text theme={"dark"}
my-workspace/
├── context/
│ ├── index.json # manifest: title, description, and source per file
│ ├── data-model-notes.md
│ ├── refund-policy.md
│ └── brand-guidelines.md
└── apps/
```
Context files are Markdown, directly inside `context/`. Subdirectories are not part of the library.
## How the agent uses it
Every turn, the agent gets an inventory of your context: each file's title, path, and one-line
description, and nothing else.
```text theme={"dark"}
Workspace context (docs the user added for you, in context/):
- Refund policy (context/refund-policy.md) — Approval thresholds, the 60-day cutoff, and EU cancellation rights
- Brand guidelines (context/brand-guidelines.md) — Required colors, fonts, and writing tone for anything customer-facing
```
From those lines it decides what is worth opening for the request in front of it, then reads those files
with its normal file tools. Bodies are never inlined, so a large library costs almost nothing until a
document is actually relevant.
The agent is told to prefer what a context document says over its own assumptions, and to treat the
library as yours: it will not edit, rename, or delete a document unless you ask. Agents connected through
the [Zite MCP](/mcp/overview) get the same instruction in their framework guide.
Because descriptions are the only thing the agent matches on, write them (or let Zite write them) as
concrete nouns: product names, table names, screens, policies. "Company guidelines and standards" tells
the agent nothing.
## The manifest
`context/index.json` decorates the files with a title, description, and where each one came from:
```json theme={"dark"}
{
"version": 1,
"files": [
{
"path": "context/refund-policy.md",
"title": "Refund policy",
"description": "Approval thresholds, the 60-day cutoff, and EU cancellation rights",
"source": "written"
},
{
"path": "context/brand-guidelines.md",
"title": "Brand guidelines",
"description": "Required colors, fonts, and writing tone for anything customer-facing",
"source": "google-doc",
"google": {
"docId": "1AbC...",
"url": "https://docs.google.com/document/d/1AbC...",
"syncedAt": "2026-08-30T17:04:11.320Z"
}
}
]
}
```
The files on disk are the source of truth, not the manifest. A `context/*.md` file with no entry still
appears in the library, titled from its first `# H1` or from its filename. An entry whose file is gone is
dropped. A malformed `index.json` degrades to an empty manifest rather than breaking anything, and the
library rebuilds from the files themselves.
That means an agent (or you, in the code editor) can add a context document by writing the Markdown file.
Adding a matching entry to `index.json` is what gives it a real description in the inventory.
| Field | Notes |
| ------------- | --------------------------------------------------------------------- |
| `path` | Repo-relative, always `context/.md`. The stable id for the file |
| `title` | Up to 80 characters, one line |
| `description` | Up to 140 characters, one line. This is what the agent matches on |
| `source` | `written`, `upload`, or `google-doc` |
| `google` | Drive linkage for a `google-doc` file: `docId`, `url`, `syncedAt` |
## Adding context
Open your workspace and use the **Context** tab. There are three ways in:
Type or paste anything the agent should know.
Drop or browse for `.md`, `.markdown`, `.txt`, `.csv`, or `.json`.
Pick a doc from Drive. Zite stores a snapshot you can refresh.
Zite writes the title and one-line description for you when a document is added, and rewrites the
description when you edit the body. Both are editable, and a title you supply is always kept verbatim.
A Google Doc is a snapshot: Drive owns the content, so the document is read only in Zite and **Refresh
from Google** re-pulls it. Written and uploaded documents are editable in place.
Every add, edit, and delete is a commit in the workspace's version history, alongside the agent's own
changes.
PDF and Word files are not accepted. A binary in `context/` is invisible to the agent, so adding one
would look like it worked while doing nothing.
## Limits
| Limit | Value |
| ------------------- | ----------------------------------------------------- |
| Files per workspace | 50 |
| Size per file | 600 KB |
| Title | 80 characters |
| Description | 140 characters |
| Location | Markdown files directly inside `context/`, no nesting |
## What to put in it
Good candidates are the things you would otherwise say out loud at the start of every chat:
* **Data model notes.** How your entities relate, which fields are authoritative, what a status value means.
* **Policies and rules.** Approval thresholds, cutoffs, regional exceptions, who can see what.
* **Brand and voice.** Colors, fonts, and tone for anything customer-facing.
* **Process and decisions.** How a team actually works, and what was already decided and why.
* **Specs.** What a feature is supposed to do, before someone builds it.
Context files are committed to the workspace repo and read by the agent, so they are not the place for
credentials. Use [integrations and secrets](/concepts/integrations-secrets) for those.
# Database client
Source: https://developers.zite.com/framework/database-client
The typed zitejs/db client: findAll, findOne, create, update, delete, bulkCreate, and read-only SQL with zite.sql().
Every app gets a typed database client, generated from the workspace schema. Import it from `zitejs/db`:
```typescript theme={"dark"}
import { zite } from 'zitejs/db';
```
The client is used inside backend [workflows](/framework/workflows). The frontend never talks to the
database directly. Each table is a property on `zite`, addressed by its **SDK name** (PascalCase), with
fully-typed record types. Read the generated `.zite/db.ts` to see the exact names and types.
## Methods
```typescript theme={"dark"}
// Query many rows
const { records, hasMore } = await zite.Tickets.findAll({
filter: { status: 'open' },
sort: [{ field: 'createdAt', direction: 'desc' }],
limit: 100,
offset: 0,
});
// One row by id (or by filter)
const ticket = await zite.Tickets.findOne({ id: 'rec_123' });
const byEmail = await zite.Customers.findOne({ filters: { email: 'a@b.com' } });
// Create, update, delete
const created = await zite.Tickets.create({ record: { subject: 'Help', status: 'open' } });
const updated = await zite.Tickets.update({ id: created.id, record: { status: 'closed' } });
await zite.Tickets.delete({ id: created.id });
// Insert many (optionally upsert on matching fields)
const result = await zite.Customers.bulkCreate({
records: [{ email: 'a@b.com' }, { email: 'c@d.com' }],
matchOn: ['email'],
});
```
| Method | Arguments | Returns |
| ------------ | ---------------------------------------------- | -------------------------- |
| `findAll` | `{ limit?, offset?, sort?, filter?, fields? }` | `{ records, hasMore }` |
| `findOne` | `{ id? }` or `{ filters? }` (+ `fields?`) | the record, or `undefined` |
| `create` | `{ record }` | the created record |
| `update` | `{ id, record }` | `{ id, fields }` |
| `delete` | `{ id }` | `{ id }` |
| `bulkCreate` | `{ records, matchOn? }` | `{ success, records }` |
## Filters and sorting
Filters are objects keyed by field SDK name. A bare value means equality; an object applies an operator:
```typescript theme={"dark"}
{ status: 'open' } // equality
{ createdAt: { gt: '2026-01-01' } } // operator
```
| Operator | Meaning | Example |
| -------------- | --------------------- | ------------------------------------- |
| *(bare value)* | Equals | `{ status: 'open' }` |
| `contains` | Substring match | `{ subject: { contains: 'refund' } }` |
| `gt` | Greater than | `{ createdAt: { gt: '2026-01-01' } }` |
| `gte` | Greater than or equal | `{ priority: { gte: 3 } }` |
| `lt` | Less than | `{ createdAt: { lt: '2026-02-01' } }` |
| `lte` | Less than or equal | `{ priority: { lte: 2 } }` |
There is no `eq` or `neq` here: equality is the bare-value shorthand. The row filters in
[`zite.permissions.json`](/framework/permissions-file) are a separate system and *do* have `eq` and `neq`.
Sort is an array: `sort: [{ field: 'createdAt', direction: 'desc' }]`. Use `fields: ['subject', 'status']`
to fetch only the columns you need.
`findAll` returns at most **2,000 records** (`hasMore` tells you there are more). For counts, sums,
group-bys, and joins, use `zite.sql()`. Aggregating in JavaScript over a capped result set gives wrong
numbers on large tables.
## Read-only SQL
`zite.sql()` runs a single read-only `SELECT` against the workspace database using human-readable SDK names:
```typescript theme={"dark"}
const { rows, rowCount, truncated } = await zite.sql({
query: `
SELECT "status", COUNT(*) AS n
FROM "Tickets"
WHERE "createdAt" > $1
GROUP BY "status"
`,
params: ['2026-01-01'],
});
```
Always use SDK names, never display labels. A wrong name is a runtime error, not a type error:
| Identifier | Convention | Quote? | Example |
| -------------------- | ---------------------------------- | -------- | ------------------------------------------ |
| Table | PascalCase SDK name | Yes | `"Tickets"` |
| Field | camelCase SDK name | Yes | `"assigneeEmail"` |
| System column | lowercase | Optional | `id`, `created_at`, `updated_at` |
| Link table | Both table SDK names, alphabetical | Yes | `"Tickets"` + `"Users"` → `"TicketsUsers"` |
| Link table id column | camelCase table name + `Id` | Yes | `"ticketsId"`, `"usersId"` |
Postgres lowercases unquoted names, which is why everything but the system columns must be double-quoted.
The `.zite/db.ts` header lists the link tables for your workspace.
`SELECT` only. Pass runtime values through `params` (`$1`, `$2`, …), and never string-interpolate them into
the query. Soft-deleted rows are excluded automatically.
Results are capped at 2,000 rows; `truncated` is `true` when the cap is hit.
## Also on the client
The generated `zite` object also exposes `zite.notifications` (see [Notifications](/framework/utilities#notifications))
and `zite.meta.listUsers()` for the workspace's users.
# Zite.js
Source: https://developers.zite.com/framework/overview
The framework every Zite app is built on: a typed database client, backend workflows, auth, schedules, and webhooks over one workspace database.
Zite.js is the framework for building business operating systems: the internal tools, portals, admin
consoles, and workflows a company runs on. Every Zite app is a Zite.js app: real TypeScript in a
[workspace monorepo](/framework/project-structure) that you and your agents can read and extend.
The framework assumes one database per [workspace](/concepts/workspaces). Tables, roles, and row-level
rules are defined once on that database, and the typed client applies them on every call, so an app is
mostly the screens and the [workflows](/framework/workflows), not an access layer you rebuild each time.
## The surface
Platform code is imported through `zitejs/*` aliases. They're generated into your workspace from its schema
and workflows, which is why they know your table and workflow names:
| Import | What it gives you |
| ----------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| [`zitejs/db`](/framework/database-client) | The typed database client: `findAll`, `create`, and read-only `zite.sql()` |
| [`zitejs/backend`](/framework/workflows) | `createEndpoint`, for typed backend workflows |
| [`zitejs/api`](/framework/workflows) | Typed callers for your app's workflows, from React |
| [`zitejs/auth`](/framework/auth) | `useAuth` and the signed-in user |
| [`zitejs/schedules`](/framework/schedules) | Run workflows on a cadence, or once at a future time |
| [`zitejs/notifications`, `zitejs/pdf`, `zitejs/upload`](/framework/utilities) | In-app notifications, PDF generation, file uploads |
| [`zitejs/integrations`](/concepts/integrations-secrets) | Typed clients for connected third-party services |
Workflows can also receive [inbound webhooks](/framework/webhooks), and the workspace's roles and row-level
rules live in [`zite.permissions.json`](/framework/permissions-file).
## A workflow
A backend workflow is one file. It reads the database through the typed client, and the frontend calls it
with a generated, typed caller:
```typescript theme={"dark"}
// apps/helpdesk/src/api/listTickets.ts
import { createEndpoint } from 'zitejs/backend';
import { zite } from 'zitejs/db';
export default createEndpoint({
authenticated: true,
execute: async () =>
zite.Tickets.findAll({ filter: { status: 'open' } }),
});
```
## Who writes it
An agent connected to the [Zite MCP](/mcp/overview) scaffolds apps, writes the code, and publishes them
from a cloud sandbox. The output is not a black box. It's the monorepo above, which you can read, review,
and edit like any other TypeScript project.
Don't confuse the two clients. The `zite` client from [`zitejs/db`](/framework/database-client) is
generated *inside* a Zite app. The `Zite` client imported from the package root is for calling the
[Database API](/api/overview) from *outside* Zite with an API key. See the
[JavaScript SDK](/api/sdk).
## Next
How a workspace is laid out, and which files are generated.
Query and mutate your tables with the typed `zite` client.
# Permissions
Source: https://developers.zite.com/framework/permissions-file
The complete zite.permissions.json schema: roles, rules, effects, and row filters.
Roles and data-access rules for a workspace live in a `zite.permissions.json` file at the workspace root.
This is the schema reference; for the concepts and evaluation model, see [Roles & permissions](/concepts/permissions).
## Structure
```typescript theme={"dark"}
type ZitePermissionsJson = {
version: '1.0';
defaultPolicy?: 'allow' | 'deny'; // for tables with no rules; default 'allow'
roles: { id: string; name: string; description?: string }[];
tables: Record; // keyed by central-DB table SDK name
};
type Rule = {
roles: string[]; // role names this rule applies to
operations: ('read' | 'create' | 'update' | 'delete')[];
effect?: 'allow' | 'deny'; // default 'allow'
rowFilter?: RowFilter; // limits which rows the rule covers
};
type RowFilter =
| {
type: 'comparison';
field: string; // a field on the table
operator: 'eq' | 'neq' | 'contains' | 'gt' | 'gte' | 'lt' | 'lte';
userField?: string; // dynamic value from context.user
staticValue?: string | number | boolean; // OR a fixed value (exactly one of the two)
}
| { type: 'and'; filters: RowFilter[] }
| { type: 'or'; filters: RowFilter[] }
| { type: 'not'; filter: RowFilter };
```
## Fields
* **`version`**: always `"1.0"`.
* **`defaultPolicy`** sets what happens on a table with no rules: `allow` (open, the default) or `deny` (locked
unless a rule grants access).
* **`roles`**: each role has a stable `id` and a display `name`. Rules reference roles by **name**; the
`id` survives renames so member assignments never break. The built-in role
`{ id: "builtin:all-team-members", name: "All team members" }` applies to every internal member.
* **`tables`**: keyed by the table's SDK name (PascalCase). Each entry has a `rules` array.
## Rules
* **`roles`**: the role names this rule applies to.
* **`operations`**: any of `read`, `create`, `update`, `delete`. Listing several is shorthand for one rule
per operation.
* **`effect`**: `allow` (default) or `deny`.
* **`rowFilter`**: restricts which rows the rule covers (like a Postgres `USING` clause).
## Row filters
A comparison filter compares a record `field` to either a value from the signed-in user (`userField`) or a
fixed `staticValue` (**exactly one** of the two) using `eq`, `neq`, `contains`, `gt`, `gte`, `lt`, or
`lte`. Compose comparisons with `and`, `or`, and `not`.
`userField` can reference the base user fields (`id`, `email`, `firstName`, `lastName`) plus any fields
synced from your [users table](/concepts/authentication#user-sync).
## Example
```json theme={"dark"}
{
"version": "1.0",
"defaultPolicy": "deny",
"roles": [
{ "id": "builtin:all-team-members", "name": "All team members" },
{ "id": "role_manager", "name": "Manager" }
],
"tables": {
"Orders": {
"rules": [
{
"roles": ["All team members"],
"operations": ["read"],
"rowFilter": { "type": "comparison", "field": "ownerEmail", "operator": "eq", "userField": "email" }
},
{
"roles": ["Manager"],
"operations": ["read", "update", "delete"]
}
]
}
}
}
```
Here every team member can read only the orders they own, while Managers can read, update, and delete all
orders. Because `defaultPolicy` is `deny`, any table without rules is fully locked down.
Permissions are **table- and row-level** and govern the workspace database. There are no field-level
permissions, and external app users bypass roles entirely. See [Roles & permissions](/concepts/permissions).
# Project structure
Source: https://developers.zite.com/framework/project-structure
How a Zite workspace is laid out: the monorepo, generated .zite/ files, config files, and the import conventions.
A workspace is a `zitejs` monorepo: one repository holding every app and workflow, the generated types,
and a shared design system. Agents build inside it; this page is the map so you (and they) know where
everything lives.
## Layout
```text theme={"dark"}
my-workspace/
├── zite.config.json # project config: name + basePublicIdentifier (the database)
├── zite.schema.json # the enriched database schema (SDK names, field types)
├── zite.permissions.json # roles + row-level rules (see Permissions file)
├── .zite/ # generated: db.ts (typed database client) — never edit
├── context/ # docs you write for the agent (see Context)
├── packages/
│ └── components/ # shared shadcn/ui design system → @project/components
└── apps/
└── helpdesk/ # one directory per app
├── src/
│ ├── App.tsx # app entry (you write this)
│ ├── api/ # backend workflows — one file per workflow
│ ├── lib/ # raw integration SDK usage (Slack, Stripe, …)
│ └── index.css # theme (CSS variables)
├── .zite/ # generated: api.ts, user.ts, auth.ts, backend.ts — never edit
├── zite.config.json # app config: accessMode, integrations, userSync, …
└── vite / tailwind / tsconfig / package.json
```
## Generated files
The `.zite/` directories, at the workspace root and inside each app, are **generated and must never be
edited**. `src/main.tsx` is generated too. They're refreshed by `zitejs generate` whenever the schema or
workflows change (the build tools run this for you):
| File | Import as | Contains |
| ---------------------------------------- | --------------------- | --------------------------------------------------------- |
| `.zite/db.ts` (root) | `zitejs/db` | The typed database client (`zite.
`, `zite.sql`, …) |
| `apps/*/.zite/api.ts` | `zitejs/api` | Typed callers for the app's workflows |
| `apps/*/.zite/auth.ts` | `zitejs/auth` | `useAuth`, narrowed to this app's `User` type |
| `apps/*/.zite/backend.ts` | `zitejs/backend` | `createEndpoint`, `ZiteError`, typed `context` |
| `apps/*/.zite/integrations/airtable.ts` | `zitejs/integrations` | Typed Airtable client (if Airtable is connected) |
Read them to learn the exact table names, record types, and workflow signatures. Never modify them.
## Import conventions
* **Platform SDKs:** always via `zitejs/*` aliases: `zitejs/db`, `zitejs/backend`, `zitejs/api`,
`zitejs/auth`, and the utility modules (`zitejs/schedules`, `zitejs/notifications`, `zitejs/pdf`,
`zitejs/upload`). Never import from `.zite/` by relative path.
* **Shared UI:** from `@project/components/ui/*` (e.g. `import { Button } from '@project/components/ui/button'`).
* **Your own files:** components, hooks, and utilities you create in an app's `src/`, via relative paths
(`./components/Foo`).
## Config files
At the workspace root: `{ "project": { "name": "...", "basePublicIdentifier": "..." } }`. This is the workspace
name and the id of its database.
In each `apps//`: `accessMode`, `integrations`, `userSync`, `envVars`, `authentication`,
`seoSettings`, `pwaSettings`. See [Authentication](/concepts/authentication).
The enriched database schema: table and field SDK names, types, and options. Drives `.zite/db.ts`.
Roles and the row-level rules that govern the workspace database.
## Context
`context/` at the root holds Markdown documents you write for the agent: data model notes, policies,
specs, brand guidelines. The agent sees an inventory of them every turn and reads the ones relevant to
what it's building. See [Context](/framework/context).
# Schedules
Source: https://developers.zite.com/framework/schedules
Run workflows on a recurring cadence or once at a future time, inline on createEndpoint, or at runtime with ZiteSchedules.
Any [workflow](/framework/workflows) can run on a schedule instead of (or in addition to) being called
from an app. Scheduled runs execute in the background with **no user**: `context.user` is `null`, and the
context carries `scheduledAt`.
## Inline schedule
Add a `schedule` to `createEndpoint` to run it on a fixed cadence:
```typescript theme={"dark"}
// apps/helpdesk/src/api/dailyDigest.ts
import { z } from 'zod';
import { createEndpoint } from 'zitejs/backend';
export default createEndpoint({
description: 'Send the daily digest',
inputSchema: z.object({}),
outputSchema: z.object({ sent: z.number() }),
schedule: {
scheduleType: 'recurring',
schedule: { frequency: 'daily', interval: 1, times: ['09:00'] },
timezone: 'America/New_York',
},
execute: async () => {
// …build and send the digest
return { sent: 1 };
},
});
```
### The schedule shape
A schedule is either `recurring` or `oneTime`:
```typescript theme={"dark"}
type ZiteSchedule =
| {
scheduleType: 'recurring';
schedule:
| { frequency: 'minutely'; interval: number; activeWindow?: ActiveWindow }
| { frequency: 'hourly'; interval: number; minute?: number; activeWindow?: ActiveWindow }
| { frequency: 'daily'; interval: number; times: string[] } // "HH:MM"
| { frequency: 'weekly'; interval: number; daysOfWeek: DayOfWeek[]; times: string[] }
| { frequency: 'monthly'; interval: 1; monthlyDay: MonthlyDay; times: string[] };
endPolicy?: { type: 'never' } | { type: 'onDate'; endAt: string } | { type: 'afterOccurrences'; occurrences: number };
overlapPolicy?: 'skip' | 'allow'; // default 'skip'
timezone: string; // IANA, e.g. "America/New_York"
paused?: boolean;
}
| { scheduleType: 'oneTime'; fireAt: string; timezone: string; paused?: boolean };
```
Each `frequency` takes a different set of fields:
| `frequency` | Required | Optional |
| ----------- | ----------------------------------------------- | ------------------------ |
| `minutely` | `interval` | `activeWindow` |
| `hourly` | `interval` | `minute`, `activeWindow` |
| `daily` | `interval`, `times` | — |
| `weekly` | `interval`, `daysOfWeek`, `times` | — |
| `monthly` | `interval` (must be `1`), `monthlyDay`, `times` | — |
`monthly` is the one frequency that pins `interval` to `1`. Every other frequency accepts any number, so
`{ frequency: 'hourly', interval: 6 }` runs every six hours.
And the shared fields:
| Field | Type | Notes |
| --------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `times` | `string[]` | `"HH:MM"` strings, in the schedule's `timezone` |
| `timezone` | `string` | Required. IANA name, e.g. `"America/New_York"` |
| `activeWindow` | `ActiveWindow` | Minutely and hourly only. Restricts firing to certain hours/days |
| `monthlyDay` | `MonthlyDay` | Monthly only. Which day to fire on, see below |
| `endPolicy` | `never` · `onDate` · `afterOccurrences` | When the recurrence stops. Omit to run indefinitely |
| `overlapPolicy` | `'skip'` · `'allow'` | Defaults to `'skip'`, which prevents a new run from starting while the previous one is still going |
| `paused` | `boolean` | Suspends the schedule without deleting it |
`monthlyDay` picks the day either by number or by weekday occurrence:
```typescript theme={"dark"}
type MonthlyDay =
| { type: 'dayOfMonth'; day: number | 'last' } // 1–31, or the last day
| { type: 'weekdayOccurrence'; // e.g. the 2nd Tuesday
weekday: DayOfWeek;
occurrence: 1 | 2 | 3 | 4 | 'last' };
```
## Managing schedules at runtime
Import `ZiteSchedules` from `zitejs/schedules` to add or change schedules from inside your logic (for
example, scheduling a reminder when a record is created):
```typescript theme={"dark"}
import { ZiteSchedules } from 'zitejs/schedules';
// Schedule a one-time reminder
const { id } = await ZiteSchedules.add({
endpointId: 'sendReminder',
schedule: { scheduleType: 'oneTime', fireAt: '2026-08-01T09:00:00Z', timezone: 'UTC' },
inputs: { ticketId: 'rec_123' },
});
await ZiteSchedules.list({ endpointId: 'sendReminder' }); // { schedules: [...] }
await ZiteSchedules.update({ id, schedule: /* … */ });
await ZiteSchedules.remove({ id });
```
| Method | Arguments | Returns |
| -------- | ----------------------------------- | ------------------- |
| `add` | `{ endpointId, schedule, inputs? }` | `{ id, cronJobId }` |
| `list` | `{ endpointId? }` | `{ schedules }` |
| `update` | `{ id, schedule?, inputs? }` | `{ id, updated }` |
| `remove` | `{ id }` | `{ id, deleted }` |
Schedule `inputs` are limited to **32 KB**. Runtime schedule changes require the app to have been
published at least once.
# Utilities
Source: https://developers.zite.com/framework/utilities
Small zitejs helpers: in-app notifications, PDF generation, and file uploads.
## Notifications
Send in-app notifications from a backend [workflow](/framework/workflows) (`zitejs/notifications`, also
`zite.notifications` on the DB client):
```typescript theme={"dark"}
import { ZiteNotifications } from 'zitejs/notifications';
await ZiteNotifications.create({
recipients: ['user_abc'], // user ids
title: 'New ticket assigned',
body: 'Ticket #482 was assigned to you.', // optional
link: { path: '/tickets/482' }, // optional in-app destination
idempotencyKey: 'ticket-482-assigned', // optional; dedupes retries
});
```
Returns `{ created: number }`.
## PDF generation
Render HTML to a hosted PDF from a workflow (`zitejs/pdf`):
```typescript theme={"dark"}
import { ZitePdf } from 'zitejs/pdf';
const { url } = await ZitePdf.renderHtml({ html, filename: 'invoice.pdf' });
```
`renderHtml({ html, filename? })` → `{ url, filename }`. Max 25 MB.
## File uploads
Upload a file and get back a hosted URL. Frontend hook:
```tsx theme={"dark"}
import { useUpload } from 'zitejs/upload';
const { upload, isUploading } = useUpload();
const { url } = await upload(file); // File | Blob | ArrayBuffer | string
```
Or `uploadFile({ data, filename })` → `{ fileUrl }` from anywhere. Store the URL in an
[attachments field](/api/field-types)'s `{ url, filename }` shape.
# Webhooks
Source: https://developers.zite.com/framework/webhooks
Receive inbound webhooks from external services by adding a webhook trigger to a workflow.
Add a `webhook` trigger to a [workflow](/framework/workflows) and it runs whenever an external service
(Stripe, GitHub, a form provider) POSTs to its ingress URL.
These are **inbound** webhooks handled by your app's code. For **outbound** webhooks that notify your
systems when database records change, see the [Database API webhooks](/api/webhooks/webhooks).
## Defining a webhook workflow
```typescript theme={"dark"}
// apps/helpdesk/src/api/stripeEvents.ts
import { z } from 'zod';
import { createEndpoint } from 'zitejs/backend';
export default createEndpoint({
description: 'Handle Stripe webhook events',
webhook: {}, // marks this workflow as webhook-triggered
inputSchema: z.any(),
outputSchema: z.object({ received: z.boolean() }),
execute: async ({ input }) => {
// input is the raw parsed body; verify the provider signature here
return { received: true };
},
});
```
## The ingress URL
The workflow receives events at:
```text theme={"dark"}
https:///public/{flowId}/webhooks/{endpointId}/{token}
```
| Segment | Value |
| -------------- | -------------------------------------------- |
| `{flowId}` | The app's public identifier |
| `{endpointId}` | The workflow's id, as declared in `src/api/` |
| `{token}` | The secret that authenticates the request |
**The secret token in the path is the only authentication.** There is no framework-level HMAC. The token
is what proves the request is legitimate at the transport level. Treat the full URL as a credential, and
verify provider signatures yourself (see below) when the provider supports them.
The token is provided as an environment variable named `ZITE_WEBHOOK_TOKEN_`, where ``
is the workflow id upper-snake-cased, so workflow `stripeEvents` becomes
`ZITE_WEBHOOK_TOKEN_STRIPE_EVENTS`.
## Verifying provider signatures
The request body is delivered **raw** so you can run provider-native signature verification on the exact
bytes. For example, Stripe's `constructEvent`:
```typescript theme={"dark"}
import Stripe from 'stripe';
const stripe = new Stripe(process.env.ZITE_STRIPE_ACCESS_TOKEN);
const event = stripe.webhooks.constructEvent(rawBody, signatureHeader, process.env.ZITE_STRIPE_WEBHOOK_SECRET);
```
Inbound webhook bodies are limited to **1 MB**.
# Workflows
Source: https://developers.zite.com/framework/workflows
createEndpoint from zitejs/backend: typed input/output, authentication, errors, streaming, and calling workflows from the frontend.
A [workflow](/concepts/workflows) is one file in an app's `src/api/` directory that
default-exports `createEndpoint` from `zitejs/backend`: server-side logic with typed inputs and outputs.
```typescript theme={"dark"}
// apps/helpdesk/src/api/createTicket.ts
import { z } from 'zod';
import { createEndpoint, ZiteError } from 'zitejs/backend';
import { zite } from 'zitejs/db';
export default createEndpoint({
description: 'Create a support ticket',
authenticated: true,
inputSchema: z.object({ subject: z.string().min(1) }),
outputSchema: z.object({ id: z.string() }),
execute: async ({ input, context }) => {
if (!input.subject) throw new ZiteError('Subject is required', { statusCode: 400 });
const ticket = await zite.Tickets.create({
record: { subject: input.subject, status: 'open', assigneeEmail: context.user.email },
});
return { id: ticket.id };
},
});
```
Each workflow must be a **default export**, and the filename is the workflow id, a single segment
matching `^[a-zA-Z0-9_-]+$` (no subfolders under `src/api/`).
## Config
| Field | Type | Notes |
| --------------- | --------------- | ------------------------------------------------------------- |
| `description` | `string` | Human/agent-readable summary |
| `inputSchema` | Zod schema | Validates and types `input` |
| `outputSchema` | Zod schema | Validates and types the return value |
| `authenticated` | `boolean` | When `true`, `context.user` is populated |
| `stream` | `boolean` | Enables streaming responses (see below) |
| `schedule` | schedule config | Run on a cadence. See [Schedules](/framework/schedules) |
| `webhook` | webhook config | Receive inbound webhooks. See [Webhooks](/framework/webhooks) |
| `execute` | function | `async ({ input, context, stream? }) => output` |
## Context
`execute` receives a `context`:
| Property | Type | When populated |
| ---------------- | ------------------ | ---------------------------------------------------- |
| `user` | `{ id, email, … }` | When `authenticated: true`. `null` on scheduled runs |
| `userId` | `string` | Identifier for the current request |
| `organizationId` | `string` | Identifier for the current request |
| `scheduledAt` | `string` | Scheduled runs only. The ISO time the run was due |
`process.env.ZITE_APP_URL` is set to your app's public URL. Use it to build links instead of hardcoding.
## Errors
Throw `ZiteError` to return a controlled error with an HTTP status:
```typescript theme={"dark"}
throw new ZiteError('Not found', { statusCode: 404 }); // statusCode defaults to 500
```
## Calling workflows from the frontend
Workflows are called through a generated, typed client (like tRPC). Each workflow exports a camel-cased
caller plus its input/output types:
```tsx theme={"dark"}
import { createTicket, CreateTicketInputType, CreateTicketOutputType } from 'zitejs/api';
const { id } = await createTicket({ subject: 'Printer is down' });
```
After adding, renaming, or deleting a workflow file, regenerate the client with `npx zitejs generate` from
the workspace root (the [build tools](/mcp/build-tools) do this for you).
Workflows are also reachable publicly at `POST /public/{flowId}/api/{name}`.
## Streaming
Set `stream: true` and write chunks as you go:
```typescript theme={"dark"}
export default createEndpoint({
stream: true,
inputSchema: z.object({ prompt: z.string() }),
outputSchema: z.object({ text: z.string() }),
execute: async ({ input, stream }) => {
await stream.write({ token: 'thinking…' });
// stream.forward(asyncIterable) pipes an async iterable (e.g. an LLM stream) through
const text = await stream.forward(model.stream(input.prompt));
return { text };
},
});
```
On the frontend, streaming workflows are called with the generated streaming caller, which yields chunks
and resolves a final `result`.
# Introduction
Source: https://developers.zite.com/introduction
Zite is the agent-native app platform. Build apps and workflows on a unified database, with your favorite agent.
You describe what you want, and a connected agent builds it and publishes it. No local setup on your end.
## The mental model
Every app and workflow in a workspace shares one database, its schema, and its permissions.
Roles and row-level rules are set once, on the workspace database. Every app inherits them.
Apps are React apps; workflows are typed backend functions. An agent builds them in a cloud sandbox.
## Next
Connect an agent, describe an app, and publish it.
The `zitejs` surface your agent writes against.
# Platform limits
Source: https://developers.zite.com/limits
Result-set caps, execution timeouts, payload sizes, rate limits, and retention windows to design around.
Design around these platform limits:
| Area | Limit |
| ------------------------------- | ---------------------------------------------------------------- |
| `findAll` / SQL result set | 2,000 rows (use `zite.sql()` aggregates for larger tables) |
| `execute_sql` statement timeout | 10 s default, 60 s max |
| `execute_sql` bind parameters | 1,000 |
| Schedule inputs payload | 32 KB |
| Inbound webhook body | 1 MB |
| Workflow execution timeout | 150 s |
| Workflow execution logs | retained 90 days (lookback up to 168 h) |
| Database REST API rate limit | 30 requests/second per database |
| Database webhooks per database | 100 (max 20 events each) |
| Webhook delivery | 5 retries, backoff 5/10/20/40/80 s, 30 s timeout, 256 KB payload |
| PDF output | 25 MB |
Because `findAll` caps at 2,000 records, do counts, sums, and group-bys in the database with
[`zite.sql()`](/framework/database-client) rather than pulling rows into JavaScript.
The Database API returns `RateLimit-*` headers on every response. Back off as you approach the limit, or
email [support@zite.com](mailto:support@zite.com) to have it raised.
# The build loop
Source: https://developers.zite.com/mcp/build-loop
How an agent goes from an empty sandbox to a published app: sandboxes, editing, checking, committing, and verifying.
Building a Zite app with the MCP follows a tight loop that mirrors how Claude Code works: boot a sandbox,
write code, check it, commit, and publish, verifying at runtime before calling it done.
## Sandboxes
A sandbox is the agent's session, much like a browser tab. `create_sandbox(workspaceId)` boots a
cloud environment with the workspace's whole monorepo checked out, and returns a `sandboxId` plus the file
tree and the apps already in the workspace.
* **Every build tool takes the `sandboxId`.** `create_sandbox` is the only tool that returns one.
* **One sandbox per chat.** Two chats get separate sandboxes, so they never clobber each other.
* **You can work on any app in the workspace** from one sandbox, and creating a new app stays in the same session.
* Idle sandboxes stop automatically after about 15 minutes; the next call transparently re-boots.
`create_sandbox` also returns a **framework guide**: the concise spec of how Zite apps are structured. See
[The framework guide](/ai-skill).
## The loop
`create_app` scaffolds an empty app at `apps//`. This is a free scaffold, so no AI build runs.
Add backend workflows in `src/api/` and the React frontend in `src/`. Use `edit_file` for changes
(exact string replacement) and `write_file` for new files. Use `grep` and `read_file` to navigate.
After adding, renaming, or deleting workflow files, run `npx zitejs generate` from the workspace root
via `bash`. It refreshes the typed `zitejs/api` client. `check_app` and `commit` also run it for you.
`check_app` runs the type-check and build validations. Fix everything it reports before committing.
`commit` type-checks, commits and pushes, and kicks off a background build and snapshot. It returns the
editor URL to share. Builds run in the background, so a fresh commit reports `buildStatus: "building"`.
A clean type-check doesn't prove the app works. SQL identifier mistakes and integration errors only
surface at runtime. Exercise the key workflows (seed a record, call the dashboard workflow) and pull
`get_logs` to check for errors.
`publish_app` takes the latest successful build live. External apps get a `zite.so` URL. See
[Publishing](/deploy/publishing).
## Data access
The frontend never touches the database directly. It calls backend [workflows](/framework/workflows), which
query the [database client](/framework/database-client) (`zite.
` and `zite.sql()`). Read `.zite/db.ts`
first for exact table and field names, and prefer `zite.sql()` for counts, joins, and aggregates, because `findAll`
caps at 2,000 records.
## Conventions
* **[Project layout](/framework/project-structure):** imports via `zitejs/*` and `@project/components/ui/*`
aliases; never edit generated `.zite/` or `src/main.tsx`; only the bundled frontend libraries compile.
* **[Build tools](/mcp/build-tools):** every tool's parameters and behavior.
# Build tools
Source: https://developers.zite.com/mcp/build-tools
Reference for the 17 Zite MCP build tools: creating workspaces and apps, editing code in a sandbox, checking, committing, and publishing.
These are the **build** tools exposed by the Zite MCP at `https://mcp.zite.com/mcp`. The server also
exposes 16 [data tools](/api/mcp/overview) for reading and writing records. The file-editing and check
tools run inside a sandbox and take the `sandboxId` returned by `create_sandbox`. The workspace tools,
`get_logs` and `publish_app` (both keyed by `appId`), `setup_integration`, and `send_feedback` work
without one.
**Availability.** The build tools are free for 14 days. The trial starts the first time your agent calls
`create_sandbox`, with no card and nothing to switch on. After that they're on the Business plan and
above: they stay listed in your client, and calling one returns an upgrade message. The workspace tools,
the data tools, and `send_feedback` are always available, and the Zite editor builds apps on any plan.
## Workspaces & sandboxes
| Tool | Parameters | Returns |
| ------------------ | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `create_workspace` | `name` | `{ workspaceId, name, url, next }`. A new, empty workspace (a database with no tables) |
| `list_workspaces` | — | `{ workspaces[] }`. Every workspace you can access, each with its apps and forms |
| `get_workspace` | `workspaceId`, `sandboxId?` | `{ id, name, url, apps, forms, overview }`. Pass `sandboxId` to also read the project `overview.md` |
| `create_sandbox` | `workspaceId` | `{ sandboxId, workspaceId, apps, files, integrations, connectableServiceTypes, docs, guide }`. Boots a sandbox and returns the [framework guide](/ai-skill) |
| `create_app` | `sandboxId`, `name`, `accessMode?` | `{ appId, name, dir, editorUrl }`. Scaffolds an empty app (`accessMode` defaults to `internal`) |
`create_app` is a **free scaffold**. It creates an empty app with no AI build, so it spends no Zite
credits. The scaffold lands in the repo with the agent's first `commit`.
## Files & editing
| Tool | Parameters | Returns |
| ------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------- |
| `list_files` | `sandboxId`, `filter?` | `{ files, totalCount }`. Substring or `*`-glob filter, capped at 1,000 |
| `grep` | `sandboxId`, `pattern`, `context?` | `{ results, matchCount }`. Extended-regex search over code, `context` lines 0–10 (default 2) |
| `read_file` | `sandboxId`, `path`, `offset?`, `limit?` | `{ path, content, totalLines, offset }` |
| `edit_file` | `sandboxId`, `path`, `oldText`, `newText` | `{ path, replaced }`. Exact string replacement, the primary way to change code |
| `write_file` | `sandboxId`, `path`, `content` | `{ path, bytes }`. New file or full rewrite |
| `bash` | `sandboxId`, `script` | `{ stdout, stderr }`. Runs in the sandbox at the workspace root |
`edit_file` requires `oldText` to appear **exactly once** in the file. If it matches zero times or more
than once, the edit fails with guidance. Include more surrounding context to make the match unique.
`bash` is the escape hatch for npm installs, `git` commands, file moves, and one-off scripts. Prefer the
structured file tools for editing so behavior stays predictable. Real commits should go through `commit`,
not raw `git commit`.
## Checks & logs
| Tool | Parameters | Returns |
| ----------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `check_app` | `sandboxId`, `appId?` | `{ ok, diagnostics, endpointErrors }`. Regenerates types, then type-checks and validates config |
| `get_logs` | `appId`, `workflowId?`, `status?`, `mode?`, `limit?`, `hoursAgo?` | `{ runs, totalCount }`. Workflow execution logs (inputs, output, console, stack traces) |
Use `get_logs` to catch runtime errors a type-check can't. Its filters:
| Parameter | Values | Default | Description |
| ------------ | --------------------------- | ------------- | ---------------------------------------------------- |
| `appId` | string, required | — | App to read logs for |
| `workflowId` | string | all workflows | Narrow to a single workflow |
| `status` | `all` · `success` · `error` | `all` | Filter by run outcome |
| `mode` | `live` · `preview` · `both` | `both` | `live` is production traffic, `preview` is test runs |
| `limit` | 1–50 | `5` | Number of runs returned |
| `hoursAgo` | 1–168 | `24` | Lookback window in hours |
## Ship
| Tool | Parameters | Returns |
| ------------- | ---------------------- | ------------------------------------------- |
| `commit` | `sandboxId`, `message` | `{ commitSha, changedApps[], message? }` |
| `publish_app` | `appId` | `{ published, liveUrl, accessMode, note? }` |
`commit` type-checks, commits and pushes, then **builds and snapshots in the background**. The returned
`changedApps` report `buildStatus: "building"`. If the remote has newer commits it returns a conflict;
run `git pull --rebase origin main` via `bash`, resolve, and commit again.
`publish_app` requires a **successful** build. If the latest build is still running or failed, it returns
an error. Wait for the build, or fix errors with `check_app` / `get_logs` and commit again. External
apps publish to `https://.zite.so`.
## Integrations
| Tool | Parameters | Returns |
| ------------------- | --------------------------------------- | ------------------------------------------------------------------------------ |
| `setup_integration` | `workspaceId`, `service?`, `sandboxId?` | `{ connectableServiceTypes, orgServices, attached, connectUrl, instructions }` |
`setup_integration` returns the integration picture for a workspace: which third-party services can be
connected, the accounts your organization has already connected (`orgServices`), and, when a `sandboxId`
is passed, what's already attached to the database and each app. Two paths from there:
* **The org already has an account for the service.** The agent attaches it itself by writing the config
entry with the returned `connectionId`. No user interaction needed.
* **No account yet.** The agent shares `connectUrl` with you; the OAuth sign-in happens in your browser,
then the agent calls the tool again to pick up the new connection.
See [Integrations & secrets](/concepts/integrations-secrets).
## Feedback
| Tool | Parameters | Returns |
| --------------- | ------------------------------------------------------------- | -------------- |
| `send_feedback` | `severity`, `summary`, `details`, `toolName?`, `workspaceId?` | `{ ok, note }` |
Lets the agent report platform problems (broken tooling, unexpected behavior, docs gaps) straight to the
Zite team. `severity` is one of `broken_tooling`, `unexpected_behavior`, `environment_failure`,
`bad_schema`, or `other`. Available on every connection, with or without app building enabled.
# Connect the Zite MCP
Source: https://developers.zite.com/mcp/connect
Add the Zite MCP server to Claude, Claude Code, Cursor, VS Code, Codex, Windsurf, Zed, or any MCP client and authorize it with OAuth.
The Zite MCP server URL is the same everywhere:
```text theme={"dark"}
https://mcp.zite.com/mcp
```
It uses OAuth. The first time you connect, you'll sign in to Zite and authorize access. The agent then
acts as you, limited to the workspaces you can access.
This is the same server that provides the [data tools](/api/mcp/overview). Connecting once gives your
agent both the build tools and the data tools.
Pick your tool below.
Zite is an approved connector in the Claude directory, so there's nothing to configure by hand.
[Open Zite in Claude](https://claude.ai/directory/connectors/dbc52c7f-cbc6-49f4-be8d-ca58126b85ed)
to go straight to the listing, or find **Zite** under **Settings → Connectors** in
[claude.ai](https://claude.ai) or Claude Desktop.
Click **Connect** and complete the Zite sign-in. The Zite tools are now available in your chats.
Connectors are account-level, so connecting once makes Zite available on web, desktop, and mobile.
**Claude Team & Enterprise (managed authorization).** On managed workspaces, connectors are enabled by a
workspace admin rather than per person. An admin enables the Zite connector once from **Settings →
Connectors**, then members turn it on from the connector directory and authorize with their own Zite
account.
```bash theme={"dark"}
claude mcp add --transport http zite https://mcp.zite.com/mcp
```
Then run `/mcp` in a session, select **zite**, choose **Authenticate**, and complete the sign-in in your browser.
Zite is an approved app in the ChatGPT apps directory.
[Open Zite in ChatGPT](https://chatgpt.com/apps/zite/asdk_app_6a0dfddea830819180d3eaf355793e42),
or search for **Zite** in the ChatGPT apps directory.
Click **Connect** and complete the Zite sign-in. (Apps are available on paid ChatGPT plans.)
Add the server to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project):
```json theme={"dark"}
{
"mcpServers": {
"zite": {
"url": "https://mcp.zite.com/mcp"
}
}
}
```
You can also add it from **Cursor Settings → Tools & Integrations → MCP → Add**. Open **MCP** in settings and
click the server to complete the OAuth sign-in when prompted.
Create `.vscode/mcp.json` in your project:
```json theme={"dark"}
{
"servers": {
"zite": {
"type": "http",
"url": "https://mcp.zite.com/mcp"
}
}
}
```
Or run **MCP: Add Server** from the Command Palette (`Ctrl/Cmd + Shift + P`), choose **HTTP**, paste
`https://mcp.zite.com/mcp`, and name it **zite**. Start the server and sign in when prompted. (Requires a
recent VS Code with MCP support.)
```bash theme={"dark"}
codex mcp add zite --url https://mcp.zite.com/mcp
```
Or add it to `~/.codex/config.toml`:
```toml theme={"dark"}
[features]
experimental_use_rmcp_client = true
[mcp_servers.zite]
url = "https://mcp.zite.com/mcp"
```
Then authenticate: `codex mcp login zite`.
Open **Settings** (`Ctrl/Cmd + ,`) → **Cascade → MCP servers → Add Server → Add custom server**, then add:
```json theme={"dark"}
{
"mcpServers": {
"zite": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.zite.com/mcp"]
}
}
}
```
A browser window opens to complete the Zite sign-in.
Open settings (`Cmd + ,`) and add:
```json theme={"dark"}
{
"context_servers": {
"zite": {
"source": "custom",
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.zite.com/mcp"],
"env": {}
}
}
}
```
Any client that supports remote MCP servers can connect with:
* **URL:** `https://mcp.zite.com/mcp`
* **Transport:** HTTP (streamable)
* **Auth:** OAuth
For clients that only support local (stdio) servers, bridge to the remote server with
[`mcp-remote`](https://www.npmjs.com/package/mcp-remote):
```bash theme={"dark"}
npx -y mcp-remote https://mcp.zite.com/mcp
```
Authorization looping or failing? Clear the local MCP auth cache with `rm -rf ~/.mcp-auth` and reconnect. More
fixes in [Troubleshooting](/troubleshooting).
## Next
Once connected, here's how the agent builds an app end to end.
# Build with agents
Source: https://developers.zite.com/mcp/overview
The Zite MCP lets an AI agent build and edit Zite apps in a cloud sandbox, using your own model, with no Zite AI credits.
The **Zite MCP** is a [Model Context Protocol](https://modelcontextprotocol.io) server that gives an AI
agent (Claude, ChatGPT, Cursor, or any MCP client) the tools to create workspaces, edit code in a cloud
sandbox, run checks, and publish apps. It feels like Claude Code operating on a Zite app, and it uses
**your** model, so no Zite AI credits are spent.
It's one server at `https://mcp.zite.com/mcp` with two tool groups on a single connection: **17 build tools**
(create / edit / commit / publish apps) and **16 data tools** (read / write records). The agent acts **as
you**, scoped to your workspaces, with every change attributed to you.
Add `mcp.zite.com/mcp` to your AI tool and authorize it.
From an empty sandbox to a published app.
Every build tool's parameters and behavior.
Read and write records from the same connection.
Automating builds from CI or your own backend? The [Build API](/build-api/overview) exposes these same
operations as REST.
## All tools
Every tool below is available on one connection to `https://mcp.zite.com/mcp`. Build tools are documented
in full on [Build tools](/mcp/build-tools); data tools on [Data tools](/api/mcp/tools).
| Group | Tool | Description |
| ------------------- | --------------------- | -------------------------------------------------------------------------------- |
| **Workspaces** | `create_workspace` | Create a new, empty workspace (a database with no tables) |
| | `list_workspaces` | List every workspace you can access, with its apps and forms |
| | `get_workspace` | Read one workspace: its apps, forms, and project overview |
| **Sandboxes** | `create_sandbox` | Boot a build sandbox for a workspace and return the framework guide |
| | `create_app` | Scaffold an empty app in the sandbox (free, no AI build runs) |
| **Files & editing** | `list_files` | List files in the sandbox, with a substring or glob filter |
| | `grep` | Regex search across the sandbox's code |
| | `read_file` | Read a file, optionally a line range |
| | `edit_file` | Replace an exact string in a file, the primary way to change code |
| | `write_file` | Create a new file or fully rewrite one |
| | `bash` | Run a shell script in the sandbox (npm installs, git, one-off scripts) |
| **Checks & logs** | `check_app` | Regenerate types, then type-check and validate app config |
| | `get_logs` | Read workflow run logs: inputs, output, console, stack traces |
| **Ship** | `commit` | Type-check, commit and push, then build and snapshot in the background |
| | `publish_app` | Take the latest successful build live |
| **Integrations** | `setup_integration` | List connectable services, attach an existing connection, or return an OAuth URL |
| **Feedback** | `send_feedback` | Report a platform problem to the Zite team |
| **Schema** | `get_database_schema` | Get every table and field in a database in one call |
| | `get_table_schema` | Get the fields for a single table |
| | `create_table` | Create a table with the given fields |
| | `update_table` | Rename a table |
| | `delete_table` | Permanently delete a table and its records |
| | `create_field` | Add a field (column) to a table |
| | `update_field` | Change a field's name, type, or configuration |
| | `delete_field` | Permanently delete a field and its data |
| **Records** | `query_records` | Query records with filtering, sorting, and pagination |
| | `get_record` | Get a single record by ID |
| | `create_record` | Create a record |
| | `update_record` | Update a record's fields |
| | `delete_record` | Delete a record |
| | `bulk_create_records` | Create many records in one call |
| | `bulk_update_records` | Update many records in one call |
| **SQL** | `execute_sql` | Run a read-only SQL query using human-readable table and field names |
**Availability.** The build tools are free for 14 days. The trial starts the first time your agent calls
`create_sandbox`, with no card and nothing to switch on. After that they're on the Business plan and
above: they stay listed in your client, and calling one returns an upgrade message. The workspace tools,
the data tools, and `send_feedback` are always available, and the Zite editor builds apps on any plan.
# Quickstart
Source: https://developers.zite.com/quickstart
Build and publish your first Zite app with your favorite AI agent.
Your agent runs on a cloud sandbox with your workspace's code, writes the app, and publishes it. No local
setup on your end.
A **workspace** is a Zite database plus the apps built on it. See [Workspaces](/concepts/workspaces).
## 1. Connect the Zite MCP
Add the Zite MCP server to your AI tool and authorize it. Claude and ChatGPT have native Zite
integrations in their app directories. Everywhere else, use the MCP URL:
```text theme={"dark"}
https://mcp.zite.com/mcp
```
```bash Claude Code theme={"dark"}
claude mcp add --transport http zite https://mcp.zite.com/mcp
# then run /mcp, select zite, and choose Authenticate
```
```text Claude theme={"dark"}
Approved connector. Open the listing, click Connect,
and sign in.
https://claude.ai/directory/connectors/dbc52c7f-cbc6-49f4-be8d-ca58126b85ed
```
```text ChatGPT theme={"dark"}
Approved app. Open it, click Connect, and authorize.
(Paid ChatGPT plans.)
https://chatgpt.com/apps/zite/asdk_app_6a0dfddea830819180d3eaf355793e42
```
```text Cursor theme={"dark"}
MCP settings → add a server
URL: https://mcp.zite.com/mcp
Transport: HTTP (streamable)
Auth: OAuth
```
On first connect you sign in and authorize access. The agent then acts as **you**, so it can only see
and change workspaces you have access to. Using VS Code, Codex, Windsurf, Zed, or another client? See the
per-tool steps in [Connect](/mcp/connect).
Building over MCP is free for 14 days, starting the first time your agent boots a sandbox. After that the build tools are on the Business plan and above.
## 2. Describe what you want to build
Tell the agent the system you want. Be specific about the data and the screens:
```text theme={"dark"}
Build a support-ticket app in a new workspace called "Helpdesk". Tickets have a
subject, description, status (open / in progress / closed), priority, and an
assignee.
```
The agent spins up the workspace (a fresh database) and a cloud sandbox, then designs the tables,
scaffolds the app, and writes the React UI and backend [workflows](/concepts/workflows). It iterates
until the app type-checks and runs. That's the [build loop](/mcp/build-loop), which you can watch live
in the editor.
Already have a workspace? Name it in your prompt and the agent builds into it instead of creating one.
## 3. Choose who can access it
New apps are **internal** by default, so only your Zite organization can open them. To make an app public, ask
the agent to set it **external**, which adds a sign-in screen (email, Google, or SSO). See
[Authentication](/concepts/authentication).
## 4. Publish
```text theme={"dark"}
Publish the app.
```
External apps go live at `https://.zite.so`; internal apps become available to your team. Add a
custom domain or roll back any time. See [Publishing](/deploy/publishing).
Next: [how building works](/mcp/overview) and [the database client](/framework/database-client).
# Use the APIs
Source: https://developers.zite.com/quickstart-api
Zite has two REST APIs: the Database API for reading and writing your data, and the Build API for creating and deploying apps.
Zite has two REST APIs. Both authenticate with the same Bearer API key, and both act as **you**,
reaching only the workspaces you have access to.
Read, write, and query the records in your workspace database over REST or the JavaScript SDK.
Create workspaces, edit app code, and publish deployments. The REST equivalent of the Zite MCP build tools.
## Get an API key
Generate a key in [Developer settings](https://app.zite.com/home/settings/developer), then send it as a
Bearer token on every request:
```bash theme={"dark"}
Authorization: Bearer
```
## Database API
Already have data in Zite and just need to reach it from your own code? Skip the agent and app framework and
talk to your tables directly. Base URL:
```text theme={"dark"}
https://tables.zite.com/api/v1
```
Open a database in the [dashboard](https://app.zite.com) to find its IDs in the URL
(`app.zite.com/database/{databaseId}/{tableId}/…`), then call it with the
[JavaScript SDK](/api/sdk) or plain HTTP:
```typescript SDK theme={"dark"}
import { Zite } from 'zitejs';
const zite = new Zite(process.env.ZITE_API_KEY!);
const { records } = await zite.record.list(databaseId, tableId, {
filter: { status: 'open' },
limit: 50,
});
```
```bash cURL theme={"dark"}
curl https://tables.zite.com/api/v1/bases/DATABASE_ID/tables/TABLE_ID/records/list \
-H "Authorization: Bearer $ZITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "filter": { "status": "open" }, "limit": 50 }'
```
Full reference: [Database API](/api/overview) · [JavaScript SDK](/api/sdk) · [Field types](/api/field-types).
## Build API
Create workspaces, edit app code, and ship deployments from CI, a script, or your own agent.
Base URL:
```text theme={"dark"}
https://api.zite.com/v1
```
Creating a workspace (a database plus the apps built on it) is one call:
```bash theme={"dark"}
curl -X POST https://api.zite.com/v1/workspaces \
-H "Authorization: Bearer $ZITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Helpdesk" }'
```
From there you [open a build session](/build-api/sessions/open-session), create an app and edit its files,
commit to kick off a build, then [publish](/build-api/deployments/publish-deployment).
Full reference: [Build API](/build-api/overview) · [Build sessions](/build-api/sessions/open-session) ·
[Deploy & logs](/build-api/deployments/publish-deployment).
# Barcode & QR scanning
Source: https://developers.zite.com/recipes/barcode-scanning
A camera-based barcode/QR scanner with react-webcam and the barcode-detector polyfill, tuned for reliable real-world scans.
The scanner runs a detection loop against the live `