საკუთარი სატელეფონო სადგურის 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§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"}'
ცარიელი სატელეფონო სადგურის წესები:
{"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-ით შეამოწმეთ. გარე დამგეგმავის გარეშე რეგულარული სამუშაო საათები ადმინისტრირების პანელში მარშრუტიზაციის განრიგით იმართება.