在使用 API 之前先验证工作流程
先在工作站中开始,确认已审核的输出符合您的工作流程,然后再使用 API 进行可重复的提取运行。
Base: https://pdf2text.ai/api/v1
身份认证
所有 API 请求都需要在 Authorization 请求头中提供 Bearer 令牌。在验证了可重复的工作流程后,请从账户设置中生成密钥。
管理 API 密钥身份认证
# 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}`
};
使用限制
Firm 每月包含 1500 页,可用于已审核的工作站工作流程和经过验证的 API 提取运行。每处理一页,就会从该额度中扣除一页。
用量按处理的页数计算,而不是按 API 调用次数计算。上传文件以及下载结果或导出文件不会消耗页数,但再次运行提取会重新处理并再次消耗这些页数。
支持上传 PDF、PNG、JPEG 和 WebP,单个文件最大 50 MiB。当前部署还将整个 multipart 请求限制为 100 MiB,包括所有文件和表单开销。上传限制为每个请求 20 个文件、每个分组 100 个文件、每个文档或每次运行 500 页,以及每个分组存储 1,000 页。图像任一边最多 20,000 像素,且不超过 4000 万像素;以 OCR 渲染分辨率渲染后会超出这些栅格限制的 PDF 页面将被拒绝。
没有单独的 API 调用配额。提取运行是异步的,会进入处理队列,因此完成时间可能因文档大小和系统负载而有所不同。
合理使用请求限流的默认值为每个账户每小时 30 次上传、60 次运行启动和 600 次读取或导出。429 响应包含 Retry-After;请等待该时间间隔后再重试。
在依赖 API 自动化之前,请先使用工作站验证文档版式、审核规则和导出格式。
比较套餐上传文档
上传一个或多个 PDF、PNG、JPEG 或 WebP 文件以创建文档分组。在保存任何内容之前,系统会验证文件字节、页数和图像尺寸。上传成功后会返回 201 Created,并附带每个文档经过验证的 page_count。
端点
POST /api/v1/documents/upload/
参数
file / files
binary
PDF、PNG、JPEG 或 WebP(必填;上传多个文件时重复 files 字段)
grouping_id
uuid
可选。添加到您现有的某个 API 分组中。匿名工作区无法通过 UUID 认领,且在运行进行期间,其所属分组在完成前不可更改。
document_type
string
可选。分类提示。
password
string
可选。在此请求中打开受密码保护的 PDF。仅用于打开文件,不会保存。未提供时,此类 PDF 返回 encrypted_pdf;密码错误返回 pdf_password_incorrect;同一文件多次输错则返回 429。
上传文档
# 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"
}
]
}
仅可在分组首次运行之前向其追加文件。对于之后的发票,请创建新的分组;否则,未经筛选的运行会处理该分组中已有的每一份文档。
启动提取任务
为文档分组启动异步处理。如需 PDF2TEXT 在提取前对文档进行分类,请使用自动路由。
每个启动请求都要发送 Idempotency-Key 请求头。使用相同的键和请求体重复请求会返回同一次运行;将该键用于不同的请求会返回 409。在运行以失败终止后,请使用新的键来有意创建替代运行。新加入队列的运行会返回 202 Accepted。每个分组同一时间只能有一个运行处于活动状态,活动运行会预留其可处理的账户页数。
202 响应仅确认运行已被接受,并不代表所请求的每一页都有足够额度。如果共享账户余额小于该运行所需,API 会处理已预留的页面,并以 partial 状态结束,同时返回 credit_limit_reached=true。在将导入视为完成之前,请检查运行的最终状态。
回调 URL 必须使用公网 HTTPS,且不能重定向到其他目标地址。投递是异步的,请求超时时间为 10 秒;对于暂时性连接错误、HTTP 429 和 HTTP 5xx 响应,最多重试四次,退避间隔为 30 秒、2 分钟和 10 分钟。
提供 callback_secret 时,请对完整的原始请求体计算 HMAC-SHA256,并与 sha256= 的值进行比较,以验证 X-PDF2TEXT-Signature。使用 X-PDF2TEXT-Run-ID 或 X-PDF2TEXT-Idempotency-Key 对投递进行去重。
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.
端点
POST /api/v1/groups/{group_id}/runs/
请求体
auto_route
boolean
自动分类(默认:true)
extraction_mode
enum
auto、raw_text、financial_table、general_table 或 unified。如需使用自定义架构,请使用 template_id 或 template_slug。
template_id / template_slug
uuid / string
仅使用其中一个来强制指定模板
selected_pages
array
可选的非空 {docUid, pageNumber} 列表;任何无效页面都会导致整个请求被拒绝
construction_project_id
uuid
可选,API 账户拥有的项目
callback_url
string
Webhook URL
callback_secret
string
HMAC 签名密钥
启动提取任务
# 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
}
}
获取结果
轮询运行端点或使用 Webhook 获取提取结果。支持导出为 JSON 或 Excel 格式。
默认的运行响应为轻量格式,适合频繁轮询,且从不缓存。请读取嵌套 run 对象中的 run.status。最终状态为 complete、partial 和 error。达到最终状态后,请使用 include=results 请求一次,以获取 full_text、structured_output 和 documents。
保留 credits_remaining 是为了兼容,它记录该次运行后的历史余额。credits_remaining_after_run 明确表示同一个历史值,而 account_credits_remaining 报告生成响应时账户当前的共享余额。
端点列表
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
对于银行对账单,export.xlsx 还包含 QuickBooks CSV 和 Xero CSV 工作表。其日期使用您账户中保存的日期格式,否则使用对账单所在国家的顺序。传入 date_format(DD/MM/YYYY、MM/DD/YYYY、YYYY/MM/DD 或 YYYY-MM-DD)即可指定;其他工作表保持 YYYY-MM-DD。
响应状态码
获取结果
# 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 */ }
}
]
}
}
模板
列出可用的提取模板。使用模板 ID 强制指定提取模式。
端点
GET /api/v1/templates/
模板
# 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"
}
]
}
需要帮助?
使用审核队列检查提取出的字段,并保留审核上下文。
身份认证
# 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}`
};
上传文档
# 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"
}
]
}
仅可在分组首次运行之前向其追加文件。对于之后的发票,请创建新的分组;否则,未经筛选的运行会处理该分组中已有的每一份文档。
启动提取任务
# 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
}
}
获取结果
# 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 */ }
}
]
}
}
模板
# 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"
}
]
}