Chiến dịchManage Contacts

Manage Contacts

Nhữ Hào Nam·6/2/2026

GET /v1/campaigns/{id}/contact-lists

Retrieve all contact lists belonging to a campaign.

Authentication:Header X-Api-Key(scope: campaign). Kong dual-auth: prioritize X-Tenant-IDheader, fallback 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

Request Body

Sample Code

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

Response 200

{
  "success": "1",
  "totalRecord": 2,
  "data": [
    {
      "listId": 501,
      "listName": "Danh sach thang 4",
      "contactCount": 150
    },
    {
      "listId": 502,
      "listName": "Default List",
      "contactCount": 30
    }
  ]
}

Response Fields

Field

Type

Description

success

string

"1" = success

totalRecord

number

Total number of lists

data[].listId

number

Contact list ID — used as {listId}

data[].listName

string

List name

data[].contactCount

number

Number of contacts in the list

Error Codes

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 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}/contact-lists/{listId}/contacts

Retrieve the list of contacts in a contact list — with pagination.

Authentication:Header X-Api-Key(scope: campaign). Kong dual-auth: prioritize X-Tenant-IDheader, fallback 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

{listId}

path

Yes

number

listId (from GET /contact-lists)

501

page

query

No

number

Page (default: 0)

0

size

query

No

number

Number of records per page (default: 20)

20

Request Body

Sample Code

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

Response 200

{
  "success": "1",
  "totalRecord": 150,
  "data": [
    {
      "contactId": 88001,
      "customerId": 55001,
      "phoneNumber": "0901234567",
      "name": "Nguyen Van A",
      "processStatus": 0,
      "callStatus": null
    }
  ]
}

Response Fields

Field

Type

Description

success

string

"1" = success

totalRecord

number

Total number of contacts

data[].contactId

number

Contact ID — used as {contactId} when deleting

data[].customerId

number

Customer ID

data[].phoneNumber

string

Phone number

data[].name

string

null

Customer name

data[].processStatus

number

0=pending, 1=in_call, 2=processed

data[].callStatus

string

null

Call status

Error Codes

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


POST /v1/campaigns/{id}/contacts

Add 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: prioritize X-Tenant-IDheader, fallback 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

Request Body

{
  "phoneNumber": "0901234567",
  "name": "Nguyen Van A",
  "listId": 501
}

Field

Required

Description

phoneNumber

Yes

Phone number — required

name

No

Customer name

listId

No

Target list ID. Skip = use the first list / create Default List

Sample Code

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

Response 200

{
  "success": "1",
  "error_code": "SUCCESS",
  "error_message": "Contact added to list 501"
}

Response Fields

Field

Type

Description

success

string

"1" = success

error_code

string

SUCCESS

error_message

string

"Contact added to list {listId}"

Error Codes

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 campaign

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

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


POST /v1/campaigns/{id}/contacts/bulk

Import 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: prioritize X-Tenant-IDheader, fallback 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

Request Body

{
  "listName": "Danh sach thang 4",
  "contacts": [
    {
      "phoneNumber": "0901000001",
      "name": "KH 1"
    },
    {
      "phoneNumber": "0901000002"
    }
  ]
}

Field

Required

Description

listName

No

List name. Skip = auto-generate name

contacts

Yes

Array of contacts — at least 1 item with phoneNumber

contacts[].phoneNumber

Yes

Phone number

contacts[].name

No

Customer name

Sample Code

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

Response 200

{
  "success": "1",
  "error_code": "SUCCESS",
  "error_message": "Bulk import started",
  "job_id": 5,
  "total_rows": 2
}

Response Fields

Field

Type

Description

success

string

"1" = success

error_code

string

SUCCESS

error_message

string

"Bulk import started"

job_id

number

Job ID — used to poll status

total_rows

number

Total number of rows received

Error Codes

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 campaign

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

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}/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: prioritize X-Tenant-IDheader, fallback 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

{job_id}

path

Yes

number

job_id received from POST /contacts/bulk

5

Request Body

Sample Code

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

Response 200

{
  "jobId": 5,
  "status": "processing",
  "totalRows": 100,
  "processedRows": 42,
  "failedRows": 0,
  "duplicateRows": 1
}

Response Fields

Field

Type

Description

jobId

number

Job ID

status

string

pending / processing / done / failed

totalRows

number

Total number of rows

processedRows

number

Number of processed rows

failedRows

number

Number of error rows

duplicateRows

number

Number of duplicate rows skipped

errorMessage

string

null

Error description when status=failed

Error Codes

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


DELETE /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: prioritize X-Tenant-IDheader, fallback 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

{contactId}

path

Yes

number

contactId (from GET /contacts — field contactId)

88001

Request Body

Sample Code

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

Response 200

{
  "success": "1",
  "error_code": "SUCCESS",
  "error_message": "Contact deleted successfully"
}

Response Fields

Field

Type

Description

success

string

"1" = success

error_code

string

SUCCESS

error_message

string

"Contact deleted successfully"

Error Codes

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 campaign

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

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