PHP (Guzzle) PHP (cURL) cURL JavaScript Python JSON

Введение

Это страница документации API KWIGA. С помощью API вы можете выполнять действия от своего имени в ваших кабинетах KWIGA — кабинет это ваше рабочее пространство (аккаунт / тенант), где живут ваши курсы, контакты и остальные данные.

Для того чтобы включить API, в личном кабинете в разделе settings нужно нажать на соответсвующий чекбокс. После этого будет сгенерирован токен для API и получен хеш кабинета, которые нужны для последующих обращений к API.

При компрометации токена его можно перевыпустить, что сделает прошлый токен неактивным и сгенерирует новый.

API доступен только для тех пользователей у кого в кабинете включено API.

Ограничение частоты запросов

API имеет ограничение на количество обращений 200 запросов в минуту. Лимит считается отдельно для каждого API-токена.

В каждом ответе возвращаются следующие заголовки с информацией о лимите:

Заголовок Описание
X-RateLimit-Limit максимальное количество запросов в минуту.
X-RateLimit-Remaining сколько запросов ещё доступно в текущей минуте.
Retry-After через сколько секунд можно повторить запрос. Возвращается только при превышении лимита (HTTP 429).
X-RateLimit-Reset UNIX timestamp, когда сбрасывается текущее окно. Возвращается только при превышении лимита (HTTP 429).

Идемпотентность

Мутирующие эндпоинты публичного API (сейчас POST /contacts/:contact/rewards; другие будут отмечаться индивидуально) принимают опциональный header Idempotency-Key. Передайте уникальное значение один раз и переиспользуйте его на каждом ретрае той же логической операции — сервер «свернёт» повторы в один сайд-эффект и на каждый ретрай вернёт тот же ответ.

Эндпоинты, поддерживающие идемпотентность, помечены в этой документации плашкой: Идемпотентный

Контракт:

Header Поведение
Idempotency-Key (request) Уникальная строка от клиента (1–255 символов). Подойдёт UUIDv4 или стабильный бизнес-ключ типа reward-for-quiz-attempt-4821. Пустой/отсутствующий header выключает идемпотентность для этого вызова. Клиенты, которые не могут передавать кастомные headers (form-based интеграции), могут передать то же значение как поле body/query idempotency_key — header имеет приоритет, если переданы оба.
Idempotent-Replayed: true (response) Возвращается на второй и последующих вызовах с тем же ключом. Если видите этот header — сервер НЕ выполнял операцию повторно, body содержит тот же объект, что был создан при первом вызове (сущность перечитывается и сериализуется заново, поэтому отражает своё текущее состояние). Если эта сущность была к этому моменту удалена окончательно, body — это минимальное подтверждение с её типом и id.

Область ключа — (кабинет, эндпоинт, ключ): тот же бизнес-ключ на другом эндпоинте или в другом кабинете считается отдельной операцией. Рекомендуем всё равно UUIDv4 — коллизии становятся статистически невозможны.

Базовая работа с API

<?php
// Get contact by ID

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/contacts/:contact', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get contact by ID

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/contacts/:contact' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get contact by ID
const url = 'https://api.kwiga.com/contacts/:contact';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get contact by ID
import requests

url = 'https://api.kwiga.com/contacts/:contact'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/contacts/:contact",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Все запросы должны отправляться на домен:

https://api.kwiga.com

Для авторизации передаём API-токен в одном из вариантов:

Для идентификации кабинета, в котором происходят действия, обязательно передаём хеш-кабинета в одном из вариантов:

PUT и DELETE запросы можно отправлять POST-запросом с указанием дополнительного параметра _method с нужным методом (PUT или DELETE).

Установить локализацию справочников и сообщений валидации можно заголовком:

X-Language: {locale}

Locale Description
en English (default)
cs Czech
de German
el Greek
es Spanish
fr Franch
hu Hungarian
it Italian
ka Georgian
lv Polish
pl Polish
pt Portuguese
ro Romanian
ru Russian
uk Ukrainian
zh Chinese (Simplified)

Ошибки

Код ошибки Значение
400 Bad Request - Ваш запрос неверен.
401 Unauthorized - Ошибка авторизации. Ваш API-ключ неверен.
403 Forbidden - Запрошенный ресурс спрятан и доступен только администраторам.
404 Not Found - Не найдено. Указанная страница не найдена.
405 Method Not Allowed - Метод не разрешен. Серверу известен метод запроса, но целевой ресурс не поддерживает этот метод.
422 Unprocessable entity - Сущность невозможно обработать.
429 Too Many Requests - Очень много запросов. Вы отправляете слишком много запросов, остановитесь!
500 Internal Server Error - Внутренняя ошибка сервера. Проблема с нашим сервером. Повторите попытку позже.
503 Service Unavailable - Сервис недоступен. Мы временно отключены от сети в связи с техническим обслуживанием. Повторите попытку позже.

CRM. Контакты

Карточки контактов

Список контактов

Пример запроса:

<?php
// Get contacts list

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/contacts', $options);

$result = json_decode($response->getBody());
?>


# ---
# Paginated example
# ---

<?php
// Get contacts list with pagination

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/contacts?page=1&per_page=15', $options);

$result = json_decode($response->getBody());
?>


# ---
# Filtered example
# ---

<?php
// Get contacts list with filters

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/contacts?page=1&per_page=15&filters[date_from]=2022-04-27&filters[search]=example.com&with_offers=1', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get contacts list

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>


# ---
# Paginated example
# ---

<?php
// Get contacts list with pagination

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts?page=1&per_page=15');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>


# ---
# Filtered example
# ---

<?php
// Get contacts list with filters

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts?page=1&per_page=15&filters[date_from]=2022-04-27&filters[search]=example.com&with_offers=1');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/contacts' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'


# ---
# Paginated example
# ---

curl --location --request GET 'https://api.kwiga.com/contacts?page=1&per_page=15' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'


# ---
# Filtered example
# ---

curl --location --request GET 'https://api.kwiga.com/contacts?page=1&per_page=15&filters[date_from]=2022-04-27&filters[search]=example.com&with_offers=1' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get contacts list
const url = 'https://api.kwiga.com/contacts';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });


# ---
# Paginated example
# ---

// Get contacts list with pagination
const url = 'https://api.kwiga.com/contacts?page=1&per_page=15';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });


# ---
# Filtered example
# ---

// Get contacts list with filters
const url = 'https://api.kwiga.com/contacts?page=1&per_page=15&filters[date_from]=2022-04-27&filters[search]=example.com&with_offers=1';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get contacts list
import requests

url = 'https://api.kwiga.com/contacts'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()


# ---
# Paginated example
# ---

# Get contacts list with pagination
import requests

url = 'https://api.kwiga.com/contacts?page=1&per_page=15'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()


# ---
# Filtered example
# ---

# Get contacts list with filters
import requests

url = 'https://api.kwiga.com/contacts?page=1&per_page=15&filters[date_from]=2022-04-27&filters[search]=example.com&with_offers=1'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/contacts",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}


# ---
# Paginated example
# ---

{
  "method": "GET",
  "url": "https://api.kwiga.com/contacts?page=1&per_page=15",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}


# ---
# Filtered example
# ---

{
  "method": "GET",
  "url": "https://api.kwiga.com/contacts?page=1&per_page=15&filters[date_from]=2022-04-27&filters[search]=example.com&with_offers=1",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
  "data": [
    {
      "id": 1,
      "created_at": "2022-02-04T12:17:32.000000Z",
      "email": "test@example.com",
      "first_name": "James",
      "last_name": "Bond",
      "phone": "+380931234567",
      "tags": [
          {
              "id": 93,
              "name": "test-tag"
          }
      ],
      "offers": [
          {
              "id": 8,
              "unique_offer_code": "ptJmPPXVYs0t",
              "title": "Предложение #8",
              "limit_type": {
                  "id": 1,
                  "name": "Неограничено"
              },
              "limit_of_sales": null
          }
      ]
    },
    "utm": {
        "utm_source": [
            "test-source"
        ],
        "utm_campaign": [],
        "utm_medium": [],
        "utm_term": [
            "test-term"
        ],
        "utm_content": []
    },
    {
      "id": 2,
      "created_at": "2022-02-04T12:17:32.000000Z",
      "email": "test2@example.com",
      "first_name": "Petr",
      "last_name": "Ivanov",
      "phone": "+380983234512",
      "tags": [],
      "offers": []
    }
  ],
  "links": {
        "first": "https://api.kwiga.com/contacts?page=1",
        "last": "https://api.kwiga.com/contacts?page=2",
        "prev": null,
        "next": "https://api.kwiga.com/contacts?page=2"
   },
   "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 2,
        "links": [
            {
                "url": null,
                "label": "&laquo; translation missing: ru.pagination_prev",
                "active": false
            },
            {
                "url": "https://api.kwiga.com/contacts?page=1",
                "label": "1",
                "active": true
            },
            {
                "url": "https://api.kwiga.com/contacts?page=2",
                "label": "2",
                "active": false
            },
            {
                "url": "https://api.kwiga.com/contacts?page=2",
                "label": "translation missing: ru.pagination_next &raquo;",
                "active": false
            }
        ],
        "path": "https://api.kwiga.com/contacts",
        "per_page": 15,
        "to": 2,
        "total": 3
    }
}

GET https://api.kwiga.com/contacts

URL Parameters

filters object optional
Filter parameters
is_active integer optional
Фильтр по активных контактам
date_from datetime optional
Фильтр по дате создания. Параметр 'от'
date_to datetime optional
Фильтр по дате создания. Параметр 'до'
last_activity_from datetime optional
Фильтр по дате последней активность. Параметр 'от'
last_activity_to datetime optional
Фильтр по дате последней активность. Параметр 'до'
search string optional
Фильтр по email, телефону, ФИО
utm_source string optional
utm_source
utm_campaign string optional
utm_campaign
utm_medium string optional
utm_medium
utm_term string optional
utm_term
utm_content string optional
utm_content
offers integer[] optional
offers ids
products integer[] optional
products ids
emails string/string[] optional
Фильтр по точному email-адресу. Принимает строку через запятую или повторяющийся массив; каждое значение должно быть валидным email.
contact_ids integer/integer[] optional
Фильтр по id контактов. Принимает строку через запятую или повторяющийся массив целых чисел.
user_ids integer/integer[] optional
Фильтр по id юзеров (аккаунтов ученика). Принимает строку через запятую или повторяющийся массив целых чисел.

with_orders boolean optional
Дополнительно получить информацию по заказам и предложениям контакта
with_certificates boolean optional
Дополнительно получить информацию по сертификатам контакта
page integer optional
Номер страницы
Default: 1
per_page integer optional
Кол-во элементов выборки
Default: 15
sort_by string optional
Possible values: asc, descDefault: desc

Структура ответа

Элементы текущей страницы.
Ссылки на другие страницы того же списка.
Состояние пагинации для текущего запроса (номер страницы, общее количество и т.д.).

POST-алиас

POST https://api.kwiga.com/contacts/query

Функциональный алиас для GET-эндпоинта выше. Принимает те же параметры в теле запроса вместо query-string — пригодится, когда список фильтров слишком большой для URL (или просто удобнее собирать JSON на клиенте). Body имеет приоритет над query-string, форма ответа идентична.

Получение контакта

Пример запроса:

<?php
// Get contact by ID

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/contacts/:contact', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get contact by ID

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/contacts/:contact' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get contact by ID
const url = 'https://api.kwiga.com/contacts/:contact';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get contact by ID
import requests

url = 'https://api.kwiga.com/contacts/:contact'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/contacts/:contact",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
    "data": {
        "id": 132,
        "email": "bond@example.com",
        "first_name": "James",
        "last_name": "Bond",
        "phone_number": "931234567",
        "tags": [],
        "created_at": "2023-06-26T19:01:00.000000Z",
        "additional_fields": [
            {
                "id": 110,
                "field": {
                    "id": 17,
                    "title": "test global",
                    "is_local": false
                },
                "value": "this is test value"
            }
        ]
    }
}

GET https://api.kwiga.com/contacts/:contact

URL Parameters

with_certificates boolean optional
Дополнительно получить информацию по сертификатам контакта

Структура ответа

Создание контакта

Пример запроса:

<?php
// Create new contact

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'first_name' => 'James',
        'last_name' => 'Bond',
        'email' => 'bond@example.com',
        'send_activation_email' => true,
        'phone' => '+380931234567',
        'manager_ids' => [264, 288],
        'additional_fields' => [{"field_id"=>17, "value"=>"this is test value"}],
    ],
];

$response = $client->request('POST', '/contacts', $options);

$result = json_decode($response->getBody());
?>
<?php
// Create new contact

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
    'first_name' => 'James',
    'last_name' => 'Bond',
    'email' => 'bond@example.com',
    'send_activation_email' => true,
    'phone' => '+380931234567',
    'manager_ids' => [264, 288],
    'additional_fields' => [{"field_id"=>17, "value"=>"this is test value"}],
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request POST 'https://api.kwiga.com/contacts' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"first_name":"James","last_name":"Bond","email":"bond@example.com","send_activation_email":true,"phone":"+380931234567","manager_ids":[264,288],"additional_fields":[{"field_id":17,"value":"this is test value"}]}'
// Create new contact
const url = 'https://api.kwiga.com/contacts';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'first_name': 'James',
    'last_name': 'Bond',
    'email': 'bond@example.com',
    'send_activation_email': true,
    'phone': '+380931234567',
    'manager_ids': [264, 288],
    'additional_fields': [{"field_id"=>17, "value"=>"this is test value"}]
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Create new contact
import requests

url = 'https://api.kwiga.com/contacts'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'first_name': 'James',
    'last_name': 'Bond',
    'email': 'bond@example.com',
    'send_activation_email': true,
    'phone': '+380931234567',
    'manager_ids': [264, 288],
    'additional_fields': [{"field_id"=>17, "value"=>"this is test value"}],
}

response = requests.post(url, headers=headers, json=data)

result = response.json()
{
  "method": "POST",
  "url": "https://api.kwiga.com/contacts",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "first_name": "James",
    "last_name": "Bond",
    "email": "bond@example.com",
    "send_activation_email": true,
    "phone": "+380931234567",
    "manager_ids": [
      264,
      288
    ],
    "additional_fields": [
      {
        "field_id": 17,
        "value": "this is test value"
      }
    ]
  }
}

Пример ответа:

{
    "data": {
        "id": 132,
        "email": "bond@example.com",
        "first_name": "James",
        "last_name": "Bond",
        "phone_number": "931234567",
        "tags": [],
        "created_at": "2023-06-26T19:01:00.000000Z",
        "additional_fields": [
            {
                "id": 110,
                "field": {
                    "id": 17,
                    "title": "test global",
                    "is_local": false
                },
                "value": "this is test value"
            }
        ]
    }
}

POST https://api.kwiga.com/contacts

Request

email string required
Email - должен быть уникальным в рамках кабинета
phone string optional
Номер телефона — формат: +{код страны}{телефон}
first_name string optional
Имя контакта
middle_name string optional
Отчество контакта (или second/middle name)
last_name string optional
Фамилия контакта
name string optional
Полное имя одной строкой. Используйте, если у вас нет разбивки на имя/отчество/фамилию — сервер сохранит значение как имя контакта.
tags string[] optional
Теги
send_activation_email boolean optional
Отправить приветственное письмо
Default: false
locale string optional
Язык контакта в формате iso_2. Полный список значений см. в блоке поддерживаемых локалей.
Default: en
create_order boolean optional
Создать пустой заказ
order_stage_id integer optional
Идентификатор статуса заказа в воронке (можно получить на странице CRM → Заказы → Настройки → Список статусов)
additional_fields object[] optional
Пользовательские (кастомные) поля
field_id integer optional
id пользовательского поля (можно получить в CRM → Контакты → Настройки → Добавление пользовательских полей)
value string optional
Значение пользовательского поля

Структура ответа

Обновление контакта

Пример запроса:

<?php
// Update contact

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'first_name' => 'John',
        'last_name' => 'Black',
        'email' => 'bond123@example.com',
        'phone' => '+380931112233',
        'tags' => ["test-tag"],
    ],
];

$response = $client->request('PUT', '/contacts/:contact', $options);

$result = json_decode($response->getBody());
?>
<?php
// Update contact

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');

$data = [
    'first_name' => 'John',
    'last_name' => 'Black',
    'email' => 'bond123@example.com',
    'phone' => '+380931112233',
    'tags' => ["test-tag"],
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request PUT 'https://api.kwiga.com/contacts/:contact' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"first_name":"John","last_name":"Black","email":"bond123@example.com","phone":"+380931112233","tags":["test-tag"]}'
// Update contact
const url = 'https://api.kwiga.com/contacts/:contact';

const options = {
  method: 'PUT',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'first_name': 'John',
    'last_name': 'Black',
    'email': 'bond123@example.com',
    'phone': '+380931112233',
    'tags': ["test-tag"]
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Update contact
import requests

url = 'https://api.kwiga.com/contacts/:contact'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'first_name': 'John',
    'last_name': 'Black',
    'email': 'bond123@example.com',
    'phone': '+380931112233',
    'tags': ["test-tag"],
}

response = requests.put(url, headers=headers, json=data)

result = response.json()
{
  "method": "PUT",
  "url": "https://api.kwiga.com/contacts/:contact",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "first_name": "John",
    "last_name": "Black",
    "email": "bond123@example.com",
    "phone": "+380931112233",
    "tags": [
      "test-tag"
    ]
  }
}

Пример ответа:

{
    "data": {
        "id": 132,
        "email": "bond@example.com",
        "first_name": "James",
        "last_name": "Bond",
        "phone_number": "931234567",
        "tags": [],
        "created_at": "2023-06-26T19:01:00.000000Z",
        "additional_fields": [
            {
                "id": 110,
                "field": {
                    "id": 17,
                    "title": "test global",
                    "is_local": false
                },
                "value": "this is test value"
            }
        ]
    }
}

PUT https://api.kwiga.com/contacts/:contact

Request

email string optional
Email - должен быть уникальным в рамках кабинета
phone string optional
Номер телефона — формат: +{код страны}{телефон}
first_name string optional
Имя контакта
last_name string optional
Фамилия контакта
tags string[] optional
Теги. Работают в режиме sync. То есть на контакте будут только те теги, которые будут переданы
additional_fields object[] optional
Пользовательские (кастомные) поля
field_id integer optional
id пользовательского поля (можно получить в CRM → Контакты → Настройки → Добавление пользовательских полей)
value string optional
Значение пользовательского поля

Структура ответа

Добавить покупку

Этот метод создаёт или находит контакт и добавляет ему покупку предложения

Пример запроса:

<?php
// Add purchase to contact

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'first_name' => 'James',
        'last_name' => 'Bond',
        'email' => 'bond@example.com',
        'send_activation_email' => true,
        'phone' => '+380931234567',
        'offer_id' => 18,
        'additional_fields' => [{"field_id"=>17, "value"=>"this is test value"}],
    ],
];

$response = $client->request('POST', '/contacts/purchases', $options);

$result = json_decode($response->getBody());
?>
<?php
// Add purchase to contact

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/purchases');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
    'first_name' => 'James',
    'last_name' => 'Bond',
    'email' => 'bond@example.com',
    'send_activation_email' => true,
    'phone' => '+380931234567',
    'offer_id' => 18,
    'additional_fields' => [{"field_id"=>17, "value"=>"this is test value"}],
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request POST 'https://api.kwiga.com/contacts/purchases' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"first_name":"James","last_name":"Bond","email":"bond@example.com","send_activation_email":true,"phone":"+380931234567","offer_id":18,"additional_fields":[{"field_id":17,"value":"this is test value"}]}'
// Add purchase to contact
const url = 'https://api.kwiga.com/contacts/purchases';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'first_name': 'James',
    'last_name': 'Bond',
    'email': 'bond@example.com',
    'send_activation_email': true,
    'phone': '+380931234567',
    'offer_id': 18,
    'additional_fields': [{"field_id"=>17, "value"=>"this is test value"}]
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Add purchase to contact
import requests

url = 'https://api.kwiga.com/contacts/purchases'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'first_name': 'James',
    'last_name': 'Bond',
    'email': 'bond@example.com',
    'send_activation_email': true,
    'phone': '+380931234567',
    'offer_id': 18,
    'additional_fields': [{"field_id"=>17, "value"=>"this is test value"}],
}

response = requests.post(url, headers=headers, json=data)

result = response.json()
{
  "method": "POST",
  "url": "https://api.kwiga.com/contacts/purchases",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "first_name": "James",
    "last_name": "Bond",
    "email": "bond@example.com",
    "send_activation_email": true,
    "phone": "+380931234567",
    "offer_id": 18,
    "additional_fields": [
      {
        "field_id": 17,
        "value": "this is test value"
      }
    ]
  }
}

Пример ответа:

{
    "data": {
        "id": 132,
        "email": "bond@example.com",
        "first_name": "James",
        "last_name": "Bond",
        "phone_number": "931234567",
        "tags": [],
        "created_at": "2023-06-26T19:01:00.000000Z",
        "additional_fields": [
            {
                "id": 110,
                "field": {
                    "id": 17,
                    "title": "test global",
                    "is_local": false
                },
                "value": "this is test value"
            }
        ],
        "orders": [
            {
                "id": 1060,
                "type_id": 1,
                "first_paid_at": "2023-10-13T18:04:02.000000Z",
                "paid_at": "2023-10-13T18:04:02.000000Z",
                "created_at": "2023-10-13T18:04:02.000000Z",
                "updated_at": "2023-10-13T18:04:02.000000Z",
                "products": [
                    {
                        "id": 25,
                        "productable_id": 10,
                        "productable_type": "course",
                        "title": "test course",
                        "url": "https://sample-school.kwiga.com/courses/test-course"
                    }
                ],
                "payments": [
                    {
                        "id": 1231,
                        "status": 2,
                        "status_title": "Paid",
                        "payment_type": null,
                        "payment_type_title": null,
                        "payment_form": null,
                        "payment_form_title": null,
                        "price_info": {
                            "amount": 0,
                            "currency": {
                                "id": 147,
                                "code": "UAH",
                                "html_code": "₴",
                                "html_letter_code": "грн"
                            }
                        },
                        "paid_at": "2023-10-13T18:04:02.000000Z",
                        "created_at": "2023-10-13T18:04:02.000000Z",
                        "updated_at": "2023-10-13T18:04:02.000000Z",
                        "transactions": [
                            {
                                "id": 28,
                                "merchant_id": 2,
                                "payment_id": 1231,
                                "order_id": 1060,
                                "payment_system_status": "Declined",
                                "failure_reason": "Cardholder session expired",
                                "price": "147",
                                "currency_code": "UAH",
                                "payer_account": null,
                                "card_mask": null,
                                "rrn": null,
                                "fee": null,
                                "created_at": "2023-11-03T08:45:10.000000Z"
                            },
                            {
                                "id": 29,
                                "merchant_id": 2,
                                "payment_id": 1231,
                                "order_id": 1060,
                                "payment_system_status": "Approved",
                                "failure_reason": null,
                                "price": "147",
                                "currency_code": "UAH",
                                "payer_account": "JKNNASWTZ99SJ",
                                "card_mask": "53****2144",
                                "rrn": 330710335365,
                                "fee": null,
                                "created_at": "2023-11-03T08:48:10.000000Z"
                            }
                        ]
                    }
                ],
                "paid_status": "paid",
                "paid_status_title": "Сплачено",
                "order_stage": {
                    "id": 43,
                    "title": "Стейдж 1",
                    "order_group": {
                        "id": 22,
                        "slug": "voronka-1",
                        "title": null,
                        "order": 4,
                        "created_at": "2023-06-22T18:20:53.000000Z"
                    },
                    "order_funnel": {
                        "id": 1,
                        "title": "Воронка за замовчуванням",
                        "created_at": "2023-05-26T09:47:16.000000Z"
                    },
                    "created_at": "2023-06-22T18:21:10.000000Z"
                },
                "cost_info": {
                    "amount": 0,
                    "currency": {
                        "id": 147,
                        "code": "UAH",
                        "html_code": "₴",
                        "html_letter_code": "грн"
                    }
                },
                "managers": [
                    {
                        "id": 264,
                        "name": "test",
                        "email": "testfsdf@fdafasd.fsd"
                    },
                    {
                        "id": 288,
                        "name": "fdsf",
                        "email": "sdfs@fdf.dfss"
                    }
                ]
            }
        ]
    }
}

POST https://api.kwiga.com/contacts/purchases

Request

email string required
Email - должен быть уникальным в рамках кабинета
phone string optional
Номер телефона — формат: +{код страны}{телефон}
first_name string optional
Имя контакта
middle_name string optional
Отчество контакта (или second/middle name)
last_name string optional
Фамилия контакта
name string optional
Полное имя одной строкой. Используйте, если у вас нет разбивки на имя/отчество/фамилию — сервер сохранит значение как имя контакта.
tags string[] optional
Теги
send_activation_email boolean optional
Отправить приветственное письмо
Default: false
send_product_access_email boolean optional
Отправить письмо о доступе к продукту
Default: false
send_payment_success_email boolean optional
Отправить письмо об успешной оплате предложения
Default: false
locale string optional
Язык контакта в формате iso_2. Полный список значений см. в блоке поддерживаемых локалей.
Default: en
offer_id integer optional
Идентификатор предложения — берётся из конца ссылки страницы редактирования предложения (например, https://sample-school.kwiga.com/expert/payments/offers/edit/3858). Если не передан — запрос обращается к полю product_ids.
product_ids integer[] optional
Массив id продуктов.
Если offer_id и product_ids отсутсвуют, то будет создан пустой заказ
order_stage_id integer optional
Идентификатор статуса заказа в воронке (можно получить на странице CRM → Заказы → Настройки → Список статусов)
is_paid boolean optional
Помечает созданный заказ как оплаченный.
Default: true
manager_ids integer[] optional
Менеджеры заказа (можно получить на странице *Настройки->Доступи по управлению*)
comment string optional
Комментарий к заказу. Max: 5000
Max length: 5000 characters
additional_fields object[] optional
Пользовательские (кастомные) поля
field_id integer optional
id пользовательского поля (можно получить в CRM → Контакты → Настройки → Добавление пользовательских полей)
value string optional
Значение пользовательского поля

Структура ответа

Теги

Добавление тегов контактам

Пример запроса:

<?php
// Add tags to contacts

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'contacts' => [23698],
        'tags' => ["test-tag"],
    ],
];

$response = $client->request('POST', '/contacts/tags', $options);

$result = json_decode($response->getBody());
?>
<?php
// Add tags to contacts

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/tags');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
    'contacts' => [23698],
    'tags' => ["test-tag"],
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request POST 'https://api.kwiga.com/contacts/tags' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"contacts":[23698],"tags":["test-tag"]}'
// Add tags to contacts
const url = 'https://api.kwiga.com/contacts/tags';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'contacts': [23698],
    'tags': ["test-tag"]
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Add tags to contacts
import requests

url = 'https://api.kwiga.com/contacts/tags'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'contacts': [23698],
    'tags': ["test-tag"],
}

response = requests.post(url, headers=headers, json=data)

result = response.json()
{
  "method": "POST",
  "url": "https://api.kwiga.com/contacts/tags",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "contacts": [
      23698
    ],
    "tags": [
      "test-tag"
    ]
  }
}

Пример ответа:

{
    "success": true
}

POST https://api.kwiga.com/contacts/tags

Request

contact_ids integer/integer[] optional
Фильтр по id контактов. Принимает строку через запятую или повторяющийся массив целых чисел.
contacts integer[] optional
Legacy-алиас для contact_ids. Сохранён ради обратной совместимости; в новых интеграциях используйте contact_ids.
tags string[] required
Теги

Структура ответа

Тело ответа возвращается без обёртки.

Удаление тегов контактов

Пример запроса:

<?php
// Remove tags from contacts

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'contacts' => [23698],
        'tags' => ["test-tag"],
    ],
];

$response = $client->request('DELETE', '/contacts/tags', $options);

$result = json_decode($response->getBody());
?>
<?php
// Remove tags from contacts

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/tags');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');

$data = [
    'contacts' => [23698],
    'tags' => ["test-tag"],
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request DELETE 'https://api.kwiga.com/contacts/tags' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"contacts":[23698],"tags":["test-tag"]}'
// Remove tags from contacts
const url = 'https://api.kwiga.com/contacts/tags';

const options = {
  method: 'DELETE',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'contacts': [23698],
    'tags': ["test-tag"]
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Remove tags from contacts
import requests

url = 'https://api.kwiga.com/contacts/tags'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'contacts': [23698],
    'tags': ["test-tag"],
}

response = requests.delete(url, headers=headers, json=data)

result = response.json()
{
  "method": "DELETE",
  "url": "https://api.kwiga.com/contacts/tags",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "contacts": [
      23698
    ],
    "tags": [
      "test-tag"
    ]
  }
}

Пример ответа:

{
    "success": true
}

DELETE https://api.kwiga.com/contacts/tags

Request

contact_ids integer/integer[] optional
Фильтр по id контактов. Принимает строку через запятую или повторяющийся массив целых чисел.
contacts integer[] optional
Legacy-алиас для contact_ids. Сохранён ради обратной совместимости; в новых интеграциях используйте contact_ids.
tags string[] required
Теги

Структура ответа

Тело ответа возвращается без обёртки.

Продукты и подписки

Список продуктов контакта

Пример запроса:

<?php
// Get contact products

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/contacts/:contact/products', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get contact products

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact/products');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/contacts/:contact/products' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get contact products
const url = 'https://api.kwiga.com/contacts/:contact/products';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get contact products
import requests

url = 'https://api.kwiga.com/contacts/:contact/products'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/contacts/:contact/products",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
  "data": [
    {
      "id": 299,
      "productable_id": 142,
      "productable_type": "course",
      "title": "Copy Test backup",
      "image_url": "",
      "is_published": false,
      "aggregated_subscription": {
        "is_active": true,
        "is_paid": true,
        "start_at": "2025-05-05T15:09:04.000000Z",
        "end_at": null,
        "offer_end_at": null,
        "order_end_at": null,
        "count_available_days": 39,
        "count_left_days": null,
        "state": {
          "id": 2,
          "name": "Open",
          "title": "Open"
        }
      },
      "subscriptions": [
        {
          "id": 4775,
          "creator_id": 24457,
          "user_id": 24457,
          "product_id": 299,
          "order_id": 2710,
          "offer_id": 757,
          "is_active": true,
          "start_at": "2025-05-05T15:09:04.000000Z",
          "order_end_at": null,
          "end_at": null,
          "paid_at": "2025-05-05T15:09:04.000000Z",
          "created_at": "2025-05-05T15:09:04.000000Z",
          "updated_at": "2025-05-05T15:09:04.000000Z"
        }
      ]
    }
  ]
}

GET https://api.kwiga.com/contacts/:contact/products

Возвращает список продуктов контакта с информацией о подписках и сроках доступа. Параметр aggregated_subscription содержит сводную информацию о сроках и активности доступа на основе всех подписок пользователя по продукту. Параметр subscriptions содержит массив всех подписок пользователя по продукту.

Структура ответа

Элементы текущей страницы.
Ссылки на другие страницы того же списка.
Состояние пагинации для текущего запроса (номер страницы, общее количество и т.д.).

Удалить продукт у контакта

Пример запроса:

<?php
// Delete contact product

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('DELETE', '/contacts/:contact/products/:product', $options);

$result = json_decode($response->getBody());
?>
<?php
// Delete contact product

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact/products/:product');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');

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

$result = json_decode($response);
?>
curl --location --request DELETE 'https://api.kwiga.com/contacts/:contact/products/:product' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Delete contact product
const url = 'https://api.kwiga.com/contacts/:contact/products/:product';

const options = {
  method: 'DELETE',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Delete contact product
import requests

url = 'https://api.kwiga.com/contacts/:contact/products/:product'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.delete(url, headers=headers)

result = response.json()
{
  "method": "DELETE",
  "url": "https://api.kwiga.com/contacts/:contact/products/:product",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
  "success": true
}

DELETE https://api.kwiga.com/contacts/:contact/products/:product

Удаляет подписки контакта по заданному продукту. Заказы становятся кастомными без доступа к удалённым продуктам.

Структура ответа

Тело ответа возвращается без обёртки.

Удалить продукты у контакта (массовое)

Пример запроса:

<?php
// Delete contact products in bulk

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'email' => 'user@example.com',
        'product_id' => 299,
        'offers' => [421],
    ],
];

$response = $client->request('DELETE', '/contacts/products', $options);

$result = json_decode($response->getBody());
?>
<?php
// Delete contact products in bulk

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/products');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');

$data = [
    'email' => 'user@example.com',
    'product_id' => 299,
    'offers' => [421],
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request DELETE 'https://api.kwiga.com/contacts/products' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"email":"user@example.com","product_id":299,"offers":[421]}'
// Delete contact products in bulk
const url = 'https://api.kwiga.com/contacts/products';

const options = {
  method: 'DELETE',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'email': 'user@example.com',
    'product_id': 299,
    'offers': [421]
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Delete contact products in bulk
import requests

url = 'https://api.kwiga.com/contacts/products'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'email': 'user@example.com',
    'product_id': 299,
    'offers': [421],
}

response = requests.delete(url, headers=headers, json=data)

result = response.json()
{
  "method": "DELETE",
  "url": "https://api.kwiga.com/contacts/products",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "email": "user@example.com",
    "product_id": 299,
    "offers": [
      421
    ]
  }
}

Пример ответа:

{
  "data": {
    "affected_subscriptions": [
      2309,
      2661
    ],
    "affected_orders": [
      1578,
      1784
    ],
    "affected_by_offers": {
      "421": {
        "affected_subscriptions": [
          2309,
          2661
        ],
        "affected_orders": [
          1578,
          1784
        ]
      }
    },
    "affected_by_products": []
  },
  "contact_id": 125
}

DELETE https://api.kwiga.com/contacts/products

Удаляет подписки по заданным условиям, а заказы становятся кастомными без доступа к удалённым продуктам. Можно комбинировать параметры product_id и offers для удаления части продуктов по определённому офферу.

Parameters

email string optional
Email контакта. Обязательный если contact_id не представлен
contact_id integer optional
Id контакта. Обязательный если email не представлен
product_id integer optional
Id продукта. Обязательный если offers не представлен. Можно получить например в эндпоинте Список продуктов контакта или в Списке курсов (это будет product_id поле в курсе)
offers integer[] optional
Массив с id предложений по которым удаляем подписки. Обязательный если product_id не представлен. Можно получить в адресной строке редактирования предложения или в апи в списке предложений контакта.

Структура ответа

Содержимое ответа.
contact_id integer
ID of the contact whose subscriptions were affected

Заморозить подписку контакта

Пример запроса:

<?php
// Freeze the subscription for 30 days

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'count_frozen_days' => 30,
    ],
];

$response = $client->request('POST', '/contacts/:contact/products/:product/freeze', $options);

$result = json_decode($response->getBody());
?>
<?php
// Freeze the subscription for 30 days

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact/products/:product/freeze');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
    'count_frozen_days' => 30,
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request POST 'https://api.kwiga.com/contacts/:contact/products/:product/freeze' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"count_frozen_days":30}'
// Freeze the subscription for 30 days
const url = 'https://api.kwiga.com/contacts/:contact/products/:product/freeze';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'count_frozen_days': 30
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Freeze the subscription for 30 days
import requests

url = 'https://api.kwiga.com/contacts/:contact/products/:product/freeze'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'count_frozen_days': 30,
}

response = requests.post(url, headers=headers, json=data)

result = response.json()
{
  "method": "POST",
  "url": "https://api.kwiga.com/contacts/:contact/products/:product/freeze",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "count_frozen_days": 30
  }
}

Пример ответа:

{
  "data": {
    "id": 299,
    "productable_id": 142,
    "productable_type": "course",
    "title": "JS Foundations",
    "image_url": "https://cdn.kwiga.com/preview.jpg",
    "url": "https://kwiga.com/courses/js-foundations",
    "is_published": true,
    "aggregated_subscription": {
      "is_active": false,
      "is_paid": true,
      "start_at": "2026-06-05T00:00:00.000000Z",
      "end_at": "2026-08-04T00:00:00.000000Z",
      "offer_end_at": null,
      "order_end_at": "2026-08-04T00:00:00.000000Z",
      "frozen_at": "2026-05-20T10:00:00.000000Z",
      "extended_at": null,
      "count_available_days": 60,
      "count_left_days": 60,
      "state": {
        "id": 4,
        "name": "Frozen",
        "title": "Frozen"
      }
    },
    "subscriptions": [
      {
        "id": 4775,
        "creator_id": 24457,
        "user_id": 24457,
        "product_id": 299,
        "order_id": 2710,
        "offer_id": 757,
        "is_active": false,
        "start_at": "2026-06-05T00:00:00.000000Z",
        "order_end_at": "2026-08-04T00:00:00.000000Z",
        "end_at": "2026-08-04T00:00:00.000000Z",
        "frozen_at": "2026-05-20T10:00:00.000000Z",
        "extended_at": null,
        "paid_at": "2026-05-05T15:09:04.000000Z",
        "created_at": "2026-05-05T15:09:04.000000Z",
        "updated_at": "2026-05-06T12:42:11.000000Z"
      }
    ]
  }
}

POST https://api.kwiga.com/contacts/:contact/products/:product/freeze

Замораживает все активные подписки контакта на этом продукте на count_frozen_days календарных дней. Заморозка сдвигает start_at / end_at / order_end_at вперёд, делает подписку неактивной и уведомляет ученика. При разморозке (ручной или автоматической по окончании срока) start_at возвращается назад на основе данных заказа ученика, а не просто откатывается на ту же дельту.

URL Parameters

count_frozen_days integer required
На сколько дней заморозить подписку (1–400).
Range: 1 – 400

Структура ответа

Содержимое ответа.

Разморозить подписку контакта

Пример запроса:

<?php
// Unfreeze the subscription (no body)

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('POST', '/contacts/:contact/products/:product/unfreeze', $options);

$result = json_decode($response->getBody());
?>
<?php
// Unfreeze the subscription (no body)

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact/products/:product/unfreeze');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request POST 'https://api.kwiga.com/contacts/:contact/products/:product/unfreeze' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Unfreeze the subscription (no body)
const url = 'https://api.kwiga.com/contacts/:contact/products/:product/unfreeze';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Unfreeze the subscription (no body)
import requests

url = 'https://api.kwiga.com/contacts/:contact/products/:product/unfreeze'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.post(url, headers=headers)

result = response.json()
{
  "method": "POST",
  "url": "https://api.kwiga.com/contacts/:contact/products/:product/unfreeze",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
  "data": {
    "id": 299,
    "productable_id": 142,
    "productable_type": "course",
    "title": "JS Foundations",
    "image_url": "https://cdn.kwiga.com/preview.jpg",
    "url": "https://kwiga.com/courses/js-foundations",
    "is_published": true,
    "aggregated_subscription": {
      "is_active": false,
      "is_paid": true,
      "start_at": "2026-06-05T00:00:00.000000Z",
      "end_at": "2026-08-04T00:00:00.000000Z",
      "offer_end_at": null,
      "order_end_at": "2026-08-04T00:00:00.000000Z",
      "frozen_at": "2026-05-20T10:00:00.000000Z",
      "extended_at": null,
      "count_available_days": 60,
      "count_left_days": 60,
      "state": {
        "id": 4,
        "name": "Frozen",
        "title": "Frozen"
      }
    },
    "subscriptions": [
      {
        "id": 4775,
        "creator_id": 24457,
        "user_id": 24457,
        "product_id": 299,
        "order_id": 2710,
        "offer_id": 757,
        "is_active": false,
        "start_at": "2026-06-05T00:00:00.000000Z",
        "order_end_at": "2026-08-04T00:00:00.000000Z",
        "end_at": "2026-08-04T00:00:00.000000Z",
        "frozen_at": "2026-05-20T10:00:00.000000Z",
        "extended_at": null,
        "paid_at": "2026-05-05T15:09:04.000000Z",
        "created_at": "2026-05-05T15:09:04.000000Z",
        "updated_at": "2026-05-06T12:42:11.000000Z"
      }
    ]
  }
}

POST https://api.kwiga.com/contacts/:contact/products/:product/unfreeze

Размораживает все замороженные подписки контакта на этом продукте. Восстанавливает start_at, пересчитывает end_at / order_end_at (отнимает неиспользованный остаток заморозки), активирует доступ если он не истёк и уведомляет ученика. Тело пустое.

Структура ответа

Содержимое ответа.

Продлить подписку контакта

Пример запроса:

<?php
// Extend access by 14 days

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'count_extend_days' => 14,
    ],
];

$response = $client->request('POST', '/contacts/:contact/products/:product/extend', $options);

$result = json_decode($response->getBody());
?>
<?php
// Extend access by 14 days

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact/products/:product/extend');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
    'count_extend_days' => 14,
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request POST 'https://api.kwiga.com/contacts/:contact/products/:product/extend' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"count_extend_days":14}'
// Extend access by 14 days
const url = 'https://api.kwiga.com/contacts/:contact/products/:product/extend';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'count_extend_days': 14
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Extend access by 14 days
import requests

url = 'https://api.kwiga.com/contacts/:contact/products/:product/extend'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'count_extend_days': 14,
}

response = requests.post(url, headers=headers, json=data)

result = response.json()
{
  "method": "POST",
  "url": "https://api.kwiga.com/contacts/:contact/products/:product/extend",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "count_extend_days": 14
  }
}

Пример ответа:

{
  "data": {
    "id": 299,
    "productable_id": 142,
    "productable_type": "course",
    "title": "JS Foundations",
    "image_url": "https://cdn.kwiga.com/preview.jpg",
    "url": "https://kwiga.com/courses/js-foundations",
    "is_published": true,
    "aggregated_subscription": {
      "is_active": false,
      "is_paid": true,
      "start_at": "2026-06-05T00:00:00.000000Z",
      "end_at": "2026-08-04T00:00:00.000000Z",
      "offer_end_at": null,
      "order_end_at": "2026-08-04T00:00:00.000000Z",
      "frozen_at": "2026-05-20T10:00:00.000000Z",
      "extended_at": null,
      "count_available_days": 60,
      "count_left_days": 60,
      "state": {
        "id": 4,
        "name": "Frozen",
        "title": "Frozen"
      }
    },
    "subscriptions": [
      {
        "id": 4775,
        "creator_id": 24457,
        "user_id": 24457,
        "product_id": 299,
        "order_id": 2710,
        "offer_id": 757,
        "is_active": false,
        "start_at": "2026-06-05T00:00:00.000000Z",
        "order_end_at": "2026-08-04T00:00:00.000000Z",
        "end_at": "2026-08-04T00:00:00.000000Z",
        "frozen_at": "2026-05-20T10:00:00.000000Z",
        "extended_at": null,
        "paid_at": "2026-05-05T15:09:04.000000Z",
        "created_at": "2026-05-05T15:09:04.000000Z",
        "updated_at": "2026-05-06T12:42:11.000000Z"
      }
    ]
  }
}

POST https://api.kwiga.com/contacts/:contact/products/:product/extend

Добавляет count_extend_days календарных дней к end_at / order_end_at всех подписок контакта на этом продукте. Та же ручка используется в CRM-автоматизациях для реактивации просроченных подписок.

URL Parameters

count_extend_days integer required
На сколько дней продлить подписку (1–400).
Range: 1 – 400

Структура ответа

Содержимое ответа.

Изменить дату окончания подписки

Пример запроса:

<?php
// Set the subscription end to the given date in the chosen timezone

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'end_at' => '2027-01-01 23:59:59',
        'timezone_id' => 1,
    ],
];

$response = $client->request('PUT', '/contacts/:contact/products/:product/end-date', $options);

$result = json_decode($response->getBody());
?>
<?php
// Set the subscription end to the given date in the chosen timezone

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact/products/:product/end-date');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');

$data = [
    'end_at' => '2027-01-01 23:59:59',
    'timezone_id' => 1,
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request PUT 'https://api.kwiga.com/contacts/:contact/products/:product/end-date' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"end_at":"2027-01-01 23:59:59","timezone_id":1}'
// Set the subscription end to the given date in the chosen timezone
const url = 'https://api.kwiga.com/contacts/:contact/products/:product/end-date';

const options = {
  method: 'PUT',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'end_at': '2027-01-01 23:59:59',
    'timezone_id': 1
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Set the subscription end to the given date in the chosen timezone
import requests

url = 'https://api.kwiga.com/contacts/:contact/products/:product/end-date'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'end_at': '2027-01-01 23:59:59',
    'timezone_id': 1,
}

response = requests.put(url, headers=headers, json=data)

result = response.json()
{
  "method": "PUT",
  "url": "https://api.kwiga.com/contacts/:contact/products/:product/end-date",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "end_at": "2027-01-01 23:59:59",
    "timezone_id": 1
  }
}

Пример ответа:

{
  "data": {
    "id": 299,
    "productable_id": 142,
    "productable_type": "course",
    "title": "JS Foundations",
    "image_url": "https://cdn.kwiga.com/preview.jpg",
    "url": "https://kwiga.com/courses/js-foundations",
    "is_published": true,
    "aggregated_subscription": {
      "is_active": false,
      "is_paid": true,
      "start_at": "2026-06-05T00:00:00.000000Z",
      "end_at": "2026-08-04T00:00:00.000000Z",
      "offer_end_at": null,
      "order_end_at": "2026-08-04T00:00:00.000000Z",
      "frozen_at": "2026-05-20T10:00:00.000000Z",
      "extended_at": null,
      "count_available_days": 60,
      "count_left_days": 60,
      "state": {
        "id": 4,
        "name": "Frozen",
        "title": "Frozen"
      }
    },
    "subscriptions": [
      {
        "id": 4775,
        "creator_id": 24457,
        "user_id": 24457,
        "product_id": 299,
        "order_id": 2710,
        "offer_id": 757,
        "is_active": false,
        "start_at": "2026-06-05T00:00:00.000000Z",
        "order_end_at": "2026-08-04T00:00:00.000000Z",
        "end_at": "2026-08-04T00:00:00.000000Z",
        "frozen_at": "2026-05-20T10:00:00.000000Z",
        "extended_at": null,
        "paid_at": "2026-05-05T15:09:04.000000Z",
        "created_at": "2026-05-05T15:09:04.000000Z",
        "updated_at": "2026-05-06T12:42:11.000000Z"
      }
    ]
  }
}

PUT https://api.kwiga.com/contacts/:contact/products/:product/end-date

Перезаписывает end_atorder_end_at, если она есть) явной датой. Дата интерпретируется в таймзоне timezone_id (см. эндпоинт списка таймзон); если timezone_id не указан, end_at трактуется как UTC.

URL Parameters

end_at datetime required
Новая дата окончания подписки. Интерпретируется в таймзоне кабинета, указанной в timezone_id.
timezone_id integer optional
ID таймзоны из эндпоинта списка таймзон. Необязательный — если не указан, end_at трактуется как UTC.

Структура ответа

Содержимое ответа.

Баллы (награды)

Журнал баллов контакта

Пример запроса:

<?php
// List rewards (default ordering)

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/contacts/:contact/rewards', $options);

$result = json_decode($response->getBody());
?>


# ---
# Filtered example
# ---

<?php
// Only accruals from quiz-passed and manual reasons, scoped to two products

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/contacts/:contact/rewards?filters[accrual_type]=accrued&filters[reason_types]=1,6&filters[product_ids]=10,11&per_page=50', $options);

$result = json_decode($response->getBody());
?>
<?php
// List rewards (default ordering)

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact/rewards');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>


# ---
# Filtered example
# ---

<?php
// Only accruals from quiz-passed and manual reasons, scoped to two products

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact/rewards?filters[accrual_type]=accrued&filters[reason_types]=1,6&filters[product_ids]=10,11&per_page=50');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/contacts/:contact/rewards' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'


# ---
# Filtered example
# ---

curl --location --request GET 'https://api.kwiga.com/contacts/:contact/rewards?filters[accrual_type]=accrued&filters[reason_types]=1,6&filters[product_ids]=10,11&per_page=50' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// List rewards (default ordering)
const url = 'https://api.kwiga.com/contacts/:contact/rewards';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });


# ---
# Filtered example
# ---

// Only accruals from quiz-passed and manual reasons, scoped to two products
const url = 'https://api.kwiga.com/contacts/:contact/rewards?filters[accrual_type]=accrued&filters[reason_types]=1,6&filters[product_ids]=10,11&per_page=50';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# List rewards (default ordering)
import requests

url = 'https://api.kwiga.com/contacts/:contact/rewards'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()


# ---
# Filtered example
# ---

# Only accruals from quiz-passed and manual reasons, scoped to two products
import requests

url = 'https://api.kwiga.com/contacts/:contact/rewards?filters[accrual_type]=accrued&filters[reason_types]=1,6&filters[product_ids]=10,11&per_page=50'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/contacts/:contact/rewards",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}


# ---
# Filtered example
# ---

{
  "method": "GET",
  "url": "https://api.kwiga.com/contacts/:contact/rewards?filters[accrual_type]=accrued&filters[reason_types]=1,6&filters[product_ids]=10,11&per_page=50",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
  "data": [
    {
      "id": 9821,
      "points": 50,
      "accrual_type": {
        "id": "accrued",
        "slug": "Plus",
        "title": "Accrued"
      },
      "reason_type": {
        "id": 6,
        "slug": "Manual",
        "title": "Manual"
      },
      "event": null,
      "event_name": null,
      "eventable_type": null,
      "eventable_id": null,
      "event_comment": null,
      "manual_comment": "Bonus for active chat participation",
      "is_visible_to_student": true,
      "message": "Manual points accrual: Bonus for active chat participation",
      "product": null,
      "creator": {
        "id": 17,
        "name": "Alice Curator",
        "email": "alice@kwiga.com"
      },
      "created_at": "2026-05-24T11:14:51.000000Z"
    },
    {
      "id": 9820,
      "points": 25,
      "accrual_type": {
        "id": "accrued",
        "slug": "Plus",
        "title": "Accrued"
      },
      "reason_type": {
        "id": 1,
        "slug": "QuizPassed",
        "title": "Quiz passed"
      },
      "event": "quiz.passed",
      "event_name": "Quiz passed",
      "eventable_type": "quiz_attempt",
      "eventable_id": 4821,
      "event_comment": null,
      "manual_comment": null,
      "is_visible_to_student": null,
      "message": "Quiz Module 3 final test in lesson Closures & scope -> Attempt",
      "product": {
        "id": 431,
        "productable_type": "course",
        "productable_id": 226,
        "name": "JS Foundations",
        "url": "https://lm4.kwiga.com/courses/js-foundations"
      },
      "creator": null,
      "created_at": "2026-05-23T11:14:51.000000Z"
    },
    {
      "id": 9810,
      "points": -30,
      "accrual_type": {
        "id": "deducted",
        "slug": "Minus",
        "title": "Deducted"
      },
      "reason_type": {
        "id": 6,
        "slug": "Manual",
        "title": "Manual"
      },
      "event": null,
      "event_name": null,
      "eventable_type": null,
      "eventable_id": null,
      "event_comment": null,
      "manual_comment": null,
      "is_visible_to_student": null,
      "message": "Manual points accrual",
      "product": null,
      "creator": {
        "id": 17,
        "name": "Alice Curator",
        "email": "alice@kwiga.com"
      },
      "created_at": "2026-05-22T09:00:00.000000Z"
    }
  ],
  "links": {
    "first": "https://api.kwiga.com/contacts/142/rewards?page=1",
    "last": "https://api.kwiga.com/contacts/142/rewards?page=1",
    "prev": null,
    "next": null
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 1,
    "path": "https://api.kwiga.com/contacts/142/rewards",
    "per_page": 15,
    "to": 3,
    "total": 3
  },
  "sum_points": 45,
  "sum_accrued_points": 75,
  "sum_deducted_points": -30
}

GET https://api.kwiga.com/contacts/:contact/rewards

Возвращает журнал начислений и списаний баллов контакта — по квизам, заказам, подаркам, автоматизациям и ручным действиям кураторов. На верхнем уровне ответа также возвращаются суммы sum_points, sum_accrued_points, sum_deducted_points по текущему фильтру.

URL Parameters

filters object optional
translation missing: ru.contacts.params.rewards_filters
product_ids integer/integer[] optional
Фильтр по id продуктов, к которым привязаны баллы.
accrual_type string optional
Фильтр по направлению операции: accrued (положительные баллы) или deducted (отрицательные).
Example: accruedPossible values: accrued, deducted
reason_types integer/integer[] optional

Фильтр по id причины начисления. Несколько значений объединяются «или».

  • 1 — Практика пройдена
  • 2 — Отмена результатов практики
  • 3 — Сброс баллов за попытку
  • 4 — Новые баллы за попытку
  • 5 — Оплата баллами
  • 6 — Вручную
  • 7 — Автоматизация
  • 8 — Изменены баллы за попытку
  • 9 — Оплата баллами за подарок
Possible values: 1, 2, 3, 4, 5, 6, 7, 8, 9

per_page integer optional
Количество элементов на странице.
Maximum: 500Default: 15
page integer optional
Номер страницы.
Default: 1

Структура ответа

Элементы текущей страницы.
Ссылки на другие страницы того же списка.
Состояние пагинации для текущего запроса (номер страницы, общее количество и т.д.).
sum_points number
Чистый итог баллов по текущему фильтру (начисления минус списания).
Сумма положительных записей (только начисления) по текущему фильтру.
Сумма отрицательных записей (только списания) по текущему фильтру.

Ручное начисление/списание баллов контакту Идемпотентный

Пример запроса:

<?php
// Accrue 50 points for finishing a milestone, unbound from any product

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'points' => 50,
    ],
];

$response = $client->request('POST', '/contacts/:contact/rewards', $options);

$result = json_decode($response->getBody());
?>


# ---
# Accrue_with_product example
# ---

<?php
// Accrue 100 points scoped to product id 10

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'points' => 100,
        'product_id' => 10,
    ],
];

$response = $client->request('POST', '/contacts/:contact/rewards', $options);

$result = json_decode($response->getBody());
?>


# ---
# With_comment example
# ---

<?php
// Accrue 50 points with a curator comment that the student also sees in their journal

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'points' => 50,
        'comment' => 'Bonus for active chat participation',
        'is_visible_to_student' => true,
    ],
];

$response = $client->request('POST', '/contacts/:contact/rewards', $options);

$result = json_decode($response->getBody());
?>


# ---
# Idempotent_accrual example
# ---

<?php
// Safely retry the same accrual — passing Idempotency-Key collapses replays to a single FlowReward

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
        'Idempotency-Key' => 'reward-for-quiz-attempt-4821',
    ],
    'json' => [
        'points' => 25,
    ],
];

$response = $client->request('POST', '/contacts/:contact/rewards', $options);

$result = json_decode($response->getBody());
?>


# ---
# Deduct example
# ---

<?php
// Deduct 30 points

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'points' => -30,
    ],
];

$response = $client->request('POST', '/contacts/:contact/rewards', $options);

$result = json_decode($response->getBody());
?>
<?php
// Accrue 50 points for finishing a milestone, unbound from any product

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact/rewards');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
    'points' => 50,
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>


# ---
# Accrue_with_product example
# ---

<?php
// Accrue 100 points scoped to product id 10

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact/rewards');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
    'points' => 100,
    'product_id' => 10,
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>


# ---
# With_comment example
# ---

<?php
// Accrue 50 points with a curator comment that the student also sees in their journal

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact/rewards');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
    'points' => 50,
    'comment' => 'Bonus for active chat participation',
    'is_visible_to_student' => true,
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>


# ---
# Idempotent_accrual example
# ---

<?php
// Safely retry the same accrual — passing Idempotency-Key collapses replays to a single FlowReward

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
    'Idempotency-Key: reward-for-quiz-attempt-4821',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact/rewards');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
    'points' => 25,
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>


# ---
# Deduct example
# ---

<?php
// Deduct 30 points

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/contacts/:contact/rewards');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
    'points' => -30,
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request POST 'https://api.kwiga.com/contacts/:contact/rewards' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"points":50}'


# ---
# Accrue_with_product example
# ---

curl --location --request POST 'https://api.kwiga.com/contacts/:contact/rewards' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"points":100,"product_id":10}'


# ---
# With_comment example
# ---

curl --location --request POST 'https://api.kwiga.com/contacts/:contact/rewards' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"points":50,"comment":"Bonus for active chat participation","is_visible_to_student":true}'


# ---
# Idempotent_accrual example
# ---

curl --location --request POST 'https://api.kwiga.com/contacts/:contact/rewards' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--header 'Idempotency-Key: reward-for-quiz-attempt-4821' \
--data-raw '{"points":25}'


# ---
# Deduct example
# ---

curl --location --request POST 'https://api.kwiga.com/contacts/:contact/rewards' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"points":-30}'
// Accrue 50 points for finishing a milestone, unbound from any product
const url = 'https://api.kwiga.com/contacts/:contact/rewards';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'points': 50
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });


# ---
# Accrue_with_product example
# ---

// Accrue 100 points scoped to product id 10
const url = 'https://api.kwiga.com/contacts/:contact/rewards';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'points': 100,
    'product_id': 10
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });


# ---
# With_comment example
# ---

// Accrue 50 points with a curator comment that the student also sees in their journal
const url = 'https://api.kwiga.com/contacts/:contact/rewards';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'points': 50,
    'comment': 'Bonus for active chat participation',
    'is_visible_to_student': true
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });


# ---
# Idempotent_accrual example
# ---

// Safely retry the same accrual — passing Idempotency-Key collapses replays to a single FlowReward
const url = 'https://api.kwiga.com/contacts/:contact/rewards';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
    'Idempotency-Key': 'reward-for-quiz-attempt-4821',
  },
  body: JSON.stringify({
    'points': 25
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });


# ---
# Deduct example
# ---

// Deduct 30 points
const url = 'https://api.kwiga.com/contacts/:contact/rewards';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'points': -30
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Accrue 50 points for finishing a milestone, unbound from any product
import requests

url = 'https://api.kwiga.com/contacts/:contact/rewards'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'points': 50,
}

response = requests.post(url, headers=headers, json=data)

result = response.json()


# ---
# Accrue_with_product example
# ---

# Accrue 100 points scoped to product id 10
import requests

url = 'https://api.kwiga.com/contacts/:contact/rewards'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'points': 100,
    'product_id': 10,
}

response = requests.post(url, headers=headers, json=data)

result = response.json()


# ---
# With_comment example
# ---

# Accrue 50 points with a curator comment that the student also sees in their journal
import requests

url = 'https://api.kwiga.com/contacts/:contact/rewards'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'points': 50,
    'comment': 'Bonus for active chat participation',
    'is_visible_to_student': true,
}

response = requests.post(url, headers=headers, json=data)

result = response.json()


# ---
# Idempotent_accrual example
# ---

# Safely retry the same accrual — passing Idempotency-Key collapses replays to a single FlowReward
import requests

url = 'https://api.kwiga.com/contacts/:contact/rewards'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
    'Idempotency-Key': 'reward-for-quiz-attempt-4821',
}

data = {
    'points': 25,
}

response = requests.post(url, headers=headers, json=data)

result = response.json()


# ---
# Deduct example
# ---

# Deduct 30 points
import requests

url = 'https://api.kwiga.com/contacts/:contact/rewards'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'points': -30,
}

response = requests.post(url, headers=headers, json=data)

result = response.json()
{
  "method": "POST",
  "url": "https://api.kwiga.com/contacts/:contact/rewards",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "points": 50
  }
}


# ---
# Accrue_with_product example
# ---

{
  "method": "POST",
  "url": "https://api.kwiga.com/contacts/:contact/rewards",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "points": 100,
    "product_id": 10
  }
}


# ---
# With_comment example
# ---

{
  "method": "POST",
  "url": "https://api.kwiga.com/contacts/:contact/rewards",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "points": 50,
    "comment": "Bonus for active chat participation",
    "is_visible_to_student": true
  }
}


# ---
# Idempotent_accrual example
# ---

{
  "method": "POST",
  "url": "https://api.kwiga.com/contacts/:contact/rewards",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Idempotency-Key": "reward-for-quiz-attempt-4821",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "points": 25
  }
}


# ---
# Deduct example
# ---

{
  "method": "POST",
  "url": "https://api.kwiga.com/contacts/:contact/rewards",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "points": -30
  }
}

Пример ответа:

{
  "data": {
    "id": 9822,
    "points": 50,
    "accrual_type": {
      "id": "accrued",
      "slug": "Plus",
      "title": "Accrued"
    },
    "reason_type": {
      "id": 6,
      "slug": "Manual",
      "title": "Manual"
    },
    "event": null,
    "event_name": null,
    "eventable_type": null,
    "eventable_id": null,
    "event_comment": null,
    "manual_comment": "Bonus for active chat participation",
    "is_visible_to_student": true,
    "message": "Manual points accrual: Bonus for active chat participation",
    "product": null,
    "creator": {
      "id": 17,
      "name": "Alice Curator",
      "email": "alice@kwiga.com"
    },
    "created_at": "2026-05-25T14:30:00.000000Z"
  }
}

POST https://api.kwiga.com/contacts/:contact/rewards

Добавляет ручную запись в журнал баллов контакта. Положительное points — начисление, отрицательное — списание. Опциональный product_id привязывает запись к конкретному продукту; иначе она «кабинет-уровневая». Reason type всегда Manual; в качестве куратора фиксируется текущий API-пользователь.

URL Parameters

points number required
Сколько баллов начислить. Положительное значение — начисление, отрицательное — списание. Запись всегда фиксируется с reason_type=Manual.
Range: -1000 – 1000
product_id integer optional
Опциональный id продукта, к которому привязать баллы. Без него — начисление «кабинет-уровневое», без привязки к продукту.
comment string optional
Опциональный комментарий куратора (до 255 символов). Сохраняется вместе с записью и дописывается в человекочитаемый message ответа. Ученик увидит комментарий в своём журнале баллов только если is_visible_to_student=true.
Max length: 255 characters
is_visible_to_student boolean optional
Показывать ли комментарий ученику в его собственном журнале баллов. Кураторы (включая этот публичный API) видят комментарий всегда; флаг управляет только видимостью на стороне ученика.
Default: false

Структура ответа

Содержимое ответа.

Продукты

Список продуктов кабинета

Пример запроса:

<?php
// Get products list

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/products', $options);

$result = json_decode($response->getBody());
?>


# ---
# Paginated example
# ---

<?php
// Get products list with pagination and sorting

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/products?per_page=20&page=1&sort_by=asc', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get products list

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/products');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>


# ---
# Paginated example
# ---

<?php
// Get products list with pagination and sorting

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/products?per_page=20&page=1&sort_by=asc');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/products' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'


# ---
# Paginated example
# ---

curl --location --request GET 'https://api.kwiga.com/products?per_page=20&page=1&sort_by=asc' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get products list
const url = 'https://api.kwiga.com/products';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });


# ---
# Paginated example
# ---

// Get products list with pagination and sorting
const url = 'https://api.kwiga.com/products?per_page=20&page=1&sort_by=asc';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get products list
import requests

url = 'https://api.kwiga.com/products'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()


# ---
# Paginated example
# ---

# Get products list with pagination and sorting
import requests

url = 'https://api.kwiga.com/products?per_page=20&page=1&sort_by=asc'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/products",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}


# ---
# Paginated example
# ---

{
  "method": "GET",
  "url": "https://api.kwiga.com/products?per_page=20&page=1&sort_by=asc",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
  "data": [
    {
      "id": 299,
      "productable_id": 142,
      "productable_type": "course",
      "title": "Sample Course Title",
      "url": "http://test.local/courses/test-slug"
    }
  ],
  "links": {
    "first": "https://api.kwiga.com/products?page=1",
    "last": "https://api.kwiga.com/products?page=5",
    "prev": null,
    "next": "https://api.kwiga.com/products?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 5,
    "path": "https://api.kwiga.com/products",
    "per_page": 15,
    "to": 15,
    "total": 75
  }
}

GET https://api.kwiga.com/products

Список продуктов кабинета. Есть пагинация, по умолчанию 15.

URL Parameters

sort_by string optional
Possible values: asc, descDefault: desc
per_page integer optional
Количество элементов на странице
Default: 15
page integer optional
Номер страницы
Default: 1

Структура ответа

Элементы текущей страницы.
Ссылки на другие страницы того же списка.
Состояние пагинации для текущего запроса (номер страницы, общее количество и т.д.).

Курсы

Список курсов кабинета

Пример запроса:

<?php
// Get courses list

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/courses', $options);

$result = json_decode($response->getBody());
?>


# ---
# With_params example
# ---

<?php
// Get courses list with pagination and includes

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/courses?with[]=offers&with[]=description&with[]=program&per_page=15&page=1', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get courses list

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/courses');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>


# ---
# With_params example
# ---

<?php
// Get courses list with pagination and includes

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/courses?with[]=offers&with[]=description&with[]=program&per_page=15&page=1');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/courses' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'


# ---
# With_params example
# ---

curl --location --request GET 'https://api.kwiga.com/courses?with[]=offers&with[]=description&with[]=program&per_page=15&page=1' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get courses list
const url = 'https://api.kwiga.com/courses';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });


# ---
# With_params example
# ---

// Get courses list with pagination and includes
const url = 'https://api.kwiga.com/courses?with[]=offers&with[]=description&with[]=program&per_page=15&page=1';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get courses list
import requests

url = 'https://api.kwiga.com/courses'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()


# ---
# With_params example
# ---

# Get courses list with pagination and includes
import requests

url = 'https://api.kwiga.com/courses?with[]=offers&with[]=description&with[]=program&per_page=15&page=1'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/courses",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}


# ---
# With_params example
# ---

{
  "method": "GET",
  "url": "https://api.kwiga.com/courses?with[]=offers&with[]=description&with[]=program&per_page=15&page=1",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
  "data": [
    {
      "id": 226,
      "product_id": 431,
      "type": {
        "id": 1,
        "name": "Course",
        "type": "course"
      },
      "title": "Test черновик квизов",
      "slug": "test-chernovik-kvizov",
      "preview": {
        "id": 3140,
        "uuid": "bdfe1347-574f-4829-a745-09ba2176cc2a",
        "name": "image_13.jpeg",
        "original_name": "image_13.jpeg",
        "url": "https://cdn57098163.ahacdn.me/local/cabinet-1/rcIJuNkWmpCQ/image_13.jpeg",
        "thumbnails": {
          "xs": "https://cdn57098163.ahacdn.me/local/cabinet-1/rcIJuNkWmpCQ/image_13_thumb_150.jpeg",
          "small": "https://cdn57098163.ahacdn.me/local/cabinet-1/rcIJuNkWmpCQ/image_13_thumb_500.jpeg",
          "medium": "https://cdn57098163.ahacdn.me/local/cabinet-1/rcIJuNkWmpCQ/image_13.jpeg",
          "large": "https://cdn57098163.ahacdn.me/local/cabinet-1/rcIJuNkWmpCQ/image_13.jpeg",
          "default": "https://cdn57098163.ahacdn.me/local/cabinet-1/rcIJuNkWmpCQ/image_13.jpeg"
        },
        "extension": "jpeg",
        "type_id": 2,
        "mime_type": "image/jpeg"
      },
      "url": "http://test.local/courses/test-chernovik-kvizov",
      "status": {
        "id": 3,
        "name": "Published"
      }
    }
  ],
  "links": {
    "first": "http://api.kwiga.local/courses?page=1",
    "last": "http://api.kwiga.local/courses?page=1",
    "prev": null,
    "next": null
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 1,
    "links": [
      {
        "url": null,
        "label": "&laquo; Previous",
        "active": false
      },
      {
        "url": "http://api.kwiga.local/courses?page=1",
        "label": "1",
        "active": true
      },
      {
        "url": null,
        "label": "Next &raquo;",
        "active": false
      }
    ],
    "path": "http://api.kwiga.local/courses",
    "per_page": 15,
    "to": 1,
    "total": 1
  }
}

GET https://api.kwiga.com/courses

Список курсов кабинета. Есть пагинация, по умолчанию 15 (max: 15).

URL Parameters

with string[] optional
Массив с дополнительными параметрами которые надо получить вместе с курсом. Возможные варианты: offers (список всех офферов с курсом), description (инфоблоки с описанием курса), program (дерево программы курса)
Possible values: offers, description, program
sort_by string optional
Possible values: asc, descDefault: desc
per_page integer optional
Количество элементов на странице
Maximum: 15Default: 15
page integer optional
Номер страницы
Default: 1

Структура ответа

Элементы текущей страницы.
Ссылки на другие страницы того же списка.
Состояние пагинации для текущего запроса (номер страницы, общее количество и т.д.).

Список участников курса

Пример запроса:

<?php
// Get course users

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/courses/:course/users', $options);

$result = json_decode($response->getBody());
?>


# ---
# Filtered example
# ---

<?php
// Get course users with filters

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/courses/:course/users?progress_general_from=50&progress_general_to=100&per_page=20', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get course users

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/courses/:course/users');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>


# ---
# Filtered example
# ---

<?php
// Get course users with filters

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/courses/:course/users?progress_general_from=50&progress_general_to=100&per_page=20');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/courses/:course/users' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'


# ---
# Filtered example
# ---

curl --location --request GET 'https://api.kwiga.com/courses/:course/users?progress_general_from=50&progress_general_to=100&per_page=20' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get course users
const url = 'https://api.kwiga.com/courses/:course/users';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });


# ---
# Filtered example
# ---

// Get course users with filters
const url = 'https://api.kwiga.com/courses/:course/users?progress_general_from=50&progress_general_to=100&per_page=20';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get course users
import requests

url = 'https://api.kwiga.com/courses/:course/users'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()


# ---
# Filtered example
# ---

# Get course users with filters
import requests

url = 'https://api.kwiga.com/courses/:course/users?progress_general_from=50&progress_general_to=100&per_page=20'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/courses/:course/users",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}


# ---
# Filtered example
# ---

{
  "method": "GET",
  "url": "https://api.kwiga.com/courses/:course/users?progress_general_from=50&progress_general_to=100&per_page=20",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
    "data": [
        {
            "user": {
                "id": 3,
                "name": "Admin Кабинет",
                "email": "test@test.com"
            },
            "contact": {
                "id": 1,
                "user_id": 3,
                "email": "test@test.com",
                "first_name": "test",
                "last_name": "admin cabinet",
                "phone": "979",
                "created_at": "2023-05-26T09:47:20.000000Z",
                "last_activity_at": null
            },
            "course_points": 0,
            "course_progress": {
                "course_id": 20,
                "course_url": "http://test.local/courses/Pm8CEofJ",
                "title": "Dripping",
                "lessons_count": 9,
                "lessons_count_viewed": 0,
                "lessons_viewed_percentage": 0,
                "lessons_count_completed": 0,
                "lessons_completion_percentage": 0,
                "quizzes_count": 0,
                "quizzes_count_completed": 0,
                "quizzes_completion_percentage": 0,
                "scores_max": 0,
                "quizzes_scores": 0,
                "product_scores": 0,
                "scores": 0,
                "is_completed": false,
                "completed_at": null,
                "current_lesson": {
                    "id": 48,
                    "course_id": 20,
                    "type_id": 1,
                    "status_id": 4,
                    "number": 6,
                    "title": "2",
                    "slug": "2",
                    "url": "http://test.local/courses/Pm8CEofJ/2",
                    "quizzes": [],
                    "module": {
                        "id": 7,
                        "course_id": 20,
                        "number": 1,
                        "title": "Module title"
                    }
                },
                "next_lesson": {
                    "id": 46,
                    "course_id": 20,
                    "type_id": 1,
                    "status_id": 4,
                    "number": 1,
                    "title": "3",
                    "slug": "3",
                    "url": "http://test.local/courses/Pm8CEofJ/3",
                    "module": {
                        "id": 7,
                        "course_id": 20,
                        "number": 1,
                        "title": "Module title"
                    }
                },
                "last_activity_at": null,
                "is_checkpoints_skipped": false,
                "checkpoints": [],
                "is_current_dripping_date_skipped": false,
                "is_next_dripping_date_skipped": false,
                "current_dripping_date": null,
                "next_dripping_date": null
            },
            "lessons_available_count": 9,
            "is_full_access": true,
            "subscription": {
                "is_active": true,
                "is_paid": true,
                "start_at": "2024-04-23T13:45:02.000000Z",
                "end_at": null,
                "offer_end_at": null,
                "order_end_at": null,
                "count_available_days": 661,
                "count_left_days": null,
                "state": {
                    "id": 2,
                    "name": "Open",
                    "title": "Open"
                }
            }
        }
    ],
    "links": {
        "first": "http://api.kwiga.local/courses/20/users?page=1",
        "last": "http://api.kwiga.local/courses/20/users?page=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "links": [
            {
                "url": null,
                "label": "&laquo; Previous",
                "active": false
            },
            {
                "url": "http://api.kwiga.local/courses/20/users?page=1",
                "label": "1",
                "active": true
            },
            {
                "url": null,
                "label": "Next &raquo;",
                "active": false
            }
        ],
        "path": "http://api.kwiga.local/courses/20/users",
        "per_page": 15,
        "to": 1,
        "total": 1
    }
}

GET https://api.kwiga.com/courses/:course/users

Выводит список участников курса и их прогресс. Есть пагинация, по умолчанию 15 (max: 250). Замените :course на id курса которое можно взять в адресной строке редактирования/управления курса или в эндпоинте по списку курсов.

URL Parameters

search string optional
Нестрогий поиск по имейлу или имени
contact_id integer optional
По id контакта. Объединяется с emails / contact_ids / user_id / user_ids в единый фильтр аудитории (union).
contact_ids integer/integer[] optional
Фильтр по id контактов. Принимает строку через запятую или повторяющийся массив целых чисел. Объединяется с emails / contact_id / user_id / user_ids в единый фильтр аудитории (union).
user_id integer optional
По id пользователя. Объединяется с emails / contact_id / contact_ids / user_ids в единый фильтр аудитории (union).
user_ids integer/integer[] optional
Фильтр по id юзеров (аккаунтов ученика). Принимает строку через запятую или повторяющийся массив целых чисел. Объединяется с emails / contact_id / contact_ids / user_id в единый фильтр аудитории (union).
emails string/string[] optional
Фильтр по точному email-адресу. Принимает строку через запятую или повторяющийся массив; каждое значение должно быть валидным email. Объединяется с contact_id / contact_ids / user_id / user_ids в единый фильтр аудитории (union).
progress_general_from integer optional
Общий прогресс от (от 0 до 99)
progress_general_to integer optional
Общий прогресс до (от 0 до 100)
lessons_viewed_from integer optional
Процент просмотра урока от (от 0 до 99)
lessons_viewed_to integer optional
Процент просмотра урока до (от 0 до 100)
quizzes_progress_from integer optional
Процент пройденных практик от (от 0 до 99)
quizzes_progress_to integer optional
Процент пройденных практик до (от 0 до 100)
last_activity_from datetime optional
Последняя активность на курсе от (дата UTC)
last_activity_to datetime optional
Последняя активность на курсе до (дата UTC)
per_page integer optional
Количество элементов на странице
Maximum: 250Default: 15
page integer optional
Номер страницы
Default: 1
include string/string[] optional

Необязательный список расширений ответа через запятую. Каждый токен подключает одну секцию ответа — клиент платит только за то, что попросил.

  • course_program — добавляет программу курса на верхнем уровне ответа в поле course_program (массив узлов CourseProgram; у lesson-узлов в children едут CourseProgramSection с привязанными квизами). Возвращается один раз рядом с data/links/meta, потому что программа одинакова для всех учеников страницы.
  • lesson_progress — добавляет в каждый элемент data[] поле lesson_progress: массив объектов LessonProgress (по одной записи на урок, по которому у ученика есть прогресс).
  • module_progress — добавляет в каждый элемент data[] поле module_progress: массив объектов ModuleProgress.
  • quiz_progress — добавляет в каждый элемент data[] поле quiz_progress: массив объектов QuizProgress, по одной записи на пару (course_lesson_id, quiz_id) со сводкой по последней non-cancelled попытке ученика.

Внимание: один и тот же квиз может быть привязан к нескольким урокам одного курса, поэтому quiz_progress адресуется парой урок/квиз, а не одним quiz_id. Клиент сшивает per-user прогресс с деревом по course_nodeble_id lesson/module-узлов и по паре (lesson.course_nodeble_id, quizzes[i].id) для section-узлов.

Структура ответа

Элементы текущей страницы.
Ссылки на другие страницы того же списка.
Состояние пагинации для текущего запроса (номер страницы, общее количество и т.д.).
Conditional: Returned when include contains course_program.
Программа курса, возвращается на верхнем уровне когда `include` содержит `course_program`. Форма полей та же, что у существующей схемы `CourseProgram`, у lesson-узлов в `children` лежат `CourseProgramSection`. Одинакова для всех учеников страницы, поэтому API возвращает её один раз вместо повторения в каждом ряду.

POST-алиас

POST https://api.kwiga.com/courses/:course/users/query

Функциональный алиас для GET-эндпоинта выше. Принимает те же параметры в теле запроса вместо query-string — пригодится, когда список фильтров слишком большой для URL (или просто удобнее собирать JSON на клиенте). Body имеет приоритет над query-string, форма ответа идентична.

Получение детальной аналитики по урокам

В дополнение к сводке прогресса по курсу эндпоинт может вернуть детальную разбивку прогресса по урокам. Расширение запрашивается query-параметром include (полный список токенов и тип данных, который каждый из них подключает, описан в его описании выше, в URL Parameters). Программа курса возвращается один раз на верхнем уровне в поле course_program — она одинакова для всех учеников страницы, поэтому размер ответа остаётся пропорциональным произведению учеников на уроки/квизы, а не размеру всего дерева.

Пример ответа с ?include=course_program,lesson_progress,module_progress,quiz_progress

{
    "data": [
        {
            "user": {
                "id": 3,
                "name": "Admin Кабинет",
                "email": "test@test.com"
            },
            "contact": {
                "id": 1,
                "user_id": 3,
                "email": "test@test.com",
                "first_name": "test",
                "last_name": "admin cabinet",
                "phone": "979",
                "created_at": "2023-05-26T09:47:20.000000Z",
                "last_activity_at": null
            },
            "course_points": 0,
            "course_progress": {
                "course_id": 20,
                "title": "Dripping",
                "lessons_count": 9,
                "lessons_count_viewed": 2,
                "lessons_viewed_percentage": 22,
                "lessons_count_completed": 1,
                "lessons_completion_percentage": 11,
                "quizzes_count": 2,
                "quizzes_count_completed": 1,
                "quizzes_completion_percentage": 50,
                "scores_max": 200,
                "quizzes_scores": 80,
                "is_completed": false,
                "completed_at": null
            },
            "lessons_available_count": 9,
            "is_full_access": true,
            "lesson_progress": [
                {
                    "lesson_id": 48,
                    "is_watched": true,
                    "is_completed": true,
                    "quizzing_status": {
                        "id": 4,
                        "slug": "Completed",
                        "title": "Completed"
                    },
                    "watching_start_at": "2026-05-20T11:20:00.000000Z",
                    "watching_end_at": "2026-05-20T11:30:00.000000Z",
                    "quizzing_start_at": "2026-05-20T11:30:00.000000Z",
                    "quizzing_end_at": "2026-05-20T11:34:00.000000Z",
                    "completed_at": "2026-05-20T11:35:00.000000Z",
                    "created_at": "2026-05-20T11:20:00.000000Z",
                    "updated_at": "2026-05-20T11:35:00.000000Z"
                },
                {
                    "lesson_id": 46,
                    "is_watched": true,
                    "is_completed": false,
                    "quizzing_status": {
                        "id": 3,
                        "slug": "InProgress",
                        "title": "In progress"
                    },
                    "watching_start_at": "2026-05-21T09:00:00.000000Z",
                    "watching_end_at": "2026-05-21T09:10:00.000000Z",
                    "quizzing_start_at": null,
                    "quizzing_end_at": null,
                    "completed_at": null,
                    "created_at": "2026-05-21T09:00:00.000000Z",
                    "updated_at": "2026-05-21T09:10:00.000000Z"
                }
            ],
            "module_progress": [
                {
                    "module_id": 7,
                    "is_watched": true,
                    "is_completed": false,
                    "quizzing_status": {
                        "id": 3,
                        "slug": "InProgress",
                        "title": "In progress"
                    },
                    "watching_start_at": "2026-05-20T11:20:00.000000Z",
                    "watching_end_at": "2026-05-21T09:10:00.000000Z",
                    "quizzing_start_at": null,
                    "quizzing_end_at": null,
                    "completed_at": null,
                    "created_at": "2026-05-20T11:20:00.000000Z",
                    "updated_at": "2026-05-21T09:10:00.000000Z"
                }
            ],
            "quiz_progress": [
                {
                    "course_lesson_id": 48,
                    "quiz_id": 101,
                    "status": {
                        "id": 1,
                        "slug": "Passed",
                        "title": "Passed"
                    },
                    "scores": 80,
                    "scores_max": 100,
                    "started_at": "2026-05-20T11:30:00.000000Z",
                    "finished_at": "2026-05-20T11:34:00.000000Z",
                    "checked_at": null,
                    "last_activity_at": "2026-05-20T11:34:00.000000Z",
                    "attempt_number": 2
                }
            ]
        }
    ],
    "links": {
        "first": "https://api.example.com/courses/20/users?page=1",
        "last": "https://api.example.com/courses/20/users?page=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "path": "https://api.example.com/courses/20/users",
        "per_page": 15,
        "to": 1,
        "total": 1
    },
    "course_structure": [
        {
            "id": 12,
            "course_nodeble_type": "course_module",
            "course_nodeble_id": 7,
            "parent_id": null,
            "order": 1,
            "course_nodeble": {
                "id": 7,
                "title": "Module title"
            },
            "children": [
                {
                    "id": 34,
                    "course_nodeble_type": "course_lesson",
                    "course_nodeble_id": 48,
                    "parent_id": 12,
                    "order": 1,
                    "course_nodeble": {
                        "id": 48,
                        "title": "Lesson 1"
                    },
                    "children": [
                        {
                            "id": 201,
                            "course_nodeble_type": "info_section",
                            "course_nodeble_id": 201,
                            "parent_id": 34,
                            "order": 1,
                            "course_nodeble": {
                                "id": 201,
                                "name": "List of practice",
                                "slug": "list-of-practice"
                            },
                            "quizzes": [
                                {"id": 101, "name": "Module wrap-up quiz"}
                            ],
                            "children": []
                        },
                        {
                            "id": 202,
                            "course_nodeble_type": "info_section",
                            "course_nodeble_id": 202,
                            "parent_id": 34,
                            "order": 2,
                            "course_nodeble": {
                                "id": 202,
                                "name": "Theory",
                                "slug": "theory"
                            },
                            "quizzes": [],
                            "children": []
                        }
                    ]
                },
                {
                    "id": 35,
                    "course_nodeble_type": "course_lesson",
                    "course_nodeble_id": 46,
                    "parent_id": 12,
                    "order": 2,
                    "course_nodeble": {
                        "id": 46,
                        "title": "Lesson 2"
                    },
                    "children": [
                        {
                            "id": 203,
                            "course_nodeble_type": "info_section",
                            "course_nodeble_id": 203,
                            "parent_id": 35,
                            "order": 1,
                            "course_nodeble": {
                                "id": 203,
                                "name": "Intro",
                                "slug": "intro"
                            },
                            "quizzes": [],
                            "children": []
                        }
                    ]
                }
            ]
        }
    ]
}

Сшивка прогресса с деревом

Эндпоинт отдаёт программу курса один раз на верхнем уровне, а массивы прогресса пользователя — внутри каждой строки data[]. Чтобы наложить прогресс конкретного ученика на дерево, сделайте так.

1. Постройте словари из его массивов прогресса для быстрого поиска.

Про имена: записи прогресса используют lesson_id/module_id/course_lesson_id, а узлы программы — course_nodeble_id. Это одно и то же значение, просто в разных частях ответа — см. шаг 2.

2. Обойдите course_program и для каждого узла возьмите нужную запись.

У каждого узла есть course_nodeble_type + course_nodeble_id. Используйте course_nodeble_id как ключ поиска:

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

Попытки прохождения квизов

Список попыток в кабинете

Пример запроса:

<?php
// List quiz attempts (defaults — newest first by status_updated_at)

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/quiz-attempts', $options);

$result = json_decode($response->getBody());
?>


# ---
# Filtered example
# ---

<?php
// Filtered list — pending check & need rework, scoped to two products

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/quiz-attempts?filters[practice_statuses]=3,6&filters[product_ids]=10,11&sort_by=last_activity_at&sort_dir=desc&per_page=50', $options);

$result = json_decode($response->getBody());
?>
<?php
// List quiz attempts (defaults — newest first by status_updated_at)

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/quiz-attempts');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>


# ---
# Filtered example
# ---

<?php
// Filtered list — pending check & need rework, scoped to two products

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/quiz-attempts?filters[practice_statuses]=3,6&filters[product_ids]=10,11&sort_by=last_activity_at&sort_dir=desc&per_page=50');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/quiz-attempts' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'


# ---
# Filtered example
# ---

curl --location --request GET 'https://api.kwiga.com/quiz-attempts?filters[practice_statuses]=3,6&filters[product_ids]=10,11&sort_by=last_activity_at&sort_dir=desc&per_page=50' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// List quiz attempts (defaults — newest first by status_updated_at)
const url = 'https://api.kwiga.com/quiz-attempts';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });


# ---
# Filtered example
# ---

// Filtered list — pending check & need rework, scoped to two products
const url = 'https://api.kwiga.com/quiz-attempts?filters[practice_statuses]=3,6&filters[product_ids]=10,11&sort_by=last_activity_at&sort_dir=desc&per_page=50';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# List quiz attempts (defaults — newest first by status_updated_at)
import requests

url = 'https://api.kwiga.com/quiz-attempts'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()


# ---
# Filtered example
# ---

# Filtered list — pending check & need rework, scoped to two products
import requests

url = 'https://api.kwiga.com/quiz-attempts?filters[practice_statuses]=3,6&filters[product_ids]=10,11&sort_by=last_activity_at&sort_dir=desc&per_page=50'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/quiz-attempts",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}


# ---
# Filtered example
# ---

{
  "method": "GET",
  "url": "https://api.kwiga.com/quiz-attempts?filters[practice_statuses]=3,6&filters[product_ids]=10,11&sort_by=last_activity_at&sort_dir=desc&per_page=50",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
  "data": [
    {
      "id": 4821,
      "root_id": 4810,
      "previous_id": 4815,
      "number_version": 3,
      "user_id": 712,
      "product_id": 431,
      "quiz_id": 88,
      "course_id": 226,
      "course_lesson_id": 504,
      "lesson_section": {
        "id": 88,
        "name": "Knowledge check",
        "order": 3,
        "url": "https://lm4.kwiga.com/courses/js-foundations/3/lessons/504",
        "crm_url": "https://lm4.kwiga.com/expert/courses/js-foundations/3/lessons/504/sections/3"
      },
      "status": {
        "id": 6,
        "slug": "PendingCheck",
        "title": "Pending review"
      },
      "scores": 12.5,
      "scores_max": 15,
      "count_questions": 10,
      "count_questions_correct": 8,
      "count_questions_incorrect": 2,
      "is_force_approved": false,
      "is_read": false,
      "started_at": "2026-05-23T11:02:14.000000Z",
      "last_activity_at": "2026-05-23T11:14:51.000000Z",
      "finished_at": "2026-05-23T11:14:51.000000Z",
      "deadline_at": null,
      "status_updated_at": "2026-05-23T11:14:51.000000Z",
      "checked_at": null,
      "commented_at": null,
      "canceled_at": null,
      "passed_time_in_seconds": 757,
      "crm_url": "https://lm4.kwiga.com/expert/practices?attempt_id=4821",
      "user": {
        "id": 712,
        "name": "Alice Student",
        "email": "alice@example.com"
      },
      "quiz": {
        "id": 88,
        "name": "Module 3 final test"
      },
      "course": {
        "id": 226,
        "product_id": 431,
        "type": {
          "id": 1,
          "name": "Course",
          "type": "course"
        },
        "title": "JS Foundations",
        "slug": "js-foundations",
        "url": "http://test.local/courses/js-foundations",
        "status": {
          "id": 3,
          "name": "Published"
        }
      },
      "lesson": {
        "id": 504,
        "title": "Closures & scope",
        "course": {
          "id": 226,
          "product_id": 431,
          "type_id": 1,
          "title": "JS Foundations",
          "url": "http://test.local/courses/js-foundations"
        },
        "module": {
          "id": 30,
          "course_id": 226,
          "number": 3,
          "title": "Functions in depth"
        }
      },
      "product": {
        "id": 431,
        "productable_type": "course",
        "productable_id": 226,
        "name": "JS Foundations",
        "url": "http://test.local/courses/js-foundations"
      }
    }
  ],
  "links": {
    "first": "https://api.kwiga.com/quiz-attempts?page=1",
    "last": "https://api.kwiga.com/quiz-attempts?page=4",
    "prev": null,
    "next": "https://api.kwiga.com/quiz-attempts?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 4,
    "path": "https://api.kwiga.com/quiz-attempts",
    "per_page": 15,
    "to": 15,
    "total": 58,
    "links": [
      { "url": null, "label": "&laquo; Previous", "active": false },
      { "url": "https://api.kwiga.com/quiz-attempts?page=1", "label": "1", "active": true },
      { "url": "https://api.kwiga.com/quiz-attempts?page=2", "label": "2", "active": false },
      { "url": "https://api.kwiga.com/quiz-attempts?page=2", "label": "Next &raquo;", "active": false }
    ]
  },
  "attempts_unread_count": 12
}

GET https://api.kwiga.com/quiz-attempts

Возвращает пагинированный список попыток учеников по квизам, доступных авторизованному куратору во всём кабинете (без привязки к продукту в URL — передайте параметр products, чтобы сузить выборку). По умолчанию 15 элементов на страницу, максимум 250. Поле attempts_unread_count на верхнем уровне показывает количество непрочитанных попыток с теми же фильтрами — удобно выводить как «бейдж» новых попыток.

URL Parameters

filters object optional
Контейнер для всех фильтров данных. Сортировка, пагинация и флаги дополнительных секций ответа лежат в корне запроса, не внутри filters.
search string optional
Нечёткий поиск по email/имени ученика, названию квиза или урока.
practice_statuses integer/integer[] optional

Фильтр по идентификаторам статусов попыток. Несколько значений объединяются «или».

  • 1 — Пройдено
  • 2 — Не пройдено
  • 3 — Требуется доработка
  • 4 — Последняя версия изменила статус
  • 5 — В процессе
  • 6 — Ожидает проверки
  • 7 — Не приступал
  • -1 — Непроверенные ответы по заданиям LMS (служебное значение только для фильтра; в ответе не встречается)
Possible values: -1, 1, 2, 3, 4, 5, 6, 7
product_ids integer/integer[] optional
Фильтр по идентификаторам продуктов (продуктов-курсов).
quiz_ids integer/integer[] optional
Фильтр по идентификаторам квизов.
lesson_ids integer/integer[] optional
Фильтр по идентификаторам уроков.
module_ids integer/integer[] optional
Фильтр по идентификаторам модулей.
offer_ids integer/integer[] optional
Фильтр по идентификаторам оферов, через которые ученик получил доступ.
curator_ids integer/integer[] optional
Фильтр по идентификаторам ответственных кураторов.
group_ids integer/integer[] optional
Фильтр по идентификаторам групп доступа к продукту.
emails string/string[] optional
Фильтр по email контактов-участников попыток. Объединяется с contact_ids / user_ids в единый фильтр аудитории (union).
contact_ids integer/integer[] optional
Фильтр по id контактов кабинета (участников попыток). Объединяется с emails / user_ids (union).
user_ids integer/integer[] optional
Фильтр по id пользователей (аккаунтов) — участников попыток. Объединяется с emails / contact_ids (union).
tag_ids integer/integer[] optional
Фильтр по идентификаторам тегов контактов учеников. Взаимоисключим с excluded_tag_ids — если переданы оба, побеждает excluded_tag_ids.
excluded_tag_ids integer/integer[] optional
Исключить попытки учеников, у которых выставлен любой из указанных тегов контакта. Взаимоисключим с tag_ids.
last_activity_from datetime optional
Нижняя граница (включительно) даты последней активности по попытке (UTC).
last_activity_to datetime optional
Верхняя граница (включительно) даты последней активности по попытке (UTC).
status_updated_from datetime optional
Нижняя граница (включительно) даты последнего изменения статуса попытки (UTC).
status_updated_to datetime optional
Верхняя граница (включительно) даты последнего изменения статуса попытки (UTC).
finished_from datetime optional
Нижняя граница (включительно) даты завершения попытки (UTC).
finished_to datetime optional
Верхняя граница (включительно) даты завершения попытки (UTC).
curator_last_activity_from datetime optional
Нижняя граница (включительно) даты последней активности куратора по попытке (UTC).
curator_last_activity_to datetime optional
Верхняя граница (включительно) даты последней активности куратора по попытке (UTC).
only_last_attempt boolean optional
Если true — возвращает только последнюю не отменённую попытку каждого ученика вместо всей истории.
curator_conditions string/string[] optional

Доп. условия по выбранным curator_ids (без них игнорируются). Несколько значений объединяются по curator_logic.

  • attached_to_quiz — куратор привязан к продукту попытки
  • changed_status — куратор менял статус попытки
  • commented_on_status — куратор оставлял комментарий при смене статуса
  • commented_on_assignments — куратор комментировал задания попытки
Possible values: attached_to_quiz, changed_status, commented_on_status, commented_on_assignments
curator_logic string optional
Как объединять curator_conditionsany (ИЛИ, по умолчанию) или all (И). Работает только вместе с curator_ids и curator_conditions.
Example: anyPossible values: any, allDefault: any

per_page integer optional
Количество элементов на странице.
Maximum: 250Default: 15
page integer optional
Номер страницы.
Default: 1
sort_by string optional
Поле сортировки результата.
Example: last_activity_atPossible values: last_activity_at, status_updated_at, finished_at, statusDefault: last_activity_at
sort_dir string optional
Направление сортировки.
Example: descPossible values: asc, descDefault: desc

Структура ответа

Элементы текущей страницы.
Ссылки на другие страницы того же списка.
Состояние пагинации для текущего запроса (номер страницы, общее количество и т.д.).
Количество непрочитанных кураторами попыток в кабинете под текущий набор фильтров. Подходит для глобального «бейджа» новых попыток.

POST-алиас

POST https://api.kwiga.com/quiz-attempts/query

Функциональный алиас для GET-эндпоинта выше. Принимает те же параметры в теле запроса вместо query-string — пригодится, когда список фильтров слишком большой для URL (или просто удобнее собирать JSON на клиенте). Body имеет приоритет над query-string, форма ответа идентична.

Предложения

Получение списка предложений

Пример запроса:

<?php
// Get offers list filtered by product

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/offers?product_id=130', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get offers list filtered by product

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/offers?product_id=130');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/offers?product_id=130' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get offers list filtered by product
const url = 'https://api.kwiga.com/offers?product_id=130';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get offers list filtered by product
import requests

url = 'https://api.kwiga.com/offers?product_id=130'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/offers?product_id=130",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
  "data": [
    {
      "id": 273,
      "unique_offer_code": "nELQzbLRPmzo",
      "url": "https://kwiga.com/o/nELQzbLRPmzo",
      "title": "This is paid offer for my course",
      "description": "<p style=\"text-align: left\">Description with html.</p>",
      "short_description": "<p style=\"text-align: left\">Shot description with html.</p>",
      "price_type": {
        "id": 2,
        "name": "Платно"
      },
      "price": {
        "amount": "10.00",
        "amount_rounded": "10.00",
        "amount_formatted": "10.00 usd",
        "amount_formatted_code": "$10.00",
        "amount_formatted_code_short": "$10.00",
        "currency": {
          "id": 5,
          "code": "USD",
          "html_code": "$",
          "html_letter_code": "usd",
          "is_volatile": false
        }
      },
      "price_discounted": {
        "amount": 5,
        "amount_rounded": "5.00",
        "amount_formatted": "5.00 usd",
        "amount_formatted_code": "$5.00",
        "amount_formatted_code_short": "$5.00",
        "currency": {
          "id": 5,
          "code": "USD",
          "html_code": "$",
          "html_letter_code": "usd",
          "is_volatile": false
        }
      },
      "discount": {
        "id": 3,
        "price_discounted": 5,
        "limit_type": {
          "id": 1,
          "name": "Безстроково"
        },
        "start_at": "2023-12-28T14:41:00.000000Z",
        "start_at_utc": "2023-12-28T12:41:00.000000Z",
        "start_timezone_id": 371,
        "end_at": "2024-01-03T14:41:00.000000Z",
        "end_type": 3,
        "end_at_utc": "2024-01-03T12:41:00.000000Z",
        "end_timezone_id": 371,
        "sales_limit": null,
        "sales": null,
        "is_active": true,
        "is_available": true,
        "created_at": "2023-12-28T12:41:58.000000Z",
        "updated_at": "2023-12-28T12:41:58.000000Z",
        "start_timezone": {
          "id": 371,
          "name": "Europe/Kyiv",
          "name_full": "Europe/Kyiv (UTC+03:00)",
          "value": "UTC+03:00"
        },
        "end_timezone": {
          "id": 371,
          "name": "Europe/Kyiv",
          "name_full": "Europe/Kyiv (UTC+03:00)",
          "value": "UTC+03:00"
        }
      },
      "has_subscription": false,
      "limit_type": {
        "id": 1,
        "name": "Необмежено"
      },
      "limit_of_sales": null,
      "is_active": true,
      "is_draft": false,
      "validity_start": {
        "type": "validity_start",
        "duration_type_id": 1,
        "date_at": "2023-12-28T14:40:00.000000Z",
        "date_at_utc": "2023-12-28T12:40:00.000000Z",
        "timezone_id": 371,
        "timezone": {
          "id": 371,
          "name": "Europe/Kyiv",
          "name_full": "Europe/Kyiv (UTC+03:00)",
          "value": "UTC+03:00"
        },
        "after_months": 0,
        "after_days": 0,
        "specific_time": null
      },
      "validity_end": {
        "type": "validity_end",
        "duration_type_id": 3,
        "date_at": "2023-12-28T14:40:00.000000Z",
        "date_at_utc": "2023-12-28T12:40:00.000000Z",
        "timezone_id": 371,
        "timezone": {
          "id": 371,
          "name": "Europe/Kyiv",
          "name_full": "Europe/Kyiv (UTC+03:00)",
          "value": "UTC+03:00"
        },
        "after_months": 0,
        "after_days": 0,
        "specific_time": null
      },
      "products": [
        {
          "id": 130,
          "productable_id": 38,
          "productable_type": "course",
          "title": "This is my course",
          "url": "https://kwiga.com/courses/test-fail"
        }
      ]
    },
    {
      "id": 272,
      "unique_offer_code": "2obtMsUEujcy",
      "url": "https://kwiga.com/o/2obtMsUEujcy",
      "title": "This is free offer for my course",
      "description": null,
      "short_description": null,
      "price_type": {
        "id": 1,
        "name": "Безкоштовно"
      },
      "price": {
        "amount": "0.00",
        "amount_rounded": "0.00",
        "amount_formatted": "0.00 usd",
        "amount_formatted_code": "$0.00",
        "amount_formatted_code_short": "$0.00",
        "currency": {
          "id": 5,
          "code": "USD",
          "html_code": "$",
          "html_letter_code": "usd",
          "is_volatile": false
        }
      },
      "price_discounted": {
        "amount": 0,
        "amount_rounded": "0.00",
        "amount_formatted": "0.00 usd",
        "amount_formatted_code": "$0.00",
        "amount_formatted_code_short": "$0.00",
        "currency": {
          "id": 5,
          "code": "USD",
          "html_code": "$",
          "html_letter_code": "usd",
          "is_volatile": false
        }
      },
      "discount": null,
      "has_subscription": false,
      "limit_type": {
        "id": 1,
        "name": "Необмежено"
      },
      "limit_of_sales": null,
      "is_active": true,
      "is_draft": false,
      "validity_start": {
        "type": "validity_start",
        "duration_type_id": 1,
        "date_at": "2023-12-27T12:45:00.000000Z",
        "date_at_utc": "2023-12-27T10:45:00.000000Z",
        "timezone_id": 371,
        "timezone": {
          "id": 371,
          "name": "Europe/Kyiv",
          "name_full": "Europe/Kyiv (UTC+03:00)",
          "value": "UTC+03:00"
        },
        "after_months": 0,
        "after_days": 0,
        "specific_time": null
      },
      "validity_end": {
        "type": "validity_end",
        "duration_type_id": 3,
        "date_at": "2023-12-27T12:45:00.000000Z",
        "date_at_utc": "2023-12-27T10:45:00.000000Z",
        "timezone_id": 371,
        "timezone": {
          "id": 371,
          "name": "Europe/Kyiv",
          "name_full": "Europe/Kyiv (UTC+03:00)",
          "value": "UTC+03:00"
        },
        "after_months": 0,
        "after_days": 0,
        "specific_time": null
      },
      "products": [
        {
          "id": 130,
          "productable_id": 38,
          "productable_type": "course",
          "title": "This is my course",
          "url": "https://kwiga.com/courses/test-fail"
        }
      ]
    }
  ],
  "links": {
    "first": "http://api.kwiga.local/offers?page=1",
    "last": "http://api.kwiga.local/offers?page=1",
    "prev": null,
    "next": null
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 1,
    "links": [
      {
        "url": null,
        "label": "&laquo; Previous",
        "active": false
      },
      {
        "url": "http://api.kwiga.local/offers?page=1",
        "label": "1",
        "active": true
      },
      {
        "url": null,
        "label": "Next &raquo;",
        "active": false
      }
    ],
    "path": "http://api.kwiga.local/offers",
    "per_page": 15,
    "to": 1,
    "total": 2
  }
}

GET https://api.kwiga.com/offers

URL Parameters

page integer optional
Номер страницы
per_page integer optional
Кол-во элементов выборки
product_id integer optional
Фильтр по продукту
filters object optional
Filter parameters
search string optional
Поисковый запрос

Структура ответа

Элементы текущей страницы.
Ссылки на другие страницы того же списка.
Состояние пагинации для текущего запроса (номер страницы, общее количество и т.д.).

Получение предложения

Пример запроса:

<?php
// Get offer by ID

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/offers/:offer', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get offer by ID

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/offers/:offer');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/offers/:offer' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get offer by ID
const url = 'https://api.kwiga.com/offers/:offer';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get offer by ID
import requests

url = 'https://api.kwiga.com/offers/:offer'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/offers/:offer",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
  "data": {
    "id": 1,
    "unique_offer_code": "6rT1wj99lbZV",
    "title": "Offer #1",
    "limit_type": {
      "id": 2,
      "name": "Certain amount"
    },
    "limit_of_sales": 20,
    "purchases_count": 1,
    "sales_left": 19
  }
}

GET https://api.kwiga.com/offers/:offer

Структура ответа

Маркетинг. Рассылка

Список списков контактов

Пример запроса:

<?php
// Get contact lists

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/mailing/contact-lists', $options);

$result = json_decode($response->getBody());
?>


# ---
# Paginated example
# ---

<?php
// Get contact lists with pagination

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/mailing/contact-lists?page=1&per_page=15', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get contact lists

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/mailing/contact-lists');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>


# ---
# Paginated example
# ---

<?php
// Get contact lists with pagination

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/mailing/contact-lists?page=1&per_page=15');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/mailing/contact-lists' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'


# ---
# Paginated example
# ---

curl --location --request GET 'https://api.kwiga.com/mailing/contact-lists?page=1&per_page=15' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get contact lists
const url = 'https://api.kwiga.com/mailing/contact-lists';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });


# ---
# Paginated example
# ---

// Get contact lists with pagination
const url = 'https://api.kwiga.com/mailing/contact-lists?page=1&per_page=15';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get contact lists
import requests

url = 'https://api.kwiga.com/mailing/contact-lists'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()


# ---
# Paginated example
# ---

# Get contact lists with pagination
import requests

url = 'https://api.kwiga.com/mailing/contact-lists?page=1&per_page=15'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/mailing/contact-lists",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}


# ---
# Paginated example
# ---

{
  "method": "GET",
  "url": "https://api.kwiga.com/mailing/contact-lists?page=1&per_page=15",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
  "data": [
    {
        "id": 2,
        "title": "Пример 2",
        "description": "Описание",
        "is_default": false,
        "created_at": "2022-04-28T13:22:44.000000Z",
        "updated_at": "2022-04-28T13:22:44.000000Z"
    },
    {
        "id": 1,
        "title": "Мой первый список",
        "description": "Данный список создаётся автоматически с вашим контактом внутри. Вы можете его отредактировать под свои потребности.",
        "is_default": true,
        "created_at": "2022-04-28T12:47:08.000000Z",
        "updated_at": "2022-04-28T12:47:08.000000Z"
    }
  ],
  "links": {
        "first": "https://api.kwiga.com/mailing/contact-lists?page=1",
        "last": "https://api.kwiga.com/mailing/contact-lists?page=2",
        "prev": null,
        "next": "https://api.kwiga.com/mailing/contact-lists?page=2"
   },
   "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 2,
        "links": [
            {
                "url": null,
                "label": "&laquo; translation missing: ru.pagination_prev",
                "active": false
            },
            {
                "url": "https://api.kwiga.com/mailing/contact-lists?page=1",
                "label": "1",
                "active": true
            },
            {
                "url": "https://api.kwiga.com/mailing/contact-lists?page=2",
                "label": "2",
                "active": false
            },
            {
                "url": "https://api.kwiga.com/mailing/contact-lists?page=2",
                "label": "translation missing: ru.pagination_next &raquo;",
                "active": false
            }
        ],
        "path": "https://api.kwiga.com/mailing/contact-lists",
        "per_page": 2,
        "to": 2,
        "total": 3
    }
}

GET https://api.kwiga.com/mailing/contact-lists

URL Parameters

page integer optional
Номер страницы
limit integer optional
Кол-во элементов выборки

Структура ответа

Элементы текущей страницы.

Ссылки на другие страницы того же списка.
Состояние пагинации для текущего запроса (номер страницы, общее количество и т.д.).

Получение списка контактов

Пример запроса:

<?php
// Get contact list by ID

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/mailing/contact-lists/:list', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get contact list by ID

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/mailing/contact-lists/:list');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/mailing/contact-lists/:list' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get contact list by ID
const url = 'https://api.kwiga.com/mailing/contact-lists/:list';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get contact list by ID
import requests

url = 'https://api.kwiga.com/mailing/contact-lists/:list'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/mailing/contact-lists/:list",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
  "data": {
    "id": 1,
    "title": "Мой первый список",
    "description": "Данный список создаётся автоматически с вашим контактом внутри. Вы можете его отредактировать под свои потребности.",
    "is_default": true,
    "created_at": "2022-04-28T12:47:08.000000Z",
    "updated_at": "2022-04-28T12:47:08.000000Z",
    "contacts": [
        {
            "id": 1,
            "first_name": "Alex",
            "middle_name": null,
            "last_name": "Иванович Burt",
            "name": "Иванович Burt Alex",
            "sex": null,
            "age": null,
            "email": "admin@grandstep.com.ua",
            "phone_country": "UA",
            "phone_number": "983721222",
            "city": null,
            "phone": "+380983721222",
            "created_at": "2022-04-28T12:47:07.000000Z",
            "updated_at": "2022-04-28T12:47:08.000000Z"
        }
    ],
    "statistic": {
        "contact_list_id": 1,
        "count_contacts": 1
    }
}
}

GET https://api.kwiga.com/mailing/contact-lists/:list

Структура ответа

Создание списка контактов

Пример запроса:

<?php
// Create contact list

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'title' => 'Название группы',
        'description' => 'описание',
    ],
];

$response = $client->request('POST', '/mailing/contact-lists/', $options);

$result = json_decode($response->getBody());
?>
<?php
// Create contact list

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/mailing/contact-lists/');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
    'title' => 'Название группы',
    'description' => 'описание',
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request POST 'https://api.kwiga.com/mailing/contact-lists/' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"title":"Название группы","description":"описание"}'
// Create contact list
const url = 'https://api.kwiga.com/mailing/contact-lists/';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'title': 'Название группы',
    'description': 'описание'
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Create contact list
import requests

url = 'https://api.kwiga.com/mailing/contact-lists/'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'title': 'Название группы',
    'description': 'описание',
}

response = requests.post(url, headers=headers, json=data)

result = response.json()
{
  "method": "POST",
  "url": "https://api.kwiga.com/mailing/contact-lists/",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "title": "Название группы",
    "description": "описание"
  }
}

Пример ответа:

{
    "data": {
        "id": 3,
        "title": "fsdaf asfsf dsafsda fsda",
        "description": "1dsad sadsa ds dsa",
        "is_default": false,
        "created_at": "2022-05-04T09:57:37.000000Z",
        "updated_at": "2022-05-04T09:57:37.000000Z"
    }
}

POST https://api.kwiga.com/mailing/contact-lists

Request

title string required
Название
description string optional
Описание

Структура ответа

Добавление контактов в список

Пример запроса:

<?php
// Bulk add contacts to lists

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'contacts[]' => 1,
        'contacts[]' => 2,
        'contact_lists[]' => 1,
    ],
];

$response = $client->request('POST', '/mailing/contact-lists/bulk-contacts', $options);

$result = json_decode($response->getBody());
?>
<?php
// Bulk add contacts to lists

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/mailing/contact-lists/bulk-contacts');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
    'contacts[]' => 1,
    'contacts[]' => 2,
    'contact_lists[]' => 1,
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request POST 'https://api.kwiga.com/mailing/contact-lists/bulk-contacts' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"contacts[]":2,"contact_lists[]":1}'
// Bulk add contacts to lists
const url = 'https://api.kwiga.com/mailing/contact-lists/bulk-contacts';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'contacts[]': 1,
    'contacts[]': 2,
    'contact_lists[]': 1
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Bulk add contacts to lists
import requests

url = 'https://api.kwiga.com/mailing/contact-lists/bulk-contacts'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'contacts[]': 1,
    'contacts[]': 2,
    'contact_lists[]': 1,
}

response = requests.post(url, headers=headers, json=data)

result = response.json()
{
  "method": "POST",
  "url": "https://api.kwiga.com/mailing/contact-lists/bulk-contacts",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "contacts[]": 2,
    "contact_lists[]": 1
  }
}

Пример ответа:

{
    "success": true
}

POST https://api.kwiga.com/mailing/contact-lists/bulk-contacts

URL Parameters

contacts integer[] required
Контакты (ID)
contact_lists integer[] required
Списки контактов (ID)

Структура ответа

Тело ответа возвращается без обёртки.

Продажи. Купоны

Список купонов

Пример запроса:

<?php
// Get coupons list

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/coupons', $options);

$result = json_decode($response->getBody());
?>


# ---
# Filtered example
# ---

<?php
// Get coupons list with date filters

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/coupons?filters[date_from]=2022-04-15&filters[date_to]=2022-04-27', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get coupons list

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/coupons');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>


# ---
# Filtered example
# ---

<?php
// Get coupons list with date filters

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/coupons?filters[date_from]=2022-04-15&filters[date_to]=2022-04-27');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/coupons' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'


# ---
# Filtered example
# ---

curl --location --request GET 'https://api.kwiga.com/coupons?filters[date_from]=2022-04-15&filters[date_to]=2022-04-27' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get coupons list
const url = 'https://api.kwiga.com/coupons';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });


# ---
# Filtered example
# ---

// Get coupons list with date filters
const url = 'https://api.kwiga.com/coupons?filters[date_from]=2022-04-15&filters[date_to]=2022-04-27';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get coupons list
import requests

url = 'https://api.kwiga.com/coupons'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()


# ---
# Filtered example
# ---

# Get coupons list with date filters
import requests

url = 'https://api.kwiga.com/coupons?filters[date_from]=2022-04-15&filters[date_to]=2022-04-27'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/coupons",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}


# ---
# Filtered example
# ---

{
  "method": "GET",
  "url": "https://api.kwiga.com/coupons?filters[date_from]=2022-04-15&filters[date_to]=2022-04-27",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
    "data": [
        {
            "id": 3,
            "code": "KwigaSMVT-MD",
            "reward": 30,
            "is_fixed": true,
            "is_disposable": false,
            "used_amount": 0,
            "total_uses": 10,
            "total_uses_per_user": 1,
            "user": {
                "id": 3,
                "avatar_url": "https://someurl.com",
                "hash": "EL9p2ycy4Ypga715",
                "name": "Test User",
                "email": "test@example.com",
                "tag_name": "TestUser"
            },
            "created_at": "2022-04-28T09:46:11.000000Z",
            "updated_at": "2022-04-28T09:46:11.000000Z",
            "deleted_at": null
        }
    ]
}

GET https://api.kwiga.com/coupons

URL Parameters

filters object optional
Filter parameters
date_from datetime optional
Фильтр по дате создания. Параметр 'от'
date_to datetime optional
Фильтр по дате создания. Параметр 'до'

Структура ответа

Элементы текущей страницы.
Ссылки на другие страницы того же списка.
Состояние пагинации для текущего запроса (номер страницы, общее количество и т.д.).

Получение купона

Пример запроса:

<?php
// Get coupon by ID

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/coupons/:coupon', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get coupon by ID

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/coupons/:coupon');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/coupons/:coupon' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get coupon by ID
const url = 'https://api.kwiga.com/coupons/:coupon';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get coupon by ID
import requests

url = 'https://api.kwiga.com/coupons/:coupon'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/coupons/:coupon",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
    "data": {
        "id": 1,
        "code": "KwigaSMVT-MD",
        "reward": 30,
        "is_fixed": true,
        "is_disposable": false,
        "used_amount": 0,
        "total_uses": 10,
        "total_uses_per_user": 1,
        "created_at": "2022-04-28T09:46:11.000000Z",
        "updated_at": "2022-04-28T09:46:11.000000Z",
        "deleted_at": null
    }
}

GET https://api.kwiga.com/coupons/:coupon

Структура ответа

Создать купон

Пример запроса:

<?php
// Create coupon

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'code' => 'MY-COUPON',
        'reward' => 0,
        'type_discount[id]' => 'discount_with_percent',
        'expires_at' => ,
        'timezone[id]' => 375,
        'total_uses' => 0,
        'total_uses_per_user' => 0,
    ],
];

$response = $client->request('POST', '/coupons', $options);

$result = json_decode($response->getBody());
?>
<?php
// Create coupon

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/coupons');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
    'code' => 'MY-COUPON',
    'reward' => 0,
    'type_discount[id]' => 'discount_with_percent',
    'expires_at' => ,
    'timezone[id]' => 375,
    'total_uses' => 0,
    'total_uses_per_user' => 0,
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request POST 'https://api.kwiga.com/coupons' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"code":"MY-COUPON","reward":0,"type_discount[id]":"discount_with_percent","expires_at":null,"timezone[id]":375,"total_uses":0,"total_uses_per_user":0}'
// Create coupon
const url = 'https://api.kwiga.com/coupons';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'code': 'MY-COUPON',
    'reward': 0,
    'type_discount[id]': 'discount_with_percent',
    'expires_at': ,
    'timezone[id]': 375,
    'total_uses': 0,
    'total_uses_per_user': 0
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Create coupon
import requests

url = 'https://api.kwiga.com/coupons'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'code': 'MY-COUPON',
    'reward': 0,
    'type_discount[id]': 'discount_with_percent',
    'expires_at': ,
    'timezone[id]': 375,
    'total_uses': 0,
    'total_uses_per_user': 0,
}

response = requests.post(url, headers=headers, json=data)

result = response.json()
{
  "method": "POST",
  "url": "https://api.kwiga.com/coupons",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "code": "MY-COUPON",
    "reward": 0,
    "type_discount[id]": "discount_with_percent",
    "expires_at": null,
    "timezone[id]": 375,
    "total_uses": 0,
    "total_uses_per_user": 0
  }
}

Пример ответа:

{
    "data": {
        "id": 8,
        "code": "MY-COUPON",
        "reward": 100,
        "type_discount": {
            "id": "discount_with_percent",
            "name": "In % of order/offer value"
        },
        "type_date_expired": {
            "id": "indefinite_action",
            "name": "Never expires"
        },
        "type_total_count_used": {
            "id": "certain_amount",
            "name": "A certain amount"
        },
        "type_used_contact_lists": {
            "id": "all_users",
            "name": "All users"
        },
        "is_fixed": false,
        "is_active": true,
        "used_amount": 0,
        "has_infinite_used": true,
        "has_access_contact_lists": false,
        "total_uses": 0,
        "total_uses_per_user": 1,
        "contact_lists": [],
        "type": {
            "id": 4,
            "title": "Offers"
        },
        "expires_at": null,
        "expires_at_utc": null,
        "timezone_id": null,
        "currency_id": null,
        "currency": null,
        "created_at": "2024-01-09T16:17:55.000000Z",
        "updated_at": "2024-01-09T16:17:55.000000Z",
        "deleted_at": null
    }
}

POST https://api.kwiga.com/coupons

URL Parameters

code string optional
Код купона. Если не указать, то код сгенерируется автоматически
type_discount.id string required
Тип скидки: процент от суммы - `discount_with_percent` или фиксированная скидка - `discount_with_currency`
Example: discount_with_percent or discount_with_currency
expires_at datetime optional
Срок действия купона. Если null или не указывать, то бессрочно
Example: 2024-12-31 or 2024-12-31 23:59:59
timezone.id integer optional
Таймзона даты срока действия купона
total_uses integer optional
Лимит на количество использований купона. 0 - без ограничений
total_uses_per_user integer optional
Лимит на количество использований купона одним пользователем

Структура ответа

Проверка купона

Пример запроса:

<?php
// Check coupon validity

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
    'json' => [
        'code' => 'VAJ6-EA',
        'price' => 65,
    ],
];

$response = $client->request('POST', '/coupons/check', $options);

$result = json_decode($response->getBody());
?>
<?php
// Check coupon validity

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/coupons/check');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$data = [
    'code' => 'VAJ6-EA',
    'price' => 65,
];

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

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

$result = json_decode($response);
?>
curl --location --request POST 'https://api.kwiga.com/coupons/check' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"code":"VAJ6-EA","price":65}'
// Check coupon validity
const url = 'https://api.kwiga.com/coupons/check';

const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
  body: JSON.stringify({
    'code': 'VAJ6-EA',
    'price': 65
  })
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Check coupon validity
import requests

url = 'https://api.kwiga.com/coupons/check'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

data = {
    'code': 'VAJ6-EA',
    'price': 65,
}

response = requests.post(url, headers=headers, json=data)

result = response.json()
{
  "method": "POST",
  "url": "https://api.kwiga.com/coupons/check",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  "body": {
    "code": "VAJ6-EA",
    "price": 65
  }
}

Пример ответа:

{
  "data": {
    "can_use": true,
    "discount": 6.5
  }
}

POST https://api.kwiga.com/coupons/check

Проверяет купон на существование и возможность использования (не исчерпан лимит / не закончился срок действия).
Обратите внимание: при использовании на checkout странице купон может не применяться при следующих условиях: если на предложении выключено использование купонов/данного купона; на купоне стоят ограничения списками контактов; пользователь уже использовал купон и на купоне стоит ограничение по количеству использований.

Parameters

code string required
Код купона для проверки
price number optional
Цена для расчёта скидки. Если указать, то в ответе будет возвращено значение скидки в поле discount

Структура ответа

Сертификаты

Получение сертификат по номеру

Пример запроса:

<?php
// Get certificate by number

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/certificates/by-number/:number', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get certificate by number

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/certificates/by-number/:number');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/certificates/by-number/:number' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get certificate by number
const url = 'https://api.kwiga.com/certificates/by-number/:number';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get certificate by number
import requests

url = 'https://api.kwiga.com/certificates/by-number/:number'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/certificates/by-number/:number",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
    "data": {
        "id": 55,
        "uuid": "262e7042-1933-42a9-ba01-e96d7d5110dc",
        "cabinet_id": 1,
        "user_id": 273,
        "issued_at": "2023-07-25T15:36:44.000000Z",
        "number": "1986-0001",
        "url": "http://cabinet-1.kwiga.local/certificates/262e7042-1933-42a9-ba01-e96d7d5110dc",
        "created_at": "2023-07-25T15:36:44.000000Z",
        "updated_at": "2023-07-25T15:36:44.000000Z",
        "finished_at": "2023-07-25T15:36:44.000000Z",
        "points": 120,
        "user": {
            "id": 273,
            "name": "test name",
            "email": "test-student@kwiga.com"
        },
        "certificateble_title": "Test course"
    }
}

GET https://api.kwiga.com/certificates/by-number/:number

Структура ответа

Таймзоны

Получения списка таймзон

Пример запроса:

<?php
// Get timezones list

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://api.kwiga.com']);

$options = [
    'headers' => [
        'Accept' => 'application/json',
        'Token' => '<Token>',
        'Cabinet-Hash' => '<Cabinet-Hash>',
    ],
];

$response = $client->request('GET', '/timezones', $options);

$result = json_decode($response->getBody());
?>
<?php
// Get timezones list

$headers = [
    'Accept: application/json',
    'Token: <Token>',
    'Cabinet-Hash: <Cabinet-Hash>',
];

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/timezones');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

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

$result = json_decode($response);
?>
curl --location --request GET 'https://api.kwiga.com/timezones' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get timezones list
const url = 'https://api.kwiga.com/timezones';

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
  },
};

fetch(url, options)
  .then(response => response.json())
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('Error:', error);
  });
# Get timezones list
import requests

url = 'https://api.kwiga.com/timezones'

headers = {
    'Accept': 'application/json',
    'Token': '<Token>',
    'Cabinet-Hash': '<Cabinet-Hash>',
}

response = requests.get(url, headers=headers)

result = response.json()
{
  "method": "GET",
  "url": "https://api.kwiga.com/timezones",
  "headers": {
    "Token": "<Token>",
    "Cabinet-Hash": "<Cabinet-Hash>",
    "Accept": "application/json"
  }
}

Пример ответа:

{
    "data": [
        {
            "id": 1,
            "name": "Asia/Kabul",
            "name_full": "Asia/Kabul (UTC+04:30)",
            "value": "UTC+04:30"
        },
        {
            "id": 2,
            "name": "Europe/Tirane",
            "name_full": "Europe/Tirane (UTC+02:00)",
            "value": "UTC+02:00"
        },
        ...
    ]
}

GET https://api.kwiga.com/timezones

Структура ответа

Элементы текущей страницы.
Ссылки на другие страницы того же списка.
Состояние пагинации для текущего запроса (номер страницы, общее количество и т.д.).

Схемы данных

Переиспользуемые объекты ответов API. Поля с пометкой conditional появляются в ответе только при выполнении документированного условия (флаг запроса, опциональное include или определённый эндпоинт).

Контакты

Contact

id integer
Уникальный идентификатор контакта
user_id integer nullable
Идентификатор связанного пользователя (null, если у контакта ещё нет аккаунта пользователя)
email string
Email контакта
first_name string nullable
middle_name string nullable
last_name string nullable
name string nullable
Отображаемое имя, собранное из имени/отчества/фамилии в предпочтительном для кабинета порядке.
phone string nullable
crm_url string nullable
Прямая ссылка на страницу контакта в CRM вашего кабинета.
created_at datetime
Момент создания контакта
tags FlowTag[] conditional
Conditional: Included when tags are loaded for the contact.
Теги, привязанные к контакту
last_activity_at datetime conditional
Conditional: Included when the contact has activity history in the cabinet.
Момент последней активности контакта в кабинете
first_visit Visit conditional
Conditional: Included when the contact has at least one recorded visit.
Первый зафиксированный визит контакта
utm UtmList conditional
Conditional: Included when UTM data has been collected for the contact.
Агрегированная UTM-атрибуция по всем визитам контакта
utm_visits Visit[] conditional
Conditional: Returned only on `GET /contacts/{id}` — not present on the contacts list.
Все UTM-отслеженные визиты контакта
offers Offer[] conditional
Conditional: Included when `with_orders=true` is passed.
Предложения, купленные контактом
orders Order[] conditional
Conditional: Included when `with_orders=true` is passed.
Заказы, размещённые контактом
Conditional: Included when the contact has custom field values configured.
Кастомные поля кабинета, заполненные для этого контакта
Conditional: Included when `with_certificates=true` is passed.
Информация о выпущенных сертификатах по продуктам контакта

ContactIdentifier

id integer
user_id integer nullable
first_name string nullable
last_name string nullable
name string nullable
Полное имя контакта (имя + фамилия), если указано
email string nullable
Email контакта (может быть скрыт для не-владельцев)
phone string nullable
Телефон контакта (может быть скрыт для не-владельцев)

ContactAdditionalField

field ContactField conditional
Conditional: Included when the field definition is part of the response.
Определение кастомного поля кабинета
value string nullable
Сырое значение, сохранённое для этого поля на контакте
display_value string nullable
Читаемое отображение значения (например, локализованный enum-label)

ContactField

id integer
title string
Локализованное название поля
is_local boolean
Является ли поле локальным для кабинета (не частью глобальной схемы)
input_title string conditional
Conditional: Returned only for viewers with extended access to the field.
format string conditional
Conditional: Returned only for viewers with extended access to the field.
Токен формата ввода для поля
validation string conditional
Conditional: Returned only for viewers with extended access to the field.
Выражение правила валидации
default_value string conditional
Conditional: Returned only for viewers with extended access to the field.
placeholder string conditional
Conditional: Returned only for viewers with extended access to the field.
type string conditional
Conditional: Returned only for viewers with extended access to the field.
Идентификатор типа поля (text, number, date, enum и т.д.)
is_default boolean conditional
Conditional: Returned only for viewers with extended access to the field.
is_required boolean conditional
Conditional: Returned only for viewers with extended access to the field.
is_at_import boolean conditional
Conditional: Returned only for viewers with extended access to the field.
visible_type Enum conditional
Conditional: Returned only for viewers with extended access to the field.
order integer conditional
Conditional: Returned only for viewers with extended access to the field.
Порядок отображения внутри кабинета
values object[] conditional
Conditional: Returned for viewers with extended access when value options are configured.
Допустимые значения для enum-полей
name_order_type object conditional
Conditional: Returned for viewers with extended access when the field is the default full-name field.
Порядок отображения имени/фамилии, заданный в кабинете

ContactList

id integer
cabinet_id integer conditional
Conditional: Included when the cabinet relation is part of the response.
Идентификатор кабинета-владельца
user_id integer conditional
Conditional: Included when the owning user is part of the response.
Идентификатор пользователя-владельца
title string
description string nullable
is_default boolean
True для встроенного списка по умолчанию в кабинете
is_active boolean
is_segment boolean
True, если список — динамический сегмент, управляемый фильтрами
is_new boolean
Маркер для списков, созданных после 2023-08-15 (используется в UI)
paused_at datetime nullable
created_at datetime
updated_at datetime
contacts_count integer conditional
Conditional: Included when contact counts are available for the list.
Conditional: Returned on the single-list endpoint; not present on the list response.
Conditional: Included when list statistics are part of the response.
Conditional: Included when the list's email campaigns are part of the response.
Email-рассылки, связанные со списком
segment_settings object conditional
Conditional: Present for dynamic segments; empty object for regular lists.
Правила фильтрации, определяющие членство в сегменте

ContactListStatistic

contact_list_id integer
count_contacts integer
Количество контактов, сейчас находящихся в списке

FlowTag

id integer
name string
contacts_count integer conditional
Conditional: Included when contact counts are available for the tag.
Сколько контактов помечено этим тегом

Курсы

Course

id integer
product_id integer
Идентификатор связанного продукта (для поиска через /contacts/{contact}/products)
type string
Идентификатор типа курса (course / marathon / и т.д.)
title string
Название курса (фолбэк на `Course
slug string nullable
URL-совместимый slug
preview FileSimple conditional
Conditional: Included when the course has a preview image.
url string
Публичная ссылка на лендинг курса
status string
Идентификатор статуса публикации курса (`draft`, `published` и т.д.)
offers Offer[] conditional
Conditional: Included when the course offers are part of the response.
info_units InfoUnit[] conditional
Conditional: Included when course info units are part of the response.
Conditional: Included when the course program is part of the response (rendered as a tree).

CourseIdentify

id integer
product_id integer
Идентификатор связанного продукта
type_id integer
Идентификатор типа курса (course / marathon / и т.д.)
title string
Conditional: Included when the course's lesson identity payloads are loaded.
Краткие идентификационные записи уроков курса
url string
Публичная ссылка на лендинг курса
preview_url string conditional
Conditional: Included when the course preview is loaded.
URL превью-картинки курса (или плейсхолдер по умолчанию, если изображение не задано)
Conditional: Included when offer identity payloads tied to the course are loaded.
Краткие идентификационные записи предложений, связанных с курсом

CourseLessonIdentify

id integer
title string
course CourseIdentify conditional
Conditional: Included when the parent course identity is part of the response.
Conditional: Included when the lesson's module branch is part of the response. Null for top-level lessons.

CourseLessonSimple

id integer
course_id integer
type_id integer
Идентификатор типа урока
status_id integer
Идентификатор статуса публикации урока
number string nullable
Видимый пользователю номер урока (токен порядка главы)
title string
slug string nullable
url string
Публичная ссылка на урок
Conditional: Included when lesson quizzes are part of the response.
Квизы, прикреплённые к уроку
course CourseIdentify conditional
Conditional: Included when the parent course summary is part of the response.
Идентификационные данные родительского курса
Сводка по родительскому модулю; null для уроков верхнего уровня

CourseModuleSimple

id integer
course_id integer
number string nullable
Видимый пользователю номер модуля / токен порядка
title string

CourseProgram

id integer
order integer
Позиция среди соседних узлов
Полиморфный тип нижележащей сущности (урок, модуль и т.д.)
course_id integer
title string
Название нижележащей сущности
preview FileSimple conditional
Conditional: Included when the node has a preview image attached.
Вложенные дочерние узлы (пустой массив у листьев)
root_id integer nullable
Идентификатор корня дерева
parent_id integer nullable
is_public boolean
url string nullable
Публичная ссылка на узел (если применимо)

CourseProgress

course_id integer
course_url string
Публичная ссылка на лендинг курса
title string
Название курса
lessons_count integer
Общее число активных уроков в курсе
Число уроков, открытых пользователем хотя бы раз
Процент просмотренных пользователем уроков (0 – 100)
Число уроков, пройденных пользователем
Процент пройденных пользователем уроков (0 – 100)
Процент прохождения для отображения (обрезанный/округлённый для UI)
quizzes_count integer
Общее число квизов в курсе
Число квизов, пройденных пользователем
Процент пройденных пользователем квизов (0 – 100)
scores_max number
Максимально возможный балл пользователя по курсу
Суммарный балл пользователя по всем квизам
Суммарный балл пользователя на уровне продукта (вне квизов)
scores number
Суммарный балл пользователя по всему курсу
is_completed boolean
True, если пользователь полностью прошёл курс
completed_at datetime nullable
Момент полного завершения курса; null пока курс в процессе
Урок, на котором пользователь сейчас находится (null, если нет)
Следующий доступный пользователю урок (null в конце курса)
last_activity_at datetime nullable
Момент последней активности пользователя в курсе
True, если пользователь пропустил блокирующие чекпоинты
Чекпоинт-квизы, до которых дошёл пользователь
True, если пользователь обошёл текущий drip-content барьер
True, если пользователь обошёл следующий drip-content барьер
current_dripping_date datetime nullable
Когда разблокируется текущий drip-content барьер
next_dripping_date datetime nullable
Когда разблокируется следующий drip-content барьер

CourseUser

Аккаунт ученика, который участвует в курсе — идентификатор, имя, email. Стабильно между запросами.
Связанная запись CRM-контакта для этого ученика.
course_points integer
Общее количество баллов пользователя в курсе
Сводка по прохождению уроков/модулей
Количество уроков, доступных пользователю сейчас
is_full_access boolean
True, если у пользователя есть доступ ко всем активным урокам курса
Conditional: Included when the user has an aggregated subscription for the course.
Conditional: Included when include contains lesson_progress.
Массив объектов `LessonProgress` — per-lesson снимки просмотра/завершения для этого пользователя. Возвращается при `include=lesson_progress`.
Conditional: Included when include contains module_progress.
Массив объектов `ModuleProgress` — per-module снимки просмотра/завершения для этого пользователя (агрегируются сервером из уроков модуля). Возвращается при `include=module_progress`.
Conditional: Included when include contains quiz_progress.
Массив объектов `QuizProgress` — по одной записи на пару `(course_lesson_id, quiz_id)` со сводкой по последней non-cancelled попытке ученика. Возвращается при `include=quiz_progress`.

InfoUnit

id integer
title string
description_formatted string nullable
Описание с rich-text форматированием и обработанными ссылками
True, если описание содержит внешние ссылки
position integer
Позиция элемента в родительской коллекции
type_id integer
Идентификатор типа info-unit

QuizIdentifier

id integer
name string
Название квиза

QuizAttemptIdentifier

id integer
course_id integer nullable
quiz_id integer
user_id integer
status_id integer
Идентификатор статуса попытки (passed / failed / in-progress)
comment string nullable
quiz object conditional
Conditional: Included when the quiz relation is loaded.
Метаданные квиза
course Course conditional
Conditional: Included when the parent course is loaded.
lesson object conditional
Conditional: Included when the parent lesson is loaded.
Метаданные урока

Купоны

ExpertCoupon

id integer
code string
Код купона, который покупатели вводят при оформлении
reward number nullable
Значение вознаграждения (сумма скидки или процент в зависимости от `is_fixed`)
Тип скидки (enum-payload)
Тип правила истечения (enum-payload)
is_fixed boolean
True для скидки в валюте, false для процентной
is_active boolean
used_amount integer
Сколько раз купон был использован
Ограничено ли использование купона определёнными списками контактов
total_uses integer nullable
Максимальное общее число использований купона
total_uses_per_user integer nullable
Максимум использований на одного контакта
Conditional: Included when the coupon's contact-list restrictions are part of the response.
type CouponType conditional
Conditional: Included when the coupon-type metadata is part of the response.
Метаданные типа купона
expert UserSimple conditional
Conditional: Included when the expert who owns the coupon is part of the response.
user UserSimple conditional
Conditional: Included when the issuing user is part of the response.
contact_id integer nullable
Conditional: Included when the linked contact is part of the response.
Краткие идентификационные данные связанного контакта
Conditional: Included when the offers limited by this coupon are part of the response.
Идентификационные данные предложений, на которые действует купон
expires_at datetime nullable
expires_at_utc datetime nullable
timezone_id integer nullable
timezone Timezone conditional
Conditional: Included when the coupon has timezone settings configured.
Conditional: Included when the latest internal comment is part of the response.
Последний внутренний комментарий (для администраторов)
currency_id integer nullable
currency Currency nullable
created_at datetime
updated_at datetime
deleted_at datetime nullable

CouponType

id integer
title string
Локализованное название типа купона

CouponCheck

can_use boolean
Можно ли применить купон к заказу
discount number nullable
Вычисленная сумма скидки, если в запросе передавался `price`; иначе null

Сертификаты

Certificate

id integer
uuid string
Универсальный уникальный идентификатор сертификата
cabinet_id integer
user_id integer nullable
number string
Публичный номер сертификата для поиска
url string nullable
Публичная ссылка для проверки; `null` пока сертификат не выпущен
issued_at datetime nullable
finished_at datetime conditional
Conditional: Included when both the user and the certificate target are available in the response.
Когда пользователь завершил certificateble-сущность (курс и т.п.)
points integer conditional
Conditional: Included when both the user and the certificate target are available in the response.
Баллы, набранные пользователем для получения сертификата
user UserSimple conditional
Conditional: Included when the related user is part of the response.
creator UserSimple conditional
Conditional: Included when creator information is part of the response.
Conditional: Included when the certificate template is part of the response.
Название сущности, на которую выпущен сертификат (курс, продукт и т.п.)

CertificateTemplate

id integer
title string
Внутреннее название шаблона
public_title string nullable
Публичное название шаблона, отображаемое на выпущенном сертификате

StudentProductCertificateInfo

Тип сертификата (enum-payload с id/title)
Conditional: Included when the certificate template is part of the response.
Полиморфный тип certificateble-сущности (курс, продукт и т.п.)
certificateble_title string conditional
Conditional: Included when the certificate target is part of the response.
Название certificateble-сущности
message string nullable
system_comment string nullable
certificate Certificate conditional
Conditional: Returned only after the certificate has been issued.

Предложения

Offer

id integer
unique_offer_code string nullable
Стабильный код, используемый в URL оплаты
url string
Публичный URL для покупки предложения
crm_url string
Глубокая ссылка на редактирование оффера в кабинете эксперта (CRM).
title string
description string nullable
short_description string nullable
price_type string
Идентификатор модели цены предложения (`free`, `fixed`, `recurring`)
price_discounted Price conditional
Conditional: Included when the offer has an active discount.
Цена с применённой активной скидкой
discount OfferDiscount conditional
Conditional: Included when the offer has an active, currently valid discount.
limit_type string nullable
Идентификатор типа ограничения продаж (`unlimited`, `limited`)
limit_of_sales integer nullable
Максимум разрешённых покупок (null если без лимита)
purchases_count integer conditional
Conditional: Included when purchase counts are available for the offer.
sales_left integer conditional
Conditional: Included when purchase counts are available. Null when the offer has no sales limit.
is_draft boolean
is_active boolean conditional
Conditional: Included when the offer has date settings configured.
Conditional: Returned together with `is_active` when the offer has date settings configured.
Conditional: Returned together with `is_active` when the offer has date settings configured.
products Product[] conditional
Conditional: Included when the offer's products are part of the response.
is_paid boolean conditional
Conditional: Included when payment status is determined for the current viewer.

OfferIdentifier

id integer
title string

OfferSimple

id integer
title string
description string nullable
url string
Публичный URL для покупки предложения
type_id integer
Идентификатор типа предложения
status string nullable
Идентификатор статуса предложения
Тарифицирует ли подписку мерчант
Conditional: Included when product identity entries are loaded for the offer.
curators object[] conditional
Conditional: Included when curator entries are loaded for the offer.
is_paid boolean conditional
Conditional: Included when payment status is determined for the current viewer.

OfferDiscount

id integer
Финальная цена с применённой скидкой
Тип ограничения скидки (enum-payload)
start_at datetime nullable
start_at_utc datetime nullable
start_timezone_id integer nullable
end_at datetime nullable
end_type string nullable
Как заканчивается скидка (фиксированная дата, после числа продаж и т.п.)
end_at_utc datetime nullable
end_timezone_id integer nullable
sales_limit integer nullable
Максимальное число продаж со скидкой
sales integer
Сколько продаж со скидкой уже произошло
is_active boolean
is_available boolean
Действует ли скидка сейчас (не истекла, не исчерпана)
created_at datetime
updated_at datetime
offer OfferIdentifier conditional
Conditional: Included when the parent offer is part of the response.
Краткие идентификационные данные родительского предложения

Заказы

Order

id integer
type_id integer
Идентификатор типа заказа
crm_url string
Глубокая ссылка на заказ в кабинете эксперта (CRM).
first_paid_at datetime nullable
paid_at datetime nullable
created_at datetime
updated_at datetime
products Product[] conditional
Conditional: Included when the order contains product information.
payments Payment[] conditional
Conditional: Included when the order has payment records.
paid_status integer nullable
Идентификатор статуса оплаты
paid_status_title string nullable
Локализованное название статуса оплаты
order_stage OrderStage conditional
Conditional: Included when the order has a stage assigned.
Полная стоимость заказа с валютой
managers UserSimple[] conditional
Conditional: Included when the order has assigned managers.
offers Offer[] conditional
Conditional: Included when the order contains offer information.
utm UtmList conditional
Conditional: Included when UTM data is available for the order.

OrderFunnel

id integer
title string
Локализованное название воронки
created_at datetime

OrderGroup

id integer
slug string
title string
Локализованное название группы
order integer
Позиция группы внутри её воронки
created_at datetime

OrderStage

id integer
title string
Локализованное название этапа
order_group OrderGroup conditional
Conditional: Included when the parent group is part of the response.
Conditional: Included when the parent funnel is part of the response.
created_at datetime

Платежи

Payment

id integer
status string
Идентификатор статуса оплаты
status_title string
Локализованное название статуса оплаты
payment_type string nullable
Идентификатор типа оплаты (разовая, рекуррентная и т.д.)
payment_type_title string nullable
Локализованное название типа оплаты
payment_form string nullable
Идентификатор формы оплаты (онлайн, вручную и т.д.)
payment_form_title string nullable
Локализованное название формы оплаты
purchase_type string nullable
Идентификатор типа покупки
purchase_type_title string nullable
Локализованное название типа покупки
Цена с информацией о валюте
paid_at datetime nullable
created_at datetime
updated_at datetime
Conditional: Included when payment transactions are part of the response.

Transaction

id integer
merchant_id integer nullable
payment_id integer
order_id integer nullable
payment_system_status string nullable
Сырая строка статуса от платёжной системы
failure_reason string nullable
Причина неудачи транзакции, если применимо
price number
currency_code string nullable
payer_account string nullable
Маскированный идентификатор плательщика (последние цифры карты / handle кошелька)
card_mask string nullable
rrn string nullable
Retrieval Reference Number от банка
fee number nullable
Комиссия платёжной системы
created_at datetime

Price

amount number
Сырое значение суммы
Сумма, округлённая по настройкам округления кабинета
Сумма с подставленным символом / кодом валюты
Сумма с подставленным ISO-кодом валюты

Currency

id integer
code string
ISO-код валюты
html_code string
Локализованное HTML-представление символа валюты
Локализованное HTML-представление буквенного кода валюты

Продукты

Product

id integer
productable_id integer
Идентификатор нижележащей сущности (курс, info-unit и т.д.)
Полиморфный тип нижележащей сущности (`course`, `info_unit` и т.д.)
title string
Локализованное название productable-сущности
url string conditional
Conditional: Included when the underlying entity is part of the response.
Публичная ссылка на productable-сущность

ProductIdentifier

id integer
Полиморфный тип нижележащей сущности (`course`, `info_unit` и т.д.)
productable_id integer
name string
Локализованное название нижележащей сущности
url string
Публичная ссылка на нижележащую сущность
course_type_id Enum conditional
Conditional: Included when the underlying entity is a course; carries the course type enum.
Conditional: Included when course lesson identity payloads are loaded for the product.

ProductAggregatedSubscription

is_active boolean
Активен ли сейчас доступ к продукту
is_paid boolean
Оплачена ли подписка (не пробная)
start_at datetime nullable
end_at datetime nullable
Эффективная дата окончания доступа (вычисляется по всем активным подпискам)
offer_end_at datetime nullable
order_end_at datetime nullable
frozen_at datetime nullable
Когда подписка была заморожена; null, если не заморожена.
extended_at datetime nullable
Когда доступ продлевали в последний раз; null, если не продлевали.
count_available_days integer nullable
Общее количество дней доступа контакта к продукту
count_left_days integer nullable
Осталось дней доступа
Текущее состояние (enum-payload)

ProductSubscription

id integer
creator_id integer nullable
user_id integer
product_id integer
order_id integer nullable
offer_id integer nullable
is_active boolean
start_at datetime nullable
order_end_at datetime nullable
end_at datetime nullable
Эффективная дата окончания доступа для этой подписки
frozen_at datetime nullable
Когда эта подписка была заморожена; null, если не заморожена.
extended_at datetime nullable
Когда эту подписку продлевали в последний раз; null, если не продлевали.
paid_at datetime nullable
created_at datetime
updated_at datetime

UserProduct

id integer
productable_id integer
Полиморфный тип нижележащей сущности
title string
image_url string nullable
URL превью-картинки productable-сущности
url string
Публичная ссылка на страницу продукта.
is_published boolean
Conditional: Included when subscription summary is part of the response.
Агрегированная информация о доступе (общий is_active/start/end по всем подпискам)
Conditional: Included when individual subscription records are part of the response.
Отдельные записи подписок (по одной на заказ/предложение)

Email

EmailCampaign

id integer
title string
Название рассылки; для системных рассылок фолбэк на локализованную подпись «Транзакционная»
state_id integer
Идентификатор состояния рассылки
is_restricted boolean
True для встроенных системных рассылок, которые нельзя редактировать
not_verified boolean
True, если у владельца аккаунта нет подтверждённого способа оплаты / пополнения
is_system boolean
is_segment boolean
True, если рассылка нацелена на динамический сегмент
cabinet_id integer
user_id integer
subject string nullable
subsubject string nullable
Прехедер / превью-текст, отображаемый под темой письма
start_type string nullable
Идентификатор типа расписания (immediate, scheduled, recurring и т.п.)
start_at_utc datetime nullable
start_at datetime nullable
timezone_id integer nullable
paused_at datetime nullable
rejected_at datetime nullable
finished_at datetime nullable
timezone Timezone nullable
Conditional: Included when the campaign's contact lists are loaded.
bounced_limit number
Порог, выше которого доля bounce считается проблемной
Порог, выше которого доля жалоб считается проблемной
count_recipients integer conditional
Conditional: Included when the campaign statistic is loaded.
complaint_rate number conditional
Conditional: Included when the campaign statistic is loaded.
bounced_rate number conditional
Conditional: Included when the campaign statistic is loaded.
count_opened integer conditional
Conditional: Included when the campaign statistic is loaded.
count_clicks integer conditional
Conditional: Included when the campaign statistic is loaded.
percent_opened number conditional
Conditional: Included when the campaign statistic is loaded.
percent_clicks number conditional
Conditional: Included when the campaign statistic is loaded.
percent_completed number conditional
Conditional: Included when the campaign statistic is loaded.
distributor_id integer conditional
Conditional: Returned only for viewers with extended mailing access.
templatable_type string conditional
Conditional: Returned only for viewers with extended mailing access.
templatable_id integer conditional
Conditional: Returned only for viewers with extended mailing access.
template object conditional
Conditional: Returned only for viewers with extended mailing access when the templatable relation is loaded.
Данные email-шаблона (системный или кастомный)
distributor object conditional
Conditional: Returned only for viewers with extended mailing access when the distributor relation is loaded.
statistic object conditional
Conditional: Returned only for viewers with extended mailing access when the statistic relation is loaded.
Полный снимок статистики рассылки

EmailCampaignIdentifier

id integer
title string
Название рассылки; для системных рассылок фолбэк на локализованную подпись

Визиты

Visit

id integer
ip string nullable
landing_url string nullable
Полный URL приземления (auth-хеш удалён)
landing_domain string nullable
landing_path string nullable
landing_params string nullable
Строка query landing-URL без auth-хеша
referrer_url string nullable
referrer_domain string nullable
utm_source string nullable
utm_campaign string nullable
utm_medium string nullable
utm_term string nullable
utm_content string nullable
location string nullable
Распознанная метка локации (город, страна)
Подробная разбивка по геолокации
device VisitUserAgent conditional
Conditional: Included when user agent / device data is available for the visit.
Информация о user agent / устройстве
created_at datetime

VisitLocation

Локализованный payload страны
state_name string nullable
Название штата / региона
city string nullable
full_location string
Путь `city, state, country` через запятую, пустые части пропускаются

VisitUserAgent

user_agent string nullable
Сырой заголовок User-Agent, зафиксированный на визите
browser string nullable
browser_version string nullable
platform string nullable
Название операционной системы
platform_version string nullable
Тип устройства (enum-payload: desktop, mobile, tablet)
title string
Читаемое резюме user agent

UtmList

utm_source string[]
Уникальные значения utm_source по всем визитам контакта
utm_campaign string[]
utm_medium string[]
utm_term string[]
utm_content string[]

Комментарии

CommentIdentifier

id integer
text string
Сырой текст комментария
commentable_id integer
Идентификатор сущности, к которой прикреплён комментарий
Полиморфный тип целевой сущности
commentable object conditional
Conditional: Included when the target entity is part of the response.
Краткое представление целевой сущности

Попытки квизов

QuizAttempt

id integer
root_id integer nullable
ID самой первой попытки в цепочке пересдач. Для корневой попытки — NULL.
previous_id integer nullable
ID предыдущей попытки в цепочке пересдач. Для первой попытки — NULL.
number_version integer
Порядковый номер попытки внутри цепочки пересдач, начиная с 1.
user_id integer
product_id integer
quiz_id integer
course_id integer nullable
course_lesson_id integer nullable
Краткая инфа о секции инфоблока, к которой привязан квиз. Появляется только когда квиз внутри секции урока (а не на самом уроке). Иначе NULL.
status Enum nullable

Статус попытки в виде enum-объекта — id (числовое значение), slug (стабильное имя кейса) и title (подпись, уже переведённая под локаль кабинета).

  • 1 — Пройдено
  • 2 — Не пройдено
  • 3 — Требуется доработка
  • 4 — Последняя версия изменила статус
  • 5 — В процессе
  • 6 — Ожидает проверки
  • 7 — Не приступал
scores number nullable
Сумма баллов, набранная учеником в этой попытке.
scores_max number nullable
Максимально возможное количество баллов для попытки. Если в самой попытке значение не сохранено (например, для virtual-строк «не приступал») — берётся из квиза. NULL, если у квиза нет балльной системы (например, задание).
count_questions integer
count_questions_correct integer nullable
count_questions_incorrect integer nullable
Признак того, что куратор вручную перебил автоматическую оценку попытки.
is_read boolean
Признак, что куратор прочитал попытку. Актуально только для квизов с открытыми вопросами — остальные типы помечаются прочитанными автоматически.
started_at datetime nullable
last_activity_at datetime nullable
finished_at datetime nullable
deadline_at datetime nullable
status_updated_at datetime nullable
Время последней смены статуса попытки.
checked_at datetime nullable
commented_at datetime nullable
canceled_at datetime nullable
Длительность попытки в секундах (finished_at минус started_at; для незавершённых — now() минус started_at). Минимум 1.
crm_url string
Глубокая ссылка на CRM-карточку попытки в кабинете эксперта (на поддомене кабинета). Позволяет команде сразу открыть попытку в дашборде.
user UserSimple conditional
Conditional: Included when the user relation is loaded.
quiz QuizIdentifier conditional
Conditional: Included when the quiz relation is loaded.
course Course conditional
Conditional: Included when the parent course is loaded.
Conditional: Included when the parent lesson is loaded.
Conditional: Included when the product relation is loaded.

InfoSectionReport

id integer
name string
Отображаемое название секции (если в секции нет своего заголовка — генерируется автоматически).
order integer
Позиция секции внутри родительского урока, нумерация с 1.
url string
Публичная ссылка на секцию — как её видит участник.
crm_url string
Ссылка на конструктор секции в кабинете эксперта.

Награды (геймификация)

FlowRewardLog

id integer
points number
Сколько баллов сдвинула эта запись журнала. Положительное — начисление, отрицательное — списание.
Направление операции, вычисляется по знаку pointsaccrued или deducted. Совпадает со значениями фильтра accrual_type в списке.

Почему появилась эта запись. Возможные id:

  • 1 — Практика пройдена
  • 2 — Отмена результатов практики
  • 3 — Сброс баллов за попытку
  • 4 — Новые баллы за попытку
  • 5 — Оплата баллами
  • 6 — Вручную
  • 7 — Автоматизация
  • 8 — Изменены баллы за попытку
  • 9 — Оплата баллами за подарок
event string nullable
Стабильный строковый ключ исходного flow-эвента (только у автоматических/event-driven записей). Для ручных — NULL.
event_name string nullable
Человекочитаемое название исходного flow-эвента (тот же источник, что и event). Для ручных — NULL.
eventable_type string nullable
Полиморфный тип исходной сущности (например, quiz_attempt, order, gift). Для ручных — NULL.
eventable_id integer nullable
ID исходной сущности внутри eventable_type. Для ручных — NULL.
event_comment string nullable
Свободный комментарий, зафиксированный в момент события (например, причина куратора при отмене квиза). NULL, если не задан.
manual_comment string nullable
Комментарий куратора к ручному начислению/списанию (reason_type=Manual). До 255 символов. NULL для остальных причин.
is_visible_to_student boolean nullable
Показывается ли manual_comment ученику в его собственном журнале баллов. Для не-ручных записей — NULL. Кураторы (включая этот API) видят комментарий всегда, независимо от флага.
message string
Готовый текст записи для отображения в UI. Тот же шаблон, что и в CRM, только без HTML-разметки. Для ручных записей сюда дописывается комментарий куратора.
Conditional: Included when the reward is tied to a product.
creator UserSimple conditional
Conditional: Included for rewards added by a curator/expert (manual or automation triggered by a user).
created_at datetime

Общее

DateSetting

type string
Тип настройки — `fixed`, `relative`, `recurring` и т.д.
duration_type_id integer nullable
Идентификатор единицы длительности (дни/недели/месяцы)
date_at datetime nullable
Фиксированная дата в таймзоне кабинета
date_at_utc datetime nullable
Та же дата, переведённая в UTC
timezone_id integer nullable
timezone Timezone nullable
after_months integer nullable
Относительное смещение в месяцах (для relative-типов)
after_days integer nullable
specific_time string nullable
Конкретное время дня (HH:MM)
specific_day_of_week integer nullable
1 (понедельник) – 7 (воскресенье), если привязано к конкретному дню недели
is_current_week boolean nullable
is_current_day boolean nullable

Timezone

id integer
name string
Идентификатор таймзоны IANA
name_full string
Название таймзоны со смещением UTC для отображения
value string
Строка UTC-смещения

Enum

id integer
Числовое значение enum (в некоторых местах называется `value`)
slug string
Имя enum-кейса (стабильный строковый идентификатор)
title string
Локализованная подпись enum в текущем `X-Language`

FileSimple

id integer
uuid string
Универсальный уникальный идентификатор файла
name string
Внутреннее сохранённое имя файла
original_name string
Оригинальное имя файла, загруженного пользователем
url string nullable
Публичная ссылка на файл (null, если хранилище недоступно или у читателя нет доступа)
thumbnails object nullable
Карта размеров превью на публичные ссылки
extension string nullable
Расширение файла без ведущей точки
type_id integer
Идентификатор типа файла
mime_type string nullable

UserSimple

id integer
name string nullable
Полное имя пользователя (имя + фамилия)
email string nullable
Email пользователя (может быть скрыт от не-владельцев)

CountrySimple

id integer
code string
ISO 3166-1 alpha-2 код страны
name string
Локализованное название страны в текущем `X-Language`

Обёртки и пагинация

Success

success boolean

AffectedSubscriptions

ID удалённых записей подписок
affected_orders integer[]
ID заказов, у которых был затронут доступ (стали custom)
Разбивка по offer_id; каждое значение содержит массивы `affected_subscriptions` и `affected_orders`. Пустой объект, если offers не передавались.
Разбивка по product_id; та же структура, что у `affected_by_offers`. Пустой объект, если product_id не передавался.

first string nullable
URL первой страницы
last string nullable
URL последней страницы
prev string nullable
URL предыдущей страницы (null на первой)
next string nullable
URL следующей страницы (null на последней)

PaginationMeta

current_page integer
from integer nullable
Индекс первого элемента на текущей странице (с 1)
last_page integer
Элементы навигации (prev / кнопки-номера страниц / next) для отрисовки пагинатора
path string
Базовый путь для построения URL пагинации
per_page integer
to integer nullable
Индекс последнего элемента на текущей странице (с 1)
total integer
Общее количество элементов по всем страницам

url string nullable
URL ссылки (null для текущей страницы)
label string
Отображаемая подпись — номер страницы, "« Previous", "Next »" и т.п.
active boolean
True для элемента, представляющего текущую страницу