API 스캔
API 스캔은 REST 호출로 PDF를 사실적인 스캔한 문서로 바꿔 주며, 자동화 파이프라인과 앱 연동에 적합합니다. 작업을 만들고 PDF를 업로드한 다음 상태를 폴링하거나 webhook을 기다리면 됩니다. 이 세 단계면 HTTP 요청을 보낼 수 있는 어떤 환경이나 언어에서도 쓸 수 있습니다. 색 공간, 해상도, 회전, 흐림, 노이즈, 밝기, 대비, 테두리를 모두 지정할 수 있습니다.
호출 흐름
작업 만들기
POST /v1/scan-jobs
config와 선택 항목인 webhookUrl을 보내면 jobID와 미리 서명된 uploadURL을 받습니다.
PDF 업로드
PUT {uploadURL}
앞 단계에서 받은 미리 서명된 S3 주소로 파일을 그대로 PUT 합니다. 토큰은 필요 없습니다.
스캔한 문서 가져오기
GET /v1/scan-jobs/{jobID}
상태를 폴링하거나 webhook을 기다렸다가, completed가 되면 downloadURL에서 내려받습니다.
이런 곳에 맞습니다
백엔드에서 일괄 생성
서버에서 만든 계약서, 청구서, 보고서를 곧바로 스캔 효과에 통과시켜, 웹 페이지에서 같은 작업을 다시 할 필요가 없습니다.
기존 시스템에 붙이기
CRM이나 ERP, 티켓 시스템에 '스캔한 문서 내보내기' 동작을 추가하고 API를 호출하게 하면 됩니다.
자동화 파이프라인
CI나 n8n, Zapier 같은 플랫폼이 이벤트에 맞춰 작업을 시작하고, 완료되면 webhook이 다음 단계로 넘깁니다.
대량 파일 대기열
작업 방식이라 만든 뒤에는 각각 비동기로 처리되며, 진행 상황은 status와 createdAfter로 확인할 수 있습니다.
지원하는 언어와 환경
API는 표준 HTTP와 JSON이므로 요청을 보낼 수 있는 언어나 자동화 플랫폼이면 무엇이든 호출할 수 있습니다.
코드 예제
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
토큰은 계정에 연결되어 있으며 언제든 다시 만들 수 있습니다. API 스캔에는 Pro 계정이 필요합니다. 유효한 토큰이 없으면 401, Pro 역할이 없으면 403이 돌아옵니다.
API 스캔은 Pro 기능입니다
이 계정은 아직 Pro가 아닙니다. 업그레이드하면 여기에서 토큰을 받을 수 있습니다. 토큰이나 역할이 없으면 API는 401 / 403을 돌려줍니다.
사용해 보기
매개변수를 조정하면 요청 본문도 그대로 따라 바뀝니다. 그런 다음 세 번의 호출을 차례로 실행해 보세요.
스캔 매개변수
시험 실행은 본인의 토큰으로 API를 호출하므로 Pro 계정이 필요합니다. 매개변수와 요청 본문은 자유롭게 볼 수 있습니다.
{
"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"
}
}스캔 작업 정보
예시{
"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-…"
}API 참조
| 메서드 | 경로 | 설명 |
|---|---|---|
| POST | /v1/scan-jobs | 스캔 작업을 만듭니다. config와 선택 항목인 webhookUrl을 보내면 status가 created인 작업 객체와 미리 서명된 uploadURL을 받습니다. |
| PUT | {uploadURL}앞 단계에서 받은 미리 서명된 S3 주소이며 api.lookscanned.io에 있지 않습니다 | 원본 PDF를 Content-Type: application/pdf와 Content-Length를 붙여 업로드합니다. 주소 자체에 서명이 들어 있으므로 Authorization 헤더를 추가하지 마세요. |
| GET | /v1/scan-jobs/{jobID} | 작업 하나를 조회합니다. 폴링용이며 created일 때는 uploadURL이, completed일 때는 downloadURL이 들어 있습니다. |
| GET | /v1/scan-jobs | 자신의 작업 목록을 보여 주며 jobID, status, createdAfter로 걸러낼 수 있습니다. |
401유효한 토큰 없음403계정이 Pro가 아님404작업이 없음
요청 본문
| 필드 | 형식 | 기본값 | 설명 |
|---|---|---|---|
| webhookUrl | string · — | — | 작업이 끝나면 한 번 호출되므로 직접 폴링할 필요가 없습니다. |
| config.colorspace | 'gray' | 'sRGB' · gray | gray | 출력 이미지의 색 공간이며, gray는 흑백 스캔한 문서입니다. |
| config.resolution | number · 72 | 72 | 출력 이미지의 해상도(DPI). |
| config.rotate | number · — | — | 문서 전체의 회전 각도(도). |
| config.rotate_var | number · — | — | 페이지마다 무작위로 적용되는 회전의 범위(도)로, 종이를 비뚤게 놓은 느낌을 냅니다. |
| config.blur | number · 0 | 0 | 흐림의 세기. |
| config.noise | number · 0 | 0 | 노이즈의 세기. |
| config.brightness | number · 1 | 1 | 밝기이며 1이면 그대로입니다. |
| config.contrast | number · 1 | 1 | 대비이며 1이면 그대로입니다. |
| config.border | boolean · false | false | 페이지에 스캔 테두리를 넣을지 여부. |
| config.output_format | 'image/png' | 'image/jpeg' · image/jpeg | image/jpeg | 페이지를 이미지로 렌더링할 때 쓰는 형식. |
모든 필드는 생략할 수 있습니다. '사용해 보기'의 초기값(해상도 150, 회전 1, 밝기와 대비 1.3)은 웹 앱이 권장하는 조합이며 API의 기본값이 아닙니다.
작업 객체에서 눈여겨볼 필드
- status
- created / processing / completed / failed이며, 아래 두 주소가 들어 있는지를 결정합니다.
- uploadURL
- created일 때만 제공되는, 유효 기간이 있는 미리 서명된 업로드 주소.
- downloadURL
- completed가 된 뒤에만 제공되는, 유효 기간이 있는 미리 서명된 다운로드 주소.
- inputUploadedAt / completedAt
- 원본 업로드가 끝난 시각과 작업이 끝난 시각이며, 그 차이가 처리 시간입니다.
자주 묻는 질문
API 스캔에 Pro가 필요한가요?
필요합니다. 유효한 토큰이 없으면 401이, Pro 역할이 없는 계정에는 403이 돌아옵니다. 업그레이드한 뒤 로그인하면 이 페이지에서 토큰을 받을 수 있습니다.
작업이 언제 끝났는지 어떻게 알 수 있나요?
두 가지 방법이 있습니다. GET /v1/scan-jobs/{jobID}를 폴링하거나, 작업을 만들 때 webhookUrl을 넘겨 완료 시 서비스가 한 번 알려 주게 하면 됩니다.
결과가 웹 페이지에서 스캔한 것과 같은가요?
같습니다. 양쪽 모두 같은 스캔 효과 구현을 쓰며, config의 색 공간, 해상도, 회전, 흐림, 노이즈, 밝기, 대비, 테두리는 웹 페이지의 같은 이름 설정에 대응합니다. 같은 매개변수면 같은 결과가 나오고, 차이는 처리 위치뿐입니다. 웹 페이지는 로컬에서, API는 원격 서비스에서 처리합니다.
업로드와 다운로드 주소를 저장해 두고 다시 써도 되나요?
권하지 않습니다. uploadURL과 downloadURL은 모두 유효 기간이 있는 미리 서명된 주소이며, 만료되면 작업을 다시 조회해 새로 받아야 합니다.
작업 처리에는 얼마나 걸리나요?
페이지 수와 해상도에 따라 다릅니다. 몇 페이지짜리 문서는 보통 몇 초 안에 끝나고, 해상도가 높거나 분량이 많을수록 오래 걸립니다. inputUploadedAt과 completedAt의 차이로 실제 소요 시간을 계산할 수 있습니다.
작업이 실패하면 어떻게 하나요?
상태가 failed로 바뀝니다. 흔한 원인은 유효한 PDF가 아닌 파일, 암호화 제한, 업로드 중단입니다. 파일이 정상적으로 열리는지 확인한 뒤 작업을 새로 만들면 됩니다.
지난 작업을 조회할 수 있나요?
있습니다. GET /v1/scan-jobs가 자신의 작업 목록을 보여 주고 jobID, status, createdAfter로 거를 수 있어 대사 확인이나 재다운로드에 쓰기 충분합니다.