S3 Bucket Integrations
Connect your own object-storage bucket (Amazon S3, Cloudflare R2, Backblaze B2, or any S3-compatible service) and weβll automatically remediate every PDF you drop in. The remediated copy lands back in your bucket β no copy-paste, no manual uploads.
This is the right fit if youβre publishing accessibility-compliant PDFs at volume (course materials, regulatory filings, public-records portals) and donβt want to run files through the web converter one-by-one.
How it works
your bucket theaccessible.org your bucketβββββββββββββ βββββββββββββββββ βββββββββββββ uploads/ βββΆ detected βββΆ remediated βββΆ written βββΆ remediated/- You upload a PDF to the input prefix in your bucket.
- We detect the upload (either by polling every few minutes, or by event notification β see Detection mode below).
- We pull the file, run our accessibility pipeline (tagging, alt text, reading order, color contrast, etc.), and write the remediated PDF back to your bucket under the output prefix.
- The original is left untouched.
Everything is per-user and isolated β we never see or touch any object outside the prefix you point us at.
Supported providers
| Provider | Auth | Setup time | Event mode |
|---|---|---|---|
| Amazon S3 β IAM role (recommended) | AssumeRole via STS | ~5 min | Yes |
| Amazon S3 β access key | IAM user key | ~2 min | No |
| Cloudflare R2 | R2 token | ~2 min | Yes |
| Backblaze B2 | Application key | ~2 min | No |
| Generic S3-compatible | Access key + endpoint | ~2 min | No |
βEvent modeβ means we react within seconds of an upload instead of waiting for the next poll cycle.
Setup
Amazon S3 (IAM role β recommended)
This option doesnβt share long-lived credentials with us. The trust policy limits access to your bucket and a per-integration external ID.
- In the dashboard, open Integrations β Add integration, pick AWS S3 (AssumeRole), and enter your bucket name + input/output prefixes.
- Copy the trust policy and permissions policy the dashboard renders.
- In your AWS console, create a new IAM role with the trust policy. Attach the permissions policy.
- Paste the resulting role ARN back into the dashboard and click Test
connection. A green check means we can
HeadBucketagainst your role.
Amazon S3 (access key)
Use this if you canβt grant cross-account roles (e.g. corporate IAM restrictions). We seal the access key with AWS KMS the moment it arrives β the plaintext is never stored.
Required IAM policy on the user:
s3:ListBucketon the buckets3:GetObject,s3:PutObject,s3:DeleteObjectonarn:aws:s3:::your-bucket/*
Cloudflare R2
- Create an R2 API token (Cloudflare dashboard β R2 β Manage API tokens) scoped to the bucket. Permission: Object Read & Write.
- In the integration form, choose Cloudflare R2. Endpoint is
https://<your-account-id>.r2.cloudflarestorage.com. Region isauto. - Paste the access key ID and secret. We seal both with AWS KMS on insert; the plaintext is never persisted. (If you later enable event mode for R2, youβll also receive a separate event source secret that gates inbound notifications β see Event mode below.)
Backblaze B2
- Create an Application Key in the B2 console scoped to one bucket.
- In the integration form, choose Backblaze B2. The endpoint depends on
your bucket region:
https://s3.us-west-001.backblazeb2.com,https://s3.eu-central-003.backblazeb2.com, etc. β copy it from the B2 console. - Paste the keyID and applicationKey.
Generic S3-compatible
For self-hosted MinIO, SeaweedFS, Wasabi, or anything else that speaks the S3 API. You supply the endpoint + region + access key. We force path-style addressing because virtual-hosted style breaks on most non-AWS endpoints.
Detection mode
After the integration is created, you can switch between two modes from the detail page:
Poll (default)
We list your input prefix every two minutes. Simplest setup, but new uploads can wait up to ~2 minutes before processing starts.
Event (faster, opt-in)
We react within seconds of an upload. Setup differs by provider:
Event mode β Amazon S3
- In the integration detail page, expand Event-driven setup (EventBridge rule) and copy the rule JSON.
- In your AWS console, enable EventBridge notifications on the bucket: S3 β Properties β Amazon EventBridge β On.
- Create the EventBridge rule in the same AWS account that owns the bucket (paste the rule JSON in EventBridge β Rules β Create).
- Back in the dashboard, flip detection mode to Event.
Event mode β Cloudflare R2
Because R2 event notifications can only target a Cloudflare Queue in your own account, you deploy a tiny forwarder Worker we generate for you:
- In the integration detail page, expand Event-driven setup (R2 forwarder Worker).
- Create a Cloudflare Queue in your account (Cloudflare dashboard β
Queues β Create). Pick a name and keep it consistent across the
next two steps β e.g.
accessible-pdf-forward. - Add an R2 Event Notification on your bucket targeting the same queue name as step 2: Cloudflare dashboard β R2 β your bucket β Settings β Event Notifications.
- Copy the Worker source from the dashboard into a new file (e.g.
src/index.ts) and create awrangler.tomlthat binds the same queue name as steps 2β3 as a consumer:name = "accessible-r2-forwarder"main = "src/index.ts"compatibility_date = "2024-09-23"# nodejs_compat is NOT needed β the generated Worker only uses# standard fetch / JSON / Queue APIs.[[queues.consumers]]queue = "accessible-pdf-forward" # must match steps 2 and 3 - Set the secret:
Terminal window wrangler secret put EVENT_SOURCE_SECRET# paste the value shown in the dashboard - Deploy:
wrangler deploy. - Flip detection mode to Event in the dashboard.
Rotating the secret in the dashboard invalidates the deployed Worker β
re-set the secret with wrangler secret put after every rotation.
Pausing and resuming
The Pause button stops polling and rejects event-mode messages. Use it during maintenance windows or when a bad upload is loop-failing. Resume restores normal processing.
Troubleshooting
βTest connectionβ fails
- AWS AssumeRole: confirm the roleβs trust policy references our account
and the external ID shown in the dashboard. The role must allow
sts:AssumeRolefrom our principal, ands3:ListBucket/s3:HeadBucketon the target. - Access-key setups: confirm the key isnβt disabled and that the policy
grants
s3:ListBucketon the bucket (not just the objects). - R2: regenerate the token if it was created before the bucket existed β some older R2 tokens were created with stale bucket lists.
Polling never picks up a file
- Confirm the file is under the input prefix exactly as configured (case-sensitive).
- File size cap is 200 MB. Larger files are skipped β check the audit log on the detail page.
- If you flipped to event mode, polling stops on purpose. Switch back to poll if the event source isnβt wired up yet.
Event mode produces no jobs
- Check the audit log on the detail page. Events rejected for authentication, bucket mismatch, or wrong mode are logged with a reason code.
- After rotating the event source secret, every event with the old secret
is rejected as
invalid_secretuntil you update your customer-side Worker / EventBridge rule.
Remediated files arenβt appearing
- They land under the output prefix, not the input prefix.
- Confirm the role/key has
s3:PutObjectonarn:aws:s3:::your-bucket/<output-prefix>/*. - If output prefix is empty, files land in the bucket root β we explicitly block this unless your input prefix is also non-empty (otherwise weβd loop on our own output).
Limits
- Max integrations per user: 25
- Max object size: 200 MB
- Polling cadence: every 2 minutes
- Event-mode rate limit per integration: 100 events per 10 seconds (excess is queued by your event source and replayed)
Pricing
S3 integrations themselves are free; you only pay for the per-page conversion credits used when we remediate a file. See pricing for details. AWS / R2 / B2 egress on your side is billed by your storage provider.