Uploading to Your S3 Bucket
Use customS3PresignedUrl when you want PDFBolt to upload the generated PDF directly to your own S3-compatible storage. This keeps the final PDF in your storage environment, where you control retention, access policies, and downstream processing.
How It Works
- Generate an HTTPS pre-signed PUT URL for the final PDF object in your S3-compatible bucket.
- Send the URL to PDFBolt as
customS3PresignedUrlin a/v1/syncor/v1/asyncrequest. - PDFBolt renders the PDF and uploads it to the pre-signed URL with an HTTP
PUT. - Your application reads and manages the PDF from your own bucket.
Pre-signed URL Requirements
The URL you pass to PDFBolt must:
- Use HTTPS. HTTP URLs are rejected.
- Be no longer than 2048 characters.
- Allow
PUTfor the exact bucket and object key. - Remain valid long enough for the full PDF conversion and upload.
- Accept
Content-Type: application/pdf. - Accept the
Content-Dispositionheader PDFBolt sends during upload.
By default, PDFBolt uploads with:
Content-Type: application/pdf
Content-Disposition: inline
If your request includes contentDisposition or filename, make sure the pre-signed URL allows the matching Content-Disposition header.
Supported S3-Compatible Storage
PDFBolt can upload to S3-compatible storage providers that support HTTPS pre-signed PUT URLs, including:
- Amazon S3
- Cloudflare R2
- DigitalOcean Spaces
- MinIO
- Wasabi
- Backblaze B2
Other S3-compatible providers may work if they support HTTPS pre-signed PUT URLs and the required upload headers.
Example: Generating a Pre-signed URL in Node.js
Install the AWS SDK v3 packages:
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');
const s3 = new S3Client({
region: process.env.S3_REGION || 'us-east-1',
...(process.env.S3_ENDPOINT ? { endpoint: process.env.S3_ENDPOINT } : {}),
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY
},
// Some S3-compatible providers, such as MinIO, require path-style URLs.
forcePathStyle: process.env.S3_FORCE_PATH_STYLE === 'true'
});
async function generatePresignedUrl(bucketName, objectKey) {
const command = new PutObjectCommand({
Bucket: bucketName,
Key: objectKey,
ContentType: 'application/pdf',
ContentDisposition: 'inline'
});
const url = await getSignedUrl(s3, command, { expiresIn: 3600 });
console.log(url);
return url;
}
generatePresignedUrl('your-bucket-name', `pdfbolt/document-${Date.now()}.pdf`)
.catch((error) => {
console.error('Error generating pre-signed URL:', error);
process.exit(1);
});
S3_REGION=us-east-1 \
S3_ACCESS_KEY_ID=your-access-key-id \
S3_SECRET_ACCESS_KEY=your-secret-access-key \
node generatePresignedUrl.js
For S3-compatible providers other than AWS S3, also set S3_ENDPOINT. For providers that require path-style URLs, set S3_FORCE_PATH_STYLE=true.
The output should look like this:
https://your-bucket.s3.amazonaws.com/pdfbolt/document-1714580000000.pdf?<presigned-query-params>
- Generate a separate pre-signed URL for each PDF. Reusing the same bucket/key can overwrite an existing object.
- Generate the URL on your server, not in browser code.
- Keep storage credentials in environment variables or a secret manager.
- Store the bucket and object key in your application so you can find the PDF later without relying on the pre-signed URL.
- Set
expiresInlong enough for the conversion to finish, especially for large pages or slow external assets.
Use the Pre-signed URL with PDFBolt
Sync Conversion
Use the generated URL as customS3PresignedUrl in the /v1/sync request body:
{
"url": "https://example.com",
"customS3PresignedUrl": "https://your-bucket.s3.amazonaws.com/pdfbolt/document.pdf?<presigned-query-params>"
}
When the upload succeeds, the response has documentUrl: null, expiresAt: null, and isCustomS3Bucket: true because the PDF is already in your bucket.
{
"requestId": "09dd133a-a064-44b6-80f5-b2a7571f77ed",
"status": "SUCCESS",
"errorCode": null,
"errorMessage": null,
"documentUrl": null,
"expiresAt": null,
"isAsync": false,
"duration": 664,
"documentSizeMb": 0.02,
"isCustomS3Bucket": true
}
Async Conversion
For /v1/async, include both webhook and customS3PresignedUrl:
{
"url": "https://example.com",
"webhook": "https://your-app.com/webhooks/pdfbolt",
"customS3PresignedUrl": "https://your-bucket.s3.amazonaws.com/pdfbolt/document.pdf?<presigned-query-params>"
}
The initial API response returns a requestId. When the conversion succeeds, PDFBolt sends a webhook with documentUrl: null, expiresAt: null, and isCustomS3Bucket: true because the PDF is already in your bucket.
{
"requestId": "fadb0a9c-a5f5-4d15-8588-b15bde7c201d",
"status": "SUCCESS",
"errorCode": null,
"errorMessage": null,
"documentUrl": null,
"expiresAt": null,
"isAsync": true,
"duration": 601,
"documentSizeMb": 0.02,
"isCustomS3Bucket": true
}
See Async Workflow Details for the full async flow.
Troubleshooting
Upload Error
When PDFBolt receives a non-2xx response while uploading the PDF to your pre-signed URL, the conversion fails with errorCode: "CUSTOM_S3_UPLOAD_ERROR". The errorMessage includes the storage provider's response code and body.
This usually means one of the following:
- The pre-signed URL expired before the PDF upload started.
- The URL doesn't allow
PUTfor the exact bucket/key. - The signing identity doesn't have
s3:PutObjector equivalent write permission. - The bucket or object key is wrong.
- The provider rejected an upload header, usually
Content-TypeorContent-Disposition. - The bucket policy or provider allowlist rejects PDFBolt's upload request.
If your storage provider uses IP allowlisting, see PDFBolt IP Addresses.
Pre-signed URL Rejected Before Conversion
Make sure customS3PresignedUrl uses HTTPS and is no longer than 2048 characters. HTTP URLs and longer URLs are rejected before PDF generation starts.
Next Steps
📄️ Sync Endpoint
/v1/sync endpoint reference
📄️ Async Endpoint
/v1/async endpoint reference
📄️ Async Workflow Details
Conceptual overview of the async flow
📄️ Error Handling
HTTP status codes and recommended actions