Get Presigned URL
curl --request POST \
--url https://api.example.com/api/validate_stream_presigned_url/ \
--header 'Content-Type: application/json' \
--data '
{
"document_category": "<string>",
"document_metadata": {},
"content_type": "<string>",
"filename": "<string>"
}
'import requests
url = "https://api.example.com/api/validate_stream_presigned_url/"
payload = {
"document_category": "<string>",
"document_metadata": {},
"content_type": "<string>",
"filename": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
document_category: '<string>',
document_metadata: {},
content_type: '<string>',
filename: '<string>'
})
};
fetch('https://api.example.com/api/validate_stream_presigned_url/', 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/validate_stream_presigned_url/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'document_category' => '<string>',
'document_metadata' => [
],
'content_type' => '<string>',
'filename' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$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/validate_stream_presigned_url/"
payload := strings.NewReader("{\n \"document_category\": \"<string>\",\n \"document_metadata\": {},\n \"content_type\": \"<string>\",\n \"filename\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
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/validate_stream_presigned_url/")
.header("Content-Type", "application/json")
.body("{\n \"document_category\": \"<string>\",\n \"document_metadata\": {},\n \"content_type\": \"<string>\",\n \"filename\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/validate_stream_presigned_url/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"document_category\": \"<string>\",\n \"document_metadata\": {},\n \"content_type\": \"<string>\",\n \"filename\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"job_id": "abc123def456",
"upload_url": "https://zerodrift-ai-v1-validation-documents-dev.s3.amazonaws.com/documents/abc123def456.bin?X-Amz-Algorithm=...",
"upload_method": "PUT",
"s3_bucket": "zerodrift-ai-v1-validation-documents-dev",
"s3_key": "documents/abc123def456.bin",
"expires_at": "2026-01-08T11:30:00Z",
"expires_in_seconds": 3600,
"content_type": "application/pdf",
"required_headers": {
"Content-Type": "application/pdf",
"x-amz-meta-job_id": "abc123def456",
"x-amz-meta-uploaded_at": "2026-01-08T10:30:00",
"x-amz-meta-filename": "my_document.pdf",
"x-amz-meta-document_category": "retail_investor_letter"
},
"instructions": {
"step_1": "Upload your document to the upload_url using HTTP PUT with ALL required_headers",
"step_2": "After successful upload, call POST /api/validate_stream_start/ with the job_id to start validation",
"note": "You MUST include all required_headers exactly as provided or S3 will return SignatureDoesNotMatch",
"example_curl": "curl -X PUT \"<upload_url>\" -H \"Content-Type: application/pdf\" -H \"x-amz-meta-job_id: abc123...\" ... --data-binary @your-document.pdf"
}
}
Validation
Get Presigned URL
Get an S3 presigned URL for uploading large documents
POST
/
api
/
validate_stream_presigned_url
/
Get Presigned URL
curl --request POST \
--url https://api.example.com/api/validate_stream_presigned_url/ \
--header 'Content-Type: application/json' \
--data '
{
"document_category": "<string>",
"document_metadata": {},
"content_type": "<string>",
"filename": "<string>"
}
'import requests
url = "https://api.example.com/api/validate_stream_presigned_url/"
payload = {
"document_category": "<string>",
"document_metadata": {},
"content_type": "<string>",
"filename": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
document_category: '<string>',
document_metadata: {},
content_type: '<string>',
filename: '<string>'
})
};
fetch('https://api.example.com/api/validate_stream_presigned_url/', 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/validate_stream_presigned_url/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'document_category' => '<string>',
'document_metadata' => [
],
'content_type' => '<string>',
'filename' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$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/validate_stream_presigned_url/"
payload := strings.NewReader("{\n \"document_category\": \"<string>\",\n \"document_metadata\": {},\n \"content_type\": \"<string>\",\n \"filename\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
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/validate_stream_presigned_url/")
.header("Content-Type", "application/json")
.body("{\n \"document_category\": \"<string>\",\n \"document_metadata\": {},\n \"content_type\": \"<string>\",\n \"filename\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/validate_stream_presigned_url/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"document_category\": \"<string>\",\n \"document_metadata\": {},\n \"content_type\": \"<string>\",\n \"filename\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"job_id": "abc123def456",
"upload_url": "https://zerodrift-ai-v1-validation-documents-dev.s3.amazonaws.com/documents/abc123def456.bin?X-Amz-Algorithm=...",
"upload_method": "PUT",
"s3_bucket": "zerodrift-ai-v1-validation-documents-dev",
"s3_key": "documents/abc123def456.bin",
"expires_at": "2026-01-08T11:30:00Z",
"expires_in_seconds": 3600,
"content_type": "application/pdf",
"required_headers": {
"Content-Type": "application/pdf",
"x-amz-meta-job_id": "abc123def456",
"x-amz-meta-uploaded_at": "2026-01-08T10:30:00",
"x-amz-meta-filename": "my_document.pdf",
"x-amz-meta-document_category": "retail_investor_letter"
},
"instructions": {
"step_1": "Upload your document to the upload_url using HTTP PUT with ALL required_headers",
"step_2": "After successful upload, call POST /api/validate_stream_start/ with the job_id to start validation",
"note": "You MUST include all required_headers exactly as provided or S3 will return SignatureDoesNotMatch",
"example_curl": "curl -X PUT \"<upload_url>\" -H \"Content-Type: application/pdf\" -H \"x-amz-meta-job_id: abc123...\" ... --data-binary @your-document.pdf"
}
}
Get an S3 presigned URL for uploading large documents (recommended for files > 6MB).
Request Body
Document category. Required if
document_metadata is not provided.Detailed metadata for precise rule matching. Required if
document_category is not provided.MIME type of the document (e.g.,
application/pdf for PDFs). Defaults to application/octet-stream if not specified.Original filename for reference
Response
Unique identifier for the validation job
S3 presigned URL for file upload
HTTP method to use for upload (always “PUT”)
S3 bucket name
S3 object key
ISO 8601 timestamp when the URL expires
Seconds until URL expiration
The content type to use when uploading
Important: All headers that MUST be included in the upload request. The presigned URL is signed with these headers, so omitting any will result in a
SignatureDoesNotMatch error from S3.Step-by-step instructions and example curl command for uploading
{
"job_id": "abc123def456",
"upload_url": "https://zerodrift-ai-v1-validation-documents-dev.s3.amazonaws.com/documents/abc123def456.bin?X-Amz-Algorithm=...",
"upload_method": "PUT",
"s3_bucket": "zerodrift-ai-v1-validation-documents-dev",
"s3_key": "documents/abc123def456.bin",
"expires_at": "2026-01-08T11:30:00Z",
"expires_in_seconds": 3600,
"content_type": "application/pdf",
"required_headers": {
"Content-Type": "application/pdf",
"x-amz-meta-job_id": "abc123def456",
"x-amz-meta-uploaded_at": "2026-01-08T10:30:00",
"x-amz-meta-filename": "my_document.pdf",
"x-amz-meta-document_category": "retail_investor_letter"
},
"instructions": {
"step_1": "Upload your document to the upload_url using HTTP PUT with ALL required_headers",
"step_2": "After successful upload, call POST /api/validate_stream_start/ with the job_id to start validation",
"note": "You MUST include all required_headers exactly as provided or S3 will return SignatureDoesNotMatch",
"example_curl": "curl -X PUT \"<upload_url>\" -H \"Content-Type: application/pdf\" -H \"x-amz-meta-job_id: abc123...\" ... --data-binary @your-document.pdf"
}
}
Required Headers: The presigned URL is signed with metadata headers. You must include ALL headers from the
required_headers field exactly as provided when uploading your file. Missing or incorrect headers will result in a SignatureDoesNotMatch error from S3.Example
# Step 1: Get presigned URL
RESPONSE=$(curl -s -X POST "https://{api-url}/api/validate_stream_presigned_url/" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"document_category": "retail_investor_letter",
"content_type": "application/pdf",
"filename": "my_document.pdf"
}')
# Extract values from response
UPLOAD_URL=$(echo $RESPONSE | jq -r '.upload_url')
JOB_ID=$(echo $RESPONSE | jq -r '.job_id')
# Step 2: Upload file with ALL required headers
# IMPORTANT: Include every header from required_headers or you'll get SignatureDoesNotMatch
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/pdf" \
-H "x-amz-meta-job_id: $JOB_ID" \
-H "x-amz-meta-uploaded_at: $(echo $RESPONSE | jq -r '.required_headers["x-amz-meta-uploaded_at"]')" \
-H "x-amz-meta-filename: my_document.pdf" \
-H "x-amz-meta-document_category: retail_investor_letter" \
--data-binary @my_document.pdf
echo "Job ID: $JOB_ID"
import requests
API_KEY = "YOUR_API_KEY"
API_BASE = "https://{api-url}"
# Step 1: Get presigned URL
response = requests.post(
f"{API_BASE}/api/validate_stream_presigned_url/",
headers={
"x-api-key": API_KEY,
"Content-Type": "application/json"
},
json={
"document_category": "retail_investor_letter",
"content_type": "application/pdf",
"filename": "my_document.pdf"
}
)
data = response.json()
job_id = data["job_id"]
upload_url = data["upload_url"]
required_headers = data["required_headers"]
# Step 2: Upload file to S3 with ALL required headers
# IMPORTANT: Include every header from required_headers
with open("my_document.pdf", "rb") as f:
requests.put(
upload_url,
headers=required_headers, # Use all required headers
data=f
)
print(f"Job ID: {job_id}")
const axios = require('axios');
const fs = require('fs');
const API_KEY = 'YOUR_API_KEY';
const API_BASE = 'https://{api-url}';
// Step 1: Get presigned URL
const response = await axios.post(
`${API_BASE}/api/validate_stream_presigned_url/`,
{
document_category: 'retail_investor_letter',
content_type: 'application/pdf',
filename: 'my_document.pdf'
},
{
headers: {
'x-api-key': API_KEY,
'Content-Type': 'application/json'
}
}
);
const { job_id, upload_url, required_headers } = response.data;
// Step 2: Upload file to S3 with ALL required headers
// IMPORTANT: Include every header from required_headers
const fileBuffer = fs.readFileSync('my_document.pdf');
await axios.put(upload_url, fileBuffer, {
headers: required_headers // Use all required headers
});
console.log(`Job ID: ${job_id}`);
⌘I

