Alert API (Beta)
Beta. Alert API jest w fazie testów. Format zapytań, odpowiedzi i webhooków może się jeszcze zmienić. Prosimy nie traktować go jako ostatecznego i zgłaszać wszelkie uwagi.
Alert API dzwoni na jeden lub wiele numerów, odtwarza komunikat (syntezator mowy lub plik dźwiękowy), opcjonalnie zbiera potwierdzenie z klawiatury telefonu (DTMF), wykrywa automatyczną sekretarkę i raportuje wynik każdego połączenia.
W odróżnieniu od Voice API nie trzeba budować logiki połączenia z obiektów - wystarczy podać numery, komunikat i sposób potwierdzenia. Typowe zastosowania:
- alarm z potwierdzeniem - dzwonimy do wszystkich naraz, pierwsza osoba, która naciśnie
1, potwierdza alarm, a pozostałe połączenia są kończone, - lista eskalacyjna - dzwonimy po kolei, aż ktoś potwierdzi,
- powiadomienie - komunikat do odsłuchania, bez potwierdzania.
Zasady wspólne z pozostałymi API:
- autoryzacja tokenem JWT w nagłówku
Authorization: Bearer <token>(patrz Autentykacja), - każda odpowiedź zawiera pole
resulto wartościOKluberror; w przypadku błędu poleerrorzawiera jego opis, - numery telefonów w formacie E.164 bez znaku
+i bez00, np.48501000000.
Utworzenie alertu
POST https://api.vpbx.pl/api/v1/alerts
Alarm z potwierdzeniem - dzwonimy do dwóch osób jednocześnie, pierwsze naciśnięcie 1 potwierdza alarm:
curl --header "Content-Type: application/json" \
--header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAoMS0Iiwi" \
--request POST \
--data '{
"to": ["48600100200", "48600100201"],
"from": "48221234567",
"message": {
"tts": {
"text": "Alarm w serwerowni. Naciśnij 1 aby potwierdzić, 0 aby odrzucić.",
"lang": "pl-PL/Maja"
}
},
"dtmf": {"accept": "1", "reject": "0", "timeout": 10},
"confirmation": {
"accepted": {"tts": {"text": "Dziękujemy, alarm potwierdzony.", "lang": "pl-PL/Maja"}}
},
"amd": {"on_machine": "hangup"},
"webhook": "https://twoj.serwer.pl/alerty",
"reference": "incydent-4711"
}' \
https://api.vpbx.pl/api/v1/alerts
Lista eskalacyjna z nagranym komunikatem - dzwonimy po kolei, a gdy odbierze poczta głosowa, zostawiamy wiadomość:
{
"to": ["48600100200", "48600100201", "48600100202"],
"from": "48221234567",
"mode": "sequential",
"message": {"playfile": {"url": "https://twoj.serwer.pl/alarm.wav"}},
"dtmf": {},
"amd": {"on_machine": "leave_message"}
}
Powiadomienie na jeden numer, bez potwierdzania:
{
"to": "48600100200",
"from": "48221234567",
"message": {"tts": {"text": "Dziś o 22:00 planowana przerwa serwisowa.", "lang": "pl-PL/Maja"}},
"repeat": 2
}
Parametry zapytania
| Pole | Typ | Domyślnie | Opis |
|---|---|---|---|
| to | String lub Array of Strings | wymagane | Jeden numer, np. "48600100200", lub lista numerów, np. ["48600100200", "48600100201"], w formacie E.164 bez +. Duplikaty są usuwane. Maksymalnie 10 numerów |
| from | String | wymagane | Numer prezentowany odbiorcy (CLI), w tym samym formacie. Musi być numerem dozwolonym dla Twojego konta |
| mode | String | parallel | Sposób dzwonienia: parallel - do wszystkich jednocześnie (w ramach limitu jednoczesnych połączeń konta), sequential - po kolei |
| stop_on_accept | Boolean | true | Tylko z dtmf. Pierwsze potwierdzenie kończy alert: w trybie parallel pozostałe połączenia są rozłączane, w trybie sequential kolejne numery są pomijane. false - dzwonimy do wszystkich |
| message | Object | wymagane | Komunikat. Dokładnie jedno z: tts lub playfile |
| message.tts.text | String | Tekst do odczytania (maks. 3000 znaków) | |
| message.tts.lang | String | Głos, np. pl-PL/Maja - dostępne są wszystkie głosy z listy Voice API |
|
| message.playfile.url | String | Publicznie dostępny adres URL pliku WAV lub MP3 (http/https) | |
| repeat | Int | 2 | Ile razy odtworzyć komunikat (1-5) |
| dtmf | Object | brak | Zbieranie potwierdzenia z klawiatury. Pominięcie oznacza alert typu powiadomienie |
| dtmf.accept | String | 1 | Klawisz potwierdzenia: 0-9, * lub # |
| dtmf.reject | String | 0 | Klawisz odrzucenia: 0-9, * lub # |
| dtmf.timeout | Int | 10 | Czas oczekiwania na klawisz po ostatnim odtworzeniu komunikatu, w sekundach (1-60) |
| confirmation.accepted | Object | Komunikat (tts lub playfile) odtwarzany po potwierdzeniu, przed rozłączeniem. Wymaga dtmf |
|
| confirmation.rejected | Object | Komunikat odtwarzany po odrzuceniu, przed rozłączeniem. Wymaga dtmf |
|
| amd.enabled | Boolean | true | Wykrywanie automatycznej sekretarki / poczty głosowej |
| amd.on_machine | String | hangup | Co zrobić, gdy odbierze automat: hangup - rozłączyć, leave_message - poczekać na sygnał, odtworzyć komunikat raz i rozłączyć |
| ring_timeout | Int | 30 | Czas dzwonienia w sekundach (5-120) |
| webhook | String | Adres URL, na który wysyłane są wyniki. Bez niego wyniki można pobrać zapytaniem GET | |
| webhook_method | String | POST | Metoda HTTP webhooka: POST lub PUT |
| reference | String | Własny identyfikator (maks. 128 znaków), zwracany w odpowiedziach i webhookach |
Nieznane pola w zapytaniu powodują błąd 400.
Z włączonym wykrywaniem automatycznej sekretarki (amd) komunikat zaczyna się po rozpoznaniu, że odebrał człowiek - zwykle 1-3 sekundy po odebraniu połączenia.
Odpowiedź - alert przyjęty
Połączenia są kolejkowane i wykonywane w tle; odpowiedź wraca od razu.
{
"result": "OK",
"alert_id": "1f0c6a52-3a7e-4b8e-9d0a-5c1b2f7e8a90",
"status": "in_progress",
"mode": "parallel",
"confirmable": true,
"stop_on_accept": true,
"reference": "incydent-4711",
"created_at": "2026-11-05T10:00:00Z",
"calls": [
{"call_id": "8a2e4c1d-5b6f-4e7a-9c8d-1e2f3a4b5c6d", "to": "48600100200", "from": "48221234567", "status": "queued"},
{"call_id": "93b17f0e-2c3d-4a5b-8e9f-0a1b2c3d4e5f", "to": "48600100201", "from": "48221234567", "status": "queued"}
]
}
Odpowiedź - odrzucenie alertu
{
"result": "error",
"error": "to: \"+48600100200\" is not valid, use E.164 digits without \"+\" or \"00\": 3-15 digits starting with the country code, e.g. 48600100200"
}
Przy braku stawki lub środków odpowiedź zawiera dodatkowo listę numerów, których dotyczy błąd:
{
"result": "error",
"error": "insufficient credit",
"numbers": ["48600100201"]
}
Kody błędów HTTP
| Kod HTTP | Opis |
|---|---|
| 200 | Alert przyjęty, połączenia zakolejkowane |
| 400 | Nieprawidłowe zapytanie. Komunikat wskazuje pole, np. unknown field "numbers" lub repeat has an invalid type |
| 401 | Brak tokenu, token nieprawidłowy (invalid token) lub wygasły (token expired), nieznany użytkownik |
| 402 | Brak stawki dla numeru (no rate for destination) lub brak środków (insufficient credit); pole numbers zawiera numery, których dotyczy błąd |
| 403 | Numer from niedozwolony dla konta (caller id not allowed) lub konto zablokowane (account disabled) |
| 413 | Zbyt duże zapytanie |
| 429 | Przekroczony limit zapytań na sekundę (Too Many Requests. Exceeded limit of N CPS), nagłówek Retry-After: 1 |
| 503 | Usługa chwilowo niedostępna (service temporarily unavailable) - należy ponowić zapytanie później |
Środki sprawdzane są dla każdego numeru przed przyjęciem alertu oraz ponownie tuż przed wykonaniem każdego połączenia. Czas trwania połączenia jest ograniczony do kwoty dostępnej na koncie.
Status alertu
GET https://api.vpbx.pl/api/v1/alerts/[alert_id]
curl --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAoMS0Iiwi" \
--request GET \
https://api.vpbx.pl/api/v1/alerts/1f0c6a52-3a7e-4b8e-9d0a-5c1b2f7e8a90
Odpowiedź ma ten sam format co przy utworzeniu alertu, z aktualnym stanem połączeń:
{
"result": "OK",
"alert_id": "1f0c6a52-3a7e-4b8e-9d0a-5c1b2f7e8a90",
"status": "completed",
"outcome": "confirmed",
"mode": "parallel",
"confirmable": true,
"stop_on_accept": true,
"accepted_by": "8a2e4c1d-5b6f-4e7a-9c8d-1e2f3a4b5c6d",
"reference": "incydent-4711",
"created_at": "2026-11-05T10:00:00Z",
"completed_at": "2026-11-05T10:00:22Z",
"calls": [
{
"call_id": "8a2e4c1d-5b6f-4e7a-9c8d-1e2f3a4b5c6d",
"to": "48600100200",
"from": "48221234567",
"status": "completed",
"result": "accepted",
"digit": "1",
"amd_result": "human",
"dialed_at": "2026-11-05T10:00:00Z",
"answered_at": "2026-11-05T10:00:06Z",
"ended_at": "2026-11-05T10:00:21Z",
"duration_total": 21,
"duration_answered": 15
},
{
"call_id": "93b17f0e-2c3d-4a5b-8e9f-0a1b2c3d4e5f",
"to": "48600100201",
"from": "48221234567",
"status": "completed",
"result": "cancelled",
"dialed_at": "2026-11-05T10:00:00Z",
"ended_at": "2026-11-05T10:00:16Z",
"duration_total": 16,
"duration_answered": 0
}
]
}
Wyniki dostępne są przez 7 dni od zakończenia alertu. Alert innego konta zwraca 404 (alert not found).
Pola alertu
| Pole | Typ | Opis |
|---|---|---|
| result | String | Status zapytania. [OK | error] |
| alert_id | String | Identyfikator alertu (UUID) |
| status | String | [in_progress | completed] |
| outcome | String | Wynik, po zakończeniu: confirmed / not_confirmed (z dtmf), delivered / not_delivered (powiadomienie), cancelled (anulowany) |
| mode | String | [parallel | sequential] |
| confirmable | Boolean | true, gdy alert zbiera potwierdzenie (dtmf) |
| stop_on_accept | Boolean | Czy pierwsze potwierdzenie kończy alert |
| accepted_by | String | call_id pierwszego połączenia, które potwierdziło alert |
| reference | String | Własny identyfikator z zapytania |
| created_at, completed_at | String | Czas utworzenia i zakończenia, RFC3339 UTC |
| calls | Array | Połączenia, po jednym na numer, w kolejności z zapytania |
Pola połączenia
| Pole | Typ | Opis |
|---|---|---|
| call_id | String | Identyfikator połączenia (UUID) |
| to, from | String | Numer docelowy i prezentowany |
| status | String | [queued | dialing | in_progress | completed] |
| result | String | Wynik połączenia - patrz Wyniki połączeń |
| result_reason | String | Przyczyna wyniku busy, no_answer lub failed |
| digit | String | Naciśnięty klawisz |
| amd_result | String | Wynik wykrywania automatu: [human | machine | no_speech | not_sure | timeout] |
| dialed_at, answered_at, ended_at | String | Czas wybrania numeru, odebrania i zakończenia, RFC3339 UTC |
| duration_total | Int | Czas od wybrania numeru do rozłączenia, w sekundach. Obecne tylko w zakończonych połączeniach |
| duration_answered | Int | Czas rozmowy od odebrania do rozłączenia, w sekundach. Obecne tylko w zakończonych połączeniach |
Wyniki połączeń
| result | Opis |
|---|---|
| accepted | Naciśnięto klawisz potwierdzenia |
| rejected | Naciśnięto klawisz odrzucenia |
| delivered | Powiadomienie: odebrał człowiek i odsłuchał komunikat do końca co najmniej raz |
| no_input | Komunikat odsłuchany, nie naciśnięto żadnego klawisza |
| invalid_input | Naciśnięto wyłącznie inne klawisze |
| machine | Odebrał automat, rozłączono |
| machine_message_left | Odebrał automat, komunikat zostawiony po sygnale |
| no_answer | Brak odebrania |
| busy | Zajęte lub odrzucone |
| failed | Połączenia nie udało się zestawić lub komunikatu nie udało się odtworzyć |
| hangup_before_input | Odebrano, ale rozmówca rozłączył się przed wyborem / przed końcem komunikatu |
| cancelled | Przerwane, bo alert potwierdził ktoś inny (tryb parallel) lub alert został anulowany |
| skipped | Tryb sequential: numer pominięty, bo wcześniejsza osoba potwierdziła alert |
Przyczyny (result_reason)
Pole result_reason wyjaśnia, dlaczego połączenie zakończyło się wynikiem busy, no_answer lub failed, zgodnie z informacją z sieci odbiorcy:
| result | result_reason | Opis |
|---|---|---|
| busy | busy | Linia zajęta |
| busy | declined | Rozmówca odrzucił połączenie |
| no_answer | ring_timeout | Brak odebrania w czasie ring_timeout |
| no_answer | no_response | Sieć odbiorcy nie odpowiedziała na czas |
| no_answer | unavailable | Telefon wyłączony lub poza zasięgiem |
| failed | number_not_found | Numer nie istnieje lub jest nieaktywny |
| failed | invalid_number | Numer niepełny lub nieprawidłowy |
| failed | call_rejected | Sieć odbiorcy odmówiła połączenia (np. numer zablokowany) |
| failed | incompatible_media | Sieć odbiorcy nie obsługuje formatu dźwięku połączenia |
| failed | network_unavailable | Sieć odbiorcy nieosiągalna lub przeciążona - warto ponowić później |
| failed | playback_failed | Nie udało się odtworzyć komunikatu, np. nie można pobrać pliku z playfile.url |
| failed | cli_not_allowed | Numer from przestał być dozwolony przed wykonaniem połączenia |
| failed | account_disabled | Konto zostało zablokowane w trakcie alertu |
| failed | no_rate, insufficient_credit | Brak stawki lub środków w chwili wykonywania połączenia |
| failed | service_unavailable | Chwilowy problem po naszej stronie - warto ponowić później |
| failed | unknown | Inna przyczyna |
Anulowanie alertu
DELETE https://api.vpbx.pl/api/v1/alerts/[alert_id]
curl --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAoMS0Iiwi" \
--request DELETE \
https://api.vpbx.pl/api/v1/alerts/1f0c6a52-3a7e-4b8e-9d0a-5c1b2f7e8a90
{
"result": "OK",
"alert_id": "1f0c6a52-3a7e-4b8e-9d0a-5c1b2f7e8a90",
"status": "cancelling"
}
Zakolejkowane połączenia są anulowane, trwające - rozłączane. status ma wartość cancelling, dopóki trwa rozłączanie, lub completed, jeśli alert był już zakończony. Dla każdego rozłączonego połączenia wysyłany jest webhook alert-call-completed, a na końcu alert-completed z outcome: cancelled (lub confirmed, jeśli ktoś zdążył potwierdzić).
Webhooki
Wyniki wysyłane są na adres z pola webhook, metodą z webhook_method. W razie niepowodzenia dostarczenie jest ponawiane. Ten sam webhook może dotrzeć więcej niż raz - do wykrywania duplikatów należy używać event_type oraz call.call_id / alert_id.
alert-call-completed
Wysyłany raz dla każdego numeru, po zakończeniu połączenia.
{
"event_type": "alert-call-completed",
"timestamp": "2026-11-05T10:00:21Z",
"alert_id": "1f0c6a52-3a7e-4b8e-9d0a-5c1b2f7e8a90",
"reference": "incydent-4711",
"call": {
"call_id": "8a2e4c1d-5b6f-4e7a-9c8d-1e2f3a4b5c6d",
"to": "48600100200",
"from": "48221234567",
"status": "completed",
"result": "accepted",
"digit": "1",
"amd_result": "human",
"dialed_at": "2026-11-05T10:00:00Z",
"answered_at": "2026-11-05T10:00:06Z",
"ended_at": "2026-11-05T10:00:21Z",
"duration_total": 21,
"duration_answered": 15
}
}
alert-completed
Wysyłany raz, po zakończeniu wszystkich połączeń alertu (także tych rozłączonych, bo potwierdził ktoś inny).
{
"event_type": "alert-completed",
"timestamp": "2026-11-05T10:00:22Z",
"alert_id": "1f0c6a52-3a7e-4b8e-9d0a-5c1b2f7e8a90",
"reference": "incydent-4711",
"outcome": "confirmed",
"accepted_by": "8a2e4c1d-5b6f-4e7a-9c8d-1e2f3a4b5c6d",
"summary": {"accepted": 1, "cancelled": 1},
"calls": [ ... ]
}
| Pole | Typ | Opis |
|---|---|---|
| event_type | String | [alert-call-completed | alert-completed] |
| timestamp | String | Czas zdarzenia, RFC3339 UTC |
| alert_id | String | Identyfikator alertu |
| reference | String | Własny identyfikator z zapytania |
| call | Object | alert-call-completed: końcowy stan połączenia (pola jak w Pola połączenia) |
| outcome | String | alert-completed: wynik alertu |
| accepted_by | String | alert-completed: call_id pierwszego potwierdzenia |
| summary | Object | alert-completed: liczba połączeń dla każdego wyniku |
| calls | Array | alert-completed: końcowy stan wszystkich połączeń |