curl --request POST \
--url https://api.example.com/api/v1/analyze/cross \
--header 'Content-Type: multipart/form-data' \
--form 'schema=<string>' \
--form 'urls=<string>' \
--form 'url_headers=<string>' \
--form 'document_labels=<string>' \
--form include_steps=false \
--form sync=true \
--form 'webhook_url=<string>' \
--form files.items='@example-file'import requests
url = "https://api.example.com/api/v1/analyze/cross"
files = { "files.items": ("example-file", open("example-file", "rb")) }
payload = {
"schema": "<string>",
"urls": "<string>",
"url_headers": "<string>",
"document_labels": "<string>",
"include_steps": "false",
"sync": "true",
"webhook_url": "<string>"
}
response = requests.post(url, data=payload, files=files)
print(response.text)const form = new FormData();
form.append('schema', '<string>');
form.append('urls', '<string>');
form.append('url_headers', '<string>');
form.append('document_labels', '<string>');
form.append('include_steps', 'false');
form.append('sync', 'true');
form.append('webhook_url', '<string>');
form.append('files.items', '{
"fileName": "example-file"
}');
const options = {method: 'POST'};
options.body = form;
fetch('https://api.example.com/api/v1/analyze/cross', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v1/analyze/cross",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"schema\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"urls\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"url_headers\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document_labels\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"include_steps\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"sync\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/v1/analyze/cross"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"schema\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"urls\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"url_headers\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document_labels\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"include_steps\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"sync\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/api/v1/analyze/cross")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"schema\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"urls\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"url_headers\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document_labels\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"include_steps\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"sync\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/analyze/cross")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"schema\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"urls\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"url_headers\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document_labels\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"include_steps\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"sync\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"confidence": {
"rates_match": 0.95
},
"credits_used": 35,
"data": {
"rates_match": false
},
"documents": [
{
"content_type": "application/pdf",
"filename": "invoice_042.pdf",
"label": "invoice",
"text_length": 1200,
"total_pages": 2
},
{
"content_type": "application/pdf",
"filename": "service_agreement.pdf",
"label": "contract",
"text_length": 15000,
"total_pages": 8
}
],
"model_used": "gpt-5.1",
"reasoning": {
"rates_match": "Invoice hourly rate ($150) differs from contract rate ($125)"
},
"sources": {
"rates_match": [
"[invoice] \"Rate: $150.00/hr\"",
"[contract] \"Hourly rate: $125.00\""
]
},
"success": true,
"total_pages": 10,
"total_text_length": 16200
}{
"detail": "File size exceeds maximum allowed (50MB)",
"error": "validation_error",
"success": false
}{
"detail": "File size exceeds maximum allowed (50MB)",
"error": "validation_error",
"success": false
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}{
"detail": "File size exceeds maximum allowed (50MB)",
"error": "validation_error",
"success": false
}{
"detail": "File size exceeds maximum allowed (50MB)",
"error": "validation_error",
"success": false
}{
"detail": "File size exceeds maximum allowed (50MB)",
"error": "validation_error",
"success": false
}Cross-Document Analysis
Analyze and cross-reference multiple documents together. Unlike /analyze (single
document) or /analyze/batch (many documents independently), this endpoint lets
the agent reason across all documents simultaneously.
Use cases:
- Validate an invoice against a contract or purchase order
- Check that a report’s numbers match a source spreadsheet
- Compare terms across multiple agreements
- Reconcile data from different sources
Upload 2-5 files and/or provide URLs. Each document gets a label (auto-generated from filename, or you can provide custom labels). The agent can search, read, and compare data across all documents.
Provide a typed schema — each field has a type and description.
Example:
{
"rates_match": {"type": "boolean", "description": "Do the hourly rates on the invoice match the contract?"},
"total_valid": {"type": "boolean", "description": "Does the invoice total equal the sum of line items?"},
"correct_vendor": {"type": "boolean", "description": "Is the vendor name on the invoice the same as the contracting party?"}
}
Pricing: 5 credits/document + 3 credits/page (minimum 10).
curl --request POST \
--url https://api.example.com/api/v1/analyze/cross \
--header 'Content-Type: multipart/form-data' \
--form 'schema=<string>' \
--form 'urls=<string>' \
--form 'url_headers=<string>' \
--form 'document_labels=<string>' \
--form include_steps=false \
--form sync=true \
--form 'webhook_url=<string>' \
--form files.items='@example-file'import requests
url = "https://api.example.com/api/v1/analyze/cross"
files = { "files.items": ("example-file", open("example-file", "rb")) }
payload = {
"schema": "<string>",
"urls": "<string>",
"url_headers": "<string>",
"document_labels": "<string>",
"include_steps": "false",
"sync": "true",
"webhook_url": "<string>"
}
response = requests.post(url, data=payload, files=files)
print(response.text)const form = new FormData();
form.append('schema', '<string>');
form.append('urls', '<string>');
form.append('url_headers', '<string>');
form.append('document_labels', '<string>');
form.append('include_steps', 'false');
form.append('sync', 'true');
form.append('webhook_url', '<string>');
form.append('files.items', '{
"fileName": "example-file"
}');
const options = {method: 'POST'};
options.body = form;
fetch('https://api.example.com/api/v1/analyze/cross', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v1/analyze/cross",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"schema\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"urls\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"url_headers\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document_labels\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"include_steps\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"sync\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/v1/analyze/cross"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"schema\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"urls\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"url_headers\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document_labels\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"include_steps\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"sync\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/api/v1/analyze/cross")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"schema\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"urls\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"url_headers\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document_labels\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"include_steps\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"sync\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/analyze/cross")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"schema\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"urls\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"url_headers\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document_labels\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"include_steps\"\r\n\r\nfalse\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"sync\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"confidence": {
"rates_match": 0.95
},
"credits_used": 35,
"data": {
"rates_match": false
},
"documents": [
{
"content_type": "application/pdf",
"filename": "invoice_042.pdf",
"label": "invoice",
"text_length": 1200,
"total_pages": 2
},
{
"content_type": "application/pdf",
"filename": "service_agreement.pdf",
"label": "contract",
"text_length": 15000,
"total_pages": 8
}
],
"model_used": "gpt-5.1",
"reasoning": {
"rates_match": "Invoice hourly rate ($150) differs from contract rate ($125)"
},
"sources": {
"rates_match": [
"[invoice] \"Rate: $150.00/hr\"",
"[contract] \"Hourly rate: $125.00\""
]
},
"success": true,
"total_pages": 10,
"total_text_length": 16200
}{
"detail": "File size exceeds maximum allowed (50MB)",
"error": "validation_error",
"success": false
}{
"detail": "File size exceeds maximum allowed (50MB)",
"error": "validation_error",
"success": false
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}{
"detail": "File size exceeds maximum allowed (50MB)",
"error": "validation_error",
"success": false
}{
"detail": "File size exceeds maximum allowed (50MB)",
"error": "validation_error",
"success": false
}{
"detail": "File size exceeds maximum allowed (50MB)",
"error": "validation_error",
"success": false
}Headers
Body
JSON schema — keys are field names, values describe what to validate or compute across documents
Files to analyze (2-5). Labeled by filename or via document_labels.
JSON array of URLs, or JSON object mapping labels to URLs (e.g. {"invoice": "https://..."}). Combined with files, total must be 2-5.
JSON-encoded headers for URL auth
JSON array of labels for the uploaded files, in order. Must match files count. E.g. ["invoice", "contract"]
Include the agent's reasoning trace in each answer
Set to false to process asynchronously. Returns a task_id to poll.
URL to POST the result to when async processing completes. Signed with HMAC-SHA256 (see GET /api/v1/jobs/{task_id}/deliveries for verification docs).
Response
Successful Response
Response for cross-document analysis — flat parallel dicts matching /extract.
Computed/derived values keyed by field name
Metadata for each document in the analysis
Show child attributes
Show child attributes
Model used for analysis
Combined text length across all documents
Combined page count across all documents
Per-field confidence scores (0.0–1.0)
Show child attributes
Show child attributes
Per-field source text snippets (prefixed with [doc_label])
Show child attributes
Show child attributes
Per-field step-by-step explanation referencing specific documents
Show child attributes
Show child attributes
Per-field agent reasoning trace. Empty unless include_steps=true.
Show child attributes
Show child attributes
Credits consumed
Schema format: always 'typed'
typed