Recipes

Keep output files in S3 or R2

Use with your agent

Give this prompt to your AI agent. The agent does the task for you.

Read https://thirds.ai/docs/integrations/object-storage and help me store a verified thirds.ai output in my private S3 or R2 bucket and recover upload failures without another render

Keep each finished PDF or image in your own private bucket so your app controls access and retention. This recipe uploads a file that the API workflow or webhook worker already collects. It does not start a render.

Prepare your bucket

Use curl and AWS CLI v2 on macOS or Linux. You need the job ID of a finished output, or a file you already downloaded from it.

Create a private bucket in your own account. Give the upload identity access only to the required bucket or prefix. It needs to write and read the output objects for verification. Configure credentials through your existing AWS credential provider or a named CLI profile. Keep storage credentials separate from your thirds.ai key. The upload commands don't need your thirds.ai key.

For S3, use the bucket's region and standard endpoint. AWS documents the upload operation in its PutObject reference. For R2, create S3 API credentials and use your Cloudflare account ID, as shown in the R2 AWS CLI guide.

Upload to S3

Read the job first, so you have a fresh link and the file's facts. Keep artifact.byte_size and artifact.sha256 from the response:

GET /v1/pdf/{id}
curl --fail-with-body --silent --show-error \
  https://thirds.ai/v1/pdf/pdf_00000000000000000000000000000000 \
  -H 'Authorization: Bearer YOUR_API_KEY'

Add https://thirds.ai before download.url and save the file:

GET /v1/downloads/{token}
curl --fail --silent --show-error \
  'https://thirds.ai/v1/downloads/YOUR_SIGNED_DOWNLOAD_TOKEN' \
  --output report.pdf

Check the local file before you upload it. The two results must match artifact.byte_size and artifact.sha256. On macOS, use shasum -a 256 in place of sha256sum:

Shell
wc -c report.pdf
sha256sum report.pdf

Use an object key that belongs to this output, such as reports/customer-42/2026-09/report.pdf. Keep the key with your job ID. Replace the sample bucket and profile with your own values:

Shell
aws s3 cp report.pdf \
  s3://YOUR_PRIVATE_BUCKET/reports/customer-42/2026-09/report.pdf \
  --profile thirds-outputs

Then read the stored object back and check its SHA-256 against artifact.sha256:

Shell
aws s3 cp \
  s3://YOUR_PRIVATE_BUCKET/reports/customer-42/2026-09/report.pdf - \
  --profile thirds-outputs | sha256sum

Mark the output stored only after this check passes. Keep your app's access checks in front of any download you offer customers.

Upload to R2

Use the same file and the same checks. Add the R2 endpoint with your Cloudflare account ID, and use the profile that holds your R2 credentials:

Shell
aws s3 cp report.pdf \
  s3://YOUR_PRIVATE_BUCKET/reports/customer-42/2026-09/report.pdf \
  --profile thirds-r2 --region auto \
  --endpoint-url https://YOUR_CLOUDFLARE_ACCOUNT_ID.r2.cloudflarestorage.com

Add the same --region and --endpoint-url options to the read-back command. Use the same steps for an image with /v1/image/{id} and a key ending in .png.

Recover an upload

What happensWhat to do
Local file does not match the jobDownload the same job again. Don't upload the damaged file.
Credentials fail or access is deniedFix the profile or bucket policy, then upload again.
Network connection stopsRerun with the same verified file and object key.
Stored bytes do not matchInvestigate concurrent writes and retry from the verified local file.
Local file is goneDownload the known job again while its file remains available.

Use one key per intended output. If the key already holds a file, run the read-back check before you upload again. A mismatch needs investigation or a new key for a separately approved output. Bucket retention follows your own storage policy.

Keep the local file until the upload check passes. A signed thirds.ai link is temporary and is not a storage record. Follow file and link expiry when you recover a missing download. A storage failure does not require another billed render.

Ready to make your first file?

Start in the playground with a gallery template, or get an API key and send your first request from your own terminal.