Перейти до вмісту

Каскадна маршрутизація

Каскадна маршрутизація дозволяє визначити кілька upstream цілей для одного запиту. MirApi спробує їх послідовно (Priority) або паралельно (Race) і поверне першу успішну відповідь.

Каскадні маршрути налаштовуються у вашому дашборді та активуються заголовком X-Route-Key.

Налаштування маршруту у дашборді

Section titled “Налаштування маршруту у дашборді”
  1. Відкрийте Routes → Створити маршрут
  2. Вкажіть ім’я (наприклад, ai-providers)
  3. Додайте цілі — список повних URL через кому:
    https://api.openai.com/v1/chat/completions,
    https://api.anthropic.com/v1/messages,
    https://api.groq.com/openai/v1/chat/completions
  4. Оберіть стратегію: Priority або Race
  5. Задайте тайм-аут на ціль (мілісекунди)
  6. Опційно налаштуйте body mapping та витягнення відповіді

Дашборд списку каскадних маршрутів

Terminal window
curl -X POST https://proxy.mirapi.io/ \
-H "X-MirApi-Key: $MIRAPI_KEY" \
-H "X-Route-Key: ai-providers" \
-H "X-Identity-Key: Bearer $OPENAI_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Привіт"}]}'

Priority (Послідовний failover)

Section titled “Priority (Послідовний failover)”

Цілі пробуються по порядку. Наступна ціль пробується лише якщо попередня провалилась (5xx, тайм-аут або помилка з’єднання).

Ціль 1: https://api.openai.com/... → збій (503)
Ціль 2: https://api.anthropic.com/... → збій (тайм-аут)
Ціль 3: https://api.groq.com/... → успіх ✓
Відповідь: X-Rescued: cascade_fallback

Найкраще для:

  • Економії коштів (лише один API-виклик у happy path)
  • Платіжних API де потрібний детермінований порядок
  • Будь-яких сценаріїв де важливий пріоритет
Terminal window
# AI провайдер — спочатку OpenAI, потім Anthropic, потім Groq
curl -X POST https://proxy.mirapi.io/ \
-H "X-MirApi-Key: $MIRAPI_KEY" \
-H "X-Route-Key: ai-priority" \
-H "X-Identity-Key: Bearer $OPENAI_KEY" \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "Що таке Go?"}]}'

Всі цілі викликаються одночасно. Перша успішна відповідь перемагає; решта скасовуються через context cancellation.

Ціль 1: https://api.openai.com/... → відповів за 1200ms ✗ (програв)
Ціль 2: https://api.anthropic.com/... → відповів за 890ms ✗ (програв)
Ціль 3: https://api.groq.com/... → відповів за 89ms ✓ (переміг)

Найкраще для:

  • Ультранизьких затримок де час відповіді критичний
  • Read-only запитів (курси валют, пошук, ембеддинги)
  • Коли всі провайдери повертають еквівалентні відповіді
Terminal window
curl -X POST https://proxy.mirapi.io/ \
-H "X-MirApi-Key: $MIRAPI_KEY" \
-H "X-Route-Key: ai-race" \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "Швидке питання"}]}'

Комбінування з іншими функціями

Section titled “Комбінування з іншими функціями”
Terminal window
curl -X POST https://proxy.mirapi.io/ \
-H "X-MirApi-Key: $MIRAPI_KEY" \
-H "X-Route-Key: payment-cascade" \
-H "X-Retry-Count: 2" \
-H "X-Retry-Delay: 300ms" \
-H "X-Proxy-Timeout: 10s" \
-H "X-Attempt-Timeout: 2s" \
-d '{"amount": 9900, "currency": "usd"}'
# Ціль 1 повторюється до 2 разів перед переходом до Цілі 2
Terminal window
curl -X POST https://proxy.mirapi.io/ \
-H "X-MirApi-Key: $MIRAPI_KEY" \
-H "X-Route-Key: payment-cascade" \
-H "X-Webhook-Callback: https://your-app.com/payments/done" \
-H "X-Proxy-Idempotency-Key: order_789_pay_1" \
-d '{"amount": 9900, "currency": "usd"}'
# Повертає 202 негайно
# Воркер виконує повний каскад у фоні
# POST з першою успішною відповіддю на callback
Terminal window
curl https://proxy.mirapi.io/ \
-H "X-MirApi-Key: $MIRAPI_KEY" \
-H "X-Route-Key: exchange-rates-cascade" \
-H "X-Smart-Cache: 300s"
# Пробує всі цілі маршруту
# Всі провалились → кеш → X-Rescued: cache

Персоналізована авторизація для каскадних маршрутів (Route Target Auth)

Section titled “Персоналізована авторизація для каскадних маршрутів (Route Target Auth)”

У каскадній маршрутизації (Cascade Routing) додано можливість налаштовувати унікальну авторизацію для кожного окремого сервісу (Upstream URL) у межах одного маршруту.

Раніш при перенаправленні запиту по каскаду (наприклад, Stripe ➔ Adyen ➔ Custom Bank) проксі намагався використовувати один спільний заголовок авторизації для всіх сервісів. Це обмежувало використання каскадів, оскільки різні платіжні шлюзи та API вимагають різних форматів автентифікації.

Тепер ви можете безпечно зв’язати кожен сервіс у каскаді з його власними обліковими даними з бази даних, вказавши для кожного індивідуальне ім’я HTTP-заголовка та шаблон підстановки ключа.


🛠️ Як це налаштувати (Приклад)

Section titled “🛠️ Як це налаштувати (Приклад)”

Уявімо каскадний маршрут payment-cascade. Якщо основний шлюз (Stripe) поверне помилку, запит буде автоматично перенаправлено на резервний шлюз (Custom Bank API). У кожного з них своя авторизація.

Приклад JSON-payload запиту для створення такого маршруту:

{
"route_key": "payment-cascade",
"strategy": "priority",
"targets": [
{
"priority": 1,
"target_url": "https://api.stripe.com/v1/charges",
"timeout_ms": 10000,
"credential_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"auth_header": "Authorization",
"auth_template": "Bearer {{secret}}"
},
{
"priority": 2,
"target_url": "https://api.custombank.com/v1/payments",
"timeout_ms": 15000,
"credential_id": "c3098522-8356-4c91-9e2c-29ad9c42c943",
"auth_header": "X-Bank-Token",
"auth_template": "{{secret}}"
}
]
}

🔍 Деталі конфігурації параметрів:

Section titled “🔍 Деталі конфігурації параметрів:”
  • credential_id (опціонально) — ID зашифрованого ключа авторизації у вашому сейфі Credentials. Якщо поле не вказано або залишене порожнім (null / ""), запит до цього таргету буде надіслано без авторизації (або з оригінальними заголовками клієнта).
  • auth_header (за замовчуванням: Authorization) — назва HTTP-заголовка, у який проксі вставить ключ під час проксіювання.
  • auth_template (за замовчуванням: Bearer {{secret}}) — шаблон форматування. Проксі автоматично розшифрує токен вашим X-Proxy-Master-Key і замінить плейсхолдер {{secret}} на реальний розшифрований ключ.

Усі облікові дані (credential_id) зберігаються в базі даних у зашифрованому вигляді (алгоритм AES-GCM). Розшифрування відбувається безпосередньо в оперативній пам’яті проксі в момент виконання запиту за допомогою ключа X-Proxy-Master-Key, який клієнт передає в заголовках запиту. Ключі розшифрування ніколи не зберігаються на наших дисках.


Маппінг тіла запиту цілі

Section titled “Маппінг тіла запиту цілі”

Маппінг тіла запиту розбирає JSON-тіло вхідного запиту, перейменовує ключі, обходить вкладені поля та масиви, і формує запит перед відправкою на upstream. Перейменовані ключі-джерела автоматично видаляються.

Формат правила маппінгу — розділений комами список правил: шлях_джерела=>шлях_цілі або шлях_джерела=>шлях_цілі(шаблон).

Підтримуваний синтаксис шляху

Section titled “Підтримуваний синтаксис шляху”
МожливістьПрикладОпис
Плоске перейменуванняamount=>sumПерейменовує кореневий ключ
Вкладений об’єкт (dot-notation)data.order=>data.order_idПерейменовує order на order_id всередині об’єкта data
Перебір масиву з []data.orders[].order=>data.orders[].order_idПерейменовує order на order_id у кожному елементі масиву orders
Перебір масиву без []data.orders.order=>data.orders.order_idТе саме — дужки необов’язкові, визначаються автоматично
Шаблон значення {value}order=>order_text(Order ID: #{value})Форматує значення поля у рядковий шаблон
Міжпольовий шаблон {field_name}status=>status(Order {order} status is {value})Інтерполює значення інших полів з поточного JSON-контексту
  1. Ваш клієнт надсилає запит до проксі-шлюзу з оригінальною структурою JSON.
  2. Шлюз перехоплює тіло запиту і перед відправкою на upstream перейменовує та трансформує ключі згідно з body_map цієї цілі.
  3. Вкладені шляхи та елементи масивів обходяться автоматично. Решта ключів залишаються без змін.

A. Плоске перейменування:

// body_map: "amount=>sum, currency=>cur"
// Вхід: {"amount": 100, "currency": "USD"}
// Вихід: {"sum": 100, "cur": "USD"}

B. Вкладені об’єкти:

// body_map: "data.order=>data.order_id"
// Вхід: {"action": "get_status", "data": {"order": 353454876}}
// Вихід: {"action": "get_status", "data": {"order_id": 353454876}}

C. Перебір масиву:

// body_map: "data.orders[].order=>data.orders[].order_id"
// або "data.orders.order=>data.orders.order_id"
// Вхід: {"data": {"orders": [{"order": 101}, {"order": 102}]}}
// Вихід: {"data": {"orders": [{"order_id": 101}, {"order_id": 102}]}}

D. Динамічний шаблон значення {value}:

Форматує значення поля у рядковий шаблон. {value} — пластина для значення джерельного поля:

// body_map: "order=>order_text(Order ID: #{value})"
// Вхід: {"order": 101}
// Вихід: {"order_text": "Order ID: #101"}

E. Міжпольовий шаблон {field_name}:

Інтерполює значення інших полів з того ж JSON-контексту. Поле шукається спочатку в поточному об’єкті, а потім у глобальному корені:

// body_map: "status=>status(Order {order} status is {value})"
// Вхід: {"order": 101, "status": "success"}
// Вихід: {"order": 101, "status": "Order 101 status is success"}

F. Посилання на сусідні поля у масивах:

Доступає до сусідніх ключів всередині кожного окремого елементу масиву під час ітерації:

// body_map: "data.orders.status=>data.orders.status_text(Order {order} is {value})"
// Вхід: {"data": {"orders": [{"order": 101, "status": "success"}, {"order": 102, "status": "fail"}]}}
// Вихід: {"data": {"orders": [{"order": 101, "status_text": "Order 101 is success"}, {"order": 102, "status_text": "Order 102 is fail"}]}}
---
## Власні умови відкату по тілу відповіді
За замовчуванням каскадна маршрутизація переходить до наступної цілі лише тоді, коли поточний ендпоінт повертає статус `5xx`, виникає тайм-аут або помилка з'єднання.
Однак багато API (особливо платіжні шлюзи) повертають статус `200 OK` навіть тоді, коли транзакцію відхилено або вона завершилася помилкою. Для підтримки маршрутизації на основі бізнес-логіки збоїв, кожна ціль маршруту може визначати власну умову відкату у дашборді чи базі даних за допомогою двох параметрів:
- `fallback_field`: вираз JSONPath, що вказує на поле в JSON відповіді для перевірки. Префікс `$.` є **необов'язковим** — додається автоматично, якщо відсутній. Підтримуються вкладені об'єкти (dot-notation) та елементи масиву (нотація `[]`).
- `fallback_value`: рядкове значення для співпадіння (регістронезалежна перевірка на входження підрядка).
### Підтримуваний синтаксис `fallback_field`
| Приклад | Еквівалентно | Опис |
|---|---|---|
| `status` | `$.status` | Поле на верхньому рівні |
| `error.code` | `$.error.code` | Поле вкладеного об'єкта |
| `errors[].code` | `$.errors[*].code` | Поле всередині будь-якого елемента масиву |
### Як це працює:
1. Цільовий ендпоінт повертає успішний код статусу (менше `500`).
2. Шлюз зчитує тіло відповіді та витягує значення поля за вказаним шляхом JSONPath у `fallback_field`.
3. Якщо витягнуте значення містить `fallback_value` як підрядок, шлюз розглядає цю відповідь як **помилку**.
4. Шлюз відкидає цю відповідь, логує спрацювання умови та негайно переходить до наступної цілі у ланцюжку каскаду.
![Дашборд налаштування умов відкату](../../assets/cascade_1.png)
### Приклад: Відкат при відхиленні платежу
Upstream платіжного процесора повернув статус `200 OK`, але платіж відхилено:
```json
{
"transaction_id": "tx_abc123",
"status": "declined",
"failure_reason": "insufficient_funds"
}

Налаштувавши ціль параметрами:

  • Fallback Field: status (або $.status)
  • Fallback Value: declined

Шлюз перехопить 200 OK, виявить, що $.status дорівнює "declined", трактуватиме це як збій і надішле запит на оплату до наступного резервного банку у вашому каскаді.

Приклад: Відкат за кодом помилки в масиві

Section titled “Приклад: Відкат за кодом помилки в масиві”

Upstream повертає помилки як масив:

{
"errors": [{"code": "card_declined", "message": "Недостатньо коштів"}]
}

Налаштувавши ціль параметрами:

  • Fallback Field: errors[].code
  • Fallback Value: card_declined

Шлюз перевіряє поле code кожного елемента масиву errors і тригерить відкат, якщо будь-яке з них співпадає.


Шлюз валідує всі цільові URL щодо власних IP-адрес та хостнеймів. Будь-яка цільова URL що резолвиться до самого проксі блокується з 400 Bad Request.

Коли використовується не первинна ціль:

HTTP/1.1 200 OK
X-Rescued: cascade_fallback

Якщо первинна ціль (перша у списку) успішна — X-Rescued не додається.