დაიწყეთ უფასოდ

საკუთარი ტელეფონური სადგურის REST API

სატელეფონო სისტემის REST API-ის ყველა მეთოდი უფლებებით, პარამეტრებითა და პასუხებით: ზარების ჟურნალი, ჩაწერები და ტრანსკრიპტები, ზარი ერთი დაწკაპუნებით, გადამისამართება და „ნუ შემაწუხებ“, მეტრიკები, ზარების წესები, პარამეტრების იმპორტი, შეცდომები და მზა სცენარები

14 min read ადმინ პანელის დემო
On this page 13

საკუთარი სატელეფონო სადგურის REST API

CRM-ის, სააღრიცხვო სისტემის ან საკუთარი პანელის დეველოპერისთვის: REST API კითხულობს ზარებსა და ჩანაწერებს, იწყებს ზარს თანამშრომლის ტელეფონიდან და მართავს სატელეფონო სადგურის პარამეტრებს. მფლობელი ქმნის გასაღებს საჭირო უფლებებით — ინტეგრაცია მისი სახელით და მხოლოდ ამ უფლებების ფარგლებში მოქმედებს. გასაღებები, უფლებები, OAuth და ლიმიტები — მიმოხილვაში საკუთარი სატელეფონო სადგურის API და MCP.

სტატიაში: მისამართი და გასაღები, შეცდომები, ჟურნალი, ზარი ერთი დაწკაპუნებით, შიდა ნომრები, მეტრიკები, წესები, კონფიგურაცია, მოვლენები, ღია და დახურული, სცენარები.

მისამართი და გასაღები#

API-ის მისამართია https://api.cenaly.ge/pbx-api/v1. გასაღები გადაეცემა სათაურით Authorization: Bearer cpx_… ან X-Api-Key: cpx_…. გასაღები მოთხოვნის მისამართში არ გადასცეთ და ბრაუზერის JavaScript-ში არ ჩადოთ.

BASE='https://api.cenaly.ge/pbx-api/v1'
KEY='cpx_…'
curl -sS "$BASE/me" -H "Authorization: Bearer $KEY"

გასაღებს მფლობელი ქმნის: ტელეფონია → API და MCP → გასაღებები → გასაღების შექმნა. საიდუმლო ერთხელ ჩანს; შეინახეთ საიდუმლოების მენეჯერში. მაგალითებში საიდუმლოს ნაცვლად ჩანაცვლების ნიმუშია cpx_….

ნაკრები უფლებები
CRM calls:read, recordings:read, calls:dial, extensions:read
AI-ასისტენტი CRM + extensions:write, presence:read
მხოლოდ კითხვა calls:read, recordings:read, extensions:read, routes:read, presence:read, reports:read
ყველაფერი ყველა ჩვეულებრივი უფლება, ცალკე თანხმობის ხუთი უფლების გარდა

უფლებები routes:write, config:export, config:import, webhooks:write, daynight:write არცერთ ნაკრებში არ შედის — ცალკე მონიშნეთ, თუ გჭირდებათ.

location — ობიექტის id /locations-იდან. GET-ისთვის ის query-ში გადაეცემა, POST/PUT/PATCH-ისთვის — JSON-ში ან query-ში. თუ გასაღებს ზუსტად ერთი ობიექტი აქვს ხელმისაწვდომი, პარამეტრის გამოტოვება შეიძლება; წინააღმდეგ შემთხვევაში პასუხი 400 location_required ხელმისაწვდომ ობიექტებს ჩამოთვლის. ინტეგრაციაში უმჯობესია location ყოველთვის გადასცეთ: გასაღებს შეიძლება მეორე ობიექტიც დაემატოს.

სპეციფიკაცია: OpenAPI 3.1; აღწერა მაგალითებით — api.cenaly.ge/pbx-api/v1/docs. ამ სტატიაში პასუხების მაგალითები კოდის მიხედვით, გამოგონილ მონაცემებზეა შედგენილი; არასავალდებულო ველები მონაცემებსა და სატელეფონო სადგურის პარამეტრებზეა დამოკიდებული.

ლიმიტები და შეცდომები#

120 მოთხოვნა წუთში და 20 000 დღეში თითო გასაღებზე ან კავშირზე — REST-ისა და MCP-ის საერთო ბიუჯეტია. ერთი დაწკაპუნებით ზარს დამატებითი შეზღუდვა აქვს: 10 წუთში და 300 დღეში. ჟურნალი ერთ მოთხოვნაზე 100-მდე ზარს აბრუნებს, მოვლენების ლენტა — 500-მდე.

შეცდომა — JSON ველით error, მაგალითად {"error":"scope_required","scope":"calls:read"}. ველ message-ზე ნუ იქნებით დამოკიდებული.

HTTP error-ის მაგალითები რა გააკეთოთ
400 invalid_json, location_required, time_invalid, cursor_expired გაასწორეთ JSON ან პარამეტრები; კურსორის ვადის გასვლისას შერჩევა თავიდან დაიწყეთ
401 api_key_missing, api_key_invalid შეამოწმეთ გასაღები, ვადა და გაუქმება; OAuth ტოკენი — განაახლეთ
403 scope_required, locations_outside_key მიანიჭეთ უფლება; ვებჰუკის ობიექტები გასაღების ობიექტებამდე შეზღუდეთ
404 not_found, location_not_found, call_not_found, recording_not_found შეამოწმეთ გზა, id, გასაღების წვდომა ობიექტზე და შენახვის პერიოდი
409 rev_conflict, transcript_in_progress, daynight_code_off ხელახლა წაიკითხეთ მდგომარეობა; დაელოდეთ გაშიფვრას; ჩართეთ კოდით *28 დახურვა
429 rate_limited დაელოდეთ Retry-After წამს (60)
429 dial_rate_limited პასუხის სხეულშია retryAfterSec: 60; ამ შეცდომას Retry-After სათაური არ აქვს
503 auth_unavailable გასაღების შემოწმების დროებითი შეფერხება — მოგვიანებით გაიმეორეთ

ზარის გაშვება ავტომატურად არ გაიმეოროთ ქსელის გაწყვეტის შემდეგ, როცა შედეგი უცნობია: ზარი შეიძლება უკვე დაწყებული იყოს. /dial-ს იდემპოტენტურობის ცალკე გასაღები არ აქვს.

გასაღები და ობიექტები#

მეთოდი გზა უფლება პარამეტრები პასუხის ველები
GET /me ნებისმიერი გასაღები — key{id,name,hint,scopes,locations,expiresAt?}, limits, direct?
GET /locations ნებისმიერი გასაღები — items[]{id,name,pbx,direct?}; pbx შეიძლება იყოს null
curl -sS "$BASE/me" -H "Authorization: Bearer $KEY"
curl -sS "$BASE/locations" -H "Authorization: Bearer $KEY"

/me პასუხი ყველა ობიექტის გასაღებისთვის (locations: [] — ყველა ობიექტი, მათ შორის მომავალიც):

{"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"}}}

/locations მაგალითი ობიექტისთვის, რომელსაც გამართული სატელეფონო სადგური არ აქვს:

{"items":[{"id":"loc1","name":"Главная точка","pbx":null}]}

სატელეფონო სადგურის პირდაპირი მისამართი#

საკუთარი სატელეფონო სადგურის მქონე ობიექტში იმავე API-ს თავად სადგურის სერვერიც ემსახურება: https://sip-<ობიექტის id>.cenaly.ge/pbx-api/v1 (MCP — /pbx-api/mcp; id პატარა ასოებით). ზარების ჟურნალი, მოვლენები, ხელმისაწვდომობა და ზარი ერთი დაწკაპუნებით იქიდან პასუხობს, ღრუბლის გვერდის ავლით. მისამართი ჩანს ფანჯარაში ტელეფონია → API და MCP → როგორ მივუერთდეთ და ველში direct პასუხებში /me (ობიექტის id-ით შედგენილი რუკა) და /locations: ready, rest, mcp, mcpReady; როცა ready:false, მიზეზი reason-შია (no_feature, no_host, lease). სადგურის პასუხებს აქვს სათაური X-Cenaly-Served-By: pbx.

ტრანსკრიფციები, მეტრიკები, წესები, კონფიგურაცია და ვებჰუკები ღრუბელში რჩება: სადგური პასუხობს 501 served_by_cloud-ით და cloudUrl-ით — გაიმეორეთ მოთხოვნა ამ მისამართზე იმავე გასაღებით. 503 lease_expired ან direct_api_unavailable (სადგურს გასაღებები არ მიუღია ან ღრუბელთან არ დაუდასტურებია) — ასევე cloudUrl. სადგურის ჟურნალი მოიცავს 30 დღესა და 20 000 ზარს, ღრუბლისა — 90 დღესა და 400 ზარს. ახალი გასაღები სადგურამდე წამებში აღწევს (ახლად მიერთებულ სადგურზე — 2–3 წუთამდე). მისამართი https://api.cenaly.ge/pbx-api/v1 ყველა ობიექტისთვის მუშაობს — სარეზერვო გზად. OAuth-კავშირებს direct არ გადაეცემა: პირდაპირი მისამართი მხოლოდ cpx_… გასაღებებს იღებს.

ზარების ჟურნალი#

მეთოდი გზა უფლება პარამეტრები პასუხის ველები
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, id გზაში location, call ველით steps[]
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 — ჩათვლით, until — გამოკლებით: წანაცვლებიანი ISO-დრო მოსახერხებელია ადგილობრივი დღისთვის. მიმართულებებია inbound, outbound, internal, transfer. სტატუსი — ზუსტი მნიშვნელობა ან ჯგუფი: answered მოიცავს forwarded-ს; missed — missed, busy, cancelled, abandoned; ai — bot, secretary, voicemail. number ნომრის ციფრების ნაწილს ეძებს, extension — მხარის ან მოპასუხის შიდა ნომერს და არა ყველა ტელეფონს, რომელზეც დარეკეს.

ნაგულისხმევად limit=50, მაქსიმუმია 100; ახალი ზარები პირველად ჩანს. მიღებული nextCursor უცვლელად და იმავე ფილტრებით გადაეცით, სანამ ველი არ გაქრება.

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 ხედავს ობიექტის ბოლო 400 ზარს 90 დღის განმავლობაში (window); full:true ნიშნავს, რომ ფანჯარა შევსებულია და ძველი ზარები აღარ ჩანს. თუ სრული არქივი გჭირდებათ, ჟურნალი თქვენთან შეინახეთ.

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":[]}}

ჩანაწერის მოთხოვნის პასუხი:

{"callId":"1792742400.2","url":"https://example.com/temporary-audio","expiresIn":600,"format":"wav"}

აქ URL მაგალითია; გამოიყენეთ ზუსტად მიღებული ბმული. ის დროებითია — 10 წუთით, ზუსტი ვადა მითითებულია expiresIn-ში. ტრანსკრიფცია:

{"callId":"1792742400.2","recId":"rec-example","language":"ru","text":"Здравствуйте.","segments":[{"speaker":"1","start":0,"end":1.2,"text":"Здравствуйте."}]}

404 transcript_not_ready და 409 transcript_in_progress სხვადასხვა ეტაპია: ტრანსკრიფცია ჯერ არ არსებობს ან სწორედ ახლა მიმდინარეობს. summary არასავალდებულოა — ტექსტის საჩვენებლად მის არსებობას ნუ მოითხოვთ.

დაწკაპუნებით დარეკვა#

მეთოდი გზა უფლება პარამეტრები პასუხის ველები
POST /dial calls:dial location, extension, number commandId,location,extension,number,e164?,state,fast
GET /dial/{commandId} calls:dial location commandId,state,stage,reason?

ჯერ თანამშრომლის ტელეფონი რეკავს, მისი პასუხის შემდეგ სატელეფონო სადგური გარე ნომერს კრეფს. გამავალი ზარების უფლებები და ობიექტის შეზღუდვები კვლავ მოქმედებს.

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" ნიშნავს, რომ ბრძანება მიღებულია და არა იმას, რომ საუბარი შედგა. fast:false აფრთხილებს, რომ პირველ ზარს სატელეფონო სადგურთან კავშირის დამყარებამდე შეიძლება ერთ წუთამდე დასჭირდეს; შემდეგ რამდენიმე წუთში ბრძანებები წამებში იგზავნება. ზარის მიმდინარეობა შეამოწმეთ commandId-ით:

stage რა ხდება
queued ბრძანება სატელეფონო სადგურს ელოდება
sent სატელეფონო სადგურმა ბრძანება აიღო
calling_you თანამშრომლის ტელეფონი რეკავს
dialing_client თანამშრომელმა უპასუხა, სატელეფონო სადგური კლიენტს კრეფს
connected კლიენტმა უპასუხა
ended ზარი დასრულდა
failed ვერ შესრულდა; მიზეზი reason-შია
{"commandId":"cb-0123456789abcdef0123","state":"ringing","stage":"dialing_client"}

საუბრის საბოლოო შედეგი შეადარეთ ჟურნალს ან მოვლენებს.

დამატებითი ნომრები, გადამისამართება, DND და სტატუსი#

მეთოდი გზა უფლება პარამეტრები პასუხის ველები
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 ან room. დამატებითი ნომრების სიაში presence მხოლოდ presence:read უფლების არსებობისას გამოჩნდება. live:false აფრთხილებს, რომ მონაცემები PBX-ის ცოცხალი მდგომარეობა არ არის (მაგალითად, მას დიდი ხანია სურათი არ გამოუგზავნია).

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}

სტატუსის ცარიელი სია:

{"location":"loc1","live":true,"items":[]}

forward-ში დასაშვებია პირობები always, busy, noAnswer, offline, dnd, offShift და რიცხვი noAnswerSec. მიზანი შეიძლება იყოს {kind:"extension",extension}, {kind:"number",number} ან {kind:"voicemail"}. forward:null ხსნის გადამისამართებას. ყოველთვის გაგზავნეთ სასურველი სრული ობიექტი: ეს ნაწილობრივი ცვლილება არ არის.

DND-ის მოხსნის პასუხი, სანამ PBX მოთხოვნას ჯერ კიდევ იყენებს:

{"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 რეჟიმს ხსნის; ჩართვისას until არის Unix-წამები ან "morning" (შემდეგი ცვლის დასაწყისი, ხოლო ამის არქონისას ადგილობრივი დროით 08:00), PBX რეჟიმს თავად მოხსნის. dnd აჩვენებს როგორც მოთხოვნას, ისე მის გამოყენებას: მომხმარებელს „მზადაა“ არ შეატყობინოთ, სანამ pending:true არის.

მეტრიკები#

მეთოდი გზა უფლება პარამეტრები პასუხის ველები
GET /metrics reports:read location, from, to, compare, sections, callbacks location,tz,today,history,range,coverage,sl, არჩეული განყოფილებები, compare, compareSkipped?, callbacks?

from/to — წერტილის ადგილობრივი თარიღები YYYY-MM-DD, ორივე ჩათვლით; მათ გარეშე — ბოლო 7 დღე. compare: prev, year, prev,year ან ცარიელი. განყოფილებები: metrics,loss,queues,lines,operators,directions,heatmap,daily; heatmap და daily მხოლოდ პირდაპირი მოთხოვნით მოდის. callbacks=true დამატებით აჩვენებს ხელახლა დარეკვის სიჩქარეს (92 დღემდე პერიოდებისთვის). წინა წელთან შედარება 400 დღემდე პერიოდებისთვისაა.

რას ნიშნავს მომსახურების დონე, ლოდინი, გამოტოვებული და მიტოვებული ზარები და რატომ არ შეიძლება გამოტოვებულისა და მიტოვებულის შეკრება — სტატიაში «ქოლ-ცენტრის დაფა». პასუხის ველი sl იგივე პარამეტრებია, რაც დაფის პარამეტრების ჩანართზე „მომსახურების დონე“: პასუხის ვადა, მიზანი და მოკლე მიტოვების ზღვარი.

curl -sS "$BASE/metrics?location=loc1&from=2026-09-01&to=2026-09-30&compare=prev,year" -H "Authorization: Bearer $KEY"

მონაცემების გარეშე პერიოდის მაგალითი:

{"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":{}}

მაჩვენებლები პასუხის ძირეულ metrics ველშია — იგივე ციფრები, რაც ეკრანზე ტელეფონია → ზარების ჟურნალი → ანალიტიკა: შემომავალი, მიღებული და გამოტოვებული ზარები, მომსახურების დონე, ლოდინი, მიტოვებული ზარები, AHT, FCR, CSAT. შეამოწმეთ coverage და history.gaps, რათა მონაცემებში ხარვეზი ზარების არქონად არ ჩათვალოთ.

ზარების წესები#

გზები მითითებულია /pbx-api/v1-ის მიმართ; location სავალდებულოა, თუ გასაღებს რამდენიმე ობიექტი აქვს.

მეთოდი გზა უფლება პარამეტრები პასუხის ველები
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? იგივე
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? იგივე
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-იდან. წესის რედაქტირება მხოლოდ გადაცემულ ველებს ცვლის, null ველს შლის. წესის ველები შეგიძლიათ გადასცეთ მოთხოვნის სხეულის ზედა დონეზე ან rule ობიექტით. სრულდება პირველი შესაბამისი შემომავალი წესი; warnings:["after_catch_all"] ნიშნავს, რომ ზემოთ უკვე დგას უპირობო წესი და ახალი არ შესრულდება.

შემომავალი rule: name, enabled, match, when, action, extension, botId, forwardNumber, menuId, fallback. match.kind: any, time (days, from, to), callerPrefix, line (lineId), trunk (trunkId). ჯგუფში ზარის გადასამისამართებლად გამოიყენეთ action:"extension" და ჯგუფის შიდა ნომერი; თანამშრომლებზე განაწილების რთული სქემა ადმინისტრირების პანელში იმართება.

გამავალი rule: name, enabled, pattern, via, scope, stripDigits, prefix, backupVia, backupVias. შაბლონი: ციფრები, X (0–9), N (2–9), Z (1–9), საწყისი +, ბოლო წერტილი. via — {kind:"line",lineId} ან {kind:"trunk",trunkId}; scope — {kind:"all"} ან {kind:"extensions",extensions:["101"]}. გასაღები ნებადართულ ქვეყნებსა და თაღლითობისგან დაცვას არ ცვლის.

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&section=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"}'

ცარიელი სატელეფონო სადგურის წესები:

{"location":"loc1","lines":[],"trunks":[],"inbound":[],"outbound":null}

პასუხი წესის რედაქტირებაზე:

{"location":"loc1","rule":{"id":"r-example","order":1,"name":"Дежурный","enabled":true,"match":{"kind":"any"},"action":"extension","extension":"101"},"order":["r-example"],"rev":12}

აღდგენა ახალ ვერსიას ქმნის: {"location":"loc1","section":"inbound","versionId":"version-example","restored":true}. კონფლიქტის შემთხვევაში, ცვლილების გამეორებამდე ხელახლა წაიკითხეთ წესები და ისტორია.

კონფიგურაციის ექსპორტი და იმპორტი#

მეთოდი გზა უფლება პარამეტრები პასუხის ველები
GET /config/export config:export location, tables? (მძიმით გამოყოფილი) location,rev,tables
POST /config/import config:import location, tables, sync?, apply?, baseRev? location,applied,changed,rev,tables,errors,newAccounts?,created?,passwords?

ცხრილები იმავე ფორმატშია, როგორც ადმინისტრირების პანელიდან ჩამოტვირთვისას: შიდა ნომრები, ჯგუფები, ხაზები, შემომავალი, გამავალი. პაროლები არ იტვირთება. იმპორტი tables-ს იღებს სტრიქონების მასივებად ან ექსპორტიდან მიღებულ {columns,rows} ობიექტებად.

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

შიდა ნომრების ცარიელი სიის ექსპორტი tables=extensions:

{"location":"loc1","rev":12,"tables":{"extensions":{"columns":["extension","name","type","external_number","via","staff_email","staff_name","groups","recording","voicemail","voicemail_email","enabled"],"rows":[]}}}

წინასწარი ნახვა ცვლილებების გარეშე:

{"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}

რევიზიის გარეშე გამოყენება უარყოფილია:

{"error":"base_rev_required","hint":"Run import_config without apply first and pass its rev as baseRev."}

წინასწარ ნახვაში applied:false; errors — შეცდომების რაოდენობაა, დეტალები — tables[].rows[]-ში. გამოყენებისას შეცდომებიანი სტრიქონები გამოტოვებულია: წაიკითხეთ მთელი ანგარიში და არა მხოლოდ HTTP-კოდი. წარმატებული გამოყენების შემდეგ applied:true, rev — ახალი რევიზიაა; თუ შესაცვლელი არაფერია, applied რჩება false. sync:{"<таблица>":true} შლის სტრიქონებს, რომლებიც ფაილში არ არის — ჩართეთ გააზრებულად. რევიზიის კონფლიქტი ახალი წინასწარი ნახვის საფუძველია და არა ძველ გამოყენებაში ახალი ნომრის ჩასმის. ახალი შიდა ნომრების პაროლები ხელმისაწვდომია მხოლოდ ადმინისტრირების პანელში (passwords:"admin_only").

მოვლენები და ვებჰუკები#

მეთოდი გზა უფლება პარამეტრები პასუხის ველები
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 შესაცვლელი ველები 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}

მოვლენები ძველიდან ახლისკენ მიდის; შემდეგი მოთხოვნა since-ად წინა პასუხის next-ს გადასცემს, მათ შორის more:true-ის დროსაც. types მძიმეებით გამოყოფილი სიაა. წერტილის ნაკადი 7 დღის განმავლობაში 2000-მდე მოვლენას ინახავს. მოვლენების სხეულები, ხელმოწერა და გამეორებები აღწერილია სტატიაში ზარების მოვლენების ვებჰუკები.

ღია და დახურული#

მეთოდი გზა უფლება პარამეტრები პასუხის ველები
GET /open-closed routes:read location location,codeEnabled,remoteSupported,closed,closedUntil?,closedBy?,request?
PUT /open-closed daynight:write location, closed, until? ok, rev და წერტილის მდგომარეობა
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}

ღრუბელი მოთხოვნას PBX-ს წამებში გადასცემს: სანამ request.applied false-ის ტოლია, PBX მას ჯერ არ ამოქმედებს. until-ის გარეშე წერტილი ადგილობრივი დღე-ღამის ბოლომდე დახურულია. წანაცვლების გარეშე დრო წერტილის დროის სარტყელში იკითხება; ვადა 30 დღეზე შორს არ უნდა იყოს, შემდეგ წერტილი თვითონ გაიხსნება. closed:false წერტილს ხსნის და until-ს ხსნის. მარშრუტიზაციაში ჩართული უნდა იყოს „კოდით *28 დახურვა“, წინააღმდეგ შემთხვევაში 409 daynight_code_off. დახურვა იგივეა, რაც ტელეფონიდან *28: ზარები „სამუშაო საათების გარეთ“ წესებით მიდის, სერვერი არ ითიშება.

მზა სცენარები#

გუშინდელი ჟურნალი CSV-ში: Python#

დააყენეთ requests. მიუთითეთ PBX_KEY, PBX_TZ (ობიექტის დროის სარტყელი, მაგალითად Asia/Tbilisi) და PBX_LOCATION; გაუშვით სკრიპტი გადამისამართებით > calls.csv. ის კითხულობს ყველა გვერდს, CSV-ს სწორად აფორმატებს და ცხრილების რედაქტორების ფორმულებს ანეიტრალებს. იტვირთება ის, რაც ჟურნალის დროის ფანჯარაშია და არა სრული არქივი.

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-იდან: Node.js#

კოდი სრულდება CRM-ის სერვერზე (Node.js 18+, ფაილი .mjs), გასაღები — PBX_KEY-ში. გამოძახება მიაბით ავტორიზებული თანამშრომლის აშკარა დაწკაპუნებას და CRM-ში დაბლოკეთ ორმაგი დაწკაპუნება.

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());

ღამის რეჟიმი განრიგით: curl#

დამგეგმავი ბრძანებას საღამოს უშვებს; UNTIL — შემდეგი გახსნის დრო ISO ფორმატში დროის სარტყლის წანაცვლებით, არაუმეტეს 30 დღის შემდეგ (24-დან 25 ოქტომბრის ღამისთვის — 2026-10-25T09:00:00+04:00). ყოველი გაშვებისას თარიღი თავიდან გამოთვალეთ.

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'

ობიექტის ადრე გასახსნელად — იგივე PUT მნიშვნელობით {"location":"loc1","closed":false}. მდგომარეობა GET-ით შეამოწმეთ. გარე დამგეგმავის გარეშე რეგულარული სამუშაო საათები ადმინისტრირების პანელში მარშრუტიზაციის განრიგით იმართება.

იხილეთ ასევე#