Introduction
This is the KWIGA API documentation page. You can use this API to perform actions on your behalf inside your KWIGA cabinets β a cabinet is the workspace (your account / tenant) where your courses, contacts and other data live.
In order to enable the API you should click on the corresponding checkbox in the settings section of your account. After that a token will be generated and account hash will be created, that will be used for subsequent API calls.
If a token is compromised, it can be re-issued, that will make the old token inactive and generate a new one.
API is only available for those users who have API enabled in their account.
Rate limiting
The API has a rate limit of 200 requests per minute. The limit is counted per API token.
Every response includes the following rate-limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit |
maximum number of requests allowed per minute. |
X-RateLimit-Remaining |
number of requests still allowed in the current minute. |
Retry-After |
seconds to wait before retrying. Returned only when the limit is exceeded (HTTP 429). |
X-RateLimit-Reset |
UNIX timestamp indicating when the current window resets. Returned only when the limit is exceeded (HTTP 429). |
Idempotency
Mutating endpoints in the public API (currently POST /contacts/purchases and POST /contacts/:contact/rewards; more will be marked individually) accept an optional Idempotency-Key request header. Pass a unique value once and reuse it on every retry of the same logical operation β the server will collapse retries to a single side-effect and return the same response on every replay.
Endpoints that support idempotency are marked with this badge in the reference: Idempotent
Contract:
| Header | Behavior |
|---|---|
Idempotency-Key (request) |
Client-generated unique string (1β255 chars). UUIDv4 or a stable business key like reward-for-quiz-attempt-4821 both work. Empty/missing header disables idempotency for that call. Clients that cannot set custom headers (form-based integrations) may pass the same value as a body/query field named idempotency_key β header takes precedence if both are present. |
Idempotent-Replayed: true (response) |
Returned on the second and subsequent calls with the same key. When you see this header, the server did NOT execute the operation again β the body contains the same object that was created on the first call (the entity is re-read and serialized again, so it reflects its current state). If that entity has since been permanently deleted, the body is a minimal acknowledgement with its type and id. |
Scope of a key is (cabinet, endpoint, key): the same business key on a different endpoint or in another cabinet is treated as a fresh operation. We recommend UUIDv4 anyway β collisions become statistically impossible.
Two responses specific to idempotency. 409 β an operation with this key is already running in another request: retry shortly and you will get its result; we will not run it a second time. 400 β the key is longer than 255 characters: an unusable key is not silently ignored, otherwise you would believe you are protected while every retry creates another order.
Basic work with 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"
}
}
All requests must be directed to the domain:
https://api.kwiga.com
For authorization the API token should be send in one of the next variants:
in headers:
Token: {token}in GET/POST parameters:
token={token}
To identify the account in which the actions take place, the hash of the account must be sent along in one of the next variants:
in headers:
Cabinet-Hash: {cabinet_hash}in GET/POST parameters:
cabinet_hash={cabinet_hash}
PUT and DELETE requests can be sent as POST requests by specifying an additional _method parameter with the required method (PUT or DELETE).
You can set the localization of directories and validation messages with the header:
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) |
Errors
| Error Code | Meaning |
|---|---|
| 400 | Bad Request - Your request is invalid. |
| 401 | Unauthorized - Your API key is wrong. |
| 403 | Forbidden - The requested resource is hidden for administrators only. |
| 404 | Not Found - Server cannot find the requested resource. |
| 405 | Method Not Allowed -- Server knows the request method, but the target resource doesn't support this method. |
| 422 | Unprocessable entity. |
| 429 | Too Many Requests - You're sending too many requests! Slow down! |
| 500 | Internal Server Error - We had a problem with our server. Try again later. |
| 503 | Service Unavailable - We're temporarily offline for maintenance. Please try again later. |
CRM. Contacts
Contact records
List of contacts
Request example:
<?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_orders=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_orders=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 --globoff --request GET 'https://api.kwiga.com/contacts?page=1&per_page=15&filters[date_from]=2022-04-27&filters[search]=example.com&with_orders=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_orders=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_orders=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_orders=1",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Accept": "application/json"
}
}
Example of response:
{
"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"
}
],
"utm": {
"utm_source": [
"test-source"
],
"utm_campaign": [],
"utm_medium": [],
"utm_term": [
"test-term"
],
"utm_content": []
},
"offers": [
{
"id": 8,
"unique_offer_code": "ptJmPPXVYs0t",
"title": "ΠΡΠ΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΠ΅ #8",
"limit_type": {
"id": 1,
"name": "ΠΠ΅ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΎ"
},
"limit_of_sales": null
}
],
"telegram_accounts": [
{
"telegram_id": 123456789,
"username": "jamesbond"
},
{
"telegram_id": 987654321,
"username": null
}
]
},
{
"id": 2,
"created_at": "2022-02-04T12:17:32.000000Z",
"email": "test2@example.com",
"first_name": "Petr",
"last_name": "Ivanov",
"phone": "+380983234512",
"tags": [],
"offers": [],
"telegram_accounts": []
}
],
"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": "« translation missing: en.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: en.pagination_next »",
"active": false
}
],
"path": "https://api.kwiga.com/contacts",
"per_page": 15,
"to": 2,
"total": 3
}
}
GET https://api.kwiga.com/contacts
URL Parameters
115asc, descDefault: descResponse envelope
POST alias
POST https://api.kwiga.com/contacts/query
A functional alias for the GET endpoint above. Accepts the same parameters in the request body instead of the query string β useful when the filter list is too large to fit a URL (or when JSON is simply easier to build on the client). Body has priority over query string, response shape is identical.
Get contact
Request example:
<?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"
}
}
Example of response:
{
"data": {
"id": 132,
"email": "bond@example.com",
"first_name": "James",
"last_name": "Bond",
"phone_number": "931234567",
"tags": [],
"telegram_accounts": [
{
"telegram_id": 123456789,
"username": "jamesbond"
},
{
"telegram_id": 987654321,
"username": null
}
],
"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
Response envelope
Create contact
Request example:
<?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"
}
]
}
}
Example of response:
{
"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
falseenResponse envelope
Contact update
Request example:
<?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"
]
}
}
Example of response:
{
"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
Response envelope
Add purchase Idempotent
This method creates or finds contact, as well as adds purchase of the specified offer
Every call creates a new order, even when the contact already owns that product. To keep a retry (a repeated submit, a double click, a re-delivered webhook) from duplicating orders, pass an Idempotency-Key: a repeat with the same key creates no second order and returns the same contact with the Idempotent-Replayed: true header.
An idempotency key protects against a repeat of THE SAME request. When duplicates arrive as different requests instead (the integrator re-sends the purchase, with no key or a new one each time), pass skip_same_subscription: true β what the contact already owns is then not granted again and no extra order is created. The two mechanisms are independent and can be used together.
Request example:
<?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());
?>
# ---
# Idempotent example
# ---
<?php
// Safely retry the same purchase β passing Idempotency-Key prevents a duplicate order
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' => 'purchase-crm-deal-4623778',
],
'json' => [
'email' => 'bond@example.com',
'offer_id' => 18,
],
];
$response = $client->request('POST', '/contacts/purchases', $options);
$result = json_decode($response->getBody());
?>
# ---
# Skip_same_subscription example
# ---
<?php
// Skip the purchase when the contact already owns the offer
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' => 'bond@example.com',
'offer_id' => 18,
'skip_same_subscription' => true,
],
];
$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);
?>
# ---
# Idempotent example
# ---
<?php
// Safely retry the same purchase β passing Idempotency-Key prevents a duplicate order
$headers = [
'Accept: application/json',
'Token: <Token>',
'Cabinet-Hash: <Cabinet-Hash>',
'Idempotency-Key: purchase-crm-deal-4623778',
];
$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 = [
'email' => 'bond@example.com',
'offer_id' => 18,
];
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response);
?>
# ---
# Skip_same_subscription example
# ---
<?php
// Skip the purchase when the contact already owns the offer
$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 = [
'email' => 'bond@example.com',
'offer_id' => 18,
'skip_same_subscription' => true,
];
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"}]}'
# ---
# Idempotent example
# ---
curl --location --request POST 'https://api.kwiga.com/contacts/purchases' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--header 'Idempotency-Key: purchase-crm-deal-4623778' \
--data-raw '{"email":"bond@example.com","offer_id":18}'
# ---
# Skip_same_subscription example
# ---
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 '{"email":"bond@example.com","offer_id":18,"skip_same_subscription":true}'
// 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);
});
# ---
# Idempotent example
# ---
// Safely retry the same purchase β passing Idempotency-Key prevents a duplicate order
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>',
'Idempotency-Key': 'purchase-crm-deal-4623778',
},
body: JSON.stringify({
'email': 'bond@example.com',
'offer_id': 18
})
};
fetch(url, options)
.then(response => response.json())
.then(result => {
console.log(result);
})
.catch(error => {
console.error('Error:', error);
});
# ---
# Skip_same_subscription example
# ---
// Skip the purchase when the contact already owns the offer
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({
'email': 'bond@example.com',
'offer_id': 18,
'skip_same_subscription': true
})
};
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()
# ---
# Idempotent example
# ---
# Safely retry the same purchase β passing Idempotency-Key prevents a duplicate order
import requests
url = 'https://api.kwiga.com/contacts/purchases'
headers = {
'Accept': 'application/json',
'Token': '<Token>',
'Cabinet-Hash': '<Cabinet-Hash>',
'Idempotency-Key': 'purchase-crm-deal-4623778',
}
data = {
'email': 'bond@example.com',
'offer_id': 18,
}
response = requests.post(url, headers=headers, json=data)
result = response.json()
# ---
# Skip_same_subscription example
# ---
# Skip the purchase when the contact already owns the offer
import requests
url = 'https://api.kwiga.com/contacts/purchases'
headers = {
'Accept': 'application/json',
'Token': '<Token>',
'Cabinet-Hash': '<Cabinet-Hash>',
}
data = {
'email': 'bond@example.com',
'offer_id': 18,
'skip_same_subscription': true,
}
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"
}
]
}
}
# ---
# Idempotent example
# ---
{
"method": "POST",
"url": "https://api.kwiga.com/contacts/purchases",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Idempotency-Key": "purchase-crm-deal-4623778",
"Accept": "application/json",
"Content-Type": "application/json"
},
"body": {
"email": "bond@example.com",
"offer_id": 18
}
}
# ---
# Skip_same_subscription example
# ---
{
"method": "POST",
"url": "https://api.kwiga.com/contacts/purchases",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Accept": "application/json",
"Content-Type": "application/json"
},
"body": {
"email": "bond@example.com",
"offer_id": 18,
"skip_same_subscription": true
}
}
Example of response:
{
"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"
}
]
}
]
},
"order": {
"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"
}
],
"crm_url": "https://sample-school.kwiga.com/expert/crm/orders/1060",
"offers": [
{
"id": 18,
"unique_offer_code": "full-course",
"title": "Full course access",
"price_info": {
"amount": 1200,
"currency": {
"id": 147,
"code": "UAH",
"html_code": "β΄"
}
}
}
]
}
}
POST https://api.kwiga.com/contacts/purchases
Request
falsefalsefalseenhttps://sample-school.kwiga.com/expert/payments/offers/edit/3858). If not provided, the request falls back to the product_ids field.
If offer_id and product_ids are missing, an empty order will be created
trueoffer_id the purchase is skipped entirely; from product_ids the already granted products are dropped, and if none is left, no order is created. When no order is created, the response has no order field β the contact itself is returned as usual, with its current products. Off by default, so renewals and repeat purchases keep working as before.
falseResponse envelope
offer_id nor product_ids is passed. Absent when the call created no order: with skip_same_subscription: true the contact already owned what was requested; product_ids together with is_paid: false creates a pre-subscription and no order; the contact has no linked user account. A replay by Idempotency-Key carries no order either β the operation did not run, which the Idempotent-Replayed header makes visible.
Tags
Contact tags adding
Request example:
<?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"
]
}
}
Example of response:
{
"success": true
}
POST https://api.kwiga.com/contacts/tags
Request
contact_ids. Kept for backwards compatibility; new integrations should use contact_ids.
Response envelope
Contact tags removing
Request example:
<?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"
]
}
}
Example of response:
{
"success": true
}
DELETE https://api.kwiga.com/contacts/tags
Request
contact_ids. Kept for backwards compatibility; new integrations should use contact_ids.
Response envelope
Products & subscriptions
Contact products list
Request example:
<?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"
}
}
Example of response:
{
"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
Returns a list of contact products with subscription and access period information. The aggregated_subscription parameter contains summary information about the terms and activity of access based on all user subscriptions for the product. The subscriptions parameter contains an array of all user subscriptions for the product.
Response envelope
Delete product from contact
Request example:
<?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"
}
}
Example of response:
{
"success": true
}
DELETE https://api.kwiga.com/contacts/:contact/products/:product
Deletes contact subscriptions for the specified product. Orders become custom without access to deleted products.
Response envelope
Delete products from contact (bulk)
Request example:
<?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
]
}
}
Example of response:
{
"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
Deletes subscriptions by specified conditions, and orders become custom without access to deleted products. You can combine product_id and offers parameters to delete part of products by specific offer.
Parameters
Response envelope
Freeze contact subscription
Request example:
<?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
}
}
Example of response:
{
"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
Freezes all of the contact's active subscriptions for the given product for count_frozen_days calendar days. The freeze shifts start_at / end_at / order_end_at forward, marks the subscription inactive, and notifies the student. On unfreeze (manual or automatic when the freeze period elapses) start_at is restored from the contact's order data, not just moved back by the same delta.
URL Parameters
Response envelope
Unfreeze contact subscription
Request example:
<?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"
}
}
Example of response:
{
"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
Unfreezes all of the contact's frozen subscriptions on the given product. The unfreeze restores start_at, recomputes end_at / order_end_at (subtracting any unused part of the freeze period), reactivates access where applicable, and notifies the student. Body is empty.
Response envelope
Extend contact subscription
Request example:
<?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
}
}
Example of response:
{
"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
Adds count_extend_days calendar days to end_at / order_end_at of every subscription the contact has on this product. For automation-style use, the same endpoint powers re-activation of expired subscriptions in CRM.
URL Parameters
Response envelope
Change subscription end date
Request example:
<?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
}
}
Example of response:
{
"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
Replaces end_at (and order_end_at when present) with an explicit datetime. The provided value is interpreted in the cabinet timezone identified by timezone_id (see the Timezones list endpoint); when timezone_id is omitted, end_at is treated as UTC.
URL Parameters
timezone_id.
end_at is treated as UTC.
Response envelope
Rewards (points)
List contact rewards (points)
Request example:
<?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 --globoff --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"
}
}
Example of response:
{
"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
Returns the contact's points journal β every accrual and deduction tied to quizzes, orders, gifts, automations or manual actions by curators. The response envelope adds sum_points, sum_accrued_points, sum_deducted_points for the filtered window.
URL Parameters
151Response envelope
Add a manual reward (points) to a contact Idempotent
Request example:
<?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
}
}
Example of response:
{
"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
Adds a manual reward to the contact's points journal. Positive points = accrual, negative = deduction. Optional product_id ties the reward to a specific product (otherwise it is cabinet-wide). Reason type is always Manual; the API user is recorded as the curator who issued it.
URL Parameters
message in the response. The student sees the comment in their points journal only when is_visible_to_student is true.
falseResponse envelope
CRM. Orders
Update order
Request example:
<?php
// Move the order to another funnel stage
use GuzzleHttp\Client;
$client = new Client(['base_uri' => 'https://api.kwiga.com']);
$options = [
'headers' => [
'Accept' => 'application/json',
'Token' => '<Token>',
'Cabinet-Hash' => '<Cabinet-Hash>',
],
'json' => [
'order_stage_id' => 42,
],
];
$response = $client->request('PUT', '/orders/:order', $options);
$result = json_decode($response->getBody());
?>
# ---
# Contact example
# ---
<?php
// Reassign the order to another 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' => [
'contact_id' => 132,
],
];
$response = $client->request('PUT', '/orders/:order', $options);
$result = json_decode($response->getBody());
?>
# ---
# Both example
# ---
<?php
// Change stage and contact in one request
use GuzzleHttp\Client;
$client = new Client(['base_uri' => 'https://api.kwiga.com']);
$options = [
'headers' => [
'Accept' => 'application/json',
'Token' => '<Token>',
'Cabinet-Hash' => '<Cabinet-Hash>',
],
'json' => [
'order_stage_id' => 42,
'contact_id' => 132,
],
];
$response = $client->request('PUT', '/orders/:order', $options);
$result = json_decode($response->getBody());
?>
<?php
// Move the order to another funnel stage
$headers = [
'Accept: application/json',
'Token: <Token>',
'Cabinet-Hash: <Cabinet-Hash>',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/orders/:order');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
$data = [
'order_stage_id' => 42,
];
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response);
?>
# ---
# Contact example
# ---
<?php
// Reassign the order to another contact
$headers = [
'Accept: application/json',
'Token: <Token>',
'Cabinet-Hash: <Cabinet-Hash>',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/orders/:order');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
$data = [
'contact_id' => 132,
];
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response);
?>
# ---
# Both example
# ---
<?php
// Change stage and contact in one request
$headers = [
'Accept: application/json',
'Token: <Token>',
'Cabinet-Hash: <Cabinet-Hash>',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/orders/:order');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
$data = [
'order_stage_id' => 42,
'contact_id' => 132,
];
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/orders/:order' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"order_stage_id":42}'
# ---
# Contact example
# ---
curl --location --request PUT 'https://api.kwiga.com/orders/:order' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"contact_id":132}'
# ---
# Both example
# ---
curl --location --request PUT 'https://api.kwiga.com/orders/:order' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>' \
--data-raw '{"order_stage_id":42,"contact_id":132}'
// Move the order to another funnel stage
const url = 'https://api.kwiga.com/orders/:order';
const options = {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Token': '<Token>',
'Cabinet-Hash': '<Cabinet-Hash>',
},
body: JSON.stringify({
'order_stage_id': 42
})
};
fetch(url, options)
.then(response => response.json())
.then(result => {
console.log(result);
})
.catch(error => {
console.error('Error:', error);
});
# ---
# Contact example
# ---
// Reassign the order to another contact
const url = 'https://api.kwiga.com/orders/:order';
const options = {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Token': '<Token>',
'Cabinet-Hash': '<Cabinet-Hash>',
},
body: JSON.stringify({
'contact_id': 132
})
};
fetch(url, options)
.then(response => response.json())
.then(result => {
console.log(result);
})
.catch(error => {
console.error('Error:', error);
});
# ---
# Both example
# ---
// Change stage and contact in one request
const url = 'https://api.kwiga.com/orders/:order';
const options = {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Token': '<Token>',
'Cabinet-Hash': '<Cabinet-Hash>',
},
body: JSON.stringify({
'order_stage_id': 42,
'contact_id': 132
})
};
fetch(url, options)
.then(response => response.json())
.then(result => {
console.log(result);
})
.catch(error => {
console.error('Error:', error);
});
# Move the order to another funnel stage
import requests
url = 'https://api.kwiga.com/orders/:order'
headers = {
'Accept': 'application/json',
'Token': '<Token>',
'Cabinet-Hash': '<Cabinet-Hash>',
}
data = {
'order_stage_id': 42,
}
response = requests.put(url, headers=headers, json=data)
result = response.json()
# ---
# Contact example
# ---
# Reassign the order to another contact
import requests
url = 'https://api.kwiga.com/orders/:order'
headers = {
'Accept': 'application/json',
'Token': '<Token>',
'Cabinet-Hash': '<Cabinet-Hash>',
}
data = {
'contact_id': 132,
}
response = requests.put(url, headers=headers, json=data)
result = response.json()
# ---
# Both example
# ---
# Change stage and contact in one request
import requests
url = 'https://api.kwiga.com/orders/:order'
headers = {
'Accept': 'application/json',
'Token': '<Token>',
'Cabinet-Hash': '<Cabinet-Hash>',
}
data = {
'order_stage_id': 42,
'contact_id': 132,
}
response = requests.put(url, headers=headers, json=data)
result = response.json()
{
"method": "PUT",
"url": "https://api.kwiga.com/orders/:order",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Accept": "application/json",
"Content-Type": "application/json"
},
"body": {
"order_stage_id": 42
}
}
# ---
# Contact example
# ---
{
"method": "PUT",
"url": "https://api.kwiga.com/orders/:order",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Accept": "application/json",
"Content-Type": "application/json"
},
"body": {
"contact_id": 132
}
}
# ---
# Both example
# ---
{
"method": "PUT",
"url": "https://api.kwiga.com/orders/:order",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Accept": "application/json",
"Content-Type": "application/json"
},
"body": {
"order_stage_id": 42,
"contact_id": 132
}
}
Example of response:
{
"data": {
"id": 5821,
"type_id": 1,
"crm_url": "https://sample-school.kwiga.com/expert/payments/orders/5821",
"first_paid_at": "2026-05-12T10:03:00.000000Z",
"paid_at": "2026-05-12T10:03:00.000000Z",
"created_at": "2026-05-10T08:15:00.000000Z",
"updated_at": "2026-07-17T11:40:00.000000Z",
"paid_status": 2,
"paid_status_title": "Paid",
"order_stage": {
"id": 42,
"title": "In progress",
"order_group": {
"id": 3,
"slug": "in-progress",
"title": "In progress",
"order": 2,
"created_at": "2025-01-15T09:00:00.000000Z"
},
"order_funnel": {
"id": 1,
"title": "Default funnel",
"created_at": "2025-01-15T09:00:00.000000Z"
},
"created_at": "2025-01-15T09:00:00.000000Z"
},
"cost_info": {
"amount": 199.99,
"amount_rounded": 200,
"amount_formatted": "$199.99",
"amount_formatted_code": "199.99 USD",
"currency": {
"id": 1,
"code": "USD",
"html_code": "$",
"html_letter_code": "USD"
}
}
}
}
PUT https://api.kwiga.com/orders/:order
Request
Response envelope
Products
Cabinet products list
Request example:
<?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"
}
}
Example of response:
{
"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
Cabinet products list. Pagination is available, default is 15.
URL Parameters
asc, descDefault: desc151Response envelope
Courses
Cabinet courses list
Request example:
<?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 --globoff --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"
}
}
Example of response:
{
"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": "« Previous",
"active": false
},
{
"url": "http://api.kwiga.local/courses?page=1",
"label": "1",
"active": true
},
{
"url": null,
"label": "Next »",
"active": false
}
],
"path": "http://api.kwiga.local/courses",
"per_page": 15,
"to": 1,
"total": 1
}
}
GET https://api.kwiga.com/courses
Cabinet courses list. Pagination is available, default is 15 (max: 15).
URL Parameters
offers, description, programasc, descDefault: desc151per_page. Kept for backward compatibility with existing integrations; use per_page in new code. When both are passed, per_page wins.
Response envelope
Course participants list
Request example:
<?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"
}
}
Example of response:
{
"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": "« Previous",
"active": false
},
{
"url": "http://api.kwiga.local/courses/20/users?page=1",
"label": "1",
"active": true
},
{
"url": null,
"label": "Next »",
"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
Returns a list of course participants and their progress. Pagination is available, default is 15 (max: 250). Replace :course with the course id that can be obtained from the course editing/management URL or from the course list endpoint.
URL Parameters
emails / contact_ids / user_id / user_ids into a single audience filter (union).
emails / contact_id / user_id / user_ids into a single audience filter (union).
emails / contact_id / contact_ids / user_ids into a single audience filter (union).
emails / contact_id / contact_ids / user_id into a single audience filter (union).
contact_id / contact_ids / user_id / user_ids into a single audience filter (union).
151Optional comma-separated list of response extensions. Each token enables one section of the response β clients only pay for what they ask for.
course_programβ adds the course program tree at the top level of the response ascourse_program(array of CourseProgram nodes; lesson nodes contain same-shaped info-section children with attached quizzes inquizzes). Returned once next todata/links/metabecause it is the same for every user on the page.lesson_progressβ addslesson_progressinside everydata[]row: array of LessonProgress objects (one per lesson the user has progress on).module_progressβ addsmodule_progressinside everydata[]row: array of ModuleProgress objects.quiz_progressβ addsquiz_progressinside everydata[]row: array of QuizProgress objects, one per(course_lesson_id, quiz_id)pair summarised from the user's latest active attempt.
Note: the same quiz can be attached to several lessons of the same course, so quiz_progress is keyed by the lesson/quiz pair, not by quiz_id alone. The client stitches per-user progress to the program tree by course_nodeble_id of lesson/module nodes and by the (lesson.course_nodeble_id, quizzes[i].id) pair for section nodes.
Response envelope
include contains course_program.Course program tree returned at the top level when
include contains course_program. A tree of CourseProgram nodes; lesson nodes contain same-shaped info-section children carrying the attached quizzes. Identical for all users on the page, so the API returns it once instead of repeating it per row.
POST alias
POST https://api.kwiga.com/courses/:course/users/query
A functional alias for the GET endpoint above. Accepts the same parameters in the request body instead of the query string β useful when the filter list is too large to fit a URL (or when JSON is simply easier to build on the client). Body has priority over query string, response shape is identical.
Receiving lesson-level analytics
In addition to the per-user course summary the endpoint can return
a detailed lesson-level progress breakdown. Opt in with the
include query parameter (see its description in the URL
parameters above for the full list of tokens and the response shape
each one enables). The course program is returned once at the top
level under course_program β it is the same for every user on the
page, so this keeps the response size proportional to the number of
users multiplied by the number of lessons/quizzes, not by the size
of the whole tree.
Response example with
?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,
"node_id": 341,
"parent_node_id": null,
"title": "Mathematics",
"lessons_count": 12,
"lessons_count_viewed": 5,
"lessons_count_completed": 4,
"lessons_completed_display_percentage": 33,
"quizzes_count": 9,
"quizzes_count_completed": 3,
"quizzes_completion_percentage": 34,
"quizzes_scores": 36,
"scores_max": 108,
"is_watched": false,
"is_completed": false,
"quizzing_status": {
"id": 3,
"slug": "InProgress",
"title": "In progress"
},
"watching_start_at": "2026-05-20T11:20:00.000000Z",
"watching_end_at": null,
"quizzing_start_at": "2026-05-20T12:05:00.000000Z",
"quizzing_end_at": null,
"completed_at": null,
"created_at": "2026-05-20T11:20:00.000000Z",
"updated_at": "2026-05-21T09:10:00.000000Z"
},
{
"module_id": 8,
"node_id": 352,
"parent_node_id": 341,
"title": "Mathematics β geometry",
"lessons_count": 5,
"lessons_count_viewed": 0,
"lessons_count_completed": 0,
"lessons_completed_display_percentage": 0,
"quizzes_count": 4,
"quizzes_count_completed": 0,
"quizzes_completion_percentage": 0,
"quizzes_scores": 0,
"scores_max": 48,
"is_watched": false,
"is_completed": false,
"quizzing_status": null,
"watching_start_at": null,
"watching_end_at": null,
"quizzing_start_at": null,
"quizzing_end_at": null,
"completed_at": null,
"created_at": null,
"updated_at": null
}
],
"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",
"quiz_score": {
"common_scores_min": 0,
"common_scores_max": 100,
"quiz_scores": 0,
"questions_scores_max": 80,
"statements_scores_max": 0,
"answers_scores_min": 0,
"answers_scores_max": 20
}
}
],
"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": []
}
]
}
]
}
]
}
Stitching progress to the tree
The endpoint returns the course program once at the top level and
the per-user progress arrays inside each data[] row. To render
a user's progress on top of the program tree, do the following.
1. Index this user's progress arrays for fast lookup.
lesson_progressβ index bylesson_idmodule_progressβ index bymodule_idquiz_progressβ index by the pair(course_lesson_id, quiz_id), because the same quiz can be attached to several lessons
A note on names: progress entries use lesson_id/module_id/course_lesson_id,
while program nodes use course_nodeble_id. These are the same value
from different angles β see step 2.
module_progress additionally carries node_id / parent_node_id. Those are
course-tree node ids β the same coordinates as the program nodes' own id /
parent_id β so you can rebuild the module hierarchy from module_progress
alone, without walking the program. Use them rather than pairing modules by
module_id: ancestry lives in the node tree, and one module can in principle
have more than one node. Note also that module_progress returns a row for
EVERY module of the course, so its length is not "how many modules the student
started" β read watching_start_at for that. One row may carry
module_id = null: those are the lessons that sit directly at the course root
and belong to no module.
2. Walk course_program and pick the right entry per node.
Every node carries course_nodeble_type + course_nodeble_id. Use
course_nodeble_id as the lookup key:
course_modulenode βmodule_progress[node.course_nodeble_id]course_lessonnode βlesson_progress[node.course_nodeble_id]info_sectionnode β sections don't carry their own progress. Walknode.quizzesand look upquiz_progress[(parent_lesson.course_nodeble_id, quiz.id)]for each quiz, whereparent_lessonis the closest enclosingcourse_lessonnode.
If the lookup returns nothing, the user has no progress on that node yet β render the empty state. The course program is the same for every user on the page, so it's safe to build it once and reuse across rows.
Closed groups
Get closed groups list
Request example:
<?php
// Get closed groups 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', '/closed-groups', $options);
$result = json_decode($response->getBody());
?>
# ---
# Filtered example
# ---
<?php
// Get closed groups list filtered by title
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', '/closed-groups?filters[search]=VIP', $options);
$result = json_decode($response->getBody());
?>
<?php
// Get closed groups list
$headers = [
'Accept: application/json',
'Token: <Token>',
'Cabinet-Hash: <Cabinet-Hash>',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/closed-groups');
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 closed groups list filtered by title
$headers = [
'Accept: application/json',
'Token: <Token>',
'Cabinet-Hash: <Cabinet-Hash>',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/closed-groups?filters[search]=VIP');
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/closed-groups' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
# ---
# Filtered example
# ---
curl --location --globoff --request GET 'https://api.kwiga.com/closed-groups?filters[search]=VIP' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get closed groups list
const url = 'https://api.kwiga.com/closed-groups';
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 closed groups list filtered by title
const url = 'https://api.kwiga.com/closed-groups?filters[search]=VIP';
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 closed groups list
import requests
url = 'https://api.kwiga.com/closed-groups'
headers = {
'Accept': 'application/json',
'Token': '<Token>',
'Cabinet-Hash': '<Cabinet-Hash>',
}
response = requests.get(url, headers=headers)
result = response.json()
# ---
# Filtered example
# ---
# Get closed groups list filtered by title
import requests
url = 'https://api.kwiga.com/closed-groups?filters[search]=VIP'
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/closed-groups",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Accept": "application/json"
}
}
# ---
# Filtered example
# ---
{
"method": "GET",
"url": "https://api.kwiga.com/closed-groups?filters[search]=VIP",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Accept": "application/json"
}
}
Example of response:
{
"data": [
{
"id": 1,
"product_id": 128,
"title": "VIP mastermind",
"slug": "vip-mastermind",
"type": {
"id": 1,
"slug": "Telegram",
"title": "Telegram"
},
"status": {
"id": 2,
"slug": "Active",
"title": "Active"
},
"chatbot": {
"name": "@my_group_bot"
},
"channel": {
"telegram_channel_id": "-1001234567890",
"linked_chat_id": "-1009876543210",
"url": "https://t.me/my_group"
},
"created_at": "2024-01-15T10:00:00.000000Z",
"updated_at": "2024-02-20T08:30:00.000000Z"
},
{
"id": 2,
"product_id": 131,
"title": "Community (Kwiga native)",
"slug": "community-kwiga-native",
"type": {
"id": 2,
"slug": "Kwiga",
"title": "Kwiga"
},
"status": {
"id": 5,
"slug": "NotConnected",
"title": "Not connected"
},
"chatbot": null,
"channel": null,
"created_at": "2024-03-01T09:00:00.000000Z",
"updated_at": "2024-03-01T09:00:00.000000Z"
}
]
}
GET https://api.kwiga.com/closed-groups
Returns the cabinet's closed groups (Telegram or Kwiga-native communities bound to a product). Requires the closed_group_read permission.
URL Parameters
Response envelope
Get closed group participants
Request example:
<?php
// Get closed group participants
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', '/closed-groups/:closedGroup/participants', $options);
$result = json_decode($response->getBody());
?>
# ---
# Paginated example
# ---
<?php
// Get closed group participants 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', '/closed-groups/:closedGroup/participants?page=1&per_page=50', $options);
$result = json_decode($response->getBody());
?>
# ---
# Filtered example
# ---
<?php
// Get closed group participants filtered by status and search
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', '/closed-groups/:closedGroup/participants?filters[search]=Alice&filters[status_id]=1', $options);
$result = json_decode($response->getBody());
?>
<?php
// Get closed group participants
$headers = [
'Accept: application/json',
'Token: <Token>',
'Cabinet-Hash: <Cabinet-Hash>',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/closed-groups/:closedGroup/participants');
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 closed group participants with pagination
$headers = [
'Accept: application/json',
'Token: <Token>',
'Cabinet-Hash: <Cabinet-Hash>',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/closed-groups/:closedGroup/participants?page=1&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);
?>
# ---
# Filtered example
# ---
<?php
// Get closed group participants filtered by status and search
$headers = [
'Accept: application/json',
'Token: <Token>',
'Cabinet-Hash: <Cabinet-Hash>',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/closed-groups/:closedGroup/participants?filters[search]=Alice&filters[status_id]=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/closed-groups/:closedGroup/participants' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
# ---
# Paginated example
# ---
curl --location --request GET 'https://api.kwiga.com/closed-groups/:closedGroup/participants?page=1&per_page=50' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
# ---
# Filtered example
# ---
curl --location --globoff --request GET 'https://api.kwiga.com/closed-groups/:closedGroup/participants?filters[search]=Alice&filters[status_id]=1' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get closed group participants
const url = 'https://api.kwiga.com/closed-groups/:closedGroup/participants';
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 closed group participants with pagination
const url = 'https://api.kwiga.com/closed-groups/:closedGroup/participants?page=1&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);
});
# ---
# Filtered example
# ---
// Get closed group participants filtered by status and search
const url = 'https://api.kwiga.com/closed-groups/:closedGroup/participants?filters[search]=Alice&filters[status_id]=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 closed group participants
import requests
url = 'https://api.kwiga.com/closed-groups/:closedGroup/participants'
headers = {
'Accept': 'application/json',
'Token': '<Token>',
'Cabinet-Hash': '<Cabinet-Hash>',
}
response = requests.get(url, headers=headers)
result = response.json()
# ---
# Paginated example
# ---
# Get closed group participants with pagination
import requests
url = 'https://api.kwiga.com/closed-groups/:closedGroup/participants?page=1&per_page=50'
headers = {
'Accept': 'application/json',
'Token': '<Token>',
'Cabinet-Hash': '<Cabinet-Hash>',
}
response = requests.get(url, headers=headers)
result = response.json()
# ---
# Filtered example
# ---
# Get closed group participants filtered by status and search
import requests
url = 'https://api.kwiga.com/closed-groups/:closedGroup/participants?filters[search]=Alice&filters[status_id]=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/closed-groups/:closedGroup/participants",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Accept": "application/json"
}
}
# ---
# Paginated example
# ---
{
"method": "GET",
"url": "https://api.kwiga.com/closed-groups/:closedGroup/participants?page=1&per_page=50",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Accept": "application/json"
}
}
# ---
# Filtered example
# ---
{
"method": "GET",
"url": "https://api.kwiga.com/closed-groups/:closedGroup/participants?filters[search]=Alice&filters[status_id]=1",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Accept": "application/json"
}
}
Example of response:
{
"data": [
{
"user_id": 1024,
"contact": {
"id": 42,
"user_id": 1024,
"first_name": "John",
"last_name": "Doe",
"name": "John Doe",
"email": "user@example.com",
"phone": "+380931234567"
},
"status": {
"id": 1,
"slug": "ActiveMember",
"title": "Active member"
},
"is_joined": true,
"is_banned": false,
"telegram_accounts": [
{
"telegram_id": 123456789,
"username": "johndoe"
},
{
"telegram_id": 987654321,
"username": null
}
],
"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,
"frozen_at": null,
"extended_at": null,
"count_available_days": 661,
"count_left_days": null,
"state": {
"id": 2,
"slug": "Open",
"title": "Open"
}
}
},
{
"user_id": 1031,
"contact": null,
"status": {
"id": 3,
"slug": "NotJoined",
"title": "Not joined"
},
"is_joined": false,
"is_banned": false,
"telegram_accounts": [],
"subscription": {
"is_active": false,
"is_paid": true,
"start_at": "2024-04-23T13:45:02.000000Z",
"end_at": "2024-05-23T13:45:02.000000Z",
"offer_end_at": "2024-05-23T13:45:02.000000Z",
"order_end_at": null,
"frozen_at": null,
"extended_at": null,
"count_available_days": 30,
"count_left_days": 0,
"state": {
"id": 3,
"slug": "Closed",
"title": "Closed"
}
}
}
],
"links": {
"first": "https://api.kwiga.com/closed-groups/1/participants?page=1",
"last": "https://api.kwiga.com/closed-groups/1/participants?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "https://api.kwiga.com/closed-groups/1/participants",
"per_page": 15,
"to": 2,
"total": 2
}
}
GET https://api.kwiga.com/closed-groups/:closedGroup/participants
Returns the paginated members of a single closed group, one row per user. Requires the closed_group_participants or product_participants permission. Replace :closedGroup with the closed group id.
URL Parameters
151Response envelope
Quizzes
Cabinet quiz placements list
Request example:
<?php
// Get all quiz placements of the cabinet
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-placements', $options);
$result = json_decode($response->getBody());
?>
# ---
# By_course example
# ---
<?php
// Get quiz placements of one course
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-placements?filters[course_ids]=35412&per_page=50', $options);
$result = json_decode($response->getBody());
?>
# ---
# Counted_in_progress example
# ---
<?php
// Get only placements counted in progress, with module chain
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-placements?filters[course_ids]=35412&filters[is_used_in_progress]=1&include=module_ids', $options);
$result = json_decode($response->getBody());
?>
# ---
# Shown_in_program example
# ---
<?php
// Get only placements shown in the course program
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-placements?filters[course_ids]=35412&filters[can_display_in_program]=1', $options);
$result = json_decode($response->getBody());
?>
<?php
// Get all quiz placements of the cabinet
$headers = [
'Accept: application/json',
'Token: <Token>',
'Cabinet-Hash: <Cabinet-Hash>',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/quiz-placements');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response);
?>
# ---
# By_course example
# ---
<?php
// Get quiz placements of one course
$headers = [
'Accept: application/json',
'Token: <Token>',
'Cabinet-Hash: <Cabinet-Hash>',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&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);
?>
# ---
# Counted_in_progress example
# ---
<?php
// Get only placements counted in progress, with module chain
$headers = [
'Accept: application/json',
'Token: <Token>',
'Cabinet-Hash: <Cabinet-Hash>',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&filters[is_used_in_progress]=1&include=module_ids');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response);
?>
# ---
# Shown_in_program example
# ---
<?php
// Get only placements shown in the course program
$headers = [
'Accept: application/json',
'Token: <Token>',
'Cabinet-Hash: <Cabinet-Hash>',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&filters[can_display_in_program]=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/quiz-placements' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
# ---
# By_course example
# ---
curl --location --globoff --request GET 'https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&per_page=50' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
# ---
# Counted_in_progress example
# ---
curl --location --globoff --request GET 'https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&filters[is_used_in_progress]=1&include=module_ids' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
# ---
# Shown_in_program example
# ---
curl --location --globoff --request GET 'https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&filters[can_display_in_program]=1' \
--header 'Content-Type: application/json' \
--header 'Token: <Token>' \
--header 'Cabinet-Hash: <Cabinet-Hash>'
// Get all quiz placements of the cabinet
const url = 'https://api.kwiga.com/quiz-placements';
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);
});
# ---
# By_course example
# ---
// Get quiz placements of one course
const url = 'https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&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);
});
# ---
# Counted_in_progress example
# ---
// Get only placements counted in progress, with module chain
const url = 'https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&filters[is_used_in_progress]=1&include=module_ids';
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);
});
# ---
# Shown_in_program example
# ---
// Get only placements shown in the course program
const url = 'https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&filters[can_display_in_program]=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 all quiz placements of the cabinet
import requests
url = 'https://api.kwiga.com/quiz-placements'
headers = {
'Accept': 'application/json',
'Token': '<Token>',
'Cabinet-Hash': '<Cabinet-Hash>',
}
response = requests.get(url, headers=headers)
result = response.json()
# ---
# By_course example
# ---
# Get quiz placements of one course
import requests
url = 'https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&per_page=50'
headers = {
'Accept': 'application/json',
'Token': '<Token>',
'Cabinet-Hash': '<Cabinet-Hash>',
}
response = requests.get(url, headers=headers)
result = response.json()
# ---
# Counted_in_progress example
# ---
# Get only placements counted in progress, with module chain
import requests
url = 'https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&filters[is_used_in_progress]=1&include=module_ids'
headers = {
'Accept': 'application/json',
'Token': '<Token>',
'Cabinet-Hash': '<Cabinet-Hash>',
}
response = requests.get(url, headers=headers)
result = response.json()
# ---
# Shown_in_program example
# ---
# Get only placements shown in the course program
import requests
url = 'https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&filters[can_display_in_program]=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/quiz-placements",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Accept": "application/json"
}
}
# ---
# By_course example
# ---
{
"method": "GET",
"url": "https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&per_page=50",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Accept": "application/json"
}
}
# ---
# Counted_in_progress example
# ---
{
"method": "GET",
"url": "https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&filters[is_used_in_progress]=1&include=module_ids",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Accept": "application/json"
}
}
# ---
# Shown_in_program example
# ---
{
"method": "GET",
"url": "https://api.kwiga.com/quiz-placements?filters[course_ids]=35412&filters[can_display_in_program]=1",
"headers": {
"Token": "<Token>",
"Cabinet-Hash": "<Cabinet-Hash>",
"Accept": "application/json"
}
}
Example of response:
{
"data": [
{
"id": 9142,
"quiz": {
"id": 4211,
"name": "Ukrainian language β unit 1 test",
"type": {
"id": 1,
"slug": "Normal",
"title": "Normal"
},
"questions_type": {
"id": 2,
"slug": "Single",
"title": "Single choice"
},
"questions_count": 12,
"quiz_score": {
"common_scores_min": 0,
"common_scores_max": 12,
"quiz_scores": 0,
"questions_scores_max": 12,
"statements_scores_max": 0,
"answers_scores_min": 0,
"answers_scores_max": 12
},
"is_estimate_on": true,
"is_auto_approve": true,
"can_retry": true,
"can_retry_only_failure": false,
"count_tries": 3
},
"product_id": 431,
"product": {
"id": 431,
"productable_type": "course",
"productable_id": 226,
"name": "PreStart 2026. PRO5.0",
"url": "https://lm4.kwiga.com/courses/prestart-2026"
},
"course_lesson_id": 504,
"lesson": {
"id": 504,
"title": "Unit 1 β vocabulary"
},
"info_section": {
"id": 4410,
"name": "Practice",
"order": 3,
"url": "https://lm4.kwiga.com/courses/prestart-2026/3/lessons/504",
"crm_url": "https://lm4.kwiga.com/expert/course/226/lesson/504/constructor?section=2"
},
"quizable_type": "info_units",
"quizable_id": 8801,
"order": 1,
"is_used_in_progress": true,
"can_display_in_program": true,
"is_checkpoint": true,
"dripping": {
"accessible_type": {
"id": 1,
"slug": "Immediately",
"title": "Immediately from the moment of opening access to the course"
},
"accessible_after_days": null,
"accessible_after_hours": null,
"accessible_after_minutes": null,
"accessible_start_at": null,
"closed_type": {
"id": 2,
"slug": "AfterDays",
"title": "N days after access is granted"
},
"closed_after_days": 14,
"closed_after_hours": null,
"closed_after_minutes": null,
"closed_start_at": null
},
"module_ids": [
122189
]
},
{
"id": 9143,
"quiz": {
"id": 4211,
"name": "Ukrainian language β unit 1 test",
"type": {
"id": 1,
"slug": "Normal",
"title": "Normal"
},
"questions_type": {
"id": 2,
"slug": "Single",
"title": "Single choice"
},
"questions_count": 8,
"quiz_score": {
"common_scores_min": 0,
"common_scores_max": 12,
"quiz_scores": 0,
"questions_scores_max": 12,
"statements_scores_max": 0,
"answers_scores_min": 0,
"answers_scores_max": 12
},
"is_estimate_on": true,
"is_auto_approve": true,
"can_retry": true,
"can_retry_only_failure": false,
"count_tries": 3
},
"product_id": 431,
"product": {
"id": 431,
"productable_type": "course",
"productable_id": 226,
"name": "PreStart 2026. PRO5.0",
"url": "https://lm4.kwiga.com/courses/prestart-2026"
},
"course_lesson_id": 519,
"lesson": {
"id": 519,
"title": "Unit 1 β vocabulary"
},
"info_section": null,
"quizable_type": "info_units",
"quizable_id": 8940,
"order": 2,
"is_used_in_progress": false,
"can_display_in_program": true,
"is_checkpoint": false,
"dripping": null,
"module_ids": [
122190,
122188
]
}
],
"links": {
"first": "https://api.kwiga.com/quiz-placements?page=1",
"last": "https://api.kwiga.com/quiz-placements?page=4",
"prev": null,
"next": "https://api.kwiga.com/quiz-placements?page=2"
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 4,
"path": "https://api.kwiga.com/quiz-placements",
"per_page": 50,
"to": 50,
"total": 187
}
}
GET https://api.kwiga.com/quiz-placements
Returns where each quiz is placed inside the cabinet's courses. A row is a
placement, not a quiz: the same quiz can be attached to several
lessons and sections, and each placement counts towards progress on its own.
Several rows sharing the same quiz.id are therefore expected.
This is the missing link between course structure and
quiz attempts β in particular it is where the values
for filters[quiz_ids] come from. To find out how many points the whole
course is worth, take the placements with is_used_in_progress = true and
sum their quiz.quiz_score.common_scores_max. That describes the course
contents, the same for everyone. An individual student may still have lessons that
have not opened on schedule yet, or that their offer excludes, so their personal figures come from
course_progress / module_progress instead.
URL Parameters
module_ids adds the chain of ancestor modules for the placement's lesson (nearest module first).
module_idsResponse envelope
POST alias
POST https://api.kwiga.com/quiz-placements/query
A functional alias for the GET endpoint above. Accepts the same parameters in the request body instead of the query string β useful when the filter list is too large to fit a URL (or when JSON is simply easier to build on the client). Body has priority over query string, response shape is identical.
Cabinet quiz attempts list
Request example:
<?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 --globoff --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"
}
}
Example of response:
{
"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": "« 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 »", "active": false }
]
},
"attempts_unread_count": 12
}
GET https://api.kwiga.com/quiz-attempts
Returns a paginated list of student quiz attempts visible to the
authenticated curator across the whole cabinet (no product is
scoped from the URL β pass products if you need to narrow the
result). Pagination defaults to 15 items per page, max 250.
attempts_unread_count at the top level reflects the
unread count for the same filter set, suitable for a "new attempts"
badge.
URL Parameters
151last_activity_atPossible values: last_activity_at, status_updated_at, finished_at, statusDefault: last_activity_atdescPossible values: asc, descDefault: descResponse envelope
POST alias
POST https://api.kwiga.com/quiz-attempts/query
A functional alias for the GET endpoint above. Accepts the same parameters in the request body instead of the query string β useful when the filter list is too large to fit a URL (or when JSON is simply easier to build on the client). Body has priority over query string, response shape is identical.
Offers
Get offers
Request example:
<?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"
}
}
Example of response:
{
"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": "« Previous",
"active": false
},
{
"url": "http://api.kwiga.local/offers?page=1",
"label": "1",
"active": true
},
{
"url": null,
"label": "Next »",
"active": false
}
],
"path": "http://api.kwiga.local/offers",
"per_page": 15,
"to": 1,
"total": 2
}
}
GET https://api.kwiga.com/offers
URL Parameters
Response envelope
Get offer
Request example:
<?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"
}
}
Example of response:
{
"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
Response envelope
Marketing. Mailing
Contact List List
Request example:
<?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"
}
}
Example of response:
{
"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": "« translation missing: en.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: en.pagination_next »",
"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
Response envelope
Getting a contact list
Request example:
<?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"
}
}
Example of response:
{
"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
Response envelope
Creating a contact list
Request example:
<?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": "ΠΎΠΏΠΈΡΠ°Π½ΠΈΠ΅"
}
}
Example of response:
{
"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
Response envelope
Adding Contacts to a List
Request example:
<?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
}
}
Example of response:
{
"success": true
}
POST https://api.kwiga.com/mailing/contact-lists/bulk-contacts
URL Parameters
Response envelope
Payments. Coupons
Coupon list
Request example:
<?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 --globoff --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"
}
}
Example of response:
{
"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
Response envelope
Getting a coupon
Request example:
<?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"
}
}
Example of response:
{
"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
Response envelope
Create coupon
Request example:
<?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
}
}
Example of response:
{
"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
discount_with_percent or fixed discount - discount_with_currency
discount_with_percent or discount_with_currency2024-12-31 or 2024-12-31 23:59:59Response envelope
Check coupon
Request example:
<?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
}
}
Example of response:
{
"data": {
"can_use": true,
"discount": 6.5
}
}
POST https://api.kwiga.com/coupons/check
Checks coupon for existence and usability (limit not exhausted / not expired).
Note: when used on checkout page, the coupon may not be applied under the following conditions: if the use of coupons/this coupon is disabled on the offer; there are restrictions on the coupon by contact lists; the user has already used the coupon and there is a limit on the number of uses on the coupon.
Parameters
Response envelope
Certificates
Receiving a certificate by number
Request example:
<?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"
}
}
Example of response:
{
"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
Response envelope
Timezones
Getting a list of time zones
Request example:
<?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"
}
}
Example of response:
{
"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
Response envelope
Schemas
Reusable response objects referenced across endpoints. Fields marked as conditional only appear in the response under the documented circumstance (a query flag, an optional include, or a specific endpoint).
Contacts
Contact
Tags attached to the contact
Timestamp of the contact's last activity within the cabinet
First recorded visit of the contact
Aggregated UTM attribution across the contact's visits
All UTM-tracked visits of the contact
Offers the contact has purchased
Orders placed by the contact
Cabinet-specific custom fields filled in for this contact
Certificate issuance details per product the contact owns
ContactIdentifier
ContactAdditionalField
Definition of the cabinet-specific custom field
ContactField
Field label shown to whoever fills it in.
Field input format token
Validation rule expression
Value pre-filled by default. Empty when there is no default.
Hint shown inside the empty field. Empty when no hint is set.
Field type identifier (text, number, date, enum, etc.)
The field ships with the platform and exists in every cabinet β unlike fields an expert added.
The field is mandatory.
The field can be filled in during contact import.
Who the field is visible to.
Display order within the cabinet
Allowed values for enum-style fields
Cabinet-specific ordering for first/last name display
ContactList
Owning cabinet identifier
Owning user identifier
How many contacts the list holds.
List contacts in compact form.
Summary statistics for the list.
Email campaigns associated with the list
Filter rules driving the segment membership
ContactListStatistic
FlowTag
Number of contacts the tag is attached to
Courses
Course
Course cover image. Empty when no cover is set.
draft, published, etc.)
The offers the course is sold under.
Course info units β the building blocks its lesson content is assembled from.
Course program β the tree of modules and lessons.
CourseIdentify
Compact identity entries for the course's lessons
URL to the course preview image (or default placeholder when missing)
Compact identity entries for offers linked to the course
CourseLessonIdentify
The course the lesson belongs to.
The module the lesson sits in. Empty for lessons that sit in the course on their own, outside any module.
CourseLessonSimple
Quizzes attached to the lesson
Identity payload of the parent course
CourseModuleSimple
CourseProgram
course_node.id). Caveat for info-section nodes: while info-sections are not real course_nodes yet, this is info_sections.id; after the course-engine update it will be the actual course_node.id β the contract does not change.
course_module, course_lesson, and always info_section for section nodes. Values are stable across versions, safe to match against.
course_nodeble_type. Not the same as id: that one addresses a position in the tree, while the entity itself may appear in the tree more than once.
Node cover image. Empty when no cover is set.
include=course_program) lesson nodes contain info-section nodes carrying the attached quizzes. On info-section nodes themselves the field is reserved for the course-engine update and is currently always empty.
course_lesson node.
Info-section nodes only. Quizzes attached to the section as an array of
QuizIdentifier objects. Combines quizzes attached via the section's practice info-units with lesson-level "List of practice" quizzes mapped onto the section that holds them. The pair (parent_lesson.course_nodeble_id, quizzes[i].id) matches quiz_progress[].course_lesson_id + quiz_id.
CourseProgress
LessonProgress
id / slug): 1 / WithoutQuizzes β the lesson has no quizzes, 2 / NotStarted, 3 / InProgress, 4 / Completed, 5 / NotDetected β could not be determined.
ModuleProgress
course_program, so you can rebuild the subject structure from them. Nesting is expressed by tree positions rather than module pairs because the database stores the tree. Empty on the summary row for lessons outside modules: no module there, so no tree position either.
is_practice_configured next to the list.
quizzes_count β the course-level figure behaves the same way.
scores.
false both when the student has not started the module and when they started but did not finish. Tell them apart by watching_start_at.
lesson_progress.quizzing_status: 1 / WithoutQuizzes, 2 / NotStarted, 3 / InProgress, 4 / Completed, 5 / NotDetected.
null means they never started it.
QuizProgress
quiz_id it forms the row key β one quiz can sit in several lessons and counts separately in each.
/quiz-placements.
id / slug): 1 / Passed, 2 / Failed, 3 / NeedRework, 4 / LastAttemptChangedStatus, 5 / InProcess, 6 / PendingCheck, 7 / DoesntStart.
null while the attempt is still in progress.
null for quizzes that don't require review or for attempts not yet checked.
(course_lesson_id, quiz_id) pair; ones superseded by a newer attempt do not count. Since the endpoint always returns the latest active attempt, this is equivalent to the total count of active attempts β useful when you want to know how many times the user has tried this quiz.
CourseUser
The student aggregated access to the course product β validity dates collapsed across all of their payments. Absent when no such access exists.
include contains lesson_progress.Array of
LessonProgress objects β per-lesson watched/completed snapshots for this user. Returned when include contains lesson_progress.
include contains module_progress.Array of
ModuleProgress objects β one row per EVERY module of the course, carrying both the progress metrics and the watched-status snapshot. Modules the student never opened are included too, with zeroed metrics. Rows must not be summed indiscriminately β a lesson counts towards every module of its ancestor chain, so only rows with parent_node_id = null add up to the course-level figures. Returned when include contains module_progress.
include contains quiz_progress.Array of
QuizProgress objects β one entry per (course_lesson_id, quiz_id) pair summarised from the user's latest active attempt β ones superseded by a newer attempt are not counted. Returned when include contains quiz_progress.
include contains module_progress.Whether practice is enabled for this student by at least one of their offers. Per student, not per module β the setting lives on the offer. When
false, every module reports zero quizzes because progress does not count quizzes for this student at all (their tariff has practice switched off), not because the course has none. When true, a zero still has other possible causes β the author unchecked "count in progress" for those quizzes, they sit in sections outside the student's tariff, or their lessons are not open yet. Use /quiz-placements with filters[is_used_in_progress] to tell those apart. Returned when include contains module_progress.
InfoUnit
QuizIdentifier
Scoring configuration of the quiz β current values from the quiz settings, not the historical snapshot of an attempt. Returned only where the endpoint explicitly exposes scoring (currently β quizzes inside
course_program). NULL when the quiz has no stored scoring record. All values are zero when the quiz scoring system is disabled.
Whether this placement of the quiz counts towards progress. It is a property of the placement, not of the quiz β the same quiz may count in one lesson and not in another. Present where the quiz arrives together with its placement (inside
course_program and course checkpoints); absent where the quiz is loaded on its own.
QuizAttemptIdentifier
Quiz metadata payload
The course the quiz belongs to.
Lesson metadata payload
Coupons
ExpertCoupon
is_fixed)
Contact lists the coupon is available to.
Coupon type metadata
The expert the coupon belongs to.
The user who created the coupon.
Compact identity payload of the linked contact
Identity payloads of offers limited by this coupon
timezone. Empty when no expiry is set and the coupon runs without a time limit.
Timezone the expiry is set in.
Latest internal comment payload (admin-facing)
CouponType
CouponCheck
price was sent in the request; otherwise null
Certificates
Certificate
null until the certificate is issued
When the user completed the certificateble entity (course, etc.)
Points earned by the user toward the certificateble
The student the certificate was issued to.
Who issued the certificate.
Template the certificate is built from.
CertificateTemplate
StudentProductCertificateInfo
Template the certificate will be built from.
certificateble_type.
Title of the certificateble entity
The certificate itself, once it has been issued.
Closed groups
ClosedGroup
id / slug): 1 / Draft, 2 / Active, 3 / NotActive, 4 / NotSpecified, 5 / NotConnected.
null otherwise.
null otherwise.
ClosedGroupChatbot
ClosedGroupChannel
null otherwise
null otherwise
ClosedGroupParticipant
null when the participant has no contact in this cabinet
id / slug): 1 / ActiveMember, 2 / Banned, 3 / NotJoined, 4 / LeftGroup, 5 / AccessExpired.
Offers
Offer
free, fixed, recurring)
price_discounted.
Price after the active discount is applied
The running discount. Present only when a discount exists and is currently valid β both by dates and by remaining sales.
unlimited, limited)
How many times the offer has been bought.
How many purchases remain before the limit. Empty when no limit is set and sales are unlimited.
The offer is currently on sale by its sale dates β the sale period has started and has not ended. The sales limit is NOT taken into account here: check
sales_left for that.
When the offer goes on sale.
When the offer sale ends.
Products the buyer receives under this offer.
The offer is paid. Present only where this could be determined.
OfferIdentifier
OfferSimple
Products included in the offer, in compact form.
Curators assigned to the offer.
The offer is paid. Present only where this could be determined.
OfferDiscount
start_timezone. Empty when no start is set and the discount is live immediately.
end_timezone. Empty when no end is set and the discount runs indefinitely.
Compact identity payload of the parent offer
Orders
Order
first_paid_at.
Products included in the order.
Payments on the order. There can be several β with installments or a subscription.
The funnel stage the order is currently at.
Managers assigned to the order.
Offers the order was made under.
OrderFunnel
OrderGroup
OrderStage
The group the stage belongs to.
The funnel the stage belongs to.
Payments
Payment
Transactions behind this payment β charge attempts in the payment provider. One payment can have several attempts.
Transaction
currency_code.
Price
Currency
Products
Product
course, info_unit)
Public link to the productable entity
ProductIdentifier
course, info_unit)
productable_type.
Course type. Present only for products that are courses.
Course lessons in compact form β identifier, title and parents.
ProductAggregatedSubscription
end_at, the earlier of offer_end_at and order_end_at.
end_at.
ProductSubscription
start_at and end_at.
end_at.
UserProduct
Aggregated access info (overall is_active/start/end across all subscriptions)
Individual subscription records (one per order/offer)
EmailCampaign
timezone. Empty when no launch is scheduled.
Contact lists the campaign is sent to.
Number of recipients. For regular campaigns this is the count of contacts eligible for sending; for system ones it is the count of messages actually sent.
Share of recipients who marked the message as spam. The acceptable threshold comes alongside in
complaint_limit.
Share of messages that could not be delivered. The acceptable threshold comes alongside in
bounced_limit.
How many recipients opened the message.
How many recipients clicked a link in the message.
Open share, computed against the total number of campaign contacts β not against delivered messages.
Click share, computed against the total number of campaign contacts β not against those who opened.
How far the campaign has been sent: share of sent messages against its total contacts.
Identifier of the sender the message goes out from. Empty when no sender is set.
Type of the entity the email template is tied to. Empty when no template is tied.
Identifier of the entity the email template is tied to. Empty when no template is tied.
Email template payload (system or custom)
The sender the message goes out from.
Full campaign statistic snapshot
EmailCampaignIdentifier
Visitors
Visit
utm_source tag from the URL. Empty when it was absent.
utm_campaign tag from the URL. Empty when it was absent.
utm_medium tag from the URL. Empty when it was absent.
utm_term tag from the URL. Empty when it was absent.
utm_content tag from the URL. Empty when it was absent.
User agent / device information
VisitLocation
city, state, country path with empty parts skipped
VisitUserAgent
UtmList
utm_campaign value seen across this entity visits.
utm_medium value seen across this entity visits.
utm_term value seen across this entity visits.
utm_content value seen across this entity visits.
Comments
CommentIdentifier
Compact payload of the target entity
Quiz attempts
QuizAttempt
Attempt status as an enum object — id (numeric), slug (stable case name) and title (label already translated for the cabinet locale).
1— Completed2— Not completed3— Revision required4— The latest version changed the status5— In progress6— Pending review7— Not started
The student the attempt belongs to.
The quiz the attempt belongs to.
The course the quiz belongs to.
The lesson the quiz sits in.
The product the attempt was made under.
QuizScore
InfoSectionReport
QuizDetailed
Number of questions in the quiz.
course_program). NULL when the quiz has no stored scoring record. All values are zero when the quiz scoring system is disabled.
count_tries.
QuizPlacement
quizable_type: the practice block or the lesson itself.
Chain of ancestor modules of the placement's lesson, nearest module first. Empty array β the lesson sits directly at the course root.
QuizPlacementDripping
Rewards (gamification)
FlowRewardLog
points β accrued or deducted. Mirrors the accrual_type filter on the list endpoint.
Why this points entry exists. Possible ids:
1— Quiz passed2— Quiz canceled3— Quiz attempt reset previous scores4— Quiz attempt set new scores5— Pay by points6— Manual7— Automation8— Quiz scores changing9— Pay by points for gift
event). NULL for manual entries.
quiz_attempt, order, gift). NULL for manual entries.
eventable_type. NULL for manual entries.
manual_comment is shown to the student in their own points journal. NULL for non-manual entries. Curators (this API included) always see the comment regardless of the flag.
The product the points were awarded under.
Who awarded the points. Empty when an automation did it.
Common
DateSetting
fixed, relative, recurring, etc.
after_months, when the date is set as a period. Empty when no period is used.
is_current_week.
Timezone
Enum
value in some places)
X-Language request header
FileSimple
UserSimple
CountrySimple
X-Language
TelegramAccount
username, as the key when matching accounts.
@ prefix. Not every Telegram account has a username set β null in that case. Do not rely on it being present; treat telegram_id as the stable identifier.
Envelope & Pagination
Success
true β an acknowledgement that the operation went through. Endpoints answering this way return no data.
AffectedSubscriptions
affected_subscriptions and affected_orders integer arrays. Empty object when no offers were passed in the request.
affected_by_offers. Empty object when no product_id was passed.
PaginationLinks
PaginationMeta
per_page.