Files

31 KiB
Raw Permalink Blame History

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

# 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"

Ответ:

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

TOKEN=$(curl -s -X POST \
  "https://altum.nstart.cloud/auth/realms/altum/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?as_scope=PUBLIC"

as_scope обязателен (CSV: PUBLIC,INTERNAL,RESTRICTED).

[
  {
    "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?as_scope=PUBLIC"

4.3 Фильтры

# Locale — для x-localized полей (name, mission, operator etc).
# ⚠️ Сейчас работает ТОЛЬКО через Accept-Language header. ?locale= query
# param backend игнорирует — TODO в roadmap.
curl -H "Accept-Language: en-US" \
  "$ORDINIS/api/v1/spacecraft/records/1999-068A?as_scope=PUBLIC"
# default ru-RU:    "mission": "Глобальный мониторинг суши и атмосферы"
# с Accept-Language en-US:  "mission": "Global land and atmosphere monitoring"

# 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 Сводка по запланированным записям

curl "$ORDINIS/api/v1/spacecraft/records/scheduled-summary?as_scope=PUBLIC"
{
  "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?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 схемы (структурные изменения)

curl "$ORDINIS/api/v1/dictionaries/spacecraft/changelog"
{
  "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)

# Все записи операторов в PUBLIC scope; фильтрация на клиенте
curl -s "$ORDINIS/api/v1/operator/records?as_scope=PUBLIC" \
  | jq '.[] | select(.data.status == "ACTIVE") | {key: .businessKey, name: .data.name}'

TypeScript / Fetch

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)

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)

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. Дальше

  • Приложение A: Cheat Sheet — одностраничник со всеми endpoints, ниже в этом документе
  • OpenAPI: $ORDINIS/v3/api-docs (staging) / Swagger UI выше
  • Postman collection: ~/Desktop/Ordinis-Postman/ (27 рабочих запросов + 10-шаговый demo flow)
  • Bundle spec (для интеграторов): bundle-format-spec.md
  • CHANGELOG: docs/changelog.md — что меняется по версиям
  • Webhooks deep-dive: в admin UI → /webhooks → Detail page показывает histogram доставок, DLQ, signature test

Приложение A · Cheat Sheet

Дублирует api-cheatsheet.md — одностраничный quick-reference со всеми endpoints, query-params, error codes и реальными примерами для copy-paste. Дублирование осознанное: интегратор открывает quickstart один раз, далее живёт в приложении.

Одностраничный референс. Полный гайд — 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

# client_credentials (для service-to-service интеграторов)
TOKEN=$(curl -s -X POST https://altum.nstart.cloud/auth/realms/altum/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.

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

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 — единая форма

{ "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-зависимость: кто использует справочник

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_stationantenna.ground_station (4 records, BLOCK)
  • spacecraft_statusspacecraft.status
  • countryspacecraft.country

Локализация записи (Accept-Language)

# 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: запись на момент в прошлом

# Запись на 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 — все ревизии записи

curl -s "$ORDINIS/api/v1/dictionaries/spacecraft/records/1999-068A/history?as_scope=PUBLIC" | jq '.[0] | {revisionNumber, revisionType, updatedAt}'

Schema time-travel (changelog diff)

# Список ревизий
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

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

Постман collection (на случай если потерял ссылку из section 13)

27 рабочих запросов + 10-шаговый demo flow:

  • ~/Desktop/Ordinis-Postman/ (если генерировал локально)
  • docs/integration/postman/ — версионный snapshot

— конец приложения A —