Quét bằng API
Quét bằng API biến một tệp PDF thành bản quét chân thực qua một lệnh gọi REST, phù hợp với quy trình tự động và tích hợp ứng dụng. Tạo công việc, tải PDF lên rồi hỏi trạng thái hoặc chờ webhook: ba bước, từ bất kỳ môi trường hay ngôn ngữ nào gửi được yêu cầu HTTP. Không gian màu, độ phân giải, xoay, làm mờ, nhiễu, độ sáng, độ tương phản và viền đều tùy chỉnh được.
Một lệnh gọi diễn ra thế nào
Tạo công việc
POST /v1/scan-jobs
Gửi config của bạn kèm webhookUrl nếu muốn; bạn nhận lại jobID và uploadURL đã ký sẵn.
Tải PDF lên
PUT {uploadURL}
PUT tệp thẳng tới địa chỉ S3 đã ký sẵn ở bước trước — không cần mã thông báo.
Nhận bản quét
GET /v1/scan-jobs/{jobID}
Hỏi trạng thái hoặc chờ webhook; khi công việc ở trạng thái completed thì tải về từ downloadURL.
Hợp với những việc này
Xuất hàng loạt ở phía máy chủ
Hợp đồng, hóa đơn và báo cáo sinh ra trên máy chủ đi thẳng qua hiệu ứng quét, không ai phải làm lại thủ công trên trang web.
Gắn vào hệ thống sẵn có
Thêm thao tác «xuất bản quét» vào CRM, ERP hay hệ thống phiếu yêu cầu và để nó gọi API.
Dây chuyền tự động
CI hay n8n, Zapier và những nền tảng tương tự khởi động công việc theo sự kiện, xong việc thì webhook chuyển tiếp sang bước sau.
Hàng đợi tệp lớn
Công việc chạy bất đồng bộ: tạo xong thì mỗi công việc được xử lý riêng, tiến độ tra được qua status và createdAfter.
Ngôn ngữ và môi trường
API dùng HTTP và JSON tiêu chuẩn nên ngôn ngữ hay nền tảng tự động hóa nào gửi được yêu cầu đều gọi được.
Ví dụ mã
Add Look Scanned API Scan to this project, so I can turn a PDF into a
realistic scanned copy from code.
API docs: https://lookscanned.io/en/scan/api
Write one function that:
1. POST https://api.lookscanned.io/v1/scan-jobs
Header: Authorization: Bearer $LOOKSCANNED_API_TOKEN
Body: {"config": {"colorspace": "gray", "resolution": 150, "rotate": 1}}
It returns jobID and a presigned uploadURL.
2. PUT the PDF bytes to uploadURL with Content-Type: application/pdf.
Send no Authorization header — that URL is already signed.
3. Poll GET /v1/scan-jobs/{jobID} until status is "completed" (or "failed"),
then return downloadURL.
Read the token from the LOOKSCANNED_API_TOKEN environment variable. Use the
language and HTTP client this project already uses, and add one test.interface ScanConfig {
rotate?: number // degrees to rotate the document
rotate_var?: number // degrees to rotate the document randomly
colorspace?: 'gray' | 'sRGB' // the colorspace of the output image
blur?: number // the amount of blur to apply to the image
noise?: number // the amount of noise to apply to the image
border?: boolean // whether to add a border to the image
brightness?: number // the brightness of the image. 1 is no change
contrast?: number // the contrast of the image. 1 is no change
resolution?: number // the resolution of the image in DPI
output_format?: 'image/png' | 'image/jpeg' // the format of the output image
}
interface ScanOptions {
config: ScanConfig
webhookUrl?: string // webhook URL to notify when job is completed
}
interface ScanResponse {
jobID: string // UUID of the scan job
userID: string // UUID of the user who created the job
createdAt: number // timestamp of job creation
status: 'pending' | 'processing' | 'completed' | 'failed'
config: ScanConfig
inputUploadedAt?: number // timestamp when input file was uploaded
completedAt?: number // timestamp when job was completed
webhookUrl?: string // webhook URL for notifications
uploadURL?: string // S3 presigned URL for file upload
downloadURL?: string // S3 presigned URL for file download
}
async function apiScan(pdfBlob: Blob, scanOptions: ScanOptions, token: string): Promise<ScanResponse> {
const response = await fetch('https://api.lookscanned.io/v1/scan-jobs', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(scanOptions)
})
const result: ScanResponse = await response.json()
// PUT PDF Blob to upload URL
const uploadURL = result.uploadURL
await fetch(uploadURL, {
method: 'PUT',
headers: {
'Content-Type': 'application/pdf',
'Content-Length': pdfBlob.size.toString()
},
body: pdfBlob
})
// get scan job status
const jobStatusResponse = await fetch(`https://api.lookscanned.io/v1/scan-jobs/${result.jobID}`, {
headers: {
'Authorization': `Bearer ${token}`
}
})
return await jobStatusResponse.json()
}import requests
def api_scan(pdf_file, scan_options, token):
# Create scan job
response = requests.post(
'https://api.lookscanned.io/v1/scan-jobs',
headers={'Authorization': f'Bearer {token}'},
json=scan_options
)
result = response.json()
# Upload PDF to presigned URL
upload_url = result['uploadURL']
requests.put(
upload_url,
headers={
'Content-Type': 'application/pdf',
'Content-Length': str(len(pdf_file))
},
data=pdf_file
)
# Get scan job status
job_status = requests.get(
f'https://api.lookscanned.io/v1/scan-jobs/{result["jobID"]}',
headers={'Authorization': f'Bearer {token}'}
)
return job_status.json()
# Example usage
if __name__ == "__main__":
with open('document.pdf', 'rb') as f:
pdf_content = f.read()
options = {
'config': {
# Optional parameters:
# 'rotate': 0, # degrees to rotate the document
# 'colorspace': 'gray', # gray or sRGB
# 'resolution': 300, # DPI
# 'rotate_var': 0, # random rotation variance in degrees
# 'blur': 0, # amount of blur
# 'noise': 0, # amount of noise
# 'border': False, # whether to add border
# 'brightness': 1, # 1 is no change
# 'contrast': 1, # 1 is no change
# 'output_format': 'image/png' # image/png or image/jpeg
},
'webhookUrl': 'https://example.com/webhook'
}
result = api_scan(pdf_content, options, 'your-api-token')
print(f"Scan job created with ID: {result['jobID']}")# Set your API token and PDF file as environment variables
export LOOKSCANNED_API_TOKEN='your_api_token_here'
# Create a new scan job
curl -X POST 'https://api.lookscanned.io/v1/scan-jobs' \
-H "Authorization: Bearer ${LOOKSCANNED_API_TOKEN}" \
-H 'Content-Type: application/json' \
-d '{
"config": {
"rotate": 0,
"rotate_var": 1,
"colorspace": "gray",
"blur": 0.2,
"noise": 0.1,
"border": true,
"brightness": 1.0,
"contrast": 1.0,
"resolution": 300,
"output_format": "image/jpeg"
},
"webhookUrl": "https://your-domain.com/webhook"
}'
# Response will include uploadURL and jobID
# {
# "jobID": "550e8400-e29b-41d4-a716-446655440000",
# "userID": "446655440000-e29b-41d4-a716-550e8400",
# "createdAt": 1616161616,
# "status": "created",
# "uploadURL": "...",
# "config": { ... }
# }
# Upload PDF file to the presigned URL
curl -X PUT 'PRESIGNED_UPLOAD_URL' \
-H 'Content-Type: application/pdf' \
-H "Content-Length: PDF_FILE_SIZE" \
--data-binary "@path/to/your/file.pdf"
# Check job status
curl 'https://api.lookscanned.io/v1/scan-jobs/JOB_ID' \
-H "Authorization: Bearer ${LOOKSCANNED_API_TOKEN}"
# Response will include status and downloadURL when completed
# {
# "jobID": "550e8400-e29b-41d4-a716-446655440000",
# "status": "completed",
# "downloadURL": "...",
# ...
# }
# Download the PDF
curl -o scanned.pdf 'DOWNLOAD_URL'API Bearer Token
Mã thông báo gắn với tài khoản của bạn và có thể tạo lại bất cứ lúc nào. Quét bằng API cần tài khoản Pro: không có mã thông báo hợp lệ thì API trả về 401, không có vai trò Pro thì trả về 403.
Quét bằng API là tính năng Pro
Tài khoản này chưa có Pro; nâng cấp xong là mã thông báo hiện ở đây. Không có mã thông báo hoặc không có vai trò thì API trả về 401 / 403.
Dùng thử
Chỉnh tham số, xem phần thân yêu cầu đổi theo, rồi lần lượt gọi ba lệnh tới API.
Tham số quét
Lần chạy thử gọi API bằng mã thông báo của bạn và cần tài khoản Pro; tham số và phần thân yêu cầu thì xem thoải mái.
{
"config": {
"rotate": 1,
"rotate_var": 0.5,
"colorspace": "gray",
"blur": 0,
"noise": 0,
"border": false,
"brightness": 1.3,
"contrast": 1.3,
"resolution": 150,
"output_format": "image/jpeg"
}
}Thông tin công việc quét
ví dụ{
"jobID": "3f9c1e64-0000-4000-8000-00000000a71b",
"userID": "8f21c4b0-0000-4000-8000-000000004a17",
"createdAt": 1724409600,
"status": "completed",
"inputUploadedAt": 1724409601,
"completedAt": 1724409602,
"numPages": 6,
"downloadURL": "https://…/output/3f9c.pdf?X-Amz-…"
}Tham chiếu API
| Phương thức | Đường dẫn | Mô tả |
|---|---|---|
| POST | /v1/scan-jobs | Tạo một công việc quét. Gửi config và webhookUrl nếu cần; bạn nhận lại đối tượng công việc có trạng thái created cùng uploadURL đã ký sẵn. |
| PUT | {uploadURL}Địa chỉ S3 đã ký sẵn từ bước trước, không nằm trên api.lookscanned.io | Tải tệp PDF nguồn lên kèm Content-Type: application/pdf và Content-Length. Địa chỉ đã mang sẵn chữ ký nên đừng thêm tiêu đề Authorization. |
| GET | /v1/scan-jobs/{jobID} | Đọc một công việc, dùng để hỏi trạng thái. Ở created có uploadURL, ở completed có downloadURL. |
| GET | /v1/scan-jobs | Liệt kê các công việc của bạn, lọc theo jobID, status hoặc createdAfter. |
401không có mã thông báo hợp lệ403tài khoản không phải Pro404không có công việc này
Thân yêu cầu
| Trường | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
| webhookUrl | string · — | — | Được gọi một lần khi công việc xong, nhờ vậy bạn không phải hỏi trạng thái liên tục. |
| config.colorspace | 'gray' | 'sRGB' · gray | gray | Không gian màu của ảnh đầu ra; gray là bản quét đen trắng. |
| config.resolution | number · 72 | 72 | Độ phân giải của ảnh đầu ra, tính bằng DPI. |
| config.rotate | number · — | — | Góc xoay của cả tài liệu, tính bằng độ. |
| config.rotate_var | number · — | — | Biên độ xoay ngẫu nhiên của từng trang, tính bằng độ — cho cảm giác tờ giấy đặt lệch. |
| config.blur | number · 0 | 0 | Mức độ làm mờ. |
| config.noise | number · 0 | 0 | Mức độ nhiễu. |
| config.brightness | number · 1 | 1 | Độ sáng; 1 là giữ nguyên. |
| config.contrast | number · 1 | 1 | Độ tương phản; 1 là giữ nguyên. |
| config.border | boolean · false | false | Có thêm viền quét cho trang hay không. |
| config.output_format | 'image/png' | 'image/jpeg' · image/jpeg | image/jpeg | Định dạng ảnh dùng khi kết xuất các trang. |
Mọi trường đều có thể bỏ qua. Các giá trị khởi đầu ở phần «Dùng thử» — độ phân giải 150, xoay 1, độ sáng và độ tương phản 1,3 — là tổ hợp mà ứng dụng web khuyến nghị, không phải mặc định của API.
Những trường đáng chú ý trong đối tượng công việc
- status
- created / processing / completed / failed — quyết định hai địa chỉ bên dưới có xuất hiện hay không.
- uploadURL
- Chỉ có khi ở created. Địa chỉ tải lên đã ký sẵn, có hạn dùng.
- downloadURL
- Chỉ có khi đã completed. Địa chỉ tải xuống đã ký sẵn, có hạn dùng.
- inputUploadedAt / completedAt
- Thời điểm tải xong tệp nguồn và thời điểm công việc xong; hiệu số là thời gian xử lý.
Câu hỏi thường gặp
Quét bằng API có cần Pro không?
Có. Không có mã thông báo hợp lệ thì API trả về 401, còn tài khoản không có vai trò Pro nhận 403. Nâng cấp rồi đăng nhập là lấy được mã thông báo ngay trên trang này.
Làm sao biết công việc đã xong?
Có hai cách: hỏi định kỳ GET /v1/scan-jobs/{jobID}, hoặc truyền webhookUrl khi tạo công việc để dịch vụ gọi lại một lần lúc xong.
Kết quả có giống khi quét trên trang web không?
Giống. Cả hai dùng chung một bản cài đặt hiệu ứng quét, và không gian màu, độ phân giải, xoay, làm mờ, nhiễu, độ sáng, độ tương phản, viền trong config tương ứng với các tùy chọn cùng tên trên trang web: cùng tham số thì cùng kết quả. Chỉ khác chỗ xử lý — trang web xử lý cục bộ, API xử lý ở dịch vụ từ xa.
Có thể lưu địa chỉ tải lên và tải xuống để dùng lại không?
Không nên. uploadURL và downloadURL đều là địa chỉ đã ký sẵn và có hạn dùng; hết hạn thì phải đọc lại công việc để lấy địa chỉ mới.
Xử lý một công việc mất bao lâu?
Tùy số trang và độ phân giải. Tài liệu vài trang thường xong trong vài giây; độ phân giải càng cao, số trang càng nhiều thì càng lâu. Hiệu của inputUploadedAt và completedAt cho biết thời gian thực tế.
Công việc thất bại thì làm gì?
Trạng thái chuyển thành failed. Nguyên nhân thường gặp là tệp không phải PDF hợp lệ, bị hạn chế do mã hóa, hoặc quá trình tải lên bị gián đoạn. Kiểm tra tệp mở được rồi tạo công việc mới.
Có tra được công việc cũ không?
Có. GET /v1/scan-jobs liệt kê các công việc của bạn và lọc được theo jobID, status, createdAfter, đủ để đối chiếu hoặc tải lại.