Přeskočit na obsah

Průvodci

Úkoly

Úkol vytočí seznam příjemců jedním agentem podle pravidel, která nastavíš předem - rozpočet, volací okno, počet pokusů. Nic se nevytáčí, dokud úkol sám nespustíš.

Co je úkol

Úkol (task_...) je odchozí kampaň: jeden agent na motoru volai, seznam příjemců s telefonním číslem a volitelnými proměnnými pro systémový prompt, a pravidla (rozpočet, volací okno, maximální počet pokusů a délka hovoru). Tři typy podle účelu - invoice (upomínka platby), appointment (potvrzení termínu) a custom (cokoli jiného) - u prvních dvou navíc, když source je integration, každý příjemce nese referenci na doklad nebo událost (externalRefs), kterou agent v hovoru zmiňuje.

Agent musí být na vlastním motoru volai (useForOutboundTasks: true při založení, viz POST /v1/agents), ne na ElevenLabs - motor umí proměnné pro systémový prompt, stropy délky hovoru a ověření, že se za běhu kampaně nezměnila jeho konfigurace. Koncept agenta musí být PUBLIKOVANÝ (PATCH /v1/agents/{id} nebo POST /v1/agents/{id}/draft/publish) A publikovaná verze musí být OTESTOVANÁ (POST /v1/agents/{id}/simulate) - chybí-li kterýkoli krok, start vrátí agent_not_ready.

Stavy a přechody

V praxi jde úkol vždy ze `draft` rovnou do `running` (`start`); smazat (`DELETE`) jde jen v označených stavech.

draft
Co znamenáPrávě založený, ještě nikdy neproběhl start. Editovatelný (PATCH), smazatelný.
ready
Co znamenáStav modelu mezi draft a running, který start jako výchozí bod akceptuje - žádný veřejný endpoint (REST ani MCP) do něj ale úkol dnes nepřevádí, takže ho v praxi nepotkáš. Kdyby nastal, je editovatelný a smazatelný stejně jako draft.
running
Co znamenáVytáčí příjemce. NEEDITOVATELNÝ - PATCH vrátí task_not_editable bez výjimky. Kdo potřebuje změnit proměnné, musí úkol nejdřív zastavit (POST .../pause) a upravit až v paused - a jen dokud se ještě žádný příjemce nevytáčel; po prvním pokusu už recipients (a tím i proměnné) vyměnit nejde (items_immutable) a zbývá založit nový úkol. Smazat nejde. Když narazí na limit hovorů (rate_limited - limit odchozích minut vlastního motoru, strop jeho souběžných odchozích hovorů, výchozích 10 hovorů za minutu nebo 1000 za den), zůstává running: nastaví lastError: "rate_limited", pokus se nezapočítá a čekající kontakty dostanou items[].nextAttemptAt na konec limitu (podle Retry-After, jinak za 5 minut; u stropu souběžných hovorů motoru posílá motor Retry-After: 30). Po jeho uplynutí pokračuje sám. Stejně čeká při plné kvótě souběžných hovorů agenta na účet (relay_lease_limit, výchozí 2 běžící hovory): lastError: "relay_lease_limit", další pokus za minutu, pokus se nezapočítá. Obsazené číslo (destination_busy - na stejné číslo právě běží jiný hovor, třeba z jiného úkolu nebo ručního volání, nebo je po nejistém vytočení 15 minut zamčené) čeká jen dotčený kontakt, ne celý úkol: items[].lastError: "destination_busy", items[].nextAttemptAt podle zbývající doby zámku čísla (nejméně 30 s, nejvýš 16 min, bez zámku 90 s), pokus se nezapočítá (countsAsAttempt: false) a ostatní kontakty se volají dál. Po 6. obsazení za sebou (tedy po 5 čekáních) jde kontakt do review s reviewReason: "dial:destination_busy". Trvalá překážka v čísle (on_dnc, destination_auto_blocked, cannot_call_own_number, cannot_call_volai_number, invalid_number, unsupported_country, blocked_destination) úkol také nezastaví: kontakt jde do review s reviewReason: "dial:<kód>" (hovor nevznikl, takže pokus má countsAsAttempt: false a nečerpá maxAttempts), úkol zůstává running, volá ostatní kontakty a do needs_attention přejde, až nezbude jiná práce. Když provozovatel volai agenta úkolu ručně pozastaví, úkol zůstává running a nic nevytáčí; po obnovení pokračuje sám. Pozastaveného agenta poznáš podle suspended: true v GET /v1/agents/{id}.
paused
Co znamenáZastaveno před dalším voláním (POST .../pause); rozvolaný hovor doběhne. Editovatelný, smazatelný, start ho obnoví. Pozastavení uprostřed hovoru nechá reviewReason: "dispatch_in_flight", dokud reconcile (plánovač ho spustí do pár minut) ten hovor nevyúčtuje, a do té doby start odmítne s task_needs_attention; potom úkol s prací zůstane paused, i když jiné kontakty čekají v review. Skončí-li rozvolaný hovor bez známé ceny, přejde úkol nejdřív do needs_attention s reviewReason: "billing_unknown" a do paused se vrátí, až cena dorazí (reconcile ji doplní). Snížíš-li mezitím maxAttempts tak, že žádný kontakt čekající na další pokus už nemá volný pokus, zůstane úkol paused (takový kontakt se počítá jako práce), ale start ho odmítne s task_not_startable, dokud maxAttempts nezvýšíš.
completed
Co znamenáVšichni příjemci mají konečný výsledek. Smazatelný (už nic neplatí).
needs_attention
Co znamenáNěco vyžaduje zásah - typicky se za běhu změnila konfigurace agenta (agent_version_changed), motor dočasně nešel (engine_contract_unavailable) nebo ještě není známá cena už proběhlého hovoru (billing_unknown). POST .../reconcile sladí stav; smazatelný, dokud žádná položka nezůstala in_progress. Kontakt ve stavu review vyřídíš přes POST .../items/{itemId}/resolve (retry/skip); kontakt s trvalou překážkou v čísle (dial:on_dnc, dial:destination_auto_blocked, dial:cannot_call_own_number, dial:cannot_call_volai_number, dial:invalid_number, dial:unsupported_country, dial:blocked_destination, nebo dial:destination_busy po 6. obsazení za sebou) tak úkol zastaví až ve chvíli, kdy ostatní kontakty doběhnou. Nejistý výsledek vytáčení (např. dial:engine_unavailable) pošle úkol do needs_attention hned a ostatní kontakty se nevytočí. Až se hovor za takovým přerušením (nebo neznámou cenou) vyúčtuje přes reconcile, úkol přejde do paused, dokud některý kontakt čeká na další pokus, i když jiné jsou v review; do needs_attention jen když nezbývá nic k vytočení kromě kontaktu v review, a do completed, když nezbývá nic. Vytočení, jehož výsledek zůstává nejistý (hovor se nedohledá), drží úkol v needs_attention, dokud o kontaktu nerozhodneš. U běžného důvodu po posledním rozhodnutí úkol přejde do paused, pokud zbývá práce, nebo do completed, pokud jsou všechny kontakty hotové či přeskočené. Důvod na úrovni celého úkolu zůstává v needs_attention do svého vyřešení; výjimkou je budget_exceeded, kdy přeskočení všech kontaktů čekajících na další pokus úkol dokončí, pokud už nečeká žádný stav poskytovatele. Zvláštní postup pro billing_unknown je níže.

Přesná podmínka smazání: stav je jeden z výše označených A ŽÁDNÁ položka nemá status: "in_progress" (rozvolaný hovor, který ještě neskončil) - jinak task_not_deletable.

Import příjemců (CSV)

POST /v1/tasks/csv-preview naparsuje CSV a vrátí ho zpátky s chybami u jednotlivých řádků - NIC se neukládá, jde jen o náhled před založením nebo úpravou úkolu. Stejnou funkci volá i formulář nového úkolu v portálu.

  • Oddělovač se pozná automaticky (čárka nebo středník) podle prvního řádku.
  • Musí existovat sloupec phone - velikost písmen, mezery kolem názvu ani dvojtečka za ním nerozhodují a projde i telefon, tel nebo mobil; v náhledu se sloupec vrací vždy jako phone. Samotný seznam čísel bez hlavičky (jedno na řádek, jediný sloupec) projde také. Jinak celé CSV padá na jedinou chybu phone u hlavičky.
  • Každý další sloupec je proměnná pro systémový prompt agenta ({{jméno_sloupce}}) - nejvýš 20 sloupců, název max 64 znaků, hodnota max 512 znaků.
  • Telefonní číslo se normalizuje stejně jako všude v API (normalizePhone) - neplatné nebo prázdné číslo je chyba jen na TOM řádku, zbytek CSV se dál zpracuje.
  • Buňka, která vypadá jako vzorec tabulkového procesoru (začíná =, +, - nebo @), se odmítne - ochrana proti CSV injection při otevření v Excelu.
  • Nejvýš 1000 řádků na jedno CSV (stejný strop jako počet příjemců úkolu) a 1 MB syrového textu; celý úkol i s podklady se musí vejít do 2 MB, jinak založení vrátí validation.

Odpověď nese headers, delimiter, rows (každý řádek se svými chybami), validRows (jen bezvadné, přesně ve tvaru, který recipients v POST /v1/tasks očekává) a souhrnné errors. Pošli validRows rovnou jako recipients při založení nebo úpravě úkolu.

Rozpočet a volací okno

Rozpočet

budgetHal je STROP útraty v haléřích za celý úkol. Při založení musí pokrýt aspoň jeden pokus v maximální délce (budget_too_low, jinak); při startu se před KAŽDÝM vytočením rezervuje nejvyšší možná cena pokusu (reservedBudgetHal), teprve zvednutý hovor ji promítne do spentHal. Rezervace je cena hovoru přesně v délce maxDurationSecs (noha hovoru a noha agenta, každá zaokrouhlená nahoru na haléř stejně jako při účtování) a hovor ukončený stropem délky se účtuje nejvýš za maxDurationSecs, i když skončí o zlomek vteřiny později. Úkol nikdy neutratí víc, než kolik mu dáš do budgetHal.

Volací okno

callingWindow je {timezone: "Europe/Prague", start, end, days} - dnes jediné podporované pásmo je pražské, start/end ve tvaru HH:MM (konec včetně celé minuty: okno 09:00-18:00 pustí poslední hovor v 18:00:59, okno do 23:59 pokrývá celý den) a days čísla 0-6 (0 = neděle). Bez zadání platí pracovní dny 9:00-18:00. Mimo okno start vrátí window_closed - buď počkej na otevření, nebo oknem upravíš přes PATCH. Volitelné expiresAt je konec kampaně jako kladné bezpečné celé číslo v Unix milisekundách. Kontroluje se při startu a před každým vytočením včetně prvního. Od tohoto okamžiku nový hovor nezačne a pokus se nespotřebuje; probíhající hovor doběhne. Horizont opakování politiky lorela_enterprise (retryMaxAgeDays, výchozí 5) se naopak počítá jako N × 24 hodin od prvního započítaného pokusu, nikoli od založení úkolu.

Pokusy a délka hovoru

maxAttempts je bez politiky 1-3 (výchozí 1), s retryPolicy: "lorela_enterprise" 1-4 (při založení výchozí 4). Určuje nejvyšší počet automatických vytočení jednoho kontaktu včetně prvního (1 = bez opakování). Počítá se každý pokus, při kterém hovor mohl proběhnout, tedy i nezvednutý nebo odmítnutý volaným a pokus s nejistým výsledkem. Nepočítá se pokus, který volai odmítl ještě před vytočením (například blokované, vlastní nebo neplatné číslo, seznam nevolat, číslo, na které právě běží jiný hovor, limity hovorů); takový pokus má items[].attempts[].countsAsAttempt: false. Nepočítá se ani hovor, kterému operátor nedostal včas cíl, takže telefon volaného vůbec nezazvonil (pokus má errorCode: "relay_not_routed", hovor endReason: "rejected"): kontakt dostane nový pokus nejdřív za minutu (s politikou lorela_enterprise podle jejího rozvrhu) a po třetím takovém pokusu za sebou jde ke kontrole s reviewReason: "call:relay_not_routed". Bez politiky platí strop jen pro automatická vytočení: kontakt, kterému po vyčerpání pokusů dáš resolve s action: "retry", dostane ještě jedno vytočení navíc. maxDurationSecs (30-300) je strop délky JEDNOHO hovoru, po kterém se automaticky ukončí - výchozí délka je 120 sekund. Volitelná politika lorela_enterprise se zapíná přes API nebo MCP před prvním pokusem; stávající úkoly se samy nemění. Druhý pokus je nejdřív 8 minut od začátku prvního skutečného pokusu v době 08:00-22:00 Europe/Prague. Třetí jde do dalšího jiného okna 08:00-09:00 nebo 17:00-19:00, čtvrtý do opačného okna následující kalendářní den. Vždy platí také callingWindow a jeho dny; po horizontu opakování už politika znovu nevolá: volitelný retryMaxAgeDays je celé číslo dní 1-30 (bez zadání 5) a platí jen s politikou. Horizont končí N × 24 hodin od prvního započítaného pokusu kontaktu (pevné 24hodinové dny, takže přes změnu času se místní čas konce posune o hodinu). Jde změnit přes PATCH i po začátku pokusů (ne u úkolu running nebo completed, ten upravit nejde): zvýšení u pozastaveného úkolu vrátí kontakty, které propadly jen kvůli horizontu (reviewReason: "retry_age_expired"), zpět do stavu failed (čekají na další pokus) s novým nextAttemptAt, mají-li ještě volný pokus a nejbližší slot rozvrhu padne do nového horizontu a před expiresAt; snížení nic neotevře. Znovu otevírá jen PATCH, který horizont zvyšuje, proto retryMaxAgeDays a prodloužený expiresAt posílej v jednom požadavku. Opakuje jen no_answer, missed, busy, dokončenou hlasovou schránku a zvednutý hovor, ve kterém volaný nic neřekl (vše bez kontaktu s člověkem). Kontakt s člověkem, odmítnutí a chyba motoru automatické opakování zastaví; nejasný výsledek jde ke kontrole. Poslední dovolený pokus (maxAttempts), který skončí takovým opakovatelným výsledkem (nezvednuto, obsazeno, hlasová schránka nebo zvednutý hovor, ve kterém volaný nic neřekl), uzavře kontakt jako skipped s reviewReason: "retry_policy_exhausted" (jako retry_age_expired po vypršení horizontu): není co kontrolovat ani opakovat. Nejasný výsledek posledního pokusu zůstane v review se stejným důvodem. Bez politiky se nic nemění: poslední nezvednutý pokus je dál review. Ověřený lidský callback po vyčerpání pokusů kontakt přepne na completed. DNC, rozpočet a limity zůstávají v platnosti: automatická blokace po 3 neúspěších za 24 hodin může zastavit i čtvrtý pokus. Při PATCH bez maxAttempts zůstane dosavadní limit. S politikou vrací ruční resolve s action: "retry" chybu validation; povolené jsou jen její automaticky naplánované pokusy. Výjimkou je obnova prokazatelně nevytočeného zaniklého claimu před prvním skutečným pokusem: bez aktivního workeru, dispatch záměru, provider call ID, rezervace, ceny nebo callbacku a jen s prokázanými odmítnutími před vytočením v historii. Obnova zachová historii a stropy a vrátí kontakt mezi čekající v pozastavené úloze; sama nevolá. Připomínky takto znovu otevřít nelze. Ruční opakování tedy neobejde čtyři pokusy, horizont opakování ani ukončení po kontaktu s člověkem.

Globální propustnost

Vytáčení všech úkolů na CELÉ platformě (ne jen tvého účtu) prochází jedním cronem, který běží každé 2 minuty a zpracuje nejvýš 20 položek za běh - ALE tenhle strop se dělí mezi souběžně běžící úkoly, ne že by ho směl vyčerpat jeden úkol: z KAŽDÉHO aktivního úkolu se za jeden běh vytočí právě JEDEN příjemce. I na úplně prázdné platformě proto jeden úkol nikdy nevytočí víc než jednoho příjemce za 2 minuty - sto příjemců tak neproběhne za ~20 minut, ale za zhruba 200 minut (100 x 2 minuty). Naplánuj podle toho volací okno u časově citlivých kampaní (např. appointment den před termínem).

Postup pro agenta

Typický běh, v tomto pořadí:

  1. Agent úkolu musí být PUBLIKOVANÝ a publikovaná verze OTESTOVANÁ (POST /v1/agents/{id}/draft/publish, pak POST /v1/agents/{id}/simulate) - jinak pozdější start vrátí agent_not_ready.
  2. Volitelně POST /v1/tasks/csv-preview - zkontroluje CSV příjemců, než se založí úkol.
  3. POST /v1/tasks - založí úkol ve stavu draft. Nic se nevytáčí.
  4. GET /v1/tasks/{id} - přečti issues (dry-run validace) a ověř, že úkol je připravený na start.
  5. Podle potřeby PATCH /v1/tasks/{id} - uprav pravidla, VŽDY se revision z posledního čtení.
  6. POST /v1/tasks/{id}/start - spustí vytáčení. Každý zvednutý hovor se účtuje ihned.
  7. POST /v1/tasks/{id}/pause kdykoli za běhu, POST /v1/tasks/{id}/start znovu obnoví.
  8. Po přerušení (pozastavení uprostřed hovoru, zaniklý claim, hovor s dosud neznámou cenou nebo nejisté vytočení, ke kterému se hovor dohledal) POST /v1/tasks/{id}/reconcile sladí stav se skutečnými hovory; takový úkol s prací skončí v paused, i když některé kontakty čekají v review, a jde ho znovu spustit přes start. Běžící úkol ani jiný důvod (variables_limit, selhání ověření faktury nebo kalendáře, nejisté vytočení, ke kterému se hovor nedohledal) reconcile nemění; kontakty ke kontrole vyřiď přes resolve.
  9. Kontakt, který skončil ke kontrole (items[].status: "review"), vyřiď POST /v1/tasks/{id}/items/{itemId}/resolve s action: "retry" nebo "skip". U běžného důvodu vrátí retry kontakt mezi čekající a úkol do paused; skip ho uzavře a úkol přejde do paused, pokud zbývá práce, nebo do completed, pokud jsou všechny kontakty hotové či přeskočené. Úkol v paused pak obnov POST /v1/tasks/{id}/start.
  10. Když má kontakt reviewReason: "dial:destination_auto_blocked", číslo je 30 dní automaticky blokované po opakovaně nezvednutých hovorech a samotný retry ho neodblokuje - kontakt by se hned vrátil do review. Nejdřív zruš blokaci (POST /v1/dnc/{e164}/unblock, v MCP unblock_destination, číslo vezmi z items[].phone), potom resolve s action: "retry". U dial:on_dnc nejdřív odeber číslo ze seznamu nevolat (DELETE /v1/dnc/{e164}), jinak kontakt přeskoč. U vlastního čísla účtu, čísla jiného účtu volai, neplatného čísla, čísla mimo Česko a Slovensko a prémiové linky (dial:cannot_call_own_number, dial:cannot_call_volai_number, dial:invalid_number, dial:unsupported_country, dial:blocked_destination) další pokus nepomůže: použij jen skip.
  11. Když má kontakt reviewReason: "billing_unknown", původní hovor už mohl proběhnout, jen ještě nemá známou cenu. Nejprve zavolej reconcile a zkontroluj navázaný hovor. retry si zapamatuje, že se má kontakt zkusit znovu, ale nový hovor nepovolí, dokud se původní cena neusadí; potom úkol přejde do paused a vyžaduje nový start. skip zabrání dalšímu hovoru; původní hovor zůstává navázaný a jeho rezervace se dál počítá do rozpočtu, dokud se cena neusadí. Zbývá-li práce, úkol po skip přejde rovnou do paused a jde spustit; bez další práce počká v needs_attention a po usazení ceny skončí v completed. Hovor, který se vůbec nevytočil (motor ho odmítl), se nikdy neúčtuje a usadí se hned na 0.
  12. DELETE /v1/tasks/{id} na konci, pokud úkol nikdy neproběhl nebo už doběhl a chceš záznam smazat.

Skoro každé zapisující volání (PATCH, start, pause, DELETE) bere revision z posledního přečtení úkolu - stejná optimistická souběžnost jako u konceptů agenta. Neshoda vrátí revision_conflict: načti úkol znovu (GET) a zopakuj s aktuální revision. reconcile je výjimka - revision je tam nepovinná.

MCP

Stejné nástroje jako REST (list_tasks, get_task, create_task, update_task, pause_task, resolve_task_item, reconcile_task, start_task) - kromě jednoho rozdílu u startu.

start_task potvrzuje čísla

MCP start_task bere navíc confirmedRecipients a confirmedBudgetHal - musí přesně sedět na skutečný počet příjemců a rozpočet úkolu, jinak vrátí validation s vypsanými skutečnými hodnotami a NIC nespustí. Cizí AI agent na jednom nejasně pochopeném pokynu tak nemůže omylem rozjet placenou kampaň na vymyšlených číslech - REST POST .../start tuhle mechaniku nemá, protože tam volá přímo vlastník účtu vlastním klíčem.

delete_task a náhled CSV v MCP nejsou - smazání zůstává v REST a portálu (nevratná akce s opsáním názvu), CSV náhled je jen krok formuláře.

Chyby

  • task_not_found - Žádný úkol s tímhle id na účtu není.
  • task_not_editable - Úkol běží nebo je hotový (running/completed), nebo je needs_attention bez možnosti obnovy - pravidla teď měnit nejdou. Stejný kód vrací i POST .../items/{itemId}/resolve u běžícího úkolu: kontakt v review se objeví, už když úkol volá ostatní kontakty a je ještě running - počkej, až přejde do needs_attention, nebo ho pozastav (POST .../pause).
  • items_immutable - Seznam příjemců nejde nahradit poté, co se aspoň jeden pokus o vytočení odehrál.
  • budget_too_low - Rozpočet nepokryje ani jeden pokus v maximální délce hovoru - zvyš budgetHal nebo sniž maxDurationSecs.
  • task_not_startable - Úkol není ve stavu draft/ready/paused, nebo nemá žádného čekajícího příjemce.
  • task_needs_attention - Před dalším startem je potřeba POST .../reconcile - něco (verze agenta, motor) se za běhu změnilo.
  • agent_not_ready - Koncept agenta buď není publikovaný, nebo publikovaná verze není otestovaná - publikuj ho (POST /v1/agents/{id}/draft/publish) a pak otestuj publikovanou verzi (POST /v1/agents/{id}/simulate) před startem.
  • agent_version_changed - Konfigurace agenta se změnila BĚHEM běžícího úkolu - otestuj a restartuj přes reconcile.
  • engine_required - Agent úkolu není na motoru volai (useForOutboundTasks: true) - odchozí kampaně dnes vyžadují motor, ne ElevenLabs.
  • engine_contract_unavailable - Motor momentálně neinzeruje podporu pro proměnné a stropy odchozích úkolů - zkus start znovu později.
  • window_closed - Mimo nastavené volací okno - počkej na otevření, nebo uprav callingWindow.
  • task_not_running - Pauza jde jen u běžícího úkolu.
  • item_not_resolvable - Kontakt není ve stavu review - rozhodnout (retry/skip) jde jen o kontaktu čekajícím na kontrolu, a jen u úkolu, který neběží.
  • task_not_deletable - Smazat jde jen úkol bez položky in_progress ve stavu draft/ready/paused/completed/needs_attention.
  • revision_conflict - Úkol se mezitím změnil - načti ho znovu (GET) a zopakuj s aktuální revision.

Úplnou tabulku chyb pro každý endpoint najdeš v sekci Úkoly v REST API referenci.