docs(api): Quick Start guide + Cheat Sheet для интеграторов

This commit is contained in:
Александр Зимин
2026-05-17 13:14:33 +00:00
parent 2f06b408b8
commit 04e8251941
2 changed files with 760 additions and 0 deletions
+572
View File
@@ -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=<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