Thanh Nguyen Tài liệu tích hợp Về bảng điều khiển

API v1 · Xưởng mã thiết bị

Kết nối Xưởng với ứng dụng của bạn

Thêm tài khoản, hoàn tất xác thực và lấy mã sáu số qua API. Tài khoản và tiến trình tạo bằng API cũng xuất hiện trên bảng điều khiển của bạn.

Base URLhttps://otp.thanhnguyen.sbs/api/v1/workshop

  1. Tạo API Key tại màn hình API KEY.
  2. Gửi tài khoản để nhận account.id và job.id.
  3. Theo dõi tiến trình, gửi OTP nếu được yêu cầu.
  4. Khi hoàn tất, lấy device_api hoặc gọi API lấy mã.

Chọn ngôn ngữ thông báo bằng Accept-Language: vi, en hoặc zh-CN. X-XID-Language được ưu tiên hơn cookie ngôn ngữ; cookie được ưu tiên hơn Accept-Language. Mặc định API là tiếng Việt. Tên trường, HTTP status, mã error/status, ID, URL và dữ liệu người dùng không đổi.

API Key và quyền truy cập

Key đầy đủ chỉ hiện một lần khi tạo. Mỗi user được có tối đa 10 key hoạt động, với thời hạn và quyền riêng. Bạn có thể thu hồi bất cứ lúc nào trên màn hình API KEY.

Authorization: Bearer xid_KEY_CUA_BAN
Content-Type: application/json
  • workshop:read: xem tài khoản, hạn mức và tiến trình.
  • workshop:write: thêm/đăng nhập, xác thực, tạm dừng, quản lý thư mục/ghi chú, gỡ khỏi danh sách và hủy tiến trình.
  • workshop:code: lấy mã và xem liên kết lấy mã. Đổi liên kết cần cả quyền write và code.

Mỗi key chỉ có quyền trên tài khoản của chủ key, dùng chung hạn mức với web. Key hết hạn, bị thu hồi hoặc bị vô hiệu hóa sau đổi mật khẩu/khóa user sẽ trả 401.

Gọi API từ chương trình hoặc backend của bạn. Giữ key trên server; không nhúng vào JavaScript công khai hoặc query URL. Mọi yêu cầu có body, kể cả thao tác dùng {}, cần Content-Type JSON.

BASE="https://otp.thanhnguyen.sbs/api/v1/workshop"
# XID_API_KEY là biến môi trường chứa key của bạn.
curl --fail-with-body "$BASE/me" \
  -H "Authorization: Bearer $XID_API_KEY"

Thêm tài khoản và bắt đầu đăng nhập

Tạo file account.json trên máy của bạn với nội dung dưới đây. Các trường label, note, folder_id, proxy và otp_source đều tùy chọn. Xóa file chứa mật khẩu sau khi dùng.

{
  "apple_id": "your-account@example.com",
  "password": "MAT_KHAU_CUA_BAN",
  "label": "Tài khoản của tôi",
  "note": "Nhóm khách hàng A"
}

Mẫu này dùng cấu hình proxy đã lưu của bạn. Xem Cấu hình proxy để chọn proxy hoặc tắt proxy.

# Mỗi thao tác mới có một UUID mới. Giữ lại để gửi lại cùng yêu cầu.
REQUEST_ID="$(python3 -c 'import uuid; print(uuid.uuid4())')"
curl --fail-with-body "$BASE/accounts" \
  -H "Authorization: Bearer $XID_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $REQUEST_ID" \
  --data-binary @account.json

Phản hồi 202 có dạng {"account": {...}, "job": {...}}. Lưu cả hai ID; 202 chỉ xác nhận đã nhận yêu cầu, chưa có nghĩa đăng nhập thành công.

JOB_ID="ID_TIEN_TRINH"
curl --fail-with-body "$BASE/jobs/$JOB_ID" \
  -H "Authorization: Bearer $XID_API_KEY"

Kiểm tra mỗi 3 giây. Trạng thái tiến trình: queued, processing, awaiting_otp, succeeded, failed hoặc cancelled.

Link nhận OTP tự động

Thêm otp_source vào yêu cầu tạo/đăng nhập để đọc mã từ nguồn SMS thuộc quyền sử dụng của bạn.

"otp_source": {
  "url": "https://sms.example/your-private-endpoint",
  "phone": "+14092986901"
}

phone giúp chọn đúng số tin cậy. Nếu không chỉ định số và chỉ có một số tin cậy, hệ thống chọn số duy nhất. Xem tài liệu đầy đủ để biết các định dạng.

Cấu hình proxy qua API

Gửi proxy trong body POST /accounts hoặc POST /accounts/{id}/login. Proxy chỉ áp dụng cho kết nối từ dịch vụ tới Apple khi đăng nhập, gồm chọn cách nhận mã, gửi mã và nhập OTP. Proxy đã chọn được giữ cố định trong cùng job; không áp dụng cho đăng nhập website, kết nối từ ứng dụng của bạn tới API hoặc lấy mã qua /code và device_api.

  • Bỏ proxy hoặc gửi "proxy": null: dùng cấu hình proxy đã lưu của chủ API Key. Nếu chưa lưu, mặc định không dùng proxy.
  • Gửi đối tượng proxy: cập nhật cấu hình đã lưu của user, dùng chung với web và các lần tạo/đăng nhập tiếp theo. Job đang chạy giữ cấu hình đã nhận.
  • "proxy": {"mode":"none"} tắt proxy trong cấu hình đã lưu và cho job mới. direct là dùng một proxy cụ thể; none là kết nối không qua proxy.

Thêm một trong các trường sau vào file account.json rồi gửi bằng lệnh cURL ở trên. Các tên miền .invalid chỉ là ví dụ; thay bằng địa chỉ của bạn. Khi đăng nhập lại, body cần password và trường proxy đã chọn.

Một proxy: direct

{
  "proxy": {
    "mode": "direct",
    "strategy": "per_account",
    "value": "http://PROXY_USER:PROXY_PASSWORD@proxy.example.invalid:8080"
  }
}

API cấp proxy: api

{
  "proxy": {
    "mode": "api",
    "strategy": "smart",
    "value": "https://proxy-provider.example.invalid/next?key=YOUR_PROVIDER_KEY"
  }
}

Danh sách proxy: list

{
  "proxy": {
    "mode": "list",
    "strategy": "per_account",
    "value": "http://proxy-1.example.invalid:8080\nsocks5://PROXY_USER:PROXY_PASSWORD@proxy-2.example.invalid:1080"
  }
}

value là chuỗi, mỗi dòng một proxy; trong JSON viết xuống dòng bằng \n.

Tắt proxy: none

{"proxy": {"mode": "none"}}

strategy mặc định là per_account. Trong Xưởng, chiến lược này tái sử dụng phân bổ đã lưu theo tài khoản; nguồn list/api có thể phục vụ nhiều tài khoản qua các lượt dùng chung. smart ưu tiên proxy khả dụng đang rảnh hoặc có ít lượt chờ nhất, tái sử dụng proxy khỏe cho lượt mới. Cả hai giữ proxy cố định sau khi phiên bắt đầu, kể cả qua OTP; với direct, mọi tài khoản dùng địa chỉ duy nhất đã nhập.

Hỗ trợ http, https, socks4, socks5, socks5h và cổng cụ thể. Proxy phải có địa chỉ công khai; URL nhà cung cấp ở chế độ api phải dùng HTTPS. Khi giữ nguyên mode, bỏ value hoặc gửi null/chuỗi trống sẽ dùng lại giá trị đã lưu của mode đó, nếu có.

Account/job không trả thông tin xác thực proxy. Giữ file chứa proxy có mật khẩu hoặc URL có key riêng tư và không in ra log. Mẫu Python phía dưới dùng prompt ẩn cho proxy.

Chọn nhận mã trên thiết bị hoặc qua SMS

Khi job.status = awaiting_otp, có thể nhập mã trên thiết bị tin cậy. Để chọn số điện thoại, gọi API tải lựa chọn rồi đọc lại tiến trình đến khi two_factor.options_loading = false và options_loaded = true. Nếu có options_error, chưa chọn số; hãy kiểm tra tiến trình và thử tải lại sau.

curl --fail-with-body "$BASE/jobs/$JOB_ID/factor-options" \
  -H "Authorization: Bearer $XID_API_KEY" \
  -H "Content-Type: application/json" -d '{}'

two_factor.phones trả danh sách {id, label}. Dùng id do API trả về, không suy ra từ số thứ tự hoặc đuôi điện thoại.

curl --fail-with-body "$BASE/jobs/$JOB_ID/send-code" \
  -H "Authorization: Bearer $XID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"mode":"sms","phone_id":1}'

Thay phone_id: 1 bằng ID thực tế. Muốn gửi về thiết bị tin cậy, dùng {"mode":"trusted_device"}. Chờ sending = false và code_sent = true, kiểm tra send_error trước khi nhập mã.

curl --fail-with-body "$BASE/jobs/$JOB_ID/otp" \
  -H "Authorization: Bearer $XID_API_KEY" \
  -H "Content-Type: application/json" -d '{"code":"123456"}'

Mã là chuỗi sáu chữ số, có thể bắt đầu bằng 0. Thay ví dụ bằng mã của bạn, trước otp_expires_at. Sau khi gửi OTP, tiếp tục theo dõi đến trạng thái kết thúc. Nếu đang tự nhận OTP từ link, gọi /automation/cancel với {} trước khi chuyển sang nhập thủ công.

Lấy mã và liên kết riêng

Khi tiến trình succeeded, đọc lại tài khoản để lấy device_api. Key cần quyền workshop:code để nhận liên kết này.

ACCOUNT_ID="ID_TAI_KHOAN"
curl --fail-with-body "$BASE/accounts/$ACCOUNT_ID" \
  -H "Authorization: Bearer $XID_API_KEY"

curl --fail-with-body "$BASE/accounts/$ACCOUNT_ID/code" \
  -H "Authorization: Bearer $XID_API_KEY" \
  -H "Content-Type: application/json" -d '{}'
# {"code":"000007","generated_at":"..."}

device_api dùng GET và trả mã dạng văn bản. Ai có liên kết đều có thể dùng liên kết; giữ riêng như key. Khi server chưa cấu hình địa chỉ công khai, liên kết có thể là đường dẫn tương đối trên cùng website.

Thu hồi API Key chặn những yêu cầu API tiếp theo nhưng không đổi link lấy mã đã tạo. Để vô hiệu link cũ, dùng POST /accounts/{id}/rotate-link hoặc đổi liên kết trên web. Dừng tài khoản sẽ chặn việc lấy mã.

Thư mục, ghi chú và gỡ khỏi danh sách

Gửi POST /folders với {"name":"Khách hàng A"} để tạo thư mục rồi lưu folder.id. Mỗi user có tối đa 200 thư mục; các thư mục chỉ thuộc chủ API key. Gửi ID này trong folder_id khi thêm hoặc sửa tài khoản. note nhận tối đa 4.000 ký tự, có thể nhiều dòng.

curl --fail-with-body -X PATCH "$BASE/accounts/$ACCOUNT_ID" \
  -H "Authorization: Bearer $XID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"note":"Khách hàng A\nGia hạn tháng sau","folder_id":"YOUR_FOLDER_ID","hidden":false}'

GET /accounts mặc định dùng visibility=visible. Chọn hidden để xem tài khoản đã gỡ, hoặc all để xem cả hai. Thêm folder_id=YOUR_FOLDER_ID để lọc một thư mục; folder_id=unfiled chỉ lấy tài khoản chưa phân loại. search cũng tìm trong ghi chú. Danh sách và chi tiết trả note, folder_id, folder_name và hidden.

DELETE /accounts/{id} chỉ gỡ khỏi danh sách chính. Danh tính và device_api được giữ nguyên; phản hồi có hidden:true và link_preserved:true. Tài khoản đã gỡ vẫn dùng được API chi tiết/lấy mã và vẫn tính vào hạn mức. Gửi {"hidden":false} bằng PATCH để đưa lại danh sách, không cần đăng nhập lại.

Muốn chặn lấy mã, gửi {"enabled":false}; muốn vô hiệu hóa link cũ, dùng /rotate-link. Link sau khi gỡ vẫn phụ thuộc dịch vụ/state và việc Apple tiếp tục chấp nhận thiết bị. Tài khoản đã bị xóa và thu hồi token ở phiên bản cũ không tự được khôi phục.

Xóa thư mục đưa tài khoản về chưa phân loại, giữ link và ghi chú. Hoặc gửi {"folder_id":null} khi sửa một tài khoản. Trên web, xuất TXT có dạng AppleID|link|ghi_chú; cột ghi chú được bỏ nếu trống. Trong văn bản, \\, \|, \n, \r lần lượt biểu diễn dấu gạch chéo ngược, dấu phân cách, xuống dòng và carriage return, để mỗi tài khoản luôn nằm trên một dòng.

Nhập hàng loạt, xuất TXT và proxy đã lưu

Các endpoint dưới đây dùng chung parser, hàng đợi, hạn mức và cấu hình proxy với Xưởng trên web. Mỗi API Key chỉ thao tác dữ liệu của chính chủ key, kể cả key của super admin.

1. Kiểm tra rồi gửi nhiều tài khoản

Gửi POST /bulk/preview với text trước. Mỗi dòng gồm Apple ID và mật khẩu, phân cách bằng | hoặc khoảng trắng; mật khẩu có khoảng trắng phải đặt trong dấu nháy kép. Có thể thêm link OTP HTTPS hoặc số----link OTP. Đây là định dạng Xưởng; không nhận OTP có sẵn hoặc đáp án bảo mật.

{"text":"one@example.invalid|PASSWORD\ntwo@example.invalid|PASSWORD|14092986901----https://sms.example.invalid/read?key=YOUR_KEY"}

Preview chỉ trả số lượng, tình trạng từng dòng, has_otp_url và masked_phone; không đăng nhập, gửi SMS hay trả lại mật khẩu/link OTP. Sửa dòng không hợp lệ trước khi gửi. Tối đa 100 dòng không rỗng và 256 KiB UTF-8 mỗi lượt.

Gửi cùng text đến POST /bulk với Idempotency-Key mới. Có thể thêm note, folder_id, profile_id, device_model, device_os, device_name và proxy như tạo một tài khoản. HTTP 202 trả batch, gồm id, status, counts và rows. Mỗi user chỉ có một batch hoạt động; API và web chia sẻ giới hạn này.

curl "$BASE/api/v1/workshop/bulk" \
  -H "Authorization: Bearer $XID_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $OPERATION_ID" \
  --data-binary @accounts.json

GET /bulk trả 20 batch gần nhất. GET /bulk/{batch_id} trả tiến trình từng dòng; dùng job_id với các endpoint /jobs để chọn số, gửi SMS, nhập OTP hoặc dừng tự lấy mã như luồng một tài khoản. Poll khoảng 3 giây/lần, điều phối tổng request để không vượt 120 request/phút. POST /bulk/{batch_id}/cancel dừng dòng chờ và job đang chạy; không xóa tài khoản/link đã hoàn tất.

Không tự gửi lại batch bằng key mới khi mất kết nối. Gửi lại cùng key và JSON sẽ trả batch hiện tại; request_in_progress/request_incomplete có thể kèm batch_id để tra cứu. Mật khẩu/link OTP chỉ ở bộ nhớ khi chạy; restart giữ lịch sử nhưng không tự khôi phục mật khẩu hay chạy lại dòng interrupted. Muốn thử lại, xem kết quả rồi gửi riêng các dòng lỗi với key mới; tài khoản mới/lỗi đủ điều kiện được dùng lại, không tạo bản sao.

2. Xuất các tài khoản đã chọn hoặc toàn bộ

POST /accounts/export cần cả workshop:read và workshop:code vì nội dung chứa link nhận mã. Dùng scope=selected kèm account_ids (tối đa 10.000 ID), hoặc scope=all cùng visibility=visible/hidden/all và folder_id tùy chọn. ID tài khoản/thư mục của người khác làm yêu cầu thất bại, không xuất một phần.

{"scope":"selected","account_ids":["ACCOUNT_ID_1","ACCOUNT_ID_2"]}

Phản hồi JSON gồm text, filename, count, with_link, without_link. Lưu text thành TXT UTF-8: mỗi dòng apple_id|device_api, thêm |note khi có ghi chú. Ký tự gạch chéo ngược, | và xuống dòng trong trường dữ liệu được escape. Tài khoản chưa có link để trống cột link. Xuất chỉ đọc liên kết hiện có, không lấy mã, đăng nhập hay thay danh tính/link.

3. Lưu proxy dùng chung với web

GET /proxy (workshop:read) chỉ trả tóm tắt, không có URL hoặc mật khẩu proxy. PUT /proxy (workshop:write) lưu mode, strategy và value cho các lượt mới; job đang chạy giữ tuyến đã chọn. GET /proxy/details cần workshop:write và trả value đầy đủ của chính user: không ghi log hoặc chia sẻ phản hồi này.

{"mode":"api","strategy":"per_account","value":"https://proxy-provider.example.invalid/next?key=YOUR_KEY"}

Các mode là none/direct/api/list, strategy là per_account/smart. Khi giữ nguyên mode, bỏ value hoặc gửi null/chuỗi trắng giữ giá trị đã lưu; mode=none xóa cấu hình. Tạo tài khoản hoặc batch mà bỏ proxy sẽ dùng cấu hình này. Lưu cấu hình không gọi Apple hoặc nhà cung cấp proxy.

Python

from getpass import getpass
from pathlib import Path
from uuid import uuid4
from workshop_api import Client

client = Client("https://workshop.example.com", getpass("API Key: "))
text = Path("accounts.txt").read_text(encoding="utf-8")
try:
    preview = client.preview_bulk(text)
    if preview["can_submit"]:
        operation_id = str(uuid4())
        print("operation_id:", operation_id)
        result = client.submit_bulk(text, operation_id=operation_id)
        print("batch_id:", result["batch"]["id"])
finally:
    text = ""
    client.close()

Các endpoint bổ sung

  • POST /bulk/preview — workshop:write
  • POST /bulk — workshop:write
  • GET /bulk — workshop:read
  • GET /bulk/{batch_id} — workshop:read
  • POST /bulk/{batch_id}/cancel — workshop:write
  • POST /accounts/export — workshop:read + workshop:code
  • GET /proxy — workshop:read
  • PUT /proxy — workshop:write
  • GET /proxy/details — workshop:write

Danh sách API

Các đường dẫn dưới đây nối sau Base URL. Toàn bộ API yêu cầu Bearer key. Các thao tác POST không có dữ liệu vẫn gửi {}; DELETE cũng cần Content-Type JSON.

Phương thức Đường dẫn Chức năng Quyền
GET /me Hạn mức và key đang dùng read
GET /accounts Danh sách; page, page_size (tối đa 100), search, status, visibility, folder_id read
GET /folders Thư mục riêng và số tài khoản trong từng thư mục read
POST /folders Tạo thư mục: {"name":"Khách hàng A"} write
PATCH /folders/{id} Đổi tên thư mục: {"name":"Tên mới"} write
DELETE /folders/{id} Xóa thư mục, giữ tài khoản và link write
POST /accounts Thêm và đăng nhập; cần Idempotency-Key write
GET /accounts/{id} Thông tin tài khoản; xem link cần code read
PATCH /accounts/{id} Sửa label, note, folder_id, hidden hoặc enabled write
DELETE /accounts/{id} Gỡ khỏi danh sách, giữ danh tính và link nhận mã write
POST /accounts/{id}/login Đăng nhập lại; password, proxy, otp_source; cần Idempotency-Key write
POST /accounts/{id}/rotate-link Đổi link, link cũ mất hiệu lực write + code
POST /accounts/{id}/code Lấy mã sáu chữ số code
GET /jobs/{id} Đọc tiến trình và lựa chọn 2FA read
POST /jobs/{id}/factor-options Tải lựa chọn nhận mã write
POST /jobs/{id}/send-code Gửi mã tới số/thiết bị đã chọn write
POST /jobs/{id}/otp Nộp mã xác thực write
POST /jobs/{id}/cancel Hủy tiến trình write
POST /jobs/{id}/automation/cancel Dừng đọc link OTP, chuyển sang thủ công write

Lỗi, giới hạn và gửi lại yêu cầu

Giới hạn 120 yêu cầu/phút chung cho mỗi user và theo từng key. Khi nhận 429, chờ số giây trong Retry-After. Hạn mức tài khoản và hàng đợi dùng chung với web.

Khi lấy mã, 429 code_queue_full là hàng đợi đầy; 503 code_queue_timeout là hết thời gian chờ trước khi chạy. Cả hai trả Retry-After theo giây. Chờ đủ rồi gọi lại /code; không cần tạo tài khoản hay đăng nhập lại.

Tạo tài khoản, đăng nhập lại và gửi batch bắt buộc Idempotency-Key: 8–128 ký tự chữ, số hoặc ._:-, nên dùng UUID. Nếu mất kết nối, gửi lại cùng key, cùng JSON; hệ thống không tạo thêm tiến trình. Lịch sử key được giữ 7 ngày. Tạo thao tác mới phải dùng key mới.

  • 401 api_unauthorized: kiểm tra key, hạn sử dụng và trạng thái user.
  • 403 api_scope_required: key thiếu quyền.
  • 400 idempotency_key_required: thiếu hoặc sai khóa chống trùng.
  • 409 idempotency_conflict: key đã dùng với JSON khác.
  • 409 request_in_progress hoặc 503 request_incomplete: chưa xác nhận xong; kiểm tra account/job trước khi gửi thao tác mới.
  • 409 account_quota_exceeded: đã đạt hạn mức; 422 invalid_input: sửa các trường được chỉ ra.

Lỗi trả JSON {"error":"ma_loi","message":"Thông báo"}; có thể kèm account_id, job_id để kiểm tra kết quả dở dang. Nếu tài khoản đã tạo nhưng chưa xếp được lượt đăng nhập, giữ ID đó và gọi /accounts/{id}/login sau khi giải quyết lỗi. Không tự động lặp lại bằng key mới vì có thể tạo trùng tài khoản.

Các lỗi chi tiết nội bộ không được trả qua API. Tiến trình lỗi có thông báo chung tương tự giao diện web.

Chạy ví dụ Python

File mẫu hỏi key, Apple ID, mật khẩu và OTP tại Terminal, hỗ trợ chọn SMS và proxy. Mật khẩu, key và OTP không hiện khi gõ. Chương trình in ID tiến trình để bạn có thể tiếp tục kiểm tra nếu bị gián đoạn.

Không thêm --proxy thì mẫu kế thừa cấu hình đã lưu. --proxy nhập một proxy ở chế độ direct, chiến lược per_account, và cập nhật cấu hình đã lưu. Để dùng api, list, smart hoặc none, đặt trường proxy trong body theo các ví dụ ở trên và hướng dẫn Python chi tiết.

python3 -m pip install requests
python3 workshop_api.py --base-url https://workshop.example.com

# Hỏi thêm proxy cho lần đăng nhập:
python3 workshop_api.py --base-url https://workshop.example.com --proxy

# Tiếp tục theo dõi tiến trình đã tạo:
python3 workshop_api.py --resume-job ID_TIEN_TRINH