Skip to main content

saas-retriever

API-first SaaS content retriever. Yields a uniform Document stream from seven SaaS providers via their official REST APIs. Downstream pipelines (pleno-anonymize, pleno-dlp) consume the same Document shape regardless of which provider produced it.

Install

uv add saas-retriever
# or, as a CLI:
pipx install saas-retriever

Connectors

kind targets resources
github org or single repo code, issues, pull requests (title + body + comments + diff)
gitlab group (recursive) or single project code, issues, merge requests (with per-file diff)
bitbucket Cloud workspace or Server project, optionally pinned to a repo code, pull requests, issues (Cloud only)
notion search / explicit pages / database query (combinable) page tree + database row properties → Markdown
confluence Cloud or Data Center; spaces enumerated then pages page body (storage XHTML → text) + comments + attachment refs
jira Cloud (/rest/api/3 + ADF) or Data Center (/rest/api/2 + storage XHTML) issues + comments + attachment URLs
slack xoxb (bot) or xoxp (user) tokens channels → history → threads → optional file refs

Usage

CLI

# Org-wide GitHub scan (default = code + issues + PRs across every repo)
GITHUB_TOKEN=ghp_... saas-retriever fetch github --owner plenoai

# Single repo, only issues
saas-retriever fetch github --owner plenoai --repo saas-retriever \
    --resource issues

# Filter to recently-updated content
saas-retriever fetch github --owner plenoai --since 7d

fetch streams Documents as NDJSON to stdout (or --out FILE). One line per Document: ref, text or binary_b64, fetched_at, content_hash, created_by, extra.

Programmatic

import asyncio
from saas_retriever import registry

async def main() -> None:
    gh = registry.create(
        "github",
        owner="plenoai",
        resources={"code", "issues", "prs"},
    )
    try:
        async for doc in gh.discover_and_fetch():
            kind = doc.ref.metadata.get("resource_type")
            print(kind, doc.ref.path, len(doc.text or ""))
    finally:
        await gh.close()

asyncio.run(main())

Every connector exposes the same Connector protocol — swap "github" for "gitlab", "slack", etc. and the loop above keeps working.

Auth

Each connector accepts either a typed Credential or the discrete constructor kwargs (token=, username=, email=, api_token=, …). Credential payload keys are auto-redacted in repr/str.

connector accepted credential shapes
github token= (PAT). CLI also resolves GITHUB_TOKEN env var or gh auth token.
gitlab token= + auth= ∈ {pat, project, oauth}. Bearer for OAuth, PRIVATE-TOKEN otherwise.
bitbucket Cloud: token= (Bearer) or username=/app_password= (Basic). Server: token= or username=/password=.
notion token= (Bearer integration token).
confluence Cloud: token= (Bearer) or email=/api_token= (Basic). DC: token= (Bearer PAT) or username=/password=.
jira access_token= (Bearer); Cloud: email=/api_token=; DC: username=/password=.
slack token= (xoxb-… or xoxp-…).

Cursors and incremental scans

Connectors that advertise Capabilities.incremental round-trip an opaque resume token through discover(filter, cursor=...):

  • gitlab / github — server-side filters where available.
  • confluence / jira — JSON cursor anchored on version.when / updated. Stale or malformed cursors fall back to a full re-walk.
  • slack — JSON {channel_id: latest_ts} per channel, fed back into Slack's oldest= parameter.
  • notion — search cursor round-tripped on every emitted ref via metadata["_cursor"].

Persist cursor_after_run() (when the connector exposes it) and pass the same string back on the next scan to resume.

Rate limiting

saas_retriever.AdaptiveTokenBucket + GlobalRateLimiter provide an AIMD bucket per BucketKey(connector_kind, tenant_id). Connectors raise RateLimited on persistent throttle (429 on most providers, plus 503 on Atlassian Data Center where their reverse proxy emits overload signals over 429 by policy). Callers can shrink the effective rate via on_throttle_signal(factor=0.5) and grow it back with on_success(recovery=...).

Development

uv sync --all-extras
uv run ruff check
uv run mypy src
uv run pytest

The default pytest pass uses httpx.MockTransport for every HTTP call — no live API access in CI.

Release

vX.Y.Z tag pushes trigger PyPI trusted publishing via GitHub Actions — no manual token. The first publish requires a one-time Trusted Publisher configuration at https://pypi.org/manage/account/publishing/:

Field Value
PyPI Project Name saas-retriever
Owner plenoai
Repository name saas-retriever
Workflow name release.yml
Environment name pypi

License

AGPL-3.0-or-later.

Metadata

Release files for saas-retriever 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for saas-retriever 1.0.0
File Size Uploaded
saas_retriever-1.0.0.tar.gz 74.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for saas-retriever 1.0.0
File Interpreter ABI Platform
saas_retriever-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 164.6 kB

Release files / saas_retriever-1.0.0.tar.gz

Download URL saas_retriever-1.0.0.tar.gz
Size 74.9 kB
Tags Source
SHA-256 checksum
How to use checksums
7a6581360e33dd48ec8d2f264648afadfdc283ab377629588baac189451b95e3
BLAKE2b-256 checksum
How to use checksums
dfb434e08a1d926daa249793a7601866cae49e2d0e9d6194237f1ffb4e5ec608
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.8

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 6, 2026.

Transparency log

Release files / saas_retriever-1.0.0-py3-none-any.whl

Download URL saas_retriever-1.0.0-py3-none-any.whl
Size 89.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a8542a990b354858446d0df29943a6976a7cc58ecd645dfb956e778a5d7f3ade
BLAKE2b-256 checksum
How to use checksums
63636353e40ee3a8eff6ec43741597dca9598c86b249d4c0b7a9c22f330d7c61
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.8

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 6, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page