MemorySync
Integrations

Amazon S3 integration

Index documents from the S3 bucket paths an administrator approves, including S3-compatible stores such as Cloudflare R2, MinIO, Wasabi, and Backblaze B2. Retrieve the contents of documents held in object storage, with the bucket, key, and a link back to the object.

What becomes retrievable

Retrieve the contents of documents held in object storage, with the bucket, key, and a link back to the object.

Text and Markdown

Plain text, Markdown, CSV, JSON, and other text formats.

Office documents

PDF, Word, PowerPoint, and Excel files, with text extracted.

Any S3 API

AWS S3 plus S3-compatible endpoints via a custom endpoint URL.

Connecting with IAM credentials

S3 is storage, not an identity provider, so there is no sign-in redirect. An administrator creates a read-only IAM user for the bucket and enters its access key. MemorySync proves the key can both locate the bucket and list its contents before the connection is saved, so an over-narrow policy fails on the connect screen rather than as a sync that quietly returns nothing.

Include kms:Decrypt for encrypted buckets
If the bucket uses SSE-KMS, a policy with only s3:ListBucket and s3:GetObject will list objects successfully and then fail every download with a plain access denial. Grant kms:Decrypt as well.
  • An S3 bucket, or a bucket on an S3-compatible service, holding documents worth retrieving.
  • A read-only IAM user with s3:ListBucket on the bucket and s3:GetObject on its contents. Add kms:Decrypt if the bucket uses SSE-KMS.
  • A MemorySync project for the imported documents.

Choose path scope

Bucket
Approved paths
Readable documents
Outside this path: objects outside every approved path, archived storage classes, unsupported file types, and keys matching an exclusion pattern
Path boundary
Only paths an administrator approves are listed, and objects outside them are never read. Within an approved path, MemorySync downloads only files whose type it can extract text from and that fall under the size limit, and it never writes to or deletes from the bucket. Objects in Glacier or Deep Archive are skipped, because reading them requires a restore. Keys that look like credentials — .env files, private keys, Terraform state — are excluded for every tenant and cannot be re-enabled.

What is downloaded, and what is only listed

Every exclusion is decided from the listing entry alone — key, size, storage class, and ETag — before any object is fetched. That ordering is deliberate: S3 bills requests and egress to your account, so an object that will not be indexed costs nothing but the listing it already appeared in.

DecisionMade fromCost
Excluded by patternThe keyNo download
Unsupported typeThe key’s extensionNo download
In Glacier or Deep ArchiveThe storage classNo download
Over the size limitThe reported sizeNo download
Unchanged since last syncThe ETagNo download
IndexedAll gates passedOne read

S3-compatible services

Anything speaking the S3 API works by supplying a custom endpoint on the connect screen: Cloudflare R2, MinIO, Wasabi, Backblaze B2, and others. Non-AWS endpoints use path-style addressing automatically, which is what most compatible services expect.

Region on compatible services
Many S3-compatible providers ignore the region entirely, or require a specific placeholder such as auto. If a connection is refused with a region error, check the provider\u2019s documentation for the value it expects.

Guided first connection checklist

Local planning guide
Plan your Amazon S3 import

Check items for your own planning. Nothing here changes a live connection.

0 of 4 planning steps complete

Initial and incremental ingestion

  1. 1
    Add IAM credentials

    An administrator enters a read-only access key, which is verified against the bucket before it is saved.

  2. 2
    Approve paths

    They select each path to index; approving a path covers everything beneath it.

  3. 3
    Index readable files

    MemorySync lists each approved path and downloads only files it can read.

  4. 4
    Sync on a schedule

    Later runs compare each object’s ETag, so unchanged files are never downloaded again.

Verify with a real document question

Query a distinctive phrase from a document you know sits under an approved path. Confirm the result names the right bucket and key before widening path access.

Troubleshooting and related sources

Why were the credentials rejected?

MemorySync checks both that the bucket exists and that its contents can be listed. A key that can list but not read will connect and then index nothing, so confirm the policy grants s3:GetObject on the bucket’s contents as well as s3:ListBucket on the bucket itself.

Why did only a few files index out of thousands?

That is usually correct. Buckets are full of things that are not documents, and each approved path reports what it skipped and why: unsupported types, archived storage classes, oversized files, and keys matched by an exclusion pattern.

Why does listing work but every download fail with access denied?

The bucket is almost certainly encrypted with SSE-KMS and the IAM policy is missing kms:Decrypt. S3 reports this as a plain access denial on GetObject, which makes it look like an S3 permission problem rather than a key policy one.

Why is a deleted file still retrievable?

S3 sends no deletion notifications, so removals are detected by comparing a fresh listing against what is stored. That happens on a periodic reconciliation rather than immediately.

Does connecting a bucket cost anything on my AWS account?

Listing and reading objects are billed to you as requests and egress. MemorySync is built to keep that small: every exclusion is decided from the listing entry, so a file that will not be indexed is never downloaded, and unchanged files are skipped by ETag comparison rather than re-read.

Operations on this page

OperationMethod and pathPythonNode.js
List selectable prefixesGET …/{connection_id}/s3/available-prefixesconnections.s3.available_prefixesconnections.s3.availablePrefixes
List selected prefixesGET …/{connection_id}/s3/prefixesconnections.s3.prefixesconnections.s3.prefixes
Select prefixesPOST …/{connection_id}/s3/prefixesconnections.s3.add_prefixesconnections.s3.addPrefixes
Revoke one prefixDELETE …/{connection_id}/s3/prefixesconnections.s3.remove_prefixconnections.s3.removePrefix
Read the exclusion policyGET …/{connection_id}/s3/exclusion-policyconnections.s3.exclusion_policyconnections.s3.exclusionPolicy
Replace the exclusion policyPUT …/{connection_id}/s3/exclusion-policyconnections.s3.set_exclusion_policyconnections.s3.setExclusionPolicy
Read effective settingsGET …/{connection_id}/s3/settingsconnections.s3.settingsconnections.s3.settings

Authentication and scope

RequirementContract
CredentialAn API key sent as X-API-Key. Connector operations are not end-user scoped.
Read scopeintegrations:read for every GET.
Write scopeintegrations:write for every POST, PUT, PATCH and DELETE.
TenantDerived from the authenticated key. There is no tenant parameter to pass or to get wrong.
X-End-User-IDNot used. A connection belongs to the organization, not to one end user.

Before you start

S3 is the credential-based connector: create it with create_with_credentials on Connection Lifecycle, passing access_key_id, secret_access_key, region and bucket. There is no OAuth flow.

1. See what the credentials can reach

Lists prefixes visible in the bound bucket, annotated with whether each is already approved.

import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
available = client.connections.s3.available_prefixes("conn_8f21a4")
print(available["bucket"], available["total"])
for entry in available["prefixes"]:
print(entry["prefix"] or "(root)", entry["kind"], entry["approved"])
GET/api/v2/integrations/connections/{connection_id}/s3/available-prefixes
200 OK
Response fieldMeaning
bucketThe bucket this connection is bound to.
prefixesEach with prefix, kind, and approved.
totalHow many were found.

2. Approve the prefixes to read

Bare strings are accepted for the common case, and objects when you want to carry a label. Approving a prefix starts the first sync straight away if the connection is healthy.

import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
result = client.connections.s3.add_prefixes(
"conn_8f21a4",
["handbook/", "policies/2026/"],
)
print(result["sync_triggered"])
for approved in result["approved"]:
print(approved["prefix"], approved["state"])
for rejected in result["rejected"]:
print("rejected:", rejected)
Approve prefixes
POST/api/v2/integrations/connections/{connection_id}/s3/prefixes
200 OK
Request fieldContract
prefixesRequired. A list of objects, each with prefix, and optionally bucket and label. Both SDKs widen a bare string to {"prefix": "…"} for you.
Response fieldMeaning
approvedThe prefix records that were stored.
rejectedEntries that were refused, each with a prefix and a reason. Show these — a silent partial success is how half a bucket goes missing.
sync_triggeredWhether approving started a sync immediately. It does when the connection is already connected.

3. Read back what is approved

Another of the bare-array responses: iterate the result itself rather than looking for a prefixes key.

import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
for row in client.connections.s3.prefixes("conn_8f21a4"):
print(row["prefix"] or "(root)", row["state"], row["is_active"])
print(" synced:", row["objects_synced"], "indexed:", row["objects_indexed"])
print(" skipped:", row["skipped"])
Read approved prefixes
GET/api/v2/integrations/connections/{connection_id}/s3/prefixes
200 OK
FieldMeaning
idThe prefix approval record.
bucket, prefix, labelWhat is approved.
state, state_detailApproval state and why.
is_activeWhether future syncs read it.
objects_syncedLifetime tally of keys read across every run.
objects_indexedDistinct objects currently stored. This is the one that answers "how much is in there".
skippedA map of reason to count — the first place to look when a prefix produced less than expected.
last_modified_at, last_synced_at, authorized_atTimestamps.

4. Block keys by pattern

The exclusion policy is a list of glob patterns matched against keys. A pattern blocks matching keys inside every approved prefix, which is how you keep a useful prefix without importing its build artifacts or backups.

import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
client.connections.s3.set_exclusion_policy(
"conn_8f21a4",
tenant_patterns=["*.log", "backups/*", "*/tmp/*"],
)
policy = client.connections.s3.exclusion_policy("conn_8f21a4")
print(policy["effective_patterns"])
print("removed:", policy["purged_objects"], "objects,", policy["purged_memories"], "memories")
Replace the policy
PUT/api/v2/integrations/connections/{connection_id}/s3/exclusion-policy
200 OK
Read it back
GET/api/v2/integrations/connections/{connection_id}/s3/exclusion-policy
200 OK
FieldMeaning
tenant_patternsRequest and response. The patterns you set — the only field PUT reads, and the only list it replaces.
deployment_patternsResponse only. The operator-set floor, which you cannot remove here.
effective_patternsResponse only. Both lists combined, deduped — what actually applies.
purged_objects, purged_memoriesResponse only. How much already-indexed content the new policy removed.

5. Read the limits that decide what is parsed

Effective settings, including the ceilings that quietly exclude objects. An object over the size limit or with an unlisted extension produces no memories and no error, so this is the endpoint that explains an unexpectedly empty prefix.

import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
settings = client.connections.s3.settings("conn_8f21a4")
print(settings["bucket"], settings["region"], settings["endpoint"])
print("max object MB:", settings["max_object_mb"])
print("max objects per sync:", settings["max_objects_per_sync"])
print("allowed extensions:", settings["allowed_extensions"])
GET/api/v2/integrations/connections/{connection_id}/s3/settings
200 OK
FieldMeaning
bucket, region, endpointWhat the connection points at. endpoint is set for S3-compatible storage.
prefixA connection-level prefix, when one was configured with the credentials.
max_object_mbObjects larger than this are skipped.
max_objects_per_syncThe ceiling on one run. A prefix larger than this needs several syncs.
allowed_extensionsOnly these are parsed. Anything else is skipped without an error.

6. Revoke one prefix

Removes a single approval. The prefix travels as a query parameter, not in the body and not as a path segment, because prefixes contain slashes.

import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
# Stop reading it, keep what it already produced.
client.connections.s3.remove_prefix("conn_8f21a4", "policies/2026/")
# Stop reading it and delete the memories it produced.
client.connections.s3.remove_prefix("conn_8f21a4", "policies/2026/", purge=True)
Revoke a prefix
DELETE/api/v2/integrations/connections/{connection_id}/s3/prefixes
204 No Content
ParameterContract
prefixThe approved prefix. Defaults to "", which is the bucket root — so omitting it targets the root, not "all prefixes".
bucketOptional. Defaults to the bucket the connection is bound to.
purgeOptional, default false. When true, also deletes the memories already derived from the prefix.

Errors and next action

StatusMeaningNext action
401Missing, malformed or inactive API key.Check server configuration without printing the key.
403The key lacks integrations:read or integrations:write.Grant the scope on the key, or use a key that has it.
404The connection, object or job is not visible to this tenant.Confirm the identifier belongs to this organization.
409The connection is in a state that forbids the operation.Read the connection status first and act on it.
429Rate limited, either by MemorySync or by the upstream provider.Back off; do not tighten a polling loop in response.
5xxService failure.Treat a write outcome as uncertain and reconcile by reading the connection back.
Was this page helpful?