# Public API guide

A read-only, machine-readable API over the Palestinian Prisoners dataset. It is
served directly by [PostgREST](https://postgrest.org) (via Supabase): every table
below is an HTTP endpoint that speaks JSON and CSV, with filtering, ordering, and
pagination as URL parameters.

> **What's public:** only rows that an editor has marked **published**. Draft /
> in-review records are invisible to the public key. Editing requires a member
> login (see the [contributor admin](../app/README.md)); this guide covers the
> read side only.

## Base URL & key

```
https://<your-project-ref>.supabase.co/rest/v1/
```

Every request must send the public **anon** key (find it in Supabase →
Project Settings → API). It is safe to publish — row-level security is what
restricts access, and the anon key can only ever read published rows.

```bash
PROJECT="https://<your-project-ref>.supabase.co"
ANON="<your-anon-key>"

curl "$PROJECT/rest/v1/prisoners?select=name_en,date_of_death&limit=5" \
  -H "apikey: $ANON"
```

## Resources

| Endpoint              | What it is                                              |
| --------------------- | ------------------------------------------------------ |
| `/rest/v1/prisoners`     | People (the deceased-in-custody records, and beyond) |
| `/rest/v1/facilities`    | Prisons / interrogation & detention centers          |
| `/rest/v1/organizations` | Human-rights orgs and sources' publishers            |
| `/rest/v1/testimonies`   | Individual survivor / released-detainee accounts     |
| `/rest/v1/sources`       | Citations behind the records (includes reports)      |

## Querying (PostgREST basics)

```bash
# Pick columns
curl "$PROJECT/rest/v1/prisoners?select=name_en,date_of_death,cause_of_death" -H "apikey: $ANON"

# Filter: deaths at a given facility (by FK), newest first
curl "$PROJECT/rest/v1/prisoners?status=eq.administrative_detainee&order=date_of_death.desc" -H "apikey: $ANON"

# Filter on dates / ranges
curl "$PROJECT/rest/v1/prisoners?date_of_death=gte.2023-10-07&order=date_of_death.asc" -H "apikey: $ANON"

# Text search (case-insensitive contains)
curl "$PROJECT/rest/v1/facilities?name_en=ilike.*negev*" -H "apikey: $ANON"

# Pagination
curl "$PROJECT/rest/v1/prisoners?limit=50&offset=100&order=name_en.asc" -H "apikey: $ANON"
```

Common operators: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `like`, `ilike`,
`in` (e.g. `status=in.(sentenced,detainee)`), `is` (e.g. `is_deceased=is.true`).
Full reference: <https://postgrest.org/en/stable/references/api/tables_views.html>.

### CSV instead of JSON

Add an `Accept: text/csv` header to any endpoint:

```bash
curl "$PROJECT/rest/v1/prisoners?select=name_en,date_of_death" \
  -H "apikey: $ANON" -H "Accept: text/csv" > prisoners.csv
```

### Bulk dumps

Pre-generated full exports of the published dataset (JSON + CSV) live in
[`data/dumps/`](../data/dumps), refreshed by `npm run dump`.

## Data dictionary

Only the fields a consumer is likely to use are listed; see
[`supabase/migrations`](../supabase/migrations) for the authoritative schema.

### `prisoners`

| Field                              | Type      | Notes                                            |
| ---------------------------------- | --------- | ------------------------------------------------ |
| `id`                               | uuid      | Stable identifier                                |
| `name_en` / `name_ar`              | text      | Primary transliteration / Arabic name            |
| `aliases`                          | text[]    | Other spellings                                  |
| `gender`, `occupation`             | text      |                                                  |
| `date_of_birth`, `age_reported`    | date/int  |                                                  |
| `residence`, `region`              | text      | Region ∈ West Bank, Gaza, '48, Jerusalem         |
| `status`                           | enum      | administrative_detainee, sentenced, detainee, …  |
| `is_deceased`                      | bool      |                                                  |
| `date_of_death`, `cause_of_death`  | date/text |                                                  |
| `death_facility_id`                | uuid      | → `facilities.id`                                |
| `body_withheld`, `body_returned_on`| bool/date |                                                  |
| `narrative`                        | text      | Source narrative (e.g. the B'Tselem paragraph)   |
| `verification`                     | enum      | unverified, single_source, corroborated, disputed|
| `source_id`                        | uuid      | → `sources.id` (primary citation)                |

### `facilities`

| Field                 | Type   | Notes                                           |
| --------------------- | ------ | ----------------------------------------------- |
| `name_en` / `name_ar` | text   | Canonical name                                  |
| `aliases`             | text[] | All known spellings (e.g. Negev = Ketziot = al-Naqab) |
| `type`                | enum   | prison, interrogation_center, detention_center, military_court, clinic |
| `location`, `region`  | text   |                                                 |
| `notes`               | text   |                                                 |

### `organizations`

| Field                                   | Type | Notes                |
| --------------------------------------- | ---- | -------------------- |
| `name`, `description`, `location`       | text |                      |
| `website`, `instagram`                  | text |                      |
| `telegram_handle`, `telegram_url`, `telegram_subscribers` | text/int | |

### `testimonies`

| Field                                  | Type | Notes                                      |
| -------------------------------------- | ---- | ------------------------------------------ |
| `testifier_name`                       | text | Preserved verbatim (not anonymized)        |
| `age_reported`, `gender`, `origin`, `occupation`, `sector` | | About the testifier      |
| `location_of_arrest`                   | text |                                            |
| `detention_facility`                   | text | Raw string as given                        |
| `facility_id`                          | uuid | → `facilities.id` when it resolved         |
| `detention_start`, `detention_end`     | text | Free-text dates kept faithfully            |
| `full_text`                            | text | The testimony itself                       |
| `date_of_testimony`, `classification`, `injuries`, `notes` | | |
| `type_of_testimony`, `original_medium` | text |                                            |
| `is_complete`                          | bool | False when the source flagged it partial   |
| `organization_id`                      | uuid | → `organizations.id`                       |
| `report_title`                         | text | The report it came from, if any            |
| `verification`                         | enum |                                            |

### `sources`

| Field                        | Type | Notes                                        |
| ---------------------------- | ---- | -------------------------------------------- |
| `type`                       | enum | btselem_case, report, testimony, news, org_statement, telegram_post, manual |
| `organization_id`            | uuid | → `organizations.id` (publisher)             |
| `title`, `url`, `published_on` | | The citation                                   |
| `summary`, `has_individual_testimonies` | | Used by reports (`type='report'`)     |
| `raw_content`, `archived_media_path`    | | Archived post text / media               |

## Versioning & stability

This API serves the live tables directly. Field names are the contract; we'll
avoid breaking changes, and the interactive [reference](./index.html) (generated
from the live server) is always the source of truth for the exact current shape.
