Appearance
API Keys
🛠 Dành cho IT / Lập trình viên
Trang này hướng đến bộ phận IT hoặc lập trình viên trong doanh nghiệp của bạn — những người tích hợp CNV Work với hệ thống nội bộ (ERP, kế toán, e-commerce…). Nếu bạn chỉ dùng app bình thường, có thể bỏ qua mục này.
API Keys cho phép hệ thống bên ngoài (server, script, automation tool) gọi REST API của CNV Work mà không cần đăng nhập bằng tài khoản người dùng. Mỗi workspace có thể tạo nhiều key, mỗi key gán một bộ quyền (scope) riêng để hạn chế phạm vi truy cập.
Truy cập
Vào menu Cài đặt > API Keys trên thanh sidebar, hoặc truy cập trực tiếp https://hub.cnvwork.com/api-keys.
Lưu ý
Chỉ vai trò Owner và Admin mới có quyền tạo, xem và thu hồi API key. Member không nhìn thấy mục này trên sidebar.
Khi nào dùng API Keys
API Key phù hợp với các use case:
- Server-to-server: backend của bạn đồng bộ dữ liệu định kỳ với CNV Work (ETL, data warehouse)
- Automation tool: Zapier, Make.com, n8n gọi API để tạo lead khi có form mới
- Mobile/Desktop app nội bộ: app riêng của công ty đọc/ghi dữ liệu CNV Work
- Script một lần: import hàng loạt contact, migrate dữ liệu cũ
- Webhook handler: hệ thống bên ngoài nhận webhook xong gọi ngược API để bổ sung dữ liệu
Nếu bạn cần end-user đăng nhập rồi cấp quyền, hãy dùng OAuth (Mini App) thay vì API Key — xem Marketplace & Mini App.
Tổng quan luồng xác thực
Mỗi request đều được ghi vào audit log: thời gian, IP, endpoint, status code. Bạn xem lại được trong tab Audit log của từng key.
Tạo API Key
- Mở trang API Keys
- Nhấn + Tạo key
- Điền thông tin:
| Trường | Mô tả | Bắt buộc |
|---|---|---|
| Tên key | Tên dễ nhận biết (VD: "Zapier - Lead sync") | Có |
| Mô tả | Ghi chú mục đích sử dụng | Không |
| Scope | Phạm vi quyền (xem bảng bên dưới) | Có |
| IP whitelist | Danh sách IP được phép gọi (mỗi dòng 1 IP/CIDR) | Không |
| Ngày hết hạn | Tự động thu hồi sau ngày này | Không |
- Nhấn Tạo
- Copy key ngay lập tức — key chỉ hiển thị duy nhất 1 lần ở dạng đầy đủ
Quan trọng
Sau khi đóng dialog, hệ thống chỉ lưu hash của key — bạn không bao giờ xem lại được key gốc. Nếu lỡ đóng dialog trước khi copy, hãy Thu hồi key cũ và tạo key mới.
Định dạng key
Key có dạng: cnvw_<env>_<random_32_chars>
| Phần | Ý nghĩa |
|---|---|
cnvw_ | Prefix nhận diện CNV Work |
live hoặc test | Môi trường |
| 32 ký tự ngẫu nhiên | Phần bí mật |
Ví dụ: cnvw_live_4nP9kQ2xW7vB8mZ3hR5tY1jL6dC0sFgA
Scope (phạm vi quyền)
Mỗi key được gán một hoặc nhiều scope. Có 3 cấp độ:
1. Cấp độ workspace
| Scope | Mô tả |
|---|---|
read-only | Chỉ GET, không thay đổi dữ liệu |
read-write | GET + POST + PATCH + DELETE toàn bộ module |
admin | Bao gồm cả quản trị (tạo user, đổi role) |
2. Scope theo module
Hạn chế key chỉ truy cập một số module:
| Scope | Module được phép | Endpoint mẫu |
|---|---|---|
crm.* | Toàn bộ CRM | /leads, /deals, /contacts, /quotations |
crm.read | CRM chỉ đọc | GET /leads, GET /deals |
inbox.* | Toàn bộ Inbox | /conversations, /messages |
inbox.send | Chỉ gửi tin nhắn | POST /messages |
hr.* | Toàn bộ HR | /employees, /attendance, /leaves |
hr.read | HR chỉ đọc | GET /employees |
ticket.* | Tickets | /tickets, /tickets/:id/comments |
approval.* | Phê duyệt | /approvals, /approvals/:id/decisions |
webhook.* | Quản lý webhook | /webhooks |
3. Scope theo hành động cụ thể
Chỉ cho phép một số endpoint:
crm.leads.create— chỉ tạo leadcrm.deals.read— chỉ đọc dealinbox.messages.send— chỉ gửi tin nhắn
Mẹo
Áp dụng nguyên tắc least privilege: cấp scope hẹp nhất đủ dùng. Ví dụ integration Zapier chỉ cần tạo lead → chọn crm.leads.create, không cần crm.*.
Xác thực request
Mọi request đến https://api.cnvwork.com/api/v1 phải có header:
http
Authorization: Bearer cnvw_live_4nP9kQ2xW7vB8mZ3hR5tY1jL6dC0sFgA
Content-Type: application/jsonNếu thiếu hoặc sai key, API trả 401 Unauthorized:
json
{
"error": "unauthorized",
"message": "Invalid or missing API key"
}Nếu key đúng nhưng không đủ scope, API trả 403 Forbidden:
json
{
"error": "forbidden",
"message": "API key lacks required scope: crm.deals.write"
}Rate limit
Mỗi workspace bị giới hạn:
| Gói | Rate limit | Burst |
|---|---|---|
| Free | 100 req/giờ | 20 req/phút |
| Pro | 1.000 req/giờ | 100 req/phút |
| Business | 10.000 req/giờ | 500 req/phút |
| Enterprise | Theo hợp đồng | — |
Response header trả về thông tin rate limit:
http
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 873
X-RateLimit-Reset: 1714521600Khi vượt limit, API trả 429 Too Many Requests:
json
{
"error": "rate_limit_exceeded",
"message": "Workspace exceeded 1000 req/h. Retry after 234 seconds.",
"retry_after": 234
}Client nên đọc header Retry-After và backoff tương ứng.
Ví dụ code
cURL
GET danh sách lead:
bash
curl -X GET 'https://api.cnvwork.com/api/v1/leads?limit=20&status=new' \
-H 'Authorization: Bearer cnvw_live_4nP9kQ2xW7vB8mZ3hR5tY1jL6dC0sFgA' \
-H 'Content-Type: application/json'POST tạo lead mới:
bash
curl -X POST 'https://api.cnvwork.com/api/v1/leads' \
-H 'Authorization: Bearer cnvw_live_4nP9kQ2xW7vB8mZ3hR5tY1jL6dC0sFgA' \
-H 'Content-Type: application/json' \
-d '{
"name": "Nguyễn Văn A",
"phone": "0901234567",
"email": "anguyen@example.com",
"source": "website",
"note": "Quan tâm gói Pro"
}'PATCH cập nhật lead:
bash
curl -X PATCH 'https://api.cnvwork.com/api/v1/leads/lead_abc123' \
-H 'Authorization: Bearer cnvw_live_4nP9kQ2xW7vB8mZ3hR5tY1jL6dC0sFgA' \
-H 'Content-Type: application/json' \
-d '{
"status": "contacted",
"owner_id": "user_xyz789"
}'Node.js (axios)
js
const axios = require('axios');
const cnv = axios.create({
baseURL: 'https://api.cnvwork.com/api/v1',
headers: {
Authorization: `Bearer ${process.env.CNV_API_KEY}`,
'Content-Type': 'application/json',
},
timeout: 10000,
});
// GET danh sách lead
async function listLeads() {
const { data } = await cnv.get('/leads', {
params: { limit: 20, status: 'new' },
});
return data.items;
}
// POST tạo lead
async function createLead(payload) {
const { data } = await cnv.post('/leads', payload);
return data;
}
// PATCH cập nhật lead
async function updateLead(id, patch) {
const { data } = await cnv.patch(`/leads/${id}`, patch);
return data;
}
(async () => {
try {
const leads = await listLeads();
console.log(`Found ${leads.length} new leads`);
const lead = await createLead({
name: 'Nguyễn Văn A',
phone: '0901234567',
email: 'anguyen@example.com',
source: 'website',
});
console.log('Created lead:', lead.id);
await updateLead(lead.id, { status: 'contacted' });
console.log('Updated to contacted');
} catch (err) {
if (err.response?.status === 429) {
const retry = err.response.headers['retry-after'];
console.log(`Rate limited. Retry after ${retry}s`);
} else {
console.error(err.response?.data || err.message);
}
}
})();Python (requests)
python
import os
import requests
API_BASE = 'https://api.cnvwork.com/api/v1'
API_KEY = os.environ['CNV_API_KEY']
session = requests.Session()
session.headers.update({
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
})
# GET danh sách lead
def list_leads(status='new', limit=20):
r = session.get(f'{API_BASE}/leads', params={'status': status, 'limit': limit})
r.raise_for_status()
return r.json()['items']
# POST tạo lead
def create_lead(payload):
r = session.post(f'{API_BASE}/leads', json=payload)
r.raise_for_status()
return r.json()
# PATCH cập nhật lead
def update_lead(lead_id, patch):
r = session.patch(f'{API_BASE}/leads/{lead_id}', json=patch)
r.raise_for_status()
return r.json()
if __name__ == '__main__':
leads = list_leads()
print(f'Found {len(leads)} new leads')
lead = create_lead({
'name': 'Nguyễn Văn A',
'phone': '0901234567',
'email': 'anguyen@example.com',
'source': 'website',
})
print('Created lead:', lead['id'])
update_lead(lead['id'], {'status': 'contacted'})
print('Updated to contacted')Quản lý vòng đời key
Xem danh sách key
| Cột | Mô tả |
|---|---|
| Tên | Tên key bạn đặt |
| Prefix | 8 ký tự đầu (VD: cnvw_liv...) |
| Scope | Tóm tắt phạm vi quyền |
| Người tạo | Ai tạo key |
| Ngày tạo | Timestamp tạo |
| Lần dùng cuối | Lần cuối key được sử dụng |
| Trạng thái | Active / Revoked / Expired |
Rotate (xoay) key
Khi nghi ngờ key bị lộ hoặc theo lịch định kỳ (khuyến nghị 90 ngày/lần):
- Vào trang API Keys
- Nhấn + Tạo key mới với cùng scope
- Cập nhật key mới vào hệ thống dùng key (env var, secrets manager)
- Quay lại CNV Work, nhấn Thu hồi key cũ
- Quan sát audit log để chắc chắn không có request nào dùng key cũ trước khi xoá
Thu hồi (revoke) key
- Nhấn vào key trong danh sách
- Chọn Hành động > Thu hồi
- Xác nhận
Sau khi thu hồi, mọi request dùng key đó trả 401 Unauthorized ngay lập tức (không có grace period).
Lưu ý
Thu hồi key không thể hoàn tác. Hệ thống dùng key sẽ ngừng hoạt động ngay lập tức.
Audit log
Mở chi tiết key > tab Audit log xem 30 ngày gần nhất:
| Cột | Mô tả |
|---|---|
| Thời gian | Timestamp request |
| Method + Endpoint | VD: GET /leads |
| Status | 200, 401, 429, 500... |
| IP | IP gọi request |
| User-Agent | Client (axios, python-requests, ...) |
| Latency | Thời gian xử lý (ms) |
Lọc theo status code để phát hiện bất thường:
- Nhiều
401→ key bị lộ, đang bị thử dò - Nhiều
429→ cần upgrade gói hoặc thêm backoff - Nhiều
500→ contact support
Best practice bảo mật
Khuyến nghị
- Lưu key trong secrets manager (AWS Secrets Manager, HashiCorp Vault, Doppler) — không hardcode, không commit vào Git
- Mỗi integration một key riêng — dễ revoke khi cần, dễ truy vết
- Bật IP whitelist khi gọi từ server cố định
- Đặt expiry date cho key tạm thời (script migrate dữ liệu, demo)
- Rotate định kỳ 90 ngày/lần
- Dùng scope hẹp — không cấp
adminnếu chỉ cầncrm.leads.create - Monitor audit log hàng tuần — đặc biệt status
401bất thường - Thu hồi ngay khi nhân sự nghỉ việc hoặc đổi integration
Lỗi thường gặp
| Lỗi | Nguyên nhân | Xử lý |
|---|---|---|
401 unauthorized | Sai key hoặc key bị revoke | Kiểm tra key, tạo lại nếu cần |
403 forbidden | Key thiếu scope | Mở key, thêm scope tương ứng |
403 ip_not_allowed | IP không nằm trong whitelist | Thêm IP vào whitelist hoặc xoá whitelist |
429 rate_limit_exceeded | Vượt rate limit | Backoff theo Retry-After, hoặc upgrade gói |
500 internal_error | Lỗi server | Thử lại sau, báo support kèm X-Request-ID |
Liên kết liên quan
- Webhooks — nhận sự kiện realtime thay vì polling
- Marketplace & Mini App — xây Mini App tích hợp sâu vào UI
