Skip to content

Campanii ​

Apelare de ieșire în volum: o listă de contacte, un asistent, un program și controalele de conformitate care fac apelarea de ieșire legală. O campanie e un dispecer care decide pe cine sună următorul; fiecare încercare devine un apel obișnuit, cu id-ul campaniei pe el, deci analytics-ul de campanie e analytics de apel obișnuit, cu un filtru.

Modelul ​

Campaign  →  CampaignContact  →  CampaignAttempt  →  Call

Campania ține ținta și regulile. Fiecare contact își poartă propriul număr de telefon, fusul orar, variabilele, evidența de consimțământ și starea. Fiecare încercare reține tentativa — când a fost programată, când a fost dispecerizată, care a fost rezultatul și cât a costat.

API ​

GET    /api/v1/campaigns
POST   /api/v1/campaigns
GET    /api/v1/campaigns/{campaign}
PATCH  /api/v1/campaigns/{campaign}
DELETE /api/v1/campaigns/{campaign}

POST   /api/v1/campaigns/{campaign}/start
POST   /api/v1/campaigns/{campaign}/pause
POST   /api/v1/campaigns/{campaign}/resume
POST   /api/v1/campaigns/{campaign}/cancel

GET    /api/v1/campaigns/{campaign}/progress
GET    /api/v1/campaigns/{campaign}/results
GET    /api/v1/campaigns/{campaign}/contacts
POST   /api/v1/campaigns/{campaign}/contacts
POST   /api/v1/campaigns/{campaign}/contacts/upload
POST   /api/v1/campaigns/preview-contacts

Parametri și răspunsuri complete: referința campaniilor.

bash
curl -X POST "$CALLAGENT_API_URL/campaigns" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Reînnoiri ianuarie",
    "target_type": "assistant",
    "assistant_id": "01JQ…",
    "origination_type": "phone_number",
    "phone_number_id": "01JQ…",
    "timezone": "Europe/Bucharest",
    "max_concurrency": 5
  }'
ts
const campaign = await fetch(`${process.env.CALLAGENT_API_URL}/campaigns`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Reînnoiri ianuarie',
    target_type: 'assistant',
    assistant_id: assistantId,
    origination_type: 'phone_number',
    phone_number_id: numberId,
    timezone: 'Europe/Bucharest',
    max_concurrency: 5,
  }),
}).then((response) => response.json());

await fetch(`${process.env.CALLAGENT_API_URL}/campaigns/${campaign.data.id}/start`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}` },
});
python
import os, requests

auth = {"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"}
base = os.environ["CALLAGENT_API_URL"]

campaign = requests.post(f"{base}/campaigns", headers=auth, timeout=30, json={
    "name": "Reînnoiri ianuarie",
    "target_type": "assistant",
    "assistant_id": assistant_id,
    "origination_type": "phone_number",
    "phone_number_id": number_id,
    "timezone": "Europe/Bucharest",
    "max_concurrency": 5,
}).json()["data"]

requests.post(f"{base}/campaigns/{campaign['id']}/start", headers=auth, timeout=30)

Configurație ​

GrupCâmpuri
Țintătarget_type, assistant_id (și versiunea), squad_id
Inițiereorigination_type, phone_number_id sau sip_trunk_id, caller id
Fereastrătimezone, window_start_time, window_end_time, calling_days, starts_at, ends_at
Ritmmax_concurrency, dispatch_batch_size, max_dials_per_minute
Reîncercareretry_policy: max_attempts, base_delay_minutes, backoff
Căsuță vocalădetect_voicemail, voicemail_behavior, voicemail_message
Filtrarescreening_mode, answer_timeout_seconds, max_call_seconds
Conformitateun singur obiect compliance — vezi avertismentul de mai jos
Suprimaresuppression_list_ids, skip_suppressed
Variabilevariables — combinate în variableValues al fiecărui apel

Setările de conformitate trebuie imbricate

allowed_countries, blocked_countries, max_attempts și require_consent stau înăuntrul unui obiect compliance:

json
{ "compliance": {
    "allowed_countries": ["RO", "DE"],
    "require_consent": true,
    "max_attempts": 3
} }

Trimise plat, la nivelul de sus, sunt aruncate în tăcere — request-ul de creare validează doar un array compliance generic, deci nu există nicio eroare care să îți spună că au fost ignorate.

Contacte ​

Adaugă-le ca JSON, sau încarcă un CSV și previzualizează maparea înainte să te angajezi la ea:

bash
curl -X POST "$CALLAGENT_API_URL/campaigns/01JQ…/contacts" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contacts": [
        {"phone": "+40712345678", "name": "Ana", "variables": {"plan": "pro"}}
      ]}'

Câmpul e phone, nu phone_number

Câmpul validat pe un contact e contacts.*.phone, iar importatorul de CSV citește tot phone. Dacă trimiți phone_number, validarea pică cu „the phone field is required".

Un contact poartă external_id, phone, country, timezone, name, email, variables, o evidență de consent, un customer_id opțional și propria stare cu un motiv — deci „de ce nu a fost sunată persoana asta" are un răspuns per rând, nu per campanie.

Conformitate ​

Astea nu sunt extra. Ele sunt ce face apelarea de ieșire legală în majoritatea jurisdicțiilor.

ControlEfect
Fereastra de apelareEvaluată în ora locală a destinației, nu a organizației.
Liste de suprimareVerificate la fiecare încercare. Administrate separat la /api/v1/suppression-lists.
Plafoane de încercăriPer contact, pe zi și în total.
Evidența de consimțământStocată pe contact și repetată pe apel.
Politică de țarăallowed_countries / blocked_countries.
AbandonRitmul e plafonat ca rata de abandon să rămână sub limita configurată.

Listele de suprimare sunt resurse de sine stătătoare, cu propriile endpoint-uri de intrări — vezi referința de suprimare — deci o listă do-not-call poate fi împărțită de fiecare campanie și actualizată fără să atingi niciuna.

Dispecerul ​

Dispecerul decide pe cine sună următorul, dat fiind ritmul, spațiul de concurență și fereastra de apelare. Nu depășește niciodată concurența rezervată a organizației, deci o campanie nu poate înfometa apelurile de intrare, iar slotul se eliberează la finalizare chiar dacă apelul crapă.

Apelurile de campanie nu trec prin verificarea de admitere de facturare

Dispecerul e construit ca să primească o poartă de credit, ca apelurile unei campanii să treacă prin aceeași verificare de portofel ca orice alt apel de ieșire. Nimic nu leagă poarta aia. E înregistrată doar poarta de concurență, deci fiecare dispecerizare de campanie cade pe o poartă de credit goală, care citește un atribut brut credit_balance dacă există și altfel admite.

Concurența, fereastra de apelare, suprimarea și politica de destinație se aplică toate. Admiterea completă de facturare — plafonul zilnic de cheltuială, plafoanele de volum ale planului, blocajul de dunning, soldul din ledger — nu. Plafonează o campanie cu max_dials_per_minute și cu propriile ei limite de încercări, în loc să te bazezi pe portofel ca să o oprească.

Cum o urmărești ​

GET /campaigns/{campaign}/progress întoarce contoarele ținute de campanie — contacte total, în așteptare, finalizate, eșuate, sărite, și încercări total. GET /campaigns/{campaign}/results întoarce rezultatele. Pagina de campanie din dashboard le arată pe amândouă, plus istoricul încercare cu încercare.

Trei evenimente de campanie sunt livrate ca webhooks: campaign.started, campaign.contact_attempted și campaign.completed. Alea trei sunt tot vocabularul — nu există campaign.scheduled, campaign.dispatched, campaign.paused sau campaign.failed, și nu există niciun cod care să emită vreunul.

Pe apelul în sine, campaign.opt_out_detected e un eveniment de apel, nu un webhook.

Mai departe ​

Scrisă după cod. Ce nu este încă implementat este marcat ca atare, niciodată descris ca funcțional.