Valida el flujo de trabajo antes de usar la API
Empieza en la estación de trabajo, confirma que el resultado revisado coincide con tu flujo de trabajo y luego usa la API para ejecuciones de extracción repetibles.
Base: https://pdf2text.ai/api/v1
Autenticación
Todas las solicitudes a la API requieren un token Bearer en el encabezado Authorization. Genera claves desde la configuración de la cuenta después de validar un flujo de trabajo repetible.
Gestionar claves APIAutenticación
# Set your API key
export P2T_API_KEY="your_api_key_here"
# Include in all requests
curl -H "Authorization: Bearer $P2T_API_KEY" ...
import requests
import os
API_KEY = os.environ["P2T_API_KEY"]
headers = {"Authorization": f"Bearer {API_KEY}"}
const API_KEY = process.env.P2T_API_KEY;
const headers = {
'Authorization': `Bearer ${API_KEY}`
};
Límites de uso
Firm incluye 1500 páginas al mes para flujos de trabajo revisados en la estación de trabajo y ejecuciones de extracción validadas con la API. Cada página procesada consume una página de esa asignación.
El uso se mide por páginas procesadas, no por llamadas a la API. Subir archivos y descargar resultados o exportaciones no consume páginas, pero volver a ejecutar la extracción procesa y consume esas páginas de nuevo.
Se admiten subidas en PDF, PNG, JPEG y WebP, de hasta 50 MiB por archivo. La implementación actual también limita la solicitud multipart completa a 100 MiB, incluidos todos los archivos y la sobrecarga del formulario. Las subidas están limitadas a 20 archivos por solicitud, 100 archivos por grupo, 500 páginas por documento o ejecución y 1,000 páginas almacenadas por grupo. Las imágenes pueden tener hasta 20,000 píxeles por lado y 40 megapíxeles; se rechazan las páginas PDF que superarían esos límites de rasterización con la resolución de renderizado del OCR.
No hay una cuota separada de llamadas a la API. Las ejecuciones de extracción son asíncronas y entran en la cola de procesamiento, por lo que el tiempo de finalización puede variar según el tamaño del documento y la carga del sistema.
Los límites de uso razonable son, de forma predeterminada, 30 subidas, 60 inicios de ejecución y 600 lecturas o exportaciones por cuenta por hora. Una respuesta 429 incluye Retry-After; espera ese intervalo antes de volver a intentarlo.
Usa la estación de trabajo para validar los formatos de los documentos, las reglas de revisión y los formatos de exportación antes de depender de la automatización por API.
Comparar planesCargar Documentos
Sube uno o más archivos PDF, PNG, JPEG o WebP para crear un grupo de documentos. Los bytes de los archivos, el número de páginas y las dimensiones de las imágenes se verifican antes de guardar nada. Una subida correcta devuelve 201 Created con el page_count verificado de cada documento.
Endpoint
POST /api/v1/documents/upload/
Parámetros
file / files
binary
PDF, PNG, JPEG o WebP (obligatorio; repite el campo files para subir varios archivos)
grouping_id
uuid
Opcional. Agrégalo a uno de tus grupos de API existentes. Los espacios de trabajo anónimos no se pueden reclamar por UUID, y una ejecución activa hace que su grupo sea inmutable hasta que termine.
document_type
string
Opcional. Sugerencia de clasificación.
password
string
Opcional. Abre los PDF protegidos con contraseña en esta solicitud. Solo se usa para abrir los archivos y no se guarda. Sin ella, ese PDF devuelve encrypted_pdf; una incorrecta devuelve pdf_password_incorrect, y los intentos fallidos repetidos para un archivo devuelven 429.
Cargar Documentos
# Upload a PDF file
curl -X POST https://pdf2text.ai/api/v1/documents/upload/ \
-H "Authorization: Bearer $P2T_API_KEY" \
-F "file=@invoice.pdf"
# Add to existing group
curl -X POST https://pdf2text.ai/api/v1/documents/upload/ \
-H "Authorization: Bearer $P2T_API_KEY" \
-F "file=@invoice2.pdf" \
-F "grouping_id=a1b2c3d4-..."
import requests
url = "https://pdf2text.ai/api/v1/documents/upload/"
# Upload single file
with open("invoice.pdf", "rb") as f:
response = requests.post(
url,
headers=headers,
files={"file": f}
)
data = response.json()
group_id = data["group_id"]
const { readFile } = require('node:fs/promises');
const form = new FormData();
form.append(
'file',
new Blob([await readFile('invoice.pdf')], { type: 'application/pdf' }),
'invoice.pdf'
);
const response = await fetch(
'https://pdf2text.ai/api/v1/documents/upload/',
{
method: 'POST',
headers,
body: form
}
);
if (!response.ok) throw new Error(`Upload failed: ${response.status}`);
const { group_id } = await response.json();
{
"status": "success",
"group_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"documents": [
{
"doc_id": 42,
"uid": "1847f600-...",
"grouping_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"original_filename": "invoice.pdf",
"file_type": "pdf",
"page_count": 3,
"status": "pending"
}
]
}
Agrega archivos a un grupo solo antes de su primera ejecución. Para facturas posteriores, crea un grupo nuevo; de lo contrario, una ejecución sin filtros procesa todos los documentos que ya están en ese grupo.
Iniciar Ejecución de Extracción
Inicia el procesamiento asíncrono de un grupo de documentos. Usa el enrutamiento automático cuando PDF2TEXT deba clasificar los documentos antes de la extracción.
Envía un encabezado Idempotency-Key en cada solicitud de inicio. Repetir la misma clave y el mismo cuerpo devuelve la misma ejecución; reutilizar la clave para una solicitud diferente devuelve 409. Después de un fallo terminal, usa una clave nueva para crear intencionalmente una ejecución de reemplazo. Una ejecución recién puesta en cola devuelve 202 Accepted. Solo puede haber una ejecución activa por grupo a la vez, y las ejecuciones activas reservan las páginas de la cuenta que pueden procesar.
Una respuesta 202 confirma que la ejecución fue aceptada, no que todas las páginas solicitadas estén cubiertas. Si el saldo compartido de la cuenta es menor que la ejecución, la API procesa las páginas reservadas y termina como parcial con credit_limit_reached=true. Comprueba el estado terminal de la ejecución antes de dar la importación por completada.
Las URL de callback deben usar HTTPS público y no pueden redirigir a otro destino. La entrega es asíncrona, tiene un tiempo de espera de 10 segundos por solicitud y realiza hasta cuatro reintentos ante errores de conexión transitorios, respuestas HTTP 429 y HTTP 5xx, con intervalos de espera de 30 segundos, 2 minutos y 10 minutos.
Cuando se proporciona callback_secret, verifica X-PDF2TEXT-Signature calculando HMAC-SHA256 sobre el cuerpo exacto de la solicitud y comparándolo con el valor sha256=. Usa X-PDF2TEXT-Run-ID o X-PDF2TEXT-Idempotency-Key para eliminar entregas duplicadas.
The callback JSON body is {"event": "idp.run.completed", "run": {...}}; run uses the same full result shape as GET ?include=results. A signature header is sent only when callback_secret was included in the start request.
Endpoint
POST /api/v1/groups/{group_id}/runs/
Cuerpo de la Solicitud
auto_route
boolean
Clasificación automática (predeterminado: true)
extraction_mode
enum
auto, raw_text, financial_table, general_table o unified. Usa template_id o template_slug para un esquema personalizado.
template_id / template_slug
uuid / string
Usa exactamente uno para forzar una plantilla específica
selected_pages
array
Lista opcional no vacía de {docUid, pageNumber}; las páginas no válidas rechazan toda la solicitud
construction_project_id
uuid
Proyecto opcional perteneciente a la cuenta de API
callback_url
string
URL de Webhook
callback_secret
string
Secreto de firma HMAC
Iniciar Ejecución de Extracción
# Start extraction with auto-routing
curl -X POST https://pdf2text.ai/api/v1/groups/$GROUP_ID/runs/ \
-H "Authorization: Bearer $P2T_API_KEY" \
-H "Idempotency-Key: invoice-import-123" \
-H "Content-Type: application/json" \
-d '{"auto_route": true}'
# Start extraction run
url = f"https://pdf2text.ai/api/v1/groups/{group_id}/runs/"
response = requests.post(
url,
headers={**headers, "Idempotency-Key": "invoice-import-123"},
json={"auto_route": True}
)
run_data = response.json()
run_id = run_data["run_id"]
// Start extraction run
const response = await fetch(
`https://pdf2text.ai/api/v1/groups/${groupId}/runs/`,
{
method: 'POST',
headers: {
...headers,
'Idempotency-Key': 'invoice-import-123',
'Content-Type': 'application/json'
},
body: JSON.stringify({ auto_route: true })
}
);
const { run_id } = await response.json();
{
"status": "success",
"run_id": "b3143b91-...",
"job_id": "idp-run-b3143b91-...",
"group_name": "ocr_job_a1b2c3d4...",
"idempotent_replay": false,
"run": {
"id": "b3143b91-...",
"status": "queued",
"job_id": "idp-run-b3143b91-...",
"total_pages": 3,
"pages_processed": 0
}
}
Obtener Resultados
Consulta el endpoint de ejecución o usa webhooks para obtener los resultados de extracción. Exporta como JSON o Excel.
La respuesta predeterminada de la ejecución es ligera para consultas frecuentes y nunca se almacena en caché. Lee run.status dentro del objeto run anidado. Los estados terminales son complete, partial y error. Después de un estado terminal, solicita include=results una vez para obtener full_text, structured_output y documents.
credits_remaining se conserva por compatibilidad y registra el saldo histórico después de esa ejecución. credits_remaining_after_run nombra explícitamente el mismo valor histórico, mientras que account_credits_remaining indica el saldo compartido actual de la cuenta en el momento en que se genera la respuesta.
Endpoints
GET /api/v1/runs/{run_id}/
GET /api/v1/runs/{run_id}/?include=results
GET /api/v1/runs/{run_id}/export.json
GET /api/v1/runs/{run_id}/export.xlsx
GET /api/v1/runs/{run_id}/export.xlsx?date_format=MM/DD/YYYY
Para extractos bancarios, export.xlsx también incluye hojas CSV de QuickBooks y CSV de Xero. Sus fechas usan el formato guardado en tu cuenta o, si no, el orden del país del extracto. Pasa date_format (DD/MM/YYYY, MM/DD/YYYY, YYYY/MM/DD o YYYY-MM-DD) para elegirlo; las demás hojas mantienen YYYY-MM-DD.
Códigos de Estado de Respuesta
Obtener Resultados
# Lightweight status poll
curl https://pdf2text.ai/api/v1/runs/$RUN_ID/ \
-H "Authorization: Bearer $P2T_API_KEY"
# Fetch completed results once, then export
curl "https://pdf2text.ai/api/v1/runs/$RUN_ID/?include=results" \
-H "Authorization: Bearer $P2T_API_KEY"
curl -o results.xlsx \
https://pdf2text.ai/api/v1/runs/$RUN_ID/export.xlsx \
-H "Authorization: Bearer $P2T_API_KEY"
import time
run_url = f"https://pdf2text.ai/api/v1/runs/{run_id}/"
terminal = {"complete", "partial", "error"}
delay = 2
# Poll lightweight state with bounded exponential backoff
while True:
response = requests.get(run_url, headers=headers)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", delay))
time.sleep(retry_after)
delay = min(30, max(10, delay * 2))
continue
response.raise_for_status()
run = response.json()["run"]
if run["status"] in terminal:
break
time.sleep(delay)
delay = min(30, max(10, delay * 2))
if run["status"] == "error":
raise RuntimeError(run["error_message"])
results = requests.get(run_url, params={"include": "results"}, headers=headers).json()["run"]
excel = requests.get(
f"{run_url}export.xlsx",
headers=headers
)
with open("results.xlsx", "wb") as f:
f.write(excel.content)
// Poll lightweight state
const pollResults = async (runId) => {
const runUrl = `https://pdf2text.ai/api/v1/runs/${runId}/`;
const terminal = new Set(['complete', 'partial', 'error']);
let delay = 2000;
while (true) {
const response = await fetch(runUrl, { headers });
if (response.status === 429) {
const retryAfter = Number(response.headers.get('Retry-After') || delay / 1000);
await new Promise(r => setTimeout(r, retryAfter * 1000));
delay = Math.min(30000, Math.max(10000, delay * 2));
continue;
}
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const { run } = await response.json();
if (terminal.has(run.status)) {
if (run.status === 'error') throw new Error(run.error_message);
const resultResponse = await fetch(`${runUrl}?include=results`, { headers });
return (await resultResponse.json()).run;
}
await new Promise(r => setTimeout(r, delay));
delay = Math.min(30000, Math.max(10000, delay * 2));
}
};
const results = await pollResults(runId);
{
"status": "success",
"run": {
"id": "b3143b91-...",
"status": "complete",
"total_pages": 3,
"pages_processed": 3,
"credits_remaining_after_run": 497,
"account_credits_remaining": 497,
"full_text": "...",
"structured_output": { /* grouped output */ },
"documents": [
{
"doc_id": 42,
"document_name": "invoice.pdf",
"ocr_text": "...",
"structured_output": { /* document output */ }
}
]
}
}
Plantillas
Lista las plantillas de extracción disponibles. Usa los IDs de plantilla para forzar esquemas de extracción específicos.
Endpoint
GET /api/v1/templates/
Plantillas
# List available templates
curl https://pdf2text.ai/api/v1/templates/ \
-H "Authorization: Bearer $P2T_API_KEY"
# List available templates
response = requests.get(
"https://pdf2text.ai/api/v1/templates/",
headers=headers
)
templates = response.json()["templates"]
for t in templates:
print(f"{t['name']}: {t['id']}")
// List available templates
const response = await fetch(
'https://pdf2text.ai/api/v1/templates/',
{ headers }
);
const { templates } = await response.json();
templates.forEach(t => console.log(`${t.name}: ${t.id}`));
{
"status": "success",
"templates": [
{
"id": "198aa5be-ea8b-4d67-bdb5-625f5d7d29e8",
"slug": "invoice",
"name": "Invoice",
"description": "Standard invoice extraction",
"extraction_mode": "financial_table"
},
{
"id": "4317707a-f0da-4c2f-a1ec-d0119d96b55c",
"slug": "receipt",
"name": "Receipt",
"description": "Receipt and expense extraction",
"extraction_mode": "financial_table"
}
]
}
¿Necesitas Ayuda?
Usa la cola de revisión para inspeccionar los campos extraídos y conservar el contexto de la revisión.
Autenticación
# Set your API key
export P2T_API_KEY="your_api_key_here"
# Include in all requests
curl -H "Authorization: Bearer $P2T_API_KEY" ...
import requests
import os
API_KEY = os.environ["P2T_API_KEY"]
headers = {"Authorization": f"Bearer {API_KEY}"}
const API_KEY = process.env.P2T_API_KEY;
const headers = {
'Authorization': `Bearer ${API_KEY}`
};
Cargar Documentos
# Upload a PDF file
curl -X POST https://pdf2text.ai/api/v1/documents/upload/ \
-H "Authorization: Bearer $P2T_API_KEY" \
-F "file=@invoice.pdf"
# Add to existing group
curl -X POST https://pdf2text.ai/api/v1/documents/upload/ \
-H "Authorization: Bearer $P2T_API_KEY" \
-F "file=@invoice2.pdf" \
-F "grouping_id=a1b2c3d4-..."
import requests
url = "https://pdf2text.ai/api/v1/documents/upload/"
# Upload single file
with open("invoice.pdf", "rb") as f:
response = requests.post(
url,
headers=headers,
files={"file": f}
)
data = response.json()
group_id = data["group_id"]
const { readFile } = require('node:fs/promises');
const form = new FormData();
form.append(
'file',
new Blob([await readFile('invoice.pdf')], { type: 'application/pdf' }),
'invoice.pdf'
);
const response = await fetch(
'https://pdf2text.ai/api/v1/documents/upload/',
{
method: 'POST',
headers,
body: form
}
);
if (!response.ok) throw new Error(`Upload failed: ${response.status}`);
const { group_id } = await response.json();
{
"status": "success",
"group_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"documents": [
{
"doc_id": 42,
"uid": "1847f600-...",
"grouping_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"original_filename": "invoice.pdf",
"file_type": "pdf",
"page_count": 3,
"status": "pending"
}
]
}
Agrega archivos a un grupo solo antes de su primera ejecución. Para facturas posteriores, crea un grupo nuevo; de lo contrario, una ejecución sin filtros procesa todos los documentos que ya están en ese grupo.
Iniciar Ejecución de Extracción
# Start extraction with auto-routing
curl -X POST https://pdf2text.ai/api/v1/groups/$GROUP_ID/runs/ \
-H "Authorization: Bearer $P2T_API_KEY" \
-H "Idempotency-Key: invoice-import-123" \
-H "Content-Type: application/json" \
-d '{"auto_route": true}'
# Start extraction run
url = f"https://pdf2text.ai/api/v1/groups/{group_id}/runs/"
response = requests.post(
url,
headers={**headers, "Idempotency-Key": "invoice-import-123"},
json={"auto_route": True}
)
run_data = response.json()
run_id = run_data["run_id"]
// Start extraction run
const response = await fetch(
`https://pdf2text.ai/api/v1/groups/${groupId}/runs/`,
{
method: 'POST',
headers: {
...headers,
'Idempotency-Key': 'invoice-import-123',
'Content-Type': 'application/json'
},
body: JSON.stringify({ auto_route: true })
}
);
const { run_id } = await response.json();
{
"status": "success",
"run_id": "b3143b91-...",
"job_id": "idp-run-b3143b91-...",
"group_name": "ocr_job_a1b2c3d4...",
"idempotent_replay": false,
"run": {
"id": "b3143b91-...",
"status": "queued",
"job_id": "idp-run-b3143b91-...",
"total_pages": 3,
"pages_processed": 0
}
}
Obtener Resultados
# Lightweight status poll
curl https://pdf2text.ai/api/v1/runs/$RUN_ID/ \
-H "Authorization: Bearer $P2T_API_KEY"
# Fetch completed results once, then export
curl "https://pdf2text.ai/api/v1/runs/$RUN_ID/?include=results" \
-H "Authorization: Bearer $P2T_API_KEY"
curl -o results.xlsx \
https://pdf2text.ai/api/v1/runs/$RUN_ID/export.xlsx \
-H "Authorization: Bearer $P2T_API_KEY"
import time
run_url = f"https://pdf2text.ai/api/v1/runs/{run_id}/"
terminal = {"complete", "partial", "error"}
delay = 2
# Poll lightweight state with bounded exponential backoff
while True:
response = requests.get(run_url, headers=headers)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", delay))
time.sleep(retry_after)
delay = min(30, max(10, delay * 2))
continue
response.raise_for_status()
run = response.json()["run"]
if run["status"] in terminal:
break
time.sleep(delay)
delay = min(30, max(10, delay * 2))
if run["status"] == "error":
raise RuntimeError(run["error_message"])
results = requests.get(run_url, params={"include": "results"}, headers=headers).json()["run"]
excel = requests.get(
f"{run_url}export.xlsx",
headers=headers
)
with open("results.xlsx", "wb") as f:
f.write(excel.content)
// Poll lightweight state
const pollResults = async (runId) => {
const runUrl = `https://pdf2text.ai/api/v1/runs/${runId}/`;
const terminal = new Set(['complete', 'partial', 'error']);
let delay = 2000;
while (true) {
const response = await fetch(runUrl, { headers });
if (response.status === 429) {
const retryAfter = Number(response.headers.get('Retry-After') || delay / 1000);
await new Promise(r => setTimeout(r, retryAfter * 1000));
delay = Math.min(30000, Math.max(10000, delay * 2));
continue;
}
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const { run } = await response.json();
if (terminal.has(run.status)) {
if (run.status === 'error') throw new Error(run.error_message);
const resultResponse = await fetch(`${runUrl}?include=results`, { headers });
return (await resultResponse.json()).run;
}
await new Promise(r => setTimeout(r, delay));
delay = Math.min(30000, Math.max(10000, delay * 2));
}
};
const results = await pollResults(runId);
{
"status": "success",
"run": {
"id": "b3143b91-...",
"status": "complete",
"total_pages": 3,
"pages_processed": 3,
"credits_remaining_after_run": 497,
"account_credits_remaining": 497,
"full_text": "...",
"structured_output": { /* grouped output */ },
"documents": [
{
"doc_id": 42,
"document_name": "invoice.pdf",
"ocr_text": "...",
"structured_output": { /* document output */ }
}
]
}
}
Plantillas
# List available templates
curl https://pdf2text.ai/api/v1/templates/ \
-H "Authorization: Bearer $P2T_API_KEY"
# List available templates
response = requests.get(
"https://pdf2text.ai/api/v1/templates/",
headers=headers
)
templates = response.json()["templates"]
for t in templates:
print(f"{t['name']}: {t['id']}")
// List available templates
const response = await fetch(
'https://pdf2text.ai/api/v1/templates/',
{ headers }
);
const { templates } = await response.json();
templates.forEach(t => console.log(`${t.name}: ${t.id}`));
{
"status": "success",
"templates": [
{
"id": "198aa5be-ea8b-4d67-bdb5-625f5d7d29e8",
"slug": "invoice",
"name": "Invoice",
"description": "Standard invoice extraction",
"extraction_mode": "financial_table"
},
{
"id": "4317707a-f0da-4c2f-a1ec-d0119d96b55c",
"slug": "receipt",
"name": "Receipt",
"description": "Receipt and expense extraction",
"extraction_mode": "financial_table"
}
]
}