/v1/campaigns/{id}/contact-listsRetrieve all contact lists belonging to a campaign.
Authentication:Header
X-Api-Key(scope:campaign). Kong dual-auth: prioritizeX-Tenant-IDheader, fallback query DB by Authorization key.
Base URL:Dev:
https://xapi-dev.alohub.vn| Prod:https://xapi.alohub.vn
Parameters | Location | Required | Type | Description | Example |
|---|---|---|---|---|---|
| path | Yes |
| campaignId | 1042 |
curl -X GET "https://xapi.alohub.vn/v1/campaigns/1042/contact-lists" \
-H "X-Api-Key: sk_live_xxx" \
-H "X-Tenant-ID: 20260310"const axios = require('axios');
const response = await axios.get(
'{{host}}/api/v1/campaigns/{{id}}/contact-lists',
{ headers: { 'Authorization': '{{api-key}}', 'X-Tenant-ID': '{{tenant-id}}', 'Content-Type': 'application/json' } }
);
console.log(response.data);import requests
headers = {"Authorization": "{{api-key}}", "X-Tenant-ID": "{{tenant-id}}", "Content-Type": "application/json"}
response = requests.get(
'{{host}}/api/v1/campaigns/{{id}}/contact-lists',
headers=headers
)
print(response.json()){
"success": "1",
"totalRecord": 2,
"data": [
{
"listId": 501,
"listName": "Danh sach thang 4",
"contactCount": 150
},
{
"listId": 502,
"listName": "Default List",
"contactCount": 30
}
]
}Field | Type | Description |
|---|---|---|
|
| "1" = success |
|
| Total number of lists |
|
| Contact list ID — used as {listId} |
|
| List name |
|
| Number of contacts in the list |
HTTP | error_code | Description | Frontend handling |
|---|---|---|---|
401 | UNAUTHORIZED | Missing or incorrect API key | Redirect to re-enter key |
403 | INSUFFICIENT_SCOPE | Key does not have scope | Show message |
404 | NOT_FOUND | Not found | Show message |
429 | RATE_LIMIT_EXCEEDED | Exceeded request limit | Retry after Retry-After seconds |
500 | FAIL | System error | General error toast |
Header | Description |
|---|---|
| Limit tenant/10s |
| Remaining tenant/10s |
| Limit route/10s |
| Remaining route/10s |
| Seconds to wait when receiving 429 |
/v1/campaigns/{id}/contact-lists/{listId}/contactsRetrieve the list of contacts in a contact list — with pagination.
Authentication:Header
X-Api-Key(scope:campaign). Kong dual-auth: prioritizeX-Tenant-IDheader, fallback query DB by Authorization key.
Base URL:Dev:
https://xapi-dev.alohub.vn| Prod:https://xapi.alohub.vn
Parameters | Location | Required | Type | Description | Example |
|---|---|---|---|---|---|
| path | Yes |
| campaignId | 1042 |
| path | Yes |
| listId (from GET /contact-lists) | 501 |
| query | No |
| Page (default: 0) | 0 |
| query | No |
| Number of records per page (default: 20) | 20 |
curl -X GET "https://xapi.alohub.vn/v1/campaigns/1042/contact-lists/501/contacts?page=0&size=20" \
-H "X-Api-Key: sk_live_xxx" \
-H "X-Tenant-ID: 20260310"const axios = require('axios');
const response = await axios.get(
'{{host}}/api/v1/campaigns/{{id}}/contact-lists/{{listId}}/contacts?page=0&size=20',
{ headers: { 'Authorization': '{{api-key}}', 'X-Tenant-ID': '{{tenant-id}}', 'Content-Type': 'application/json' } }
);
console.log(response.data);import requests
params = {"page": "0", "size": "20"}
headers = {"Authorization": "{{api-key}}", "X-Tenant-ID": "{{tenant-id}}", "Content-Type": "application/json"}
response = requests.get(
'{{host}}/api/v1/campaigns/{{id}}/contact-lists/{{listId}}/contacts',
headers=headers, params=params
)
print(response.json()){
"success": "1",
"totalRecord": 150,
"data": [
{
"contactId": 88001,
"customerId": 55001,
"phoneNumber": "0901234567",
"name": "Nguyen Van A",
"processStatus": 0,
"callStatus": null
}
]
}Field | Type | Description | |
|---|---|---|---|
|
| "1" = success | |
|
| Total number of contacts | |
|
| Contact ID — used as {contactId} when deleting | |
|
| Customer ID | |
|
| Phone number | |
|
| null | Customer name |
|
| 0=pending, 1=in_call, 2=processed | |
|
| null | Call status |
HTTP | error_code | Description | Frontend handling |
|---|---|---|---|
401 | UNAUTHORIZED | Missing or incorrect API key | Redirect to re-enter key |
403 | INSUFFICIENT_SCOPE | Key does not have scope | Show message |
404 | NOT_FOUND | Not found | Show message |
429 | RATE_LIMIT_EXCEEDED | Exceeded request limit | Retry after Retry-After seconds |
500 | FAIL | System error | General error toast |
Header | Description |
|---|---|
| Limit tenant/10s |
| Remaining tenant/10s |
| Limit route/10s |
| Remaining route/10s |
| Seconds to wait when receiving 429 |
/v1/campaigns/{id}/contactsAdd a contact to the campaign. Do not pass listId → use the first list. If there is no list → create a Default List.
Authentication:Header
X-Api-Key(scope:campaign). Kong dual-auth: prioritizeX-Tenant-IDheader, fallback query DB by Authorization key.
Base URL:Dev:
https://xapi-dev.alohub.vn| Prod:https://xapi.alohub.vn
Parameters | Location | Required | Type | Description | Example |
|---|---|---|---|---|---|
| path | Yes |
| campaignId | 1042 |
{
"phoneNumber": "0901234567",
"name": "Nguyen Van A",
"listId": 501
}Field | Required | Description |
|---|---|---|
| Yes | Phone number — required |
| No | Customer name |
| No | Target list ID. Skip = use the first list / create Default List |
curl -X POST "https://xapi.alohub.vn/v1/campaigns/1042/contacts" \
-H "X-Api-Key: sk_live_xxx" \
-H "X-Tenant-ID: 20260310" \
-H "Content-Type: application/json" \
-d '{"phoneNumber":"0901234567","name":"KH A"}'
# Truyền listId cụ thể
curl -X POST "https://xapi.alohub.vn/v1/campaigns/1042/contacts" \
-H "X-Api-Key: sk_live_xxx" \
-H "X-Tenant-ID: 20260310" \
-H "Content-Type: application/json" \
-d '{"phoneNumber":"0901234567","listId":501}'const axios = require('axios');
const response = await axios.post(
'{{host}}/api/v1/campaigns/{{id}}/contacts',
{
"phoneNumber": "0901234567",
"name": "Nguyen Van A",
"listId": 501
},
{ headers: { 'Authorization': '{{api-key}}', 'X-Tenant-ID': '{{tenant-id}}', 'Content-Type': 'application/json' } }
);
console.log(response.data);import requests
headers = {"Authorization": "{{api-key}}", "X-Tenant-ID": "{{tenant-id}}", "Content-Type": "application/json"}
payload = {
"phoneNumber": "0901234567",
"name": "Nguyen Van A",
"listId": 501
}
response = requests.post(
'{{host}}/api/v1/campaigns/{{id}}/contacts',
json=payload,
headers=headers
)
print(response.json()){
"success": "1",
"error_code": "SUCCESS",
"error_message": "Contact added to list 501"
}Field | Type | Description |
|---|---|---|
|
| "1" = success |
|
| SUCCESS |
|
| "Contact added to list {listId}" |
HTTP | error_code | Description | Frontend handling |
|---|---|---|---|
401 | UNAUTHORIZED | Missing or incorrect API key | Redirect to re-enter key |
403 | INSUFFICIENT_SCOPE | Key does not have scope | Show message |
400 | INVALID_INPUT | Invalid input | Show specific error |
404 | NOT_FOUND | Not found | Show message |
429 | RATE_LIMIT_EXCEEDED | Exceeded request limit | Retry after Retry-After seconds |
500 | FAIL | System error | General error toast |
Header | Description |
|---|---|
| Limit tenant/10s |
| Remaining tenant/10s |
| Limit route/10s |
| Remaining route/10s |
| Seconds to wait when receiving 429 |
/v1/campaigns/{id}/contacts/bulkImport bulk contacts (async). Return job_id immediately — the frontend must poll GET /contacts/import/{job_id}. Contacts with duplicate phone numbers are skipped (count duplicateRows, not an error).
Authentication:Header
X-Api-Key(scope:campaign). Kong dual-auth: prioritizeX-Tenant-IDheader, fallback query DB by Authorization key.
Base URL:Dev:
https://xapi-dev.alohub.vn| Prod:https://xapi.alohub.vn
Parameters | Location | Required | Type | Description | Example |
|---|---|---|---|---|---|
| path | Yes |
| campaignId | 1042 |
{
"listName": "Danh sach thang 4",
"contacts": [
{
"phoneNumber": "0901000001",
"name": "KH 1"
},
{
"phoneNumber": "0901000002"
}
]
}Field | Required | Description |
|---|---|---|
| No | List name. Skip = auto-generate name |
| Yes | Array of contacts — at least 1 item with phoneNumber |
| Yes | Phone number |
| No | Customer name |
curl -X POST "https://xapi.alohub.vn/v1/campaigns/1042/contacts/bulk" \
-H "X-Api-Key: sk_live_xxx" \
-H "X-Tenant-ID: 20260310" \
-H "Content-Type: application/json" \
-d '{"listName":"DS thang 4","contacts":[{"phoneNumber":"0901000001"},{"phoneNumber":"0901000002"}]}'const axios = require('axios');
const response = await axios.post(
'{{host}}/api/v1/campaigns/{{id}}/contacts/bulk',
{
"listName": "Danh sach thang 4",
"contacts": [
{
"phoneNumber": "0901000001",
"name": "KH 1"
},
{
"phoneNumber": "0901000002"
}
]
},
{ headers: { 'Authorization': '{{api-key}}', 'X-Tenant-ID': '{{tenant-id}}', 'Content-Type': 'application/json' } }
);
console.log(response.data);import requests
headers = {"Authorization": "{{api-key}}", "X-Tenant-ID": "{{tenant-id}}", "Content-Type": "application/json"}
payload = {
"listName": "Danh sach thang 4",
"contacts": [
{
"phoneNumber": "0901000001",
"name": "KH 1"
},
{
"phoneNumber": "0901000002"
}
]
}
response = requests.post(
'{{host}}/api/v1/campaigns/{{id}}/contacts/bulk',
json=payload,
headers=headers
)
print(response.json()){
"success": "1",
"error_code": "SUCCESS",
"error_message": "Bulk import started",
"job_id": 5,
"total_rows": 2
}Field | Type | Description |
|---|---|---|
|
| "1" = success |
|
| SUCCESS |
|
| "Bulk import started" |
|
| Job ID — used to poll status |
|
| Total number of rows received |
HTTP | error_code | Description | Frontend handling |
|---|---|---|---|
401 | UNAUTHORIZED | Missing or incorrect API key | Redirect to re-enter key |
403 | INSUFFICIENT_SCOPE | Key does not have scope | Show message |
400 | INVALID_INPUT | Invalid input | Show specific error |
404 | NOT_FOUND | Not found | Show message |
429 | RATE_LIMIT_EXCEEDED | Exceeded request limit | Retry after Retry-After seconds |
500 | FAIL | System error | General error toast |
Header | Description |
|---|---|
| Limit tenant/10s |
| Remaining tenant/10s |
| Limit route/10s |
| Remaining route/10s |
| Seconds to wait when receiving 429 |
/v1/campaigns/{id}/contacts/import/{job_id}Poll the status of the bulk import job. The frontend polls every 2-3 seconds until status = done or failed. job_id exists in RAM — server restart will lose it.
Authentication:Header
X-Api-Key(scope:campaign). Kong dual-auth: prioritizeX-Tenant-IDheader, fallback query DB by Authorization key.
Base URL:Dev:
https://xapi-dev.alohub.vn| Prod:https://xapi.alohub.vn
Parameters | Location | Required | Type | Description | Example |
|---|---|---|---|---|---|
| path | Yes |
| campaignId | 1042 |
| path | Yes |
| job_id received from POST /contacts/bulk | 5 |
# Poll mỗi 2-3 giây
curl -X GET "https://xapi.alohub.vn/v1/campaigns/1042/contacts/import/5" \
-H "X-Api-Key: sk_live_xxx" \
-H "X-Tenant-ID: 20260310"const axios = require('axios');
const response = await axios.get(
'{{host}}/api/v1/campaigns/{{id}}/contacts/import/{{job_id}}',
{ headers: { 'Authorization': '{{api-key}}', 'X-Tenant-ID': '{{tenant-id}}', 'Content-Type': 'application/json' } }
);
console.log(response.data);import requests
headers = {"Authorization": "{{api-key}}", "X-Tenant-ID": "{{tenant-id}}", "Content-Type": "application/json"}
response = requests.get(
'{{host}}/api/v1/campaigns/{{id}}/contacts/import/{{job_id}}',
headers=headers
)
print(response.json()){
"jobId": 5,
"status": "processing",
"totalRows": 100,
"processedRows": 42,
"failedRows": 0,
"duplicateRows": 1
}Field | Type | Description | |
|---|---|---|---|
|
| Job ID | |
|
| pending / processing / done / failed | |
|
| Total number of rows | |
|
| Number of processed rows | |
|
| Number of error rows | |
|
| Number of duplicate rows skipped | |
|
| null | Error description when status=failed |
HTTP | error_code | Description | Frontend handling |
|---|---|---|---|
401 | UNAUTHORIZED | Missing or incorrect API key | Redirect to re-enter key |
403 | INSUFFICIENT_SCOPE | Key does not have scope | Show message |
404 | NOT_FOUND | Not found | Show message |
429 | RATE_LIMIT_EXCEEDED | Exceeded request limit | Retry after Retry-After seconds |
500 | FAIL | System error | General error toast |
Header | Description |
|---|---|
| Limit tenant/10s |
| Remaining tenant/10s |
| Limit route/10s |
| Remaining route/10s |
| Seconds to wait when receiving 429 |
/v1/campaigns/{id}/contacts/{contactId}Delete a contact from the campaign. Can only delete contacts with processStatus=0 (pending). Contacts that are calling (1) or have been processed (2) cannot be deleted.
Authentication:Header
X-Api-Key(scope:campaign). Kong dual-auth: prioritizeX-Tenant-IDheader, fallback query DB by Authorization key.
Base URL:Dev:
https://xapi-dev.alohub.vn| Prod:https://xapi.alohub.vn
Parameters | Location | Required | Type | Description | Example |
|---|---|---|---|---|---|
| path | Yes |
| campaignId | 1042 |
| path | Yes |
| contactId (from GET /contacts — field contactId) | 88001 |
curl -X DELETE "https://xapi.alohub.vn/v1/campaigns/1042/contacts/88001" \
-H "X-Api-Key: sk_live_xxx" \
-H "X-Tenant-ID: 20260310"const axios = require('axios');
const response = await axios.delete(
'{{host}}/api/v1/campaigns/{{id}}/contacts/{{contactId}}',
{ headers: { 'Authorization': '{{api-key}}', 'X-Tenant-ID': '{{tenant-id}}', 'Content-Type': 'application/json' } }
);
console.log(response.data);import requests
headers = {"Authorization": "{{api-key}}", "X-Tenant-ID": "{{tenant-id}}", "Content-Type": "application/json"}
response = requests.delete(
'{{host}}/api/v1/campaigns/{{id}}/contacts/{{contactId}}',
headers=headers
)
print(response.json()){
"success": "1",
"error_code": "SUCCESS",
"error_message": "Contact deleted successfully"
}Field | Type | Description |
|---|---|---|
|
| "1" = success |
|
| SUCCESS |
|
| "Contact deleted successfully" |
HTTP | error_code | Description | Frontend handling |
|---|---|---|---|
401 | UNAUTHORIZED | Missing or incorrect API key | Redirect to re-enter key |
403 | INSUFFICIENT_SCOPE | Key does not have scope | Show message |
404 | NOT_FOUND | Not found | Show message |
400 | INVALID_STATE | Invalid state (see state machine) | Disable button according to state |
429 | RATE_LIMIT_EXCEEDED | Exceeded request limit | Retry after Retry-After seconds |
500 | FAIL | System error | General error toast |
Header | Description |
|---|---|
| Limit tenant/10s |
| Remaining tenant/10s |
| Limit route/10s |
| Remaining route/10s |
| Seconds to wait when receiving 429 |