Wysyłka SMS przez API
API wysyłki SMS pozwala wysyłać wiadomości z Twoją nazwą nadawcy bezpośrednio z Twojego systemu. Możesz wysłać pojedynczą wiadomość, np. powiadomienie o zamówieniu albo kod, albo paczkę do 1000 wiadomości w jednym żądaniu. Koszt i rozliczenie są takie same jak przy wysyłce z panelu.
Ten przewodnik opisuje API v2 (/api/v2/sms). Dotychczasowe API v1 (/api/v1/sending/{klucz}) nadal działa. Różnice opisuje sekcja Przejście z API v1.
Zanim zaczniesz
- Usługa Wysyłka SMS jest aktywna na Twoim koncie w panelu.
- Masz co najmniej jednego zatwierdzonego nadawcę (status
active). Nazwy nadawców (do 12 znaków) rejestrujesz w panelu w zakładce Nadawcy. - Masz środki: saldo prepaid albo wolny limit postpaid.
Klucz API
Klucz znajdziesz w panelu: Wysyłka SMS → Dostęp API. Przekazuj go w nagłówku każdego żądania:
Authorization: Bearer TWOJ_KLUCZ
Adres bazowy API to https://panel.dpay.pl/api/v2/sms. Treść żądań to obiekt JSON (Content-Type: application/json).
Kto zna klucz, ten wysyła SMS na Twój koszt. Używaj go wyłącznie po stronie serwera. Nigdy nie umieszczaj go w aplikacji mobilnej ani w kodzie strony.
Endpointy
POST/api/v2/sms/messagesWysłanie jednej wiadomości.Pełny kontrakt w API Reference
GET/api/v2/sms/messages/{id}Stan wiadomości.Pełny kontrakt w API Reference
POST/api/v2/sms/batchesWysłanie paczki do 1000 wiadomości.Pełny kontrakt w API Reference
GET/api/v2/sms/batches/{id}Stan paczki i wynik każdej wiadomości.Pełny kontrakt w API Reference
GET/api/v2/sms/sendersNadawcy konta i ich status.Pełny kontrakt w API Reference
Pojedyncza wiadomość
curl -X POST "https://panel.dpay.pl/api/v2/sms/messages" \
-H "Authorization: Bearer TWOJ_KLUCZ" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: zamowienie-1234-wyslane" \
-d '{"msisdn":"600100200","sender":"SKLEP","message":"Twoje zamówienie zostało wysłane."}'
| Pole | Wymagane | Opis |
|---|---|---|
msisdn | tak | Numer odbiorcy, patrz Numer odbiorcy. |
sender | tak | Zatwierdzona nazwa nadawcy Twojego konta. |
message | tak | Treść, do 900 bajtów, patrz Treść i segmenty. |
Odpowiedź 202 Accepted oznacza, że wiadomość przyjęliśmy do kolejki i zarezerwowaliśmy jej koszt. Nie oznacza doręczenia - wiadomość przekazujemy operatorowi w tle.
{
"batch_id": null,
"id": "9b2f4c1e-6f0a-4d7e-9a41-3c8f2d7b5e10",
"msisdn": "600100200",
"sender": "SKLEP",
"status": "queued",
"segments": 1,
"cost_net": "0.081300",
"created_at": "2026-10-14T12:00:00+02:00",
"finished_at": null
}
Zapisz id - pod GET /api/v2/sms/messages/{id} sprawdzisz, jak zakończyło się przekazanie wiadomości. Adres jest też w nagłówku Location odpowiedzi.
Wysyłka masowa
Paczka to do 1000 wiadomości w jednym żądaniu. sender na poziomie paczki jest domyślnym nadawcą, a każda wiadomość może ustawić własny.
curl -X POST "https://panel.dpay.pl/api/v2/sms/batches" \
-H "Authorization: Bearer TWOJ_KLUCZ" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: kampania-2026-10" \
-d '{
"name": "Kampania październik",
"sender": "SKLEP",
"messages": [
{"msisdn": "600100200", "message": "Wiadomość 1"},
{"msisdn": "+48600100201", "message": "Wiadomość 2 od innego nadawcy", "sender": "INNY"}
]
}'
Paczkę przyjmujemy w całości albo wcale.
- Jeśli choć jedna wiadomość jest niepoprawna, dostajesz
422i nic nie zostaje przyjęte. Poleerror.fieldswskazuje każdą błędną wiadomość, np.messages.1.msisdnto numer drugiej wiadomości (liczymy od zera). - Jeśli środki nie wystarczają na całą paczkę, dostajesz
402.
Przyjęta paczka dostaje 202 ze statusem pending i wyceną w polu estimate. Wiadomości tworzymy w tle, zwykle w ciągu kilku sekund - dopiero wtedy rezerwujemy koszt.
{
"id": "3d6a0f5e-2b7c-4e19-8f3a-1c9d5e7b2a40",
"name": "Kampania październik",
"status": "pending",
"failure_reason": null,
"messages_count": 2,
"counts": {"queued": 0, "sending": 0, "sent": 0, "failed": 0, "unknown": 0},
"segments": 0,
"cost_net": null,
"created_at": "2026-10-14T12:00:00+02:00",
"updated_at": "2026-10-14T12:00:00+02:00",
"messages": [],
"estimate": {"segments": 2, "cost_net": "0.162600"}
}
Paczki wysłane przez API widać też w panelu w zakładce Import, a ich wiadomości w Historii i raportach.
Postęp i wyniki paczki
curl "https://panel.dpay.pl/api/v2/sms/batches/3d6a0f5e-2b7c-4e19-8f3a-1c9d5e7b2a40" \
-H "Authorization: Bearer TWOJ_KLUCZ"
Odpowiedź zawiera liczniki wiadomości w każdym statusie i wynik każdej wiadomości w kolejności z żądania (index):
{
"id": "3d6a0f5e-2b7c-4e19-8f3a-1c9d5e7b2a40",
"status": "completed_with_errors",
"counts": {"queued": 0, "sending": 0, "sent": 1, "failed": 1, "unknown": 0},
"segments": 2,
"cost_net": "0.081300",
"messages": [
{"index": 0, "id": "5a1e9c2d-7b3f-4a8e-b6d4-0f2c9e1a7b35", "msisdn": "600100200", "sender": "SKLEP", "status": "sent", "segments": 1, "cost_net": "0.081300"},
{"index": 1, "id": "c7d2b8e4-1f6a-4c3d-9e5b-8a0f2d4c6e19", "msisdn": "600100201", "sender": "INNY", "status": "failed", "segments": 1, "cost_net": "0.000000"}
]
}
| Status paczki | Znaczenie |
|---|---|
pending | Przyjęta, czeka na przetworzenie. Lista messages jest jeszcze pusta. |
processing | Wiadomości utworzone, część jest jeszcze w kolejce albo w trakcie przekazania. |
completed | Wszystkie wiadomości przekazane operatorowi (sent). |
completed_with_errors | Zakończona, ale część wiadomości ma status failed albo unknown. |
rejected | Odrzucona podczas przetwarzania. Nic nie wysłaliśmy i niczego nie pobraliśmy - powód jest w failure_reason. |
Paczka może zostać odrzucona już po przyjęciu, jeśli w międzyczasie zmieni się stan konta:
failure_reason | Co się stało | Co zrobić |
|---|---|---|
insufficient_funds | Środki wykorzystała inna wysyłka między przyjęciem a przetworzeniem paczki. | Doładuj konto i wyślij paczkę ponownie. |
sender_inactive | Nadawca z paczki przestał być aktywny. | Sprawdź nadawców w panelu. |
account_unavailable | Usługa SMS przestała być dostępna na koncie. | Sprawdź stan usługi w panelu. |
invalid_batch, processing_failed | Paczki nie udało się przetworzyć. | Wyślij ponownie, a przy powtórce skontaktuj się z obsługą. |
Odrzuconą paczkę możesz wysłać ponownie, także z tym samym Idempotency-Key.
Statusy wiadomości
| Status | Znaczenie |
|---|---|
queued | Przyjęta, czeka w kolejce. |
sending | Przekazujemy ją operatorowi. |
sent | Operator przyjął wiadomość. To nie jest potwierdzenie doręczenia na telefon odbiorcy. |
failed | Operator odrzucił wiadomość. Nie pobieramy za nią opłaty (cost_net = 0.000000). |
unknown | Nie udało się potwierdzić wyniku przekazania. Kwota pozostaje zarezerwowana do wyjaśnienia - w razie wątpliwości skontaktuj się z obsługą. |
Idempotencja
Sieć bywa zawodna: żądanie mogło dojść, a odpowiedź nie. Żeby ponowienie nie wysłało SMS-a drugi raz, dodaj nagłówek Idempotency-Key z własnym, unikalnym kluczem (1-128 drukowalnych znaków ASCII, wielkość liter ma znaczenie), np. numerem zamówienia.
- Powtórka z tym samym kluczem i tą samą treścią zwraca pierwotny wynik zamiast wysyłać ponownie. Odpowiedź ma wtedy nagłówek
Idempotent-Replayed: true. - Ten sam klucz z inną treścią to błąd
409 idempotency_conflict. - Klucz paczki obejmuje całą treść żądania, łącznie z
name. - Żądanie odrzucone przez walidację, brak środków albo nieaktywne konto nie zużywa klucza. Klucz paczki odrzuconej podczas przetwarzania (
rejected) też możesz użyć ponownie.
Numer odbiorcy
msisdn to polski numer w jednym z formatów: 600100200, +48600100200, 0048600100200 albo 48600100200. Spacje i myślniki są pomijane. W odpowiedziach numer wraca zawsze jako 9 cyfr.
Treść i segmenty
Treść może mieć do 900 bajtów w UTF-8. Wysyłamy ją dokładnie w takiej postaci, w jakiej przyszła - nie usuwamy spacji ani znaków nowej linii.
Długa wiadomość jest dzielona na segmenty, a opłata to liczba segmentów razy stawka Twojego konta:
| Kodowanie | 1 segment | Przy podziale, na segment |
|---|---|---|
| GSM-7 (litery łacińskie bez polskich znaków, cyfry, podstawowe symbole) | 160 znaków | 153 znaki |
| Unicode | 70 znaków | 67 znaków |
Jeden znak spoza alfabetu GSM-7, np. ą, ę albo ł, przełącza całą wiadomość na Unicode. Wiadomość z 100 znakami mieści się w jednym segmencie bez polskich znaków, ale z nimi zajmuje dwa.
Liczbę segmentów i cenę każdej wiadomości zwracamy w polach segments i cost_net (kwota netto w PLN). Liczymy je tym samym kalkulatorem co przy wysyłce z panelu.
Błędy
Każdy błąd ma tę samą postać: stabilny code do obsługi w kodzie i message dla człowieka.
{
"error": {
"code": "validation_failed",
"message": "Żądanie zawiera niepoprawne dane.",
"fields": {
"messages.1.msisdn": ["Numer odbiorcy to dziewięć cyfr, opcjonalnie z prefiksem +48."]
}
}
}
| HTTP | code | Znaczenie |
|---|---|---|
| 400 | invalid_json | Treść żądania nie jest obiektem JSON. |
| 401 | unauthenticated | Brak klucza albo niepoprawny klucz. |
| 402 | insufficient_funds | Brak salda albo wolnego limitu (przy paczce: na całą paczkę). |
| 403 | account_unavailable | Usługa SMS nie jest aktywna na koncie. |
| 404 | not_found | Nie ma takiej wiadomości albo paczki na Twoim koncie. |
| 409 | idempotency_conflict | Idempotency-Key użyty już dla innej treści. |
| 422 | validation_failed | Niepoprawne pola - szczegóły w fields. Dotyczy też nieaktywnego nadawcy. |
| 429 | rate_limited | Przekroczony limit żądań. Ponów po czasie z nagłówka Retry-After. |
Limity
- 120 żądań na minutę na klucz API. Do wysyłki wielu wiadomości używaj paczek, a nie pojedynczych żądań w pętli.
- 1000 wiadomości w paczce i 900 bajtów na wiadomość.
- Seria nieudanych prób uwierzytelnienia z jednego adresu blokuje kolejne żądania z tego adresu do końca minuty (
429).
Przejście z API v1
API v1 działa nadal, ale nowe integracje powinny korzystać z v2.
| API v1 | API v2 | |
|---|---|---|
| Klucz | w adresie: /api/v1/sending/{klucz} | w nagłówku Authorization: Bearer |
| Pola wiadomości | number, override, message | msisdn, sender, message |
| Wysyłka masowa | /{klucz}/mass, lista JSON, wiadomość po wiadomości | /batches, obiekt JSON, paczka w całości przetwarzana w tle |
| Błędy | 200 z error|... przy każdej pozycji listy | kody HTTP i error.code, błędne pozycje wskazane w fields |
| Stan wiadomości | brak | GET /messages/{id}, GET /batches/{id} |
| Ponowienia | brak ochrony przed duplikatem | Idempotency-Key |
Klucz API jest ten sam - nie trzeba generować nowego.