diff --git a/docs/integration/api-cheatsheet.md b/docs/integration/api-cheatsheet.md new file mode 100644 index 0000000..f706efc --- /dev/null +++ b/docs/integration/api-cheatsheet.md @@ -0,0 +1,188 @@ +# Ordinis API · Cheat Sheet + +Одностраничный референс. Полный гайд — `api-quickstart.md`. + +**Base:** `https://ordinis.k8s.264.nstart.cloud/api/v1` · **Version:** `v2.36.0` · **OpenAPI:** `/swagger-ui/index.html` + +## Auth + +```bash +TOKEN=$(curl -s -X POST https://auth.nstart.space/realms/nstart/protocol/openid-connect/token \ + -d client_id=ordinis -d grant_type=password -d scope=openid \ + -d username=$USER -d password=$PASS | jq -r .access_token) + +curl -H "Authorization: Bearer $TOKEN" $ORDINIS/api/v1/me/notifications/settings +``` + +Scopes: `PUBLIC` (anon ok) · `INTERNAL` (auth) · `RESTRICTED` (admin). + +## Read (no auth для PUBLIC) + +| Verb | Path | Что | +|---|---|---| +| `GET` | `/version` | Версия + commit + builtAt | +| `GET` | `/dictionaries` | Список всех справочников | +| `GET` | `/dictionaries/{name}` | Детали + JSON Schema | +| `GET` | `/dictionaries/templates` | 6 шаблонов схем | +| `GET` | `/dictionaries/templates/{id}` | Один шаблон со schemaJson | +| `GET` | `/dictionaries/graph/outgoing` | FK-граф между справочниками | +| `GET` | `/dictionaries/{name}/dependents` | Кто ссылается на этот dict | +| `GET` | `/dictionaries/{name}/changelog` | Список изменений схемы | +| `GET` | `/dictionaries/{name}/changelog/{id}/diff` | Diff между версиями | +| `GET` | `/dictionaries/{name}/snapshots?asOf=` | Snapshot на дату (ISO-8601) | +| `GET` | `/{name}/records` | Список записей (⚠️ не `/dictionaries/{name}/records`) | +| `GET` | `/{name}/records/{businessKey}` | Одна запись | +| `GET` | `/{name}/records/{businessKey}/history` | История версий записи | +| `GET` | `/{name}/records/scheduled-summary` | Будущие версии (count + nearestValidFrom) | +| `GET` | `/{name}/records/export.csv` | Bulk export | +| `GET` | `/search?q=&limit=` | Smart search (≥3 chars, trigram) | +| `GET` | `/ai/info` | `{enabled: bool}` AI feature flag | + +### Query params (records) + +``` +?limit=50&offset=0 pagination +?status=ACTIVE&country=RU фильтр по атрибутам схемы +?scope=INTERNAL если есть права +?asOf=2026-03-15T00:00:00Z bitemporal cut (где поддержано) +``` + +## Write (требует auth) + +| Verb | Path | Что | +|---|---|---| +| `POST` | `/{name}/records` | Создать запись (если approval не нужен) | +| `PUT` | `/{name}/records/{businessKey}` | Обновить | +| `DELETE` | `/{name}/records/{businessKey}` | Soft-delete (tombstone) | +| `POST` | `/{name}/records/bulk-close` | Массовое закрытие | +| `POST` | `/{name}/records/{businessKey}/cascade-close` | Закрыть с зависимыми | +| `GET` | `/{name}/records/{businessKey}/cascade-preview` | Preview каскада | +| `PATCH` | `/dictionaries/{name}/schema` | Изменить схему inline (non-breaking) | +| `PUT` | `/dictionaries/{name}` | Полностью пересоздать (breaking) | + +## Workflow (drafts + approvals) + +| Verb | Path | Что | +|---|---|---| +| `POST` | `/dictionaries/{name}/records/drafts` | Создать record draft | +| `POST` | `/dictionaries/{name}/drafts` | Создать schema draft | +| `GET` | `/dictionaries/{name}/drafts` | Список schema drafts по dict | +| `GET` | `/dictionaries/{name}/drafts/active` | Активный draft (200+null если нет) | +| `GET` | `/dictionaries/{name}/drafts/{id}` | Один draft | +| `DELETE` | `/dictionaries/{name}/drafts/{id}` | Withdraw | +| `POST` | `/dictionaries/{name}/drafts/{id}/review` | Назначить ревью | +| `POST` | `/dictionaries/{name}/drafts/{id}/decision` | Approve/reject schema draft | +| `POST` | `/dictionaries/{name}/drafts/{id}/publish` | Опубликовать после approve | +| `GET` | `/drafts/me` | Мои record-drafts (status filter) | +| `GET` | `/drafts/{id}` | Один record draft | +| `DELETE` | `/drafts/{id}` | Withdraw record draft | +| `POST` | `/drafts/{id}/approve` | Одобрить (body: `{comment}`) | +| `POST` | `/drafts/{id}/reject` | Отклонить (body: `{comment}`) | +| `GET` | `/admin/reviews/pending` | Очередь ревьюера (records) | +| `GET` | `/admin/schema-reviews/pending` | Очередь ревьюера (schemas) | +| `GET` | `/admin/schema-drafts/me` | Мои schema drafts | + +## AI + +```bash +curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ + $ORDINIS/api/v1/ai/suggest-field \ + -d '{"prompt":"контактный email оператора"}' +``` + +Limits: **30 req/min** на IP, circuit breaker **10 fails / 60s → open 5min** (`503 circuit_open`). + +## Notifications (per-user, auth) + +| Verb | Path | Что | +|---|---|---| +| `GET` | `/me/notifications` | Bell-bar feed | +| `POST` | `/me/notifications/{id}/read` | Mark as read | +| `POST` | `/me/notifications/read-all` | Mark all as read | +| `GET` | `/me/notifications/preferences` | Per-event matrix | +| `PUT` | `/me/notifications/preferences` | Сохранить матрицу | +| `GET` | `/me/notifications/settings` | Quiet hours config | +| `PUT` | `/me/notifications/settings` | Сохранить quiet hours | + +## Webhooks (admin) + +| Verb | Path | Что | +|---|---|---| +| `GET` | `/admin/webhooks/subscriptions` | Список подписок | +| `POST` | `/admin/webhooks/subscriptions` | Создать (`secret` в ответе, **один раз**) | +| `GET` | `/admin/webhooks/subscriptions/{id}` | Детали | +| `PUT` | `/admin/webhooks/subscriptions/{id}` | Обновить | +| `DELETE` | `/admin/webhooks/subscriptions/{id}` | Удалить | +| `POST` | `/admin/webhooks/subscriptions/{id}/rotate-secret` | Перевыпуск ключа | +| `POST` | `/admin/webhooks/subscriptions/{id}/test` | Test delivery | +| `GET` | `/admin/webhooks/subscriptions/{id}/stats` | Histogram (24h/7d) | +| `GET` | `/admin/webhooks/subscriptions/{id}/deliveries` | Recent deliveries | +| `GET` | `/admin/webhooks/deliveries/dlq` | DLQ list | +| `POST` | `/admin/webhooks/deliveries/{id}/retry` | Manual retry | + +### Headers delivered + +``` +X-Ordinis-Event: RecordCreated +X-Ordinis-Signature: sha256= HMAC-SHA256(body, secret) +X-Ordinis-Delivery: del_01HXY... +User-Agent: ordinis-alerts +``` + +### Event types + +`RecordCreated · RecordUpdated · RecordClosed · RecordRestored · BulkClosed · SchemaDraftSubmitted · SchemaDraftApproved · SchemaDraftRejected · SchemaDraftPublished · RecordDraftSubmitted · RecordDraftApproved` + +## Audit (admin) + +| Verb | Path | Что | +|---|---|---| +| `GET` | `/admin/audit?dictionary=&action=&user=&dateFrom=&dateTo=` | Фильтрованный аудит | +| `GET` | `/admin/audit/export.csv` | Полный экспорт | +| `GET` | `/admin/audit/export-selected.csv` | Selected ids экспорт | + +Action types (13): `DICTIONARY_CREATED · SCHEMA_UPDATED · SCHEMA_PUBLISHED · RECORD_CREATED · RECORD_UPDATED · RECORD_CLOSED · RECORD_RESTORED · DRAFT_CREATED · DRAFT_SUBMITTED · DRAFT_APPROVED · DRAFT_REJECTED · DRAFT_WITHDRAWN · REVIEWER_ASSIGNED` + +## Outbox (admin) + +| Verb | Path | Что | +|---|---|---| +| `GET` | `/admin/outbox/stats` | Pending/DLQ counters | +| `GET` | `/admin/outbox/dlq` | DLQ events list | + +## Errors — единая форма + +```json +{ "status": 422, "code": "validation_failed", "message": "...", + "details": {}, "traceId": "...", "timestamp": "..." } +``` + +| HTTP | code | | +|---|---|---| +| 400 | `bad_request` | Невалидный body | +| 401 | `unauthorized` | Нет/просрочен токен | +| 403 | `forbidden` | Scope/role не пускает | +| 404 | `not_found` | Нет такого ресурса | +| 409 | `conflict` | Concurrent update / unique violation | +| 422 | `validation_failed` | Schema validation fail | +| 429 | `rate_limited` | Лимит запросов | +| 500 | `internal_error` | См. traceId | +| 502 | `llm_error` | AI: LLM upstream | +| 503 | `circuit_open` | AI: circuit breaker | + +## 60-second smoke + +```bash +ORDINIS=https://ordinis.k8s.264.nstart.cloud + +curl -s $ORDINIS/api/v1/version | jq # alive +curl -s $ORDINIS/api/v1/dictionaries | jq 'length' # >= 40 +curl -s $ORDINIS/api/v1/dictionaries/spacecraft | jq '.scope' # "PUBLIC" +curl -s "$ORDINIS/api/v1/spacecraft/records?limit=1" | jq '.[0].businessKey' +curl -s "$ORDINIS/api/v1/search?q=Sentinel" | jq '.total' # 12 +curl -s $ORDINIS/api/v1/ai/info | jq '.enabled' # true +``` + +## Связанные доки + +`api-quickstart.md` · `bundle-format-spec.md` · `docs/changelog.md` · Swagger UI `/swagger-ui/index.html` diff --git a/docs/integration/api-quickstart.md b/docs/integration/api-quickstart.md new file mode 100644 index 0000000..5b6a2e8 --- /dev/null +++ b/docs/integration/api-quickstart.md @@ -0,0 +1,572 @@ +# Ordinis API — Quick Start + +Practical guide to НСИ-ordinis REST API. Тут не справочник всех endpoint'ов +(для этого есть OpenAPI / Swagger UI), а минимально-достаточный путь от +«первый запрос» до «подписался на webhook». + +**Версия:** v2.36.0 (`/api/v1`) +**Swagger UI:** `https://ordinis.k8s.265.nstart.cloud/swagger-ui/index.html` +(staging — на prod env-flagged) + +--- + +## 0. Принципы + +- REST + JSON. UTF-8. Все datetime в ISO-8601 UTC (`Z` suffix). +- Базовый префикс — `/api/v1`. Версионирование URL (breaking changes → `/v2`). +- Auth — Keycloak OIDC, Bearer-токен в `Authorization`. Anon разрешён для + `PUBLIC` scope. +- Pagination — query-params `?limit=N&offset=M`. По умолчанию `limit=50`. +- Ошибки — `application/json` с `{status, code, message, traceId, timestamp}`. + +## 1. Base URL и проверка живости + +```bash +# Production +ORDINIS=https://ordinis.k8s.264.nstart.cloud + +# Staging +# ORDINIS=https://ordinis.k8s.265.nstart.cloud + +curl "$ORDINIS/api/v1/version" +``` + +Ответ: + +```json +{ + "version": "0.1.0-SNAPSHOT", + "commit": "2ca8f053", + "branch": "main", + "tag": "v2.36.0", + "builtAt": "2026-05-17T08:18:07.437Z" +} +``` + +## 2. Аутентификация + +Большинство read-эндпоинтов работают без авторизации в `PUBLIC` scope. +Mutations, INTERNAL/RESTRICTED data, workflow и `/me/*` требуют JWT. + +### 2.1 Получение токена через Keycloak (password grant) + +```bash +TOKEN=$(curl -s -X POST \ + "https://auth.nstart.space/realms/nstart/protocol/openid-connect/token" \ + -d "client_id=ordinis" \ + -d "grant_type=password" \ + -d "username=user@example.com" \ + -d "password=...." \ + -d "scope=openid profile email" \ + | jq -r .access_token) +``` + +> Password grant — для CLI/CI/тестов. В production — OIDC Authorization Code +> с PKCE через ваш BFF. + +### 2.2 Использование токена + +```bash +curl -H "Authorization: Bearer $TOKEN" \ + "$ORDINIS/api/v1/me/notifications/settings" +``` + +### 2.3 Скоупы данных + +- `PUBLIC` — открыто всем, включая anon +- `INTERNAL` — только для авторизованных с ролью +- `RESTRICTED` — узкий круг, как правило admin + +Sidebar и search автоматически фильтруют по scope текущей сессии. + +## 3. Справочники (dictionaries) + +### 3.1 Список всех справочников + +```bash +curl "$ORDINIS/api/v1/dictionaries" +``` + +```json +[ + { + "id": "58ba2127-...", + "name": "spacecraft", + "displayName": "Космические аппараты", + "description": "Каталог КА (космических аппаратов) ДЗЗ-миссий", + "scope": "PUBLIC", + "schemaVersion": "1.0.0", + "bundle": "cuod", + "supportedLocales": ["ru-RU", "en-US"], + "defaultLocale": "ru-RU", + "createdAt": "2026-05-07T21:20:51.623195Z", + "updatedAt": "2026-05-07T21:20:51.623195Z" + }, + ... +] +``` + +### 3.2 Детали одного справочника (со схемой) + +```bash +curl "$ORDINIS/api/v1/dictionaries/spacecraft" +``` + +`schemaJson` — полный JSON Schema draft-07 с x-расширениями: + +```json +{ + "name": "spacecraft", + "scope": "PUBLIC", + "schemaJson": { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://ordinis.nstart.cloud/schemas/cuod/spacecraft/1.0.0", + "type": "object", + "required": ["designator", "name", "satellite_type_code", "status"], + "properties": { + "name": { + "type": "object", + "required": ["ru-RU"], + "x-localized": true, + "patternProperties": { "^[a-z]{2}-[A-Z]{2}$": { "type": "string" } } + }, + "satellite_type_code": { + "type": "string", + "x-references": "satellite_type.code" + } + } + } +} +``` + +### 3.3 Граф зависимостей (FK) + +```bash +curl "$ORDINIS/api/v1/dictionaries/graph/outgoing" +``` + +```json +{ + "outgoing": { + "spacecraft": { "fkCount": 2, "targets": ["satellite_type", "spacecraft_status"] }, + "antenna": { "fkCount": 1, "targets": ["ground_station"] } + } +} +``` + +## 4. Записи (records) + +> **Внимание к URL:** записи живут под `/api/v1/{dictName}/records` +> (а не `/api/v1/dictionaries/{name}/records` — это путь для drafts schema'ы). + +### 4.1 Список записей + +```bash +curl "$ORDINIS/api/v1/spacecraft/records?limit=2" +``` + +```json +[ + { + "id": "a211eb15-...", + "businessKey": "1999-068A", + "data": { + "name": "Terra (EOS AM-1)", + "orbit": { "type": "SSO", "inclination_deg": 98.2 }, + "status": "OPERATIONAL", + "country": "US", + "operator": "НАСА", + "satellite_type_code": "OPT_MIDRES_MS" + }, + "validFrom": "2026-05-08T00:00:00Z", + "validTo": null, + "dataScope": "PUBLIC" + } +] +``` + +### 4.2 Одна запись по business key + +```bash +curl "$ORDINIS/api/v1/spacecraft/records/1999-068A" +``` + +### 4.3 Фильтры + +```bash +# По атрибутам схемы (через query-params) +curl "$ORDINIS/api/v1/spacecraft/records?status=OPERATIONAL&country=RU" + +# По scope (если у тебя есть доступ к INTERNAL) +curl -H "Authorization: Bearer $TOKEN" \ + "$ORDINIS/api/v1/spacecraft/records?scope=INTERNAL" + +# Pagination +curl "$ORDINIS/api/v1/spacecraft/records?limit=20&offset=40" +``` + +### 4.4 Сводка по запланированным записям + +```bash +curl "$ORDINIS/api/v1/spacecraft/records/scheduled-summary" +``` + +```json +{ + "count": 0, + "nearestValidFrom": null, + "upcomingValidFroms": [], + "scheduledBusinessKeys": [] +} +``` + +## 5. Smart Search + +Поиск по содержимому всех справочников. Использует trigram-индекс. + +```bash +curl "$ORDINIS/api/v1/search?q=Sentinel&limit=10" +``` + +```json +{ + "query": "Sentinel", + "total": 12, + "groups": [ + { + "dictName": "mission", + "dictDisplayName": "Миссии / программы", + "count": 2, + "items": [ + { "businessKey": "SENTINEL_1", "dataScope": "PUBLIC", "createdAt": "..." }, + { "businessKey": "SENTINEL_2", "dataScope": "PUBLIC", "createdAt": "..." } + ] + }, + { + "dictName": "instrument", + "dictDisplayName": "Инструменты съёмки", + "count": 2, + "items": [ ... ] + } + ] +} +``` + +Минимальная длина запроса — **3 символа**. Search ограничен текущим scope сессии. + +## 6. Bitemporal queries (TimeTravel) + +### 6.1 История изменений записи + +```bash +curl "$ORDINIS/api/v1/spacecraft/records/1999-068A/history" +``` + +Возвращает все версии (по `valid_from`) и transaction history (`system_time`). + +### 6.2 Snapshot справочника на дату + +```bash +curl "$ORDINIS/api/v1/dictionaries/spacecraft/snapshots?asOf=2026-03-15T00:00:00Z" +``` + +Точная реконструкция справочника на момент `asOf`. Все записи, которые +**действовали** (`valid_from ≤ asOf < valid_to`) и были **известны системе** +(`system_time ≤ now()`). + +### 6.3 Changelog схемы (структурные изменения) + +```bash +curl "$ORDINIS/api/v1/dictionaries/spacecraft/changelog?limit=10" +``` + +```json +{ + "dictionary": "spacecraft", + "currentVersion": "1.0.0", + "entries": [] +} +``` + +Диff между двумя версиями схемы: + +```bash +curl "$ORDINIS/api/v1/dictionaries/spacecraft/changelog/{entryId}/diff" +``` + +## 7. Шаблоны схем + +```bash +curl "$ORDINIS/api/v1/dictionaries/templates" +``` + +```json +[ + { + "id": "antenna", + "name": "Антенна", + "description": "Скелет: код, имя, диаметр, FK на наземную станцию.", + "icon": "WifiHighIcon", + "tags": ["space", "hardware", "cuod"] + }, + { "id": "blank", "name": "Пустой каркас", ... }, + { "id": "spacecraft", "name": "Космический аппарат", ... } +] +``` + +Детали шаблона: + +```bash +curl "$ORDINIS/api/v1/dictionaries/templates/spacecraft" +``` + +Вернёт `{ id, name, description, schemaJson }` со sample JSON Schema. + +## 8. AI: suggest field (требует auth) + +Проверка фичи: + +```bash +curl "$ORDINIS/api/v1/ai/info" +# { "enabled": true } +``` + +Если `enabled: false` — endpoint `/suggest-field` отдаст 404. + +```bash +curl -X POST -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + "$ORDINIS/api/v1/ai/suggest-field" \ + -d '{"prompt": "контактный email оператора"}' +``` + +```json +{ + "fieldName": "operator_contact_email", + "schema": { + "type": "string", + "format": "email", + "description": "Контактный email оператора", + "x-references": "operator.code" + } +} +``` + +**Лимиты:** 30 запросов/мин на IP. Circuit breaker открывается на 5 минут +после 10 ошибок за 60 секунд (`503 circuit_open`). + +## 9. Workflow согласований (требует auth) + +### 9.1 Создать draft записи + +```bash +curl -X POST -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + "$ORDINIS/api/v1/dictionaries/spacecraft/records/drafts" \ + -d '{ + "operation": "CREATE", + "businessKey": "2026-099X", + "data": { "name": "Новый-1", "status": "PLANNED", ... }, + "validFrom": "2026-06-01T00:00:00Z" + }' +``` + +Если `approvalRequired=true` у справочника — draft уходит ревьюеру. +Иначе — record создаётся напрямую (если у тебя есть права). + +### 9.2 Мои drafts + +```bash +curl -H "Authorization: Bearer $TOKEN" \ + "$ORDINIS/api/v1/drafts/me" +``` + +### 9.3 Очередь ревьюера + +```bash +curl -H "Authorization: Bearer $TOKEN" \ + "$ORDINIS/api/v1/admin/reviews/pending" +``` + +### 9.4 Approve / reject + +```bash +# Approve +curl -X POST -H "Authorization: Bearer $TOKEN" \ + "$ORDINIS/api/v1/drafts/{id}/approve" \ + -d '{ "comment": "ok, merging" }' + +# Reject +curl -X POST -H "Authorization: Bearer $TOKEN" \ + "$ORDINIS/api/v1/drafts/{id}/reject" \ + -d '{ "comment": "не хватает источника данных" }' +``` + +## 10. Webhooks (admin) + +### 10.1 Создать подписку + +```bash +curl -X POST -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + "$ORDINIS/api/v1/admin/webhooks/subscriptions" \ + -d '{ + "name": "my-consumer", + "url": "https://my-app.example.com/ordinis-hook", + "eventTypes": ["RecordCreated", "RecordUpdated", "SchemaDraftPublished"], + "scopeFilter": ["PUBLIC", "INTERNAL"], + "dictionaries": null, + "textWrap": false + }' +``` + +Ответ содержит **`secret`** — HMAC-SHA256 ключ для верификации подписи. +Покажется один раз, сохрани. + +### 10.2 Структура payload (события) + +```json +{ + "eventId": "evt_01HXY...", + "eventType": "RecordCreated", + "occurredAt": "2026-05-17T13:00:00Z", + "dictionary": "spacecraft", + "businessKey": "2026-099X", + "actor": "user-uuid-...", + "scope": "PUBLIC", + "after": { ... } +} +``` + +Заголовки: +- `X-Ordinis-Event: RecordCreated` +- `X-Ordinis-Signature: sha256=` (HMAC-SHA256 от body) +- `X-Ordinis-Delivery: del_01HXY...` +- `User-Agent: ordinis-alerts` + +### 10.3 Верификация подписи (Node.js) + +```js +import crypto from 'crypto' + +function verify(body, signatureHeader, secret) { + const [algo, hex] = signatureHeader.split('=') + if (algo !== 'sha256') return false + const expected = crypto + .createHmac('sha256', secret) + .update(body) + .digest('hex') + return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(hex)) +} +``` + +### 10.4 Retry + DLQ + +Доставка ретраится с backoff (1m, 5m, 30m, 2h, 12h). После 5 неудач — +в DLQ. Ручной retry: + +```bash +curl -X POST -H "Authorization: Bearer $TOKEN" \ + "$ORDINIS/api/v1/admin/webhooks/deliveries/{id}/retry" +``` + +## 11. Errors + +Все ошибки — JSON одинаковой формы: + +```json +{ + "status": 422, + "code": "validation_failed", + "message": "name.ru-RU is required", + "details": { "field": "data.name.ru-RU" }, + "traceId": "abc123...", + "timestamp": "2026-05-17T13:00:00Z" +} +``` + +| HTTP | code | Когда | +|---|---|---| +| 400 | `bad_request` | Невалидный JSON, отсутствует обязательный field | +| 401 | `unauthorized` | Нет или просрочен токен | +| 403 | `forbidden` | Недостаточно прав / scope | +| 404 | `not_found` | Нет такого справочника / записи / endpoint | +| 409 | `conflict` | Concurrent update / unique constraint | +| 422 | `validation_failed` | Schema validation fail | +| 429 | `rate_limited` | Превышен лимит запросов | +| 500 | `internal_error` | Серверная ошибка (см. `traceId`) | +| 502 | `llm_error` | AI suggest — LLM недоступен | +| 503 | `circuit_open` | AI suggest — circuit breaker открыт | + +## 12. Готовые сниппеты + +### curl + jq (CI/CD) + +```bash +# Все записи активных операторов +curl -s "$ORDINIS/api/v1/operator/records?status=ACTIVE" \ + | jq '.[] | {key: .businessKey, name: .data.name}' +``` + +### TypeScript / Fetch + +```ts +const ORDINIS = 'https://ordinis.k8s.264.nstart.cloud' + +type SpacecraftRecord = { + businessKey: string + data: { name: string; status: string; orbit: { type: string } } + validFrom: string + dataScope: 'PUBLIC' | 'INTERNAL' | 'RESTRICTED' +} + +async function getSpacecraft(): Promise { + const r = await fetch(`${ORDINIS}/api/v1/spacecraft/records?limit=100`, { + headers: { Accept: 'application/json' }, + }) + if (!r.ok) throw new Error(`Ordinis ${r.status}`) + return r.json() +} +``` + +### Python (requests) + +```python +import requests + +ORDINIS = "https://ordinis.k8s.264.nstart.cloud" + +def smart_search(query: str, limit: int = 10) -> dict: + r = requests.get(f"{ORDINIS}/api/v1/search", + params={"q": query, "limit": limit}, + timeout=10) + r.raise_for_status() + return r.json() + +result = smart_search("Sentinel") +for group in result["groups"]: + print(f'{group["dictDisplayName"]}: {group["count"]} hits') +``` + +### Java (HttpClient) + +```java +HttpClient http = HttpClient.newHttpClient(); + +HttpRequest req = HttpRequest.newBuilder() + .uri(URI.create(ORDINIS + "/api/v1/dictionaries/spacecraft")) + .header("Accept", "application/json") + .GET() + .build(); + +HttpResponse resp = http.send(req, BodyHandlers.ofString()); +DictionaryDetail detail = mapper.readValue(resp.body(), DictionaryDetail.class); +``` + +## 13. Дальше + +- **OpenAPI:** `$ORDINIS/v3/api-docs` (staging) / Swagger UI выше +- **Bundle spec (для интеграторов):** [`bundle-format-spec.md`](./bundle-format-spec.md) +- **CHANGELOG:** [`docs/changelog.md`](../changelog.md) — что меняется по версиям +- **Webhooks deep-dive:** в admin UI → `/webhooks` → Detail page + показывает histogram доставок, DLQ, signature test