API 文档

一个端点。格式版本 4。Base URL https://api.consensis.app。

认证

每个请求都带 API 密钥 —— Authorization: Bearer pk_live_… 或 X-API-Key 头。登录(Google 或邮箱,登录即创建账户)后在账户页签发密钥。明文密钥只显示一次;我们只保存摘要。无法匹配的密钥返回 401 —— 打错的密钥绝不会被悄悄降级为匿名试用,所以您始终知道任务记在哪个账户上。

匿名试用:API 上每个地址每天 10 页免费 —— 网页上传器另有每天 10 页 —— 无需账户。付费使用为预付 —— 每交付一页 0.06 美元(mode=batch 为 0.036 美元),最低充值 3.50 美元。失败的页面一律不计费。

免费试用 —— 不用密钥,不用账户

免费试用直接走 API:每个地址每天 10 页免费,完全不带 Authorization 头(网页上传器另有独立的 10 页;超出配额后响应带 Retry-After)。

# 完全匿名 —— 不用密钥,不用注册
curl -X POST https://api.consensis.app/v1/parse -F "[email protected]"
# → 202 {"id": "…", "status": "queued", ...} — 轮询方式与下面相同

三行代码快速上手

curl -X POST https://api.consensis.app/v1/parse \
     -H "Authorization: Bearer pk_live_…" \
     -F "[email protected]" -F "pages=10"
# → 202 {"id": "b7f395cf7fbc", "status": "queued", ...}

curl -H "Authorization: Bearer pk_live_…" \
     https://api.consensis.app/v1/parse/b7f395cf7fbc
# → 轮询直到 "status": "done",届时 usage 与 result 一并返回

curl -OJ -H "Authorization: Bearer pk_live_…" \
     "https://api.consensis.app/v1/parse/b7f395cf7fbc/export?format=xlsx"
选择导出格式:json 与 zip 包含全部内容(置信等级、来源、readings、bbox);xlsx 保留可见的置信标记 —— 有争议的单元格标红、未验证标黄 —— 另附一张扁平的 cells 工作表供审计;csv 只有值:它无法表达哪些单元格有争议,所以在信任度重要时请与 JSON 搭配使用。

异步 —— POST 立即返回 202;之后凭 id 跟进。10 页冷启动约需 600 秒;命中缓存则快得多。不要用 stage 推断是否完成 —— 那个字符串是给人看的;请盯 status。

Batch 模式 —— 0.036/页:在 POST 里加上 -F "mode=batch"。同样的提取、同样的保证、同样的结果结构;差别在耗时 —— 其中一个读取分支运行在 batch 端点上。九次运行实测 4–55 分钟,但这是观测值,不是承诺:上游 batch 服务的目标是 24 小时,并且明确说明了这一点。我们承诺的是底线保障 —— 如果 batch 分支在 4 小时内未返回结果,我们将改用实时管线重跑,您照样拿到结果,且仍按 batch 价格计费。差价由我们承担。轮询方式相同;任务会报告 "mode": "batch"。适合隔夜运行和价格优先于时效的大批量数据回填。

每个单元格携带的内容

这就是该 API 的核心价值所在。不是“更好的提取” —— 而是每个单元格都能自证其据。

{"value": "1432",
 "confidence": "verified",
 "sources": ["vision-1", "vision-2"],
 "bbox": [436.5, 577.7, 459.8, 591.9]}

两个独立读取一致,所以该单元格为 verified,且不带 readings —— 值就是值。

readings 是关键差别:读取不一致时您拿到每一个版本 —— 我们不会悄悄挑一个。挑错了的话,您根本不知道该看哪里。只在有分歧时出现:

{"value": "1432",
 "confidence": "disputed",
 "provisional": true,
 "sources": ["vision-1", "vision-2"],
 "readings": {"vision-1": "1432", "vision-2": "1482"}}

provisional

只在 confidence 为 disputed 时出现,且恒为 true。

来源不一致时我们仍然交付一个值 —— 数据必须可用,而空单元格等于悄悄丢掉一个值。但这个值是暂定的:采信前请对照 readings 检查,并且不要直接导入生产系统。

confidence

confidence含义
exact直接从结构化文件读出 —— 不涉及任何解读
verified多个独立读取结果一致
confirmed由人对照原始页面确定
reviewed由 AI 复核器对照原始页面图像裁定 —— 快速且审慎,但未经独立验证
resolved多数读取结果一致
unverified只有一次读取 —— 没有可交叉核对的对象
disputed独立读取结果互不一致 —— 这些单元格需要人来判断

sources

这些不是供应商名称。审计问的是“有几个独立读取”,不是“哪家供应商”。

sources是什么
ocr-1来自某一模型家族的 OCR 引擎
ocr-2来自另一家族的 OCR 引擎 —— 只有不同家族才能抓到同源偏差
ocr-3第三个 OCR 家族。当前服务未启用 —— 名称保留在词汇表里,因为契约不该随我们的配置而变。
pdf-layoutPDF 自身的版面几何(有表格线和文字层时最准确 —— 而且免费)
pdf-grid从填充矩形推断出的网格线
pdf-model读取 PDF 内部结构的训练版面模型 —— 不用 OCR,不依赖表格线
pdf-textPDF 的文字层
source-data直接从结构化文件读取(XLSX/CSV —— 无任何解读)
vision-review对照原页图像裁决
layout版面读取在此位置发现了一张图
ocr第二个读取独立地在同一框内发现了重叠的图 —— 正是它让一张图成为 verified
pdf-imagePDF 本身在此处有一个嵌入的图像对象。单靠它不算第二意见 —— 它只说明文件里含有图像,不说明我们框对了
vision-1两个独立视觉读取器之一,各自查看页面图像并重建表格网格
vision-2第二个视觉读取器 —— 与 vision-1 是不同的模型家族,所以两者能独立地不一致
vision-single由视觉读取,但此单元格只有一个读取 —— 没有第二意见佐证
vision-auditconfirmed by the audit pass — a separate reading of the original page image that reviewed the whole table and raised no objection to this cell.
vision-arbiter为解决两个视觉读取器之间分歧而做的额外读取 —— 不是那两个之一
vision-grid在首轮留下空洞或行错位处,通过重新读取页面图像重建网格而恢复
human-review由人在并排复核页上核对 —— 其他来源都是在解读页面;这一个是有人确认或修正了它

bbox

[x0, y0, x1, y1],单位为 PDF 点(1/72 英寸),原点在左上;页码在表格的 page 上。

覆盖率不是 100%,且因文档差异很大(实测 3%–48%)。usage.cells_with_bbox 准确报告您的文档中有多少单元格带 bbox —— 这个数字不藏着。

一张表长什么样

上一节说的是一个单元格带什么。这一节说表格是怎么围绕它们组织的。

{
 "page": 7,
 "index": 0,
 "order": null,
 "kind": null,
 "layout_disputed": true,
 "column_major": false,
 "header": [
  "model",
  "d"
 ],
 "row_groups": [],
 "header_cells": [
  {
   "value": "model",
   "confidence": "unverified",
   "sources": []
  },
  {
   "value": "d",
   "confidence": "unverified",
   "sources": []
  }
 ],
 "rows": [
  [
   {
    "value": "LB 08",
    "confidence": "unverified",
    "sources": []
   },
   {
    "value": "1432",
    "confidence": "verified",
    "sources": [
     "vision-1",
     "vision-2"
    ]
   }
  ]
 ]
}

header 是纯列名 —— 由 header_cells 派生,从不单独计算,所以两者不会漂移。header_cells 是同样的名称附上 confidence 与 sources:一个错误的列名会让整列意思都变,而您光看是看不出来的。

rows 是行的列表,每行是单元格的列表。column_major 告诉您源表是横向还是纵向排列;我们始终按行交付,这个字段记录的是页面原本的样子。

row_groups 是规格表上您看到的旋转侧向分类(尺寸 (mm)、载荷)—— 每行一个标签。它不是一列,因为列数是交叉比对的基础,但没有它您就分不清 C₀ 是载荷还是长度。表格没有分组时为空。

只在适用时出现的字段

这些遵循与 unreadable 相同的规则:每张表都带一个 false 只是噪音,您要扫的是例外。

字段何时出现
caption属于整张表的限定语(例如 HG - 15, 20),继承自它所在的分组。
kind我们认为这是哪一类表。每张表都有;值是一个简短标签,不是承诺。
layout_disputed几个版面读取对这里是否有表格意见不一。我们仍然交付提取出的内容 —— 但采信前请对照原页检查这一张。这与单元格级的 confidence 是不同的维度:它质疑的是表格是否存在,而不是某个值有没有读对。
ocr_unsupported没有独立的 OCR 读取在这张表上发现重叠内容。精神与上面相同:标记,不丢弃。
span_grid单元格跨行或跨列的表格背后的合并格网格。您拿到的扁平 rows 是这个网格拍平后的样子;这里是原始形状。
colbands列带 —— 把几列归到一个标签下的表头。
unfolded这张表是跨折页重新拼接的。
sheet工作表名。仅用于电子表格输入,目前已关闭 —— 字段说明保留,以便重新开放电子表格时契约不变。
订购代码是额外附加的,不是替代。当一张表是分段的订购代码表时,我们还会交付 segments(代码的每一段:叫什么、允许哪些值)和一个 example_code。表格本身原样交付 —— 您已经在读的东西照常可用。

每张表还带 bbox —— 整张表在页面上的框,坐标与单元格、正文和图像相同的 PDF 点(取该表各单元格框的并集;一个单元格框都没有时不给这个字段)—— 以及 order,它在页面阅读顺序中的位置。当版面读取对一页有几张表意见不一时,order 改由 bbox 量出:等于表格上方最后一个正文或图像块的 order。配合 pages[].width/height,足以把页面重建为结构化版面。

正文与图像

结果不只有 tables。每份文档还带 prose(正文 —— 标题与段落)和 images(裁出的图)。两者都按单元格同样的方式交叉比对,也都说明是谁佐证了它们。

prose

{"page": 7,
 "index": 2,
 "text": "Preload classes are selected according to the rail size.",
 "confidence": "verified",
 "sources": ["ocr-1", "ocr-2"],
 "bbox": [56.2, 98.6, 213.6, 236.9],
 "role": "paragraph",
 "level": 0,
 "order": 4}

每个文本块一条,按页编号。role 为 heading 或 paragraph;level 是标题层级(段落为 0);order 是在页面阅读顺序中的位置,与 images 共用,便于您交错排列。

渲染出这段文字的那个读取不算作它自己的证人。一个段落为 verified,是指对该页的另一个读取包含相同内容 —— 所以您看到的来源是那些一致的,绝不是被检查的那一个。段落边界不必一致:各读取切分文字的方式不同,那是排版差异,不是分歧。

实测:83–91% 的段落返回 verified,跨 7 份文档(2,812 个段落)。这个区间是真实的,取决于文档 —— 与 bbox 同样的提醒。扫描件处于低端。

images

{"page": 7,
 "index": 1,
 "path": "images/p007_00.png",
 "confidence": "verified",
 "bbox": [56.2, 98.6, 213.6, 236.9],
 "order": 3,
 "also_on": [9],
 "sources": ["layout", "ocr", "pdf-image"]}

path 相对于结果包(zip 导出);also_on 列出带有逐字节相同副本的其他页面 —— 重复项仅作报告,不予删除,因为同一张尺寸图确实会出现在两个料号下。

图像使用自己的一套来源词汇,因为问题是“这里有没有图”,不是“这个值有没有读对”。注意什么不能让一张图成为 verified:单独的 pdf-image。PDF 在该处含有嵌入图像只说明文件里有一张图,不说明我们框对了 —— 早先的版本把它当作充分条件,结果三份文档回来 100% verified,而“整张图漏掉”的概率在结构上为零。

pages

[{"page": 6, "width": 612.0, "height": 792.0},
 {"page": 7, "width": 612.0, "height": 792.0, "unreadable": true}]

当您请求了页范围时,旁边会有 source_pages:它把交付中的位置映射回您原始文件的页码,所以任何结果都能指回它来自的那一页。

您请求的每一页一条。width 与 height 是画布尺寸,单位 PDF 点 —— 没有它们,bbox 就没有参照框。

unreadable: true 不是指“这页没有表格”。它是指我们读取了这一页,而两次独立读取均失败 —— 这一页的任何内容都不在本次交付中,您应该查看原件。真正空白的页面根本不带任何标记。

这个标记只出现在失败的页面上,所以扫一遍很便宜。一页坏了不再拖垮整份文档:其他页面正常交付。如果每一页都失败,我们会拒绝该任务,而不是交给您一个空结果。

限制

限制值为什么是这个数
文件大小600 MB根据真实的扫描版产品目录(121、266、443、456 MB)量出来的 —— 不是猜的。
页数500 页我们量过的最厚产品目录是 235 页,而一份 500 页的文档已经要占用队列大约两小时 —— 再多,单个任务就会拖住后面所有人。免费试用覆盖 10 页;自托管安装可调 PDFPAGES_MAX_PAGES。
格式PDF扩展名不算数 —— 我们看内容。图像和电子表格目前已关闭 —— 管线能处理它们,但会带着各自的定价回归。
并发一次一份文档管线会占满 CPU;队列是单个 worker。多余的提交会排队 —— 不会失败。
重启最多重新排队 2 次被服务重启打断的任务会重新排队(有缓存,重跑几乎不花钱);超过次数就如实失败,而不是无限循环。
保留期完成后 24 小时我们不保留您的数据。结果同步可取;请在窗口期内取走裁图 —— 之后整个任务连同 OCR 缓存一并删除。DELETE /v1/parse/{id} 可提前删除。代价是:以后再送同一份文档会当作新任务处理 —— 并计费;没有任何副本留下来抄近路。
请设置 User-Agent。带 Python 标准库默认 UA(Python-urllib/3.x)的请求会在到达我们之前被 CDN 的反爬规则拒绝 —— 您拿到的是 CDN 错误页,不是下面列出的任何错误,看起来就像服务挂了。requests、httpx 和 curl 原样就可以;用 urllib 的话,随便设一个自己的 User-Agent。

错误

始终是 {"detail": "…"},写给人看 —— 绝不出现服务器路径,绝不出现密钥名。

状态码何时说明
400无法识别的格式文件扩展名不算数 —— 我们看内容。支持:PDF。
401未知的 API 密钥Authorization / X-API-Key 中的密钥不匹配任何账户。打错的密钥绝不会降级为匿名试用;要用试用请刻意省略密钥。
400我们已关闭的格式我们暂时只接受 PDF。图像和电子表格管线支持,但已关闭 —— 会带着各自的定价回归。这与上一条是不同的错误:文件没问题,只是我们现在不收。
400文件过大,或为空上限为 600 MB。
400pages 小于 1需要多少页就请求多少页;上限由服务器执行。
400不支持的导出格式GET 接受 xlsx、csv、json 和 zip(结果包:dataset.json 加上每一张裁图);POST /v1/export 接受 xlsx 和 csv。
413无状态导出的请求体过大POST /v1/export 接受最大 600 MB 的数据集。
402余额不足预付方案:这份文档的费用(页数 × 每页单价)超过了您的余额 —— 只有余额足以覆盖时任务才会运行。响应会说明您的剩余余额和这份文档需要多少 —— 充值后重新发送;不会排队,也不会计费。
429匿名试用的频率限制免注册试用有每小时/每天的页数限制(防滥用,不是定价)。响应带 Retry-After;已登录的预付账户不受此限制。
404找不到该提取任务job_id 错误,或服务重启后此任务没有恢复。
404没有裁出任何图时请求 format=zip结果包是裁出的图像加 dataset.json;没裁出任何图的文档(电子表格,或纯文字页)没有结果包。其他三种格式照常可用 —— json 带同样的数据。我们宁可 404,也不给您一个看起来像结果其实不是的压缩包。
409完成前请求导出请先轮询到 status 为 done。
500已完成但结果文件丢失这是我们这边的 bug。我们不会假装它还在运行、让您永远轮询下去。

还没有的

不可用原因
POST /v1/webhooks完成回调。目前请轮询 GET /v1/parse/{id};在 10 页(冷启动约 600 秒)的规模下,轮询完全够用。
page_range只能请求前 N 页,不能请求第 100–110 页。对厚产品目录这是实实在在的限制:一份 235 页的产品目录把规格表放在后半部分,而 pages 从第 1 页起算。
GET /v1/jobs, POST /v1/cancel/{id}列出与取消。目前不需要,所以没做。(删除已有:DELETE /v1/parse/{id}。)

老实说

扫描件会明显降级。我们把 PDF 的文字层当作额外的独立读取;没有它,我们实测的交叉比对覆盖率降到 1/3–1/18。text_layer 会在 202 响应中报告 —— 不必等运行结束才知道。

准确率因文档差异很大。我们怎么量、样本多大、在哪里失败都有记录,并附人工核对的下限。我们不会在这里放一句笼统的“99%+”。