# The console

Source: https://dabadata.com/docs/console/ (HTML) - https://dabadata.com/docs/console/index.md (this page as markdown)

The console is where people who build with dabadata sign in, switch between workspaces and projects, copy keys, manage their app's users and their team, and change the plan. It lives at [`/console/`](https://dabadata.com/console/) and calls the same HTTP API your app calls, as you.

## Open it

Go to [https://dabadata.com/console/](https://dabadata.com/console/) and [create an account](https://dabadata.com/console/auth/register): an email and a password, an email link, or Google. Your first sign-in sets up a workspace named after your email and a first project, `my-first-app`, so there is something to look at straight away. [Getting started](https://dabadata.com/docs/getting-started/index.md) walks from there to a live row in your own page; [Accounts and teams](https://dabadata.com/docs/accounts/index.md) covers sign-in, sessions and invitations.

A self-hosted server serves the console at `/console/` too, once `inspect/` is built. The older viewer keeps working at `/` with `?token=<view token>`.

## Workspaces and projects

The top bar, left to right: the `dabadata` wordmark, the **workspace switcher** (the workspace's name with a pill for its plan: Free, Starter, Pro, Scale or Enterprise), a `/`, the **project switcher**, the **live / test** switch (two small pills, `live` and `test`), a Docs link and your avatar (Account settings, Documentation, the theme and Sign out). The sidebar is in two groups: the first is headed with the chosen project's name and holds the project's pages, which act on one environment of that project; the second, **Workspace**, holds the pages that act on the whole workspace.

- A **workspace** is your team: its members, its plan and its projects. Everything a plan limits (connections, storage, projects, members) is counted per workspace. Create another one from the workspace switcher (**+ New workspace**); see [the workspace cap](https://dabadata.com/docs/console/index.md#workspace-cap). The switcher's menu shows each workspace's plan and your role in it, as `Free - owner`.
- A **project** is one app, with a live and a test environment ([Projects and keys](https://dabadata.com/docs/projects/index.md)). Developers and up create projects from the project switcher (**+ New project**): from the **To-do starter** (a `tasks` table and its rules), **Empty** (roles `admin` and `member`, no tables), a curated template or one saved in the workspace. The new project's four keys are shown once, in a dialog that closes only after you tick that you stored the secret keys.
- The URL says where you are: `/console/<workspace id>/<project id>/<page>`, with `_test` after the project id for the test environment, and `/console/<workspace id>/<page>` for a workspace page. Links can be shared with anyone in the workspace.

## Live and test

The live / test switch in the top bar decides which environment every project page shows and changes. On test the `test` pill is amber and an amber strip under the top bar says "Test environment - its own data, keys and trace. Nothing here touches live." The two share nothing but the project: test has its own tables and rows, its own keys (`pk_test_...`, `sk_test_...` against `pk_live_...`, `sk_live_...`), trace, logs, users, allowed origins and secrets. A change you make on test never reaches live, and the Overview, Logs and Users pages count each environment on its own.

Work on test first. Apply a definition to live when it is ready; nothing is copied from one to the other by itself.

## Project pages

In the sidebar's order. Each acts on the environment the switch shows.

### Overview

Today's numbers (UTC) as of when the page opened (the **Refresh** button in the header reads them again; the console does not poll) in four tiles: **Connections now**, **Requests today** (your app's calls, not the console's own), **Denied today** (calls your rules refused, red above 0) and **Errors today** (red above 0). Under them **Plan usage** (this month, every project of the workspace), **Last errors** and the **Project** card with what your app needs to reach this environment: the project id, the API URL, the environment, the region. On a new account's first project, developers see **Get your secret keys** until both secret keys have been shown (each is shown once). Until your app writes its first row, a three-step **Get started** card shows: copy your keys, paste the snippet, and "Waiting for your app's first write". It turns into "Your app is live" when a row arrives from a real caller (not the console, not the secret key).

### Definition

The project definition - tables, roles, rules, views, storage, identity and the `mcp` block - as JSON in `definition.json`, with **Check** (a dry run: `Valid - N change(s) to apply`, or `N problem(s)` with the path of each), **Apply...**, **+ Insert table**, **+ Insert view**, every earlier version with its diff, and **Roll back to this**. Applying only adds tables and columns; one you removed stays in the database and shows as **Drift**. The **Drift** panel beside the editor resolves each item - **Drop...**, **Rename into...** a declared table or column, **Convert to ...** for a type that differs - always as a dry run first, with the rows that would block a conversion; and when a check shows a new table or column beside a leftover, it offers the rename that keeps the rows. It is the console's face of [`PUT /v1/schema`](https://dabadata.com/docs/api/index.md#schema-put), [`POST /v1/schema/migrate`](https://dabadata.com/docs/api/index.md#schema-migrate) and [its versions](https://dabadata.com/docs/api/index.md#schema-versions).

### Tables

The rows of every table with filters (or a SQL `where`) and sort, live. Insert a row, edit a cell, delete a row, export CSV; at most the first 1,000 rows, use SQL for more. You act as the console: the rules are bypassed and every change is in the trace as `bypass`, with your account as the user. A strict table (`access: "views"`) has a lock and the badge `views only`: your app's users reach it only through views and actions.

### Files

The files your app's users uploaded to this environment, live, bucket by bucket ([File storage](https://dabadata.com/docs/storage/index.md)). On top, the workspace's file storage against the plan and the largest file allowed. Each bucket shows its file count, its size and its `read`, `write` and `delete` rules; open one for its files with a filter, a preview and the details of each, a link to open it (signed for 5 minutes unless the bucket is public), and delete (developers and up). Uploads that were started but never completed show as `pending` and are removed after an hour. Buckets are declared in the definition's `storage` block; with none yet the page shows a block to start from. Every bucket is also the read-only table `files_<bucket>`.

### Sites

Static sites (HTML, CSS, JS, images) of this environment, at `<name>.dabadata.site` and, from Starter, at your own domains ([Sites](https://dabadata.com/docs/sites/index.md)). Developers and up create a site (the name is its address and cannot change; **Single-page app** answers `index.html` for a path with no file), roll back to an earlier deploy, connect a domain, which then shows the DNS records to add, and **Move to live** (or **Move to test**) a site with its name and files ([Move a site](https://dabadata.com/docs/sites/index.md#move)). With no site yet the create form is open; once there is one it is a **New site** button below the sites. Open a deploy's row to see its files; on the live deploy each opens on the site. Each site with a live deploy shows its **Readiness**: a badge (`reachable`, `propagating`, `unreachable`) and, per public hostname, whether DNS, TLS, the route, the deploy and (for a domain) the certificate pass, with the reason for any that fails; **Check now** runs the [check](https://dabadata.com/docs/sites/index.md#readiness) again. The page does not upload: a deploy comes from the CLI (`dabadata sites deploy ./dist --site <name>`) or the MCP tool `site_deploy`. The item is hidden when the server has sites off.

### SQL

A query editor on this environment's schema. Read mode (the default, every role) runs as a database role that can only `SELECT`. Write mode (developers and up, after typing the project id) turns the editor red and first runs your statements as a preview that is rolled back, shows what they did, and commits only when you press **Commit** within 5 minutes. Statements stop after 10 seconds, and at most 1,000 rows per statement come back. Writes made here bypass the rules and are not pushed to live subscribers until they reconnect. Save queries you use often by name. The routes are [`POST /v1/sql`](https://dabadata.com/docs/api/index.md#sql-run) and the ones after it.

### Trace

Every call with its decision and the rule that made it, live, filtered by user, table, operation, decision and time. Open a row for the explanation, the rule and the before-and-after diff, or "Simulate as another user" to see what the same call would have done for someone else. See [Trace](https://dabadata.com/docs/trace/index.md).

### Logs

Three tabs, live: **API** (every request with its route, status, duration, credential and user), **Realtime** (sockets connecting, subscribing, being refused, and each change fanned out) and **Errors** (server errors and rejected input, grouped by route). Kept for the plan's API log retention.

### Rules

The rules as a matrix of tables and operations (`read`, `create`, `update`, `delete`), with column rules folded under their table, and a simulator: as this role and user, on this row, at this time, is this allowed, and which rule decided. Nothing is written and nothing is traced. A cell that says `nobody` has no rule: no end user may do it. See [Rules](https://dabadata.com/docs/rules/index.md).

### Views

Every view of the definition ([Views](https://dabadata.com/docs/views/index.md)): its base table, its `allow` and `rows` rules, joins, params, columns, computed fields, what callers may filter and sort, and a **Shape per role** table. **Run as** reads the view as any user with its params, filter, sort and limit; it is a real read that lands in the trace, and a refusal shows the 403 that user would get. With no views, developers get **Add a sample view**.

### Playground

The to-do demo, always on this project's test environment, even with the switch on live: sign in test users with different roles, open two frames side by side and watch your own rules decide. It needs a `tasks` table with `title` and `done`; developers get a button that adds the starter's table to the test definition. Minting the test users' tokens needs developer or higher.

### Apps

This environment as an [MCP server](https://dabadata.com/docs/mcp/index.md), so your coding agent can build the app with it and people can use the app inside ChatGPT and Claude ([Build with an AI agent](https://dabadata.com/docs/agents/index.md)):

- **MCP server**: `on` or `off` (it is on when the definition has `"mcp": { "enabled": true }`; developers get a button that adds the block), and the server's URL, `https://dabadata.com/mcp/<project id>` (with `_test` for test).
- **Connect a client**: the ready line for Claude Code (`claude mcp add ...`), the `.cursor/mcp.json` for Cursor, and the connector URL for ChatGPT and Claude, which sign people in with the environment's hosted accounts ("Sign-in is off" while those are off).
- **Build it with your agent**: a prompt to paste into your agent, with the `.env` lines it needs (the secret key is left for you to fill in from Keys).
- **Widgets**: which table draws which widget in a chat (the built-in `table`, `form` and `record`, or one of your own), and the custom widgets stored in this environment, with a preview.
- **Start from a template**: curated starting points (a definition with its rules, the `mcp` block and widgets); developers make a new project from one.

### Authentication

How your app's own users sign in, and who they are once they do ([Sign-in for your app's users](https://dabadata.com/docs/end-user-auth/index.md), [Identity and tokens](https://dabadata.com/docs/identity/index.md)). Four tabs; everything they change is saved as a new version of this environment's definition. The older Users and Identity pages are now its last two tabs, and their addresses lead there.

- **Sign-in methods**: **Email and password** and **Magic link**, on whenever hosted accounts are; and the social providers Google, Apple, Microsoft, GitHub, GitLab, Discord, LinkedIn and Slack. Each opens **Set up <provider>**: the callback URL to register at the provider, the steps there, and the client id and client secret (kept encrypted as the project secret `<PROVIDER>_CLIENT_SECRET`). A provider is `On`, `Needs setup` (configured, but hosted accounts are off or its client secret is missing) or `Off`. See [Sign in with Google, Apple and more](https://dabadata.com/docs/sign-in-providers/index.md).
- **URL configuration**: **App URLs** first - every address your app runs at, each set as an allowed origin (CORS) and a redirect URL in one step, with a **Add localhost and 127.0.0.1** button for a dev server's port (a browser treats the two as different sites). A row shows whether its origin is allowed and which redirect URLs it has; removing it takes it out of both lists. Both writes refuse a list that changed elsewhere meanwhile. Below: the address of the hosted sign-in page (`/auth/<environment id>/`), **Sign-up** (**Open** or **Invite only**, which closes sign-up through the social providers too), **Verify email addresses**, and the **Redirect URLs** list on its own (absolute, exact, at most 20).
- **Third-party tokens**: the four ways in and which are on (a provider's JWT, tokens your backend mints, anonymous, hosted accounts), the trusted issuers of the definition, **Add a provider**, which turns a client id into the exact issuer block for Google, Apple, Clerk, Auth0, Firebase Auth or Supabase Auth for you to paste into the definition (it saves nothing), and a link to [Secrets](https://dabadata.com/docs/console/index.md#secrets), where project secrets now live.
- **Users**: the accounts of your app's own users (not the workspace's members): search, **Add user** (with a password or an emailed link), change a role, disable, delete. On test, tick users (or none, for all) and **Copy to live**: each gets a live account and an invitation mail to choose a password; passwords are not copied ([Copy users to live](https://dabadata.com/docs/end-user-auth/index.md#copy-to-live)). Each shows how they sign in and a status: `active`, `invited` (has not chosen a password yet; **Resend invite** mails a new link), `unverified` (the email is not confirmed) or `disabled`.
- **Test inbox** (test environment only; developers and up): every mail the environment sent your app's users - confirmations, sign-in links, resets, invitations - with each link's state (unused, used, expired). Open or copy a link, copy a broken one, make it expired or used to try your app's error handling, empty the inbox, and switch off sending test mail to real inboxes ([The test inbox](https://dabadata.com/docs/end-user-auth/index.md#test-inbox)).

While hosted accounts are off, every tab but Third-party tokens shows "Hosted accounts are off for the <env> environment"; developers turn them on with one button (open sign-up, emails verified, new people get the default role).

### Keys

The project id, the API URL and both environments' keys. Copy the publishable key; the secret key shows only its first characters, because it is shown in full once, when it is made. The environment the top bar is on comes first; the other one is folded below. **Rotate publishable key** and **Rotate secret key** each ask first, then make a new key and keep the old one working for 24 hours (shown as `rotated - stops ...`; a new secret key is shown once, in the dialog); **Revoke** stops a key at once (revoking the only key of a kind makes its successor and shows it once). Lost the secret key? Rotate it. A key labelled `site key` belongs to a connected WordPress site. **Restricted keys**, under the current environment's keys, lists the [restricted keys](https://dabadata.com/docs/projects/index.md#restricted) (name, role, prefix, last used) with **New restricted key** (a name and the role it acts as; the key is shown once), **Rotate** and **Revoke** for developers and up. The **Snippets** tab has ready-to-run code for plain HTML, Vite / React / Vue, a Node backend, WordPress and curl, built from this environment's real keys and its first table.

### Secrets

Your own API keys and other values a definition names but must not contain, stored encrypted on the server, never shown again and separate for live and test. **AI provider keys** has a row each for Anthropic (Claude), OpenAI and Google Gemini: paste the key and it is saved as `ANTHROPIC_KEY`, `OPENAI_KEY` or `GEMINI_KEY` for an action's [ai](https://dabadata.com/docs/ai/index.md) block to name; a saved row shows when it was set, with **Replace** and **Delete**. **Other secrets** takes any name and value: a shared JWT signing key, a sign-in provider's client secret, a webhook URL, or an AI key under a name of your own. **Use one** shows an action that calls Claude with the stored key. Developers and up see and change secrets; other roles see why not.

## Workspace pages

These act on the whole workspace, whichever project is chosen.

### Members

Who is in the workspace and with which role, and the invitations still pending. Admins invite by email (the link works for 7 days), change roles and remove people; anyone but the owner can leave; the owner can hand the workspace to another member (**Make owner**). Every member and every pending invitation takes a seat of the plan; at the cap the page says "Every seat is taken". See [Invitations and seats](https://dabadata.com/docs/accounts/index.md#invitations).

### Usage & plan

This calendar month so far for the whole workspace - projects, members and invitations, connections, database storage, egress - against the plan's limits (a meter turns amber at 75% and red at 100%), the limits of the plan, the daily requests and egress of the current environment, and the plan picker (owner only). See [Plans in the beta](https://dabadata.com/docs/console/index.md#plans).

### Settings

Four tabs. **Project** (when a project is chosen): its name and logo (what your app's users see in sign-in mails and on the hosted pages - [give it your app's name and logo](https://dabadata.com/docs/end-user-auth/index.md#app-name); **Upload logo**, **Replace logo**, **Remove**: a square PNG or JPEG, at most 256 KB), id, region and status (`active`, or `paused`: idle, and the next call with one of its keys wakes it), the current environment's allowed origins (CORS), export, and delete or restore. **Workspace**: its id, your role, rename it, delete it. **Account**: your name, your password and your signed-in sessions, the CLI's among them. **Audit log** (admins and the owner): who changed what in the workspace, newest first.

## Reading a console screenshot

For AI assistants that someone shows a picture of the console. Work out where they are first, then what the screen says, then tell them the next step in the console's own words: the labels quoted on this page are the ones on their screen. If the picture shows a whole secret key (`sk_live_...` or `sk_test_...` past its first characters), do not repeat it, and tell them to rotate it on [Keys](https://dabadata.com/docs/console/index.md#keys): anyone who has it bypasses every rule.

### Where they are

- **The page**: the highlighted sidebar item, which is also the large title over the content. Sub-pages are tabs under the title (Authentication, Keys, Logs, Settings) or the end of the address, `/console/<workspace id>/<project id>/<page>/...`.
- **The environment**: the amber `test` pill and the amber strip under the top bar mean test; no strip means live. The address (`_test` after the project id) and the key prefixes (`pk_test_` against `pk_live_`) say the same. With no live / test pills, no project is chosen and it is a workspace page.
- **The workspace and its plan**: the workspace switcher shows the name and the plan pill.
- **Their role**: not in the top bar. It shows in the workspace menu (`Free - developer`), on Settings > Workspace ("Your role"), and in hints such as "Your role (viewer) can see keys. Developers and up can rotate and revoke them." A button this page mentions that is missing or greyed out usually means the role is too low: see [Roles](https://dabadata.com/docs/console/index.md#roles).
- **No sidebar**: a page outside the console frame. Sign in, "Start free" or "Join the beta waitlist", "Check your email", an invitation ("Join <workspace>"), "Approve sign-in for the dabadata CLI" (the browser half of `dabadata login`: approve only if the code matches the terminal), or "Connect <site> to <project>?" (the WordPress plugin connecting a site).

### What a page is showing

| On screen | What it means | What to do |
| --- | --- | --- |
| Overview: **Get started**, "Waiting for your app's first write" | No row has arrived from a real caller yet. Rows written in the console or with the secret key do not count. | Copy the publishable key from Keys, paste a snippet from Keys > Snippets, write a row from the app. Check the app uses this environment's keys. |
| Overview: **Denied today** in red | Your rules refused that many calls today. Not an error: the rules did their job, or a rule is stricter than the app expects. | Trace, Decision `denied`, open a row: the explanation names the rule. Then the Rules simulator. |
| Overview: **Errors today** in red, **Last errors** | Server errors (5xx) or rejected input (400). | Logs > Errors groups them by route; click one for its calls. |
| Overview: **Get your secret keys** | A new account's first project; its secret keys were not shown yet. | Show the test secret key, store it in the server's `.env`. It is shown once. |
| Definition: red `N problem(s)` | The JSON does not validate, so nothing can be applied. | Fix each path the **Check** card lists, check again. |
| Definition or Tables: **Drift** | The database has a table or column the definition does not. Applying never drops anything. | Harmless. Put it back into the definition, or drop, rename or convert it in the Drift panel of the Definition page. |
| Definition: amber `unsaved changes` | The editor differs from the stored version; nothing is in force yet. | **Check**, then **Apply...** |
| Tables: lock, `views only` | A strict table: end users read it only through views and write it only through actions. The console sees every row. | Expected. To change what users see, change the views. |
| Tables: "<table> has no rows yet" or "No tables yet" | Nothing written here, or no table declared, in this environment. | Check the live / test switch first; then the Definition. |
| Files: `pending` | An upload was started but not completed. | Nothing; it is removed after an hour. |
| Files: "No buckets yet" | The definition has no `storage` block. | Add the block shown on the page and apply. |
| Sites: `paused`, `disabled`, `suspended` | Asleep with its idle environment / over the workspace's plan (visitors see "This site is over its plan") / taken down by us. | A deploy or keyed call wakes it / change the plan / contact us. |
| Sites: a domain `pending` or `failed` | The DNS record is not seen yet, or is wrong. | Add the record shown at the DNS host; the server checks every 5 minutes, or press **Check now**. |
| SQL: a red editor, "Write mode" | The statements can change any row past every rule. Each run is a rolled-back preview. | **Commit** within 5 minutes to keep it, or **Discard**. |
| SQL: "Postgres refused the query" | A SQL error, with line, column and the Postgres code. | Fix the statement. Read mode cannot write: use write mode (developers). |
| Trace: `allowed`, `denied`, `bypass`, a red `error` | A rule let it through / a rule refused it / the secret key or the console, rules not checked / the call failed. | Open the row for the explanation and the rule; "Simulate as another user". |
| Trace: "No calls yet" | Nothing has called this environment. | If the app has run, it most likely uses the other environment's keys: flip the switch. |
| Rules: a cell that says `nobody` | No rule for that operation: no end user may do it. | Add the rule in the Definition. |
| Views: "Denied (403)" | The view's `allow` rule refuses that user; the app would get the same 403. | Change the user, or the view's `allow`. |
| Apps: `off` | The definition has no `"mcp": { "enabled": true }`; the MCP address answers 404. | **Turn it on in the definition**, check, apply. |
| Authentication: "Hosted accounts are off for the <env> environment" | dabadata keeps no accounts for this app's users here. | **Turn on hosted accounts** (developers), or bring your own tokens (Third-party tokens). |
| Sign-in methods: `Needs setup` | The provider is configured, but hosted accounts are off or its client secret is missing. | Open it and paste the client secret, or turn hosted accounts on. |
| Users: `unverified` / `disabled` | The email is not confirmed yet (with Verify email on, no password sign-in until it is) / the account may not sign in. | They open the mailed link / **Enable**. |
| Keys: `rotated - stops ...` | An old key that still works until then. | Put the `current` key in every app and server before it stops. |
| Keys: a secret key as `sk_..••••` | Secret keys are shown once, when made. | Lost it: **Rotate secret key** (the old one works 24 more hours). |
| Members: "Every seat is taken" | Members plus pending invitations fill the plan's seats. | Withdraw an invitation, remove someone, or the owner changes the plan. |
| Usage & plan: an amber or red meter | 75% / 100% of the plan's limit this month. | See [Plans and limits](https://dabadata.com/docs/limits/index.md) for what happens at the limit. |
| Usage & plan: `on request in the beta`, `requested` | Pro and Scale are switched on by us; the request is in. | Wait for our email. |
| Settings: status `paused` | The environment was idle and sleeps. | Nothing: the next call with one of its keys wakes it. Console calls do not. |
| "<project> is being deleted" | Deleted less than 7 days ago; both environments answer "project deleted". | An admin restores it on Settings > Project. |
| "This workspace is not here" / "This project is not here" | A wrong id, or they are not a member (or signed in with another account). | Check the account in the avatar menu; ask for an invitation. |
| "Set a password (optional)" | The account signs in by email link or Google only. | Optional: Settings > Account. |

### Messages that pop up

Errors show as red toasts at the bottom right, or as a red box in the dialog. The common ones:

| Message | What it means |
| --- | --- |
| "Your role in this workspace may not do that." | 403 `forbidden`: the role is too low. An admin or the owner changes roles on Members. |
| "Your plan does not allow more of this. Change the plan on Usage & plan." | 403 `plan_limit`: projects, seats, storage or workspaces are at the cap. Delete something, or change the plan. |
| "The workspace uses more than that plan allows: ..." | 409 `over_limit` on moving down a plan. Delete a project or remove someone first. |
| "Project limit reached" | In **New project**: the plan's projects are used up. |
| "This project is being deleted. Restore it under Settings to use it again." | See the row above. |
| "You are signed out. Sign in again." | The session ended or was revoked. |
| "Too many attempts. Try again in ..." | Rate limited; wait. |
| "The server had a problem (5xx). Try again in a moment." | A server error. If it stays, contact us. |
| "Request failed (<status> <code>)." | An error without its own words; look the code up in the [HTTP API reference](https://dabadata.com/docs/api/index.md). |

### What people often get wrong

- **The wrong environment.** The app writes with test keys and they look at live, or the other way round: the rows seem missing. Compare the key prefix in the app with the switch.
- **The console is not the app.** Tables, SQL and the console bypass the rules, so a write that works here may be denied for the app's users. Ask the Rules simulator, Views' **Run as**, or the Playground.
- **Denied is not an error.** A denial is a rule deciding; it is in the Trace with its explanation, not in Logs > Errors.
- **SQL writes are quiet.** Live subscribers see them only after they reconnect.
- **Applying never drops.** A table or column taken out of the definition stays in the database and shows as drift, until you drop it in the Drift panel.
- **The mails say "my-first-app".** Sign-in mails and hosted pages carry the project's name and logo, not the site's; rename the project and add a logo in **Settings** > **Project** > **General**. Until then Overview, Authentication and Sites show "Your app's users will see "my-first-app"".
- **The secret key stays on a server.** Never in browser code, a chat connector or a screenshot; the publishable key is the one for the browser.

## Roles

Everyone in a workspace has one role, the same in every project of it. Each role may do everything the roles below it may:

| Role | May |
| --- | --- |
| `viewer` | Look at everything: rows, the definition and its versions, the trace, logs, the overview, usage, users, keys (publishable in full, secret by prefix), members. Run read-only SQL and the rules simulator. Not: read secret names, export, or change anything. |
| `developer` | Change the project: insert, edit and delete rows, apply and roll back definitions, write SQL, rotate and revoke keys, set secrets and allowed origins, manage the app's users, mint user tokens, export. Create projects. |
| `admin` | Run the team: invite people, change roles, remove members (not another admin), revoke invitations, rename the workspace, restore deleted projects, read the audit log. |
| `owner` | Exactly one per workspace. Change the plan, transfer ownership, delete projects and the workspace, change or remove admins. |

A button your role may not use is hidden or disabled, and the API answers the same thing: 403 `forbidden` with an `explanation` such as `your role in this workspace (viewer) may not apply definitions - that needs developer or higher`.

## Plans in the beta

The plans and their limits are on [Plans and limits](https://dabadata.com/docs/limits/index.md) and the [pricing page](https://dabadata.com/pricing/). During the beta:

- **Self-service up to Starter.** The owner moves a workspace between Free and Starter on **Usage & plan**, at once.
- **Pro and Scale on request.** Their button says **Request**; we switch them on. The API answers an owner who asks for them with 403 `plan_requires_approval`. A workspace we put on Pro or Scale stays there, and its owner can still move it down.
- **Nothing is billed.** The plan is recorded; you hear from us before billing starts.
- **Moving down** is refused while the workspace uses more than the smaller plan allows (projects not being deleted, members plus pending invitations): 409 `over_limit` with what is over. Delete a project or remove someone first.
- Enterprise is arranged with us, not picked in the console.

## The workspace cap

An account may own at most **3 workspaces** that are not deleted, the one made at sign-up included. Creating a fourth answers 403 `{ "error": "plan_limit", "limit": "workspaces", "message" }`; delete one or ask us for more. Workspaces you are only a member of do not count, and becoming the owner of one by a transfer is never refused. A self-hosted server sets the cap with `DABADATA_MAX_WORKSPACES_PER_ACCOUNT` and the highest self-service plan with `DABADATA_BETA_MAX_PLAN` (default `starter`).

## How it talks to the API

The console has no back door. It signs in with a session cookie and calls the public routes with three extras: the cookie, the header `X-Requested-With` (without it the cookie is ignored, which stops other sites from using your session), and `X-Project: prj_...` (or `prj_..._test`) to name the environment. The server checks that you are a member of the project's workspace and then calls you the `console` identity:

- Inside the project it is service-level: the rules are bypassed, and every call is in the trace with decision `bypass`, source `console` and your account as the user (`role: "console"`, your email as the name). `console` is therefore a reserved role name.
- Your workspace role limits what it may do, as in the table above.
- Console sockets do not count toward the plan's connections, console calls and sockets do not count as requests (so **Requests today** is your app's traffic), and console calls do not wake a paused environment. Their bytes still count as egress.
- Signing out, or revoking the session, closes the console's live sockets at once.

Every route, with its credentials and errors, is in the [HTTP API reference](https://dabadata.com/docs/api/index.md#console-session).
