Documentation
Connecting multiple repositories
How findings roll up across repos, plan-based repo caps, and what happens when you downgrade.
Last updated May 11, 2026
Attestly is built around a tenant — one company, one trust center, one published policy set — but you can connect as many repositories to that tenant as your plan allows. This page explains the model.
Findings aggregate, the trust center stays single
Every repo you connect runs the scanner independently. Each scan produces its own set of findings (subprocessors, AI providers, PII shapes, license set, custom-detector hits). Those findings then aggregate into a tenant-level view:
| Surface | Scope |
|---|---|
/dashboard/repos and /dashboard/repos/<id> | Per-repo: status, history, latest-scan findings, dependency counts. |
/dashboard/subprocessors | Tenant-wide: union across all active repos. |
/dashboard/drift | Tenant-wide: alerts from any active repo. |
/dashboard/documents | Tenant-wide: documents are generated from the aggregate finding set. |
Public /trust/<slug> | Tenant-wide: one trust center per tenant, regardless of how many repos contributed. |
If @sentry/nextjs is detected in three different repos, Sentry shows
up once in your subprocessor list. The trust center is what your
customers see — they care that you process data with Sentry, not
which microservice imports its SDK.
Plan caps
The repo count is the primary axis of pricing. See Pricing; current caps:
| Plan | Active repos | Scan cadence |
|---|---|---|
| Free | 1 | weekly |
| Starter ($99/mo) | 1 | weekly |
| Growth ($299/mo) | 5 | daily |
| Scale ($999/mo) | Unlimited | realtime |
| Enterprise | Unlimited | realtime |
"Active" is the operative word: a repo counts against your cap from the moment you connect it. There is no "free trial of an extra repo".
What happens at the cap
When you try to connect a repo and you're already at the cap,
Attestly redirects you to billing with an explicit upgrade prompt.
You'll also see this state on the Connect a repo page itself —
the Scan button is replaced with Upgrade to scan for any
repository GitHub returns once you're at the limit. The block is
enforced server-side in addRepoAction; the dashboard never silently
swallows the request.
The block is recorded in your audit log as repo.connect.blocked
with the plan id and current usage, so you can see the upgrade
pressure if you're chasing why a teammate hit a wall.
What happens on downgrade
Downgrades are the trickier case. If you're on Growth with 5 repos connected and you downgrade to Starter (1 repo), nothing is deleted. Instead, repos beyond the new cap are paused:
- All repos remain visible in
/dashboard/reposwith an Active or Paused (over plan) pill. - The oldest repos by
createdAtstay active; newer ones pause. Ordering is deterministic, so the same repos are active across page loads and there's no surprise reshuffle on each request. - Paused repos refuse to scan: the Inngest
scan/requestedworker short-circuits at theassert-repo-active-on-planstep, writes ascan.skipped.cap_exceededaudit entry, and exits cleanly. - The dashboard's per-repo page shows a banner explaining the pause with a one-click Upgrade path.
- Existing scan history, findings, generated documents, and trust center content for paused repos are preserved — only future scans stop.
To "switch which repo is active" on a constrained plan, just disconnect the active one. The next-oldest paused repo will become active on the following render.
Drift, PR checks, and customer notifications across repos
- Drift detection runs per-repo; alerts surface in the tenant-wide drift queue with the source repo shown on the alert row.
- GitHub PR-scoped scans run on whichever repo opened the PR — the check appears against that PR head only.
- Subprocessor change emails fire when the aggregate tenant inventory changes. Adding Sentry in a new repo when it's already detected elsewhere does not trigger an email — your customers were already notified.
- Removing a subprocessor only fires when the last repo to detect
it stops detecting it. Internally each
(subprocessor, repo)pair is recorded insubprocessor_detections; the scan worker reconciles only the rows for the repo it just scanned and sweeps subprocessors that have no remaining attribution toremoved_at. - On the subprocessors page, multi-repo tenants get a Detected in column with chips for the repos currently producing each finding. Clicking a chip opens the per-repo page so you can trace a vendor back to the source code that imports its SDK.
Notifications on downgrade
When a Stripe subscription downgrade drops you below your current
connected-repo count, we send a repos-paused email to the tenant's
contactEmail and create an in-app notification on /dashboard.
The event is logged as billing.repos_paused_on_downgrade in your
audit chain so the cap change is provable later. You can re-activate
a paused repo by upgrading or by disconnecting an older active repo.
Onboarding tip
The fastest path for a multi-repo tenant is usually:
- Connect your primary monorepo or main service first. Let the scan run; it bootstraps the document set.
- Add satellite repos one at a time. Watch the subprocessor list to confirm each one adds (or doesn't add) the vendors you expect.
- Set custom detectors at the tenant level (
.attestly/detectors.tsin a single source-of-truth repo) — the scanner reads them per repo, but you usually want the same definitions everywhere.
Programmatic access
Every repo has an id you can use from the REST API:
curl https://api.attestly.dev/v1/scans?repo_id=<id>&limit=10 \
-H "Authorization: Bearer $ATTESTLY_API_KEY"
Subprocessor and finding endpoints are tenant-scoped only; if you
need per-repo finding queries, drill in from the dashboard or use
/v1/findings?scan_id=… with a scan id from the scans endpoint.