> ## Documentation Index
> Fetch the complete documentation index at: https://zerodrift.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Get Presigned URL

> Get an S3 presigned URL for uploading large documents

Get an S3 presigned URL for uploading large documents (recommended for files > 6MB).

## Request Body

<ParamField body="document_category" type="string">
  Document category. Required if `document_metadata` is not provided.
</ParamField>

<ParamField body="document_metadata" type="object">
  Detailed metadata for precise rule matching. Required if `document_category` is not provided.
</ParamField>

<ParamField body="content_type" type="string" default="application/octet-stream">
  MIME type of the document (e.g., `application/pdf` for PDFs). Defaults to `application/octet-stream` if not specified.
</ParamField>

<ParamField body="filename" type="string">
  Original filename for reference
</ParamField>

## Response

<ResponseField name="job_id" type="string">
  Unique identifier for the validation job
</ResponseField>

<ResponseField name="upload_url" type="string">
  S3 presigned URL for file upload
</ResponseField>

<ResponseField name="upload_method" type="string">
  HTTP method to use for upload (always "PUT")
</ResponseField>

<ResponseField name="s3_bucket" type="string">
  S3 bucket name
</ResponseField>

<ResponseField name="s3_key" type="string">
  S3 object key
</ResponseField>

<ResponseField name="expires_at" type="string">
  ISO 8601 timestamp when the URL expires
</ResponseField>

<ResponseField name="expires_in_seconds" type="integer">
  Seconds until URL expiration
</ResponseField>

<ResponseField name="content_type" type="string">
  The content type to use when uploading
</ResponseField>

<ResponseField name="required_headers" type="object">
  **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.
</ResponseField>

<ResponseField name="instructions" type="object">
  Step-by-step instructions and example curl command for uploading
</ResponseField>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "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"
    }
  }
  ```
</ResponseExample>

<Warning>
  **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.
</Warning>

## Example

<CodeGroup>
  ```bash cURL theme={null}
  # 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"
  ```

  ```python Python theme={null}
  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}")
  ```

  ```javascript JavaScript theme={null}
  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}`);
  ```
</CodeGroup>
