Retry requests with an idempotency key and download files

An idempotency key is a name you pick for one file you want, sent in the Idempotency-Key header. When your app loses a response and sends the same request again, thirds.ai sees the key and returns the job it already made. You get one PDF or image, and you pay for it once. A key can be 1 to 255 printable ASCII characters, and it protects you for 24 hours.

Use with your agent

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

Read https://thirds.ai/docs/retry-and-download and add safe retries, job polling, and verified downloads to my thirds.ai integration

thirds.ai is a web app and API that makes branded PDFs and images. You save a design, such as an invoice, a report, or a certificate, once as a template. Then you send new details from your own code and get back a finished file with your logo, colours, and fonts. A network error can hide whether your request arrived, and a blind retry could send your customer two invoices. With a key, you can retry as often as you need. A failed render costs nothing, and a finished PDF costs 1 credit.

If you make files in the editor, you click Export and never need a key. How to make a template you can reuse shows that no-code way. The PDF generation API guide explains the 200 and 202 responses that a create request returns. To see one PDF go from request to download, follow Generate your first PDF with the API.

What is an idempotency key?

An idempotency key is a string you send with a create request so thirds.ai can tell a retry from a new request. The key is saved with the job it starts. For the next 24 hours, the same account, operation, key, and input return that same job. A different input with the same key returns 409 idempotency_conflict, so a mistake never gives you the wrong file.

The key is optional on POST /v1/pdf and POST /v1/image. Batches, carousels, image packs, AI images, and AI template chat messages require one. Send the header once, because two Idempotency-Key headers fail validation.

To decide if two requests are the same, thirds.ai compares the HTML or the exact template version, your data with its keys sorted, and the render options that apply. The wait field, request IDs, and how you connect don't count.

Build the key from your own record, such as invoice-INV-1042-v1, and add a new revision when the content changes on purpose. If you have no record ID, a random UUID from your app works too. Keep email addresses and other personal details out of keys.

Retry a request without a second job

You retry safely in three steps, and you need an API key and one key for each file you want. First, save the key with your record before you send the request. Next, send the request with the Idempotency-Key header. Last, if the connection times out, send the same request again with the same key and body.

POST /v1/pdf
curl --fail-with-body --silent --show-error https://thirds.ai/v1/pdf \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: invoice-INV-1042-v1' \
  --data '{"html":"<h1>Invoice INV-1042</h1><p>Thank you for your order.</p>","wait":false}'

Even if retries arrive together, they create at most one job and one credit reservation. A reused key can return Idempotency-Replayed: true while the key lasts.

For saved templates, send an exact version number in requests you may retry. If you leave it out, publishing a new template version can change which version the request selects.

The wait field changes only how long the response waits. It doesn't change the output or its price. After a key expires, it no longer protects you from a second job. Keep the job ID beside your own record, such as the invoice that needs that PDF. The Node.js invoice guide shows a create, poll, and download flow that keeps the same key on every retry.

Poll the job until it ends

You poll a thirds.ai render job by reading it until its status is succeeded, failed, or cancelled. POST /v1/pdf and POST /v1/image return a job object. A 200 response means the job has finished, which can include failure or cancellation. Read status to find out which. A 202 response means you need to check the job again. Send wait: false to skip the initial wait.

Check GET /v1/pdf/{id} or GET /v1/image/{id} with an API key from the same account. Use Location and Retry-After when the response includes them. Set a time deadline for polling in your app. If the deadline ends first, save the job ID and check it later.

StatusNext step
queuedWait before you check again.
runningKeep polling with a delay while the render runs.
succeededRead download and save the file.
failedStop polling and inspect error.category and error.code.
cancelledStop polling. This job produces no new output.

A job in one of the last three states never restarts. Some internal failures get retried before the job reaches its final state. Keep checking the same job while it runs. You can use webhooks to receive the result on your server instead of polling. Verify each webhook signature before your server acts on the result. To download each file from a saved event without a second render, use the webhook endpoint recipe.

Handle errors by code

You handle a thirds.ai error by its code, because the code stays the same and the message text can change. Request errors include an error object with a stable code and request_id. Failed jobs include an error with category and code. Use these fields to decide what your app does next. Only a few codes are safe to retry with the same key.

ErrorWhat to do
400 or 422 validation errorCorrect the named request fields or template data.
401 authentication errorCheck the API key and Bearer header.
403 account_suspendedContact support and stop submitting work.
409 idempotency_conflictFind the original request, or use a new key if you want a new output.
402 credit or spend-cap errorFollow billing recovery.
429 rate_limited, account_concurrency_limited, or key_concurrency_limitedWait for Retry-After, then retry with the same key. Send fewer requests at once.
429 abuse_limitedStop repeated failures. Fix their cause and follow the retry delay.
503 overloadedWait for Retry-After, then retry the same request with the same key.
Job category invalid_input or unsafe_assetFix the HTML, script, or asset before you create another render.
Job category resource_limit or timeoutReduce the work or fix JavaScript readiness. The same unchanged request can fail again.
Job category renderer_failure or internal_failureInternal recovery has ended. Use a new job with a limit on retries, or contact support.

The OpenAPI contract lists response fields. To find out what an error code means, look it up in the API error codes list. If you contact support, include the job ID, request ID, and error code. For quick answers on where your files are and how long you can download them, read common questions. Keep API keys, signed links, and private content out of support messages and logs.

Download and keep the file

You download a finished thirds.ai PDF or image from the download.url of a succeeded job, and you save your own copy before artifact.expires_at. Read a successful job to get a fresh download.url while its file remains available. This signed link lasts 15 minutes. Resolve the path against https://thirds.ai before you download it. Anyone with the link can collect the file without an API key, so keep it private.

To keep a copy in a private bucket, upload the file to S3 or R2 with the AWS CLI. The two expiry fields tell you different things. download.expires_at is the link deadline, and artifact.expires_at is the file deadline.

How long we keep your file

How long a file stays depends on your plan. On a paid plan (Starter, Growth, or Scale), a file stays for up to 365 days. On the Free plan, with credit packs only, or during the $1 Starter trial, a file stays for up to 30 days. A new file gets your plan's longest time unless you pick a shorter one. Files you make in the anonymous playground stay for one hour. You can delete a file sooner at any time.

To set a shorter default for new files, open Account in the app and change Keep new files for. You can pick anything from 1 day up to your plan's longest time. Batches, image packs, and carousels use this default. To pick a time for one file, change Keep this file for when you export it. From your own code, send retention_days with POST /v1/pdf, POST /v1/image, or the MCP render tool. It's a whole number from 1 to 365. Leave it out, and the file uses your account default. A value above your plan's longest time returns 422 with the code retention_limit, and no file is made.

To change how long a file you already made stays, open it in Renders and click the pencil button beside Expires. Pick a date, or pick 1, 3, 6, or 12 months from the day you made the file. You can go as far as your plan's longest time, and the new end has to be after today. From your own code, send retention_days with PATCH /v1/pdf/{id} or PATCH /v1/image/{id}. It counts from the job's created_at. A file you change this way keeps its time when you move to a paid plan.

When your paid plan ends, or your account moves to the Free plan or to credit packs only, a file that would stay longer than 30 days gets 30 days from that day. After that, we delete it. We send you one email when this happens. While your plan is still paid, nothing changes. When you move to a paid plan, your older files get the longer time too, counted from the day you made each one. A file you picked its own time for keeps that time.

An expired valid link returns 403. Read the job again for a fresh link. A missing, deleted, or expired file returns 404. A new link can't restore that file. If download is null, check status and artifact.removed_reason.

Before you mark delivery complete, compare the file's byte count and SHA-256 with artifact.byte_size and artifact.sha256. If a transfer stops early, download the file again. You don't need to make the file again.

Delete an output

You delete a thirds.ai output early with DELETE /v1/pdf/{id} or DELETE /v1/image/{id}, which removes the file before its expiry date. You can repeat the delete request safely. A successful job stays in history. A failed or cancelled job leaves history, but you can still read it by ID. Deletion doesn't change credits or retry keys. Queued and running jobs return 409.

Deletion stops new downloads. A download that has already opened the file can finish. The credit charge for a successful output stays in place. Your render input is removed after the job ends. See the privacy policy for storage and backup terms.

Stay within the limits

You stay within the thirds.ai render limits by keeping your HTML, data, assets, and finished file under fixed sizes that are the same on every plan. A render that goes over a limit fails, and you pay nothing for it. Higher plans give you more credits and a different place in the queue. To see what each 429 code means and how the queue orders jobs, read API rate limits. To keep a PDF or image inside these limits, set its page size or pixel dimensions before you send it.

LimitMaximum
HTML input5 MiB
Template data3 MiB of canonical JSON
One fetched asset10 MiB
All fetched assets20 MiB across at most 128 assets
PDF output100 pages and 25 MiB
Image output32,000,000 pixels and 25 MiB
Worker render time30 seconds
Live jobs per account16
Live jobs per API key8
API-key request rate5 per second, with an idle burst of 21

Memory, CPU, browser processes, and temporary storage also have safety limits. Repeated limit failures count toward abuse controls. Reduce source size, asset count, or document length before you retry.

Free accounts get a monthly credit grant. Anonymous playground use has its own limit of 200 renders per browser each month. There is no lifetime render quota. See credits and billing for credit grants, expiry dates, and spending limits.

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.