new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Supabase · all subjects

api

147 notes in this subject, read out of this brain and free to use. This is page 1 of 3.

Data REST API URL and Keys locations

The API URL can be found in the Integrations > Data API section of the Dashboard. API Keys can be found in the Settings > API Keys section of the Dashboard.

Supabase API can be used in two-tier or three-tier architecture

The auto-generated API can be used directly from the browser in a two-tier architecture, or as a complement to your own API server in a three-tier architecture.

REST API security model

The API is configured to work with Postgres's Row Level Security and is provisioned behind an API gateway with key-auth enabled.

REST API scalability

The API can serve thousands of simultaneous requests and works well for Serverless workloads.

REST API single SQL statement resolution

The REST API resolves all requests to a single SQL statement, leading to fast response times and high throughput.

Supabase auto-generates REST API from database schema

Supabase automatically generates a RESTful API directly from your database schema, allowing you to connect to your database through a restful interface directly from the browser without writing code.

REST API respects Postgres security model

The REST API respects the Postgres security model including Row Level Security, Roles, and Grants.

REST API auto-generates from database schema

The API is instant and auto-generated. As you update your database, the changes are immediately accessible through your API. The API is self-documenting with Supabase generating documentation in the Dashboard which updates as you make database changes.

REST API works with Postgres Views and Functions

The REST API works with Postgres Views, Materialized Views, Foreign Tables, and Postgres Functions. It also supports user-defined computed columns and computed relationships.

REST API performance benchmarks

Supabase benchmarks show basic reads are more than 300% faster than Firebase. The API is a very thin layer on top of Postgres, which does most of the heavy lifting.

REST API CRUD operations

The REST API supports basic CRUD operations (Create/Read/Update/Delete).

REST API supports deep relationships

The REST API supports arbitrarily deep relationships among tables and views. Functions that return table types can also nest related tables and views.

Supabase REST API uses PostgREST

Supabase provides a RESTful API using PostgREST, which is a thin API layer on top of Postgres.

JavaScript client initialization and select query

Initialize the JavaScript client with: import { createClient } from '@supabase/supabase-js'; const supabase = createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY); Make a request with: const { data: todos, error } = await supabase.from('todos').select('*')

Postgres privileges for Data API access

Granting privileges like select or execute to roles such as anon or authenticated makes those tables or functions accessible through the Data API. The API checks Postgres permissions—only objects with explicit grants are exposed, and all other access is denied by default.

cURL request to table endpoint

Make a request to a table using cURL by appending /rest/v1/ to your project URL, then the table name. Include both apikey and Authorization headers with the API key: curl '<SUPABASE_URL>/rest/v1/todos' -H "apikey: <SUPABASE_PUBLISHABLE_KEY>" -H "Authorization: Bearer <SUPABASE_PUBLISHABLE_KEY>"

Grant least-privilege access to table via SQL

Use SQL GRANT statements to control table access: grant select on public.todos to anon for read-only anonymous access; grant select, insert, update, delete on public.todos to authenticated for full CRUD by authenticated clients; grant select, insert, update, delete on public.todos to service_role for full CRUD by server-side service role. Enable Row Level Security and create appropriate policies before granting write access to client roles.

Expose tables via Data API through dashboard

In the Integrations > Data API section of the Dashboard, you can expose specific tables and functions you want to access. Enable Default privileges for new entities to automatically grant access for new tables and functions in the public schema.

API routes automatically created from database objects

API routes are automatically created when you create Postgres Tables, Views, or Functions. Each table creates a corresponding API route with the same name that can accept GET, POST, PATCH, and DELETE requests.

JavaScript client available methods

The JavaScript client provides these methods for interacting with tables: select(), insert(), update(), upsert(), delete(), and rpc() for calling Postgres functions.

Create table with todos example

To create a table called todos with a column to store tasks, use SQL: create table todos (id bigint generated by default as identity primary key, task text check (char_length(task) > 3));

REST API endpoint URL format

The REST API is accessible through the URL https://<project_ref>.supabase.co/rest/v1. All requests require an API key to be passed through an apikey header.

Dashboard auto-docs access path

Navigate to Project Settings > Data API > Docs to view auto-generated REST API documentation for your database tables and views.

Auto-generated REST API documentation in Dashboard

Supabase generates REST API documentation automatically in the Dashboard that updates whenever you make changes to your database. To access it, go to Project Settings, select Data API -> Docs, then choose any table or view from the Tables and Views sidebar. The documentation provides code examples in both JavaScript and cURL formats, and you can select which SUPABASE_KEY to use for the examples.

Auto-docs language switching

The auto-generated API documentation in the Dashboard supports switching between JavaScript and cURL code examples using tabs.

Community-contributed Supabase client libraries

Supabase has community-contributed client libraries for C# (supabase-csharp), Go (supabase-go), Kotlin (supabase-kt), Ruby (supabase-rb), Godot Engine/GDScript (supabase-gdscript), Elixir (supabase-elixir), and R (supabaseR). These are maintained by the community rather than the official Supabase team.

Official Supabase client libraries

Supabase provides official client libraries for JavaScript/TypeScript (supabase-js), Dart/Flutter (supabase-flutter), Swift (supabase-swift), and Python (supabase-py). These are officially supported libraries for accessing the REST and Realtime APIs.

REST API route created automatically from table

Creating a database table in Supabase automatically creates a corresponding REST API route at `/rest/v1/{table_name}`. This route accepts GET, POST, PATCH, and DELETE requests for querying and modifying the table.

Query leaderboard with C# client library

Query a leaderboard table using the C# client library: ```c# [Table("leaderboard")] class Leaderboard : BaseModel { [PrimaryKey("id", false)] public int Id { get; set; } [Column("player")] public string Player { get; set; } [Column("score")] public int Score { get; set; } } var result = await supabase .From<Leaderboard>() .Order(x => x.Score, Ordering.Descending) .Get(); ```

Query leaderboard with Swift client library

Query a leaderboard table using the Swift client library: ```swift let response = try await supabase .from("leaderboard") .select() .order("score", ascending: false) ```

Query leaderboard with Dart client library

Query a leaderboard table using the Dart client library: ```dart final data = await supabase .from('leaderboard') .select('*') .order('score', ascending: false); ```

Query leaderboard with JavaScript client library

Query a leaderboard table using the JavaScript client library: ```js const { data, error } = await supabase .from('leaderboard') .select() .order('score', { ascending: false }) ```

Query leaderboard via browser

Query a leaderboard table directly in a browser by appending the publishable API key as a query parameter: `https://<PROJECT_REF>.supabase.co/rest/v1/leaderboard?apikey=<PUBLISHABLE_KEY>`

Query leaderboard via REST API with curl

Query a leaderboard table via REST API using curl: ```bash curl 'https://<PROJECT_REF>.supabase.co/rest/v1/leaderboard?select=*&order=score.desc' \ -H "apikey: <PUBLISHABLE_KEY>" ``` Replace `<PROJECT_REF>` with your project reference and `<PUBLISHABLE_KEY>` with your publishable API key found in Settings > API Settings.

Grant write access after RLS setup

After configuring RLS policies, grant write permissions to authenticated and service roles: `grant select, insert, update, delete on public.leaderboard to authenticated; grant select, insert, update, delete on public.leaderboard to service_role;`

RLS policies for public leaderboard

To enable Row Level Security (RLS) for a public leaderboard table with the following access rules: (1) Anyone (anon and authenticated) can read the leaderboard using a SELECT policy with condition `using (true)`, (2) Only authenticated users can insert scores using an INSERT policy with condition `with check (true)`, (3) Only authenticated users can update scores using an UPDATE policy with conditions `using (true) with check (true)`. First enable RLS with `alter table leaderboard enable row level security;`

Grant anonymous read access to table via Data API

To expose a table through the Data API for anonymous (unauthenticated) read-only access, grant the select permission to the anon role: `grant select on public.leaderboard to anon;`. This allows the table to be queried over HTTP without authentication.

Query leaderboard with Python client library

Query a leaderboard table using the Python client library: ```python response = ( supabase.table('leaderboard') .select("*") .order('score', desc=True) .execute() ) ```

Leaderboard table schema example

To create a leaderboard table, use this SQL: ```sql create table leaderboard ( id serial primary key, player text not null, score integer not null default 0, created_at timestamptz default now() ); ``` The table has columns: id (serial primary key), player (text, not null), score (integer, not null, default 0), and created_at (timestamptz, default now()).

Automate Python type generation with GitHub Actions

Add this script to package.json to generate Python types: "update-types": "npx supabase gen types --lang=python --project-id \"$PROJECT_REF\" > database_types.py". Create .github/workflows/update-types.yml with a scheduled workflow that runs daily (0 0 * * *) to automatically commit type changes to the repository. The workflow requires SUPABASE_ACCESS_TOKEN and PROJECT_REF environment variables.

GitHub Actions workflow for Python type updates

Example GitHub Actions workflow for automatic Python type generation: name: Update database types on: schedule: - cron: '0 0 * * *' jobs: update: runs-on: ubuntu-latest permissions: contents: write env: SUPABASE_ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} PROJECT_REF: <your-project-id> steps: - uses: actions/checkout@v4 with: persist-credentials: false fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 22 - run: npm run update-types - name: check for file changes id: git_status run: | echo "status=$(git status -s)" >> $GITHUB_OUTPUT - name: Commit files if: ${{contains(steps.git_status.outputs.status, ' ')}} run: | git add database_types.py git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" git commit -m "Update database types" -a - name: Push changes if: ${{contains(steps.git_status.outputs.status, ' ')}} uses: ad-m/github-push-action@master with: github_token: ${{ secrets.GITHUB_TOKEN }} branch: ${{ github.ref }}

Using generated Python types for database operations

Example of using generated Python types with Supabase: from .database_types import PublicMovies, PublicMoviesInsert, PublicMoviesUpdate from supabase import create_client client = create_client("YOUR_SUPABASE_URL", "YOUR_SUPABASE_KEY") movies = client.table("movies") # Select selected = [PublicMovies(m) for m in movies.select("*").execute().data] # Insert inserted = [PublicMovies(m) for m in movies.insert(PublicMoviesInsert(name="foo", data="bar")).execute().data] # Update updated = [PublicMovies(m) for m in movies.update(PublicMoviesUpdate(name="bar")).eq("id", 5).execute().data]

Python generated types example

Example of generated Python types from a movies table: class PublicMovies(BaseModel): data: Optional[Json[Any]] = Field(alias="data") id: int = Field(alias="id") name: str = Field(alias="name") class PublicMoviesInsert(TypedDict): data: NotRequired[Annotated[Json[Any], Field(alias="data")]] id: NotRequired[Annotated[int, Field(alias="id")]] name: Annotated[str, Field(alias="name")] class PublicMoviesUpdate(TypedDict): data: NotRequired[Annotated[Json[Any], Field(alias="data")]] id: NotRequired[Annotated[int, Field(alias="id")]] name: NotRequired[Annotated[str, Field(alias="name")]]

Python type classes generated from database schema

For a database table like public.movies with columns (id bigint primary key, name text not null, data jsonb null), the Supabase CLI generates three type classes: PublicMovies for parsing SELECT results, PublicMoviesInsert for insert operations with NotRequired fields, and PublicMoviesUpdate for update operations where all fields are NotRequired.

Generate Python types with Supabase CLI

Generate Python types from your database schema using the supabase gen types command. For a project, run: npx supabase gen types --lang=python --project-id "$PROJECT_REF" --schema public > database.types.py. For local development, run: npx supabase gen types --lang=python --local > database_types.py

Query database level API errors in logs

To find all API errors at database level in the log explorer, use: select cast(postgres_logs.timestamp as datetime) as timestamp, event_message, parsed.error_severity, parsed.user_name, parsed.query, parsed.detail, parsed.hint, parsed.sql_state_code, parsed.backend_type from postgres_logs cross join unnest(metadata) as metadata cross join unnest(metadata.parsed) as parsed where regexp_contains(parsed.error_severity, 'ERROR|FATAL|PANIC') and parsed.user_name = 'authenticator' order by timestamp desc limit 100;

PostgREST authentication error codes

Authentication errors when request lacks proper credentials: PGRST300 = 500 (PostgREST does not have active JWT secret), PGRST301 = 401 (JWT could not be decoded or invalid), PGRST302 = 401 (request without Auth: Bearer header when anonymous role disabled), PGRST303 = 401 (JWT claims validation or parsing failed).

PostgREST schema cache error codes

Schema cache errors when API cannot identify relationships or objects: PGRST200 = 400 (stale foreign key relationships or resources not in database), PGRST201 = 300 (ambiguous embedding request), PGRST202 = 404 (stale function signature or function not in database), PGRST203 = 300 (overloaded functions with same argument names but different types, or POST to overloaded functions with JSON/JSONB unnamed parameter), PGRST204 = 400 (column in query parameter not found), PGRST205 = 404 (table in URI not found).

PostgREST API request error codes

API request errors with data structures or formatting: PGRST100 = 400 (parsing error in query string), PGRST101 = 405 (database functions only allow GET and POST), PGRST102 = 400 (invalid request body), PGRST103 = 416 (invalid range for limits), PGRST105 = 405 (invalid UPDATE/UPSERT), PGRST106 = 406 (schema not exposed to API), PGRST107 = 415 (invalid Content-Type), PGRST108 = 400 (filter on embedded resource not in select), PGRST111 = 500 (invalid response.headers), PGRST112 = 500 (status code not positive integer), PGRST114 = 400 (UPSERT with PUT using limits/offsets), PGRST115 = 400 (UPSERT primary key mismatch), PGRST116 = 406 (singular response returned multiple or no items), PGRST117 = 405 (HTTP verb not supported), PGRST118 = 400 (cannot order by related table without relationship), PGRST120 = 400 (embedded resource only filtered by is.null/not.is.null), PGRST121 = 500 (cannot parse JSON in RAISE PGRST), PGRST122 = 400 (invalid preferences in Prefer header with strict handling), PGRST123 = 400 (aggregate functions disabled), PGRST124 = 400 (max-affected preference violated), PGRST125 = 404 (invalid path in URL), PGRST126 = 404 (OpenAPI config disabled but root path accessed), PGRST127 = 400 (feature not implemented), PGRST128 = 400 (max-affected preference violated with RPC).

PostgREST connection error codes

Connection errors prevent the Data API from interacting with Postgres: PGRST000 = 503 (Could not connect due to incorrect connection string or Postgres service not running), PGRST001 = 503 (Could not connect due to internal error), PGRST002 = 503 (Could not connect when building schema cache), PGRST003 = 504 (Request timed out waiting for connection from internal pool).

Database level error codes mapping

Postgres error codes map to HTTP status codes as follows: 08* = 503 (connection error), 09* = 500 (triggered action exception), 0L* = 403 (invalid grantor), 0P* = 403 (invalid role specification), 23503 = 409 (foreign key violation), 23505 = 409 (uniqueness violation), 25006 = 405 (read only SQL transaction), 25* = 500 (invalid transaction state), 28* = 403 (invalid auth specification), 2D* = 500 (invalid transaction termination), 38* = 500 (external routine exception), 39* = 500 (external routine invocation), 3B* = 500 (savepoint exception), 40* = 500 (transaction rollback), 53400 = 500 (config limit exceeded), 53* = 503 (insufficient resources), 54* = 500 (too complex), 55* = 500 (obj not in prerequisite state), 57* = 500 (operator intervention), 58* = 500 (system error), F0* = 500 (config file error), HV* = 500 (foreign data wrapper error), P0001 = 400 (default code for "raise"), P0* = 500 (PL/pgSQL error), XX* = 500 (internal error), 42883 = 404 (undefined function), 42P01 = 404 (undefined table), 42P17 = 500 (infinite recursion), 42501 = 403 if authenticated or 401 if not (insufficient privileges), other = 400.

PostgREST error JSON structure

Error codes from the Data API are returned as JSON objects with these fields: code (string), details (null or string), hint (string or null), and message (string). Example: {"code": "42703", "details": null, "hint": "Perhaps you meant to reference the column some_table.fake_col", "message": "column some_table.fake_col does not exist"}

Count errors per path by hour in logs

To count errors per API path by hour in the log explorer: select format_timestamp("%c", timestamp_trunc(cast(edge_logs.timestamp as timestamp), hour), "UTC") as hour, count(proxy_status) as error_count, path, coalesce(proxy_status, 'not_recorded') as error_codes from edge_logs cross join unnest(metadata) as metadata cross join unnest(response) as response cross join unnest(response.headers) as headers cross join unnest(request) as request where status_code >= 300 and regexp_contains(path, '^/rest/v1/') group by hour, proxy_status, path;

Query specific database error code in logs

To find a specific database error from the data API in the log explorer, filter by sql_state_code: select cast(postgres_logs.timestamp as datetime) as timestamp, event_message, parsed.error_severity, parsed.user_name, parsed.query, parsed.detail, parsed.hint, parsed.sql_state_code, parsed.backend_type from postgres_logs cross join unnest(metadata) as metadata cross join unnest(metadata.parsed) as parsed where parsed.sql_state_code like '42501' and parsed.user_name = 'authenticator' order by timestamp desc limit 100;

Query data API request from specific authenticated user

To find data API requests from a specific authenticated user in the log explorer: select cast(timestamp as datetime) as timestamp, event_message, cf_connecting_ip as requesters_ip, url as request_url, request.method as request_method, sb.auth_user as user_id, apikey_payload.role as apikey_role, authorization_payload.role as authorization_token_role, user_agent, city, country, continent, postalCode from edge_logs cross join unnest(metadata) as metadata cross join unnest(request) as request cross join unnest(sb) as sb cross join unnest(jwt) as jwt cross join unnest(jwt.apikey) as jwt_apikey cross join unnest(jwt_apikey.payload) as apikey_payload cross join unnest(authorization) as authorization_key cross join unnest(authorization_key.payload) as authorization_payload cross join unnest(headers) as headers cross join unnest(cf) as cf cross join unnest(response) as response where regexp_contains(path, '^/rest/v1/') and sb.auth_user = 'SOME_USER_ID' order by timestamp desc;

Query specific API error in logs

To find a specific API error in the log explorer, query edge_logs: select cast(timestamp as datetime) as timestamp, status_code, event_message, coalesce(proxy_status, 'not_recorded') as error_codes, path from edge_logs cross join unnest(metadata) as metadata cross join unnest(response) as response cross join unnest(request) as request where status_code >= 300 and regexp_contains(path, '^/rest/v1/') and regexp_contains(proxy_status, '(?i)THE_RELEVANT_STATUS_CODE');

PostgREST error codes only captured in logs for V14+

PostgREST error codes are only captured in the logs for projects running V14 or later. You can check your PostgREST version and upgrade your project in the Infrastructure Settings.

PostgREST internal error codes

Internal errors: PGRSTX00 = 500 (internal errors related to database connection library).

Data API security: two-layer access control with Grants and RLS

The Data API works with two layers of Postgres access control: Grants determine which Postgres roles can reach a table, view, or function over the Data API (roles include anon, authenticated, and service_role). Row Level Security (RLS) policies determine which rows those roles can read or modify. Grants control whether a role can access an object. RLS controls which rows the role can access. Both controls must be used for every exposed object.

Default privileges on Supabase projects

On existing projects, tables created in the public schema receive SELECT, INSERT, UPDATE, and DELETE privileges for anon, authenticated, and service_role roles by default. Functions receive EXECUTE privileges. These grants make new objects reachable through the Data API even when you do not intend to expose them. Supabase is changing the platform default to revoke these automatic grants so that exposure becomes opt-in. These default privileges are part of the standard Supabase permission model and do not bypass RLS. The internal supabase_admin role grants them to anon, authenticated, and service_role, but supabase_admin cannot authenticate through the Data API.

Give your agent this brain