Files
mdm-ordinis/docs/integration/api-quickstart.md
T
Zimin A.N. ae05e17f96 docs(api): cheatsheet + quickstart sync с реальным API после Postman audit
Audit Postman collection выявил расхождения docs ↔ runtime — фикс:

Topology clarification:
- Records (consumer) — БЕЗ /dictionaries/ префикса: /api/v1/{name}/records
- History/cascade-preview/changelog/drafts — С /dictionaries/ (writer audit)
- export-selected.csv — POST, не GET

Query params (records list, /{name}/records):
-  as_scope (required, CSV) — было документировано как ?scope= (неверно)
-  at (ISO timestamp) — было ?asOf= (неверно)
-  locale + Accept-Language header
-  bbox / polygon (geo)
-  убраны: ?limit, ?offset (pagination отсутствует),
            ?status/?country (нет ad-hoc filtering по schema полям)

Snapshots params:
-  from / to / granularity (hour/day/week) — было ?asOf= (неверно)

Base URLs:
- prod: ordinis.nstart.cloud (public LE-cert)
- staging: ordinis.k8s.265.nstart.cloud (Swagger UI здесь)
- было: ordinis.k8s.264.nstart.cloud (устаревший internal, prod переехал)

Version: v2.36 → v2.37.x.

Refs MR !242 (snapshots backend fix), Postman collection в
~/Desktop/Ordinis-Postman/.
2026-05-19 23:15:40 +03:00

584 lines
16 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 — Quick Start
Practical guide to НСИ-ordinis REST API. Тут не справочник всех endpoint'ов
(для этого есть OpenAPI / Swagger UI), а минимально-достаточный путь от
«первый запрос» до «подписался на webhook».
**Версия:** v2.37.x (`/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 — на records list **не поддерживается**, возвращается весь массив. Для больших dicts фильтруй на клиенте или через `?at=` time-slice.
- Ошибки — `application/json` с `{status, code, message, traceId, timestamp}`.
## 1. Base URL и проверка живости
```bash
# Production
ORDINIS=https://ordinis.nstart.cloud # prod (public LE)
# ORDINIS=https://ordinis.k8s.265.nstart.cloud # staging
# 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.37.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?as_scope=PUBLIC"
```
`as_scope` обязателен (CSV: `PUBLIC,INTERNAL,RESTRICTED`).
```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?as_scope=PUBLIC"
```
### 4.3 Фильтры
```bash
# Locale — для x-localized полей сервер вернёт только указанную локаль
curl "$ORDINIS/api/v1/spacecraft/records/1999-068A?as_scope=PUBLIC&locale=en-US"
# или через стандартный header:
curl -H "Accept-Language: en-US" "$ORDINIS/api/v1/spacecraft/records?as_scope=PUBLIC"
# Time-travel: состояние на конкретный момент (validFrom <= at < validTo)
curl "$ORDINIS/api/v1/spacecraft/records/1999-068A?as_scope=PUBLIC&at=2026-01-01T00:00:00Z"
# По scope (если есть права на INTERNAL/RESTRICTED)
curl -H "Authorization: Bearer $TOKEN" \
"$ORDINIS/api/v1/spacecraft/records?as_scope=PUBLIC,INTERNAL"
# Geo (для geo-dicts с PostGIS support)
curl "$ORDINIS/api/v1/ground_station/records?as_scope=PUBLIC&bbox=30,55,40,60"
```
⚠️ **НЕ работает на records list endpoint**: `?limit=`, `?offset=` (pagination отсутствует), `?status=...&country=...` (нет ad-hoc filtering по schema полям). Фильтруй на клиенте.
### 4.4 Сводка по запланированным записям
```bash
curl "$ORDINIS/api/v1/spacecraft/records/scheduled-summary?as_scope=PUBLIC"
```
```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?from=2026-01-01T00:00:00Z&to=2026-05-01T00:00:00Z&granularity=day"
```
Точная реконструкция справочника на момент `asOf`. Все записи, которые
**действовали** (`valid_from ≤ asOf < valid_to`) и были **известны системе**
(`system_time ≤ now()`).
### 6.3 Changelog схемы (структурные изменения)
```bash
curl "$ORDINIS/api/v1/dictionaries/spacecraft/changelog"
```
```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=<hex>` (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
# Все записи операторов в PUBLIC scope; фильтрация на клиенте
curl -s "$ORDINIS/api/v1/operator/records?as_scope=PUBLIC" \
| jq '.[] | select(.data.status == "ACTIVE") | {key: .businessKey, name: .data.name}'
```
### TypeScript / Fetch
```ts
const ORDINIS = 'https://ordinis.nstart.cloud'
type SpacecraftRecord = {
businessKey: string
data: { name: string; status: string; orbit: { type: string } }
validFrom: string
dataScope: 'PUBLIC' | 'INTERNAL' | 'RESTRICTED'
}
async function getSpacecraft(): Promise<SpacecraftRecord[]> {
const r = await fetch(
`${ORDINIS}/api/v1/spacecraft/records?as_scope=PUBLIC`,
{ headers: { Accept: 'application/json', 'Accept-Language': 'en-US' } },
)
if (!r.ok) throw new Error(`Ordinis ${r.status}`)
return r.json()
}
```
### Python (requests)
```python
import requests
ORDINIS = "https://ordinis.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<String> 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