Błędy
Koperta błędu, kody statusu HTTP i pełny katalog kodów zwracanych przez zewnętrzne API.
Koperta
Każdy błąd, bez wyjątku, ma ten sam kształt:
{
"error": {
"code": "API_KEY_INSUFFICIENT_SCOPE",
"message": "Klucz API nie ma wymaganych uprawnień",
"details": {
"requiredScope": "tasks.write",
"availableScopes": ["tasks.read", "users.read"]
}
}
}| Pole | Co to jest |
|---|---|
code | Stabilny identyfikator maszynowy. Rozgałęziaj się na nim. |
message | Tekst dla człowieka. Tłumaczony na język wywołującego i może się zmienić bez zapowiedzi. |
details | Opcjonalny kontekst. Kształt zależy od kodu. |
Nigdy nie parsuj `message`
message jest lokalizowany — ta sama sytuacja da inny tekst po polsku i po angielsku, a treść może się zmienić w każdym wydaniu. Jedyną częścią kontraktu jest code.
VALIDATION_ERROR
Wyjątek od reguły „details to obiekt": przy VALIDATION_ERROR details jest tablicą obiektów { field, message }, po jednym na każde pole, które nie przeszło walidacji.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Walidacja nie powiodła się",
"details": [
{ "field": "title", "message": "Tytuł jest wymagany" },
{ "field": "priority", "message": "Nieprawidłowa wartość. Oczekiwano 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT'" }
]
}
}field jest ścieżką z kropkami do miejsca w ciele żądania, np. context.variables.session_id.
Kody statusu HTTP
| Status | Znaczenie |
|---|---|
200 OK | Żądanie się powiodło |
201 Created | Zasób powstał |
202 Accepted | Przyjęte do przetworzenia asynchronicznego (zdarzenie testowe, ponowne dostarczenie) |
400 Bad Request | Żądanie jest źle uformowane albo odrzucone przez usługę niżej |
401 Unauthorized | Brak klucza, klucz błędny, wygasły albo unieważniony |
403 Forbidden | Klucz poprawny, ale brakuje zakresu (albo polityka telefonii zabrania) |
404 Not Found | Zasób nie istnieje w Twoim workspace |
409 Conflict | Zasób zmienił się pod żądaniem albo koliduje z istniejącym |
410 Gone | Zasób istniał, ale jego treść jest już niedostępna |
422 Unprocessable Entity | Walidacja albo reguła biznesowa |
429 Too Many Requests | Limit na klucz albo zajęta linia; patrz nagłówek Retry-After |
500 Internal Server Error | Błąd po naszej stronie |
502 Bad Gateway | Usługa, od której zależymy, padła lub była nieosiągalna |
503 Service Unavailable | Nie udało się zweryfikować klucza z powodu awarii backendu. Ponawialne — poświadczenia są w porządku |
404 na zasobie z innego workspace wygląda identycznie jak 404 na identyfikatorze, który nie istnieje. To celowe: API nie jest wyrocznią istnienia.
Katalog kodów
401 — uwierzytelnianie
| Kod | Znaczenie |
|---|---|
API_KEY_INVALID | Format klucza jest zły albo klucza nie ma |
API_KEY_EXPIRED | Klucz minął datę ważności |
API_KEY_REVOKED | Klucz został unieważniony |
Poza rozróżnieniem „wygasł" / „unieważniony" 401 jest celowo nieinformacyjne — żeby nie dało się nim sondować, które klucze istnieją.
403 — uprawnienia
| Kod | Znaczenie |
|---|---|
API_KEY_INSUFFICIENT_SCOPE | Klucz nie ma wymaganego zakresu; details niesie requiredScope i availableScopes |
TELEPHONY_COUNTRY_NOT_ALLOWED | Kraj docelowy jest poza tym, do czego ten workspace może dzwonić |
BOT_WORKFLOW_SYSTEM_PROMPT_OVERRIDE_STAFF_ONLY | Węzeł przepływu włącza overrideSystemPrompt, które może ustawić tylko zespół Mediflo; details.nodeIds wskazuje węzły |
429 — limity
| Kod | Znaczenie |
|---|---|
RATE_LIMITED | Przekroczony limit na klucz; details.retryAfterSeconds i nagłówek Retry-After |
BOT_SESSION_LINE_BUSY | Numer jest już wybierany albo bot osiągnął limit równoległych rozmów. Ponów z tym samym idempotencyKey; details.dispatchId to identyfikator, który zwróciłaby powtórka |
Szczegóły limitów: Limity zapytań.
400 — żądanie odrzucone
| Kod | Znaczenie |
|---|---|
INVALID_REQUEST_DATA | Ciało żądania nie przeszło walidacji schematu (trasy botów, przepływów, lokalizacji, wiedzy) |
BOT_VALIDATION_ERROR | Dane bota są niespójne (np. lokalizacja spoza workspace) |
BOT_CONFIG_INCOMPLETE | Publikacja bez wymaganych pól; details.missingFields, a details.hint mówi, skąd wziąć brakujące |
BOT_CONFIG_SCHEMA_TOO_COMPLEX | remarksOutputSchemaJson przekracza dopuszczalną złożoność |
BOT_CONFIG_PROCEDURES_INVALID | Procedury przypięte do konfiguracji nie kompilują się |
BOT_ENTRY_PROCEDURE_INVALID | Wskazana procedura wejściowa nie nadaje się na wejście |
BOT_PROCEDURE_VALIDATION_FAILED | Procedura nie przeszła walidacji |
BOT_PROCEDURE_REORDER_INCOMPLETE | Żądanie zmiany kolejności nie wymienia wszystkich procedur |
BOT_WORKFLOW_VALIDATION_FAILED | Definicja przepływu jest nieprawidłowa; details niesie listę problemów |
BOT_WORKFLOW_SKILL_REF_NOT_SERVABLE | Przepływ odwołuje się do umiejętności, której nie da się podać |
PHONE_NUMBER_NOT_CONNECTED_TO_TRUNK | Numer nie jest wpięty w trunk, więc nie da się podpiąć bota |
TELEPHONY_INVALID_NUMBER | Dyspozytor uznał numer za niewybieralny |
RESERVED_CONTEXT_VARIABLE | Nazwa w context.variables jest zarezerwowana. W praktyce walidacja łapie to wcześniej jako 422 VALIDATION_ERROR |
CONTACT_REQUEST_INVALID_CALL_REFERENCE | telephonyCallId nie istnieje w Twoim workspace |
CONTACT_REQUEST_INVALID_DISPATCH_REFERENCE | dispatcherDispatchId nie istnieje w Twoim workspace |
404 — nie znaleziono
| Kod | Znaczenie |
|---|---|
TASK_NOT_FOUND | Zadanie nie istnieje (albo zostało usunięte — patrz przegląd) |
BOT_NOT_FOUND | Bot nie istnieje w Twoim workspace |
BOT_CONFIG_SNAPSHOT_NOT_FOUND | Migawka konfiguracji nie istnieje |
BOT_KNOWLEDGE_SOURCE_NOT_FOUND | Źródło wiedzy nie istnieje |
BOT_PROCEDURE_NOT_FOUND | Procedura nie istnieje |
BOT_WORKFLOW_NOT_FOUND | Przepływ nie istnieje |
BOT_SESSION_NOT_FOUND | Sesja bota nie istnieje |
LOCATION_NOT_FOUND | Lokalizacja nie istnieje |
PHONE_NUMBER_NOT_FOUND | Numer nie istnieje |
TICKET_NOT_FOUND | Zgłoszenie nie istnieje |
WEBHOOK_ENDPOINT_NOT_FOUND | Punkt końcowy webhooka nie istnieje albo został usunięty |
WEBHOOK_DELIVERY_NOT_FOUND | Dostarczenie nie istnieje |
409 — konflikt
| Kod | Znaczenie |
|---|---|
BOT_CONFIG_CONFLICT | expectedUpdatedAt nie zgadza się z aktualnym stanem konfiguracji |
BOT_CONFIG_PUBLISH_FAILED | Publikacja weszła w konflikt z równoległą zmianą |
BOT_WORKFLOW_VERSION_CONFLICT | expectedVersion nie zgadza się z aktualną wersją przepływu |
BOT_WORKFLOW_NAME_CONFLICT | Przepływ o tej nazwie już istnieje |
BOT_WORKFLOW_ENTRY_PROCEDURE_CONFLICT | Przepływ i procedura wejściowa wykluczają się |
BOT_PROCEDURE_NAME_CONFLICT | Procedura o tej nazwie już istnieje |
BOT_PROCEDURE_ENTRY_IN_USE | Procedura wejściowa jest w użyciu i nie da się jej usunąć |
BOT_PROCEDURE_LIMIT_EXCEEDED | Bot ma już maksymalną liczbę procedur |
LOCATION_CODE_EXISTS | Lokalizacja o tym kodzie już istnieje |
PHONE_NUMBER_HAS_CUSTOM_FLOW | Numer ma własny przepływ IVR; podpięcie bota nadpisałoby go |
PHONE_NUMBER_MANAGED_BY_MEDIFLO | Wybrany numer to przekierowany numer placówki, którym zarządza Mediflo — nie może być numerem wychodzącym bota (POST /bots, PATCH /bots/{botId}) |
WEBHOOK_DELIVERY_NOT_REDELIVERABLE | Dostarczenie wciąż jest PENDING |
BOT_SESSION_CREATE_FAILED | Dyspozytor zgłosił identyfikator jako zajęty, ale sesji nie dało się jeszcze odczytać. Nic nowego nie zadzwoniło — ponów z tym samym idempotencyKey |
410 — zasób wygasł
| Kod | Znaczenie |
|---|---|
WEBHOOK_DELIVERY_PAYLOAD_UNAVAILABLE | Treść dostarczenia nie jest już dostępna do odczytu |
422 — walidacja i reguły biznesowe
| Kod | Znaczenie |
|---|---|
VALIDATION_ERROR | Ciało lub parametry nie przeszły walidacji; details to tablica { field, message } |
OUTSIDE_CALL_WINDOW | Poza oknem dzwonienia; details niesie timezone, window { startHour, endHour } i localTime |
IDEMPOTENCY_KEY_REUSED | Ten sam klucz idempotencji, inny bot lub numer; details.dispatchId to sesja, która ten klucz już trzyma |
BOT_INACTIVE | Bot jest wyłączony w Portalu (wyłącznik operatora) |
BOT_NO_OUTBOUND_NUMBER | Bot nie ma przypisanego numeru wychodzącego |
BOT_NOT_PUBLISHED | Bot nie ma opublikowanej konfiguracji |
TICKET_INVALID_REPORTER | reportedByUserId nie wskazuje aktywnego użytkownika Twojego workspace |
TICKET_UNKNOWN_BOT_SESSION | Któryś z botSessionIds nie istnieje; zgłoszenie nie powstało |
WEBHOOK_ENDPOINT_INVALID_URL | URL nie jest https albo rozwiązuje się na adres prywatny |
WEBHOOK_ENDPOINT_LIMIT_REACHED | Osiągnięty limit punktów końcowych w workspace |
WEBHOOK_ENDPOINT_DISABLED | Punkt końcowy jest wyłączony; włącz go przed testem |
BOT_SESSION_CREATE_FAILED | Odmowa dyspozytora do naprawienia przez operatora (źle skonfigurowana pojemność, nieużywalny trunk); status z góry jest zachowany, a details niesie powód |
502 — usługa niżej
| Kod | Znaczenie |
|---|---|
BOT_SESSION_CREATE_FAILED | Dyspozytor padł albo był nieosiągalny. Żądanie mogło zostać przyjęte — ponów później z tym samym idempotencyKey |
WEBHOOK_ENDPOINT_TEST_FAILED | Nie udało się zakolejkować zdarzenia testowego |
503 — chwilowa niedostępność
| Kod | Znaczenie |
|---|---|
EXTERNAL_API_UNAVAILABLE | Nie udało nam się zweryfikować klucza z powodu awarii backendu |
`503` nigdy nie oznacza problemu z kluczem
Nie rotuj poświadczeń w reakcji na 503. Ponów z wykładniczym odczekaniem.
500 — błąd po naszej stronie
Wszystkie znaczą to samo — operacja padła u nas — ale kod mówi, która. Ponów z wykładniczym odczekaniem; jeśli wraca, zgłoś to.
BOTS_FETCH_FAILED, BOT_FETCH_FAILED, BOT_CREATE_FAILED, BOT_UPDATE_FAILED,
BOT_CONFIG_FETCH_FAILED, BOT_CONFIG_UPDATE_FAILED, BOT_CONFIG_PUBLISH_FAILED, BOT_CONFIG_ROLLBACK_FAILED, BOT_CONFIG_SNAPSHOTS_FETCH_FAILED,
BOT_PROCEDURES_FETCH_FAILED, BOT_PROCEDURES_COMPILE_INVALID, BOT_PROCEDURE_FETCH_FAILED, BOT_PROCEDURE_CREATE_FAILED, BOT_PROCEDURE_UPDATE_FAILED, BOT_PROCEDURE_DELETE_FAILED, BOT_PROCEDURE_REORDER_FAILED,
BOT_WORKFLOWS_FETCH_FAILED, BOT_WORKFLOW_FETCH_FAILED, BOT_WORKFLOW_CREATE_FAILED, BOT_WORKFLOW_UPDATE_FAILED, BOT_WORKFLOW_DELETE_FAILED, BOT_WORKFLOW_ACTIVATE_FAILED, BOT_WORKFLOW_DEACTIVATE_FAILED, BOT_WORKFLOW_VALIDATION_REQUEST_FAILED,
BOT_SESSION_CREATE_FAILED, BOT_SESSION_TRANSCRIPT_FETCH_FAILED, EXTERNAL_API_BOT_SESSIONS_FETCH_FAILED,
LOCATIONS_FETCH_FAILED, LOCATION_CREATE_FAILED, LOCATION_UPDATE_FAILED,
PHONE_NUMBERS_FETCH_FAILED, PHONE_NUMBER_ATTACH_BOT_FAILED,
MODEL_CATALOG_FETCH_FAILED, WEBHOOK_EVENT_TYPES_FETCH_FAILED,
TASKS_FETCH_FAILED, TASK_CREATE_FAILED, TASK_UPDATE_FAILED, TASK_DELETE_FAILED,
USERS_FETCH_FAILED, TAGS_FETCH_FAILED, EXTERNAL_API_CALLS_FETCH_FAILED,
CONTACT_REQUESTS_FETCH_FAILED, CONTACT_REQUEST_CREATE_FAILED,
TICKET_CREATE_FAILED, TICKET_UPDATE_FAILED, EXTERNAL_API_TICKETS_FETCH_FAILED,
WEBHOOK_ENDPOINTS_FETCH_FAILED, WEBHOOK_ENDPOINT_CREATE_FAILED, WEBHOOK_ENDPOINT_UPDATE_FAILED, WEBHOOK_ENDPOINT_DELETE_FAILED,
WEBHOOK_DELIVERIES_FETCH_FAILED, WEBHOOK_DELIVERY_REDELIVER_FAILED.
Obsługa błędów w kodzie
Ponawiaj 429 zawsze. 5xx ponawiaj tylko dla operacji bezpiecznych (GET, HEAD) albo takich, które sam zabezpieczyłeś kluczem idempotencji (np. POST /bot-sessions z idempotencyKey) — POST bez takiej gwarancji mógł się wykonać po stronie serwera, zanim dostałeś błąd, a ponowienie założy duplikat. Nie ponawiaj 4xx — one nie przejdą same z siebie.
async function medifloFetch(url, options = {}, { maxRetries = 3, idempotent = false } = {}) {
const method = (options.method ?? 'GET').toUpperCase()
const safeToRetry = idempotent || method === 'GET' || method === 'HEAD'
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options)
if (response.ok) return response.json()
const body = await response.json().catch(() => null)
const code = body?.error?.code
if (response.status === 429) {
const retryAfter = Number(response.headers.get('Retry-After') ?? 1)
await new Promise(r => setTimeout(r, retryAfter * 1000))
continue
}
if (response.status >= 500 && safeToRetry) {
await new Promise(r => setTimeout(r, 2 ** attempt * 1000))
continue
}
// 4xx — rozgałęź się na kodzie, nie ponawiaj.
switch (code) {
case 'API_KEY_EXPIRED':
case 'API_KEY_REVOKED':
throw new Error('Zrotuj klucz API')
case 'API_KEY_INSUFFICIENT_SCOPE':
throw new Error(`Brakuje zakresu: ${body.error.details.requiredScope}`)
case 'VALIDATION_ERROR':
throw new Error(
body.error.details.map(d => `${d.field}: ${d.message}`).join('; ')
)
default:
throw new Error(`${response.status} ${code ?? 'UNKNOWN'}`)
}
}
throw new Error('Wyczerpano ponowienia')
}Przykład z curl, który pokazuje samą kopertę:
curl -s -o /dev/null -w '%{http_code}\n' \
https://app.mediflo.ai/api/external/v1/bots \
-H "Authorization: Bearer mflo_live_..."