# Şlüz və müştəri API-si

> Müştərilərin modulunuzun API-sini api.cosuite.io vasitəsilə öz CoSuite açarları ilə necə çağırdığı və tətbiqinizin bu sorğuları necə yoxlayıb cavablandırdığı (müqavilə 1.8).

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

Müştərilər modulunuzun API-sini **`https://api.cosuite.io/v1/apps/{app}/{path}`** ünvanından öz CoSuite API açarları ilə çağırır. CoSuite açarı, abunəliyi və əhatəni yoxlayır, sonra sorğunu tətbiqinizə Connect imzası ilə ötürür; çağıran biznes `X-CoSuite-Tenant` başlığında göstərilir. Tətbiqiniz müştərinin açarını, müştəri də sizinkini heç vaxt görmür.

```text
Müştəri proqramı ──Bearer cs_api_…──▶ api.cosuite.io/v1/apps/dent/patients
                                            │  açar · abunəlik · yol · əhatə · sürət limiti
                                            ▼
                      {server}/patients  ◀── Connect imzası + X-CoSuite-Tenant
```

## Tətbiqiniz üçün: şlüz sorğularına cavab vermək {#serving}

Şlüz yalnız modulunuzun [OpenAPI 3.1 sənədində](https://cosuite.io/az/developers/module-definition#api) olan əməliyyatları ötürür. Bunun üçün müştərinin açarında əməliyyatın tələb etdiyi əhatələrdən biri olmalıdır.

**Ünvan.** OpenAPI sənədinizdəki `servers[0].url` tətbiqinizin əsas ünvanı ilə eyni mənbədədirsə, sorğular ora gedir; əks halda əsas ünvana. Yol və sorğu parametrləri dəyişmədən əlavə olunur: `GET /v1/apps/dent/patients?page=2` → `GET {server}/patients?page=2`.

**Gələn başlıqlar** (Connect [imza](https://cosuite.io/az/developers/contract#signature) başlıqlarına əlavə olaraq):

| Başlıq | Mənası |
|---|---|
| `X-CoSuite-Gateway: 1` | Sorğu Connect çağırışından deyil, şlüzdən gəlir. |
| `X-CoSuite-Tenant` | Biznesin `tenantId`-si (`POST /tenants`-dakı ilə eyni). **Yalnız bu biznesin məlumatını** qaytarın. |
| `X-CoSuite-Scopes` | Açarın bu sorğuda keçərli əhatələri, boşluqla ayrılmış. |
| `X-CoSuite-Api-Key-Id` | Müştərinin CoSuite açarının identifikatoru (açarın özü deyil); qeydlər və limitlər üçün. |
| `Content-Type`, `Accept`, `Accept-Language`, `If-Match`, `If-None-Match` | Müştəridən olduğu kimi ötürülür. |

Müştərinin `Authorization` başlığı heç vaxt ötürülmür.

**Öhdəlikləriniz:**

- İmzanı yoxlayın və təkrarlanan sorğu identifikatorlarını rədd edin. `verifyGatewayRequest` hər ikisini birlikdə edir.
- Naməlum və ya bağlanmamış `tenantId` üçün `404` və `tenant_not_found` qaytarın; heç bir məlumat qaytarmayın.
- 30 saniyə ərzində, ən çox 10 MB olan cavab verin və heç vaxt yönləndirmə etməyin (3xx xəta sayılır).
- Əməliyyatın əhatəsini şlüz artıq yoxlayıb; `X-CoSuite-Scopes` ilə əlavə məhdudlaşdıra bilərsiniz.

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

const replay = new MemoryReplayStore(); // bir neçə prosesdə Redis və ya verilənlər bazanızdan istifadə edin

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) });
}
```

Digər dillərdə imzanı Connect çağırışlarındakı kimi yoxlayın və yuxarıdakı başlıqları oxuyun.

**Uyğunluq testi.** `api` tərifi olan hər modulda test parametrsiz ən çox üç `GET` əməliyyatını çağırır:

1. Test biznesi ilə: cavab gözlənilir.
2. Naməlum biznes ilə: `404 tenant_not_found` gözlənilir.
3. İmzasız: `401` gözlənilir.

## Müştərilər üçün: API-ni çağırmaq {#calling}

1. CoSuite panelində **İdarəetmə › API açarları** bölməsini açın və açar yaradın. Tətbiqi və yalnız lazım olan əhatələri seçin; mümkünsə etibarlılıq müddəti təyin edin. Açar yalnız bir dəfə göstərilir.
2. Açarı bearer token kimi göndərin:

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

Açar bu şərtlərlə işləyir:
- Yalnız yaradıldığı tətbiq üçün.
- Yalnız seçdiyiniz əhatələrlə.
- Yalnız modul abunəliyinizdə olduğu müddətcə. Modulu buraxdıqda onun əhatələri işləməz.

Ləğv edilmiş və ya müddəti bitmiş açar dərhal rədd edilir.

Hər çağırış API audit qeydinə yazılır və 12 ay saxlanılır: açar, uc nöqtə, əhatə, status və müddət. Gövdələr heç vaxt saxlanılmır.

**Limitlər:**

- Sorğu gövdəsi ən çox 2 MB.
- Biznes başına dəqiqədə 600, açar başına dəqiqədə 300 sorğu.
- Ardıcıl 5 dəfə xəta verən tətbiq 60 saniyə dayandırılır.

## Veb-hooklar {#webhooks}

**Tətbiqiniz** modulun yaydığı hadisələri manifestdə təyin edir (`modules[].webhooks[]`, bax: [modul tərifi](https://cosuite.io/az/developers/module-definition)). Hər hadisəni CoSuite-ə digər [hadisələr](https://cosuite.io/az/developers/contract#events) kimi göndərir: `POST /connect/v1/events`, gövdədə `tenantId`, unikal `id`, təyin olunmuş `type` və `data`. CoSuite hadisəni abunə olan bizneslərə ötürür; gövdəni çatdırıldıqdan sonra saxlamır.

**Müştərilər** abunəliyi **İdarəetmə › API açarları › Veb-hooklar** bölməsində qurur: tətbiqi, hadisələri və https ünvanı seçirlər, imza üçün gizli dəyəri bir dəfə alırlar. Hər çatdırılma bir `POST` sorğusudur. JSON gövdəsi `{ "id", "type", "app", "occurredAt", "data" }` formasındadır və bu başlıqları daşıyır:

| Başlıq | Mənası |
|---|---|
| `X-CoSuite-Event` | Hadisə növü, məs. `treatment.completed` |
| `X-CoSuite-Delivery` | Çatdırılma identifikatoru (hər təkrarda eyni) |
| `X-CoSuite-Timestamp` | Unix saniyəsi |
| `X-CoSuite-Signature` | `v1=` + gizli dəyərlə `{vaxt}.{xam gövdə}` üzərindən hex HMAC-SHA256 |

İmzanı yoxlayın, vaxt 5 dəqiqə daxilindədirsə qəbul edin və 10 saniyə ərzində `2xx` ilə cavab verin. Uğursuz çatdırılmalar 24 saata qədər təkrar sınanır (1, 5, 15, 60, 180, 360 və 720 dəqiqədən sonra). Təkrarları hadisə `id`-si ilə ayıklayın.

## Xətalar {#errors}

Tətbiqin öz cavabları (status və gövdə) dəyişmədən qaytarılır. Şlüzün yaratdığı xətalar Connect zərfindən istifadə edir: `{ "ok": false, "error": { "code", "message", "retryable" } }`.

| Status | Kod | Nə vaxt |
|---|---|---|
| 401 | `unauthorized` | Açar yoxdur, etibarsızdır, ləğv edilib və ya müddəti bitib |
| 403 | `forbidden` | Açar başqa tətbiqə aiddir |
| 403 | `not_subscribed` | Açarın heç bir əhatəsi cari abunəlikdə yoxdur |
| 403 | `insufficient_scope` | Əməliyyat açarda olmayan əhatə tələb edir |
| 404 | `app_not_found` · `route_not_found` | Naməlum tətbiq və ya yol tətbiqin API-sində yoxdur |
| 405 | `method_not_allowed` | Yol var, metod yoxdur |
| 409 | `not_linked` | Biznes hələ tətbiqə bağlanmayıb (CoSuite-dən bir dəfə açın) |
| 413 | `payload_too_large` | Gövdə 2 MB-dan böyükdür |
| 429 | `rate_limited` | Sürət limiti aşılıb; `Retry-After` başlığına baxın |
| 502 | `bad_gateway` | Tətbiqə çatmaq olmadı, yönləndirmə etdi və ya 10 MB-dan böyük cavab verdi |
| 503 | `app_unavailable` | Tətbiq ardıcıl xətalara görə dayandırılıb; `Retry-After` başlığına baxın |
| 504 | `upstream_timeout` | Tətbiq 30 saniyə ərzində cavab vermədi |

Şlüzün hər cavabında `X-CoSuite-Request-Id` başlığı var; dəstəklə əlaqə saxlayarkən bu identifikatoru göstərin.
