Skip to content

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/
  1. You upload a PDF to the input prefix in your bucket.
  2. We detect the upload (either by polling every few minutes, or by event notification β€” see Detection mode below).
  3. 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.
  4. 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

ProviderAuthSetup timeEvent mode
Amazon S3 β€” IAM role (recommended)AssumeRole via STS~5 minYes
Amazon S3 β€” access keyIAM user key~2 minNo
Cloudflare R2R2 token~2 minYes
Backblaze B2Application key~2 minNo
Generic S3-compatibleAccess key + endpoint~2 minNo

β€œEvent mode” means we react within seconds of an upload instead of waiting for the next poll cycle.

Setup

This option doesn’t share long-lived credentials with us. The trust policy limits access to your bucket and a per-integration external ID.

  1. In the dashboard, open Integrations β†’ Add integration, pick AWS S3 (AssumeRole), and enter your bucket name + input/output prefixes.
  2. Copy the trust policy and permissions policy the dashboard renders.
  3. In your AWS console, create a new IAM role with the trust policy. Attach the permissions policy.
  4. Paste the resulting role ARN back into the dashboard and click Test connection. A green check means we can HeadBucket against 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:ListBucket on the bucket
  • s3:GetObject, s3:PutObject, s3:DeleteObject on arn:aws:s3:::your-bucket/*

Cloudflare R2

  1. Create an R2 API token (Cloudflare dashboard β†’ R2 β†’ Manage API tokens) scoped to the bucket. Permission: Object Read & Write.
  2. In the integration form, choose Cloudflare R2. Endpoint is https://<your-account-id>.r2.cloudflarestorage.com. Region is auto.
  3. 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

  1. Create an Application Key in the B2 console scoped to one bucket.
  2. 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.
  3. 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

  1. In the integration detail page, expand Event-driven setup (EventBridge rule) and copy the rule JSON.
  2. In your AWS console, enable EventBridge notifications on the bucket: S3 β†’ Properties β†’ Amazon EventBridge β†’ On.
  3. Create the EventBridge rule in the same AWS account that owns the bucket (paste the rule JSON in EventBridge β†’ Rules β†’ Create).
  4. 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:

  1. In the integration detail page, expand Event-driven setup (R2 forwarder Worker).
  2. 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.
  3. Add an R2 Event Notification on your bucket targeting the same queue name as step 2: Cloudflare dashboard β†’ R2 β†’ your bucket β†’ Settings β†’ Event Notifications.
  4. Copy the Worker source from the dashboard into a new file (e.g. src/index.ts) and create a wrangler.toml that 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
  5. Set the secret:
    Terminal window
    wrangler secret put EVENT_SOURCE_SECRET
    # paste the value shown in the dashboard
  6. Deploy: wrangler deploy.
  7. 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:AssumeRole from our principal, and s3:ListBucket / s3:HeadBucket on the target.
  • Access-key setups: confirm the key isn’t disabled and that the policy grants s3:ListBucket on 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_secret until 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:PutObject on arn: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.