<!-- Source: https://docs.snoutdata.com/cloud/api -->

# The project API

A SnoutData Cloud project is a Postgres database, and in front of it is an HTTPS door serving the
things an application actually needs: a REST and GraphQL API over your tables, authentication,
file storage, realtime subscriptions, and your own functions on the edge.

```
https://<ref>.api.snoutdata.com
```

That is a different name from `<ref>.db.snoutdata.com`, which is the Postgres port. One project,
two doors.

:::note
SnoutData Cloud is in private testing, and this half of it is the newest part. Read
[what is switched on, and how](#what-is-switched-on-and-how) before you plan around it.
:::

## What is behind each path

| Path | What it is | Plan |
| --- | --- | --- |
| `/rest/v1` | REST over your tables and functions, generated from the schema | Plus and Pro |
| `/graphql/v1` | The same data over GraphQL, the same policies | Plus and Pro |
| `/auth/v1` | Sign-up, sign-in and sessions, signed with your project's own secret | every plan |
| `/storage/v1` | Files in buckets, in object storage | every plan |
| `/realtime/v1` | Broadcast, presence and table changes, over a websocket | every plan |
| `/functions/v1` | Your own TypeScript. See [Snout Functions](https://docs.snoutdata.com/cloud/functions) | every plan |

**The data API is the one that costs money**, and the reason is running cost rather than
packaging. `/rest/v1` is a server per project that runs whether or not anybody calls it, and on a
free project it would cost more per month than the database does. A free project asking for it is
refused with a sentence about the plan, never an error that reads like a fault.

## It is ordinary HTTP, and you need nothing installed

`/rest/v1` is a REST API. Two headers and a URL, from anything that can make a request:

```bash
# Read. Filters, ordering and paging are query parameters.
curl "https://<ref>.api.snoutdata.com/rest/v1/todos?select=id,title&done=eq.false&order=id.desc&limit=20" \
  -H "apikey: <your anon key>" \
  -H "Authorization: Bearer <your anon key>"

# Write.
curl -X POST "https://<ref>.api.snoutdata.com/rest/v1/todos" \
  -H "apikey: <your anon key>" \
  -H "Authorization: Bearer <your anon key>" \
  -H "Content-Type: application/json" \
  -H "Prefer: return=representation" \
  -d '{"title":"write the docs","done":false}'
```

The same thing from a browser or a server, with no dependency:

```js
const BASE = 'https://<ref>.api.snoutdata.com/rest/v1'
const KEY = '<your anon key>'

// The anon key goes in both headers signed out. Once somebody signs in, put THEIR access token
// in Authorization and leave apikey alone: that is what makes `auth.uid()` work in your policies.
const headers = { apikey: KEY, Authorization: `Bearer ${KEY}` }

const todos = await fetch(`${BASE}/todos?select=*&done=eq.false`, { headers }).then((r) => r.json())

await fetch(`${BASE}/todos`, {
  method: 'POST',
  headers: { ...headers, 'Content-Type': 'application/json', Prefer: 'return=representation' },
  body: JSON.stringify({ title: 'write the docs', done: false })
})
```

Row-level security decides what comes back, whichever way you call it. There is no client library
in the path and no SDK to keep up to date.

## A client library works too, if you already use one

The six paths above and the two key roles are a **frozen compatibility surface**: they are shaped
the way they are on purpose, so an application already written against that shape works here by
changing one URL. `@supabase/supabase-js` is written against exactly it:

```js
import { createClient } from '@supabase/supabase-js'

const client = createClient('https://<ref>.api.snoutdata.com', '<your anon key>')
```

**It is checked rather than believed.** A harness drives a real application through the real client
against a real project: sign-up, sign-in, an insert under row-level security and a read-back, the
signed-out refusal, broadcast, `postgres_changes`, a file up and down, a signed URL, a Snout
Function and a GraphQL query. As of 2026-09-11 that is **27 of 27, nothing skipped**.

## Your two keys

```bash
snoutdata keys                    # both, with what each one is
snoutdata keys --json             # for a deploy script
snoutdata keys rotate --force     # new ones
```

| Key | Who it is | Where it belongs |
| --- | --- | --- |
| `anon` | a signed-out visitor, and then whoever signs in | your front end, in the browser |
| `service_role` | **bypasses row-level security entirely** | a server you control, and nowhere else |

The two look identical in a terminal and the difference between them is your entire dataset, so
the CLI says which is which every time it prints them.

**Rotating breaks every key already issued**, including any client shipped to a browser and any
session a user is holding, which is why `rotate` requires `--force`. Do it when a key has leaked,
not on a schedule.

Row-level security is the thing that actually decides what the `anon` key can see. A table with no
policy returns nothing to it, which is the safe default and is also the commonest reason a new
project's first query comes back empty.

## Auth

`/auth/v1` signs its tokens with your project's own secret, so a token it issues is a token your
database understands: `auth.uid()` inside a policy is the user who made the request.

Email sign-up and sign-in work, and a confirmation email is really sent (ours goes out through an
already-verified sending domain). **OAuth providers are not switched on yet.** Google and the rest
are per-project settings holding your own client credentials, and the screen for entering them is
not built.

## Storage

`/storage/v1` holds files in object storage, with buckets, policies you write yourself as SQL on
`storage.objects`, and signed URLs for handing out a private file temporarily.

Writing your own storage policies works. It did not until 2026-09-11: the project owner had no
rights on the `storage` schema, so `create policy on storage.objects`, the commonest statement in
any application that stores files, failed outright. It is fixed, and a signed-in user uploading
under their own policy is one of the checks in the harness above.

## Realtime

`/realtime/v1` carries both halves of what Realtime does: **broadcast**, which is a message bus
between clients, and **`postgres_changes`**, which is your database's own inserts, updates and
deletes arriving on a websocket, filtered by the same row-level security that governs a read.

Two honest caveats, both written down rather than smoothed over:

- **The first subscription on a project that has been quiet is dropped, and the next one works.**
  If a subscribe goes silent, subscribe again.
- **A schema you create after the project was set up is not covered yet.** Realtime evaluates your
  policy as an internal role, and that role is granted access to the schemas that existed at
  bootstrap. A new schema needs us to re-run it. Tables in `public` are unaffected.

## Extensions

Your database is Postgres 17, and you install extensions yourself with plain
`create extension`, without being a superuser: `pgvector`, PostGIS and the rest of the usual set
are available. There is no per-plan extension list yet, so anything the image ships can be
switched on.

## What is switched on, and how

Being straight about this, because it is the part most likely to catch you out.

**Snout Functions are entirely self-serve.** `snoutdata functions deploy` works on any project, on
any plan, with nothing to switch on first. See [Snout Functions](https://docs.snoutdata.com/cloud/functions).

**The other four are switched on by us, per project, today.** The REST and GraphQL API, auth,
storage and realtime are all deployed and serving, but there is no button in the dashboard or flag
in the CLI that turns one on for your project yet. During private testing, ask and we will enable
what you need. Each product has its own tab in the dashboard, and each one says what is
answering for your project right now, distinguishing "not built yet" from "not on this plan" and
from "we asked and could not tell" rather than collapsing all three into one grey light.

## Also read

- [Snout Functions](https://docs.snoutdata.com/cloud/functions), and the secrets they run with.
- [Local development](https://docs.snoutdata.com/cloud/local), for the same schema on your own machine.
- [Limits, and what is not built](https://docs.snoutdata.com/cloud/limits), for the plan table and the honest gaps.
