# Connect v1 müqaviləsi

> Partnyorlar üçün CoSuite Connect v1 müqaviləsi: sorğu imzası, xəta zərfi, tenant, entitlement, üzv, istifadə və müştəri endpoint-ləri, SSO, versiyalama.

Source: https://cosuite.io/az/developers/contract

Bu səhifə CoSuite ilə sənin tətbiqin arasındakı tam serverdən-serverə müqavilədir. Müqavilə identifikatoru: `cosuite-connect/1.0`, cari paket versiyası **1.7**. Məqsəd bir cümlədə: **tətbiqin daxildə istədiyi qədər dəyişə bilər; bu müqaviləyə əməl etdikcə heç bir tərəfdə heç nə pozulmur.**

## Prinsiplər {#principles}

1. **Tətbiqin müstəqil məhsuldur.** Öz verilənlər bazasını, istifadəçilərini və interfeysini saxlayır. CoSuite heç vaxt onun içinə müdaxilə etmir; yalnız bu səhifədəki endpoint-ləri çağırır.
2. **Nə kimə aiddir:**

   | Mövzu | Sahibi |
   |---|---|
   | Biznesin (tenant) kimliyi, modul alışları, qiymətlər, fakturalar | CoSuite |
   | Entitlement-lər: hansı modul aktivdir, limitlər, bitmə tarixi | CoSuite (tətbiqin onlara riayət edir) |
   | Tətbiqinin öz məlumatları (xəstələr, rezervasiyalar, fayllar…) | Sənin tətbiqin |
   | İstifadəçinin tətbiqindəki sessiyası | Sənin tətbiqin (CoSuite yalnız kimliyi təsdiqləyir) |

3. **Ayrı ad sahəsi.** Bütün müqavilə endpoint-ləri əsas URL-ində `/api/cosuite/v1/*` altında yerləşir. Mövcud açıq API-nə toxunulmur.
4. **v1 daxilində yalnız əlavələr.** Sahələr, endpoint-lər və enum dəyərləri heç vaxt silinmir, adı dəyişdirilmir və yeni məna almır (bax: [versiyalama](#versioning)).
5. **Tanımadığını nəzərə alma.** Hər iki tərəf naməlum JSON sahələrini, modul açarlarını və hadisə növlərini səssizcə nəzərə almır. Əlavələri təhlükəsiz edən də budur.
6. **Sənin nasazlığın CoSuite-i dayandırmır.** CoSuite-in etdiyi hər çağırışın vaxt limiti, təkrar cəhdləri və növbəsi var (bax: [etibarlılıq](#reliability)).

## İdentifikatorlar {#identifiers}

| Termin | Mənası |
|---|---|
| tətbiq açarı | Tətbiqinin açarı, kiçik hərflərlə, 2–24 simvol (`^[a-z][a-z0-9]{1,23}$`), məsələn, `dent`. Manifestin `asist` sahəsində və SSO auditoriyası kimi görünür. Heç vaxt dəyişmir. |
| `tenantId` | Biznesin CoSuite-dəki identifikatoru. **Qeyri-şəffaf sətir**, 64 simvola qədər (`[A-Za-z0-9_.:-]`). Formatına güvənmə. |
| `localTenantId` | Eyni biznes üçün sənin identifikatorun (təşkilat, iş sahəsi, klinika…). Qeyri-şəffafdır, sən yaradırsan. |
| `userId` | CoSuite istifadəçi identifikatoru. Qeyri-şəffaf, 64 simvola qədər. |
| `module` | `<appKey>.<module>` formasında modul açarı, məsələn, `dent.treatment`. `core.*` CoSuite üçün ayrılıb. |

`tenantId ↔ localTenantId` uyğunluğunu öz cədvəlində saxla (tövsiyə olunan ad: `cosuite_links`). Bir `tenantId` yalnız bir dəfə bağlana bilər.

## Ötürmə və imzalama {#signing}

Bütün serverdən-serverə çağırışlar HTTPS və JSON (`application/json; charset=utf-8`), UTF-8 mətn və ISO 8601 UTC zaman möhürlərindən (`2026-10-26T09:00:00Z`) istifadə edir.

### Etimadnamələr {#credentials}

CoSuite hər tətbiq və mühit üçün bir **açar cütü** verir: `keyId` (məsələn, `csk_dent_test`) və 32 baytlıq təsadüfi `secret`. Hər iki tərəf gizli açarı şifrələnmiş saxlayır və heç vaxt loglara yazmır. Eyni gizli açar çağırışları hər iki istiqamətdə imzalayır (CoSuite → tətbiqin və tətbiqin → CoSuite).

### İmza {#signature}

Hər sorğu dörd başlıq daşıyır:

| Başlıq | Dəyər |
|---|---|
| `X-CoSuite-Key` | `keyId` |
| `X-CoSuite-Timestamp` | Saniyələrlə Unix vaxtı |
| `X-CoSuite-Request-Id` | Hər sorğu üçün unikal (UUID; `[A-Za-z0-9_-]` simvollarından 8–128 simvol). Sorğu təkrar göndəriləndə **eyni qalır**. |
| `X-CoSuite-Signature` | `v1=` + hex(HMAC-SHA256(secret, kanonik sətir)) |

Kanonik sətir `|` ilə birləşdirilmiş dörd hissədən ibarətdir:

```text
{METHOD}|{pathAndQuery}|{timestamp}|{hex(sha256(body))}
```

- `METHOD` böyük hərflərlə (`PUT`).
- `pathAndQuery` sorğu sətrində olduğu kimi, host olmadan: `/api/cosuite/v1/tenants/abc/entitlements?x=1`. Reverse proxy yolu yenidən yazırsa, klientin göndərdiyi xam yoldan istifadə et.
- Boş gövdə üçün boş sətrin SHA-256 dəyərindən istifadə et (`e3b0c442…b855`).
- Alındığı kimi **xam gövdə baytlarını** hash-lə; JSON-u yalnız yoxlamadan sonra parse et.

Yoxlayan tərəf:

1. Öz saatından **±300 saniyədən** çox fərqlənən zaman möhürünü `401 signature_expired` ilə rədd edir.
2. İmzanı sabit vaxtda müqayisə edir; uyğunsuzluqda `401 invalid_signature`. Naməlum `keyId` `401 unknown_key` olur.
3. Yazma sorğularında `X-CoSuite-Request-Id` dəyərini **10 dəqiqə** yadda saxlayır: eyni id **fərqli gövdə** ilə gəlsə `409 request_id_reused`; eyni id eyni gövdə ilə gəlsə ilk cavab yenidən qaytarılır.

256 KB-a qədər sorğu gövdələrini qəbul et. Test vektorları və Node, .NET, PHP və Python üçün hazır funksiyalar [SDK səhifəsindədir](https://cosuite.io/az/developers/sdk#signature-snippets).

### Açar rotasiyası {#key-rotation}

Rotasiya zamanı eyni `keyId` üçün iki gizli açar etibarlı ola bilər. Yoxlayan tərəf hər ikisini sınayır. Köhnə gizli açar ən gec 7 gün sonra silinir.

## Cavablar və xətalar {#errors}

Uğur:

```json
{ "ok": true, "data": { } }
```

Xəta:

```json
{ "ok": false, "error": { "code": "tenant_not_found", "message": "…", "retryable": false }, "requestId": "…" }
```

- `code` sabitdir və maşınlar üçündür; `message` insanlar üçündür (ingiliscə kifayətdir).
- `retryable` `true` olduqda CoSuite eyni `X-CoSuite-Request-Id` ilə təkrar cəhd edir.
- Müqavilə endpoint-lərində **server xətaları üçün 5xx** qaytar, heç vaxt 4xx yox: CoSuite təkrar cəhd edib-etməyəcəyinə statusa görə qərar verir.

| HTTP | `code` | Təkrar? |
|---|---|---|
| 400 | `invalid_request` (+ `details`: sahə xətaları) | yox |
| 401 | `invalid_signature`, `signature_expired`, `unknown_key` | yox |
| 403 | `subscription_suspended` | yox |
| 404 | `tenant_not_found`, `member_not_found`, `not_found` | yox |
| 409 | `tenant_already_linked`, `member_conflict`, `stale_version`, `request_id_reused`, `invalid_link_code` | yox |
| 413 | `payload_too_large` | yox |
| 422 | `unsupported_module`, `limit_unsupported`, `capability_unsupported` | yox |
| 429 | `rate_limited` (+ `Retry-After` başlığı) | bəli |
| 500/502/503 | `internal`, `unavailable` | bəli |

## Tətbiqinin təqdim etdiyi endpoint-lər {#endpoints}

Hamısı `{baseUrl}/api/cosuite/v1` altındadır və hamısı, o cümlədən `/health`, [imza](#signature) ilə qorunur.

| Metod | Yol | Məqsəd |
|---|---|---|
| GET | `/manifest` | Tətbiqini və modullarını təsvir edir |
| GET | `/health` | İşləkliyin yoxlanması |
| POST | `/tenants` | Biznes açır və ya bağlayır |
| GET | `/tenants/{tenantId}` | Bağlantı vəziyyəti |
| PATCH | `/tenants/{tenantId}` | Ad, dil, valyuta, saat qurşağı və ya ölkə dəyişib |
| DELETE | `/tenants/{tenantId}` | Bağlantını bağlayır (məlumatların silinməsi planlaşdırılır) |
| PUT | `/tenants/{tenantId}/entitlements` | Entitlement-i yazır (tam sənəd) |
| GET | `/tenants/{tenantId}/entitlements` | Tətbiq etdiyin son entitlement |
| PUT | `/tenants/{tenantId}/members/{userId}` | Üzv əlavə edir və ya yeniləyir |
| DELETE | `/tenants/{tenantId}/members/{userId}` | Üzvü çıxarır |
| GET | `/tenants/{tenantId}/members` | Bağlı üzvlər |
| GET | `/tenants/{tenantId}/usage?period=YYYY-MM` | İstifadə göstəriciləri |
| GET | `/tenants/{tenantId}/summary` | CoSuite idarə paneli üçün qısa kartlar |
| GET | `/tenants/{tenantId}/customers?cursor=&limit=` | Müştərilər, səhifələnmiş (`customers` imkanı) |
| GET/POST | `/sso` | Brauzerdə giriş (imza yox, token; bax: [SSO](#sso)) |

Yol parametrləri URL-kodlaşdırılmış olur; istifadə etməzdən əvvəl onları dekodlaşdır.

### GET /manifest {#manifest}

```json
{
  "contract": "cosuite-connect/1.0",
  "asist": "dent",
  "name": "DentCloud",
  "version": "2026.11.1",
  "baseUrl": "https://app.dentcloud.example",
  "locales": ["en", "tr", "az"],
  "modules": [
    {
      "key": "dent.treatment",
      "name": { "en": "Patients and treatment plans", "tr": "Hasta ve tedavi planı", "az": "Xəstə və müalicə planı" },
      "limits": [
        { "key": "chairs", "unit": "count", "name": { "en": "Chairs", "tr": "Koltuk", "az": "Kreslo" } },
        { "key": "smsPerMonth", "unit": "count_per_month" }
      ],
      "usage": ["smsSent", "storageBytes"]
    }
  ],
  "capabilities": ["sso", "members", "usage", "summary"]
}
```

- `asist` sənin tətbiq açarındır; `baseUrl` müqavilə endpoint-lərinin yerləşdiyi kökdür.
- CoSuite kataloqunu manifestə qarşı yoxlayır: manifestində olmayan modul satıla bilməz.
- CoSuite imkanı `capabilities` siyahısında olmayan endpoint-i heç vaxt çağırmır (`sso`, `members`, `usage`, `summary`, `customers`, `link`). Məsələn, `members` olmayan tətbiq heç vaxt üzv çağırışları almır.
- Modullar özlərini daha ətraflı təsvir edə bilər (funksiyalar, API, webhook-lar, bazarlar). Bax: [modul tərifi](https://cosuite.io/az/developers/module-definition).

### GET /health {#health}

```json
{ "ok": true, "data": { "status": "ok", "version": "2026.11.1" } }
```

`status` `ok` və ya `degraded` olur. Tez cavab ver; CoSuite 3 saniyəlik vaxt limitindən istifadə edir.

### POST /tenants: açma və ya bağlama {#tenants-create}

Sorğu:

```json
{
  "tenantId": "org_8f2c",
  "name": "Smile Dental Clinic",
  "locale": "en",
  "currency": "USD",
  "timeZone": "Europe/Istanbul",
  "country": "TR",
  "owner": { "userId": "usr_1a", "email": "owner@smile.example", "name": "Deniz Kaya", "locale": "en" },
  "link": null,
  "test": false
}
```

- **İdempotentdir:** eyni `tenantId` üçün yenidən çağırış eyni `localTenantId` ilə `200` qaytarır; yeni hesab yaradılmır.
- Biznesin tətbiqində **artıq hesabı varsa**, `link` doldurulur: `{ "code": "<one-time link code>" }`. Bax: [mövcud hesabın bağlanması](#link).
- `test: true` olan tenant-lar uyğunluq testindən və sandbox-dan gəlir. Onlara ödəniş yazma, hesabatlarından kənarda saxla və `DELETE` gələndə **dərhal** sil.

Cavab `201` (yeni) və ya `200` (artıq bağlıdır):

```json
{ "ok": true, "data": { "localTenantId": "b7c1", "status": "linked", "created": true } }
```

`GET /tenants/{tenantId}` eyni formanı qaytarır (`status` `linked` və ya `deleting` olur, istəyə görə `entitlementVersion` ilə). `PATCH /tenants/{tenantId}` `name`, `locale`, `currency`, `timeZone`, `country` sahələrindən istənilənini daşıyır və eyni formanı qaytarır. Naməlum `tenantId` `404 tenant_not_found` olur.

### Mövcud hesabın bağlanması (`link` imkanı) {#link}

Biznes tətbiqindən artıq öz abunəsi ilə istifadə edirsə, CoSuite-ə keçid yeni boş hesab yaratmamalıdır; mövcud hesab bağlanır.

1. Tətbiqində hesab sahibi və ya admin **"CoSuite-ə qoş"** düyməsinə basır. Sən yalnız həmin hesaba bağlı, **ən çoxu 30 dəqiqə etibarlı birdəfəlik kod** yaradıb göstərirsən (tövsiyə olunan format `LNK-XXXXXXXXXXXX`, 8–200 simvol).
2. İstifadəçi kodu CoSuite-də daxil edir (Abunə › "Mövcud hesabını bağla"). CoSuite modulu abunəyə əlavə edir və `link: { code }` ilə `POST /tenants` göndərir.
3. Sən kodu yoxlayırsan (mövcuddur, vaxtı keçməyib, istifadə olunmayıb), yenisini yaratmadan **mövcud** hesabı bu `tenantId`-yə bağlayırsan, kodu istifadə olunmuş sayırsan və `200 { localTenantId, status: "linked", created: false }` qaytarırsan. Etibarsız və ya vaxtı keçmiş kod `409 invalid_link_code` olur və **heç bir hesab yaradılmır**; CoSuite istifadəçiyə bildirir və yeni kodla yenidən cəhd edir.
4. Bağlandıqdan sonra:
   - Hesabın məlumatları və mövcud üzvləri olduğu kimi qalır; CoSuite üzvləri [üzv endpoint-ləri](#members) vasitəsilə əlavə edilir.
   - `billing.managedBy = "cosuite"` olan entitlement gələn kimi ödəniş CoSuite-ə keçir: öz təkrarlanan ödənişini dayandır, növbəti dövr üçün ödəniş alma və ödəniş ekranlarını gizlət. Artıq ödənilmiş dövr üçün geri qaytarmalar sənin və CoSuite arasında həll olunur.
   - Entitlement-dəki limit cari istifadədən aşağıdırsa, məlumatları silmə; artıq hissə yalnız oxunan olur.

`link` elan etməyən tətbiqlər "Mövcud hesabını bağla" seçimini heç vaxt görmür. Buna baxmayaraq, **hər** tətbiq naməlum kodu `409 invalid_link_code` ilə rədd etməlidir; uyğunluq testi bunu yoxlayır. `tenant.linked_by_user` hadisəsi istəyə bağlıdır və yalnız məlumat xarakteri daşıyır.

### PUT /tenants/{tenantId}/entitlements {#entitlements}

CoSuite həmişə **tam sənədi** göndərir, heç vaxt fərqi yox:

```json
{
  "version": 42,
  "status": "active",
  "validUntil": "2026-11-26T00:00:00Z",
  "graceUntil": "2026-12-03T00:00:00Z",
  "modules": [
    { "key": "dent.treatment", "limits": { "chairs": 4, "smsPerMonth": 500 } },
    { "key": "dent.imaging" }
  ],
  "billing": { "managedBy": "cosuite" },
  "issuedAt": "2026-10-26T09:00:00Z"
}
```

Qaydalar:

- **`version` yalnız artır.** Gələn versiya tətbiq etdiyindən kiçik və ya ona bərabərdirsə, onu tətbiq etmə, amma yenə də `applied: false` ilə `200` qaytar. Bu, gecikmiş, təkrarlanan və ya sırası pozulmuş çağırışları zərərsiz edir.
- `modules` siyahısında olmayan modul **söndürülüb**.
- Verilməyən limit üçün həmin modulda sənin standart dəyərin işləyir.
- Naməlum modul açarlarını nəzərə alma və cavabda `ignored` siyahısında göstər.
- `status` və tətbiqinin davranışı:

  | `status` | Sənin tətbiqin |
  |---|---|
  | `trial`, `active` | Normal işləyir |
  | `past_due` | Normal işləyir, "ödəniş gözlənilir" bildirişi göstərir |
  | `suspended` | **Yalnız oxumaq**: məlumatlar görünür, yazmalar `403 subscription_suspended` qaytarır |
  | `canceled` | Giriş bağlıdır; məlumatlar `DELETE` gələnə qədər saxlanılır |

- `billing.managedBy` `"cosuite"` olduqda bu biznesə **öz ödəniş ekranlarını, paket yüksəltmə düymələrini və ya sınaq sayğaclarını göstərmə**. Bu, ikiqat ödənişin qarşısını alır. Daxili plan sahələrini bu sənəddən çıxar.

Cavab:

```json
{ "ok": true, "data": { "applied": true, "version": 42, "ignored": [] } }
```

`GET /tenants/{tenantId}/entitlements` tətbiq etdiyin son sənədi (və ya `null`) qaytarır. CoSuite bundan gecəlik tutuşdurma üçün istifadə edir.

### Üzvlər {#members}

`PUT /tenants/{tenantId}/members/{userId}`:

```json
{ "email": "ayse@smile.example", "name": "Ayşe Demir", "locale": "tr", "role": "admin" }
```

- `role` `owner`, `admin`, `member`, `viewer` dəyərlərindən biridir. Onu öz rol modelinə uyğunlaşdır.
- Bir şəxs bir biznesdə işləyir. Tətbiqin bir e-poçtu yalnız bir hesabda qəbul edirsə və bu e-poçt artıq başqa hesaba aiddirsə, `409 member_conflict` qaytar; heç vaxt hesabları öz başına köçürmə. CoSuite üzvü "qoşula bilmədi" kimi işarələyir və adminə göstərir.
- İnsanlar heç vaxt silinmir, yalnız biznesdən çıxarılır (`DELETE`). Onların tətbiqindəki sessiyalarını bağla. Naməlum üzv `404 member_not_found` olur.
- `GET /tenants/{tenantId}/members` `{ "members": [ { "userId", "email", "name", "locale", "role", "localUserId"? } ] }` qaytarır.

### GET /tenants/{tenantId}/usage {#usage}

```json
{ "ok": true, "data": { "period": "2026-10", "metrics": { "smsSent": 212, "storageBytes": 734003200 }, "asOf": "2026-10-26T08:00:00Z" } }
```

Göstərici açarları manifestindəki modulun `usage` siyahısından gəlir. Ay bağlandıqdan sonra onun dəyərləri dəyişməməlidir. Etibarsız `period` `400 invalid_request` olur.

### GET /tenants/{tenantId}/summary {#summary}

Biznesin CoSuite ana səhifəsi üçün kartlar. Dil `Accept-Language` başlığından götürülür.

```json
{ "ok": true, "data": { "cards": [
  { "key": "today", "title": "Appointments today", "value": "18", "trend": null, "url": "https://app.dentcloud.example/calendar" }
] } }
```

- Ən çoxu 6 kart.
- Cavab **1 saniyə** ərzində gəlməsə, CoSuite kartları ötürür. Onları keşdən ver, burada heç vaxt ağır sorğular işlətmə.

### Müştərilər (`customers` imkanı) {#customers}

Müştərilərin CoSuite-in müştəri siyahısında tətbiqinin nişanı, mənbə filtri və tətbiqindəki qeydə keçid ilə görünür. İstiqamət **tətbiqin → CoSuite**-dir: qeyd sənə aiddir və tətbiqində redaktə olunur; CoSuite bu sahələri kilidli göstərir. CoSuite-də yaradılan müştərilər sənə göndərilmir.

**1. Səhifələnmiş siyahı:** `GET /tenants/{tenantId}/customers?limit=200&cursor=…`

```json
{ "ok": true, "data": {
  "customers": [
    { "id": "cus_8x2", "kind": "person", "name": "Ayşe Demir", "email": "ayse@example.com", "phone": "+905321234567",
      "taxNumber": null, "city": "Istanbul", "country": "TR", "url": "https://app.dentcloud.example/patients/cus_8x2",
      "updatedAt": "2026-10-26T09:12:00Z" }
  ],
  "nextCursor": "eyJ1IjoiMjAyNi0xMC0yNlQwOToxMjowMFoiLCJpIjoiY3VzXzh4MiJ9"
} }
```

- `limit` 1–200 arasındadır (standart 200); bu aralıqdan kənar dəyərdə və ya dekodlaşdıra bilmədiyin cursor-da `400 invalid_request` qaytar.
- Sıra sabit olmalıdır (tövsiyə: `updatedAt, id` artan sırada, cursor hər ikisini daşıyır). Son səhifədə `nextCursor: null` olur.
- Silinmiş müştərilər siyahıya düşmür.
- CoSuite bunu bağlantı yaradılanda (ilkin idxal) və hər gecə (uyğunsuzluq yoxlaması) çağırır.

**2. Hadisələr**, müştəri yaradılanda, dəyişdiriləndə və ya silinəndə outbox-undan (bax: [hadisələr](#events)):

```json
{ "id": "evt_01", "type": "customer.upserted", "tenantId": "org_8f2c", "occurredAt": "2026-10-26T09:12:00Z", "data": { "customer": { } } }
```

```json
{ "id": "evt_02", "type": "customer.deleted", "tenantId": "org_8f2c", "occurredAt": "2026-10-26T09:30:00Z", "data": { "customerId": "cus_8x2", "deletedAt": "2026-10-26T09:30:00Z" } }
```

`data.customer` siyahı elementi ilə tam eyni formadadır.

| Sahə | Qayda |
|---|---|
| `id` | Sənin öz identifikatorun, heç vaxt dəyişmir (1–64, `[A-Za-z0-9_.:-]`) |
| `kind` | `person` və ya `company`; naməlum dəyərlər `person` sayılır |
| `name` | Məcburidir, 1–200 simvol |
| `email`, `phone`, `taxNumber`, `city`, `country`, `url` | İstəyə bağlıdır. `phone` E.164 formatında (`+905…`), `country` ISO 3166 iki hərfli kod, `url` sənin `baseUrl` altında. **Etibarsız dəyər qeydin itməsinə səbəb olmur; boş sayılır.** |
| `updatedAt` | Məcburidir. CoSuite əlindəkindən köhnə yeniləmələri nəzərə almır, ona görə də sırası pozulmuş hadisələr təhlükəsizdir. |

CoSuite-də eyni biznesdə eyni e-poçt və ya telefonu olan müştəri təkrarlanmır, mövcud qeydlə uyğunlaşdırılır. Tətbiqində silinən müştəri CoSuite-də silinmir; yalnız bağlantı qaldırılır (onun fakturaları ola bilər).

### DELETE /tenants/{tenantId} {#tenants-delete}

- Bağlantı bağlanır və giriş dayanır.
- Məlumatları **30 gün** saxla, sonra sil. CoSuite bunu dəyişmək üçün gövdədə `{ "retentionDays": 0–90 }` göndərə bilər. Bu müddət ərzində eyni `tenantId` üçün `POST /tenants` gəlsə, hesabı yenidən aç.
- `test: true` tenant-lar dərhal silinir.
- Cavab: `200 { "deleted": true }`. Məlumatlar nəhayət silinəndə `tenant.data_deleted` hadisəsini göndər.

**Kodla bağlanmış hesablar** (`created: false`, bax: [bağlama](#link)) **silinmir, ayrılır**:

- Bağlantı və CoSuite üzv uyğunluqları qaldırılır; hesab, onun məlumatları və istifadəçiləri tətbiqində qalır, giriş də açıq qalır.
- Ödəniş sənə qayıdır (`managedBy: "cosuite"` təsiri bitir); plan sənin öz qaydalarına tabe olur.
- `retentionDays` nəzərə alınmır və `tenant.data_deleted` **göndərilmir**.
- Cavab: `200 { "deleted": false, "detached": true }`. CoSuite bağlantını dərhal bağlayır.
- Eyni `tenantId`-ni yenidən bağlamaq üçün yeni bağlama kodu lazımdır; kodsuz `POST /tenants` yeni hesab açır.
- Bağlanmış hesab, sorğuda `test: true` yazılsa belə, heç vaxt test hesabı sayılmır.

## CoSuite-in təqdim etdiyi endpoint-lər {#cosuite-endpoints}

Əsas URL `https://api.cosuite.io/connect/v1`. Eyni [imza](#signature), öz `keyId` açarınla imzalanır.

| Metod | Yol | Məqsəd |
|---|---|---|
| GET | `/tenants/{tenantId}/entitlements` | Şübhə olduqda ən son entitlement-i götürür |
| POST | `/events` | CoSuite-ə hadisə göndərir |
| GET | `/jwks.json` | SSO tokenlərini yoxlamaq üçün açıq açarlar (açıq, imzasız) |

### POST /events {#events}

```json
{ "id": "evt_01J9", "type": "usage.threshold_reached", "tenantId": "org_8f2c", "occurredAt": "2026-10-26T09:00:00Z", "data": { "metric": "smsPerMonth", "percent": 80 } }
```

| Hadisə | Nə vaxt |
|---|---|
| `tenant.linked_by_user` | İstifadəçi tətbiqində "CoSuite-ə qoş" düyməsinə basıb (`data.linkCode`) |
| `usage.threshold_reached` | Limitin 80%-i və ya 100%-i istifadə olunub |
| `member.left` | Üzv tətbiqindəki öz hesabını silib |
| `tenant.data_deleted` | Saxlama müddəti bitib və məlumatlar silinib |
| `customer.upserted` | Müştəri yaradılıb və ya dəyişdirilib (`data.customer`; `customers` imkanı) |
| `customer.deleted` | Müştəri silinib (`data.customerId`, `data.deletedAt`) |

- Hadisələri **outbox** vasitəsilə göndər: əvvəlcə hadisəni verilənlər bazana yaz, sonra çatdır. Artan intervallarla təkrar cəhd et; 24 saat ərzində çatdırıla bilməsə, onu ölü kimi işarələ.
- Hadisənin `id` dəyərini `X-CoSuite-Request-Id` kimi istifadə et. CoSuite təkrarları `id` üzrə aradan qaldırır, ona görə də iki dəfə çatdırmaq zərərsizdir.

## Vahid giriş (SSO) {#sso}

İstifadəçi CoSuite-də tətbiqin üçün "Aç" düyməsinə basanda:

1. CoSuite qısa ömürlü **JWT** buraxır və brauzer onu avtomatik göndərilən forma ilə `POST {baseUrl}/api/cosuite/v1/sso` ünvanına göndərir. Token heç vaxt ünvan sətrində və ya loglarda görünmür.
2. Token **ES256** (asimmetrik) ilə imzalanıb. Onu CoSuite-in `https://api.cosuite.io/connect/v1/jwks.json` ünvanındakı açıq açarları ilə yoxla. Açarları ən azı bir saat keşdə saxla; naməlum `kid` gəldikdə dəqiqədə ən çoxu bir dəfə yenidən yüklə. Ortaq gizli açar olmadığı üçün heç bir tətbiq başqa tətbiq üçün giriş tokeni yarada bilməz.
3. Tokenin claim-ləri:

   ```json
   { "iss": "https://cosuite.io", "aud": "dent", "sub": "usr_1a", "tid": "org_8f2c",
     "email": "owner@smile.example", "name": "Deniz Kaya", "locale": "en", "role": "owner",
     "redirect": "/calendar", "iat": 1761469200, "exp": 1761469260, "jti": "4f1c…" }
   ```

4. Bunları yoxla:
   - imza etibarlıdır və alqoritm ES256-dır;
   - `iss` `https://cosuite.io`, `aud` isə sənin tətbiq açarındır;
   - `exp` keçməyib (ömür ən çoxu **60 saniyə**, `exp - iat ≤ 60`; ±30 saniyə saat fərqinə icazə ver);
   - `jti` əvvəl istifadə olunmayıb (birdəfəlik; 2 dəqiqə yadda saxla);
   - `tid` bağlı tenant-dır.
5. Üzv hələ mövcud deyilsə, amma tenant bağlıdırsa, üzvü yerindəcə yarat ([üzvlər](#members) ilə eyni qaydalar). Sonra öz sessiyanı başlat və `redirect` ünvanına yönləndir. `redirect` yalnız öz domenindəki yol ola bilər: `/` ilə başlayır, `//` və ya `/\` ilə başlamır.
   - Sessiyanı öz giriş səhifən vasitəsilə də qura bilərsən (məsələn, birdəfəlik bilet və öz kimlik provayderin). Bu halda ilk yönləndirmə `redirect` yolunu daşıyır, giriş səhifəsi isə sessiya qurulduqdan sonra oraya keçir. Bileti URL fraqmentində (`#…`) daşı ki, heç vaxt server loglarına düşməsin.
6. İstənilən xətada CoSuite-ə geri keçidi olan öz giriş səhifəni göstər. Xam xətanı heç vaxt istifadəçiyə göstərmə.

## Dil, valyuta, vaxt {#locale}

- Dillər `en`, `tr`, `az` kimi kodlardır. Bir dili dəstəkləmirsənsə, dəstəklədiyin ən yaxın dilə keç; heç vaxt xəta vermə.
- Valyuta (`USD`, `TRY`, `EUR`, `AZN`…) məlumat xarakterlidir. **Qiymətləndirmə və ödənişin yığılması CoSuite-ə aiddir**; CoSuite vasitəsilə qoşulmuş biznesə qiymət göstərmə.
- Saat qurşaqları IANA adlarıdır (`Europe/Istanbul`, `Asia/Baku`).

## Etibarlılıq (CoSuite-in vədi) {#reliability}

- CoSuite-in tətbiqinə çağırışları 10 saniyədən sonra vaxt limitinə düşür; `summary` 1 saniyədən, `health` 3 saniyədən sonra.
- Yazma çağırışları (`tenants`, `entitlements`, `members`) CoSuite-in outbox-undan göndərilir. Tətbiqin işləmirsə, satış və ödəniş yenə də tamamlanır; çağırış eyni request id ilə 1 dəq, 5 dəq, 30 dəq, 2 saat və 6 saat sonra təkrarlanır.
- Ardıcıl 5 uğursuzluqdan sonra CoSuite tətbiqini 2 dəqiqəliyə çağırmağı dayandırır (circuit breaker), sonra tək bir yoxlama sorğusu göndərir.
- **Gecəlik tutuşdurma:** hər gecə CoSuite hər bağlı tenant üçün `GET …/entitlements` oxuyur və sənin `version` dəyərin geridə qalıbsa, cari sənədi yenidən göndərir.

## Versiyalama: "bizi pozma" siyahısı {#versioning}

**v1 daxilində icazə verilir:**

- Endpoint əlavə etmək (və onun imkanını manifestə).
- Cavaba sahə əlavə etmək.
- Sorğuya **istəyə bağlı** sahə əlavə etmək.
- Modul, limit, istifadə göstəricisi, hadisə növü və ya xəta kodu əlavə etmək.

**Diqqət:** CoSuite-in sənə **göndərdiyi** sabit siyahılar (`status`, `role`, `billing.managedBy`) v1 daxilində genişləndirilmir, çünki köhnə tətbiq yeni dəyəri tanımaz.

**v1 daxilində icazə verilmir:**

- Sahəni, endpoint-i, enum dəyərini və ya xəta kodunu silmək və ya adını dəyişmək.
- Sahənin tipini və ya mənasını dəyişmək (məsələn, aylıq limiti gündəliyə çevirmək).
- İstəyə bağlı sahəni məcburi etmək.
- Uğurlu cavabın HTTP statusunu dəyişmək.
- Autentifikasiyanı və ya imza formatını dəyişmək.
- Modul açarının adını dəyişmək. Bunun əvəzinə yeni açar əlavə et; köhnəsi ən azı 6 ay yaşayır.

**Nə vaxtsa pozucu dəyişiklik lazım olarsa**, v1-in **yanında** `/api/cosuite/v2` açılır; v1 söndürülmür. v1-in bağlanma tarixi ən azı **6 ay** əvvəlcədən manifestdəki `deprecated` sahəsi (`{ "sunset": "<ISO date>", "message": "…" }`) və `Deprecation` cavab başlığı ilə elan olunur.

Eyni qaydalar sənə də aiddir: modulun canlıya çıxdıqdan sonra onun açarlarını, limitlərini, scope-larını və webhook növlərini daimi say. CoSuite öz tərəfini SDK-dakı avtomatik müqavilə yoxlaması ilə qoruyur; sənin tərəfin isə [uyğunluq testi](https://cosuite.io/az/developers/sdk#conformance) ilə yoxlanılır.
