Get discounted Screen Watermarking with your subscription!

Broker API

Lightweight document protection — includes watermarks and metadata establishing clear chain of custody.

SOC2 Type II Certified Infrastructure
Files are encrypted in transit (TLS 1.2+) and at rest (AES-256). All downloads are logged. Files are automatically deleted and not retained in backups.
Setup time: 2-4 hours
Standard HTTP requests • Polling or webhook delivery

Storage tip: Each funder receives a unique PDF, so files can accumulate quickly. We recommend deleting watermarked files immediately after confirming successful delivery to the funder by adding a basic file deletion command to your existing workflow.

How It Works

The API uses asynchronous processing to handle large volumes without timeouts. Choose whichever delivery method fits your workflow:

Option A: Polling (pull)

1 Submit job → Get job_id instantly

2 Poll status → Check if complete

3 Download → Get your watermarked PDFs
Option B: Webhooks (push)

1 Submit job with webhook_url → Get job_id instantly

2 We POST results to your URL when ready

3 Download → Get your watermarked PDFs

Both options return the same watermarked files. Webhooks eliminate the need to poll, making them ideal for high-volume or event-driven workflows.

Endpoints

Submit Job

POST https://aquamark-broker-funder.onrender.com/watermark-broker-funder

Check Status

GET https://aquamark-broker-funder.onrender.com/job-status/:job_id

Download (Authenticated)

GET https://aquamark-broker-funder.onrender.com/download/:job_id

Requires Bearer token. Returns the watermarked ZIP file directly. All downloads are logged for audit purposes.

Authentication

Authorization: Bearer aqua-api-watermark-77204041104282

This API key is universal and can be used immediately for testing.

Parameters

Parameter Type Description
user_email String Your Aquamark account email
Use [email protected] for testing with Aquamark branding
funder String or Array (optional) Funder name(s)
Max 15 per request. If omitted, files receive logo-only watermarks and are renamed with a -protected suffix.
files Array of Objects Array of file objects, each containing:
name (required): Filename with .pdf extension
url or data (required): Public URL or base64-encoded PDF
Can mix URL and base64 files in the same request
webhook_url String (optional) HTTPS URL to receive job results
If provided, we POST the result to this URL on completion or failure.
metadata Object (optional) Freeform JSON object for your own tracking data
Stored with the job and echoed back in every response and webhook payload.

Max file size: 50MB per file

Files Parameter Format:

Each file object must include:
name: The filename (e.g., "bank-statement-jan.pdf")
url: Public URL to the PDF file, OR
data: Base64-encoded PDF data

You can mix URL and base64 files in the same request for maximum flexibility.

Note: Examples use cURL for clarity. Translate to your preferred language (Python, JavaScript, Java, etc.). Standard HTTP requests—nothing custom.

Complete Example: Polling

1 Submit Job

curl -X POST https://aquamark-broker-funder.onrender.com/watermark-broker-funder \
  -H "Authorization: Bearer aqua-api-watermark-77204041104282" \
  -H "Content-Type: application/json" \
  -d '{
    "user_email": "[email protected]",
    "funder": ["Funder A", "Funder B", "Funder C"],
    "files": [
      {
        "name": "bank-statement-jan.pdf",
        "url": "https://www.aquamark.io/test.pdf"
      },
      {
        "name": "bank-statement-feb.pdf",
        "data": "YOUR-BASE64-ENCODED-PDF-HERE"
      },
      {
        "name": "credit-application.pdf",
        "url": "https://www.aquamark.io/test.pdf"
      }
    ],
    "metadata": {
      "deal_id": "AP-2024-8832",
      "source": "crm"
    }
  }'

Note: You can mix files with url and files with data (base64) in the same request. The data field should contain the complete base64-encoded PDF — not a truncated string. Convert your PDF files to base64 using any standard library (e.g. base64 in Python, Buffer.from(file).toString('base64') in Node.js) before sending.

2 Response (instant)

{
  "job_id": "abc-123-def-456",
  "status": "processing",
  "file_count": 3,
  "webhook_url": null,
  "message": "Job created successfully. Poll /job-status/{job_id} for updates.",
  "metadata": {
    "deal_id": "AP-2024-8832",
    "source": "crm"
  }
}

3 Poll Status

Use the job_id from Step 2 to check status every 10 seconds:

curl https://aquamark-broker-funder.onrender.com/job-status/abc-123-def-456

While processing:

{
  "job_id": "abc-123-def-456",
  "status": "processing",
  "progress": "Processing 2 of 3 files",
  "created_at": "2025-01-06T10:00:00Z",
  "metadata": {
    "deal_id": "AP-2024-8832",
    "source": "crm"
  }
}

When complete:

{
  "job_id": "abc-123-def-456",
  "status": "complete",
  "download_url": "https://...broker-job-results/abc-123-def-456.zip?token=eyJhbG...",
  "download_expires_at": "2026-02-24T19:45:22.418Z",
  "authenticated_download_url": "https://aquamark-broker-funder.onrender.com/download/abc-123-def-456",
  "message": "Ready for download. Signed link expires in 10 minutes. For logged/authenticated downloads, use the authenticated_download_url with your Bearer token.",
  "created_at": "2026-01-06T10:00:00Z",
  "completed_at": "2026-01-06T10:01:30Z",
  "metadata": {
    "deal_id": "AP-2024-8832",
    "source": "crm"
  }
}

On error:

{
  "job_id": "abc-123-def-456",
  "status": "error",
  "error_message": "Job timed out after 10 minutes",
  "created_at": "2025-01-06T10:00:00Z",
  "completed_at": "2025-01-06T10:10:00Z",
  "metadata": {
    "deal_id": "AP-2024-8832",
    "source": "crm"
  }
}

The metadata field is only present if you included it in the original request.

4 Download

Two download methods are available:

Option A: Signed URL (direct)

Use the download_url from Step 3. No auth required, but the link expires in 10 minutes.

curl "https://...broker-job-results/abc-123-def-456.zip?token=eyJhbG..." --output watermarked.zip

Option B: Authenticated download

Use the authenticated_download_url with your Bearer token. Every download is logged with IP, timestamp, and file size.

curl -H "Authorization: Bearer aqua-api-watermark-77204041104282" \
  https://aquamark-broker-funder.onrender.com/download/abc-123-def-456 \
  --output watermarked.zip

Complete Example: Webhooks

Instead of polling, add a webhook_url to your request and we'll POST the results directly to your server when the job finishes.

1 Submit Job with Webhook

curl -X POST https://aquamark-broker-funder.onrender.com/watermark-broker-funder \
  -H "Authorization: Bearer aqua-api-watermark-77204041104282" \
  -H "Content-Type: application/json" \
  -d '{
    "user_email": "[email protected]",
    "funder": ["Funder A", "Funder B", "Funder C"],
    "files": [
      {
        "name": "bank-statement-jan.pdf",
        "url": "https://www.aquamark.io/test.pdf"
      }
    ],
    "webhook_url": "https://your-server.com/aquamark-webhook",
    "metadata": {
      "deal_id": "AP-2024-8832"
    }
  }'

2 Response (instant)

{
  "job_id": "abc-123-def-456",
  "status": "processing",
  "file_count": 1,
  "webhook_url": "https://your-server.com/aquamark-webhook",
  "message": "Job created successfully. You will receive a webhook when processing completes.",
  "metadata": {
    "deal_id": "AP-2024-8832"
  }
}

3 Webhook Delivery

When processing completes, we POST to your webhook_url:

On success:

POST https://your-server.com/aquamark-webhook

Headers:
  Content-Type: application/json
  X-Aquamark-Event: job.completed
  X-Aquamark-Signature: 2ed0b90b005db9ef29a048f28a1e8acf...
  X-Aquamark-Delivery: 7860e1bc-0b42-48e5-ad95-587c04c0efbe
  User-Agent: Aquamark-Webhooks/1.0

Body:
{
  "event": "job.completed",
  "job_id": "abc-123-def-456",
  "status": "complete",
  "download_url": "https://...broker-job-results/abc-123-def-456.zip?token=eyJhbG...",
  "download_expires_at": "2026-02-24T19:45:22.418Z",
  "authenticated_download_url": "https://aquamark-broker-funder.onrender.com/download/abc-123-def-456",
  "file_count": 3,
  "failure_count": 0,
  "elapsed_ms": 2283,
  "completed_at": "2026-02-18T23:32:33.679Z",
  "metadata": {
    "deal_id": "AP-2024-8832"
  }
}

On error:

{
  "event": "job.failed",
  "job_id": "abc-123-def-456",
  "status": "error",
  "error_message": "All files/funders failed to process",
  "completed_at": "2026-02-18T23:35:00.000Z",
  "metadata": {
    "deal_id": "AP-2024-8832"
  }
}

4 Download

Two download options are available:

Option A: Signed URL (direct)

Use the download_url from the webhook payload. No auth required, but the link expires in 10 minutes.

curl "https://...broker-job-results/abc-123-def-456.zip?token=eyJhbG..." --output watermarked.zip

Option B: Authenticated download

Use the authenticated_download_url with your Bearer token. Every download is logged.

curl -H "Authorization: Bearer aqua-api-watermark-77204041104282" \
  https://aquamark-broker-funder.onrender.com/download/abc-123-def-456 \
  --output watermarked.zip

Verifying Webhook Signatures

Each webhook includes an X-Aquamark-Signature header — an HMAC-SHA256 hash of the request body signed with your API key. Verify it to confirm the webhook came from Aquamark:

// Node.js example
const crypto = require('crypto');

function verifyWebhook(requestBody, signatureHeader, apiKey) {
  const expected = crypto
    .createHmac('sha256', apiKey)
    .update(requestBody)
    .digest('hex');
  return expected === signatureHeader;
}

Webhook Behavior

Retry policy: If your server returns a 5xx error or is unreachable, we retry up to 3 times with exponential backoff (2s, 4s, 8s). Client errors (4xx) are not retried.

Timeout: Your endpoint must respond within 10 seconds.

HTTPS required: Webhook URLs must use HTTPS.

Fallback: You can always poll /job-status/:job_id as a backup, even when using webhooks.

Example ZIP Contents

With funders:

watermarked.zip/
├── bank-statement-jan-funder-a.pdf
├── bank-statement-jan-funder-b.pdf
├── bank-statement-jan-funder-c.pdf
├── bank-statement-feb-funder-a.pdf
├── bank-statement-feb-funder-b.pdf
├── bank-statement-feb-funder-c.pdf
├── credit-application-funder-a.pdf
├── credit-application-funder-b.pdf
└── credit-application-funder-c.pdf

Each file is watermarked with your broker logo and the funder's name.

Without funders (logo-only):

watermarked.zip/
├── bank-statement-jan-protected.pdf
├── bank-statement-feb-protected.pdf
└── credit-application-protected.pdf

Each file is watermarked with your broker logo only.

Polling Interval

File Availability & Security

Important Notes

Unzip required. Downloads are ZIP files. Use standard extraction libraries in your language.

Additional Examples

Logo-Only Protection (No Funders)

curl -X POST https://aquamark-broker-funder.onrender.com/watermark-broker-funder \
  -H "Authorization: Bearer aqua-api-watermark-77204041104282" \
  -H "Content-Type: application/json" \
  -d '{
    "user_email": "[email protected]",
    "files": [
      {
        "name": "bank-statement-june.pdf",
        "url": "https://www.aquamark.io/test.pdf"
      }
    ]
  }'

Output: bank-statement-june-protected.pdf — watermarked with your broker logo, no funder text.

Single File, Multiple Funders (URL)

curl -X POST https://aquamark-broker-funder.onrender.com/watermark-broker-funder \
  -H "Authorization: Bearer aqua-api-watermark-77204041104282" \
  -H "Content-Type: application/json" \
  -d '{
    "user_email": "[email protected]",
    "funder": ["Funder A", "Funder B"],
    "files": [
      {
        "name": "statement.pdf",
        "url": "https://www.aquamark.io/test.pdf"
      }
    ]
  }'

Single File, Multiple Funders (Base64)

curl -X POST https://aquamark-broker-funder.onrender.com/watermark-broker-funder \
  -H "Authorization: Bearer aqua-api-watermark-77204041104282" \
  -H "Content-Type: application/json" \
  -d '{
    "user_email": "[email protected]",
    "funder": ["Funder A", "Funder B"],
    "files": [
      {
        "name": "statement.pdf",
        "data": "<complete-base64-encoded-pdf>"
      }
    ]
  }'

Important: Replace <complete-base64-encoded-pdf> with the full base64-encoded contents of your PDF file. Truncated or placeholder strings will fail.

With Webhook (URL)

curl -X POST https://aquamark-broker-funder.onrender.com/watermark-broker-funder \
  -H "Authorization: Bearer aqua-api-watermark-77204041104282" \
  -H "Content-Type: application/json" \
  -d '{
    "user_email": "[email protected]",
    "funder": ["Funder A", "Funder B"],
    "files": [
      {
        "name": "statement.pdf",
        "url": "https://www.aquamark.io/test.pdf"
      }
    ],
    "webhook_url": "https://your-server.com/aquamark-webhook"
  }'

With Metadata

curl -X POST https://aquamark-broker-funder.onrender.com/watermark-broker-funder \
  -H "Authorization: Bearer aqua-api-watermark-77204041104282" \
  -H "Content-Type: application/json" \
  -d '{
    "user_email": "[email protected]",
    "funder": ["Funder A"],
    "files": [
      {
        "name": "statement.pdf",
        "url": "https://www.aquamark.io/test.pdf"
      }
    ],
    "metadata": {
      "deal_id": "AP-2024-8832",
      "broker_name": "John Smith",
      "crm_record": "sf-00482"
    }
  }'

The metadata object is stored with the job and returned in every response and webhook payload. Use it to correlate Aquamark jobs with your internal records.

Error Handling & Failure Behavior

Failures fall into three categories depending on when they occur. Here's what to expect at each stage.

Submission Failures (Synchronous)

These happen before a job is created — your HTTP call gets an error status code and message directly. There is no job_id to poll.

Common causes: Invalid or missing API key (401), account not on a qualifying plan (402), missing or malformed files array (400), file exceeds 50MB (400), invalid funder name characters (400), invalid webhook_url (400), rate limit exceeded (429), or estimated output ZIP exceeds the 200MB storage limit (400).

What to do: Check the error message in the response body. Fix the request and resubmit. No cleanup is needed since no job was created.

Processing Failures (Asynchronous)

The job was created and you received a job_id, but something failed during watermarking. The job status changes to "error".

Common causes: A PDF URL is unreachable or returns a non-200 response, a PDF is corrupted or can't be parsed, a PDF is password-protected and can't be decrypted, the output ZIP upload to storage fails after retries, or all files fail to process.

How you'll know: Poll /job-status/:job_id and the status will be "error" with an error_message field. If you provided a webhook_url, you'll receive a job.failed webhook with the same error message.

Timeout: Jobs that haven't completed within 10 minutes are automatically marked as errored with the message "Job timed out after 10 minutes".

Partial Failures

If you submit multiple files or funders and some succeed while others fail, the job still completes with the successful files in the ZIP.

How to detect: The response includes both file_count (successful) and failure_count. If failure_count is greater than 0, some files or funder combinations failed. The job status will be "complete", not "error".

Example: You submit 3 files × 2 funders (6 total outputs). If 1 file is corrupted, you'll get 4 successful PDFs in the ZIP, with "file_count": 4 and "failure_count": 2.

Download Failures

Signed URL expired (403 from storage): Poll /job-status/:job_id again to get a fresh signed URL. The file stays available for 30 minutes total after job completion.

File already deleted (410): The file was either downloaded via the authenticated endpoint (which auto-deletes), manually confirmed via /job-complete/:job_id, or auto-cleaned after 30 minutes. The job data is gone — resubmit the original request.

Invalid API key on authenticated download (401): Check your Bearer token.

Webhook Delivery Failures

Your endpoint is down or returns 5xx: We retry up to 3 times with exponential backoff (2s, 4s, 8s). If all retries fail, the webhook is dropped.

Your endpoint returns 4xx (except 429): Not retried. Check your endpoint logic.

Fallback: Polling /job-status/:job_id always works regardless of webhook delivery.

Error Codes

Code Meaning Example Response
400 Bad request Missing 'files' array. Provide array of file objects with 'name' and either 'data' (base64) or 'url'
401 Invalid API key Invalid API key
402 Account not authorized Requires the Leak Detection plan
404 Job not found Job not found
410 File expired or already downloaded File has already been downloaded and removed, or has expired
413 File too large File 'statement.pdf' exceeds 50MB limit
429 Rate limit exceeded Too many requests. Please wait 15 minutes and try again.
500 Server error Internal server error

Limits

Ready for Production?

The examples above use test credentials with Aquamark branding. To deploy with your own branding:

  1. Sign up: Create an account at aquamark.io/signup
  2. Upload your logo: Go to the Watermark Console in your portal
  3. Billing: Subscribe from your Dashboard
  4. Update your code: Replace [email protected] with your Aquamark account email in the user_email parameter

Questions? Email [email protected]