> Public product help. This page has no customer entries, projects, chat transcripts, API keys, or horizon contents.
> Reading that data requires `Authorization: Bearer` (a signed-in app session JWT, or a Headless access token).
> Do not call Supabase `/rest/v1` with a Headless JWT. Data reads go through `/casc/v1/headless/v1/rest`.

# Headless PostgREST

Horizon entries are ordinary Postgres rows. Zagger already uses **PostgREST** (Supabase `/rest/v1`) internally. Headless integrations can use the same filter language through a scoped proxy so you do not invent a second query API.

Headless Bearer tokens are **not** Supabase JWTs. Do not call `SUPABASE_URL/rest/v1` with the headless access token or with the service role key. Use:

`GET /casc/v1/headless/v1/rest/{resource}?…PostgREST query…`

Read access works with `data:read`, or with the existing `chat` / `ingest` scopes. Writes need `data:write`. The proxy only exposes tables on the integration horizon (suffix).

## Resources

| Alias | Table | JSONB |
| --- | --- | --- |
| `entries` | `entries_{suffix}` | `raw` — ingested payload |
| `groups` | `ingestion_groups_{suffix}` | `config` |
| `group_entries` | `ingestion_group_entries_{suffix}` | membership only |
| `output_keys` | `canonical_output_keys_{suffix}` | `value` — prompt / intrinsic fields |

`GET /casc/v1/headless/v1/rest` lists these for the token’s suffix. Foreign-key embeds (`select=*,other(*)`) are blocked.

## JSONB on raw — what is robust

Each entry has `raw jsonb` with a GIN index using `jsonb_path_ops`. That index is for **containment** (`@>`), which PostgREST calls `cs`.

**Prefer:** `raw=cs.{"status":"ok"}` — extra keys on the object are allowed; JSON types must match (number `1` is not string `"1"`). Nested: `raw=cs.{"meta":{"author":"Ada"}}`. Arrays: `raw=cs.{"tags":["news"]}`.

**Also works, usually unindexed:** `raw->>status=eq.ok` and `ilike` on extracted text. `->>` always returns text (or SQL null if the key is missing). Fine for small sets; can sequential-scan a large horizon. Hyphenated keys should use containment, not `raw->>article-id`.

`jsonb_path_ops` does **not** speed up key-exists, contained-by, or `ilike`. Do not use PostgREST as a full-text engine; use vector search tools for prose.

## Output keys are not inside raw

Prompt outputs and `intrinsic.*` values are rows in `output_keys` (`entry_id`, `key`, `value`), not fields on `raw`. Query `/rest/output_keys?key=eq.MD` and join to entries in your client.

## Writes

`PATCH` replaces the `raw` object; it does not merge keys. Prefer ingest apply or the entry-update tool for partial updates. Unbounded `PATCH`/`DELETE` without a filter (for example `id=eq.…`) is rejected. Requires `data:write`.

## Examples

```
GET /casc/v1/headless/v1/rest/entries?select=id,created_at,raw&amp;raw=cs.{"status":"ok"}&amp;limit=50
GET /casc/v1/headless/v1/rest/groups?select=id,group_slug,name
GET /casc/v1/headless/v1/rest/entries/{uuid}
```

Default page size is 100; maximum 1000. See `cascade-node-server/docs/headless-postgrest.md` for operators, pagination headers, and when to use filter/search tools instead.
