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.
By default, results are written to the same bucket the upload came from. You can instead set an output bucket on the integration to keep inputs and outputs in entirely separate buckets — results go there (reached with the same credentials), and the output prefix may then even be left empty.
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:
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 — in the output bucket if you configured one, otherwise in the same bucket as the input.
- Confirm the role/key has
s3:PutObjectonarn:aws:s3:::your-output-bucket/<output-prefix>/*. - Without a separate output bucket, the output prefix must be non-empty and must not overlap the input prefix (otherwise we'd loop on our own output). With a separate output bucket those restrictions don't apply.
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.