One endpoint, OpenAI-compatible request and response envelope. Use any OpenAI SDK with a different base URL. Base URL: https://api.dokumentli.ai/v1
Send the pages followed by one text part that contains the spec as a JSON string. A PDF goes in as a file part, images (JPEG or PNG, one per page, in reading order) as image_url parts. Both must be base64 data: URLs. Remote URLs are not accepted. Streaming is not supported.
curl https://api.dokumentli.ai/v1/chat/completions \
-H "Authorization: Bearer $DOKUMENTLI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "dokumentli",
"messages": [{
"role": "user",
"content": [
{"type": "file", "file": {"filename": "invoice.pdf", "file_data":
"data:application/pdf;base64,JVBERi0xLjc..."}},
{"type": "text", "text": "{\"type\":\"extraction\",\"items\":{\"invoice_number\":{\"type\":\"string\"},\"invoice_date\":{\"type\":\"date\"},\"total_amount\":{\"type\":\"float\"}}}"}
]
}]
}'
from openai import OpenAI
import base64, json
client = OpenAI(
base_url="https://api.dokumentli.ai/v1",
api_key="dk_live_...",
)
spec = {"type": "extraction", "items": {
"invoice_number": {"type": "string"},
"invoice_date": {"type": "date"},
"total_amount": {"type": "float"},
}}
pdf = base64.b64encode(open("invoice.pdf", "rb").read()).decode()
r = client.chat.completions.create(
model="dokumentli",
messages=[{"role": "user", "content": [
{"type": "file", "file": {
"filename": "invoice.pdf",
"file_data": f"data:application/pdf;base64,{pdf}"}},
{"type": "text", "text": json.dumps(spec)},
]}],
)
print(json.loads(r.choices[0].message.content))
import OpenAI from "openai";
import fs from "node:fs";
const client = new OpenAI({
baseURL: "https://api.dokumentli.ai/v1",
apiKey: process.env.DOKUMENTLI_API_KEY,
});
const spec = { type: "extraction", items: {
invoice_number: { type: "string" },
invoice_date: { type: "date" },
total_amount: { type: "float" },
}};
const pdf = fs.readFileSync("invoice.pdf").toString("base64");
const r = await client.chat.completions.create({
model: "dokumentli",
messages: [{ role: "user", content: [
{ type: "file", file: {
filename: "invoice.pdf",
file_data: `data:application/pdf;base64,${pdf}` } },
{ type: "text", text: JSON.stringify(spec) },
]}],
});
console.log(JSON.parse(r.choices[0].message.content));
The result is the JSON string in choices[0].message.content. Field values are always strings; an empty string means not found. usage.pages and usage.cost_eur show what was billed.
HTTP/1.1 200 OK
x-ratelimit-limit-pages: 20
x-ratelimit-remaining-pages: 18
x-dokumentli-credit-remaining: 49.95
{
"id": "chatcmpl-9f2c...",
"model": "dokumentli",
"choices": [{
"index": 0,
"finish_reason": "stop",
"message": {"role": "assistant", "content":
"{\"invoice_number\":\"RE-2026-0412\",\"invoice_date\":\"2026-11-03\",\"total_amount\":\"1284.50\"}"}
}],
"usage": {"prompt_tokens": 1234, "completion_tokens": 56, "total_tokens": 1290,
"pages": 2, "cost_eur": 0.05}
}
The full spec reference (groups, field types, guidance) is at dokumentli-docs.parashift.io.
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/chat/completions | Spec and pages in, structured JSON out. |
| GET | /v1/models | Lists available models (dokumentli). |
| GET | /v1/usage | Credit balance, tier and limits of your organisation. |
One page is one PDF page or one image. Only successful requests (status 200) are billed. A request may contain up to 50 pages and 50 MB.
Limits apply per organisation: requests per minute, pages per minute and concurrent requests. Every response carries x-ratelimit-* headers. A 429 response includes Retry-After.
| Status | Code | Meaning | Billed |
|---|---|---|---|
| 400 | invalid_request_error | Malformed request or spec | No |
| 401 | invalid_api_key | Missing, wrong or revoked key | No |
| 402 | insufficient_credit | Balance too low for this request | No |
| 413 | request_too_large | Too many pages or bytes in one request | No |
| 429 | rate_limit_exceeded | Slow down, see Retry-After | No |
| 502 | api_error | Model unreachable or unusable output, retry with backoff | No |
| 503 | overloaded | Capacity is saturated, retry with backoff | No |
| 200 | Success. Unreadable documents return empty values. | Yes |