MemorySync
API Reference

Web Crawler

Turn a public website into memories. Crawling and importing are deliberately separate steps: a crawl fetches and stores pages so you can look at what it found, and nothing becomes a memory until you import it.

Operations on this page

OperationMethod and pathPythonNode.js
Read crawler limitsGET …/web-crawler/configintegrations.web_crawler.configintegrations.webCrawler.config
Validate a URLPOST …/web-crawler/validateintegrations.web_crawler.validateintegrations.webCrawler.validate
Start a crawlPOST …/web-crawler/crawlintegrations.web_crawler.crawlintegrations.webCrawler.crawl
List crawl jobsGET …/web-crawler/jobsintegrations.web_crawler.jobsintegrations.webCrawler.jobs
Get one jobGET …/web-crawler/jobs/{job_id}integrations.web_crawler.jobintegrations.webCrawler.job
List running crawlsGET …/web-crawler/activeintegrations.web_crawler.activeintegrations.webCrawler.active
Read fetched pagesGET …/web-crawler/jobs/{job_id}/contentintegrations.web_crawler.job_contentintegrations.webCrawler.jobContent
Read job statisticsGET …/web-crawler/jobs/{job_id}/statisticsintegrations.web_crawler.job_statisticsintegrations.webCrawler.jobStatistics
Import pages as memoriesPOST …/web-crawler/jobs/{job_id}/importintegrations.web_crawler.import_jobintegrations.webCrawler.importJob
Cancel a crawlPOST …/web-crawler/jobs/{job_id}/cancelintegrations.web_crawler.cancel_jobintegrations.webCrawler.cancelJob
Delete a jobDELETE …/web-crawler/jobs/{job_id}integrations.web_crawler.delete_jobintegrations.webCrawler.deleteJob

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.

There is no connection to create

Unlike Slack or S3 there is no provider to authorise and no connection_id. A crawl targets a public URL, so the crawler is reached directly under /api/v1/integrations/web-crawler, and jobs are scoped to your organization by the API key alone.

How a crawl becomes memories

Two stages, one decision point
  1. 11. Validate

    Check the URL is reachable and permitted before spending a job on it.

  2. 22. Crawl

    Pages are fetched and stored. No memories exist yet.

  3. 33. Inspect

    Read the fetched pages and the statistics. Decide what is worth keeping.

  4. 44. Import

    The pages you choose become memories. This is the step that changes your memory store.

  5. 55. Tidy up

    Cancel a crawl that is going wrong; delete a finished job you no longer need.

1. Read the limits before designing a crawl

The ceilings are set by the deployment, not by your request. Reading them first means a client can present real bounds instead of guessing and being rejected.

import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
limits = client.integrations.web_crawler.config()
print(limits["max_depth"], limits["max_pages"])
print(limits["allowed_protocols"], limits["rate_limit_ms"], "ms between requests")
GET/api/v1/integrations/web-crawler/config
200 OK
FieldMeaning
max_depthThe deepest link depth the deployment permits.
max_pagesThe most pages one crawl may fetch.
rate_limit_msDelay the crawler leaves between requests to one host.
max_content_size_bytesPer-page ceiling. A larger page is skipped rather than truncated.
max_total_crawl_size_bytesCeiling across the whole job.
max_crawl_duration_minutesWall-clock ceiling. A crawl that exceeds it stops where it got to.
allowed_protocolsSchemes the crawler will follow.

2. Validate the URL first

Validation is cheap and tells you whether a crawl is worth starting. It checks reachability and whether the URL is permitted, and returns the domain it resolved.

import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
check = client.integrations.web_crawler.validate("https://example.com/handbook")
if not check["valid"]:
print("cannot crawl:", check["error_code"], check["error"])
else:
print("will crawl", check["domain"])
POST/api/v1/integrations/web-crawler/validate
200 OK

3. Start the crawl

A crawl returns a job immediately. The depth is the setting that decides whether this is one page or a site: max_depth of 0 fetches only the URL you gave.

import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
job = client.integrations.web_crawler.crawl(
"https://example.com/handbook",
crawl_type="page",
settings={"max_depth": 1, "max_pages": 25, "store_html": False},
)
print(job["id"], job["status"], job["domain"])
POST/api/v1/integrations/web-crawler/crawl
201 Created
Request fieldContract
urlRequired. The page to start from.
crawl_typeOptional, defaults to page. Also accepts article and document.
settings.max_depthOptional, 05, defaults to 0. Zero means the single page you named.
settings.max_pagesOptional, 11000, defaults to 10.
settings.store_htmlOptional, defaults to false. Keeps the original markup as well as the text.
settings.include_imagesOptional, defaults to false. Records image URLs in page metadata.

4. Watch the job

Poll the job, or list the ones running now. can_import is the flag worth branching on — it tells you the job reached a state where importing is possible, without your having to interpret the status string.

import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
job = client.integrations.web_crawler.job("crawl_5d90e2")
print(job["status"], job["pages_crawled"], "of", job["pages_found"])
print("failed:", job["pages_failed"], "skipped:", job["pages_skipped"])
if job["can_import"]:
print("ready to import")
for running in client.integrations.web_crawler.active():
print("in flight:", running["id"], running["url"])
GET/api/v1/integrations/web-crawler/jobs/{job_id}
200 OK
GET/api/v1/integrations/web-crawler/active
200 OK
Job fieldMeaning
id, url, domain, crawl_typeWhat was asked for.
statusWhere the job is. pending and processing mean still running.
pages_found, pages_crawledDiscovered versus successfully fetched.
pages_failed, pages_skippedFetch errors, and pages excluded by size or type.
error_message, error_codeWhy the job itself failed, when it did.
created_at, started_at, completed_atTimestamps. Null until each stage is reached.
can_importWhether the fetched pages can be imported now.

5. List and page through jobs

The job list is paginated with limit and offset, and filtered with status.

import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
page = client.integrations.web_crawler.jobs(status="completed", limit=20, offset=0)
print(page["total"], "jobs")
for job in page["items"]:
print(job["id"], job["url"], job["pages_crawled"])
GET/api/v1/integrations/web-crawler/jobs
200 OK
Query parameterContract
statusOptional. Filter to one job status.
limitOptional, 1100, defaults to 20.
offsetOptional, 0 or more, defaults to 0.

6. Look at what was fetched, before importing

This is the step that makes the two-stage design worth having. Read the pages the crawl stored and decide which are worth keeping — a navigation page or a login wall is not.

import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
pages = client.integrations.web_crawler.job_content("crawl_5d90e2", limit=50, offset=0)
print(pages["total"], "pages fetched")
for content in pages["items"]:
print(content["id"], content["status_code"], content["content_length"], content["title"])
print(" ", content["url"])
print(" ", (content["content_preview"] or "")[:120])
stats = client.integrations.web_crawler.job_statistics("crawl_5d90e2")
print(stats["pages_crawled"], "crawled in", stats["crawl_duration_seconds"], "s")
print("average", stats["average_page_time_ms"], "ms per page")
GET/api/v1/integrations/web-crawler/jobs/{job_id}/content
200 OK
GET/api/v1/integrations/web-crawler/jobs/{job_id}/statistics
200 OK
Page fieldMeaning
idThe content id. Pass these to import to select specific pages.
url, title, depthWhere it came from and how far in.
content_preview, content_lengthThe start of the extracted text, and its full length.
status, status_code, error_messageFetch outcome, including the HTTP status the site returned.
content_size_bytesStored size, useful for spotting a page that hit the size ceiling.
metadataExtracted metadata, including image URLs when include_images was set.
crawled_at, crawl_duration_msWhen it was fetched and how long it took.
memory_id, imported_atSet once the page has been imported. Null means not yet imported.
StatisticMeaning
pages_crawled, pages_failed, pages_skippedOutcome counts for the job.
total_content_bytes, total_html_bytesExtracted text and, when stored, original markup.
average_page_time_ms, crawl_duration_secondsHow long it took.

7. Import the pages worth keeping

Importing is what creates memories. Omit content_ids to import everything the job fetched, or pass the specific ids you chose in the previous step. Tags are applied to every memory the import creates, which is what makes a crawl's output findable and removable as a set.

import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
result = client.integrations.web_crawler.import_job(
"crawl_5d90e2",
content_ids=["cnt_4a1", "cnt_4a2"],
tags=["handbook", "crawl-2026-08"],
)
print(result["imported_count"], "imported,", result["failed_count"], "failed")
print(result["memory_ids"])
for error in result["errors"]:
print("error:", error)
POST/api/v1/integrations/web-crawler/jobs/{job_id}/import
200 OK
Request fieldContract
content_idsOptional. Specific page ids to import. Omit to import everything the job fetched.
tagsOptional. Applied to every memory this import creates.
Response fieldMeaning
successWhether the import completed as a whole.
imported_count, failed_countPages turned into memories, and pages that could not be.
memory_idsThe memories created. Keep these if you may need to remove the set later.
errorsPer-page reasons. Read these rather than only the counts.

8. Cancel a crawl, or delete a finished job

Cancelling stops a running crawl and keeps the pages already fetched, so a crawl that is going wider than intended can be stopped and still be useful. Deleting removes the job and its fetched pages.

import os
from memorysync import MemorySyncClient
client = MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
)
client.integrations.web_crawler.cancel_job("crawl_5d90e2")
# Only once it is no longer running.
client.integrations.web_crawler.delete_job("crawl_5d90e2")
POST/api/v1/integrations/web-crawler/jobs/{job_id}/cancel
200 OK
DELETE/api/v1/integrations/web-crawler/jobs/{job_id}
200 OK

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.