Escaneo por API
El escaneo por API convierte un PDF en una copia escaneada realista mediante una llamada REST, ideal para procesos automatizados e integraciones. Crea el trabajo, sube el PDF y consulta el estado o espera el webhook: tres pasos desde cualquier entorno o lenguaje capaz de enviar una petición HTTP. El espacio de color, la resolución, la rotación, el desenfoque, el ruido, el brillo, el contraste y el borde son configurables.
Cómo funciona la llamada
Crear el trabajo
POST /v1/scan-jobs
Envía tu config y, opcionalmente, webhookUrl; recibes un jobID y una uploadURL prefirmada.
Subir el PDF
PUT {uploadURL}
Haz PUT del archivo directamente a la dirección S3 prefirmada del paso anterior: no hace falta token.
Recoger la copia escaneada
GET /v1/scan-jobs/{jobID}
Consulta el estado o espera el webhook; cuando el trabajo esté completed, descárgala desde downloadURL.
Dónde encaja
Producción por lotes desde el backend
Los contratos, las facturas y los informes generados en el servidor pasan directamente por el efecto de escaneo, sin que nadie repita el proceso a mano en la web.
Dentro de un sistema existente
Añade una acción «exportar copia escaneada» a un CRM, un ERP o un sistema de tickets y deja que llame a la API.
Procesos automatizados
CI, n8n, Zapier y similares lanzan un trabajo con un evento, y el webhook da paso al siguiente al terminar.
Grandes colas de archivos
Los trabajos son asíncronos: se crean y cada uno se procesa por su cuenta, con el progreso disponible mediante status y createdAfter.
Lenguajes y entornos
La API es HTTP y JSON estándar, así que cualquier lenguaje o plataforma de automatización capaz de enviar una petición puede usarla.
Ejemplos de código
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
El token pertenece a tu cuenta y puedes regenerarlo cuando quieras. El escaneo por API requiere una cuenta Pro: sin un token válido la API responde 401, y sin el rol Pro responde 403.
Todavía no hay tokens de API.
Copia este token ahora. Solo se mostrará una vez.
El escaneo por API es una función Pro
Esta cuenta aún no tiene Pro; al mejorarla, el token aparece aquí. Sin token, o sin el rol, la API responde 401 / 403.
Pruébalo
Ajusta los parámetros, mira cómo cambia el cuerpo de la petición y lanza las tres llamadas a la API.
Parámetros de escaneo
La prueba llama a la API con tu token y necesita una cuenta Pro; los parámetros y el cuerpo de la petición se pueden consultar libremente.
{
"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"
}
}Información del trabajo de escaneo
ejemplo{
"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-…"
}Referencia de la API
| Método | Ruta | Descripción |
|---|---|---|
| POST | /v1/scan-jobs | Crea un trabajo de escaneo. Envía config y, opcionalmente, webhookUrl; recibes el objeto del trabajo con status created y una uploadURL prefirmada. |
| PUT | {uploadURL}La dirección S3 prefirmada del paso anterior, que no está en api.lookscanned.io | Sube el PDF de origen con Content-Type: application/pdf y Content-Length. La dirección lleva su propia firma, así que no añadas la cabecera Authorization. |
| GET | /v1/scan-jobs/{jobID} | Consulta un único trabajo para hacer sondeo. Con created incluye uploadURL; con completed, downloadURL. |
| GET | /v1/scan-jobs | Lista tus propios trabajos, filtrados por jobID, status o createdAfter. |
401sin token válido403la cuenta no es Pro404el trabajo no existe
Cuerpo de la petición
| Campo | Tipo | Predeterminado | Descripción |
|---|---|---|---|
| webhookUrl | string · — | — | Se llama una vez cuando el trabajo termina, así no hace falta ir consultando el estado. |
| config.colorspace | 'gray' | 'sRGB' · gray | gray | Espacio de color de la imagen de salida; gray es un escaneo en escala de grises. |
| config.resolution | number · 72 | 72 | Resolución de la imagen de salida, en PPP. |
| config.rotate | number · — | — | Rotación de todo el documento, en grados. |
| config.rotate_var | number · — | — | Margen de la rotación aleatoria por página, en grados: el aspecto del papel colocado torcido. |
| config.blur | number · 0 | 0 | Cantidad de desenfoque. |
| config.noise | number · 0 | 0 | Cantidad de ruido. |
| config.brightness | number · 1 | 1 | Brillo; 1 lo deja sin cambios. |
| config.contrast | number · 1 | 1 | Contraste; 1 lo deja sin cambios. |
| config.border | boolean · false | false | Si se añade un borde de escaneo a la página. |
| config.output_format | 'image/png' | 'image/jpeg' · image/jpeg | image/jpeg | Formato de imagen al que se renderizan las páginas. |
Todos los campos son opcionales. Los valores de partida de «Pruébalo» (resolución 150, rotación 1, brillo y contraste 1,3) son la combinación que recomienda la aplicación web, no los valores predeterminados de la API.
Campos del objeto del trabajo que conviene vigilar
- status
- created / processing / completed / failed: determina si aparecen las dos direcciones siguientes.
- uploadURL
- Solo mientras está created. Dirección de subida prefirmada que caduca.
- downloadURL
- Solo cuando está completed. Dirección de descarga prefirmada que caduca.
- inputUploadedAt / completedAt
- Cuándo terminó de subirse el origen y cuándo terminó el trabajo; la diferencia es el tiempo de procesamiento.
Preguntas frecuentes
¿El escaneo por API necesita Pro?
Sí. Sin un token válido la API responde 401, y una cuenta sin el rol Pro recibe 403. Si mejoras la cuenta e inicias sesión, el token aparece en esta página.
¿Cómo sé cuándo ha terminado un trabajo?
De dos formas: consulta GET /v1/scan-jobs/{jobID} periódicamente, o pasa un webhookUrl al crear el trabajo y deja que el servicio te avise una vez.
¿El resultado es igual que al escanear en la web?
Sí. Ambos usan la misma implementación del efecto de escaneo, y el espacio de color, la resolución, la rotación, el desenfoque, el ruido, el brillo, el contraste y el borde de config son las mismas opciones de la web con otro nombre: con los mismos parámetros se obtiene el mismo resultado. Solo cambia dónde ocurre el trabajo: en local en la página, en remoto a través de la API.
¿Puedo guardar las direcciones de subida y descarga para reutilizarlas?
Es mejor no hacerlo. uploadURL y downloadURL son direcciones prefirmadas con caducidad; cuando expiran hay que volver a consultar el trabajo para obtener unas nuevas.
¿Cuánto tarda un trabajo?
Depende del número de páginas y de la resolución. Unas pocas páginas suelen terminar en segundos, y una resolución mayor o un documento más largo tardan más. La diferencia entre inputUploadedAt y completedAt es el tiempo real transcurrido.
¿Qué pasa si un trabajo falla?
El estado pasa a failed. Las causas habituales son un archivo que no es un PDF válido, restricciones de cifrado o una subida interrumpida. Comprueba que el archivo se abre y crea un trabajo nuevo.
¿Puedo consultar trabajos anteriores?
Sí. GET /v1/scan-jobs lista tus propios trabajos y admite filtros por jobID, status y createdAfter, suficiente para cuadrar cuentas o repetir una descarga.