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 → CallCampania ț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-contactsParametri și răspunsuri complete: referința campaniilor.
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
}'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}` },
});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
| Grup | Câmpuri |
|---|---|
| Țintă | target_type, assistant_id (și versiunea), squad_id |
| Inițiere | origination_type, phone_number_id sau sip_trunk_id, caller id |
| Fereastră | timezone, window_start_time, window_end_time, calling_days, starts_at, ends_at |
| Ritm | max_concurrency, dispatch_batch_size, max_dials_per_minute |
| Reîncercare | retry_policy: max_attempts, base_delay_minutes, backoff |
| Căsuță vocală | detect_voicemail, voicemail_behavior, voicemail_message |
| Filtrare | screening_mode, answer_timeout_seconds, max_call_seconds |
| Conformitate | un singur obiect compliance — vezi avertismentul de mai jos |
| Suprimare | suppression_list_ids, skip_suppressed |
| Variabile | variables — 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:
{ "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:
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.
| Control | Efect |
|---|---|
| Fereastra de apelare | Evaluată în ora locală a destinației, nu a organizației. |
| Liste de suprimare | Verificate la fiecare încercare. Administrate separat la /api/v1/suppression-lists. |
| Plafoane de încercări | Per contact, pe zi și în total. |
| Evidența de consimțământ | Stocată pe contact și repetată pe apel. |
| Politică de țară | allowed_countries / blocked_countries. |
| Abandon | Ritmul 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
- Telefonie — numerele de pe care sună o campanie.
- Analytics și costuri — rezultatele de campanie sunt analytics de apel.