# Ağ geçidi ve müşteri API'si

> Müşterilerin modülünüzün API'sini api.cosuite.io üzerinden CoSuite anahtarlarıyla nasıl çağırdığı ve uygulamanızın bu istekleri nasıl doğrulayıp yanıtladığı (sözleşme 1.8).

Source: https://cosuite.io/developers/customer-api

Müşteriler modülünüzün API'sini **`https://api.cosuite.io/v1/apps/{app}/{path}`** adresinden kendi CoSuite API anahtarlarıyla çağırır. CoSuite anahtarı, aboneliği ve kapsamı denetler, ardından isteği uygulamanıza Connect imzasıyla ve çağıran işletmeyi `X-CoSuite-Tenant` başlığında belirterek iletir. Uygulamanız müşterinin anahtarını, müşteri de sizinkini hiç görmez.

```text
Müşteri yazılımı ──Bearer cs_api_…──▶ api.cosuite.io/v1/apps/dent/patients
                                            │  anahtar · abonelik · yol · kapsam · hız sınırı
                                            ▼
                      {server}/patients  ◀── Connect imzası + X-CoSuite-Tenant
```

## Uygulamanız için: ağ geçidi isteklerini karşılamak {#serving}

Ağ geçidi yalnız modülünüzün [OpenAPI 3.1 belgesinde](https://cosuite.io/developers/module-definition#api) bulunan işlemleri iletir. Bunun için müşterinin anahtarında, işlemin istediği bir kapsam olmalıdır.

**Adres.** OpenAPI belgenizdeki `servers[0].url`, uygulamanızın temel adresiyle aynı kökendeyse istekler oraya gider; değilse temel adrese. Yol ve sorgu değiştirilmeden eklenir: `GET /v1/apps/dent/patients?page=2` → `GET {server}/patients?page=2`.

**Gelen başlıklar** (Connect [imza](https://cosuite.io/developers/contract#signature) başlıklarına ek olarak):

| Başlık | Anlamı |
|---|---|
| `X-CoSuite-Gateway: 1` | İstek bir Connect çağrısından değil, ağ geçidinden geliyor. |
| `X-CoSuite-Tenant` | İşletmenin `tenantId`'si (`POST /tenants`'taki kimlikle aynı). **Yalnız bu işletmenin verisini** döndürün. |
| `X-CoSuite-Scopes` | Anahtarın bu istekte geçerli kapsamları, boşlukla ayrılmış. |
| `X-CoSuite-Api-Key-Id` | Müşterinin CoSuite anahtarının kimliği (anahtarın kendisi değil); kayıt ve sınırlarınız için. |
| `Content-Type`, `Accept`, `Accept-Language`, `If-Match`, `If-None-Match` | Müşteriden olduğu gibi aktarılır. |

Müşterinin `Authorization` başlığı hiçbir zaman iletilmez.

**Yükümlülükleriniz:**

- İmzayı doğrulayın ve tekrar kullanılan istek kimliklerini reddedin. `verifyGatewayRequest` ikisini birlikte yapar.
- Bilinmeyen ya da bağlı olmayan bir `tenantId` için `404` ve `tenant_not_found` döndürün, hiç veri döndürmeyin.
- 30 saniye içinde, en fazla 10 MB'lık bir yanıt verin ve asla yönlendirme yapmayın (3xx hata sayılır).
- İşlemin kapsamını ağ geçidi zaten denetledi; `X-CoSuite-Scopes` ile ayrıca daraltabilirsiniz.

```ts
import { verifyGatewayRequest, MemoryReplayStore } from "@cosuite/connect";

const replay = new MemoryReplayStore(); // birden çok süreçte Redis ya da veritabanınızı kullanın

export async function GET(req: Request) {
  const url = new URL(req.url);
  const body = await req.text();
  const v = await verifyGatewayRequest({
    method: req.method,
    pathAndQuery: url.pathname + url.search,
    body,
    headers: req.headers,
    secretsFor: (keyId) => (keyId === process.env.COSUITE_KEY_ID ? [process.env.COSUITE_SECRET!] : undefined),
    replay,
  });
  if (!v.ok) return Response.json({ ok: false, error: { code: "unauthorized", message: v.reason, retryable: false } }, { status: 401 });

  const account = await findAccountByCoSuiteTenant(v.context.tenantId);
  if (!account) return Response.json({ ok: false, error: { code: "tenant_not_found", message: "tenant not found", retryable: false } }, { status: 404 });

  return Response.json({ items: await listPatients(account.id) });
}
```

Diğer dillerde imzayı Connect çağrılarındaki gibi doğrulayın ve yukarıdaki başlıkları okuyun.

**Uyumluluk testi.** `api` tanımı olan her modülde, test parametresiz en fazla üç `GET` işlemini çağırır:

1. Test işletmesiyle: bir yanıt beklenir.
2. Bilinmeyen bir işletmeyle: `404 tenant_not_found` beklenir.
3. İmzasız olarak: `401` beklenir.

## Müşteriler için: API'yi çağırmak {#calling}

1. CoSuite panelinde **Yönetim › API anahtarları** ekranını açın ve bir anahtar oluşturun. Uygulamayı ve yalnız ihtiyacınız olan kapsamları seçin; mümkünse bir geçerlilik süresi belirleyin. Anahtar bir kez gösterilir.
2. Anahtarı bearer token olarak gönderin:

```bash
curl -H "Authorization: Bearer $COSUITE_API_KEY" \
  "https://api.cosuite.io/v1/apps/dent/patients?page=2"
```

Anahtar şu koşullarda çalışır:
- Yalnız oluşturulduğu uygulama için.
- Yalnız seçtiğiniz kapsamlarla.
- Yalnız modül aboneliğinizde olduğu sürece. Modülü bırakınca kapsamları çalışmaz olur.

Kaldırılan ya da süresi dolan anahtar hemen reddedilir.

Her çağrı API denetim kaydına yazılır ve 12 ay tutulur: anahtar, uç, kapsam, durum ve süre. Gövdeler asla kaydedilmez.

**Sınırlar:**

- İstek gövdesi en fazla 2 MB.
- İşletme başına dakikada 600, anahtar başına dakikada 300 istek.
- Art arda 5 kez hata veren uygulama 60 saniye bekletilir.

## Web kancaları {#webhooks}

**Uygulamanız**, bir modülün yaydığı olayları manifest'te tanımlar (`modules[].webhooks[]`, bkz. [modül tanımı](https://cosuite.io/developers/module-definition)). Her olayı CoSuite'e diğer [olaylar](https://cosuite.io/developers/contract#events) gibi gönderir: `POST /connect/v1/events`, gövdede `tenantId`, tekil bir `id`, tanımlı `type` ve `data`. CoSuite olayı abone olan işletmelere iletir; gövdeyi teslimden sonra saklamaz.

**Müşteriler** aboneliği **Yönetim › API anahtarları › Web kancaları** ekranından kurar: uygulamayı, olayları ve bir https adresi seçerler, imza için gizli değeri bir kez alırlar. Her teslim bir `POST` isteğidir. JSON gövdesi `{ "id", "type", "app", "occurredAt", "data" }` biçimindedir, şu başlıkları taşır:

| Başlık | Anlamı |
|---|---|
| `X-CoSuite-Event` | Olay türü, ör. `treatment.completed` |
| `X-CoSuite-Delivery` | Teslim kimliği (her yeniden denemede aynı) |
| `X-CoSuite-Timestamp` | Unix saniyesi |
| `X-CoSuite-Signature` | `v1=` + gizli değerle `{zaman}.{ham gövde}` üzerinden hex HMAC-SHA256 |

İmzayı doğrulayın, zamanı 5 dakika içindeyse kabul edin ve 10 saniye içinde `2xx` ile yanıt verin. Başarısız teslimler 24 saate kadar yeniden denenir (1, 5, 15, 60, 180, 360 ve 720 dakika sonra). Tekrarları olay `id`'siyle ayıklayın.

## Hatalar {#errors}

Uygulamanın kendi yanıtları (durum kodu ve gövde) değiştirilmeden döner. Ağ geçidinin ürettiği hatalar Connect zarfını kullanır: `{ "ok": false, "error": { "code", "message", "retryable" } }`.

| Durum | Kod | Ne zaman |
|---|---|---|
| 401 | `unauthorized` | Anahtar yok, geçersiz, kaldırılmış ya da süresi dolmuş |
| 403 | `forbidden` | Anahtar başka bir uygulamaya ait |
| 403 | `not_subscribed` | Anahtarın kapsamlarından hiçbiri güncel abonelikte yok |
| 403 | `insufficient_scope` | İşlem, anahtarda olmayan bir kapsam istiyor |
| 404 | `app_not_found` · `route_not_found` | Bilinmeyen uygulama ya da yol uygulamanın API'sinde yok |
| 405 | `method_not_allowed` | Yol var, yöntem yok |
| 409 | `not_linked` | İşletme henüz uygulamaya bağlanmamış (CoSuite'ten bir kez açın) |
| 413 | `payload_too_large` | Gövde 2 MB'tan büyük |
| 429 | `rate_limited` | Hız sınırı aşıldı; `Retry-After` başlığına bakın |
| 502 | `bad_gateway` | Uygulamaya ulaşılamadı, yönlendirme yaptı ya da 10 MB'tan büyük yanıt verdi |
| 503 | `app_unavailable` | Uygulama art arda hatalar yüzünden bekletiliyor; `Retry-After` başlığına bakın |
| 504 | `upstream_timeout` | Uygulama 30 saniye içinde yanıt vermedi |

Ağ geçidinin her yanıtında `X-CoSuite-Request-Id` başlığı bulunur; destekle iletişime geçerken bu kimliği belirtin.
