Files
mdm-ordinis/docs/integration/api-quickstart.md
T
2026-05-17 13:14:33 +00:00

573 lines
15 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.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=<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
# Все записи активных операторов
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<SpacecraftRecord[]> {
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<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