Data API

Read and write your tables over HTTPS from a browser or mobile app, with row-level security deciding access.

The Data API serves your Plexivo Postgres tables over HTTPS, so a web or mobile app can read and write them with no backend server in between. Each request runs inside your database as its caller, and PostgreSQL row-level security decides which rows that caller may touch.

Turn it on#

  1. Open the Connect tab of your database and find From a browser or mobile app.
  2. Set the Allowed web origin to the exact HTTPS address your web app runs on, for example https://shop.example.in, and http://localhost:5173 while you build. A browser request from any other origin is refused; apps and servers that send no origin are not affected. Leave it empty if only servers and mobile apps call the API.
  3. Choose Turn on Data API. Copy the Project URL, the Publishable key and the secret key now: the full keys are shown only this once.

The Data API tab then lists every table your apps can reach, its columns, and ready-to-copy code for JavaScript and Flutter built from that table’s own columns.

What apps can reach#

Nothing is exposed by accident. A table appears in the Data API only once row-level security is on and it has at least one policy. A view appears only if it runs with its caller’s rights (security_invoker), and a database function only when its comment says @expose. Until then the API answers as if the table did not exist, and the Not exposed list on the Data API tab says what to change.

New tables and policies appear within 30 seconds, or at once when you choose Reload schema or run SQL in the console’s SQL editor.

Keys and callers#

Every request carries a key in the apikey header. Who the request runs as depends on what else it carries:

The request carriesIt runs asUse it in
The publishable keyanonYour app, before anyone signs in
The publishable key and a user’s access token as Authorization: Bearer …authenticated, as that userYour app, after sign-in
The secret keyservice_role, which bypasses row-level securityYour servers only

A token that has expired or is not valid is refused, never treated as anonymous. The secret key is never accepted in a URL, and a request that carries it with a browser’s Origin header is refused with secret_key_in_browser.

Keep the secret key on your serversPut it in Plexivo Secrets, never in a browser, a mobile app or a public repository.

Read and write#

An example table, created with the secret key as in Raw SQL:

sql
create table customers (
  id bigint generated always as identity primary key,
  owner uuid not null default auth.uid(),
  name text not null
);
create table orders (
  id bigint generated always as identity primary key,
  owner uuid not null default auth.uid(),
  customer_id bigint references customers (id),
  total numeric(10, 2) not null,
  status text not null default 'open',
  created_at timestamptz not null default now()
);
alter table customers enable row level security;
alter table orders enable row level security;
create policy own on customers for all to authenticated using (owner = auth.uid()) with check (owner = auth.uid());
create policy own on orders for all to authenticated using (owner = auth.uid()) with check (owner = auth.uid());

With the client libraries, in GORM’s style or Supabase’s:

js
import { createClient } from '@plexivo/client';

const db = createClient(PLEXIVO_DATA_URL, PLEXIVO_PUBLISHABLE_KEY);
await db.auth.signUp({ email, password });

const asha = await db.from('customers').create({ name: 'Asha' });
const order = await db.from('orders').create({ customer_id: asha.id, total: 1299 });

const open = await db.from('orders')
  .where('status', 'open')
  .preload('customers')
  .order('created_at', 'desc')
  .limit(20);

const { data, count } = await db.from('orders')
  .select('id, total, customer:customers(name)', { count: 'exact' })
  .eq('status', 'open')
  .range(0, 19);

await db.from('orders').where('id', order.id).update({ status: 'paid' });
dart
import 'package:plexivo_client/plexivo_client.dart';

final db = PlexivoClient(dataUrl, publishableKey);
await db.signUp(email: email, password: password);

final ravi = await db.from('customers').create({'name': 'Ravi'});
final order = await db.from('orders').create({'customer_id': ravi['id'], 'total': 480});

final open = await db.from('orders')
    .where('status', 'open')
    .preload('customers')
    .order('created_at', 'desc')
    .limit(20);

await db.from('orders').where('id', order['id']).update({'status': 'paid'});

Or over HTTPS from any language, with the same query language as PostgREST, so queries written for Supabase keep working:

bash
curl "$PLEXIVO_DATA_URL/rest/v1/orders?select=id,total,customer:customers(name)&status=eq.paid&order=created_at.desc&limit=20&count=exact" \
  -H "apikey: $PLEXIVO_PUBLISHABLE_KEY" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Responses are JSON: {"data": [...], "count": 42}. GET /rest/v1/orders/17 reads one row by its primary key.

Limits#

  • Up to 1,000 rows per read, per write and per list of related rows; up to 300 conditions in one query, and related rows four levels deep.
  • Each request has 8 seconds to run in the database; a longer one is stopped with 504 and statement_timeout.
  • Requests a minute, for each project and visitor address, depend on your plan, with a separate, lower rate for sign-up, sign-in and email codes. Beyond the rate, the answer is 429 and rate_limited, with a Retry-After header saying how many seconds to wait.
  • Request bodies up to 5 MB for reads and writes, 1 MB for auth and GraphQL. A larger one is refused with 413 and too_large.
  • Each project has a monthly number of requests that comes with your plan. When it is reached, requests are answered with 429 and project_request_cap_reached, with a Retry-After header, until the count resets at the start of the next month (UTC).

Errors#

An error comes back as {"error": {"code": "…", "message": "…"}, "trace_id": "…"}, with a hint where one helps. Quote the trace_id to support. The client libraries raise it as PlexivoError in JavaScript and PlexivoException in Dart. Every code, and what to do about it, is in Errors.

For startups Generate TypeScript types from your live API with renderApiTypes from @plexivo/client/generate-types, and every table name and column is checked as you type.

Next steps#

Updated 28 October 2026