Skip to content
Get started

Storage destination

On the Business plan, render jobs can deliver their files to your own bucket instead of Doquill storage. Doquill then keeps the file’s metadata (name, size, tags, location) but not its content.

Rendering itself still runs on Doquill. A job’s input data is held until the job finishes, then deleted, as for any render job.

Synchronous POST /v1/render calls are unaffected; they return the document and store nothing.

In the app, open Developers → Storage and click Connect a bucket. You need the manage_workspace permission. Any S3-compatible service works:

Provider Endpoint Region
Amazon S3 leave empty the bucket’s region, e.g. eu-central-1
Google Cloud Storage https://storage.googleapis.com auto
Cloudflare R2 https://<account-id>.r2.cloudflarestorage.com auto
UpCloud https://<id>.upcloudobjects.com the object storage region

The endpoint must use https and be publicly reachable. Turn on path-style addressing for buckets with dots in their names, and for providers that don’t support virtual-hosted URLs.

When you save, Doquill uploads a test object, <prefix>/.doquill-verify. Nothing is saved unless that upload succeeds. The object stays in the bucket, and you can delete it whenever you like.

The access key needs only s3:PutObject on the prefix. On AWS:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::invoices/doquill/*"
}
]
}

Doquill never reads, lists, or deletes objects in your bucket. Each save overwrites .doquill-verify, so only one test object ever exists.

Doquill uses Cloud Storage’s S3-compatible XML API, which takes HMAC keys:

  1. Create a service account for Doquill.
  2. On the bucket, grant it Storage Object Creator (roles/storage.objectCreator). Grant Storage Object User instead if you want the test object cleaned up.
  3. In Cloud Storage → Settings → Interoperability, create an HMAC key for that service account.
  4. Enter the HMAC access ID and secret as the access key ID and secret access key.

The secret access key is encrypted at rest and never shown again. To change keys, edit the destination and enter both the new access key ID and its secret.

Each file is stored at:

<prefix>/<template id>/<file id>.<extension>

Objects are uploaded with Content-Type, a Content-Disposition carrying the job’s filename, a Content-MD5 checksum (so buckets with Object Lock accept them), and these metadata entries:

Metadata Value
x-amz-meta-doquill-job-id The render job’s ID
x-amz-meta-doquill-template-id The template’s ID
x-amz-meta-doquill-template-version The rendered version

On Google Cloud Storage the same entries appear as x-goog-meta-*. Job tags stay in Doquill; they aren’t written as S3 object tags.

If a render is retried, the same key is written again. On a versioned bucket that adds a version.

  • GET /v1/render/jobs/{id} returns location: { endpoint, bucket, key } once the job is ready.
  • The render.succeeded webhook carries the same location in data.storage.
  • GET /v1/files/{id} returns storage: "external" and the location.
  • GET /v1/files/{id}/content returns 409 file_stored_externally, with the location in error.details.

Deleting a file in Doquill removes only its record. The object in your bucket stays; use your bucket’s lifecycle rules to expire it.

If your bucket rejects an upload for a reason a retry won’t fix, such as a revoked key, a missing bucket or the wrong region, the job fails with storage destination rejected the upload: <S3 error code>. The Storage page then shows the error until an upload succeeds. Temporary failures (timeouts, 5xx, SlowDown) are retried like other render failures.

A job never falls back to Doquill storage. Jobs upload with the destination’s settings at upload time, so fixing a wrong region or key also delivers jobs that are still waiting. If you remove the destination while jobs are waiting, those jobs fail.

The destination is kept when a workspace leaves the Business plan, but render jobs created after that are stored by Doquill. Upgrading again turns the destination back on.