Chiến dịchCampaign Management

Campaign Management

Tạ Quốc Thắng·6/2/2026

API Campaign

General authentication:Header X-Api-Key(scope: campaign) required for all endpoints. Kong dual-auth: prioritize X-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


GET /v1/campaigns

Retrieve 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: prioritize X-Tenant-IDheader, fallback to query DB by Authorization key.

Base URL:Dev: https://xapi-dev.alohub.vn|  Prod: https://xapi.alohub.vn

Parameters

Parameters

Location

Required

Type

Description

Example

userName

query

Yes

string

Logged in username

AloHub

status

query

No

number

Filter: 1=inprocess, 2=running, 3=paused, 4=stopped

2

type

query

No

string

Campaign type filter (case-sensitive)

CAMPAIGN_CALL_AUTO

page

query

No

number

Page (default: 0)

0

limit

query

No

number

Number of records per page (default: 20)

20

Sample Code

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())

Response 200

{
  "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"
    }
  ]
}

Response Fields

Field

Type

Description

success

string

"1" = success

totalRecord

number

Total number of campaigns

data[].campaignId

number

Campaign ID — used as {id}

data[].campaignName

string

Campaign name

data[].campaignType

string

Campaign type

data[].status

number

1=inprocess, 2=running, 3=paused, 4=stopped

data[].statusLabel

string

inprocess / running / paused / stopped

data[].totalContacts

number

Total number of contacts

data[].processedContacts

number

Number of contacts processed

data[].startTime

string

Start time (ISO 8601)

data[].endTime

string

End time (ISO 8601)

data[].createBy

string

Creator username

data[].createTime

string

Creation time

data[].updateTime

string

Last updated time

Error Codes

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 campaign

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

Rate Limit Headers

Header

Description

X-RateLimit-Limit-Tenant

Limit tenant/10s

X-RateLimit-Remaining-Tenant

Remaining tenant/10s

X-RateLimit-Limit-Route

Limit route/10s

X-RateLimit-Remaining-Route

Remaining route/10s

Retry-After

Seconds to wait when receiving 429


GET /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: prioritize X-Tenant-IDheader, fallback to query DB by Authorization key.

Base URL:Dev: https://xapi-dev.alohub.vn|  Prod: https://xapi.alohub.vn

Parameters

Parameters

Location

Required

Type

Description

Example

{id}

path

Yes

number

campaignId (obtained from GET /v1/campaigns)

1042

userName

query

Yes

string

Logged in username

AloHub

Sample Code

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())

Response 200

{
  "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
}

Response Fields

Field

Type

Description

campaignId

number

Campaign ID

campaignName

string

Campaign name

campaignType

string

Campaign type

status

number

1=inprocess, 2=running, 3=paused, 4=stopped

statusLabel

string

Text label

totalContacts

number

Total number of contacts

processedContacts

number

Number of contacts processed

startTime

string

Start time

endTime

string

End time

concurrentCall

number

Number of concurrent calls

maxRecall

number

Maximum recall count

callNumber

string

Outbound phone number

webhookUrl

string

Webhook URL

createBy

string

Creator username

createTime

string

Creation time

updateTime

string

Update time

tenantId

number

Tenant ID

Error Codes

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 campaign

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

Rate Limit Headers

Header

Description

X-RateLimit-Limit-Tenant

Limit tenant/10s

X-RateLimit-Remaining-Tenant

Remaining tenant/10s

X-RateLimit-Limit-Route

Limit route/10s

X-RateLimit-Remaining-Route

Remaining route/10s

Retry-After

Seconds to wait when receiving 429


PUT /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: prioritize X-Tenant-IDheader, fallback to query DB by Authorization key.

Base URL:Dev: https://xapi-dev.alohub.vn|  Prod: https://xapi.alohub.vn

Parameters

Parameters

Location

Required

Type

Description

Example

{id}

path

Yes

number

campaignId

1042

userName

query

Yes

string

Logged in username

AloHub

Request Body

{
  "action": "start"
}

Field

Required

Description

action

Yes

"start"

"stop"

"pause" — other values → INVALID_INPUT

Sample Code

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())

Response 200

{
  "success": "1",
  "error_code": "SUCCESS",
  "error_message": "Campaign started successfully. New status: running"
}

Response Fields

Field

Type

Description

success

string

"1" = success

error_code

string

SUCCESS when control is successful

error_message

string

Description of the result and new status

Error Codes

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 campaign

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

Rate Limit Headers

Header

Description

X-RateLimit-Limit-Tenant

Limit tenant/10s

X-RateLimit-Remaining-Tenant

Remaining tenant/10s

X-RateLimit-Limit-Route

Limit route/10s

X-RateLimit-Remaining-Route

Remaining route/10s

Retry-After

Seconds to wait when receiving 429


GET /v1/campaigns/{campaignId}/results

Retrieve 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.

Path Parameters

Parameters

Required

Type

Description

Example

campaignId

Yes

number (integer)

Campaign ID to retrieve results

502

Request Body

None. Method GETdoes not use body.

Sample Code

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())

Response 200 — Direct Object (no envelope)

{
  "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
}

Response Fields

Field

Type

Description

campaignId

number

Campaign ID

campaignCode

string | null

Custom code (may be null)

campaignName

string

Campaign name

status

number

Status: 0=Draft, 1=Pending, 2=Running, 3=Paused, 4=Completed, 5=Cancelled

campaignType

string

Campaign type: CAMPAIGN_SMS_NORMAL, CAMPAIGN_VOICE, CAMPAIGN_ZNS, ...

totalCustomer

number

Total number of customers in the campaign

processedCustomer

number

Number of customers processed

successCall

number

Number of successful calls (only voice campaigns)

failCall

number

Number of failed calls (only voice campaigns)

actualCost

number

Actual cost (VND)

refundedAmount

number

Refunded amount (VND)

znsSuccessCount

number

Number of ZNS sent successfully (only ZNS campaigns)

Response 404 — When error occurs (with envelope)

{
  "success": "0",
  "error_code": "NOT_FOUND",
  "error_message": "Campaign not found or access denied",
  "totalRecord": 0,
  "data": []
}

Error Codes

HTTP

error_code

Description

FE handling

401

UNAUTHORIZED

Missing or incorrect X-Api-Key

Redirect to re-enter key

403

INSUFFICIENT_SCOPE

Key does not have scope campaign

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

campaignIdnot a number

Validate before calling API

429

RATE_LIMIT_EXCEEDED

Exceeded request limit

Retry after Retry-Afterseconds

500

FAIL

System error

General error toast


GET /v1/campaigns/{campaignId}/results/export

Export 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 is application/jsonwith standard envelope.

Timeout:This route has read_timeout/ write_timeout= 120s (double the default) because export can be slow with large campaigns.

Path Parameters

Parameters

Required

Type

Description

Example

campaignId

Yes

number (long)

Campaign ID to export

502

Request Body

None. Just need campaignIdin the URL.

Sample Code

# 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())

CSV Columns

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 to CampaignResults_{{campaignId}}.csv.

Error Codes

HTTP

error_code

Description

FE handling

401

UNAUTHORIZED

Missing or incorrect X-Api-Key

Redirect to re-enter key

403

INSUFFICIENT_SCOPE

Key does not have scope campaign

Contact admin message

404

NOT_FOUND

Campaign does not exist or no permission

Error message

429

RATE_LIMIT_EXCEEDED

Exceeded request limit

Retry after Retry-Afterseconds

500

FAIL

System error

General error toast

Rate Limit Headers

Header

Description

X-RateLimit-Limit-Tenant

Limit tenant/10s

X-RateLimit-Remaining-Tenant

Remaining tenant/10s

X-RateLimit-Limit-Route

Limit route/10s

X-RateLimit-Remaining-Route

Remaining route/10s

Retry-After

Seconds to wait when receiving 429


Related Articles

Was this article helpful?
Updated: 6/2/2026
để chuyển bài