Към съдържанието

Оператори

Управлявайте операторите за чат във вашите организации в ChatHub. Операторите са агенти, които се справят с разговорите с клиенти.

Преглед

Операторите в ChatHub:

  • Обработват клиентски чат разговори
  • Принадлежат към определени организации
  • Имат активен, неактивен или изтрит статус
  • Могат да имат индивидуални токени за удостоверяване
  • Получават и отговарят на съобщения

Списък с оператори

Извлечете всички оператори за определена организация.

Крайна точка (Endpoint)

GET /api/operator?organizationId={id}

Заявка

curl -X GET "https://chatapi.smsbat.com/api/operator?organizationId=24" \
  -H "Authorization: Bearer {company-token}" \
  -H "Accept: text/plain"

Параметри

Параметър Тип Местоположение Задължителен Описание
organizationId integer Query Да ID на организация

Заглавки (Headers)

Header Стойност Задължителен
Authorization Bearer Да
Accept text/plain Да

Отговор

[
  {
    "id": 101,
    "name": "Иван Иванов",
    "status": 0,
    "organization": {
      "id": 24,
      "name": "Customer Support"
    }
  },
  {
    "id": 102,
    "name": "Сара Джонсън",
    "status": 0,
    "organization": {
      "id": 24,
      "name": "Customer Support"
    }
  },
  {
    "id": 103,
    "name": "Майк Уилсън",
    "status": 1,
    "organization": {
      "id": 24,
      "name": "Customer Support"
    }
  }
]

Полета в отговора

Поле Тип Описание
id integer Уникален идентификатор на оператора
name string Име на оператора за показване
status integer Статус на оператора (0=Активен, 1=Неактивен, 2=Изтрит)
organization object Подробности за родителската организация
organization.id integer ID на организация
organization.name string Име на организация

Статус на оператора

Статус Стойност Описание
Активен (Active) 0 Операторът работи в момента и може да приема чатове
Неактивен (Inactive) 1 Операторът е временно деактивиран
Изтрит (Deleted) 2 Операторът е премахнат от системата

Добавяне на оператори

Добавете нови оператори към организации, използвайки крайната точка (endpoint) за синхронизация.

Крайна точка (Endpoint)

POST /api/operator/synchronize

Заявка

curl -X POST https://chatapi.smsbat.com/api/operator/synchronize \
  -H "Authorization: Bearer {company-token}" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "organizationId": 24,
      "name": "Алекс Браун"
    },
    {
      "organizationId": 24,
      "name": "Ема Дейвис"
    }
  ]'

Тяло на заявката

Масив от обекти за оператори:

[
  {
    "organizationId": 24,
    "name": "Алекс Браун"
  },
  {
    "organizationId": 24,
    "name": "Ема Дейвис"
  }
]

Полета в заявката

Поле Тип Задължителен Описание
organizationId integer Да ID на целевата организация
name string Да Име на оператора за показване

Отговор

[
  {
    "id": 104,
    "name": "Алекс Браун",
    "status": 0
  },
  {
    "id": 105,
    "name": "Ема Дейвис",
    "status": 0
  }
]

Промяна на статуса на оператор

Актуализирайте статуса на оператора (Активен/Неактивен/Изтрит).

Крайна точка (Endpoint)

POST /api/operator/status

Заявка

curl -X POST https://chatapi.smsbat.com/api/operator/status \
  -H "Authorization: Bearer {company-token}" \
  -H "Content-Type: application/json" \
  -d '{
    "id": 104,
    "status": 1
  }'

Тяло на заявката

{
  "id": 0,
  "status": 0
}

Полета в заявката

Поле Тип Задължителен Описание
id integer Да ID на оператор
status integer Да Нов статус (0=Активен, 1=Неактивен, 2=Изтрит)

Отговор

200 OK

Успехът връща HTTP 200 без тяло на отговора.

Стойности на статуса

Статус Стойност Описание
Активен (Active) 0 Операторът може да обработва чатове
Неактивен (Inactive) 1 Операторът е временно деактивиран
Изтрит (Deleted) 2 Операторът е премахнат от системата

Пример: Деактивиране на оператор

curl -X POST https://chatapi.smsbat.com/api/operator/status \
  -H "Authorization: Bearer {company-token}" \
  -H "Content-Type: application/json" \
  -d '{
    "id": 104,
    "status": 1
  }'

Пример: Реактивиране на оператор

curl -X POST https://chatapi.smsbat.com/api/operator/status \
  -H "Authorization: Bearer {company-token}" \
  -H "Content-Type: application/json" \
  -d '{
    "id": 104,
    "status": 0
  }'

Пример: Изтриване на оператор

curl -X POST https://chatapi.smsbat.com/api/operator/status \
  -H "Authorization: Bearer {company-token}" \
  -H "Content-Type: application/json" \
  -d '{
    "id": 104,
    "status": 2
  }'

Примери за внедряване

Python

import requests

class OperatorManager:
    def __init__(self, company_token, base_url='https://chatapi.smsbat.com'):
        self.company_token = company_token
        self.base_url = base_url
        self.headers = {
            'Authorization': f'Bearer {company_token}',
            'Accept': 'text/plain'
        }

    def list_operators(self, organization_id):
        """Вземете всички оператори за организация"""
        response = requests.get(
            f'{self.base_url}/api/operator',
            headers=self.headers,
            params={'organizationId': organization_id}
        )
        response.raise_for_status()
        return response.json()

    def add_operators(self, operators):
        """Добавете нови оператори
        Args:
            operators: Списък с речници (dicts) с organizationId и name
        """
        response = requests.post(
            f'{self.base_url}/api/operator/synchronize',
            headers={**self.headers, 'Content-Type': 'application/json'},
            json=operators
        )
        response.raise_for_status()
        return response.json()

    def get_active_operators(self, organization_id):
        """Вземете само активни оператори"""
        operators = self.list_operators(organization_id)
        return [op for op in operators if op['status'] == 0]

    def find_operator_by_name(self, organization_id, name):
        """Намерете оператор по име"""
        operators = self.list_operators(organization_id)
        for operator in operators:
            if operator['name'].lower() == name.lower():
                return operator
        return None

    def change_operator_status(self, operator_id, status):
        """Промяна на статуса на оператор
        Args:
            operator_id: ID на оператор
            status: 0 (Активен), 1 (Неактивен), 2 (Изтрит)
        """
        response = requests.post(
            f'{self.base_url}/api/operator/status',
            headers={**self.headers, 'Content-Type': 'application/json'},
            json={'id': operator_id, 'status': status}
        )
        response.raise_for_status()
        return response.status_code == 200

# Употреба
manager = OperatorManager('your-company-token')

# Списък с оператори
operators = manager.list_operators(organization_id=24)
print(f"Намерени {len(operators)} оператора")

# Вземете само активни оператори
active = manager.get_active_operators(24)
print(f"{len(active)} активни оператори")

# Добавете нови оператори
new_operators = manager.add_operators([
    {'organizationId': 24, 'name': 'Джон Доу'},
    {'organizationId': 24, 'name': 'Джейн Смит'}
])
print(f"Добавени {len(new_operators)} оператора")

# Намерете оператор по име
operator = manager.find_operator_by_name(24, 'Джон Доу')
if operator:
    print(f"Намерен оператор: {operator['name']} (ID: {operator['id']})")

# Промяна на статуса на оператор
manager.change_operator_status(104, 1)  # Деактивиране
print("Операторът е деактивиран")

manager.change_operator_status(104, 0)  # Реактивиране
print("Операторът е реактивиран")

JavaScript (Node.js)

const axios = require('axios');

class OperatorManager {
  constructor(companyToken, baseUrl = 'https://chatapi.smsbat.com') {
    this.companyToken = companyToken;
    this.baseUrl = baseUrl;
    this.headers = {
      'Authorization': `Bearer ${companyToken}`,
      'Accept': 'text/plain'
    };
  }

  async listOperators(organizationId) {
    const response = await axios.get(
      `${this.baseUrl}/api/operator`,
      {
        headers: this.headers,
        params: { organizationId }
      }
    );

    return response.data;
  }

  async addOperators(operators) {
    const response = await axios.post(
      `${this.baseUrl}/api/operator/synchronize`,
      operators,
      {
        headers: {
          ...this.headers,
          'Content-Type': 'application/json'
        }
      }
    );

    return response.data;
  }

  async getActiveOperators(organizationId) {
    const operators = await this.listOperators(organizationId);
    return operators.filter(op => op.status === 0);
  }

  async findOperatorByName(organizationId, name) {
    const operators = await this.listOperators(organizationId);
    return operators.find(op =>
      op.name.toLowerCase() === name.toLowerCase()
    );
  }

  async getOperatorById(organizationId, operatorId) {
    const operators = await this.listOperators(organizationId);
    return operators.find(op => op.id === operatorId);
  }

  async changeOperatorStatus(operatorId, status) {
    const response = await axios.post(
      `${this.baseUrl}/api/operator/status`,
      { id: operatorId, status },
      {
        headers: {
          ...this.headers,
          'Content-Type': 'application/json'
        }
      }
    );

    return response.status === 200;
  }
}

// Употреба
const manager = new OperatorManager('your-company-token');

async function manageOperators() {
  // Списък с оператори
  const operators = await manager.listOperators(24);
  console.log(`Намерени ${operators.length} оператора`);

  // Вземете активни оператори
  const active = await manager.getActiveOperators(24);
  console.log(`${active.length} активни оператори`);

  // Добавете нови оператори
  const newOperators = await manager.addOperators([
    { organizationId: 24, name: 'Джон Доу' },
    { organizationId: 24, name: 'Джейн Смит' }
  ]);
  console.log(`Добавени ${newOperators.length} оператора`);

  // Намерете оператор по име
  const operator = await manager.findOperatorByName(24, 'Джон Доу');
  if (operator) {
    console.log(`Намерен: ${operator.name} (ID: ${operator.id})`);
  }

  // Промяна на статуса на оператор
  await manager.changeOperatorStatus(104, 1); // Деактивиране
  console.log('Операторът е деактивиран');

  await manager.changeOperatorStatus(104, 0); // Реактивиране
  console.log('Операторът е реактивиран');
}

manageOperators();

PHP

<?php

class OperatorManager {
    private $companyToken;
    private $baseUrl;

    public function __construct($companyToken, $baseUrl = 'https://chatapi.smsbat.com') {
        $this->companyToken = $companyToken;
        $this->baseUrl = $baseUrl;
    }

    public function listOperators($organizationId) {
        $url = $this->baseUrl . '/api/operator?organizationId=' . $organizationId;

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Authorization: Bearer ' . $this->companyToken,
            'Accept: text/plain'
        ]);

        $response = curl_exec($ch);
        curl_close($ch);

        return json_decode($response, true);
    }

    public function addOperators($operators) {
        $ch = curl_init($this->baseUrl . '/api/operator/synchronize');

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Authorization: Bearer ' . $this->companyToken,
            'Content-Type: application/json',
            'Accept: text/plain'
        ]);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($operators));

        $response = curl_exec($ch);
        curl_close($ch);

        return json_decode($response, true);
    }

    public function getActiveOperators($organizationId) {
        $operators = $this->listOperators($organizationId);
        return array_filter($operators, function($op) {
            return $op['status'] === 0;
        });
    }

    public function findOperatorByName($organizationId, $name) {
        $operators = $this->listOperators($organizationId);

        foreach ($operators as $operator) {
            if (strcasecmp($operator['name'], $name) === 0) {
                return $operator;
            }
        }

        return null;
    }

    public function changeOperatorStatus($operatorId, $status) {
        $ch = curl_init($this->baseUrl . '/api/operator/status');

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Authorization: Bearer ' . $this->companyToken,
            'Content-Type: application/json'
        ]);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
            'id' => $operatorId,
            'status' => $status
        ]));

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return $httpCode === 200;
    }
}

// Употреба
$manager = new OperatorManager('your-company-token');

// Списък с оператори
$operators = $manager->listOperators(24);
echo "Намерени " . count($operators) . " оператора\n";

// Вземете активни оператори
$active = $manager->getActiveOperators(24);
echo count($active) . " активни оператори\n";

// Добавяне на нови оператори
$newOperators = $manager->addOperators([
    ['organizationId' => 24, 'name' => 'Джон Доу'],
    ['organizationId' => 24, 'name' => 'Джейн Смит']
]);
echo "Добавени " . count($newOperators) . " оператори\n";

// Намиране на оператор
$operator = $manager->findOperatorByName(24, 'Джон Доу');
if ($operator) {
    echo "Намерен: " . $operator['name'] . " (ID: " . $operator['id'] . ")\n";
}

// Промяна на статуса
$manager->changeOperatorStatus(104, 1); // Деактивиране
echo "Операторът е деактивиран\n";

$manager->changeOperatorStatus(104, 0); // Реактивиране
echo "Операторът е реактивиран\n";

Често срещани случаи на употреба

Въвеждане на нови членове на екипа (Onboard)

async function onboardOperators(teamMembers, organizationId) {
  const operators = teamMembers.map(member => ({
    organizationId,
    name: member.fullName
  }));

  const created = await addOperators(operators);

  // Генериране на токени за всеки оператор
  for (const operator of created) {
    const token = await getOperatorToken(operator.id);
    await sendWelcomeEmail(operator, token);
  }

  return created;
}

Наблюдаване на статуса на оператор

async function getOperatorStatistics(organizationId) {
  const operators = await listOperators(organizationId);

  return {
    total: operators.length,
    active: operators.filter(op => op.status === 0).length,
    inactive: operators.filter(op => op.status === 1).length,
    deleted: operators.filter(op => op.status === 2).length
  };
}

Разпределение на натоварването (Load Balancing)

async function assignChatToOperator(organizationId, chatId) {
  const activeOperators = await getActiveOperators(organizationId);

  if (activeOperators.length === 0) {
    throw new Error('Няма налични активни оператори');
  }

  // Просто кръгово (round-robin) присвояване
  const operatorIndex = chatId % activeOperators.length;
  return activeOperators[operatorIndex];
}

Масово импортиране

async function importOperatorsFromCSV(csvData, organizationId) {
  const lines = csvData.split('\n').slice(1); // Спокойствие за заглавката

  const operators = lines
    .filter(line => line.trim())
    .map(line => {
      const [name] = line.split(',');
      return { organizationId, name: name.trim() };
    });

  // Групово импортиране на порции (chunks) от 100
  const chunkSize = 100;
  const results = [];

  for (let i = 0; i < operators.length; i += chunkSize) {
    const chunk = operators.slice(i, i + chunkSize);
    const imported = await addOperators(chunk);
    results.push(...imported);

    console.log(`Импортирани ${results.length}/${operators.length}`);
  }

  return results;
}

Добри практики

Обработка на грешки

async function addOperatorsSafely(operators) {
  try {
    return await addOperators(operators);
  } catch (error) {
    if (error.response?.status === 400) {
      console.error('Невалидни данни за оператор:', error.response.data);
      // Обработка на грешки при валидиране
    } else if (error.response?.status === 401) {
      console.error('Неуспешно удостоверяване');
      // Опресняване на токена
    } else {
      console.error('Неочаквана грешка:', error);
    }

    throw error;
  }
}

Кеширане

class CachedOperatorManager extends OperatorManager {
  constructor(companyToken) {
    super(companyToken);
    this.cache = new Map();
    this.cacheTTL = 60000; // 1 минута
  }

  async listOperators(organizationId, useCache = true) {
    const cacheKey = `org_${organizationId}`;
    const cached = this.cache.get(cacheKey);

    if (useCache && cached && Date.now() - cached.time < this.cacheTTL) {
      return cached.data;
    }

    const data = await super.listOperators(organizationId);

    this.cache.set(cacheKey, {
      data,
      time: Date.now()
    });

    return data;
  }

  clearCache(organizationId = null) {
    if (organizationId) {
      this.cache.delete(`org_${organizationId}`);
    } else {
      this.cache.clear();
    }
  }
}

Валидиране

function validateOperatorData(operators) {
  const errors = [];

  operators.forEach((op, index) => {
    if (!op.organizationId) {
      errors.push(`Оператор ${index}: Липсва organizationId`);
    }

    if (!op.name || op.name.trim().length === 0) {
      errors.push(`Оператор ${index}: Името е задължително`);
    }

    if (op.name && op.name.length > 100) {
      errors.push(`Оператор ${index}: Името е твърде дълго (макс. 100 знака)`);
    }
  });

  if (errors.length > 0) {
    throw new Error('Валидирането е неуспешно:\n' + errors.join('\n'));
  }
}

// Употреба
try {
  validateOperatorData(operatorData);
  await addOperators(operatorData);
} catch (error) {
  console.error(error.message);
}

Ограничаване на скоростта (Rate Limiting)

class RateLimitedOperatorManager extends OperatorManager {
  constructor(companyToken, requestsPerSecond = 5) {
    super(companyToken);
    this.minInterval = 1000 / requestsPerSecond;
    this.lastRequest = 0;
  }

  async throttle() {
    const now = Date.now();
    const timeSinceLastRequest = now - this.lastRequest;

    if (timeSinceLastRequest < this.minInterval) {
      await new Promise(resolve =>
        setTimeout(resolve, this.minInterval - timeSinceLastRequest)
      );
    }

    this.lastRequest = Date.now();
  }

  async listOperators(organizationId) {
    await this.throttle();
    return super.listOperators(organizationId);
  }

  async addOperators(operators) {
    await this.throttle();
    return super.addOperators(operators);
  }
}