# AGIL Calls как SaaS: мультиорганизационность

Дата: 2026-11-13 · зона: `packages/server` · статус: реализовано (MVP).

Один процесс/API обслуживает несколько организаций (тенантов). У каждой
организации — собственный конфиг (`TenantConfig`): брендинг (название, логотип,
support/footer), тема, фичи, лимиты, безопасность, встроенные/внешние
провайдеры, AI/knowledge, scale. Комнаты, файлы, записи, аудит и
WS-подключения всегда скоупятся `tenantId`.

## 1. Два типа credentials

| Credential | env | Назначение |
|---|---|---|
| Platform key | `AGIL_PLATFORM_KEY` | управление организациями: `GET/POST /v1/admin/tenants`, `POST /v1/admin/tenants/:id/keys`, `DELETE /v1/admin/tenants/:id`; **не является tenant-ключом** |
| Tenant API key | `AGIL_API_KEYS` (env) и динамические | обычный admin key: `/v1/admin/*`, создание комнат, токены; видит только свой тенант |

Динамические ключи создаются через platform API и хранятся **только хэшем**
(`sha256(AGIL_IP_SALT + ':' + key)`) в `AGIL_DATA_DIR/api-keys.json`
(атомарная запись). Plaintext возвращается один раз в ответе на создание/ротацию
и больше не показывается ни в списках, ни в аудите. Redis/БД не требуются —
файл на volume.

Ротация: `POST /v1/admin/tenants/:id/keys` заменяет динамический ключ тенанта
(старый сразу даёт 401). Env-ключи API не отзывает — удаление env-key тенанта
запрещено, чтобы не оставить «вечный» ключ.

## 2. Онбординг организации

`POST /v1/admin/tenants`:

- `tenantId` — `[a-zA-Z0-9._-]{1,64}`, уникален глобально (409 при дубле);
- `name`, `organizationName`, `branding{productName,logoUrl,faviconUrl,supportUrl,footerText}`;
- `theme{preset?,scheme?,colors?}`;
- `preset`: `default` | `corporate` | `minimal` (см. таблицу);
- `templateTenantId` — копия конфига существующего тенанта (брендинг/тема
  применяются поверх).

| Preset | Что включает |
|---|---|
| `default` | обычный `defaultConfig` (как demo) |
| `corporate` | waiting room, строгий lockout (`maxFailedJoinAttempts=5`, `lockoutSec=600`), файлы/запись выключены |
| `minimal` | только базовый звонок/чат: без файлов, записи, субтитров, knowledge, screen share, вебинаров, breakout, перевода |

Ответ: `{tenant: TenantSummary, apiKey, embedUrl}`. Ключ сохранить сразу.

## 3. Изоляция и авторизация

`apiKeyTenant` резолвит env-ключи, затем динамические (constant-time compare).
Platform key намеренно не резолвится как tenant. `requireApiKeyTenant`:
platform key на tenant-ручке → 403, неизвестный/отозванный → 401. Все
admin/public ручки проверяют `requireTenantMatch`/tenant-скоуп; чужая комната →
404/403 по контракту, чужие данные не утекают. Tenant-ручки
`GET/PATCH /v1/admin/tenants/:id` доступны платформе и самому тенанту;

## 4. Жизненный цикл и удаление

`DELETE /v1/admin/tenants/:id`:
- demo (`AGIL_DEFAULT_TENANT`) и тенанты из `AGIL_API_KEYS` защищены (400);
- конфиг архивируется в `AGIL_DATA_DIR/tenants/.archive/<id>-<ts>.json`;
- динамический ключ удаляется; все in-memory комнаты тенанта закрываются;
- аудит `tenant.deleted`. Записи/файлы на диске остаются (retention — политика
  компании; purge — roadmap).

Аудит платформенных операций: `tenant.created`, `tenant.updated`,
`tenant.key.rotated`, `tenant.deleted` (без секретов/ключей).

## 5. Кастомные домены и биллинг

Каркас доменов уже есть: `branding.customDomain` + `GET/PUT/DELETE /v1/admin/domain`
(tenant admin). API валидирует формат (bare hostname, ≤253, no scheme/path/IP/
wildcards/localhost), возвращает инструкцию `CNAME <domain> → <publicHost>` и
`verified:false` (реальной DNS/ACME-верификации пока нет). Дальше:

1. **Custom domains**: DNS-верификация (CNAME/TXT + периодический recheck),
   Caddy/Traefik по `Host` → tenantId (mapping `AGIL_DATA_DIR/domains.json`),
   автоматический TLS через ACME; fallback на `?tenant=`. API уже отдаёт
   `dnsInstructions` и `docsHint` для панели.
2. **Биллинг**: план/лимиты (`limits.maxParticipants`, запись, AI-квоты) в
   отдельном биллинг-сервисе; usage-события (комнаты/участники/минуты) уже
   можно отдавать вебхуками, добавить `billing.usage` sink.
3. **Admin UI платформы**: список организаций, создание/ротация/удаление,
   «войти как тенант» (impersonation через короткоживущий platform-issued
   ключ с аудитом) — отдельная панель.
4. **Персистентность ключей**: файл → БД/секрет-менеджер, кэш резолва,
   инвалидация по webhook.
5. **Квоты/rate-limit per tenant** (сейчас общий limiter per tenant key).
