image

Доброго времени суток. Ранее RingCloud анонсировал запуск REST API, которое позволит нашему продукту интегрироваться с различными CRM-системами. И хотя его разработка еще продолжается, мы рады представить Вашему вниманию первую версию. Под катом мы рассмотрим общую архитектуру данного сервиса, его принцип работы, а так же разберём примеры использования.

Когда мы только планировали свой сервис, мы ставили перед собой цели сделать его максимально удобным для интеграции, а такие задачи без достойного API не решить. По этому решили уделить данном сервису как можно больше внимания, построив его максимально надёжным и удобным.

Управление ключами API


Для использования API Вам потребуется уникальный API Key и пароль к нему, для этого идём в свой личный кабинет и в соответствующем разделе создаём ключик

image

Ключ генерируется автоматически, он состоит из префикса с номером вашего аккаунта и телом самого ключа, для того чтобы скопировать ключ необходимо просто кликнуть ЛКМ на строку с ключом. Пароль так же генерируется автоматически и для его изменения, если он вам покажется слишком простым, нужно нажать на иконку справа. Для каждого ключа необходимо указать права доступа, это сделано для разграничения полномочий между вашими приложениями. Уровни доступа работают по принципу «RO/RW» и позволяют разграничить разрешённые действия.

Описание функций RingCloud API, доступных на данный момент, вы сможете получить на странице документации

Архитектура


Данный сервис построен по принципу кластера, с балансировкой нагрузки между нодами обрабатывающими запросы. Так как сервис использует HTTPS, то было решено сместить эту задачу на балансировщик. Ноды, обработав запросы, обращаются к ядру системы включающему в себя сервера VoIP, БД, и cache основанный на Redis.
Надо сказать, что Redis у нас очень широко используется во всех узлах RingCloud, мы им очень довольны в плане удобства и быстродействия.

В качестве балансировщика и серверов WEB интерфейса мы используем Nginx так как он нас полностью устроил по функционалу и производительности, на нём прекрасно реализован функционал балансировки, он отлично работает с HTTPS, обладает очень удобной конфигурацией.

image

Примеры использования


Наше API мы разрабатываем опираясь на REST методологию, чтобы максимально упростить использование.
Давайте рассмотрим принцип формирования запроса. Все url-адреса API имеют вид:
https://api.ringcloud.ru/<<url>>?api_key=API_KEY&hash=HASH

здесь:
  • url – адрес, соответствующий вызываемой функции API
  • api_key – ключ API
  • hash – хэш — вычисляется как md5 от ключа API и пароля

В случае успешного выполнения любого запроса (за исключением загрузки файла записи разговора – в этом случае будет возвращен непосредственно сам файл) ответ от сервера имеет вид:
{"status": "success", "message": null, "data": "some_data"}

В случае возникновения ошибки (кроме ошибки при загрузке файла записи разговора – в этом случае будет возвращена пустая строка) ответ от сервера имеет вид:
{"status": "error", "message": "some_error", "data": null}


Каждый ответ от сервера содержит код состояния HTTP. В таблице ниже приведены наиболее часто используемые из них.

Наиболее часто используемые коды состояния
Код состояния HTTP Описание
200 Функция успешно выполнена
400 Неправильно переданы параметры. Например, при обновлении данных был указан несуществующий пользователь
403 Не задан ключ API или хэш, ключ API не существует или неверно вычислен хэш
404 Запрашиваемый url или файл не найден
405 Метод не поддерживается – вместо GET использован POST запрос или наоборот
429 Слишком много запросов– достигнуто ограничение на частоту запросов к API
500 Внутренняя ошибка. В идеале не должен возвращаться никогда.


На данный момент возможна работа с двумя объектами (ресурсами в терминологии REST): «Пользователи» и «Вызовы». С помощью первого можно создавать пользователей, получать о них информацию и изменять их данные. Второй объект предназначен для работы со звонками. С его помощью можно совершить звонок, а также получить информацию об активных и совершенных вызовах. Далее, существуют определенные ограничения на частоту обращений к нашему API. Давайте поясним используемую нами модель. Во-первых, ограничения считаются отдельно для каждого ключа. Во-вторых, в рамках одного ключа существуют ограничения на однотипные запросы, т.е. запросы с полностью одинаковыми URL-адресами. Например, если у Вас есть только один ключ API, и Вы отправляете в минуту два запроса на изменение внутреннего номера у одного и того же пользователя, то успешно отработает только первый запрос. А вот изменить внутренние номера у двух разных пользователей вполне возможно.

Функции объекта «Пользователи» и временные ограничения
Название URL-адрес Ограничение
Получение списка пользователей аккаунта
/v1/users
1 запрос в секунду
Получение информации по конкретному пользователю
/v1/users/<username>
1 запрос в секунду
Изменение пароля
/v1/users/<username>/update_password
1 запрос в секунду
Изменение внутреннего номера
/v1/users/<username>/update_extension_number
1 запрос в 5 секунд
Изменение email
/v1/users/<username>/update_email
1 запрос в 5 секунд
Включение Voice Box
/v1/users/<username>/voice_mail_box_on
1 запрос в 5 секунд
Выключение Voice Box
/v1/users/<username>/voice_mail_box_off
1 запрос в 5 секунд
Создание пользователя
/v1/users/create
1 запрос в 60 секунд
Получение списка записей
/v1/users/<username>/records
1 запрос в 5 секунд
загрузка записи разговора
/v1/users/<username>/records/<filename>
1 запрос в секунду


Функции объекта «Вызовы» и временные ограничения

Название URL-адрес Ограничение
Получение списка каналов для контекста
/v1/calls/channels
1 запрос в 2 секунды
Получение информации по каналу
/v1/calls/channels/<channel>
1 запрос в 2 секунды
Оригинация вызова
/v1/calls
1 запрос в 5 секунды
Получение списка совершённых вызовов
/v1/calls/complite
1 запрос в 5 секунды
Получение списка текущих вызовов
/v1/calls/active
1 запрос в 5 секунды


Как работает оригинация вызова

Для того чтобы сделать звонок на потребуется сотрудник который подключился по SIP к серверу RingCloud с помощью «софтфона» или стационарного SIP телефона, и номер на который будем звонить. При отправке запроса в API система сначала вызывает сотрудника (назовём его абонент А), а затем, только когда сотрудник ответил, набирает номер на который мы планируем совершить звонок, таким образом мы оригинируем вызов между абонентом А и абонентом Б
image

Рассмотрим небольшой пример обращения на Python к нашему API. Вот как можно позвонить с внутреннего номера, закрепленного за сотрудником с именем 1679728441 на номер 7910123456:

# Импортируем  всё что нам потребуется для оригинации вызова
import requests
import hashlib
import json
# объявляем переменные с данными в виде строки, которые нам потребуются для работы с RingCloud API

api_key = 'YOUR_API_KEY'
password = 'YOUR_PASS'

hash = hashlib.md5(api_key+password).hexdigest()
payload = {
    'user': '1679728441',
    'num': '7910123456'
}
data = {'params': payload}
# Выполняем запрос
r = requests.post("https://api.ringcloud.ru/v1/calls?api_key=%s&hash=%s" %
                  (api_key, hash), data=json.dumps(data))
print r.text


И тоже самое на PHP:
<?php
$apiKey = 'YOUR_API_KEY’;
$password = 'YOUR_PASS';
$hash = md5($apiKey . $password);
$apiUrl = 'https://api.ringcloud.ru/v1/calls?api_key=' . $apiKey . '&hash=' . $hash;
$callData = [
   'params' => [
   'num' => '7910123456',
   'user' => '1679728441'
   ]
];
$ch = curl_init($apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($callData));
$result = curl_exec($ch);
echo $result; 
/* 
{
"status": "success",
"message": null,
"data": "Originate successfully queued"
}
*/
?>



Также параллельно с проектом разработки RingCloud API мы разрабатываем библиотеки, позволяющие максимально быстро начать использование его в своих приложениях и сервисах. Пока готова только первая версия библиотеки на PHP она находится здесь

Ну вот вроде бы и всё чем мы хотели с вами поделиться. Актуальную версию документации по нашему API можно найти здесь. В ближайших планах – создание полноценного пакета для Python для работы с нашим API и, конечно же, расширение функционала. Спасибо за внимание.

Комментарии (5)


  1. artyfarty
    20.05.2015 14:28
    +1

    Робот на иллюстрации растерянно смотрит на читателя – как из предложенных деталей шуруповертом с буром от перфоратора и отвёрткой собрать API?


    1. Yud_SS
      20.05.2015 14:34

      По итогу-то всё у него получилось :)


    1. lukianenko Автор
      20.05.2015 14:34

      Ну вообще мы не подразумевали особой смысловой нагрузки в картинке :) хотя реализация API сервиса та ещё задача :)
      Очень интересно вы подметили эмоциональное выражение взгляда робота на картинке, никогда бы не задумался об этом.


  1. mr_smith
    25.05.2015 15:36

    Есть вопросы:

    1. Почему вы решили использовать формат /v1/users/<username>/update_email для обновления поля, а не PUT или PATCH запрос на /v1/users/ как это обычно делается в rest? Тоже самое для update_extension_number, update_email, voice_mail_box_on, voice_mail_box_off

    2. Непонятно зачем данные в ответе сервера обернуты в объект со статусом и сообщением — {«status»: «error», «message»: «some_error», «data»: null}. status может быть либо error, либо success, и это можно понять по коду ответа 2хх — success, 4хх и 5хх — error.

    3. У вас создание пользователя делается при помощи POST запроса на /v1/users/create. По REST это обычное делается POST запросом на /v1/users. Вот создание звонка у вас так и делается — POST запросом на /v1/calls. Вы уж определитесь)

    4. /v1/calls/complete и /v1/calls/active как будто можно было сделать как /v1/calls?status=complete и /v1/calls?status=active, при этом /v1/calls возвращал бы все звонки


    1. lukianenko Автор
      25.05.2015 19:10

      1. Мы сознательно отказались в своем API от использования любых методов, кроме GET и POST, чтобы избежать возможных проблем. Дискуссию на эту тему можно посмотреть здесь. Если вкратце, то может случиться ситуация, когда криво настроенный прокси-сервер проглотит методы типа PUT или DELETE (http://habrahabr.ru/post/181988/#comment_6366880).
      Или попадется клиент, который кроме GET и POST, ничего посылать не умеет (http://habrahabr.ru/post/181988/#comment_6369042).
      Прецеденты уже были: http://stackoverflow.com/questions/2061898/urlrequest-with-delete-method

      2. По поводу статуса — это сделано больше для наглядности, чтобы можно было оценить результат запроса без обращения к значению статусу кода. А в message содержится подробная причина ошибки, которая не всегда соответствует прямому описанию HTTP кода. Например, сообщения в случае, если в запросе вообще не указан ключ API и в случае, когда указан не существующий ключ, будут разные, но код ответа один — 403.

      3. Здесь дело в том, что для пользователей в будущем планируется метод удаления. А поскольку мы отказались от метода DELETE, то единственным вариантом остается это сделать через

      url: /v1/users/create и /v1/users/delete
      
      . Для звонков же операция удаления бессмысленна, поэтому можно ограничиться POST запросом на /v1/calls, который позволит совершить звонок.

      4. Мы не планировали смешивать в одной выдаче активные и совершенные звонки. Поэтому у нас нет GET-метода для /v1/calls.
      По поводу /v1/calls?status=complete и /v1/calls?status=active — да, можно было сделать так. Здесь решили так не делать, чтобы избежать путаницы. Дело в том, что для совершенных запросов можно указать фильтр по номеру телефона и/или количеству дней за которые запрашиваются данные. Для активных же звонков данные фильтры не используются. Поэтому, решили использовать разные URL.