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

15 KiB
Raw Blame History

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 и проверка живости

# Production
ORDINIS=https://ordinis.k8s.264.nstart.cloud

# Staging
# ORDINIS=https://ordinis.k8s.265.nstart.cloud

curl "$ORDINIS/api/v1/version"

Ответ:

{
  "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)

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 Использование токена

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 Список всех справочников

curl "$ORDINIS/api/v1/dictionaries"
[
  {
    "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 Детали одного справочника (со схемой)

curl "$ORDINIS/api/v1/dictionaries/spacecraft"

schemaJson — полный JSON Schema draft-07 с x-расширениями:

{
  "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)

curl "$ORDINIS/api/v1/dictionaries/graph/outgoing"
{
  "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 Список записей

curl "$ORDINIS/api/v1/spacecraft/records?limit=2"
[
  {
    "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

curl "$ORDINIS/api/v1/spacecraft/records/1999-068A"

4.3 Фильтры

# По атрибутам схемы (через 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 Сводка по запланированным записям

curl "$ORDINIS/api/v1/spacecraft/records/scheduled-summary"
{
  "count": 0,
  "nearestValidFrom": null,
  "upcomingValidFroms": [],
  "scheduledBusinessKeys": []
}

Поиск по содержимому всех справочников. Использует trigram-индекс.

curl "$ORDINIS/api/v1/search?q=Sentinel&limit=10"
{
  "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 История изменений записи

curl "$ORDINIS/api/v1/spacecraft/records/1999-068A/history"

Возвращает все версии (по valid_from) и transaction history (system_time).

6.2 Snapshot справочника на дату

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 схемы (структурные изменения)

curl "$ORDINIS/api/v1/dictionaries/spacecraft/changelog?limit=10"
{
  "dictionary": "spacecraft",
  "currentVersion": "1.0.0",
  "entries": []
}

Диff между двумя версиями схемы:

curl "$ORDINIS/api/v1/dictionaries/spacecraft/changelog/{entryId}/diff"

7. Шаблоны схем

curl "$ORDINIS/api/v1/dictionaries/templates"
[
  {
    "id": "antenna",
    "name": "Антенна",
    "description": "Скелет: код, имя, диаметр, FK на наземную станцию.",
    "icon": "WifiHighIcon",
    "tags": ["space", "hardware", "cuod"]
  },
  { "id": "blank", "name": "Пустой каркас", ... },
  { "id": "spacecraft", "name": "Космический аппарат", ... }
]

Детали шаблона:

curl "$ORDINIS/api/v1/dictionaries/templates/spacecraft"

Вернёт { id, name, description, schemaJson } со sample JSON Schema.

8. AI: suggest field (требует auth)

Проверка фичи:

curl "$ORDINIS/api/v1/ai/info"
# { "enabled": true }

Если enabled: false — endpoint /suggest-field отдаст 404.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  "$ORDINIS/api/v1/ai/suggest-field" \
  -d '{"prompt": "контактный email оператора"}'
{
  "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 записи

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

curl -H "Authorization: Bearer $TOKEN" \
  "$ORDINIS/api/v1/drafts/me"

9.3 Очередь ревьюера

curl -H "Authorization: Bearer $TOKEN" \
  "$ORDINIS/api/v1/admin/reviews/pending"

9.4 Approve / reject

# 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 Создать подписку

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 (события)

{
  "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)

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:

curl -X POST -H "Authorization: Bearer $TOKEN" \
  "$ORDINIS/api/v1/admin/webhooks/deliveries/{id}/retry"

11. Errors

Все ошибки — 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)

# Все записи активных операторов
curl -s "$ORDINIS/api/v1/operator/records?status=ACTIVE" \
  | jq '.[] | {key: .businessKey, name: .data.name}'

TypeScript / Fetch

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)

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)

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
  • CHANGELOG: docs/changelog.md — что меняется по версиям
  • Webhooks deep-dive: в admin UI → /webhooks → Detail page показывает histogram доставок, DLQ, signature test