Lightweight document protection — includes watermarks and metadata establishing clear chain of custody.
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.
The API uses asynchronous processing to handle large volumes without timeouts. Choose whichever delivery method fits your workflow:
webhook_url → Get job_id instantlyBoth options return the same watermarked files. Webhooks eliminate the need to poll, making them ideal for high-volume or event-driven workflows.
Requires Bearer token. Returns the watermarked ZIP file directly. All downloads are logged for audit purposes.
Authorization: Bearer aqua-api-watermark-77204041104282
This API key is universal and can be used immediately for testing.
| 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 extensionurl or data (required): Public URL or base64-encoded PDFCan 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
name: The filename (e.g., "bank-statement-jan.pdf")url: Public URL to the PDF file, ORdata: Base64-encoded PDF dataNote: Examples use cURL for clarity. Translate to your preferred language (Python, JavaScript, Java, etc.). Standard HTTP requests—nothing custom.
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.
{
"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"
}
}
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.
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
Instead of polling, add a webhook_url to your request and we'll POST the results directly to your server when the job finishes.
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"
}
}'
{
"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"
}
}
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"
}
}
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
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;
}
/job-status/:job_id as a backup, even when using webhooks.
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.
complete or error/download/:job_id) are deleted immediately after downloadPOST /job-complete/:job_id or are auto-deleted after 30 minutescurl -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.
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"
}
]
}'
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.
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"
}'
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.
Failures fall into three categories depending on when they occur. Here's what to expect at each stage.
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.
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).The job was created and you received a job_id, but something failed during watermarking. The job status changes to "error".
/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."Job timed out after 10 minutes".
If you submit multiple files or funders and some succeed while others fail, the job still completes with the successful files in the ZIP.
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"."file_count": 4 and "failure_count": 2.
/job-status/:job_id again to get a fresh signed URL. The file stays available for 30 minutes total after job completion./job-complete/:job_id, or auto-cleaned after 30 minutes. The job data is gone — resubmit the original request./job-status/:job_id always works regardless of webhook delivery.
| 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 |
The examples above use test credentials with Aquamark branding. To deploy with your own branding:
[email protected] with your Aquamark account email in the user_email parameterQuestions? Email [email protected]