Přeskočit na obsah

SMS brána přes API za 10 minut v Node.js

2. 9. 2026 · 6 minut čtení

Abys z Node.js poslal SMS, stačí jeden POST požadavek na https://volai.cz/v1/messages s hlavičkou Authorization: Bearer vk_... a JSON tělem {to, body}. Node 20 má fetch vestavěný, takže nepotřebuješ žádný balíček navíc. Funkční příklad je hned pod tímhle odstavcem, včetně ošetření chyb a idempotence, aby ti výpadek sítě neposlal stejnou zprávu dvakrát.

Co budeš potřebovat

  • Node.js 18 nebo novější - fetch je ve vestavěné knihovně už od téhle verze; doporučujeme aktuální podporovanou LTS řadu (24, stav k 2. 9. 2026).
  • Účet na volai a API klíč z portálu (Nastavení -> API a MCP), ve tvaru vk_....
  • Telefonní číslo příjemce v Česku nebo na Slovensku (+420 nebo +421) - jiné země posílání SMS v MVP nepodporuje.
  • Vlastní telefonní číslo NEPOTŘEBUJEŠ. Odesílatel je vždy pevné jméno volai - operátoři nedovolují nastavit vlastní jméno odesílatele SMS. Svou identitu proto napiš přímo do textu zprávy, tak jako v příkladu níž.

Pošli první SMS

Requestu stačí dvě pole: to (číslo příjemce) a body (text zprávy). Odpověď vrátí id zprávy, status, cenu v haléřích (priceHal) a počet segmentů (segments) - k oběma se za chvíli vrátíme.

javascript
const response = await fetch("https://volai.cz/v1/messages", {
  method: "POST",
  headers: {
    Authorization: "Bearer vk_TVUJ_KLIC",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    to: "+420777123456",
    body: "Ahoj z volai! - Moje Appka",
  }),
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error("volai " + error.code + ": " + error.message);
}

const message = await response.json();
console.log(message);
// { id: "msg_7c1f9a2e", status: "sent", priceHal: 136, segments: 1 }

Kolik stojí SMS a co jsou segmenty?

Jedna SMS do jednoho segmentu stojí 1,36 Kč bez DPH (cena platná k 2. 9. 2026). Kolik segmentů zpráva zabere, závisí na tom, jestli obsahuje diakritiku - operátoři počítají SMS podle standardu GSM 03.38:

KódováníZnaků v 1. segmentuZnaků v dalších dílech
GSM-7 (bez diakritiky)160153
UCS-2 (s diakritikou)7067

Stačí JEDEN znak mimo základní GSM-7 abecedu - typicky česká diakritika (ě š č ř ž ý á í ů ď ť ň) - a celá zpráva přepne na UCS-2, i kdyby byl ve zprávě jen jeden háček. Tip: chceš-li se spolehlivě vejít do jednoho segmentu, piš bez diakritiky.

Příklad: zpráva s diakritikou nad 70 znaků

Tahle zpráva má 112 znaků a obsahuje diakritiku, takže se počítá jako UCS-2 (limit 67 znaků na segment u vícedílné zprávy) - vyjde na 2 segmenty, tedy 2,72 Kč, ne 1,36 Kč:

javascript
await fetch("https://volai.cz/v1/messages", {
  method: "POST",
  headers: {
    Authorization: "Bearer vk_TVUJ_KLIC",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    to: "+420777123456",
    body: "Příliš žluťoučký kůň úpěl ďábelské ódy - a tahle věta má přes sedmdesát znaků, takže spadne do druhého segmentu.",
  }),
});
// -> { id: "msg_9a3f1c7d", status: "sent", priceHal: 272, segments: 2 }

Idempotence: bezpečné opakování požadavku

Pošli navíc hlavičku Idempotency-Key s libovolným vlastním řetězcem, třeba ID objednávky. Když požadavek selže na výpadku sítě a workflow ho zopakuje se STEJNOU hlavičkou, volai vrátí uloženou odpověď z prvního pokusu a zprávu neodešle podruhé - klíč platí 24 hodin.

bash
curl -X POST https://volai.cz/v1/messages \
  -H "Authorization: Bearer vk_TVUJ_KLIC" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: objednavka-4471" \
  -d '{"to": "+420777123456", "body": "Vaše objednávka č. 4471 byla přijata."}'

Bezpečně zacházej s API klíčem

API klíč (vk_...) má stejnou váhu jako heslo - kdokoli ho zná, může tvým jménem posílat zprávy a utrácet kredit z tvého účtu. Do zdrojového kódu, který commitneš do repozitáře, ho proto nepiš nikdy. Patří do proměnné prostředí (process.env.VOLAI_API_KEY v Node.js) nebo do správce tajemství, který používá tvůj hosting.

javascript
// .env.local (nikdy necommituj do repozitáře)
// VOLAI_API_KEY=vk_tvuj_klic

const response = await fetch("https://volai.cz/v1/messages", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.VOLAI_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ to, body }),
});

Endpoint volej vždy ze serveru, ne z prohlížeče - kdokoli by si klíč přečetl v developerských nástrojích prohlížeče a začal na tvůj účet posílat vlastní zprávy. Uniknul-li klíč (třeba omylem v commitu), vygeneruj si v portálu nový a ten starý zneplatni - stará hodnota přestane fungovat okamžitě.

Vyzkoušej to nejdřív bezpečně

Než napojíš odesílání SMS na skutečný proces (třeba potvrzení objednávky), pošli první pár zpráv na vlastní telefonní číslo. Uvidíš přesně to, co uvidí zákazník - jak vypadá odesílatel volai, jak dlouho doručení trvá a jestli se diakritika počítá tak, jak čekáš. Teprve pak přepni to na skutečné číslo zákazníka.

Co dělat, když API vrátí chybu?

Neúspěšná odpověď má vždy tvar {error: {code, message}} a HTTP status podle typu chyby:

StavKódCo to znamená
400invalid_numberTo není platné české nebo slovenské telefonní číslo.
400invalid_bodyText zprávy musí mít 1 až 765 znaků.
400unsupported_countryČíslo mimo ČR/SR - v MVP nepodporujeme.
400recipient_cannot_receive_smsPevná linka - SMS by nedorazila. Nic se neúčtuje.
402insufficient_creditKredit nepokryje cenu všech segmentů zprávy.
429rate_limitedVíc než 1 SMS za 2 vteřiny, nebo přes 100 SMS na účet denně.
502send_failedTelefonní síť žádost odmítla. Kredit se nestrhl, zkus to znovu.
502send_unknownZprávu jsme předali síti, potvrzení nedorazilo - účtuje se, neposílej ji prosím znovu naslepo.

Jaké stavy může zpráva mít?

Pole status ve výpisu (GET /v1/messages) i v detailu jedné zprávy nabývá čtyř hodnot: pending (uložená, ještě se odesílá), sent (operátor odeslání potvrdil), failed (operátor odmítl - neúčtuje se) a unknown (potvrzení o doručení nedorazilo, typicky timeout na straně operátora - přesto se účtuje plnou cenou, protože zpráva mohla dojít).

Jak rozeslat víc SMS najednou?

Rate limit dovoluje nejvýš jednu SMS za dvě vteřiny na účet a nejvýš 100 SMS denně. Pro rozeslání víc zpráv najednou (třeba připomínek schůzek) je jednodušší mezi požadavky krátce počkat, než chytat rate_limited a zkoušet znovu:

javascript
const messageText = "Připomínáme zítřejší termíny - Moje Appka";

for (const to of recipients) {
  const response = await fetch("https://volai.cz/v1/messages", {
    method: "POST",
    headers: {
      Authorization: "Bearer vk_TVUJ_KLIC",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ to, body: messageText }),
  });

  if (!response.ok) {
    const { error } = await response.json();
    console.error("volai " + to + ": " + error.code);
  }

  // Limit je nejvýš 1 SMS za 2 vteřiny na účet - krátká pauza mezi
  // požadavky je jednodušší než řešit rate_limited a zkoušet znovu.
  await new Promise((resolve) => setTimeout(resolve, 2100));
}

Příchozí SMS API nevidí

volai SMS jen ODESÍLÁ, přijímat je neumí. Odpověď zákazníka na tvoji zprávu se nikde neobjeví - ani v GET /v1/messages, ani ve webhooku. Na obousměrnou komunikaci přes SMS zatím nespoléhej; kdy je pro tenhle případ lepší volba Twilio, popisuje srovnání Twilio a volai.

Shrnutí: jeden POST /v1/messages s poli to a body pošle SMS, hlavička Idempotency-Key ochrání workflow před dvojím odesláním při opakování požadavku a cena se počítá po segmentech podle toho, jestli text obsahuje diakritiku. Klíč drž mimo repozitář a nejdřív si pošli zprávu sám sobě.

Co dál

Plná reference REST API (čísla, hovory, webhooky, agenti) je na /docs/api. Chceš vědět hned, jakmile volai zprávu předá operátorovi, bez pollování GET /v1/messages? Přihlas se k odběru události message.sent - potvrzuje jen převzetí operátorem, ne doručení do telefonu (to volai nevidí, viz výš) - viz dokumentace webhooků. A pokud kromě SMS potřebuješ i telefonní číslo, které samo zvedá hovory, mrkni na AI recepční.

Vyzkoušej volai - prvních 50 Kč na nás

Založíš účet za minutu a rovnou máš na kontě startovní kredit na hovory, SMS i číslo.

Vyzkoušet zdarma