Files
mdm-ordinis/docs/integration/api-cheatsheet.md
T
Zimin A.N. f3c3e33289 docs(cheatsheet): добавить конкретные примеры с реальными dicts
После Postman demo audit — в cheatsheet не хватало живых примеров,
которые можно скопировать и сразу проверить. Добавил:

* FK-зависимость:
  GET /dictionaries/satellite_type/dependents →
  [{sourceDict: spacecraft, sourceField: satellite_type_code,
    onClose: BLOCK, activeRecordsInSourceDict: 14}]
  + список других живых FK на staging (ground_station, country etc).

* Локализация записи через Accept-Language:
  ru: НАСА / Глобальный мониторинг суши и атмосферы
  en: NASA / Global land and atmosphere monitoring
  + warning что ?locale= игнорируется и schema endpoint вообще
  не локализуется.

* Time-travel: 14.05.2026 12:00 → 200, 14.05.2026 00:00 → 404
  record_not_active (ожидаемое поведение, не bug).

* History и changelog/diff живые примеры с test1.

Также убрал password-grant из Auth (используется client_credentials —
это правильный flow для server-to-server интеграторов). И добавил
dependents проверку в 60-second smoke.
2026-05-20 11:52:04 +03:00

280 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ordinis API · Cheat Sheet
Одностраничный референс. Полный гайд — `api-quickstart.md`.
**Base prod:** `https://ordinis.nstart.cloud/api/v1` (public LE-cert)
**Base staging:** `https://ordinis.k8s.265.nstart.cloud/api/v1` (Swagger UI здесь)
**Version:** `v2.37.x` · **OpenAPI:** `/v3/api-docs` (только staging — на prod Swagger выключен)
## Auth
```bash
# client_credentials (для service-to-service интеграторов)
TOKEN=$(curl -s -X POST https://auth.nstart.space/realms/nstart/protocol/openid-connect/token \
-d grant_type=client_credentials \
-d client_id=$KC_CLIENT_ID -d client_secret=$KC_CLIENT_SECRET | jq -r .access_token)
curl -H "Authorization: Bearer $TOKEN" $ORDINIS/api/v1/spacecraft/records?as_scope=PUBLIC,INTERNAL
```
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?from=&to=&granularity=` | Bucket'ы изменений для TimeTravel slider (granularity: hour/day/week) |
| `GET` | `/{name}/records?as_scope=PUBLIC` | Список записей (⚠️ **БЕЗ** `/dictionaries/` префикса) |
| `GET` | `/{name}/records/{businessKey}?as_scope=PUBLIC` | Одна запись |
| `GET` | `/dictionaries/{name}/records/{businessKey}/history?as_scope=PUBLIC` | История версий записи (Envers) — **с** `/dictionaries/` |
| `GET` | `/dictionaries/{name}/records/{businessKey}/cascade-preview?as_scope=PUBLIC` | Preview каскада — **с** `/dictionaries/` |
| `GET` | `/{name}/records/scheduled-summary?as_scope=PUBLIC` | Будущие версии (count + nearestValidFrom) |
| `GET` | `/search?q=` | Поиск по справочникам (records не индексируются) |
| `GET` | `/ai/info` | `{enabled: bool}` AI feature flag |
> ⚠️ **Корень частой путаницы**: consumer endpoints (`/records/...`) идут БЕЗ `/dictionaries/` префикса. Admin-style audit endpoints (history, cascade-preview, changelog, drafts, snapshots) — С `/dictionaries/`. См. [api-quickstart.md § Topology](api-quickstart.md).
### Query params (records)
```
?as_scope=PUBLIC,INTERNAL (required) CSV scope filter
?at=2026-03-15T00:00:00Z bitemporal: запись на конкретный момент (validFrom <= at < validTo)
Accept-Language: en-US (header) локаль — единственный рабочий путь
⚠️ ?locale= query param backend сейчас игнорирует
⚠️ Работает ТОЛЬКО на records endpoints.
Schema endpoint (`/dictionaries/{name}`) всегда
возвращает schemaJson на defaultLocale.
?bbox=lon1,lat1,lon2,lat2 geo: bounding box (для geo-dicts)
?polygon=<GeoJSON> geo: polygon-фильтр
```
**Что НЕ работает (на read-api endpoint):**
- `?limit=`, `?offset=` — pagination отсутствует, возвращается весь массив
- `?status=ACTIVE&country=RU` — нет ad-hoc filtering по schema полям; фильтруй на клиенте
## Write (требует auth)
⚠️ Все write-endpoints — на **writer** сервисе, с `/dictionaries/` префиксом.
| Verb | Path | Что |
|---|---|---|
| `POST` | `/dictionaries/{name}/records` | Создать запись (если approval не нужен) |
| `PUT` | `/dictionaries/{name}/records/{businessKey}` | Обновить |
| `DELETE` | `/dictionaries/{name}/records/{businessKey}` | Soft-delete (tombstone) |
| `POST` | `/dictionaries/{name}/records/bulk-close` | Массовое закрытие |
| `POST` | `/dictionaries/{name}/records/{businessKey}/cascade-close` | Закрыть с зависимыми |
| `POST` | `/dictionaries/{name}/records/export-selected.csv` | Bulk export (выборка по ids в body) |
| `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=<hex> 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 |
## Реальные примеры (staging 265)
### FK-зависимость: кто использует справочник
```bash
curl -s "$ORDINIS/api/v1/dictionaries/satellite_type/dependents" | jq
# [
# {
# "sourceDict": "spacecraft",
# "sourceDisplayName": "Космические аппараты",
# "sourceField": "satellite_type_code",
# "targetField": "code",
# "onClose": "BLOCK",
# "activeRecordsInSourceDict": 14
# }
# ]
```
**Семантика**: `spacecraft.satellite_type_code` ссылается на `satellite_type.code`. На staging 14 КА используют этот справочник, и `onClose: BLOCK` означает что нельзя удалить тип КА пока на него ссылаются записи (без cascade-close).
Другие живые FK на staging:
- `ground_station``antenna.ground_station` (4 records, BLOCK)
- `spacecraft_status``spacecraft.status`
- `country``spacecraft.country`
### Локализация записи (Accept-Language)
```bash
# default ru-RU
curl -s "$ORDINIS/api/v1/spacecraft/records/1999-068A?as_scope=PUBLIC" | jq '.data | {mission, operator}'
# {"mission": "Глобальный мониторинг суши и атмосферы", "operator": "НАСА"}
# en-US
curl -sH "Accept-Language: en-US" \
"$ORDINIS/api/v1/spacecraft/records/1999-068A?as_scope=PUBLIC" | jq '.data | {mission, operator}'
# {"mission": "Global land and atmosphere monitoring", "operator": "NASA"}
```
⚠️ Только Accept-Language header. `?locale=` query param backend сейчас игнорирует. Schema endpoint (`/dictionaries/{name}`) **не локализуется** вообще — всегда возвращает на defaultLocale.
### Time-travel: запись на момент в прошлом
```bash
# Запись на 14.05.2026 12:00 UTC (точно после её validFrom = 11:34)
curl -s "$ORDINIS/api/v1/test1/records/Dima_record_test_1?as_scope=PUBLIC&at=2026-05-14T12:00:00Z" \
| jq '{businessKey, validFrom, validTo, data}'
# 200 OK — запись действительная на указанный момент
# До validFrom → 404 record_not_active (это ожидаемо)
curl -s "$ORDINIS/api/v1/test1/records/Dima_record_test_1?as_scope=PUBLIC&at=2026-05-01T00:00:00Z" | jq '.code'
# "record_not_active"
```
### History — все ревизии записи
```bash
curl -s "$ORDINIS/api/v1/dictionaries/spacecraft/records/1999-068A/history?as_scope=PUBLIC" | jq '.[0] | {revisionNumber, revisionType, updatedAt}'
```
### Schema time-travel (changelog diff)
```bash
# Список ревизий
curl -s "$ORDINIS/api/v1/dictionaries/test1/changelog" | jq '.currentVersion, (.entries | length)'
# Diff конкретной ревизии (before/after JSON-Schema)
curl -s "$ORDINIS/api/v1/dictionaries/test1/changelog/22/diff" | jq '{eventType, occurredAt, before: .before.properties | keys, after: .after.properties | keys}'
```
## 60-second smoke
```bash
ORDINIS=https://ordinis.nstart.cloud # prod (public LE)
# или: ORDINIS=https://ordinis.k8s.265.nstart.cloud # staging
curl -s $ORDINIS/api/v1/version | jq # alive
curl -s $ORDINIS/api/v1/dictionaries | jq 'length' # 37 на prod / 39 на staging
curl -s $ORDINIS/api/v1/dictionaries/spacecraft | jq '.scope' # "PUBLIC"
curl -s "$ORDINIS/api/v1/spacecraft/records?as_scope=PUBLIC" | jq '.[0].businessKey'
curl -s "$ORDINIS/api/v1/dictionaries/satellite_type/dependents" | jq 'length' # 1 (spacecraft зависит)
curl -s "$ORDINIS/api/v1/search?q=spacecraft" | jq '.total' # >0
curl -s $ORDINIS/api/v1/ai/info | jq '.enabled' # true / false
```
## Postman collection
Готовая collection с реальными запросами (28 запросов, demo-flow): см. [docs/integration/postman/](postman/) или `~/Desktop/Ordinis-Postman/` (если генерировал локально).
## Связанные доки
`api-quickstart.md` · `bundle-format-spec.md` · `docs/changelog.md` · Swagger UI `/swagger-ui/index.html`