# Liap Cloud API Reference (v1)

> Bu dosya, Liap Cloud edge data-plane üzerinden çalışan tüm public API'leri tanımlar.
> Bir yapay zekaya bu dosyayı vererek uçtan uca çalışan bir istemci veya demo proje üretebilirsiniz.

## 1. Temel kavramlar

| Kavram | Açıklama |
|--------|----------|
| **Control plane** | `liap-supervisor` — hesaplar, proje kaydı, API key üretimi (`https://api.liap.cloud`) |
| **Data plane** | `liap-edge` — tenant verisi (DB, auth, storage) bölgede kalır |
| **API key** | Dashboard → API Keys → `liap_live_…` — **projeyi seçen kimlik** |
| **API kökü** | `{EDGE}/{service}/…` + header `X-Liap-Key` |

**Veri egemenliği:** Tenant satırları ve dosyalar yalnızca projenin atandığı edge bölgesinde tutulur.

---

## 2. Base URL

```text
EDGE_BASE   = https://cdn-tr-01.liap.cloud    # projenin data_plane_url (edge)
SUPERVISOR  = https://api.liap.cloud
API_KEY     = liap_live_...                   # Dashboard → API Keys
```

API kalıbı (her servis için):

```http
{EDGE_BASE}/{service}/{path}
X-Liap-Key: {API_KEY}
```

Örnek:

```http
GET https://cdn-tr-01.liap.cloud/db/tables/todos/rows
X-Liap-Key: liap_live_...
```

Proje **API anahtarından** çözülür. Path’te UUID gerekmez.

```http
GET {EDGE_BASE}/db/tables/todos/rows
X-Liap-Key: liap_live_...
```

Dashboard oturumu (API key yok) `X-Liap-Project` kullanabilir. Özel alan adı (Dashboard → Alan adları) da projeyi seçer.

**Servis alias'ları** (`{service}`):

| Alias | Açıklama |
|-------|----------|
| `auth` | Tenant son-kullanıcı kimlik doğrulama |
| `db` | Postgres row CRUD, SQL, GraphQL |
| `storage` | Bucket / object dosya depolama |
| `realtime` | SSE stream, WebSocket, presence |
| `functions` | Edge JavaScript fonksiyonları |
| `notifications` | Push / in-app bildirimler |
| `search` | Tam metin arama |
| `monitor` | Servis durumu, analytics |
| `ai` | AI inference (edge) |
| `webhooks` | Outbound webhook'lar |
| `secrets` | Proje secret'ları |

Edge ingress bu isteği iç servise çevirir: `http://127.0.0.1:711x/v1/{service}/{path}`.

---

## 3. Kimlik doğrulama

### 3.1 Proje API key (service-role / sunucu tarafı)

Dashboard → Project → API Keys → **Create key**.

| Alan | Değer |
|------|-------|
| Format | `liap_live_{40 hex karakter}` |
| Header | `X-Liap-Key: liap_live_...` |
| Alternatif | `Authorization: Bearer liap_live_...` |

**Kurallar:**

- İstek: `{EDGE}/{service}/…` + **`X-Liap-Key`**. Proje UUID yazılmaz.
- Key **tek projeye** bağlıdır; o proje otomatik seçilir.
- API key **kullanıcı oturumu değildir**. Auth’ta key projeyi seçer; `register`/`login` e-posta+şifre kullanır. `/auth/me` ve sonrası için **Key + `Authorization: Bearer lca_…` birlikte** gerekir. JSON gövdesinde `project_id` yok.
- Key edge'de hash olarak replikalanır (`liap_edge_meta`); plain secret yalnızca oluşturma anında gösterilir.
- Her istek için gerekli **scope** aşağıdaki tabloda.

```http
GET {EDGE}/db/tables/todos/rows
X-Liap-Key: liap_live_...
```

| Hata | Anlam |
|------|--------|
| 401 | Key geçersiz veya scope yok |
| 404 | Proje çözülemedi (`X-Liap-Key` yok) |

### 3.2 Son-kullanıcı oturumu (`lca_` token)

Uygulamanızın müşterileri için:

1. `POST /auth/register` veya `/auth/login` — header: **`X-Liap-Key`**
2. Yanıt: `access_token` (`lca_...`) + `refresh_token` (üst düzey ve `tokens` altında)
3. Sonraki auth istekleri (`/auth/me`, MFA, passkey): **`X-Liap-Key` + `Authorization: Bearer lca_...`**
4. 401 → `POST /auth/refresh` (`X-Liap-Key` + `{ "refresh_token" }`; rotation — eski çift geçersiz)

Yalnız Bearer (keysiz) veya yalnız Key (tokensız `/auth/me`) 401 döner.

JWT yok; opaque token + refresh. `expires_in` = 900 saniye.

### 3.3 Scope tablosu (API key)

| Scope | Kullanım |
|-------|----------|
| `data:read` | `db` GET, `search`, analytics okuma |
| `data:write` | `db` POST/PATCH/DELETE, notifications yazma |
| `storage:read` | Storage list/download (private bucket) |
| `storage:write` | Upload, bucket oluşturma |
| `realtime:subscribe` | SSE / WebSocket abonelik |
| `functions:invoke` | Fonksiyon çağırma ve deploy (service-role) |
| `auth:admin` | `monitor`, kullanıcı listesi (admin) |
| `notifications:send` | `POST /notifications/email` — **yalnız sunucu anahtarı**, uygulamaya gömmeyin |

Varsayılan yeni key scope'ları: `data:read`, `data:write`.

### 3.4 Hata formatı

```json
{ "error": "insan okunur mesaj" }
```

HTTP durumları: `401` yetkisiz, `403` scope/yasak, `404` bulunamadı, `409` çakışma, `429` kota.

---

## 4. Auth API (`service=auth`)

Base path: `/auth/...` → iç route `/v1/auth/...`. Proje **`X-Liap-Key`** ile seçilir. Gövdeye `project_id` koymayın.

| Aşama | Header |
|-------|--------|
| register / login / anonymous / refresh | `X-Liap-Key` |
| `/auth/me`, MFA, passkey, logout (oturumlu) | `X-Liap-Key` + `Authorization: Bearer lca_…` |
| `/auth/users` (admin liste) | Dashboard oturumu (edge imzalar) |

### Kayıt / giriş

```http
POST /auth/register
X-Liap-Key: liap_live_...
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "min8chars",
  "name": "Ayşe",
  "user_metadata": { "username": "ayse", "phone": "+90555" }
}
```

`user_metadata` alias'ları: `data`, `metadata`. Nesne olmalı.

```http
POST /auth/login
X-Liap-Key: liap_live_...
Content-Type: application/json

{ "email": "user@example.com", "password": "..." }
```

Yanıt (örnek):

```json
{
  "user": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "email": "user@example.com",
    "role": "user",
    "email_verified": false,
    "metadata": { "username": "ayse", "phone": "+90555" },
    "user_metadata": { "username": "ayse", "phone": "+90555" },
    "app_metadata": {}
  },
  "access_token": "lca_...",
  "refresh_token": "lcr_...",
  "token_type": "Bearer",
  "expires_in": 900,
  "tokens": {
    "access_token": "lca_...",
    "refresh_token": "lcr_...",
    "token_type": "Bearer",
    "expires_in": 900
  }
}
```

Kayıt / OAuth / anonymous sonrası tenant DB'de `public.profiles` satırı **aynı UUID** ile açılır (`profiles.id = user.id`). Dashboard'da tanımlanan ek alanlar `profiles` kolonlarıdır.

MFA açıksa login `200` + `mfa_required` + `mfa_token` döner → `POST /auth/mfa/verify`.

### Oturum

| Method | Path | Açıklama |
|--------|------|----------|
| GET | `/auth/me` | Mevcut kullanıcı (`X-Liap-Key` + Bearer `lca_`) — `user_metadata` / `app_metadata` |
| PATCH | `/auth/me` | `{ "name"?, "user_metadata"? }` — Postgres'e kalıcı yazılır |
| POST | `/auth/refresh` | `{ "refresh_token": "lcr_..." }` + `X-Liap-Key` |
| POST | `/auth/logout` | Oturumu sonlandır |
| POST | `/auth/anonymous` | Anonim kullanıcı + oturum (+ profiles satırı) |
| GET | `/auth/users` | Admin liste (dashboard) |
| PATCH | `/auth/users/{id}` | Admin: `role`, `user_metadata`, `app_metadata`, `email_verified` |
| GET | `/auth/user-fields` | Ek alan şeması |
| PUT | `/auth/user-fields` | `{ "fields": [{ "name", "type", "required", "in_signup" }] }` + `ALTER TABLE profiles` |
| GET | `/auth/roles` | Proje rol listesi (varsayılan `user`) |
| PUT | `/auth/roles` | `{ "roles": ["user", "vip", ...] }` — müşteri tanımlı roller |

`type`: `text` \| `bool` \| `int8` \| `timestamptz` \| `float8`. Kullanıcı `app_metadata` yazamaz. Rol ataması yalnızca `/auth/roles` listesindeki adlara yapılabilir (`user` silinemez).

### MFA

| Method | Path |
|--------|------|
| POST | `/auth/mfa/setup` |
| POST | `/auth/mfa/enable` `{ "code": "123456" }` |
| POST | `/auth/mfa/disable` |
| POST | `/auth/mfa/verify` `{ "mfa_token", "code" }` |

### Passkey (WebAuthn)

| Method | Path |
|--------|------|
| POST | `/auth/passkey/register/begin` |
| POST | `/auth/passkey/register/finish` |
| POST | `/auth/passkey/login/begin` |
| POST | `/auth/passkey/login/finish` |

### OAuth

```http
GET /auth/oauth/google/start?redirect_uri=https://app.example/callback
```

### E-posta akışları (tenant BYO SMTP)

| Method | Path |
|--------|------|
| POST | `/auth/recover` |
| POST | `/auth/recover/confirm` |
| POST | `/auth/magic` |
| POST | `/auth/magic/consume` |
| POST | `/auth/verify` |
| POST | `/auth/verify/confirm` |

---

## 5. Database API (`service=db`)

Tenant Postgres — her proje `proj_{uuid8}` DB'sinde izole.

**Örnek:**

```http
GET {EDGE_BASE}/db/tables/todos/rows
X-Liap-Key: liap_live_...
```

Scope: `data:read` (GET), `data:write` (POST/PATCH/DELETE).

### Satır modeli

**Document tablolar** (eski varsayılan):

```sql
id UUID PRIMARY KEY,
data JSONB NOT NULL,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ
```

Uygulama alanları `data` JSON içinde tutulur.

**İlişkisel tablolar** (Dashboard tablo oluşturucu / `POST /db/tables`): gerçek Postgres kolonları. API yine `{ id, data: { …kolonlar }, … }` şeklinde döner.

**Auth profiles:**

```sql
CREATE TABLE profiles (
  id TEXT PRIMARY KEY,          -- = auth_users.id (UUID string)
  updated_at TIMESTAMPTZ,
  -- + dashboard'da tanımlanan ek kolonlar (username, phone, …)
);
```

```http
GET /db/tables/profiles/rows?filter=id.eq.{user_id}
X-Liap-Key: liap_live_...
```

### Şema & tablo oluşturma

| Method | Path | Açıklama |
|--------|------|----------|
| GET | `/db/schema` | Tablolar, kolonlar, FK grafiği |
| POST | `/db/tables` | `{ name, columns[], foreign_keys[] }` ile tablo oluştur |

### Row CRUD uçları

| Method | Path | Scope | Açıklama |
|--------|------|-------|----------|
| GET | `/db/tables` | read | Tablo listesi |
| GET | `/db/tables/{table}/rows` | read | Satırları listele (filtre / select) |
| POST | `/db/tables/{table}/rows` | write | Satır ekle |
| GET | `/db/tables/{table}/rows/{id}` | read | Tek satır |
| PATCH | `/db/tables/{table}/rows/{id}` | write | JSON merge güncelle |
| DELETE | `/db/tables/{table}/rows/{id}` | write | Sil |

### Yazma (insert / update)

**Insert** — yeni satır:

```http
POST /db/tables/todos/rows
X-Liap-Key: liap_live_...
Content-Type: application/json

{ "data": { "title": "İlk görev", "done": false } }
```

Yanıt `201` + oluşturulan satır (tam `data`).

**Update** — mevcut `data` ile birleştir (PATCH merge):

```http
PATCH /db/tables/todos/rows/{id}
Content-Type: application/json

{ "data": { "done": true } }
```

Yalnız gönderilen alanlar güncellenir; diğer `data` anahtarları korunur.

### Okuma — hangi satırlar? (`filter`)

Query parametresi `filter` **hangi satırların** döneceğini belirler (satır kümesi).

Format: `alan.op.değer` — birden fazla `filter=` AND ile birleşir.

| Operatör | Anlam |
|----------|--------|
| `eq` | eşit |
| `neq` | eşit değil |
| `gt`, `gte`, `lt`, `lte` | sayısal karşılaştırma |
| `like` | SQL LIKE (`%` joker) |

Örnekler:

```http
# Tüm satırlar (filtre yok)
GET /db/tables/todos/rows

# Yalnız tamamlananlar
GET /db/tables/todos/rows?filter=done.eq.true

# Başlıkta "görev" geçenler
GET /db/tables/todos/rows?filter=title.like.%görev%

# İki koşul birlikte
GET /db/tables/todos/rows?filter=done.eq.false&filter=title.like.%acil%
```

Sistem kolonları: `id`, `created_at`, `updated_at`. Diğer alanlar `data` JSON içinden okunur.

**Önemli:** `limit` satır kümesini tanımlamaz — yalnızca sayfalama için. Filtre yoksa eşleşen **tüm** satırlar döner (sunucu üst sınırı varsayılan 10.000).

### Okuma — hangi alanlar? (`select`)

Query parametresi `select` **satırın hangi alanlarının** `data` içinde görüneceğini belirler (alan kümesi).

```http
# Tüm data alanları
GET /db/tables/todos/rows

# Yalnız title ve done (id yine döner)
GET /db/tables/todos/rows?select=title,done

# Tek satırda da geçerli
GET /db/tables/todos/rows/{id}?select=title
```

`select` **limit ile aynı şey değil**: `limit=5` ilk 5 satırı keser; `select=title` her satırda yalnız `title` alanını gösterir.

### Sıralama ve sayfalama

| Parametre | Örnek | Açıklama |
|-----------|--------|----------|
| `order` | `created_at.desc` | Sıralama (`asc` / `desc`) |
| `limit` | `50` | Opsiyonel — üst sayfa boyutu |
| `offset` | `100` | Opsiyonel — atlama |

```http
GET /db/tables/todos/rows?filter=done.eq.false&select=title,done&order=created_at.desc&limit=20
```

### Tam örnek — Node.js istemci

```javascript
const EDGE = 'https://cdn-tr-01.liap.cloud'
const KEY = process.env.LIAP_API_KEY
const gw = EDGE

async function db(path, { method = 'GET', body } = {}) {
  const headers = { 'X-Liap-Key': KEY }
  if (body) headers['Content-Type'] = 'application/json'
  const res = await fetch(`${gw}/db${path}`, {
    method,
    headers,
    body: body ? JSON.stringify(body) : undefined,
  })
  if (!res.ok) throw new Error(await res.text())
  if (res.status === 204) return null
  return res.json()
}

// 1) Yazma
const created = await db('/tables/todos/rows', {
  method: 'POST',
  body: { data: { title: 'Liap demo', done: false } },
})

// 2) Tüm satırlar (tam data)
const all = await db('/tables/todos/rows')

// 3) Filtreli okuma — yalnız açık görevler
const open = await db('/tables/todos/rows?filter=done.eq.false')

// 4) Alan seçimi — yalnız istenen alanlar (tüm satırlar)
const titles = await db('/tables/todos/rows?select=title,done')

// 5) Filtre + select birlikte
const slim = await db(
  '/tables/todos/rows?filter=done.eq.true&select=title'
)

// 6) Güncelle
await db(`/tables/todos/rows/${created.id}`, {
  method: 'PATCH',
  body: { data: { done: true } },
})
```

### Python SDK

```python
from liap import Client

c = Client(base_url=EDGE, api_key=KEY)

c.db.insert("todos", {"title": "demo", "done": False})
c.db.list("todos")  # tüm satırlar
c.db.list("todos", filter="done.eq.false")
c.db.list("todos", select="title,done")
c.db.get("todos", row_id, select="title")
```

### JSON-RPC (`db.list`)

```http
POST /db/rpc
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "db.list",
  "params": {
    "table": "todos",
    "filter": ["done.eq.true"],
    "select": "title,done",
    "order": "created_at.desc",
    "limit": 50
  }
}
```

### RLS (Row Level Security)

| Method | Path |
|--------|------|
| GET | `/db/tables/{table}/rls` |
| PUT | `/db/tables/{table}/rls` |

### SQL / GraphQL / RPC (ek)

```http
POST /db/query
X-Liap-Key: liap_live_...
{ "sql": "SELECT * FROM todos LIMIT 10" }
```

```http
POST /db/graphql
{ "query": "...", "variables": {} }
```

---

## 6. Storage API (`service=storage`)

Dosyalar **bucket** içinde tutulur. Public bucket’taki dosyalar için kalıcı URL **proje ID içermez**.

### Public URL (önerilen — `<img src>`)

```text
https://{EDGE_HOST}/storage/objects/{bucket}/{short_id}
```

Örnek: `https://cdn-tr-01.liap.cloud/storage/objects/data/a1b2c3d4-e5f6-7890.png`

| Özellik | Değer |
|---------|--------|
| `short_id` | Yükleme yanıtındaki `id` |
| Auth | Public bucket → **yok** |
| Boyut | `?w=800&h=600` veya `?w=800px&g=600px` (`g` = yükseklik) |
| Auth | Gerekmez (direkt edge path) |

Eski kök kısa URL `/{short_id}` geriye dönük çalışabilir; yeni entegrasyonlarda `/storage/objects/...` kullanın.

### Yazma / private okuma (API)

```http
PUT /storage/objects/{bucket}/{key}
X-Liap-Key: liap_live_...
Content-Type: image/png
```

Private bucket GET için `storage:read` + Bearer veya API key gerekir.

### Uçlar

| Method | Path | Scope | Açıklama |
|--------|------|-------|----------|
| GET | `/storage/buckets` | read | Bucket listesi |
| POST | `/storage/buckets` | write | `{ "name", "public": true\|false }` |
| DELETE | `/storage/buckets/{bucket}` | write | Sil |
| GET | `/storage/objects/{bucket}?prefix=` | read* | Liste |
| PUT | `/storage/objects/{bucket}/{key}` | write | Yükle (raw body) |
| GET | `/storage/objects/{bucket}/{key}` | read* | İndir (API) |
| DELETE | `/storage/objects/{bucket}/{key}` | write | Sil |
| GET | `{EDGE}/storage/objects/{bucket}/{short_id}` | — | **Direkt public okuma** |
| GET | `{EDGE}/{short_id}` | — | Eski kısa URL (opsiyonel) |
| POST | `/storage/signed/{bucket}/{key}` | write | İmzalı URL |
| POST/PUT | `/storage/multipart…` | write | Parçalı yükleme |

\* Public bucket okuma auth'suz; private → key/token.

**Yükleme yanıtı:**

```json
{
  "project_id": "...",
  "key": "hero.png",
  "size": 48210,
  "content_type": "image/png",
  "id": "a1b2c3d4-e5f6-7890.png",
  "original_name": "hero.png"
}
```

```html
<img src="https://cdn-tr-01.liap.cloud/storage/objects/data/a1b2c3d4-e5f6-7890.png" alt="Hero" />
```

**Node — public yükle:**

```javascript
const EDGE = 'https://cdn-tr-01.liap.cloud'
const KEY = 'liap_live_...'

const bytes = await fs.readFile('logo.png')
const put = await fetch(
  `${EDGE}/storage/objects/data/logo.png`,
  {
    method: 'PUT',
    headers: { 'X-Liap-Key': KEY, 'Content-Type': 'image/png' },
    body: bytes,
  },
)
const obj = await put.json()
const publicUrl = `${EDGE}/storage/objects/data/${obj.id}`
```

Iframe yükleyici: `GET/POST {EDGE}/upload?key=liap_live_…&bucket=data` (`storage:write`). Proje API anahtarından çözülür.

---

## 7. Realtime API (`service=realtime`)

| Method | Path | Scope |
|--------|------|-------|
| GET | `/realtime/stream?table=todos` | subscribe (SSE) |
| GET | `/realtime/ws?table=todos` | subscribe (WebSocket) |
| GET | `/realtime/presence?channel=lobby` | subscribe |
| POST | `/realtime/presence` | subscribe |
| POST | `/realtime/broadcast` | subscribe |

WebSocket edge üzerinden:

```text
wss://{EDGE}/realtime/ws?table=todos
Header: Authorization: Bearer lca_...
```

DB satır değişiklikleri Postgres NOTIFY → wal_bridge → SSE event.

SSE event örneği:

```json
{
  "project_id": "...",
  "table": "todos",
  "event": "insert",
  "row": { "id": "...", "data": {}, "created_at": "...", "updated_at": "..." }
}
```

---

## 8. Functions API (`service=functions`)

| Method | Path | Scope |
|--------|------|-------|
| GET | `/functions` | invoke |
| POST | `/functions/{name}` | invoke — `{ "a": 1, "b": 2 }` body |
| PUT | `/functions/{name}` | invoke — deploy kaynak (service-role) |
| DELETE | `/functions/{name}` | invoke |
| GET | `/functions/runtime` | invoke |
| POST | `/functions/jobs` | invoke — arka plan iş |

Invoke yanıt:

```json
{ "result": { ... }, "logs": ["..."] }
```

---

## 9. Search (`service=search`)

```http
GET /search/{table}?q=rapor&limit=20
X-Liap-Key: liap_live_...
```

```http
POST /search/semantic
Content-Type: application/json
```

Scope: `data:read`.

---

## 10. Notifications (`service=notifications`)

Push (Expo, FCM, APNs, Web Push), e-posta (Resend) ve uygulama içi bildirimler. Gönderimi Liap yapar; sunucunuzda Expo veya Resend SDK'sı gerekmez.
Sağlayıcı anahtarları dashboard → Proje → **API Bilgileri**'nden girilir ve yalnız projenin edge'inde (`secrets` servisi) saklanır.

| Method | Path | Yetki |
|--------|------|-------|
| GET | `/notifications` | son kullanıcı `Bearer lca_` |
| POST | `/notifications` | `data:write` — uygulama içi bildirim (user_id yoksa herkese) |
| POST | `/notifications/{id}/read` | `Bearer lca_` |
| POST | `/notifications/devices` | `Bearer lca_` (kendi cihazı) veya sunucu + `user_id` |
| GET | `/notifications/devices` | sunucu — `{ total, users, by_platform }` |
| POST | `/notifications/push` | `data:write` |
| POST | `/notifications/email` | `notifications:send` |
| GET | `/notifications/vapid-public-key` | açık |

**İki anahtar kullanın:** istemci anahtarı (`data:read` + `data:write`) uygulamaya gömülür ve cihaz kaydı yapar; sunucu anahtarı
(`data:write` + `notifications:send`) yalnız sunucunuzda durur ve push / e-posta gönderir. API key ile birlikte `Authorization: Bearer lca_…`
gönderildiğinde cihaz kaydı ve gelen kutusu oturumdaki kullanıcıya bağlanır.

### Expo push

1. API Bilgileri → Expo kartı: Expo'da "Enhanced push security" açıksa `EXPO_ACCESS_TOKEN` girin (kapalıysa boş kalabilir).
2. Uygulamada token'ı kaydedin (oturum açıkken):

```js
import * as Notifications from 'expo-notifications'
const { data: expoPushToken } = await Notifications.getExpoPushTokenAsync({ projectId: EAS_PROJECT_ID })
await liap.notifications.registerExpoToken(expoPushToken)   // POST /notifications/devices
```

3. Sunucudan gönderin:

```json
POST /notifications/push
{ "title": "Sipariş yolda", "body": "#1042", "user_id": "…", "data": { "orderId": 1042 },
  "sound": "default", "badge": 1, "channel_id": "orders" }
```

`ExponentPushToken[…]` biçimindeki token platform yazılmasa da `expo` sayılır. `tokens: ["ExponentPushToken[…]"]` verilirse kayıt olmadan
doğrudan bu token'lara gönderilir (en fazla 1000, in-app kaydı oluşmaz). Yanıt: `expo_targets`, `expo_delivered`, `expo_failed`,
`expo_errors`, `expo_removed` — Expo `DeviceNotRegistered` diyen cihazlar kayıttan otomatik silinir.

FCM / APNs / Web Push cihazları aynı uçla `platform: "fcm" | "apns" | "webpush"` kaydedilir (webpush token = PushSubscription JSON);
bu kanalların anahtarları edge ortam değişkenlerindedir (`LIAP_FCM_SERVER_KEY`, `LIAP_VAPID_*`, `LIAP_APNS_*`).

### E-posta (Resend)

1. resend.com'da alan adınızı doğrulayın ve "Sending access" yetkili bir API anahtarı oluşturun.
2. API Bilgileri → Resend kartı: `RESEND_API_KEY` ve (opsiyonel) varsayılan gönderen `RESEND_FROM` (ör. `Uygulamam <noreply@alanadim.com>`).
3. Sunucu anahtarına `notifications:send` kapsamını verin ve gönderin:

```json
POST /notifications/email
{ "from": "Destek <destek@alanadim.com>", "to": "musteri@ornek.com",
  "subject": "Hoş geldin", "html": "<h1>Merhaba</h1>", "text": "Merhaba",
  "cc": [], "bcc": [], "reply_to": "cevap@alanadim.com" }
→ 200 { "id": "…", "provider": "resend", "from": "…", "to": ["…"] }
```

`from`, doğrulanmış alan adınızdaki herhangi bir adres olabilir; verilmezse `RESEND_FROM`. `to`/`cc`/`bcc`/`reply_to` tek adres veya dizi
(toplam en fazla 50 alıcı). Hatalar: `412` Resend bağlı değil, `400` şema hatası veya Resend reddi (geçersiz anahtar, doğrulanmamış alan adı —
Resend'in mesajı döner), `429` Resend hız sınırı, `502` Resend'e ulaşılamadı.

Ayrıntılı rehber ve SDK örnekleri: website `/docs/notifications`.

---

## 10.1 Live (canlı log ve istatistik)

Edge'e gelen her istek proje bazında kaydedilir (gövde ve query string kaydedilmez). Dashboard oturumu veya
`auth:admin` scope'lu API key gerekir.

| Method | Path | Açıklama |
|--------|------|----------|
| GET | `/live?since={seq}` | Anlık görüntü: `{ seq, events, stats, edge }` |
| GET | `/live/stream` | SSE: `hello` (anlık görüntü), `log` (her olay), `stats` (2 sn'de bir) |
| GET | `/public/traffic` | Kimliksiz SSE (`hit`): yalnız servis, bayt, ~11 km'ye yuvarlı konum — ana sayfa haritası |
| GET | `/public/traffic/recent?since={seq}` | Aynı akışın yoklama sürümü |

Olaylar `kind: "change"` ile işaretlenir ve okunur bir `label` taşır (ör. `API bilgisi güncellendi: RESEND_API_KEY`).
Konum Cloudflare ziyaretçi başlıklarından gelir: tam konum için Cloudflare → Rules → Managed Transforms →
**Add visitor location headers** açık olmalı (kapalıysa yalnız ülke kullanılır).

---

## 11. Monitor / Analytics (`service=monitor` veya `analytics`)

Tarayıcıda **`POST /analytics/events` kullanmayın** — uBlock/EasyList bu yolu keser (`HTTP 0 Failed to fetch`). Tarayıcı ve SDK: `/monitor/events`. Curl için eski yol durur.

| Method | Path | Scope |
|--------|------|-------|
| GET | `/monitor/status` | auth:admin |
| POST | `/monitor/crashes` | auth:admin |
| GET | `/monitor/crashes` | auth:admin |
| POST | `/monitor/events` | write (önerilen) |
| GET | `/monitor/summary` | read (önerilen) |
| POST | `/analytics/events` | write (alias; tarayıcıda engellenir) |
| GET | `/analytics/summary` | read (alias) |

---

## 12. Webhooks (`service=webhooks`)

Scope: `data:write`.

| Method | Path |
|--------|------|
| GET | `/webhooks` |
| POST | `/webhooks` | `{ "url": "https://…", "events": ["insert","update","delete"] }` |
| DELETE | `/webhooks/{id}` |
| POST | `/webhooks/dispatch` | test |

Tablo insert/update/delete → edge eşleşen URL’lere JSON POST.

---

## 13. Secrets (`service=secrets`)

Scope: `data:read` / `data:write`. Dashboard'daki **API Bilgileri** bu servisi kullanır (`EXPO_ACCESS_TOKEN`, `RESEND_API_KEY`, `RESEND_FROM`);
gizli olmayan ayarlar (ör. `RESEND_FROM`) listede açık döner, diğerleri maskelenir.

| Method | Path |
|--------|------|
| GET | `/secrets` | değerler maskeli |
| PUT | `/secrets/{key}` | `{ "value": "…" }` |
| DELETE | `/secrets/{key}` |
| GET | `/secrets/flags` |
| PUT/DELETE | `/secrets/flags/{key}` |

---

## 14. Tam örnek: Todo uygulaması (Node.js)

Aşağıdaki kod çalışan bir minimal istemci üretir. Değiştir: `EDGE_BASE`, `API_KEY`.

```javascript
const EDGE_BASE = 'https://cdn-tr-01.liap.cloud'
const API_KEY = 'liap_live_YOUR_KEY_HERE'

const gw = EDGE_BASE

async function api(service, path, { method = 'GET', body } = {}) {
  const headers = { 'X-Liap-Key': API_KEY }
  if (body) headers['Content-Type'] = 'application/json'
  const res = await fetch(`${gw}/${service}${path}`, {
    method,
    headers,
    body: body ? JSON.stringify(body) : undefined,
  })
  const json = await res.json().catch(() => ({}))
  if (!res.ok) throw new Error(json.error || res.statusText)
  return json
}

// 1) Todo ekle
const row = await api('db', '/tables/todos/rows', {
  method: 'POST',
  body: { data: { title: 'Liap ile ilk kayıt', done: false } },
})
console.log('created', row)

// 2) Listele
const list = await api('db', '/tables/todos/rows?limit=20')
console.log('rows', list)

// 3) Güncelle
await api('db', `/tables/todos/rows/${row.id}`, {
  method: 'PATCH',
  body: { data: { done: true } },
})

// 4) Storage — rezerve `public` bucket'a yükle (herkes görebilir)
const png = Uint8Array.from([137, 80, 78, 71]) // örnek
const up = await fetch(`${EDGE_BASE}/storage/objects/public/logo.png`, {
  method: 'PUT',
  headers: { 'X-Liap-Key': API_KEY, 'Content-Type': 'image/png' },
  body: png,
})
const file = await up.json()
console.log('public url', `${EDGE_BASE}/storage/objects/public/${file.id}`)
```

### Son-kullanıcı akışı (Key + oturum)

```javascript
async function authApi(path, { method = 'GET', body, token } = {}) {
  const headers = { 'X-Liap-Key': API_KEY }
  if (token) headers.Authorization = `Bearer ${token}`
  if (body) headers['Content-Type'] = 'application/json'
  const res = await fetch(`${gw}/auth${path}`, {
    method,
    headers,
    body: body ? JSON.stringify(body) : undefined,
  })
  return res.json()
}

const session = await authApi('/register', {
  method: 'POST',
  body: { email: 'demo@example.com', password: 'demo12345', name: 'Demo' },
})
const token = session.access_token

const me = await authApi('/me', { token })

const todos = await fetch(`${gw}/db/tables/todos/rows`, {
  headers: { 'X-Liap-Key': API_KEY, Authorization: `Bearer ${token}` },
}).then((r) => r.json())
```

---

## 15. SDK'lar

| Dil | Kurulum | Not |
|-----|---------|-----|
| Node.js | `npm install @liap-cloud/sdk` | `createClient(baseUrl, { apiKey })` |
| Python | `pip install liap` | Aynı API kalıbı |
| Rust | `liap` crate (monorepo) | `Client::new(...)` |

SDK API yolunu otomatik kurar; yukarıdaki ham HTTP ile birebir uyumludur.

---

## 16. Sağlık kontrolü

```http
GET {EDGE_BASE}/health
```

```json
{ "status": "ok" }
```

---

## 17. Sık sorulanlar

**Q: `GET /auth/me` → 401 «Bearer token gerekli» veya oturum düşüyor?**  
A: Login sonrası **hem** `X-Liap-Key` **hem** `Authorization: Bearer lca_…` gönderin. Yalnız key veya yalnız Bearer yetmez. SDK (`createClient(EDGE, { apiKey })`) login’den sonra ikisini otomatik ekler.

**Q: `POST /auth/register` gövdesinde `project_id` gerekir mi?**  
A: Hayır. Proje anahtardan çözülür.

**Q: `POST /auth/register` veya `/db/tables` → 404 «bu hostname kayıtlı değil»?**  
A: `X-Liap-Key` (API anahtarı) gönderin. Path’te proje UUID gerekmez. Özel alan adı proje **adı** değil; Dashboard → Alan adları’na ekleyin.

**Q: `database` servisi var mı?**  
A: Servis alias'ı `db` kullanın (`/db/...` + `X-Liap-Key`).

**Q: Supervisor URL ile edge URL farkı?**  
A: Supervisor aynı `/{service}/…` yolunu (API key ile) projenin bölgesindeki edge'e proxy eder. Üretimde genelde `data_plane_url` (edge) kullanın.

**Q: Proje `provisioning` durumunda?**  
A: Edge tenant DB oluşturuyor; birkaç saniye sonra `active` olur.

**Q: Public dosya URL’sinde proje ID neden yok?**  
A: Yol `/storage/objects/{bucket}/{shortId}` — proje değişse bile short_id aynı kalır (restore dokümantasyonu).

**Q: Tablo adı kuralları?**  
A: Küçük harf, rakam, `_`; 1–63 karakter; harf veya `_` ile başlamalı.

**Q: Dashboard özellik rehberi?**  
A: Website docs: `/docs` · API: `/docs/api`

---

## 18. Versiyon

- API sürümü: **v1**
- API prefix: `/{service}/` + `X-Liap-Key` (eski `/{projectId}/{service}/` hâlâ çalışır; key varsa UUID yok sayılır)
- Header: `X-Liap-Key` (proje), `Authorization: Bearer lca_…` (son-kullanıcı), `X-Liap-Project` (yalnız dashboard)
- Public storage: `/storage/objects/{bucket}/{shortId}`
- Bu belge: Liap Cloud monorepo `docs/api-reference.md` (website: `public/docs.md`)
- Yayın: yalnızca website `/docs` ve `/docs/api`
