# OXRO entegrasyon rehberi

Sürüm: web-console 0.3.0 · Doğrulama tarihi: 13 Eylül 2026.

Bu rehber `services/web-console/server.mjs`, `security.mjs`, `lab-runner.mjs`, `lab-catalog.mjs` ve `schema.sql` kaynaklarına dayanır. Önceki yayın doğrulamasında 18 API kontrolü ve 16 tarayıcı/sunucu tanılaması başarılıdır; bu dokümantasyon çalışması sırasında canlı veri oluşturan istekler tekrar gönderilmemiştir.

## 1. Adresler ve kapsam

| Adres | Kullanım |
|---|---|
| `https://node.orc-server.roketdns.com` | API temel adresi ve yönetim arayüzü |
| `https://node.orc-server.roketdns.com/#tests` | Kullanıcı etkileşimli Test Merkezi; `#tests` sunucuya gönderilmez |
| `https://orc-server.roketdns.com` | Tanıtım sayfası; API temel adresi değildir |

[OpenAPI 3.1 dosyası](../api/console.openapi.json) Postman ve uyumlu istemci araçlarına aktarılabilir. Dosya projede teslim edilir; `/openapi.json` veya Swagger UI endpointi mevcut değildir. [Python istemcisi](../examples/console_client.py) ek bağımlılık gerektirmez.

Panel menüsündeki **Entegrasyon dokümanı** bağlantısı `/docs.html` sayfasını açar. Rehber ve indirme dosyaları oturum gerektirmeyen, sır içermeyen geliştirici kaynaklarıdır. Web üzerinden OpenAPI `/console.openapi.json`, Python örneği `/console_client.py`, Markdown rehberi `/integration-guide.md` adreslerinden alınabilir.

Çalışan ürün, tek yönetici çalışma alanına sahip bir oda **planlama** panelidir. Oda oluşturmak medya oturumu başlatmaz, Edge tahsis etmez ve katılım tokenı üretmez. Yönetici, operatör ve salt okunur roller aynı oda/geçmiş çalışma alanını paylaşır; kullanıcı veya tenant bazında veri izolasyonu yoktur. Bu API'yi dış müşterilere doğrudan yetki devretmek için kullanmayın. Harici uygulamanızın backend'i kendi kullanıcı yetkilendirmesini yapmalı ve OXRO erişimini sunucu tarafında tutmalıdır.

## 2. Kimlik doğrulama ve bağlantı

1. `POST /api/login` ile uygulama kullanıcı adı/parolasını gönderin. SSH, SFTP veya veritabanı parolası uygulama giriş parolası değildir.
2. `Set-Cookie` yanıtındaki `__Host-oxro` cookie'sini bir cookie jar içinde saklayın; JSON içindeki `csrf` değerini alın.
3. Diğer API çağrılarında cookie'yi gönderin. Her POST için `Origin: https://node.orc-server.roketdns.com` gerekir; login dışındaki POST'larda ayrıca `X-CSRF-Token: <csrf>` gerekir.
4. `GET /api/me` ile geçerli oturumun kullanıcı adı ve CSRF değerini tekrar alabilirsiniz.
5. İş bittiğinde `POST /api/logout` çağırın.

Cookie `HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=28800` niteliklerine sahiptir. Oturumun azami ömrü 8 saattir; isteklerle uzatılmaz. Redis kaydı kaybolursa, süre dolarsa veya kullanıcı silinirse oturum geçersiz olur. Ayrı refresh endpointi yoktur. Rol/durum/parola değişikliği auth_version değerini artırır ve eski oturumları geçersiz kılar. Pasif veya silinmiş kullanıcı giriş yapamaz. `Authorization: Bearer` ve API key desteği yoktur. Login yeni oturum üretir; eski oturumların tamamını kapatan endpoint bulunmaz.

Tarayıcıda aynı origin üzerinde `fetch(..., {credentials:'same-origin'})` kullanılabilir. Ayrı bir domainden doğrudan browser entegrasyonu desteklenmez: CORS başlıkları yoktur, OPTIONS 405 döner ve POST origin kontrolü uygulanır. Tarayıcıda Origin başlığını değiştirmeye çalışmayın. Harici uygulama için backend-to-backend HTTPS istemcisi kullanın; bu istemci gerekli Origin başlığını açıkça gönderir. Cookie, CSRF ve parola loglanmamalı; mobil/masaüstü dağıtılan uygulamalara yönetici parolası gömülmemelidir.

## 3. Ortak HTTP sözleşmesi

- JSON gövde okuyan endpointler `Content-Type: application/json` ister; `; charset=utf-8` kabul edilir. Azami gövde 16.384 bayttır.
- API yalnızca GET ve POST metodlarını kabul eder. Bilinmeyen API yolları oturum kontrollerinden sonra 404 döner; oturumsuz çağrı önce 401 alabilir. Bilinen yolun desteklenmeyen GET/POST birleşimi de 404 olabilir; diğer metodlar 405'tir.
- Query parametreleriyle filtreleme/sayfalama desteklenmez; router yalnızca pathname kullanır. Son `/` ayrı yoldur. Dokümandaki yolları aynen kullanın.
- Yanıtlar JSON ve `Cache-Control: no-store` içerir. `X-Request-ID` her yanıtta bulunur. Hata kaydı açarken bu kimliği saklayın.
- Tarihler UTC ISO 8601 dizeleridir; süreler milisaniyedir. UUID'leri opak kimlik olarak saklayın.
- İstemci yeni yanıt alanlarını tolere etmelidir. URL içinde `/v1` sürümleme yoktur; `version` alanı uygulama sürümüdür, geriye uyumluluk garantisi değildir.
- Otomatik redirect izleyen istemciler cookie ve CSRF'yi başka hosta taşımamalıdır. TLS sertifikası doğrulamasını kapatmayın.

## 4. Endpoint envanteri

| Metod | Yol | Oturum | CSRF | İşlem |
|---|---|---|---|---|
| GET | `/healthz` | Hayır | Hayır | Süreç canlılığı |
| POST | `/api/login` | Hayır | Hayır | Oturum açma; Origin zorunlu |
| GET | `/api/me` | Evet | Hayır | Oturum bilgisi |
| POST | `/api/logout` | Evet | Evet | Mevcut oturumu kapatma |
| GET | `/api/overview` | Evet | Hayır | Odalar, son olaylar, bağımlılık durumu |
| POST | `/api/rooms` | Evet | Evet | Planlanmış oda oluşturma |
| POST | `/api/rooms/{id}/archive` | Evet | Evet | Planlanmış odayı arşivleme |
| GET | `/api/lab/catalog` | Evet | Hayır | Test ve özellik kataloğu |
| POST | `/api/lab/run` | Evet | Evet | Yedi sunucu tanılamasını çalıştırma |

### GET /healthz

Gövde yok. `200`:
```json
{"status":"ok","service":"oxro-console"}
```
Bu endpoint her çağrıda PostgreSQL/Redis kontrolü yapmaz. 200 yanıtını tüm bağımlılıklar hazır anlamında değerlendirmeyin. Servis başlangıçta DB/Redis bağlantısı bekler. `/readyz` bu uygulamada yoktur.

### POST /api/login

```http
POST /api/login
Origin: https://node.orc-server.roketdns.com
Content-Type: application/json

{"username":"admin","password":"<uygulama-parolası>"}
```

| Alan | Tip | Kural |
|---|---|---|
| username | string, zorunlu | Normalizasyon öncesi en fazla 64 JavaScript UTF-16 kod birimi; sorgudan önce küçük harfe çevrilir ve trim uygulanır |
| password | string, zorunlu | En fazla 256 UTF-16 kod birimi; trim uygulanmaz |

Mevcut uygulama bu iki alan için minimum uzunluk doğrulamaz; boş değerler geçerli hesapla eşleşmezse 401 alır. Ek login alanları yok sayılır; istemciler yalnızca belgelenen alanları göndermelidir.

`200` ve Set-Cookie (0.3 ayrıca `role` ve `auth_version` döner):
```json
{"username":"admin","csrf":"<oturuma-özel-değer>"}
```
Hatalar: `400 INVALID_INPUT`, `401 INVALID_CREDENTIALS`, ortak JSON/origin/hız sınırı/servis hataları. Hatalı ve başarılı giriş denemeleri login hız sınırına dahildir.

### GET /api/me

Gövde yok. `200` yanıtı `id`, `username`, `csrf`, `role` içerir. `401 AUTH_REQUIRED` oturumun bulunmadığını veya artık geçerli olmadığını bildirir.

### POST /api/logout

Cookie, Origin ve X-CSRF-Token gerekir. Gövde zorunlu değildir; `{}` gönderilebilir. Handler gövdeyi ayrıştırmaz. `200 {"ok":true}` döner, mevcut Redis oturumunu siler ve cookie'yi `Max-Age=0` ile temizler. Aynı oturumla tekrar çağrı 401 alır.

### GET /api/overview

Parametre/gövde yok. Örnek `200`:
```json
{
  "rooms":[{"id":"11111111-1111-4111-8111-111111111111","name":"Entegrasyon odası","description":"Planlama","capacity":20,"status":"planned","created_at":"2026-09-13T09:00:00.000Z"}],
  "events":[{"action":"created","room_name":"Entegrasyon odası","created_at":"2026-09-13T09:00:00.000Z"}],
  "health":{"postgres":true,"redis":true},
  "version":"0.3.0","scope":"management-console","mediaEnabled":false
}
```

`rooms`: created_at azalan sırada **en fazla 500** oda, arşivlenmiş odalar dahil. Durum `planned` veya `archived`. `updated_at`/`created_by` verilmez. `events`: olay kimliği azalan sırada **en fazla 50** olay; `action`, `room_name`, `created_at` içerir, olay/oda/aktör kimliği içermez. Normal işlemler `created`, `archived`, `updated` ve `restored` üretir; istemci action alanında başka dizelere dayanıklı olmalıdır. Liste toplam sayısı veya cursor yoktur. Aynı ada sahip oda olabilir; olayları isimden kesin kimlikle eşleştiremezsiniz. Eksiksiz veri senkronizasyonu veya muhasebe/audit dışa aktarımı için uygun değildir.

`health` anlık boolean değerlerdir. Önce oturum Redis/DB erişimi gerektirdiğinden kesinti bazen sağlık alanlı 200 yerine 503 üretir. `mediaEnabled:false` beklenen mevcut kapsamdır.

### POST /api/rooms

0.3: yönetici veya operatör rolü gerekir; viewer 403 alır.

```json
{"name":"Entegrasyon odası","description":"Harici uygulamadan planlandı","capacity":20}
```

| Alan | Tip | Kural |
|---|---|---|
| name | string, zorunlu | Trim sonrası 2–80 UTF-16 kod birimi |
| description | string, isteğe bağlı | Varsayılan boş dize; trim öncesi en fazla 500 UTF-16 kod birimi, saklamadan önce trim |
| capacity | integer, zorunlu | 2–500 dahil; `"20"` kabul edilmez |

Ek alan, null, array ve geçersiz tip reddedilir. Aynı isimle birden fazla oda oluşturulabilir. `201`:
```json
{"id":"11111111-1111-4111-8111-111111111111"}
```
Oda `planned` durumunda yaratılır; oda ve `created` olayı aynı transaction içinde yazılır. HTTP Location başlığı veya tam oda nesnesi dönmez. Hatalar: `400 INVALID_ROOM`, `409 ROOM_LIMIT` (planlanmış oda sınırı 200) ve ortak hatalar. Oda oluşturma ve geri açma ortak transaction advisory lock kullanarak 200 aktif plan sınırını korur. Paket üzerindeki oda_limit/kota alanları bu global sınırın yerine geçmez.

**Tekrar deneme:** Idempotency-Key desteği yoktur. Zaman aşımında kayıt oluşmuş olabilir. Körlemesine tekrar POST mükerrer oda oluşturabilir. İşlem sonucunu uzlaştırmadan tekrar göndermeyin; liste sınırları nedeniyle API kesin bir idempotency çözümü sunmaz.

### POST /api/rooms/{id}/archive

`id` için oluşturma yanıtından alınan küçük harfli UUID'yi aynen kullanın. Gövde zorunlu değildir ve ayrıştırılmaz. `200 {"ok":true}`. Yalnızca `planned` oda `archived` olur; fiziksel silme yapılmaz. Olay aynı transaction'da eklenir.

Olmayan **veya zaten arşivlenmiş** oda: `404 ROOM_NOT_FOUND`. Yeniden çağrı 200 dönmez. İstemci açısından 404 tek başına daha önce arşivlendiğini kanıtlamaz. Arşivden geri alma ve düzenleme 0.3 ile eklendi (§10). Fiziksel oda silme endpointi yoktur. Router tam UUID doğrulayıcı değildir; bazı bozuk 36 karakterli değerler DB hatası nedeniyle 503 alabilir. Geçerli API kimliklerini kullanın.

### GET /api/lab/catalog

`200` şekli:
```json
{"catalog":[{"id":"db-connect","name":"PostgreSQL bağlantısı","group":"Sunucu","scope":"Canlı servis","runner":"server","description":"Gerçek veritabanına sınırlandırılmış SELECT sorgusu."}],"version":"0.3.0","mediaServerEnabled":false}
```
Örnek tek karta kısaltılmıştır; mevcut katalog 30 karttır. Alanlar string, `runner` ise `server | browser | manual | unavailable` değeridir. Kart adları/açıklamaları görüntüleme içindir; entegrasyonda `id` kullanın.

| runner | Kimlikler |
|---|---|
| server (7) | db-connect, db-role, db-lifecycle, db-constraint, redis-roundtrip, redis-acl, password |
| browser (9) | https, auth-boundary, csrf-boundary, input-boundary, api-latency, crc, aes, hmac, webrtc-local |
| manual (2) | camera, microphone |
| unavailable (12) | lease, zero-copy, sfu, webtransport, mqtt, mls, sdk, pqc, a2a, worm, xdp, multi-region |

### POST /api/lab/run

0.3: yalnızca yönetici çalıştırabilir; diğer roller 403 alır.

JSON gövde **tam olarak boş nesne `{}`** olmalıdır. Komut, test seçimi veya parametre kabul edilmez. `200` örneği:
```json
{"runId":"22222222-2222-4222-8222-222222222222","startedAt":"2026-09-13T09:01:00.000Z","completedAt":"2026-09-13T09:01:00.300Z","results":[{"id":"db-connect","status":"passed","detail":"SELECT sorgusu başarılı.","durationMs":3}],"scope":"console-live-diagnostics"}
```
Örnek sonuç dizisi kısaltılmıştır; mevcut uygulama yedi sunucu sonucu üretir. `status` **passed veya failed**. HTTP 200, testlerin tamamının geçtiği anlamına gelmez; her sonuç kontrol edilmelidir. `detail` kullanıcıya yönelik açıklamadır, hata kodu olarak ayrıştırılmamalıdır.

DB yaşam döngüsü testleri transaction rollback kullanır; Redis testi rastgele uygulama anahtarı kullanır, 15 saniye TTL ile yazar ve temizler. DB sequence değerleri rollback ile geri sarılmayabilir. Sunucu tanılaması eşzamanlı tek çalışma kabul eder: `409 LAB_BUSY`. Kullanıcı başına dakikada dört çalışma sınırı vardır. Browser ve cihaz testleri **bu endpoint tarafından çalıştırılmaz**. Kamera/mikrofon açan REST endpointi yoktur.

Test Merkezi'nin indirdiği JSON yerel tarayıcıda oluşturulur: `schemaVersion:1`, `runId`, `startedAt`, `exportedAt`, `scope`, `features`. Her feature katalog alanlarını ve sonuç alanlarını içerir. UI durumları `untested`, `running`, `passed`, `failed`, `unsupported`, `unavailable` olabilir. Bu tarayıcı raporu `/api/lab/run` yanıtıyla aynı şema değildir. Rapor saklama/listeleme/indirme API'si yoktur.

## 5. Hatalar ve hız sınırları

Genel hata şekli `{"error":"ERROR_CODE"}`. Üst seviye exception yanıtlarında isteğe bağlı `requestId` da vardır; diğerlerinde kimliği `X-Request-ID` başlığından okuyun.

| HTTP | error | Açıklama |
|---|---|---|
| 400 | INVALID_JSON | JSON ayrıştırılamadı |
| 400 | INVALID_INPUT | Login alanları veya lab boş nesne kuralı geçersiz |
| 400 | INVALID_ROOM | Oda alanı/tipi/sınırı geçersiz |
| 401 | INVALID_CREDENTIALS | Login eşleşmedi |
| 401 | AUTH_REQUIRED | Oturum yok, geçersiz veya kullanıcı kaldırılmış |
| 403 | ORIGIN_REJECTED | POST Origin eksik/yanlış |
| 403 | CSRF_REJECTED | POST CSRF eksik/yanlış |
| 404 | NOT_FOUND | Yol/metod birleşimi yok |
| 404 | ROOM_NOT_FOUND | Oda yok veya zaten arşivli |
| 405 | METHOD_NOT_ALLOWED | API'de GET/POST dışı metod |
| 409 | ROOM_LIMIT | Planlanmış oda sınırı |
| 409 | LAB_BUSY | Başka sunucu test çalışması sürüyor |
| 413 | BODY_TOO_LARGE | JSON okuyucunun 16 KiB sınırı aşıldı |
| 415 | JSON_REQUIRED | JSON Content-Type eksik/geçersiz |
| 421 | INVALID_HOST | Host uygulama adresiyle uyuşmuyor |
| 429 | RATE_LIMITED | İstek limiti; Retry-After başlığını izleyin |
| 503 | SERVICE_UNAVAILABLE | Bağımlılık veya iç servis hatası |

Bu tablo uygulama yanıtlarıdır. Nginx/ağ katmanı HTML veya boş gövdeli 502/504 ve başka hatalar üretebilir; her yanıtın JSON olduğunu varsaymayın. Kontroller sıralıdır: host → metod/origin → Redis/hız sınırı → login veya session → CSRF → işlem. Dolayısıyla birden fazla kusurlu istekte ilk karşılaşılan hata döner.

| Sınır | Pencere | Retry-After |
|---|---|---|
| Tüm API trafiği toplam 600 | 60 saniye | 60 |
| IP başına 120 API isteği | 60 saniye | 60 |
| IP başına 10 login denemesi | 600 saniye | 600 |
| Kullanıcı başına 4 lab çalışması | 60 saniye | 60 |

Sayaçlar ilk istekle başlayan Redis TTL pencereleridir. Global/IP limitleri login ve lab limitleriyle birlikte uygulanır; NAT arkasındaki istemciler IP kotasını paylaşır. `/healthz` ve statik dosyalar uygulamadaki bu API sayaçlarına dahil değildir. Sağlık kontrolünü yoğun yük testi için kullanmayın.

GET çağrılarında 429/503 veya geçici ağ hatasında sınırlı, jitter içeren geri çekilme uygulanabilir; Retry-After'a uyun. 400/403/415'i aynı girdilerle tekrar göndermeyin. 401 sonrası kullanıcı/servis kimliğiyle kontrollü yeniden giriş gerekir. POST işlemlerini varsayılan olarak otomatik tekrar etmeyin.

## 6. Entegrasyon örnekleri

Python 3.12+ örneği standart kütüphaneyi kullanır. Parola konsolda gizli alınır; cookie bellekte tutulur. Varsayılan çalışma yalnızca giriş, oturum, overview ve çıkış yapar:

```powershell
python examples/console_client.py
```

Ek sunucu tanılaması için `--lab`; **gerçek oda oluşturup arşivleyen** yazma denemesi için `--exercise-room` kullanın. Yazma denemesi arşivlenmiş oda ve olay izi bırakır; canlı ortamda yalnızca bunu istediğinizde çalıştırın. Beklenmeyen yönlendirmeler reddedilir, TLS doğrulaması açıktır ve istek zaman aşımı 20 saniyedir.

Aynı origin üzerindeki güvenilir web uygulaması için, mevcut oturumla:
```javascript
const meResponse = await fetch('/api/me', {credentials: 'same-origin'});
if (!meResponse.ok) throw new Error(`Oturum hatası: ${meResponse.status}`);
const me = await meResponse.json();
const response = await fetch('/api/lab/run', {
  method: 'POST', credentials: 'same-origin',
  headers: {'Content-Type': 'application/json', 'X-CSRF-Token': me.csrf},
  body: '{}', signal: AbortSignal.timeout(20000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const report = await response.json();
const failed = report.results.filter(result => result.status !== 'passed');
console.log({runId: report.runId, failed});
```
Tarayıcı Origin ve cookie başlığını kendisi yönetir. Bu örnek farklı domaindeki sayfada doğrudan çalışmaz.

Postman: OpenAPI dosyasını içe aktarın; `baseUrl`, `username`, `password`, `csrf` değerlerini özel yerel ortamda tutun. Önce login'i gönderin, cookie jar'ı etkin bırakın, yanıttaki csrf'yi sonraki POST'ların X-CSRF-Token alanına aktarın. Parolalı ortamı dışa aktarmayın. Swagger UI farklı originde barındırılıyorsa Try it out CORS nedeniyle çalışmayacaktır; OpenAPI dosyası bu sınırlamayı kaldırmaz.

## 7. Statik HTTP kaynakları

Aşağıdakiler GET ile, oturumsuz sunulur; API veri işlemi değildir. Hepsi aynı Node temel adresindedir.

| Yol | Content-Type / kullanım |
|---|---|
| `/` | text/html; giriş/panel |
| `/app.js`, `/lab.js`, `/protocol.mjs` | text/javascript; uygulama modülleri |
| `/style.css`, `/lab.css` | text/css |
| `/vectors.json` | application/json; herkese açık CRC referans vektörleri |
| `/docs.html`, `/docs.css` | Entegrasyon rehberi HTML sayfası ve stili |
| `/console.openapi.json` | application/json; OpenAPI 3.1 sözleşmesi |
| `/console_client.py` | text/plain; Python istemci kaynak kodu |
| `/integration-guide.md` | text/markdown; rehberin indirilebilir kaynağı |

Vektör dosyası `{source, scope, frames:[{id,hex}]}` biçimindedir; mevcut beş referans frame içerir. Bunlar sır veya canlı paket verisi değildir. Statik JS modülleri sürümlenmiş harici SDK sözleşmesi değildir.

## 8. Tasarlanan fakat yayında olmayan arayüzler

Aşağıdakiler teknik şartname/proto tanımlarıdır; mevcut Node hostuna çağrı yaparak kullanılamazlar. `.example` alan adları yer tutucudur. Bu uçlar canlı OpenAPI dosyasına eklenmemiştir.

| Tasarlanan arayüz | Kaynak ve durum |
|---|---|
| CP `/api/v1/rooms`, `/healthz`, `/readyz` | [Faz 2](FAZ_2_CONTROL_PLANE.md); REST CRUD/DTO uygulaması ve nihai HTTP sözleşmesi henüz yok. Node `/api/rooms` ile eşdeğer değildir. |
| `wss://signalling.oxro.example/ws` | [Sinyalleşme sözleşmesi](contracts/SIGNALLING_AND_CONTROL_CONTRACT.md); çalışan WebSocket servisi yok |
| WebTransport Extended CONNECT `/wt?ticket=...` | [Güvenlik sözleşmesi](security/PROMPT_EXECUTION_AND_SECURITY_CONTRACT.md); normal GET değildir, tek kullanımlık bilet ve Edge kurulumu gerektirir; yayında yok |
| Edge UDP/QUIC, SFU, MQTT | İlgili faz uygulamaları henüz yok; bağlanılabilir üretim host/port veya broker hesabı teslim edilmedi |

[edge_control.proto](../proto/edge_control.proto) içindeki `oxro.control.v1.EdgeControlService` tanımları:

| gRPC metodu | İstek → yanıt | Amaç |
|---|---|---|
| ConnectNode | stream EdgeToCp → stream CpToEdge | Çift yönlü Edge komut/durum kanalı |
| RegisterNode | RegisterNodeRequest → RegisterNodeResponse | Düğüm kaydı |
| AllocateRoom | AllocateRoomRequest → AllocateRoomResponse | Oda tahsisi |
| FetchMissedCommits | FetchMissedCommitsRequest → FetchMissedCommitsResponse | Eksik commit alma |
| RegisterAuditSegment | RegisterAuditSegmentRequest → RegisterAuditSegmentResponse | Denetim segment kaydı |
| PutRetainedMessage | PutRetainedMessageRequest → PutRetainedMessageResponse | MQTT retained kalıcılığı |

Tam gRPC yol biçimi `/oxro.control.v1.EdgeControlService/<Metod>`. Mesaj alanları ve enum'lar proto dosyasında bağlayıcıdır. Bunlar Edge–CP iç arayüzleridir; son kullanıcı yönetim API'si değildir. Çalışan host, sertifika/kimlik akışı ve servis uygulaması olmadığı için canlı bağlantı örneği verilmez.

## 9. Entegrasyon kabul listesi ve kalan ihtiyaçlar

- Login, me ve logout akışını cookie jar ile doğrulayın; sırları loglamayın.
- Başarılı HTTP yanında lab sonuçlarını da inceleyin; unavailable özellikleri geçti saymayın.
- 401, 403, 429, 503, zaman aşımı ve JSON olmayan proxy yanıtını ele alın.
- Oda yazma için mükerrerlik riskini ve 500/50 liste sınırlarını iş akışınıza yansıtın.
- Uygulamanızdaki kullanıcı yetkilerini OXRO yönetici oturumundan önce denetleyin.

Dış müşterilere kapsamlı platform entegrasyonu için ayrıca servis hesapları/scoped tokenlar, tenant izolasyonu, idempotency, sayfalama, olay kimliği ve webhook/event aboneliği gerekir. Kullanıcı ve parola yönetimi, oda güncelleme/geri açma ve paket kataloğu 0.3 ile eklendi (§10). Self-service kayıt, fiziksel oda silme/katılma, dosya yükleme, kalıcı test raporu, ödeme ve medya API'si yoktur. Bu rehber eksik endpointleri varmış gibi tanımlamaz.


## 10. Dashboard 0.3: kullanıcılar, paketler ve hesap güvenliği

Tüm yeni yollar cookie oturumu gerektirir; POST yolları Origin ve CSRF de ister. POST gövdeleri JSON ve 16 KiB sınırına tabidir. Yeni yazma işlemleri bilinmeyen alanları reddeder. Yol kimlikleri geçerli küçük harf UUID olmalıdır (`400 INVALID_ID`).

| Rol | İzinler |
|---|---|
| admin | Kullanıcı/paket yönetimi, tüm oda işlemleri, sunucu testleri, denetim kayıtları |
| operator | Ortak odaları görüntüleme, oluşturma, düzenleme, arşivleme, geri açma; kendi parolası |
| viewer | Ortak odalar, dashboard, sistem ve katalog görüntüleme; kendi parolası |

Mevcut hesaplar migration sırasında admin kalır. Yeni hesap için rol açıkça gönderilir. Tenant izolasyonu veya kullanıcı başına oda sahipliği uygulanmaz. Kullanıcı liste sınırı 1000, paket sınırı 500'dür; silinmiş kullanıcılar 1000 sınıra dahildir. Tarihler ISO 8601'dir.

| Metod / yol | Yetki | Gövde / sonuç |
|---|---|---|
| GET `/api/dashboard` | Tüm roller | `{counts:{planned,archived,capacity,active_users,disabled_users,active_packages},trend:[{day,rooms}],mediaEnabled:false,billingEnabled:false}`; tüm kayıtlar üzerinden toplamlar, UTC son 14 gün |
| GET `/api/users` | admin | `{users:[{id,username,role,status,package_id,package_name,created_at}]}`; son 1000, silinenler dahil; parola/hash içermez |
| POST `/api/users` | admin | `{username,password,role,package_id?}` → 201 `{id}` |
| POST `/api/users/{id}/update` | admin | `{role,status,package_id}` → 200 `{ok:true}` |
| POST `/api/users/{id}/delete` | admin | `{}` → 200 `{ok:true}`; erişimi kapatan mantıksal silme |
| POST `/api/users/{id}/password` | admin | `{password}` → 200 `{ok:true}`; hedefin eski oturumlarını iptal eder |
| GET `/api/packages` | admin | `{packages:[{id,name,description,price_minor,currency,billing_period,room_limit,capacity,status,created_at,updated_at,assigned_users}],billingEnabled:false,quotaEnforced:false}` |
| POST `/api/packages` | admin | Paket gövdesi → 201 `{id}` |
| POST `/api/packages/{id}/update` | admin | Tüm paket alanları → 200 `{ok:true}` |
| POST `/api/packages/{id}/delete` | admin | `{}` → 200 `{ok:true}`; bağlı kullanıcı varsa 409 |
| GET `/api/audit` | admin | `{events:[{id,actor,action,target_id,detail,created_at}]}`; son 200 yönetim işlemi, id string |
| POST `/api/account/password` | Tüm roller | `{current_password,new_password}` → 200 `{ok:true,reauthenticate:true}`; yeniden giriş zorunlu |
| POST `/api/rooms/{id}/update` | admin/operator | Oda oluşturmayla aynı alanlar; 200 `{ok:true}`; aktif veya arşivlenmiş oda düzenlenebilir |
| POST `/api/rooms/{id}/restore` | admin/operator | `{}` → 200 `{ok:true}`; yalnızca arşivlenmiş oda geri açılır; aktif plan limiti 200 |

### Kullanıcı kuralları

Yeni kullanıcı adı `[a-z0-9][a-z0-9_.-]{2,63}` biçimindedir. Parola 12–256 UTF-16 kod birimidir. Rol `admin`, `operator` veya `viewer`. Kullanıcı update durumları `active` ve `disabled`; delete sonrasında `deleted` olur ve geri açma endpointi bulunmaz. Paket kimliği aktif paketin UUID'si veya null; null atamayı kaldırır. Silmede paket ataması kaldırılır. Kullanıcı adı ve denetim/oda ilişkileri saklanır; bu işlem kişisel veriyi anonimleştirme veya fiziksel silme değildir.

Kendi yönetici hesabını silmek, pasifleştirmek veya daha düşük role geçirmek `409 SELF_PROTECTION` ile reddedilir. Son aktif yöneticinin kaldırılması `409 LAST_ADMIN` ile engellenir. Rol, durum veya parola değişikliği mevcut oturumları geçersiz kılar. Aynı kullanıcı adı tekrar kullanılamaz (`409 ALREADY_EXISTS`). Kendi parola endpointinde kullanıcı başına 600 saniyede 5 deneme sınırı vardır (`429`, Retry-After: 600).

### Paket gövdesi ve sınırlar

```json
{"name":"Başlangıç","description":"Paket katalog tanımı","price_minor":9900,"currency":"TRY","billing_period":"monthly","room_limit":10,"capacity":20,"status":"active"}
```

Tüm alanlar zorunludur; description boş olabilir. name trim sonrası 2–80, description trim öncesi en fazla 500 karakter. price_minor 0–100000000 tamsayı küçük para birimidir (9900 = 99,00 TRY); floating-point fiyat gönderilmez. currency TRY/USD/EUR, billing_period monthly/yearly, room_limit 1–200, capacity 2–500, status active/archived. Ad benzersizdir. Arşivlenmiş paket mevcut atamalarda kalabilir; yeni atama kabul etmez. Paketin fiziksel silinmesi atanmış kullanıcı varken reddedilir.

Bu bir fiyat/paket kataloğudur. Ödeme sağlayıcısı, tahsilat, fatura, otomatik yenileme, abonelik başlangıç/bitişi veya paket kotasının gerçek zamanlı uygulanması yoktur. Farklı para birimleri gelir toplamı gibi birleştirilmez.

### Yeni hata kodları

`403 FORBIDDEN`, `403 CURRENT_PASSWORD_INVALID`; `400 INVALID_INPUT`, `400 INVALID_ID`, `400 PASSWORD_POLICY`, `400 PACKAGE_UNAVAILABLE`; `404 USER_NOT_FOUND`, `404 PACKAGE_NOT_FOUND`; `409 ALREADY_EXISTS`, `409 SELF_PROTECTION`, `409 LAST_ADMIN`, `409 PACKAGE_IN_USE`, `409 USER_LIMIT`, `409 PACKAGE_LIMIT`, `409 ROOM_STATE`. Önceki genel hata tablosu da geçerlidir.

Yönetim denetim kaydı kullanıcı/paket değişiklikleri, parola değişikliği ve oda güncelleme/geri açmayı içerir; oda oluşturma/arşivleme eski oda olay akışında tutulur. Audit tablosu WORM veya değiştirilemez harici denetim sistemi değildir. Parolalar/hashler audit detail içine yazılmaz.
