Query the secrets LeakWatch detects in public repositories, from your own scripts, CI, or agents. REST over HTTPS, JSON responses, an OpenAPI schema for machine consumption.
Interactive reference (Swagger) → · OpenAPI schema
The read endpoints work without any credentials. Try one right now:
curl https://leakwatch.net/api/v1/leaks/stats
Every path below is relative to the base URL https://leakwatch.net/api/v1.
Account features — and the higher rate limits — need an API key. Create one in your dashboard, then send it as a bearer token:
curl https://leakwatch.net/api/v1/leaks/search?account=octocat&source=github \ -H "Authorization: Bearer lw_live_your_key_here"
The key is shown once, when you create it: we store only a hash, so a lost key cannot be recovered — revoke it and create another. Treat it like a password; anyone holding it acts as your account. If you ever leak one, revoking it from the dashboard takes effect immediately.
These read from the public detection database and work with or without a key. Everything here is masked and detached from its repository.
| Endpoint | Auth | Description |
|---|---|---|
GET/leaks/recent | None | The 50 most recent detections. Secrets masked, repository never revealed. |
GET/leaks/search | Optional | Aggregated counts for an account: total, distinct repositories, severity breakdown. Takes account and source. |
GET/leaks/stats | None | Global statistics: all-time total, last 24 hours, severity breakdown. |
GET/detectors | None | Catalogue of every secret type the engine recognises, with its severity. |
GET/health | None | Liveness probe. |
GET/version | None | API version. |
curl https://leakwatch.net/api/v1/leaks/recent
{
"leaks": [
{
"type": "AWS Access Token",
"forge": "github",
"masked_secret": "AKIA...",
"discovered_at": "2026-08-12T09:14:22Z"
}
]
}curl "https://leakwatch.net/api/v1/leaks/search?account=octocat&source=github"
{
"account": "octocat",
"source": "github",
"total": 3,
"distinct_repos": 2,
"by_severity": { "critical": 2, "high": 0, "medium": 1, "low": 0 }
}These endpoints act on your own data and always require a key. What you get back follows your plan — GET /me tells you which one applies and how much quota is left, so integrations can check rather than guess.
| Endpoint | Plan | Description |
|---|---|---|
GET/me | Any | Your account: plan, linked identities, rate limit and remaining deep-scan quota. |
GET/me/leaks | Any | Every secret found across your verified identities. Full values on paid plans only. |
GET/scans | Any | Your recent deep scans. |
POST/scans | Any (quota) | Scan the full git history of one of your repositories. Free plans get one per window. |
GET/scans/{id} | Any | Progress of a scan — poll while queued or running. |
GET/scans/{id}/report | Any | Everything currently known about the scanned repository, values in full. |
GET/monitors | Any | Repositories you have under continuous monitoring. |
POST/monitors | Solo / Team | Put one of your public repositories under continuous monitoring. |
DELETE/monitors/{id} | Solo / Team | Stop watching a repository. Findings already detected are kept. |
GET/monitors/{id}/findings | Solo / Team | Secrets found in one of your monitored repositories, values in full. |
POST/scan/content | Any | Scan text you submit for secrets. Nothing is stored — ideal for a CI gate. |
POST/me/leaks/{id}/validate | Solo / Team | Re-test one of your own leaked keys against its provider. |
# Start the scan (returns immediately, status "queued")
curl -X POST https://leakwatch.net/api/v1/scans \
-H "Authorization: Bearer lw_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"repo_full_name": "octocat/hello-world"}'
# Poll until status is "success", then read the report
curl https://leakwatch.net/api/v1/scans/<id>/report \
-H "Authorization: Bearer lw_live_your_key_here"In GET /me/leaks, the secret, commit_url and still_active fields are populated on paid plans and null on the free plan — the fields are always present, so your parsing does not change. Scan reports and monitored-repository findings always return values in full: those are repositories you own, and a masked value would not tell you which key to revoke.
Send text — a diff, a file, an environment block — and get back the secrets found in it. The same engine and the same /detectors catalogue that power our public scanning. Useful as a pre-push hook or a CI gate that fails a build before a key ever reaches a public repository.
curl -X POST https://leakwatch.net/api/v1/scan/content \
-H "Authorization: Bearer lw_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"content": "AWS_SECRET=AKIAIOSFODNN7EXAMPLE\n", "filename": ".env"}'
{
"filename": ".env",
"count": 1,
"bytes_scanned": 41,
"secrets": [
{
"type": "AWS Access Token",
"severity": "critical",
"value": "AKIAIOSFODNN7EXAMPLE",
"line": 1
}
]
}Nothing you send is stored. The content is scanned in memory and discarded when the response is sent: no database row is written, and it is never forwarded to a third party — detection is local pattern matching, not a model call. Bodies are capped at 1 MiB; split larger inputs and scan them in parts.
Values are returned in full, since they came from the content you submitted. A hit is a pattern match, not proof the credential is live — this endpoint never tests it against its provider.
That is a separate endpoint, and it works from a leak id rather than a key value: POST /me/leaks/{id}/validate re-tests a credential that was found in one of your repositories, then updates still_active in GET /me/leaks. There is deliberately no endpoint that validates an arbitrary key you paste in: that would be a testing service for stolen credentials, which is not something we are willing to run.
These are deliberate limits, not gaps. LeakWatch publishes proof that a secret leaked — never a working copy of it:
AKIA…, ghp_…). Full values are reserved for the verified owner of the account they belong to, so they can revoke them.Limits follow your plan and are counted per key, so two integrations behind one IP each get their own budget. Over the limit, the API answers 429 — back off and retry.
| Plan | Limit | |
|---|---|---|
| No key | per IP | Enough to try things out and for occasional use. |
| Free | 60 req/min | Counted per key, not per IP. |
| Solo | 300 req/min | |
| Team | 1,000 req/min |
Errors use standard HTTP status codes and carry a detail field explaining what went wrong.
| Code | Meaning |
|---|---|
| 401 | Missing, malformed, or revoked API key. |
| 403 | Valid key, but not allowed — e.g. a Codeberg lookup for an account you have not verified. |
| 422 | Invalid parameters (unknown source, malformed account). |
| 429 | Rate limit exceeded. |
Webhooks — a URL called whenever a new secret is found in one of your repositories, instead of polling /me/leaks. Want that, or something else? Tell me.
The Swagger reference is generated from the running code — when in doubt, it is the source of truth.