Get Import Presigned URL
curl --request POST \
--url https://api.zerodrift.ai/api/policies/import/presigned_url \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"filename": "<string>",
"content_type": "<string>"
}
'import requests
url = "https://api.zerodrift.ai/api/policies/import/presigned_url"
payload = {
"filename": "<string>",
"content_type": "<string>"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({filename: '<string>', content_type: '<string>'})
};
fetch('https://api.zerodrift.ai/api/policies/import/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.zerodrift.ai/api/policies/import/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([
'filename' => '<string>',
'content_type' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$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.zerodrift.ai/api/policies/import/presigned_url"
payload := strings.NewReader("{\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
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.zerodrift.ai/api/policies/import/presigned_url")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zerodrift.ai/api/policies/import/presigned_url")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"import_id": "550e8400-e29b-41d4-a716-446655440000",
"upload_url": "https://example-bucket.s3.amazonaws.com/policy-imports/raw/550e8400-e29b-41d4-a716-446655440000.bin?X-Amz-Algorithm=...",
"upload_method": "PUT",
"s3_bucket": "example-bucket",
"s3_key": "policy-imports/raw/550e8400-e29b-41d4-a716-446655440000.bin",
"required_headers": {
"Content-Type": "application/pdf"
},
"expires_in_seconds": 900,
"expires_at": "2026-06-15T10:45:00Z",
"max_upload_bytes": 1073741824,
"next_steps": {
"step_1": "PUT the raw file bytes to upload_url with the exact required_headers",
"step_2": "POST /api/policies/import/start with { \"import_id\": \"550e8400-...\" } to begin extraction"
}
}
Policy
Get Import Presigned URL
Get an S3 presigned URL for importing large policy files
POST
/
api
/
policies
/
import
/
presigned_url
Get Import Presigned URL
curl --request POST \
--url https://api.zerodrift.ai/api/policies/import/presigned_url \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"filename": "<string>",
"content_type": "<string>"
}
'import requests
url = "https://api.zerodrift.ai/api/policies/import/presigned_url"
payload = {
"filename": "<string>",
"content_type": "<string>"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({filename: '<string>', content_type: '<string>'})
};
fetch('https://api.zerodrift.ai/api/policies/import/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.zerodrift.ai/api/policies/import/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([
'filename' => '<string>',
'content_type' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$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.zerodrift.ai/api/policies/import/presigned_url"
payload := strings.NewReader("{\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
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.zerodrift.ai/api/policies/import/presigned_url")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zerodrift.ai/api/policies/import/presigned_url")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"filename\": \"<string>\",\n \"content_type\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"import_id": "550e8400-e29b-41d4-a716-446655440000",
"upload_url": "https://example-bucket.s3.amazonaws.com/policy-imports/raw/550e8400-e29b-41d4-a716-446655440000.bin?X-Amz-Algorithm=...",
"upload_method": "PUT",
"s3_bucket": "example-bucket",
"s3_key": "policy-imports/raw/550e8400-e29b-41d4-a716-446655440000.bin",
"required_headers": {
"Content-Type": "application/pdf"
},
"expires_in_seconds": 900,
"expires_at": "2026-06-15T10:45:00Z",
"max_upload_bytes": 1073741824,
"next_steps": {
"step_1": "PUT the raw file bytes to upload_url with the exact required_headers",
"step_2": "POST /api/policies/import/start with { \"import_id\": \"550e8400-...\" } to begin extraction"
}
}
Step 1 of the large-file custom policy import flow. Returns an
import_id and an S3 presigned
upload_url so you can upload large policy documents directly to S3, bypassing the API Gateway
and Lambda payload limits that cap the inline Import Policy
endpoint.
After uploading, call Start Import with the
import_id to begin rule extraction.
Use the inline Import Policy endpoint for plain
text and small files. Use this presigned flow for large files (above a few MB). Maximum upload
size is 1 GB.Large documents (e.g. a multi-hundred-page regulatory handbook) are fully extracted — rule
extraction runs in the background in chunks, so the entire document is analyzed, not just the
first pages. The trade-off is time: a very large document can stay in
processing for several
minutes. Keep polling Get Import Details
until the status leaves processing.Request Body
string
Original filename, for reference/audit.
string
default:"application/pdf"
MIME type of the file you will upload. Must match the
Content-Type header you send on the PUT.Response
string
Unique identifier for the import. Pass this to Start Import after uploading.
string
S3 presigned URL for the file upload (HTTP PUT).
string
HTTP method to use for upload (always
PUT).string
S3 bucket name.
string
S3 object key the file will be uploaded to.
object
Important: Headers that MUST be included on the upload PUT. The presigned URL is signed
with these, so omitting any results in a
SignatureDoesNotMatch error from S3.integer
Seconds until the presigned URL expires (default 900).
string
ISO 8601 timestamp when the URL expires.
integer
Maximum allowed upload size in bytes, enforced at Start Import.
object
Step-by-step instructions for uploading and starting the import.
{
"import_id": "550e8400-e29b-41d4-a716-446655440000",
"upload_url": "https://example-bucket.s3.amazonaws.com/policy-imports/raw/550e8400-e29b-41d4-a716-446655440000.bin?X-Amz-Algorithm=...",
"upload_method": "PUT",
"s3_bucket": "example-bucket",
"s3_key": "policy-imports/raw/550e8400-e29b-41d4-a716-446655440000.bin",
"required_headers": {
"Content-Type": "application/pdf"
},
"expires_in_seconds": 900,
"expires_at": "2026-06-15T10:45:00Z",
"max_upload_bytes": 1073741824,
"next_steps": {
"step_1": "PUT the raw file bytes to upload_url with the exact required_headers",
"step_2": "POST /api/policies/import/start with { \"import_id\": \"550e8400-...\" } to begin extraction"
}
}
Required headers: Include all headers from
required_headers exactly as provided when uploading your file. Missing or mismatched headers (including Content-Type) can cause S3 to reject the upload with SignatureDoesNotMatch.Example
Get a presigned URL from this endpoint, thenPUT the file directly to S3
with every header from required_headers. Rule extraction still needs a
follow-up Start Import call.
# Step 1: Get the presigned URL
RESPONSE=$(curl -s -X POST "https://api.zerodrift.ai/api/policies/import/presigned_url" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filename": "compliance-policy.pdf",
"content_type": "application/pdf"
}')
IMPORT_ID=$(printf '%s' "$RESPONSE" | jq -r '.import_id')
UPLOAD_URL=$(printf '%s' "$RESPONSE" | jq -r '.upload_url')
# Build curl -H args from required_headers
CURL_HEADERS=()
while IFS=$'\t' read -r key value; do
CURL_HEADERS+=(-H "$key: $value")
done < <(printf '%s' "$RESPONSE" | jq -r '.required_headers | to_entries[] | "\(.key)\t\(.value)"')
# Step 2: Upload the raw file directly to S3 (no API key)
curl -X PUT "$UPLOAD_URL" \
"${CURL_HEADERS[@]}" \
--data-binary @compliance-policy.pdf
echo "Import ID: $IMPORT_ID"
# Step 3: call POST /api/policies/import/start with this import_id
import requests
API_KEY = "YOUR_API_KEY"
API_BASE = "https://api.zerodrift.ai"
# Step 1: Get the presigned URL
resp = requests.post(
f"{API_BASE}/api/policies/import/presigned_url",
headers={"x-api-key": API_KEY, "Content-Type": "application/json"},
json={"filename": "compliance-policy.pdf", "content_type": "application/pdf"},
).json()
import_id = resp["import_id"]
upload_url = resp["upload_url"]
required_headers = resp["required_headers"]
# Step 2: Upload the raw file directly to S3
with open("compliance-policy.pdf", "rb") as f:
requests.put(upload_url, headers=required_headers, data=f)
print(f"Import ID: {import_id}")
# Step 3: call POST /api/policies/import/start with this import_id
const fs = require('fs');
const axios = require('axios');
const API_KEY = 'YOUR_API_KEY';
const API_BASE = 'https://api.zerodrift.ai';
(async () => {
// Step 1: Get the presigned URL
const { data } = await axios.post(
`${API_BASE}/api/policies/import/presigned_url`,
{ filename: 'compliance-policy.pdf', content_type: 'application/pdf' },
{ headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' } }
);
// Step 2: Upload the raw file directly to S3 with the signed required_headers
const fileStream = fs.createReadStream('compliance-policy.pdf');
await axios.put(data.upload_url, fileStream, {
headers: data.required_headers,
maxBodyLength: Infinity,
maxContentLength: Infinity,
});
console.log(`Import ID: ${data.import_id}`);
// Step 3: call POST /api/policies/import/start with this import_id
})();

