惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
Help Net Security
Help Net Security
Blog — PlanetScale
Blog — PlanetScale
B
Blog RSS Feed
Recent Announcements
Recent Announcements
云风的 BLOG
云风的 BLOG
P
Proofpoint News Feed
阮一峰的网络日志
阮一峰的网络日志
MyScale Blog
MyScale Blog
WordPress大学
WordPress大学
M
MIT News - Artificial intelligence
L
LangChain Blog
J
Java Code Geeks
罗磊的独立博客
雷峰网
雷峰网
Microsoft Security Blog
Microsoft Security Blog
Microsoft Azure Blog
Microsoft Azure Blog
H
Help Net Security
Martin Fowler
Martin Fowler
Google DeepMind News
Google DeepMind News
博客园 - 聂微东
F
Full Disclosure
博客园 - 叶小钗
爱范儿
爱范儿
人人都是产品经理
人人都是产品经理
博客园 - 司徒正美
Hugging Face - Blog
Hugging Face - Blog
量子位
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
小众软件
小众软件
D
Docker
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
Visual Studio Blog
Last Week in AI
Last Week in AI
The Cloudflare Blog
MongoDB | Blog
MongoDB | Blog
T
The Blog of Author Tim Ferriss
H
Hackread – Cybersecurity News, Data Breaches, AI and More
V
V2EX
月光博客
月光博客
T
Tailwind CSS Blog
Vercel News
Vercel News
T
Threatpost
IT之家
IT之家
Simon Willison's Weblog
Simon Willison's Weblog
C
CXSECURITY Database RSS Feed - CXSecurity.com
GbyAI
GbyAI
NISL@THU
NISL@THU
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Как прокачать документацию API ковром, и причем тут Валера
AlreadyBacon · 2026-06-15 · via Все публикации подряд на Хабре

Средний

14 мин

18

Всем привет!

Я Антон, системный аналитик из команды трансграничных переводов в Uzum Fintech, и от меня уже два месяца ждут текст по результатам выступления на внутреннем митапе.

Поэтому сегодня мне придется рассказать про то, как с помощью свежего стандарта от OpenApi Initiative можно упростить процесс понимания разных чужих апишек и быстрее интегрироваться с какой-нибудь чужой вундервафлей.

  1. Проблемы с описанием API
    Дисклеймер
    Усложнение API
    Документация

  2. Ковры
    Простейший пример
    Инструменты

  3. Вместо заключения

Проблемы с описанием API

Дисклеймер

В данной статье под «интеграцией» будет пониматься скорее какая-то совсем внешняя история. Ребята из соседней команды, которые помогут и всё объяснят с расстояния пары сообщений в телеграме/slack’e тут не очень подходят, но полезно будет и для взаимодействия с ними.
А еще будет полезно разработчикам, тестировщикам, аналитикам, продактам, техписам и даже биздевам — в общем всем, кто так или иначе трогает API.

Усложнение API

Начнем с того, что сейчас очень редко можно встретить интеграцию, в рамках которой достаточно вызвать всего один метод, чтобы получить желаемый бизнес-результат. Чаще приходится дергать в нужной последовательности несколько разных эндпоинтов, причем последовательность может меняться в зависимости от полученного промежуточного результата. То есть происходит переход от «вызова метода» к сценариям. 

И тогда для успешной интеграции надо:

  1. Знать, что нужно получить на выходе — бизнесовый результат

  2. Знать сценарий, как достичь этого результата

  3. Знать данные, которые нужны для всего сценария в целом и для каждого шага в частности

Ладно, если бы это было в рамках RESTful API, где глаголы методов и названия ресурсов как-то подсказывают, что делать, но существует классный тренд на смесь REST- и RPC-подходов, где, к сожалению, нет устоявшихся и общепринятых норм: команды придерживаются какой-то одной концепции только в рамках одного конкретного сервиса.

Или, если рассмотреть абстрактный кровавый энтерпрайз, может возникнуть что-то очень похожее на нулевой уровень зрелости REST API по Ричардсону, где все операции делаются через один-два универсальных POST-эндпоинта, но выполняемое действие зависит от передаваемой в теле запроса «команды» и данных. Команд при этом может быть много, и все они требуют абсолютно разных наборов данных.

Во всё это легко вникнуть, если читатель документации — сам автор, в других же случаях явно будут вопросы разной степени глубины и отчаяния. 

Усложнение самих API — это только половина беды.

Документация

В отношении документации тоже не все так хорошо, как хотелось бы видеть в 2к26. В лучшем случае она вообще существует. А дальше начинаются всякие разные ухищрения или даже извращения.

То с чем приходилось сталкиваться на практике (без имен):

  1. Всеобъемлющий портал с документацией на все эндпоинты, с описанием структур данных, enum’ов, схем и кучей текста.
    Это очень тяжело поддерживать в актуальном состоянии и в любом случае остаются вопросы, как работает та или иная штука в таком-то (именно вашем) кейсе, которые приходится решать через поддержку. Но это скорее около-госовая история и то, к чему надо стремиться

  2. Документация на весь сервис в виде .PDF или .docx.
    Тут всё по классике: листинги запросов и ответов даны в две колонки для экономии места, но всё равно не влезают на одну страницу. Сразу возрастает сложность при копировании нужного куска JSON’a 

  3. Коллекция запросов для Postman’a, к которой нет сопроводительного текста, где описана структура папок и что же в конечном счете делает или должен делать каждый из запросов. Или этот текст есть, но вам его не прислали. Или прислали, но через 2 года 

И два вспомогательных артефакта:

  1. Sequence-диаграмма на пару экранов по высоте и ширине, любимая большинством аналитиков, и которая не предназначена для хранения всей необходимой информации (типа тел запросов)

  2. OpenApi Specification — формализованное описание API, которое, к счастью, стало стандартом в индустрии

У нас в Uzum Fintech используется комбинированный подход: есть портал разработчика, куда выкладывается документация, собранная по принципу комбинирования лучшего из всех вышеописанных подходов и артефактов.

Пользуясь случаем, передаю привет Дане и коллегам, ответственным за портал.

Подытожу: API усложняются, приходится мыслить сценариями, а не методами, и документация не всегда упрощает процесс интеграции.

Ковры

Ребята из OpenApi Initiative осознали масштаб этой проблемы и выпустили стандарт Arazzo: в переводе с итальянского «гобелен». Мне, для простоты, больше нравится слово «ковёр», поэтому я буду использовать его)

Arazzo — это описание сценария взаимодействия API на формализованном языке. То есть и человеку будет понятно, и этому вашему бездушному ИИ-агенту.

Простейший пример

Рассмотрим простейший пример файла-сценария, который описывает вызов одного метода сервиса моей команды, чтобы разобраться, из чего состоит такой файлик. На нем же поймем тесную связь Arazzo-сценария и спецификации OAS.

arazzo: '1.0.1'
info:
  title: CBT – /convert
  version: '0.5.1'


sourceDescriptions:
  - name: cbtApi
    type: openapi
    url: https://%адрес_со_спекой%/ru_crossborder.yaml


workflows:
  - workflowId: example
    summary: /convert
    parameters:
      - name: Authorization
        in: header
        value: Basic значение_токена
    steps:
      # Шаг 1 — /convert (НЕ обязателен к успеху)
      - stepId: call-convert
        description: POST /convert (optional)
        operationId: cbtApi.convert
        requestBody:
          payload:
            amount: 50000
            currencyFrom: RUB
            currencyTo: UZS
            direction: TO_UZ
        # "Широкий" успех, чтобы не валить сценарий на 4xx
        successCriteria:
          - condition: $statusCode == 200 || $statusCode == 400 || $statusCode == 401 || $statusCode == 403

Структура и синтаксис Arazzo-файла очень похожа на описание API в OAS/swagger — это тоже *.YAML с разными блоками.

Пробежимся сверху вниз по этим блокам:

  • Версия стандарта. Совсем недавно релизнули версию 1.1.0 с поддержкой AsyncAPI, то есть в рамках сценария работы можно будет описывать взаимодействие c Kafka

  • Информация о самом документе — это больше для читателей-людей

  • Ссылка на файл спецификации API. Одна из самых важных штук, благодаря которой всё работает. Можно указать как внешней ссылкой, так и локальным файлом. В этом случае я как раз ссылаюсь на наш портал

  • Блок “workflows” — непосредственно описание вызова методов.
    Workflow — это фактически сценарий работы API, в одном файле можно описать несколько сценариев, если хочется

  • Как и в OAS в запрос можно добавить параметры, в данном случае заголовок авторизации, и сразу передать желаемое значение base64 от логина и пароля учетки с препрода

  • Сценарий состоит из шагов. Здесь шаг всего один, для наглядности. Подробность описания и комментарии — на совести автора, как в OAS

  • stepId — идентификатор шага в рамках сценария

  • operationId, вторая ключевая вещь, должен совпадать с operationId вызываемого метода из спецификации

  • Далее — тело запроса, то есть требуемые в рамках шага данные

  • И в конце — критерии успеха. В примере любой ответ считаем успешным

Тут будет небольшое отступление.

Внимательный читатель заметит, что комментарии в листинге выше явно написаны не автором данной статьи, и окажется прав: весь файл сценария был написан ChatGPT, или, как я его называю, Валерой.

У Arazzo и Валеры давняя история взаимодействия: как только OAI выкатили первую публичную версию стандарта, они сразу рекомендовали использовать допиленную версию чата — Arazzo Specification.

Собственно, все файлы сценариев, которые я когда-либо использовал, написаны Валерой, я их немного редактировал, чтобы они действительно выполняли нужное. 

Вернемся к сценариям.

Как будто в вызове одного метода нет ничего сложного. А если хочется в сценарии использовать несколько методов, то возникает логичный вопрос, как вытащить данные, например, из ответа шага №1 и запихнуть в тело запроса в шаг №3?
OAI это предусмотрели, и стандарт сразу поддерживает блок “outputs”:

      - stepId: call-convert
        description: POST /convert (optional)
        operationId: cbtApi.convert
        requestBody:
          payload:
            amount: 1000000
            currencyFrom: UZS
            currencyTo: RUB
            direction: FROM_UZ
        successCriteria:
          - condition: $statusCode == 200 || $statusCode == 400 || $statusCode == 401 || $statusCode == 403
        outputs:
          amountFrom:   $response.body#/amountFrom
          currencyFrom: $response.body#/currencyFrom
          amountTo:     $response.body#/amountTo
          currencyTo:   $response.body#/currencyTo
          run_id:       $response.header.Date   # ← внешний id для следующих шагов
        onFailure:
          - name: continueToCardListEvenIfConvertFails
            type: goto
            stepId: card-list

Тут я сохраняю данные из ответа метода /convert в «переменные». Валера подсказал, что можно сохранить в переменную значение заголовка и решить, например, проблему с созданием всяких уникальных идентификаторов.

Еще добавился блок “onFailure” — что делать, если произошёл неуспех. В данном случае Валера, перемудрив с неймингом, с помощью goto отправляет на другой шаг. Ну, и собственно всё, этой базы должно хватать.

Всякие подробности можно глянуть в спеке.
Полный пример ковра с тремя сценариями и несколькими шагами в каждом я приложу файлом в конце статьи.

Инструменты

Файлики со сценариями это, конечно, хорошо, но делать-то с ними что? 

Тут второе отступление: у меня была задачка — описать работу с новым сервисом для партнёров. Наученный сложной коммуникацией с нерусскоязычными партнерами, я сразу вспомнил про Arazzo и начал консультироваться с Валерой, как же все это красиво и быстро запилить, чтобы легко пошарить в виде доки.

Валера меня обманул. Он заявил, что redocly-cli (мы его обычно используем для оформления доки, которую надо пошарить) отлично справляется с визуализацией Arazzo-сценариев прямо рядом с описанием методов спеки. Но в процессе я узнал про их инструмент respect, который тоже можно юзать из того же cli.

Расскажу подробнее именно про redocly respect, потому что про Arazzo UI и Arazzo Editor, дефолтные инструменты для работы со стандартом от самих создателей (тут привет Фрэнку из Jentic), вы скорее всего уже знаете или узнаете при первом походе в гугл. 

Отступление три. Когда-то давно, когда я только постигал азы системного анализа и мой работодатель-аутсорсер отправил меня в аутстафф, архитектор из команды заказчика сказал, что Ubuntu — плохая операционная система для аналитика. Поэтому ниже будет рассказ про использование redocly respect под Windows в дефолтной CMD.

Сначала ставим redocly, потому что npm уже установлен.
Официальный полный гайд — тут.

npm i @redocly/cli@latest

После этого можно сразу запускать сценарий:

redocly respect "C:\arazzo\simple_example.yml" --workflow example --server cbtApi=https://%адрес_препрода% --verbose

Тут всё просто:

  • redocly respect — что вообще запускаем

  • Далее указываем абсолютный путь к файлику со сценарием/сценариями

  • Указываем, какой именно сценарий нужно прогнать

  • Через ключ –server выбираем, какое окружение будем использовать
    Тут надо помнить, что приличные люди указывают адреса окружений в OAS, но я не из их числа)

  • –verbose — в исследовательских целях нас интересуют полный вывод в консоль и по запросам, и по ответам

  • Можно также скормить креды для авторизации, но у меня они прямо в файле-сценарии

После нажатия Enter в консоли появится что-то подобное:

Running workflow simple_example.yml / example


  ✓ POST /cbt/v1/transfer/convert - step call-convert


    Request URL: https://your-service-host/cbt/v1/transfer/convert
    Request Headers:
      content-type: application/json
      accept: application/json
      authorization: Basic значение_токена
    Request Body:
      {
        "amount": 50000,
        "currencyFrom": "RUB",
        "currencyTo": "UZS",
        "direction": "TO_UZ"
      }




    Response status code: 200
    Response time: 1061 ms
    Response Headers:
      cache-control: no-cache, no-store, max-age=0, must-revalidate
      connection: keep-alive
      content-length: 105
      content-type: application/json
      date: Fri, 29 May 2026 12:55:33 GMT
      expires: 0
      pragma: no-cache
      server: nginx
      vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
      x-content-type-options: nosniff
      x-frame-options: DENY
      x-xss-protection: 0
    Response Size: 105 bytes
    Response Body:
      {
        "amountFrom": 50000,
        "currencyFrom": "RUB",
        "amountTo": 7608974,
        "currencyTo": "UZS",
        "rate": "152.179490169555"
      }


    ✓ success criteria check - $statusCode == 200 || $statusCode == 400 || $statu...
    ✓ status code check - $statusCode in [200, 400, 401, 403]
    ✓ content-type check
    ✓ schema check




  Summary for simple_example.yml
  
  Workflows: 1 passed, 1 total
  Steps: 1 passed, 1 total
  Checks: 4 passed, 4 total
  Time: 2760ms


┌───────────────────────┬────────────┬─────────┬─────────┬──────────┐
│ Filename              │ Workflows  │ Passed  │ Failed  │ Warnings │
├───────────────────────┼────────────┼─────────┼─────────┼──────────┤
│ ✓ simple_example.yml  │     1      │    1    │    -    │    -     │
└───────────────────────┴────────────┴─────────┴─────────┴──────────┘




Самое интересное в выводе — в самом низу: видим, что выполнялся один шаг, он выполнился успешно и пройдены все 4 проверки:

  • код ответа входит в набор успешных, который мы описывали в файле

  • код ответа входит в набор описанных в файле спецификации метода

  • контент-тайп нужный

  • схема вернувшегося ответа совпадает с той, которая описана в спецификации

Скорее всего, именно последняя проверка дала название инструменту: можно проуважить сценарий об спеку и выявить несоответствия, например, в документации на портале.

В итоге имеем штуку, которая позволяет прогонять API-сценарий из некоторого числа методов и выводить подробности о том, что было сделано.

Вместо заключения

Может показаться, что в сочетании с redocli respect, Arazzo — отличный способ для автоматизации тестирования. Для простых API, где нет всяких хитростей типа подписи запросов и ответов, вполне.

Для чего-то сложнее — используйте привычные инструменты: авторы закладывали в Arazzo только упрощение процесса документирования API.

Как использовать ковры внутри команды?

Не знаю, честно. Я прекрасно понимаю, что сценарии в 7 методов, из которых 3 скорее информационные, не очень подходят для обкатки Arazzo в команде, которая это все реализовывала. Но для внешних партнёров, которые ваш сервис (и его спецификацию) видят впервые, файл-ковер позволит хотя бы понять, что и в каком порядке дергать.

И на этой мысли я закончу первую статью про Arazzo на русском языке)

PS: я не затрагивал MCP и прочие агентские шутки в контексте Arazzo. Существует мнение, что если раньше у вас был специальный сотрудник-человек для покупки авиабилетов, то сейчас у вас есть модный ИИ-агент для этого.

Чтобы помочь агенту c покупкой нужных билетов и можно использовать файл-ковер. И повесить гордую плашку “AI-ready” в документацию своего API.

Конец статьи и тот самый полный пример ковра с тремя сценариями и несколькими шагами в каждом (ссылка на скачивание).

Осторожно, много кода
arazzo: '1.0.1'
info:
  title: Файл с тремя сценариями сразу
  version: '0.12.0'

sourceDescriptions:
  - name: cbtApi
    type: openapi
    #url: ./oas.yaml   # локальная спецификация рядом с тестом-кейсом
    url: https://%адрес_со_спекой%/redocusaurus/ru_crossborder.yaml

workflows:
  - workflowId: credit
    summary: Линейный сценарий без ветвлений; externalTransferId берём из Date заголовка шага /convert
    x-security:
      - schemeName: basicAuth
        values:
          username: %тестовый логин%
          password: %тестовый пароль%
    steps:
      # 1) /convert (optional)
      - stepId: call-convert
        description: POST /convert (optional)
        operationId: cbtApi.convert
        requestBody:
          payload:
            amount: 1000
            currencyFrom: USD
            currencyTo: UZS
            direction: TO_UZ
        successCriteria:
          - condition: $statusCode == 200 || $statusCode == 400 || $statusCode == 401 || $statusCode == 403
        outputs:
          amountFrom:   $response.body#/amountFrom
          currencyFrom: $response.body#/currencyFrom
          amountTo:     $response.body#/amountTo
          currencyTo:   $response.body#/currencyTo
          run_id:            $response.header.Date   # ← внешний id для следующих шагов
        onFailure:
          - name: continueToCardListEvenIfConvertFails
            type: goto
            stepId: card-list

      # 2) /card_list (optional)
      - stepId: card-list
        description: POST /card_list (by phone, optional)
        operationId: cbtApi.getReceiverCardsByPhone 
        requestBody:
          payload:
            phone: "998900000101"
        successCriteria:
          - condition: $statusCode == 200 || $statusCode == 400 || $statusCode == 401 || $statusCode == 403
        outputs:
          receiver_token: $response.body#/0/token #первый элемент массива
        onFailure:
          - name: continueToCheckCreditEvenIfCardListFails
            type: goto
            stepId: check-credit

      # 3) /check_credit
      - stepId: check-credit
        description: POST /check_credit (TOKEN из шага 2; externalTransferId на основе ответа шага 1)
        operationId: cbtApi.checkCredit
        requestBody:
          payload:
            externalTransferId:  $steps.call-convert.outputs.run_id
            senderAmount:              $steps.call-convert.outputs.amountFrom
            senderCurrencyCode:        $steps.call-convert.outputs.currencyFrom
            receiverCurrencyCode:          $steps.call-convert.outputs.currencyTo
            identificationType:  TOKEN
            identificationValue: $steps.card-list.outputs.receiver_token
            senderCountry:       KR
            sender:
              personFullName: "Leo Messi"
        successCriteria:
          - condition: $statusCode == 200
        outputs:
            ext_id: $response.body#/externalTransferId
        onFailure:
          - name: continueToConfirmEvenIfCheckFails
            type: goto
            stepId: confirm-credit

      # 4) /confirm_credit
      - stepId: confirm-credit
        description: POST /confirm_credit (externalTransferId из шага 3)
        operationId: cbtApi.confirmCredit
        requestBody:
          payload:
            externalTransferId: $steps.check-credit.outputs.ext_id
        successCriteria:
          - condition: $statusCode == 200
        onFailure:
          - name: continueToStatusEvenIfConfirmFails
            type: goto
            stepId: transfer-status

      # 5) /status (optional)
      - stepId: transfer-status
        description: POST /status (externalTransferId из шага 3)
        operationId: cbtApi.transferStatus
        requestBody:
          payload:
            externalTransferId: $steps.check-credit.outputs.ext_id
        successCriteria:
          - condition: $statusCode == 200
          
  - workflowId: debit
    summary: Линейный сценарий без ветвлений; externalTransferId берём из Date заголовка шага /convert
    parameters:
      - name: Authorization
        in: header
        value: Basic %тестовые креды%

    steps:
      # 1) /convert (optional)
      - stepId: call-convert
        description: POST /convert (optional)
        operationId: cbtApi.convert
        requestBody:
          payload:
            amount: 1000000
            currencyFrom: UZS
            currencyTo: USD
            direction: FROM_UZ
        successCriteria:
          - condition: $statusCode == 200 || $statusCode == 400 || $statusCode == 401 || $statusCode == 403
        outputs:
          amountFrom:   $response.body#/amountFrom
          currencyFrom: $response.body#/currencyFrom
          amountTo:     $response.body#/amountTo
          currencyTo:   $response.body#/currencyTo
          run_id:            $response.header.Date   # ← внешний id для следующих шагов
        onFailure:
          - name: continueToCardListEvenIfConvertFails
            type: goto
            stepId: card-list
      # 2) /check_debit
      - stepId: check_debit
        description: POST /check_debit (by token)
        operationId: cbtApi.registerDebit  # при необходимости замените на ваш operationId
        requestBody:
          payload:
            externalTransferId:  $steps.call-convert.outputs.run_id
            senderAmount:              $steps.call-convert.outputs.amountFrom
            senderCurrencyCode:        $steps.call-convert.outputs.currencyFrom
            receiverCurrencyCode:          $steps.call-convert.outputs.currencyTo
            senderCardToken: vlKeomyUYGh8ktD+1hvI68kuApMKq1s8uyfgvHA=
            sender:
              personFirstName: Leo
              personLastName: Messi
              birthday: 1991-11-11
              birthPlace: Macondo
              residencyCode: 1
              document: 
                identityDocumentCode: 4
                identityDocumentSeries: 4013
                identityDocumentNumber:	844659
                identityDocumentIssuer: InterNational Passports
                identityDocumentIssueDate: 2011-12-12
                pinfl: 30101800050014
            receiver: 
                personFullName: Donald Duck
            receiverAccount: 553609******2598
            receiverCountry: KR
            phone: 998507583221
        successCriteria:
          - condition: $statusCode == 200 || $statusCode == 400 || $statusCode == 401 || $statusCode == 403
        outputs:
            ext_id: $response.body#/externalTransferId
        onFailure:
          - name: continueToConfirmEvenIfCheckFails
            type: goto
            stepId: confirm-debit

      # 3) /resend_otp
      - stepId: resend-otp
        description: POST /resend_otp (externalTransferId из шага 2)
        operationId: cbtApi.resendOTP
        requestBody:
          payload:
            externalTransferId: $steps.check_debit.outputs.ext_id
        successCriteria:
          - condition: $statusCode == 200 || $statusCode == 400 || $statusCode == 401 || $statusCode == 403
        onFailure:
          - name: continueToStatusEvenIfConfirmFails
            type: goto
            stepId: transfer-status
      # 4) /confirm_debit (optional)
      - stepId: confirm-debit
        description: POST /confirm_debit (externalTransferId из шага 2)
        operationId: cbtApi.confirmDebit
        requestBody:
          payload:
            externalTransferId:  $steps.check_debit.outputs.ext_id
            code: 112233
        successCriteria:
          - condition: $statusCode == 200 || $statusCode == 400 || $statusCode == 401 || $statusCode == 403
        onFailure:
          - name: continueToStatusEvenIfConfirmFails
            type: goto
            stepId: transfer-status

      # 5) /status (optional)
      - stepId: transfer-status
        description: POST /status (externalTransferId из шага 2)
        operationId: cbtApi.transferStatus
        requestBody:
          payload:
            externalTransferId: $steps.check_debit.outputs.ext_id
        successCriteria:
          - condition: $statusCode == 200 || $statusCode == 400 || $statusCode == 401 || $statusCode == 403

      # 6) /cancel_debit
      - stepId: cancel-debit
        description: POST /cancel_debit (externalTransferId из шага 2)
        operationId: cbtApi.cancelDebit
        requestBody:
          payload:
            externalTransferId:  $steps.check_debit.outputs.ext_id
        successCriteria:
          - condition: $statusCode == 200 || $statusCode == 400 || $statusCode == 401 || $statusCode == 403
      # 7) /status (optional)
      - stepId: re-transfer-status
        description: POST /status (externalTransferId из шага 2)
        operationId: cbtApi.transferStatus
        requestBody:
          payload:
            externalTransferId:  $steps.check_debit.outputs.ext_id
        successCriteria:
          - condition: $statusCode == 200 || $statusCode == 400 || $statusCode == 401 || $statusCode == 403
          
  - workflowId: accInfo
    summary: Последовательный вызов /transfer_list, /closing_balance, /account/operations
    parameters:
      - name: Authorization
        in: header
        value: Basic %тестовые креды%

    steps:
      - stepId: transfer-list
        description: POST /transfer_list
        operationId: cbtApi.transferList
        requestBody:
          payload:
            createTime:
              from: "2026-02-27T09:55:30.250"
              to: "2026-03-27T09:55:30.250"
            page: 1
            limit: 25
        successCriteria:
          - context: $statusCode
            type: regex
            condition: "^(200|400|401|403)$"

      - stepId: closing-balance
        description: GET /account/closing_balance
        operationId: cbtApi.getClosingBalance
        parameters:
          - name: accountNumber
            in: query
            value: "29126840100001190019"
        successCriteria:
          - context: $statusCode
            type: regex
            condition: "^(200|400|401|500|504)$"

      - stepId: account-operations
        description: GET /account/operations
        operationId: cbtApi.getAccountOperations
        parameters:
          - name: accountNumber
            in: query
            value: "29126840100001190019"
          - name: startDate
            in: query
            value: "2026-01-20"
          - name: endDate
            in: query
            value: "2026-03-21"
        successCriteria:
          - context: $statusCode
            type: regex
            condition: "^(200|400|401|500|504)$"