General authentication:Header
X-Api-Key(scope:campaign) required for all endpoints. Kong dual-auth: prioritizeX-Tenant-IDheader, fallback to query DB by Authorization key. Do not use JWT (x-access-token).
Base URL:Dev:
https://xapi-dev.alohub.vn| Prod:https://xapi.alohub.vn
/v1/campaignsRetrieve the list of campaigns for the tenant. Supports filtering by status, campaign type, and pagination.
Authentication:Header
X-Api-Key(scope:campaign). Kong dual-auth: prioritizeX-Tenant-IDheader, fallback to query DB by Authorization key.
Base URL:Dev:
https://xapi-dev.alohub.vn| Prod:https://xapi.alohub.vn
Parameters | Location | Required | Type | Description | Example |
|---|---|---|---|---|---|
| query | Yes |
| Logged in username | AloHub |
| query | No |
| Filter: 1=inprocess, 2=running, 3=paused, 4=stopped | 2 |
| query | No |
| Campaign type filter (case-sensitive) | CAMPAIGN_CALL_AUTO |
| 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?userName=AloHub" \
-H "X-Api-Key: sk_live_xxx"
# Filter đang chạy
curl -X GET "https://xapi.alohub.vn/v1/campaigns?userName=AloHub&status=2&page=0&limit=20" \
-H "X-Api-Key: sk_live_xxx"const axios = require('axios');
const response = await axios.get(
'{{host}}/api/v1/campaigns?userName=AloHub&status=2&type=CAMPAIGN_CALL_AUTO&page=0&limit=20',
{ headers: { 'Authorization': '{{api-key}}', 'X-Tenant-ID': '{{tenant-id}}', 'Content-Type': 'application/json' } }
);
console.log(response.data);import requests
params = {"userName": "AloHub", "status": "2", "type": "CAMPAIGN_CALL_AUTO", "page": "0", "limit": "20"}
headers = {"Authorization": "{{api-key}}", "X-Tenant-ID": "{{tenant-id}}", "Content-Type": "application/json"}
response = requests.get(
'{{host}}/api/v1/campaigns',
headers=headers, params=params
)
print(response.json())
{
"success": "1",
"error_code": "SUCCESS",
"error_message": "SUCCESS",
"totalRecord": 25,
"data": [
{
"campaignId": 1042,
"campaignName": "Chiến dịch tháng 4",
"campaignType": "CAMPAIGN_CALL_AUTO",
"status": 2,
"statusLabel": "running",
"totalContacts": 500,
"processedContacts": 123,
"startTime": "2026-04-01T08:00:00",
"endTime": "2026-04-30T20:00:00",
"createBy": "admin",
"createTime": "2026-03-28T10:00:00",
"updateTime": "2026-04-01T08:00:05"
}
]
}
Field | Type | Description |
|---|---|---|
|
| "1" = success |
|
| Total number of campaigns |
|
| Campaign ID — used as {id} |
|
| Campaign name |
|
| Campaign type |
|
| 1=inprocess, 2=running, 3=paused, 4=stopped |
|
| inprocess / running / paused / stopped |
|
| Total number of contacts |
|
| Number of contacts processed |
|
| Start time (ISO 8601) |
|
| End time (ISO 8601) |
|
| Creator username |
|
| Creation time |
|
| Last updated time |
HTTP | error_code | Description | FE 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}Retrieve full details of a campaign including concurrent call configuration, max recall, webhook URL.
Authentication:Header
X-Api-Key(scope:campaign). Kong dual-auth: prioritizeX-Tenant-IDheader, fallback to 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 (obtained from GET /v1/campaigns) | 1042 |
| query | Yes |
| Logged in username | AloHub |
curl -X GET "https://xapi.alohub.vn/v1/campaigns/1042?userName=AloHub" \
-H "X-Api-Key: sk_live_xxx"const axios = require('axios');
const response = await axios.get(
'{{host}}/api/v1/campaigns/{{id}}?userName=AloHub',
{ headers: { 'Authorization': '{{api-key}}', 'X-Tenant-ID': '{{tenant-id}}', 'Content-Type': 'application/json' } }
);
console.log(response.data);import requests
params = {"userName": "AloHub"}
headers = {"Authorization": "{{api-key}}", "X-Tenant-ID": "{{tenant-id}}", "Content-Type": "application/json"}
response = requests.get(
'{{host}}/api/v1/campaigns/{{id}}',
headers=headers, params=params
)
print(response.json())
{
"campaignId": 1042,
"campaignName": "Chiến dịch tháng 4",
"campaignType": "CAMPAIGN_CALL_AUTO",
"status": 2,
"statusLabel": "running",
"totalContacts": 500,
"processedContacts": 123,
"startTime": "2026-04-01T08:00:00",
"endTime": "2026-04-30T20:00:00",
"concurrentCall": 10,
"maxRecall": 3,
"callNumber": "0287661234",
"webhookUrl": "https://example.com/webhook",
"createBy": "admin",
"createTime": "2026-03-28T10:00:00",
"updateTime": "2026-04-01T08:00:05",
"tenantId": 20263010
}
Field | Type | Description |
|---|---|---|
|
| Campaign ID |
|
| Campaign name |
|
| Campaign type |
|
| 1=inprocess, 2=running, 3=paused, 4=stopped |
|
| Text label |
|
| Total number of contacts |
|
| Number of contacts processed |
|
| Start time |
|
| End time |
|
| Number of concurrent calls |
|
| Maximum recall count |
|
| Outbound phone number |
|
| Webhook URL |
|
| Creator username |
|
| Creation time |
|
| Update time |
|
| Tenant ID |
HTTP | error_code | Description | FE 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}Control campaign status: start / pause / stop. Change status according to state machine: start(1→2, 3→2), pause(2→3), stop(2→4, 3→4).
Authentication:Header
X-Api-Key(scope:campaign). Kong dual-auth: prioritizeX-Tenant-IDheader, fallback to 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 |
| query | Yes |
| Logged in username | AloHub |
{
"action": "start"
}
Field | Required | Description | ||
|---|---|---|---|---|
| Yes | "start" | "stop" | "pause" — other values → INVALID_INPUT |
curl -X PUT "https://xapi.alohub.vn/v1/campaigns/1042?userName=AloHub" \
-H "X-Api-Key: sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"action":"start"}'
# Pause
curl -X PUT "https://xapi.alohub.vn/v1/campaigns/1042?userName=AloHub" \
-H "X-Api-Key: sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"action":"pause"}'
# Stop
curl -X PUT "https://xapi.alohub.vn/v1/campaigns/1042?userName=AloHub" \
-H "X-Api-Key: sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"action":"stop"}'const axios = require('axios');
const response = await axios.put(
'{{host}}/api/v1/campaigns/{{id}}?userName=AloHub',
{
"action": "start"
},
{ headers: { 'Authorization': '{{api-key}}', 'X-Tenant-ID': '{{tenant-id}}', 'Content-Type': 'application/json' } }
);
console.log(response.data);import requests
params = {"userName": "AloHub"}
headers = {"Authorization": "{{api-key}}", "X-Tenant-ID": "{{tenant-id}}", "Content-Type": "application/json"}
payload = {
"action": "start"
}
response = requests.put(
'{{host}}/api/v1/campaigns/{{id}}',
json=payload,
headers=headers, params=params
)
print(response.json())
{
"success": "1",
"error_code": "SUCCESS",
"error_message": "Campaign started successfully. New status: running"
}
Field | Type | Description |
|---|---|---|
|
| "1" = success |
|
| SUCCESS when control is successful |
|
| Description of the result and new status |
HTTP | error_code | Description | FE 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 |
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 |
/v1/campaigns/{campaignId}/resultsRetrieve execution result statistics of a campaign — number of customers processed, number of successful/failed calls, actual cost, etc.
Note response:This endpoint returns data object directlyon success (no envelope
{{success, data, ...}}). Errors have an envelope. FE parses according to HTTP status code: 200 → direct object, 4xx/5xx → parse envelope.
Parameters | Required | Type | Description | Example |
|---|---|---|---|---|
| Yes | number (integer) | Campaign ID to retrieve results |
|
None. Method GETdoes not use body.
curl -X GET "https://xapi.alohub.vn/v1/campaigns/502/results" \
-H "X-Api-Key: sk_live_xxx" \
-H "Accept: application/json" const axios = require('axios');
const response = await axios.get(
'{{host}}/api/v1/campaigns/{{campaignId}}/results',
{ headers: { 'X-Api-Key': '{{api-key}}' } }
);
// ⚠️ response.data là object trực tiếp, không có .data.data
console.log(response.data);import requests
response = requests.get(
'{{host}}/api/v1/campaigns/{{campaignId}}/results',
headers={"X-Api-Key": "{{api-key}}"}
)
# ⚠️ Khi 200: response.json() là object trực tiếp
# Khi 4xx: response.json() có envelope {success, error_code, ...}
print(response.json())
{
"campaignId": 502,
"campaignCode": null,
"campaignName": "loilv-test",
"status": 4,
"campaignType": "CAMPAIGN_SMS_NORMAL",
"totalCustomer": 4,
"processedCustomer": 0,
"successCall": 0,
"failCall": 0,
"actualCost": 0,
"refundedAmount": 0,
"znsSuccessCount": 0
}
Field | Type | Description |
|---|---|---|
| number | Campaign ID |
| string | null | Custom code (may be null) |
| string | Campaign name |
| number | Status: 0=Draft, 1=Pending, 2=Running, 3=Paused, 4=Completed, 5=Cancelled |
| string | Campaign type: |
| number | Total number of customers in the campaign |
| number | Number of customers processed |
| number | Number of successful calls (only voice campaigns) |
| number | Number of failed calls (only voice campaigns) |
| number | Actual cost (VND) |
| number | Refunded amount (VND) |
| number | Number of ZNS sent successfully (only ZNS campaigns) |
{
"success": "0",
"error_code": "NOT_FOUND",
"error_message": "Campaign not found or access denied",
"totalRecord": 0,
"data": []
}
HTTP | error_code | Description | FE handling |
|---|---|---|---|
401 | UNAUTHORIZED | Missing or incorrect | Redirect to re-enter key |
403 | INSUFFICIENT_SCOPE | Key does not have scope | Contact admin message |
404 | NOT_FOUND | Campaign does not exist or belongs to another tenant (combine 2 cases to avoid leak) | Message "Campaign not found" |
400 | INVALID_INPUT |
| Validate before calling API |
429 | RATE_LIMIT_EXCEEDED | Exceeded request limit | Retry after |
500 | FAIL | System error | General error toast |
/v1/campaigns/{campaignId}/results/exportExport all results of a campaign to a CSV file for download. Used for reporting, offline analysis, or importing into Excel / BI tools.
Response Type: Binary CSV file(
text/csv; charset=UTF-8), not JSON. On error, Content-Type isapplication/jsonwith standard envelope.
Timeout:This route has
read_timeout/write_timeout= 120s (double the default) because export can be slow with large campaigns.
Parameters | Required | Type | Description | Example |
|---|---|---|---|---|
| Yes | number (long) | Campaign ID to export |
|
None. Just need campaignIdin the URL.
# Export và lưu file
curl -X GET "https://xapi.alohub.vn/v1/campaigns/502/results/export" \
-H "X-Api-Key: sk_live_xxx" \
-o "CampaignResults_502.csv"
# Xem headers (lấy filename từ Content-Disposition)
curl -I "https://xapi.alohub.vn/v1/campaigns/502/results/export" \
-H "X-Api-Key: sk_live_xxx" const axios = require('axios');
const response = await axios.get(
'{{host}}/api/v1/campaigns/{{campaignId}}/results/export',
{
headers: { 'X-Api-Key': '{{api-key}}' },
responseType: 'blob' // ⭐ bắt buộc để nhận binary
}
);
// Lấy filename từ Content-Disposition
const cd = response.headers['content-disposition'] || '';
const filename = cd.match(/filename="?([^"]+)"?/)?.[1]
?? `CampaignResults_{{campaignId}}.csv`;
// Trigger download
const url = window.URL.createObjectURL(response.data);
const a = document.createElement('a');
a.href = url; a.download = filename; a.click();
window.URL.revokeObjectURL(url);import requests
response = requests.get(
'{{host}}/api/v1/campaigns/{{campaignId}}/results/export',
headers={"X-Api-Key": "{{api-key}}"},
stream=True
)
if response.headers.get('Content-Type', '').startswith('text/csv'):
with open('CampaignResults.csv', 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
else:
# Lỗi trả JSON
print(response.json())
Column | Type | Description |
|---|---|---|
Campaign ID | number | Campaign ID |
Campaign Code | string | Custom code (empty if none) |
Campaign Name | string | Campaign name |
Status | number | Status (0–5, see table in API above) |
Campaign Type | string | Campaign type |
Total Customer | number | Total number of customers |
Processed Customer | number | Number of customers processed |
Success Call | number | Number of successful calls (only voice campaigns) |
Fail Call | number | Number of failed calls (only voice campaigns) |
Actual Cost | number | Actual cost (VND) |
Refunded Amount | number | Refunded amount (VND) |
ZNS Success Count | number | Number of successful ZNS (only ZNS campaigns) |
CORS & Content-Disposition:FE can read header
Content-Dispositionto get filename because Kong has exposed it in CORS:Access-Control-Expose-Headers: Content-Disposition, Content-Type. If still undefined → fallback toCampaignResults_{{campaignId}}.csv.
HTTP | error_code | Description | FE handling |
|---|---|---|---|
401 | UNAUTHORIZED | Missing or incorrect | Redirect to re-enter key |
403 | INSUFFICIENT_SCOPE | Key does not have scope | Contact admin message |
404 | NOT_FOUND | Campaign does not exist or no permission | Error message |
429 | RATE_LIMIT_EXCEEDED | Exceeded request limit | Retry after |
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 |