Set Up Sync
Set up once, stay current. A sync connects a cloud storage bucket to a Captain collection. Captain indexes what is already there, then keeps the collection current as files are added, changed, and removed.
Set up Sync with your cloud storage
Pick a provider to jump to its setup. The same sync behavior (cadence, change detection, deletion propagation, and real-time events) applies to every provider.
How Sync works
One setup, then hands-off. Point a sync at a bucket. Captain indexes the existing files, then tracks the source so your search results always reflect what is in storage.
Many sources, one collection. Set up a sync for each bucket you want included, and Captain keeps all of them current in the same collection. Each sync tracks its own source independently, so a change or deletion in one source only affects the files that came from it.
Two mechanisms keep a collection current:
- Scheduled reconciliation runs on the cadence you choose. Captain lists the source, compares it against what it has already indexed, and applies the difference: new files get indexed, changed files get re-indexed, and removed files follow your deletion policy.
- Real-time events are an optional add-on. Forward change notifications from your source and updates land within seconds. Delivery is best-effort, so reconciliation always stays on as the backstop.
You turn events on per sync with a single call, POST /v2/syncs/{sync_id}/webhooks. What it returns depends on the provider. For AWS S3, events travel AWS-natively: you pass your SNS topic’s ARN and the response contains a queue_arn to subscribe the topic to, with no URL involved. For every other provider (GCS, R2, Supabase, Backblaze), the response contains an ingest_url: an HTTPS endpoint hosted by Captain, unique to your sync, that you point the store’s change notifications at. Keep it private, because the secret embedded in it is what authenticates your event stream. Each provider section below shows the exact wiring.
AWS S3
Authentication
An AWS S3 sync can authenticate two ways.
Role assumption (recommended). Create an IAM role in your own account that grants read access to the bucket, and Captain reads through that role using a Captain-issued external ID. No long-lived keys leave your account. The setup is the same as S3 indexing, so follow the S3 Cross-Account IAM guide, then pass an auth block of type assume_role with your role_arn and external_id.
Access key. Pass an auth block of type access_key with an access_key_id and secret_access_key. Captain stores the secret securely and never returns it. Use this when a role is not an option. It is also how the S3-compatible stores below authenticate, since they do not support IAM roles. For step-by-step credential setup with screenshots, see Connect Cloud Storage.
The external ID for role assumption is issued by Captain for your organization. Email support@runcaptain.com or your account manager to get it before you create a sync.
Create a sync
Creating a sync starts the first full index of the collection and returns the sync record right away. The indexing itself runs in the background.
A few fields worth calling out: prefix scopes the sync to part of the bucket (an empty prefix syncs everything), include_patterns and exclude_patterns are glob filters over object keys, and processing_type picks the parsing tier (advanced or basic). deletion_policy and sync_interval_minutes are covered below.
Cadence
sync_interval_minutes sets how often scheduled reconciliation runs.
The minimum cadence is 5 minutes. The scheduler runs on a single five-minute tick, so a sync cannot reconcile more often than that. A value below 5 is accepted and raised to 5 rather than rejected. Any value of 5 or more is used as given, for example 15, 60, or 1440 for a daily sync. Read the sync back to see the value in effect.
Set sync_interval_minutes to null (or leave it out) for a manual sync
with no scheduled reconciliation. Real-time events and on-demand reconcile still
work. When changes need to land faster than every five minutes, use real-time
events rather than a shorter cadence. The cadence is a backstop interval, not a
latency guarantee.
Change the cadence at any time:
Change detection
Reconciliation decides what changed by comparing each object’s ETag, a content hash, against the value Captain recorded when it last indexed the object. It does not compare file size, so an in-place overwrite that keeps the same byte count is still caught and re-indexed. Objects whose content has not changed are skipped, so each pass only does work for what actually changed.
Deletion propagation
When an object is no longer in the bucket, deletion_policy decides what happens
to its indexed copy:
Removed objects are matched by their exact bucket and key, so deletion targets the right document.
Real-time events
For faster propagation, wire your bucket’s change notifications to Captain. For AWS S3 this runs entirely inside AWS: your bucket publishes to an SNS topic in your account, and that topic delivers straight to a queue on Captain’s side. There is no HTTP endpoint to host and no signature handshake to configure.
- Create an SNS topic in the bucket’s region.
- On the bucket, add an event notification for
s3:ObjectCreated:*ands3:ObjectRemoved:*(scoped to your sync prefix if you set one) with the topic as its destination. - Enroll the sync with your topic ARN. Captain binds the topic to this sync and returns the queue to subscribe it to:
- Subscribe the returned
queue_arnto your topic (protocolsqs). The subscription confirms automatically; there is nothing to click through. Leave raw message delivery off (the default) — Captain matches your events to this sync by the topic ARN carried in the SNS envelope, so raw delivery strips the routing information and your events are ignored.
Only events published through the topic you enrolled reach your sync. Events sit alongside scheduled reconciliation rather than replacing it. Delivery is at-least-once and can drop or reorder messages, so keep a cadence set even with events enabled.
Reconcile on demand
Run a reconciliation right away instead of waiting for the next scheduled tick. It returns the changes it found and applied.
Pause, resume, and delete
Pause a sync to stop syncing without losing its configuration: send a PATCH
to /v2/syncs/{sync_id} with {"status": "paused"}, and resume with
{"status": "active"}.
Delete a sync with DELETE /v2/syncs/{sync_id}. This is a soft
delete: syncing stops, the record is marked inactive, and its per-object sync
state is cleared. The history is kept.
Google Cloud Storage
Authentication
A GCS sync authenticates with a service account key. Create a service account
with read access to the bucket (roles/storage.objectViewer), download its JSON
key, and pass the whole key as service_account_json when you create the sync.
Captain stores the key securely and never returns it. For a step-by-step
walkthrough with screenshots, from creating the service account to downloading the
key, see Connect Cloud Storage.
Create a sync
Creating a sync starts the first full index of the collection and returns the sync record right away, exactly as with S3.
GCS buckets are global, so there is no region or endpoint_url to pass. The
other fields (prefix, include_patterns, exclude_patterns,
processing_type, deletion_policy, sync_interval_minutes) work the same as
in the S3 section, and so do cadence, change detection, deletion
propagation, on-demand reconcile, and pause and delete.
Real-time events
GCS publishes bucket changes to Pub/Sub, and Pub/Sub pushes them straight to
Captain. Enroll first: Captain returns an ingest_url, an HTTPS endpoint hosted
by Captain that listens for this sync’s notifications.
Then wire the bucket to it:
OBJECT_FINALIZE fires for new and overwritten files and triggers a re-index;
OBJECT_DELETE follows your deletion policy. Metadata-only changes are ignored.
Keep the ingest_url private, because it authenticates the event stream for your
sync. As with every provider, delivery is best-effort, so leave a reconciliation
cadence set as the backstop.
S3-Compatible
Sync also works with Cloudflare R2, Supabase Storage, and Backblaze B2 over their S3-compatible interfaces. Each has its own create endpoint that takes the fields native to that store. These stores do not support IAM roles, so they authenticate with an access key. Once a sync exists it is managed with the same endpoints, whichever store it came from.
Real-time events work here too. Enroll with an empty body and Captain returns an
ingest_url: an HTTPS endpoint hosted by Captain, unique to this sync, that
listens for your store’s change notifications. You configure the store to send its
notifications there (the steps differ per store, shown in each tab), and Captain
applies each change to the collection as it arrives. Keep the URL private, because
the secret embedded in it is what authenticates the event stream for your sync.
Cloudflare R2
Supabase Storage
Backblaze B2
R2 derives its endpoint from your account ID and jurisdiction, so pass account_id rather than a full URL. For step-by-step setup of an R2 API token (Access Key ID + Secret Access Key) and finding your Account ID, see Connect Cloud Storage.
Real-time events. Cloudflare only delivers R2 bucket notifications to a Queue
inside your own account, and only a Worker in your account can read that queue;
nothing outside Cloudflare can subscribe to it. So the event path needs one small
piece on your side: bucket notifications land in your queue, and a forwarder
Worker passes each one to your ingest_url (the Captain-hosted endpoint from the
enrollment step above), where Captain applies the change. Queues require the
Workers Paid plan.
Then deploy the forwarder. wrangler.toml:
worker.js:
Run npx wrangler deploy and changes in the bucket start flowing to your
collection.
Cadence, deletion propagation, and on-demand reconcile behave the same on every provider. Real-time events behave the same once wired, but each store connects its notifications differently, as shown in the tabs above.
How it fits together
Captain runs the scheduled tick for you. Once a sync has a cadence, Captain reconciles it on that interval without you calling anything. Captain never runs anything inside your account; the pieces that live there (an SNS topic, a Pub/Sub subscription, the R2 forwarder Worker) are created and controlled by you.
A common production setup: create the sync with a cadence such as 15 and
deletion_policy: mirror, enroll the event webhook for faster updates, and leave
scheduled reconciliation on as the backstop. Reconciliation catches any events
that were dropped and brings the collection back in line with the bucket.
Endpoint reference
Full request and response schemas are in the Sync section of the API Reference. At a glance:

