Kendi santralinizin REST API'si
CRM, muhasebe sistemi veya özel panel geliştirenler için: REST API aramaları ve kayıtları okur, çalışanın telefonundan arama başlatır ve santral ayarlarını yönetir. Hesap sahibi gereken yetkileri içeren bir anahtar oluşturur; entegrasyon onun adına ve yalnızca bu yetkilerin sınırları içinde işlem yapar. Anahtarlar, yetkiler, OAuth ve limitler Kendi santralinizin API ve MCP'si genel bakışında açıklanır.
Bu makalede: adres ve anahtar, hatalar, günlük, tıkla ara, dahililer, metrikler, kurallar, yapılandırma, olaylar, açık ve kapalı, senaryolar.
Adres ve anahtar#
API adresi https://api.cenaly.ge/pbx-api/v1 şeklindedir. Anahtar Authorization: Bearer cpx_… veya X-Api-Key: cpx_… başlığında gönderilir. Anahtarı istek adresinde göndermeyin ve tarayıcı JavaScript'ine koymayın.
BASE='https://api.cenaly.ge/pbx-api/v1'
KEY='cpx_…'
curl -sS "$BASE/me" -H "Authorization: Bearer $KEY"
Anahtarı hesap sahibi oluşturur: Telefon sistemi → API ve MCP → Anahtarlar → Anahtar oluştur. Gizli değer yalnızca bir kez gösterilir; bir sır yöneticisinde saklayın. Örneklerde gerçek sır yerine cpx_… yer tutucusu kullanılır.
| Yetki grubu | Yetkiler |
|---|---|
| CRM | calls:read, recordings:read, calls:dial, extensions:read |
| Yapay zekâ asistanı | CRM + extensions:write, presence:read |
| Salt okunur | calls:read, recordings:read, extensions:read, routes:read, presence:read, reports:read |
| Tümü | Ayrı onay gerektiren beş yetki dışındaki tüm normal yetkiler |
routes:write, config:export, config:import, webhooks:write, daynight:write hiçbir gruba dahil değildir; gerekiyorsa ayrı ayrı işaretleyin.
location, /locations yanıtındaki şube kimliğidir. GET için sorgu parametresiyle, POST/PUT/PATCH için JSON veya sorgu parametresiyle gönderilir. Anahtarın yalnızca bir şubeye erişimi varsa parametre atlanabilir; aksi halde 400 location_required yanıtı erişilebilen şubeleri listeler. Entegrasyonda location değerini her zaman göndermek daha iyidir: anahtara sonradan ikinci şube eklenebilir.
Teknik tanım: OpenAPI 3.1; örnekli açıklama: api.cenaly.ge/pbx-api/v1/docs. Bu makaledeki yanıt örnekleri kod temel alınarak kurgusal verilerle hazırlanmıştır; isteğe bağlı alanlar verilere ve santral ayarlarına bağlıdır.
Limitler ve hatalar#
Anahtar veya bağlantı başına dakikada 120, günde 20.000 istek; REST ve MCP bu limiti paylaşır. Tıkla arama ayrıca dakikada 10, günde 300 ile sınırlıdır. Günlük, istek başına en fazla 100 arama; olay akışı ise 500 olay döndürür.
Hata, error alanı içeren JSON'dur; örneğin {"error":"scope_required","scope":"calls:read"}. message alanının bulunacağını varsaymayın.
| HTTP | error örnekleri |
Ne yapmalı? |
|---|---|---|
| 400 | invalid_json, location_required, time_invalid, cursor_expired |
JSON'u veya parametreleri düzeltin; imlecin süresi dolmuşsa sorgulamayı baştan başlatın |
| 401 | api_key_missing, api_key_invalid |
Anahtarı, süresini ve iptal edilip edilmediğini kontrol edin; OAuth belirtecini yenileyin |
| 403 | scope_required, locations_outside_key |
Yetki verin; webhook şubelerini anahtarın şubeleriyle sınırlayın |
| 404 | not_found, location_not_found, call_not_found, recording_not_found |
Yolu, kimliği, anahtarın şubeye erişimini ve saklama aralığını kontrol edin |
| 409 | rev_conflict, transcript_in_progress, daynight_code_off |
Durumu yeniden okuyun; dökümün tamamlanmasını bekleyin; *28 koduyla kapatmayı açın |
| 429 | rate_limited |
Retry-After kadar saniye (60) bekleyin |
| 429 | dial_rate_limited |
Yanıt gövdesinde retryAfterSec: 60 vardır; bu hatada Retry-After başlığı bulunmaz |
| 503 | auth_unavailable |
Anahtar kontrolünde geçici hata; daha sonra tekrar deneyin |
Sonucu belirsiz bir ağ kesintisinden sonra arama başlatma isteğini otomatik tekrarlamayın: arama zaten başlamış olabilir. /dial için ayrı bir idempotensi anahtarı yoktur.
Anahtar ve şubeler#
| Yöntem | Yol | Yetki | Parametreler | Yanıt alanları |
|---|---|---|---|---|
| GET | /me |
Herhangi bir anahtar | — | key{id,name,hint,scopes,locations,expiresAt?}, limits, direct? |
| GET | /locations |
Herhangi bir anahtar | — | items[]{id,name,pbx,direct?}; pbx, null olabilir |
curl -sS "$BASE/me" -H "Authorization: Bearer $KEY"
curl -sS "$BASE/locations" -H "Authorization: Bearer $KEY"
Tüm şubelere erişen anahtarın /me yanıtı (locations: [], gelecekte eklenecekler dahil tüm şubeler demektir):
{"key":{"id":"pk_0123456789ab","name":"CRM","hint":"cpx_exampl","scopes":["calls:read"],"locations":[]},"limits":{"perMinute":120,"perDay":20000,"dialsPerMinute":10,"dialsPerDay":300},"direct":{"loc1":{"ready":false,"reason":"no_feature"}}}
Santrali yapılandırılmamış şube için /locations örneği:
{"items":[{"id":"loc1","name":"Главная точка","pbx":null}]}
Doğrudan santral adresi#
Kendi santrali olan şubede aynı API'yi santral sunucusunun kendisi de sunar: https://sip-<şube kimliği>.cenaly.ge/pbx-api/v1 (MCP — /pbx-api/mcp; şube kimliği küçük harfle). Arama günlüğü, olaylar, durum bilgisi ve tıkla-ara buluta uğramadan oradan yanıt verir. Adres Telefon → API ve MCP → Nasıl bağlanır penceresinde ve /me (şube kimliğine göre harita) ile /locations yanıtlarındaki direct alanında görünür: ready, rest, mcp, mcpReady; ready:false ise nedeni reason alanındadır (no_feature, no_host, lease). Santralin yanıtlarında X-Cenaly-Served-By: pbx başlığı bulunur.
Dökümler, metrikler, kurallar, yapılandırma ve webhook'lar bulutta kalır: santral bunlara cloudUrl ile 501 served_by_cloud yanıtı verir — isteği aynı anahtarla bu adrese tekrarlayın. 503 lease_expired veya direct_api_unavailable yanıtında (santral anahtarları almamış ya da bulutla doğrulamamış) da cloudUrl kullanın. Santralin günlüğü 30 gün ve 20 000 aramayı, bulutunki 90 gün ve 400 aramayı kapsar. Yeni anahtar santrale saniyeler içinde ulaşır (yeni bağlanan santralde 2–3 dakikaya kadar). https://api.cenaly.ge/pbx-api/v1 adresi tüm şubeler için yedek yol olarak çalışmaya devam eder. OAuth bağlantılarına direct verilmez: doğrudan adres yalnızca cpx_… anahtarlarını kabul eder.
Arama günlüğü#
| Yöntem | Yol | Yetki | Parametreler | Yanıt alanları |
|---|---|---|---|---|
| GET | /calls |
calls:read |
location, since, until, direction, status, number, extension, limit, cursor |
location, items, nextCursor?, window{maxCalls,maxDays,full} |
| GET | /calls/{id} |
calls:read |
location, yolda id |
location, steps[] içeren call |
| GET | /calls/{id}/recording |
recordings:read |
location, id |
callId,url,expiresIn,format |
| GET | /calls/{id}/transcript |
recordings:read |
location, id |
callId,recId,language?,piiRedacted?,text,segments[],summary? |
since dahil, until hariçtir: saat dilimi farkı içeren ISO zamanı yerel gün için uygundur. Yönler: inbound, outbound, internal, transfer. Durum, tam değer veya grup olabilir: answered, forwarded değerini de kapsar; missed, missed, busy, cancelled, abandoned değerlerini; ai ise bot, secretary, voicemail değerlerini kapsar. number numara rakamlarının bir kısmını arar; extension, çaldırılan tüm telefonların değil, taraflardan birinin veya yanıtlayanın dahilisidir.
Varsayılan limit=50, en fazla 100; en yeni aramalar önce gelir. Aldığınız nextCursor değerini değiştirmeden ve aynı filtrelerle, alan kaybolana kadar gönderin.
curl -sS -G "$BASE/calls" -H "Authorization: Bearer $KEY" \
--data-urlencode 'location=loc1' --data-urlencode 'status=missed' \
--data-urlencode 'since=2026-10-23T00:00:00+04:00' \
--data-urlencode 'until=2026-10-24T00:00:00+04:00' --data-urlencode 'limit=100'
{"location":"loc1","items":[{"id":"1792742400.1","startedAt":"2026-10-23T08:00:00Z","endedAt":"2026-10-23T08:00:20Z","direction":"inbound","status":"missed","statusGroup":"missed","from":{"number":"+995555123456"},"to":{"extension":"101"},"durationSec":20}],"window":{"maxCalls":400,"maxDays":90,"full":false}}
API, şubenin 90 gün içindeki son 400 aramasını görür (window); full:true, pencerenin dolduğu ve eski aramaların artık görünmediği anlamına gelir. Tam arşiv gerekiyorsa günlüğü kendi tarafınızda saklayın.
curl -sS "$BASE/calls/1792742400.1?location=loc1" -H "Authorization: Bearer $KEY"
curl -sS "$BASE/calls/1792742400.2/recording?location=loc1" -H "Authorization: Bearer $KEY"
curl -sS "$BASE/calls/1792742400.2/transcript?location=loc1" -H "Authorization: Bearer $KEY"
{"location":"loc1","call":{"id":"1792742400.1","startedAt":"2026-10-23T08:00:00Z","endedAt":"2026-10-23T08:00:20Z","direction":"inbound","status":"missed","statusGroup":"missed","from":{"number":"+995555123456"},"to":{"extension":"101"},"durationSec":20,"steps":[]}}
Kayıt isteğinin yanıtı:
{"callId":"1792742400.2","url":"https://example.com/temporary-audio","expiresIn":600,"format":"wav"}
Buradaki URL yer tutucudur; verilen bağlantıyı aynen kullanın. Bağlantı geçicidir: 10 dakika, kesin süre expiresIn içindedir. Döküm:
{"callId":"1792742400.2","recId":"rec-example","language":"ru","text":"Здравствуйте.","segments":[{"speaker":"1","start":0,"end":1.2,"text":"Здравствуйте."}]}
404 transcript_not_ready ve 409 transcript_in_progress farklı aşamalardır: döküm henüz yoktur veya şu anda oluşturuluyordur. summary isteğe bağlıdır; metni göstermek için bunu zorunlu tutmayın.
Tıkla ara#
| Yöntem | Yol | Yetki | Parametreler | Yanıt alanları |
|---|---|---|---|---|
| POST | /dial |
calls:dial |
location, extension, number |
commandId,location,extension,number,e164?,state,fast |
| GET | /dial/{commandId} |
calls:dial |
location |
commandId,state,stage,reason? |
Önce çalışanın telefonu çalar; yanıtladığında santral harici numarayı arar. Giden arama izinleri ve şubenin kısıtlamaları geçerliliğini korur.
curl -sS "$BASE/dial" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"location":"loc1","extension":"101","number":"+995555123456"}'
curl -sS "$BASE/dial/cb-0123456789abcdef0123?location=loc1" -H "Authorization: Bearer $KEY"
{"commandId":"cb-0123456789abcdef0123","location":"loc1","extension":"101","number":"+995555123456","state":"queued","fast":true}
state:"queued", komutun kabul edildiğini belirtir; görüşmenin gerçekleştiğini değil. fast:false, ilk aramanın santralle bağlantı için bir dakikaya kadar bekleyebileceği uyarısıdır; sonraki birkaç dakika boyunca komutlar saniyeler içinde gider. Aramayı commandId ile izleyin:
stage |
Ne oluyor? |
|---|---|
queued |
Komut santrali bekliyor |
sent |
Santral komutu aldı |
calling_you |
Çalışanın telefonu çalıyor |
dialing_client |
Çalışan yanıtladı, santral müşteriyi arıyor |
connected |
Müşteri yanıtladı |
ended |
Arama sona erdi |
failed |
İşlem başarısız; neden reason alanında |
{"commandId":"cb-0123456789abcdef0123","state":"ringing","stage":"dialing_client"}
Görüşmenin sonucunu günlük veya olaylarla karşılaştırın.
Dahililer, yönlendirme, DND ve müsaitlik#
| Yöntem | Yol | Yetki | Parametreler | Yanıt alanları |
|---|---|---|---|---|
| GET | /extensions |
extensions:read |
location |
location,items[]{extension,kind,name,enabled,forwarding?,dndRequested?,presence?,external?} |
| PUT | /extensions/{ext}/forwarding |
extensions:write |
location, forward |
location,extension,forwarding,supported? |
| PUT | /extensions/{ext}/dnd |
extensions:write |
location, on, until? |
location,extension,dnd |
| GET | /presence |
presence:read |
location |
location,live,items |
kind, person, group veya room olabilir. Dahili listesindeki presence yalnızca presence:read yetkisiyle görünür. live:false, bilgilerin santralin canlı durumunu yansıtmadığı uyarısıdır (örneğin santral uzun süredir durum görüntüsü göndermemiştir).
curl -sS "$BASE/extensions?location=loc1" -H "Authorization: Bearer $KEY"
curl -sS -X PUT "$BASE/extensions/102/forwarding" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"location":"loc1","forward":{"always":{"kind":"extension","extension":"101"}}}'
curl -sS -X PUT "$BASE/extensions/102/dnd" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"location":"loc1","on":true,"until":"morning"}'
curl -sS "$BASE/presence?location=loc1" -H "Authorization: Bearer $KEY"
{"location":"loc1","items":[{"extension":"101","kind":"person","name":"Анна","enabled":true,"forwarding":null}]}
{"location":"loc1","extension":"102","forwarding":{"always":{"kind":"extension","extension":"101"}},"supported":true}
Boş müsaitlik listesi:
{"location":"loc1","live":true,"items":[]}
forward içinde always, busy, noAnswer, offline, dnd, offShift koşulları ve noAnswerSec sayısı kullanılabilir. Hedef {kind:"extension",extension}, {kind:"number",number} veya {kind:"voicemail"} olur. forward:null yönlendirmeyi kaldırır. İstenen nesnenin tamamını gönderin: bu, kısmi güncelleme değildir.
Santral isteği henüz uygularken DND'yi kaldırma yanıtı:
{"location":"loc1","extension":"102","dnd":{"on":true,"requested":{"on":false,"at":1792836000},"appliedAt":1792835900,"pending":true,"supported":true,"until":null,"timer":true}}
DND: on:false modu kapatır; açarken until, Unix saniyesi veya "morning" (sonraki vardiyanın başlangıcı, yoksa yerel saatle 08:00) olabilir; santral modu kendisi kapatır. dnd, hem isteği hem uygulanma durumunu gösterir: pending:true iken kullanıcıya “tamamlandı” demeyin.
Metrikler#
| Yöntem | Yol | Yetki | Parametreler | Yanıt alanları |
|---|---|---|---|---|
| GET | /metrics |
reports:read |
location, from, to, compare, sections, callbacks |
location,tz,today,history,range,coverage,sl, seçili bölümler, compare, compareSkipped?, callbacks? |
from/to, şubenin yerel YYYY-MM-DD tarihleridir; iki uç da dahildir. Verilmezse son 7 gün alınır. compare: prev, year, prev,year veya boş. Bölümler: metrics,loss,queues,lines,operators,directions,heatmap,daily; heatmap ve daily yalnızca açıkça istendiğinde gelir. callbacks=true, geri arama hızını ekler (en fazla 92 günlük dönemler için). Önceki yılla karşılaştırma en fazla 400 günlük dönemlerde yapılır.
curl -sS "$BASE/metrics?location=loc1&from=2026-09-01&to=2026-09-30&compare=prev,year" -H "Authorization: Bearer $KEY"
Veri bulunmayan dönem örneği:
{"location":"loc1","tz":"Asia/Tbilisi","today":"2026-10-24","history":{"since":null,"gaps":[]},"range":{"from":"2026-10-23","to":"2026-10-23"},"coverage":{"periodDays":1,"days":0,"v2Days":0},"sl":{"slaSec":20,"targetPct":80,"shortAbandonSec":5},"queues":[],"compare":{}}
Göstergeler yanıtın kökündeki metrics alanındadır; Telefon sistemi → Arama günlüğü → Analitik ekranıyla aynı değerlerdir: gelen, yanıtlanan ve cevapsız aramalar, hizmet seviyesi, bekleme, vazgeçilen aramalar, AHT, FCR, CSAT. Veri boşluğunu arama yokluğu sanmamak için coverage ve history.gaps alanlarını kontrol edin.
Arama kuralları#
Yollar /pbx-api/v1 adresine göredir; anahtarın birden fazla şubesi varsa location zorunludur.
| Yöntem | Yol | Yetki | Parametreler | Yanıt alanları |
|---|---|---|---|---|
| GET | /routes |
routes:read |
location |
location,lines,trunks,inbound,outbound |
| GET | /routes/history |
routes:read |
location, section=inbound|outbound |
location,section,status,items[]{versionId,at,current,summary},truncated? |
| POST | /routes/inbound |
routes:write |
location, rule, position? |
location,rule,order,rev,warnings? |
| PUT | /routes/inbound/{id} |
routes:write |
location, rule, position? |
Aynı alanlar |
| DELETE | /routes/inbound/{id} |
routes:write |
location |
location,order,rev |
| POST | /routes/inbound/{id}/move |
routes:write |
location, position |
location,rule,order,rev,warnings? |
| POST | /routes/outbound/rules |
routes:write |
location, rule, position? |
location,rule,outbound,rev |
| PUT | /routes/outbound/rules/{id} |
routes:write |
location, rule, position? |
Aynı alanlar |
| DELETE | /routes/outbound/rules/{id} |
routes:write |
location |
location,outbound,rev |
| POST | /routes/outbound/rules/{id}/move |
routes:write |
location, position |
location,rule,outbound,rev |
| PATCH | /routes/outbound |
routes:write |
location, enabled?, fallback? |
location,outbound,rev |
| POST | /routes/history/restore |
routes:write |
location, section, versionId |
location,section,versionId,restored |
position 1'den başlar. Kural düzenleme yalnızca gönderilen alanları değiştirir; null alanı siler. Kural alanları gövdenin kökünde veya rule nesnesinde gönderilebilir. İlk eşleşen gelen arama kuralı uygulanır; warnings:["after_catch_all"], yukarıda koşulsuz bir kural bulunduğu ve yeni kuralın çalışmayacağı anlamına gelir.
Gelen arama rule alanları: name, enabled, match, when, action, extension, botId, forwardNumber, menuId, fallback. match.kind: any, time (days, from, to), callerPrefix, line (lineId), trunk (trunkId). Aramayı gruba yönlendirmek için action:"extension" ve grubun dahilisini kullanın; çalışanlar arasındaki karmaşık dağıtım yönetim panelinde ayarlanır.
Giden arama rule alanları: name, enabled, pattern, via, scope, stripDigits, prefix, backupVia, backupVias. Kalıp: rakamlar, X (0–9), N (2–9), Z (1–9), başta +, sonda nokta. via, {kind:"line",lineId} veya {kind:"trunk",trunkId}; scope, {kind:"all"} veya {kind:"extensions",extensions:["101"]} olur. Anahtar, izin verilen ülkeleri ve dolandırıcılık korumasını değiştiremez.
curl -sS "$BASE/routes?location=loc1" -H "Authorization: Bearer $KEY"
curl -sS -X PUT "$BASE/routes/inbound/r-example" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"location":"loc1","rule":{"name":"Дежурный"}}'
curl -sS "$BASE/routes/history?location=loc1§ion=inbound" -H "Authorization: Bearer $KEY"
# versionId — из истории, id правила — из /routes.
curl -sS "$BASE/routes/history/restore" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"location":"loc1","section":"inbound","versionId":"version-example"}'
Boş santralin kuralları:
{"location":"loc1","lines":[],"trunks":[],"inbound":[],"outbound":null}
Kural düzenleme yanıtı:
{"location":"loc1","rule":{"id":"r-example","order":1,"name":"Дежурный","enabled":true,"match":{"kind":"any"},"action":"extension","extension":"101"},"order":["r-example"],"rev":12}
Geri alma yeni bir sürüm oluşturur: {"location":"loc1","section":"inbound","versionId":"version-example","restored":true}. Çakışma olursa değişikliği tekrarlamadan önce kuralları ve geçmişi yeniden okuyun.
Yapılandırmayı dışa ve içe aktarma#
| Yöntem | Yol | Yetki | Parametreler | Yanıt alanları |
|---|---|---|---|---|
| GET | /config/export |
config:export |
location, tables? (virgülle ayrılmış) |
location,rev,tables |
| POST | /config/import |
config:import |
location, tables, sync?, apply?, baseRev? |
location,applied,changed,rev,tables,errors,newAccounts?,created?,passwords? |
Tablolar yönetim panelindeki dışa aktarmayla aynı biçimdedir: dahililer, gruplar, hatlar, gelen ve giden aramalar. Şifreler dışa aktarılmaz. İçe aktarma tables değerini satır dizileri veya dışa aktarmadaki {columns,rows} nesneleri olarak kabul eder.
curl -fsS "$BASE/config/export?location=loc1" -H "Authorization: Bearer $KEY" > export.json
# Отредактируйте export.json. Предпросмотр ничего не применяет.
jq '{location:"loc1",tables:.tables}' export.json > import.json
curl -fsS "$BASE/config/import" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
--data-binary @import.json > preview.json
cat preview.json
# Только после проверки tables и errors:
jq --slurpfile p preview.json '. + {apply:true,baseRev:$p[0].rev}' import.json > apply.json
curl -fsS "$BASE/config/import" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' --data-binary @apply.json
Boş dahili listesinin tables=extensions ile dışa aktarımı:
{"location":"loc1","rev":12,"tables":{"extensions":{"columns":["extension","name","type","external_number","via","staff_email","staff_name","groups","recording","voicemail","voicemail_email","enabled"],"rows":[]}}}
Değişiklik içermeyen önizleme:
{"location":"loc1","applied":false,"changed":false,"rev":12,"tables":[{"table":"extensions","added":0,"changed":0,"removed":0,"unchanged":0,"errors":0,"rows":[]}],"errors":0,"newAccounts":0}
Revizyon belirtilmeden uygulama reddedilir:
{"error":"base_rev_required","hint":"Run import_config without apply first and pass its rev as baseRev."}
Önizlemede applied:false olur; errors hata sayısıdır, ayrıntılar tables[].rows[] içindedir. Hatalı satırlar uygulama sırasında atlanır: yalnızca HTTP kodunu değil, raporun tamamını okuyun. Başarılı uygulamadan sonra applied:true olur, rev yeni revizyondur; değiştirilecek bir şey yoksa applied, false kalır. sync:{"<tablo>":true}, dosyada bulunmayan satırları siler; bilinçli olarak açın. Revizyon çakışmasında eski uygulama isteğine yeni numara koymak yerine yeni önizleme alın. Yeni dahililerin şifreleri yalnızca yönetim panelinde bulunur (passwords:"admin_only").
Olaylar ve webhook'lar#
| Yöntem | Yol | Yetki | Parametreler | Yanıt alanları |
|---|---|---|---|---|
| GET | /events |
calls:read |
location, since, types, limit |
location,events,more,next? |
| GET | /webhooks |
webhooks:write |
— | webhooks,events,limits,delivery |
| POST | /webhooks |
webhooks:write |
name, url, events, locations, enabled |
ok,webhook,secret,created |
| PATCH | /webhooks/{id} |
webhooks:write |
Değiştirilecek alanlar | ok,webhook,created |
| DELETE | /webhooks/{id} |
webhooks:write |
— | ok,id |
| POST | /webhooks/{id}/rotate-secret |
webhooks:write |
— | ok,webhook,secret |
| POST | /webhooks/{id}/test |
webhooks:write |
— | ok,id,queued,reason? |
| GET | /webhooks/{id}/deliveries |
webhooks:write |
— | id,deliveries,health? |
curl -sS "$BASE/events?location=loc1&limit=100" -H "Authorization: Bearer $KEY"
curl -sS "$BASE/webhooks/wh_0123456789ab/deliveries" -H "Authorization: Bearer $KEY"
{"location":"loc1","events":[],"more":false}
Olaylar eskiden yeniye sıralanır; sonraki istekte since, önceki yanıtın next değeridir; bu, more:true durumunda da geçerlidir. types virgülle ayrılmış listedir. Akış şube başına 7 gün içindeki en fazla 2.000 olayı saklar. Olay gövdeleri, imza ve yeniden denemeler Arama olayları webhook'ları makalesindedir.
Açık ve kapalı#
| Yöntem | Yol | Yetki | Parametreler | Yanıt alanları |
|---|---|---|---|---|
| GET | /open-closed |
routes:read |
location |
location,codeEnabled,remoteSupported,closed,closedUntil?,closedBy?,request? |
| PUT | /open-closed |
daynight:write |
location, closed, until? |
ok, rev ve şubenin durumu |
curl -sS "$BASE/open-closed?location=loc1" -H "Authorization: Bearer $KEY"
curl -sS -X PUT "$BASE/open-closed" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"location":"loc1","closed":true,"until":"2026-10-25T09:00:00+04:00"}'
{"location":"loc1","codeEnabled":true,"remoteSupported":true,"closed":false}
Bulut isteği saniyeler içinde santrale iletir: request.applied, false olduğu sürece santral isteği henüz uygulamamıştır. until olmadan şube yerel günün sonuna kadar kapalı kalır. Saat dilimi farkı olmayan zaman şubenin saat diliminde yorumlanır; süre en fazla 30 gündür, ardından şube kendiliğinden açılır. closed:false şubeyi açar ve until değerini kaldırır. Yönlendirmedeki “*28 ile kapat” ayarı açık olmalıdır; değilse 409 daynight_code_off döner. Kapatma, telefondan *28 tuşlamakla aynıdır: aramalar “çalışma saatleri dışında” kurallarına gider, sunucu kapanmaz.
Hazır senaryolar#
Dünün günlüğünü CSV'ye aktarma: Python#
requests kurun. PBX_KEY, PBX_TZ (şubenin saat dilimi, örneğin Asia/Tbilisi) ve PBX_LOCATION değerlerini ayarlayın; betiği > calls.csv yönlendirmesiyle çalıştırın. Tüm sayfaları okur, CSV kaçışlarını uygular ve elektronik tablo formüllerini etkisizleştirir. Tam arşiv değil, günlük penceresinde bulunanlar dışa aktarılır.
import csv, os, sys
from datetime import datetime, time, timedelta
from zoneinfo import ZoneInfo
import requests
base = 'https://api.cenaly.ge/pbx-api/v1'
tz = ZoneInfo(os.environ['PBX_TZ'])
today = datetime.now(tz).date()
params = {'location': os.environ['PBX_LOCATION'], 'limit': 100,
'since': datetime.combine(today - timedelta(days=1), time.min, tz).isoformat(),
'until': datetime.combine(today, time.min, tz).isoformat()}
headers = {'Authorization': 'Bearer ' + os.environ['PBX_KEY']}
out = csv.writer(sys.stdout)
out.writerow(['id', 'startedAt', 'from', 'to', 'status', 'durationSec'])
def cell(value):
s = str(value if value is not None else '')
return "'" + s if s.startswith(('=', '+', '-', '@', '\t', '\r', '\n')) else s
while True:
r = requests.get(base + '/calls', params=params, headers=headers, timeout=20)
r.raise_for_status()
page = r.json()
for c in page['items']:
out.writerow(map(cell, [c['id'], c.get('startedAt'),
c.get('from', {}).get('number', ''), c.get('to', {}).get('extension', ''),
c['status'], c.get('durationSec', 0)]))
if not page.get('nextCursor'):
break
params['cursor'] = page['nextCursor']
CRM'den tıkla ara: Node.js#
Kod CRM sunucusunda çalışır (Node.js 18+, .mjs dosyası); anahtar PBX_KEY içindedir. Çağrıyı yetkili çalışanın açıkça düğmeye basmasına bağlayın ve CRM'de çift tıklamayı engelleyin.
const base = 'https://api.cenaly.ge/pbx-api/v1';
const headers = {'Authorization': `Bearer ${process.env.PBX_KEY}`,
'Content-Type': 'application/json'};
const r = await fetch(`${base}/dial`, {method: 'POST', headers,
body: JSON.stringify({location: 'loc1', extension: '101', number: '+995555123456'}),
signal: AbortSignal.timeout(20000)});
const result = await r.json();
if (!r.ok) throw new Error(JSON.stringify(result));
// Сохраните commandId в действии CRM; POST автоматически не повторяйте.
console.log({commandId: result.commandId, state: result.state});
const status = await fetch(`${base}/dial/${encodeURIComponent(result.commandId)}?location=loc1`,
{headers, signal: AbortSignal.timeout(20000)});
if (!status.ok) throw new Error(await status.text());
console.log(await status.json());
Zamanlanmış gece modu: curl#
Zamanlayıcı komutu akşam çalıştırır; UNTIL, en fazla 30 gün sonrası için saat dilimi farkı içeren ISO biçimindeki bir sonraki açılış zamanıdır (24 Ekim'i 25 Ekim'e bağlayan gece için 2026-10-25T09:00:00+04:00). Her çalıştırmada tarihi yeniden hesaplayın.
BASE='https://api.cenaly.ge/pbx-api/v1'
: "${PBX_KEY:?Задайте ключ}" "${UNTIL:?Задайте время открытия}"
BODY=$(jq -nc --arg until "$UNTIL" '{location:"loc1",closed:true,until:$until}')
curl -fsS -X PUT "$BASE/open-closed" -H "Authorization: Bearer $PBX_KEY" \
-H 'Content-Type: application/json' -d "$BODY" | jq -e 'if .error then error(.error) else . end'
Şubeyi daha erken açmak için aynı PUT isteğini {"location":"loc1","closed":false} ile gönderin. Durumu GET ile kontrol edin. Harici zamanlayıcı kullanmadan düzenli çalışma saatleri, yönetim panelindeki yönlendirme takvimiyle ayarlanır.