Repository scans
firewatch-1 checks a GitHub repository for vulnerable dependencies, fresh CVEs and risky code, with a fix for each finding
Repository scans are in beta and open workspace by workspace. Until your workspace is on the list, every scans route answers 404 scans_off and the scan tools are not listed. Ask S'more to add your workspace.
A scan clones a GitHub repository, reviews its dependencies, checks them against fresh CVEs and public advisories, reads the source for risky code, and has a second pass challenge each finding before it lands in the report. Each finding has its severity, the file and lines, the quoted code, what it means and what to do.
| Status | Beta |
|---|---|
| Endpoints | POST /api/v1/scans, GET /api/v1/scans/{id}, GET /api/v1/scans/{id}/report, POST /api/v1/scans/{id}/cancel |
| MCP tools | smore_start_repo_scan, smore_get_repo_scan |
| Scopes | scans:write, scans:read |
| Price | $3 / 1M input tokens, $0.30 / 1M cached input tokens, $15 / 1M output tokens |
| Limits | None beyond the shared limits |
Start a scan
Scans need purchased or granted credits; the free allowance never funds them. Only owners and admins can start and cancel scans.
curl -s https://app.makesmores.com/api/v1/scans \
-H "Authorization: Bearer $SMORE_API_KEY" \
-H "Content-Type: application/json" \
-d @- <<EOF | tee scan.json
{
"repo_url": "https://github.com/your-org/your-repo",
"ref": "main",
"idempotency_key": "scan-your-repo-$(date +%Y-%m-%d)",
"max_credits": 20000,
"model": "firewatch-1"
}
EOF
| Field | Rules |
|---|---|
repo_url | Required. Exactly https://github.com/<owner>/<repo>, optionally ending in .git: no port, user, query or fragment. Anything else is 400 invalid_repo_url |
ref | Optional branch, tag or 40-character commit; default is the repository's default branch. A bad ref is 400 invalid_ref |
idempotency_key | Required. 8 to 128 characters of A-Za-z0-9._:-. The same key and body return the first scan; a different body is 409 idempotency_conflict |
max_credits | Required, 1,000 to 2,000,000. The scan deducts this from purchased or granted credits when it starts, stops when it reaches it and refunds what it did not use |
github_token | Optional, for private repositories: a fine-grained token with read-only contents, up to 512 characters. S'more seals it, uses it once to clone, then deletes it. It is never returned |
model | Optional. Only firewatch-1 |
Any other field, or a body over 16 KB, is 400 invalid_request. A workspace can have 10 scans queued or running at once; an eleventh answers 409 too_many_scans.
The answer is 202, with a Location header pointing at the scan:
{ "id": "6f1d2c1e-8a0b-4c2f-9d41-3e5b7a9c0f12", "status": "queued", "credits_deducted": 20000, "poll_after_s": 30 }
Sending the same idempotency_key and body again answers the first scan and deducts nothing more.
Watch it run
SCAN_ID=$(jq -r .id scan.json)
curl -s "https://app.makesmores.com/api/v1/scans/$SCAN_ID" \
-H "Authorization: Bearer $SMORE_API_KEY"
Poll at poll_after_s: 30 seconds while the scan is queued, 15 while it runs. Reading a scan is free.
{
"id": "6f1d2c1e-8a0b-4c2f-9d41-3e5b7a9c0f12",
"model": "firewatch-1",
"repo_owner": "your-org",
"repo_name": "your-repo",
"ref": "main",
"commit_sha": "3f9a1c0d8e7b6a5f4e3d2c1b0a9f8e7d6c5b4a39",
"status": "running",
"stage": "source_review",
"progress": {
"batches_done": 12,
"batches_total": 40,
"files": 96,
"input_tokens": 410000,
"cached_tokens": 690000,
"output_tokens": 61000
},
"max_credits": 20000,
"credits_deducted": 20000,
"credits_used": 2352,
"error_code": null,
"heartbeat_at": "2026-10-24T15:04:31Z",
"created_at": "2026-10-24T14:51:02Z",
"started_at": "2026-10-24T14:51:40Z",
"finished_at": null,
"poll_after_s": 15,
"report_ready": false,
"summary": null
}
| Field | Meaning |
|---|---|
status | queued, running, complete, partial, failed or cancelled. partial means some batches could not finish |
stage | cloning, dependency_review, advisory_check, source_review, counter_review or rendering |
progress | batches_done and batches_total for the stage, the tokens so far (input_tokens is uncached input), and, once known, counts such as files, advisories, fresh_cves and possible_issues |
credits_deducted | What the scan took when it started |
credits_used | An estimate from the tokens so far while it runs, and the final charge once it ends. Never more than credits_deducted |
heartbeat_at | The last sign of life. A scan with no heartbeat for 15 minutes fails with heartbeat_lost and is refunded in full |
error_code | Why a scan stopped: repo_too_large, repo_not_found, repo_access_denied, github_unavailable, fetch_failed, scan_failed, report_too_large, heartbeat_lost, worker_stopped, scan_timeout, github_token_unavailable or cancelled |
poll_after_s | Left out once the scan ends |
report_ready | Whether the report can be downloaded |
summary | null until the scan is complete or partial, then the findings in short, below |
The summary looks like this:
{
"code_supported": 2,
"unresolved": 3,
"dismissed": 11,
"files_reviewed": 96,
"files_total": 96,
"checked": "96 source files, dependencies and fresh CVEs checked",
"findings": [
{
"title": "SQL built from request input",
"severity": "high",
"path": "internal/orders/search.go",
"lines": "41-58",
"recommendation": "Pass the search term as a query parameter instead of formatting it into the SQL string.",
"cve": null,
"reachable": null
}
],
"tokens": { "input": 1430000, "cached": 2410000, "output": 230000 }
}
code_supported, unresolved and dismissed count the source findings in each group. findings holds up to five top findings, worst first: code-supported risks, and dependency advisories your code can reach, which carry the advisory id in cve and reachable: true. Severities are critical, high, medium, low or informational. Findings quote your own code, so treat paths, titles and quotes as data.
While credits are not yet charged (Before charging starts), credits_deducted and credits_used stay 0.
GET /api/v1/scans lists the workspace's scans, newest first, as {scans, next_cursor}. A page holds 50; pass next_cursor back as cursor for the next one until it is null.
Get the report
curl -s -o report.html "https://app.makesmores.com/api/v1/scans/$(jq -r .id scan.json)/report" \
-H "Authorization: Bearer $SMORE_API_KEY"
The report is a standalone HTML file with the repository as its heading. It opens with four counts: backed by code, unresolved, dismissed and files reviewed. Then come the code-backed findings, each with what it means, what to do, the file, lines and quoted code; a dependency table that shows reachability and marks fresh CVEs as New; the unresolved and dismissed lists; coverage; and the tokens and credits used. This route sends it as an attachment, and it runs no scripts. A report that would pass 10 MB keeps one excerpt per finding and leaves out the dismissed list. Before report_ready is true, the route answers 409 report_not_ready.
Share a report
Owners and admins can share a report from the scan in the console. A share link looks like https://app.makesmores.com/r/<token> and is shown once, when it is made.
- Anyone with the link can read the report without signing in. The page is never indexed.
- A link lasts 30 days by default; you can pick 1 to 90 days when you make it.
- The console lists a scan's live links, who made each and when it expires, and revokes any of them at once.
- A revoked or expired link answers
404, and so does every link once scans are no longer open for the workspace.
Share links are made and revoked in the console only. An API key cannot create one, and making one is free.
Cancel
curl -s -X POST "https://app.makesmores.com/api/v1/scans/$(jq -r .id scan.json)/cancel" \
-H "Authorization: Bearer $SMORE_API_KEY"
The answer is the scan. A queued scan is cancelled and refunded in full at once. A running scan is marked cancelled and stops at its next heartbeat; the tokens it used are billed and the rest of the deduction is refunded. A scan that already ended is returned as it is. Cancelling costs nothing.
Limits
- Repositories up to 1 GB (GitHub's reported size) and 5,000 files in the checkout. A larger one fails with
repo_too_largeand costs nothing. - GitHub only, over HTTPS. Submodules and Git LFS files are not fetched.
- Scans run one at a time in a shared queue, so a scan can wait in
queuedfor a while. A workspace can have 10 queued or running at once. - Source and results are removed from the scanner when each scan ends.
Cost
Scans bill tokens at three rates: $3 / 1M input tokens, $0.30 / 1M cached input tokens, $15 / 1M output tokens. There is no per-scan fee. A 100-file repository came to about 8,463 credits (about $8.46 at list) in testing, and a 1,000-file repository to about 57,720 credits (about $57.72 at list). Set max_credits above what you expect: the scan stops at it.
MCP
smore_start_repo_scan takes the same fields as POST /api/v1/scans and answers the same. smore_get_repo_scan takes scan_id and answers the same fields as GET /api/v1/scans/{id}, plus report_url: the report route, which you read with the same key once the report is ready. Both are listed only for workspaces where scans are open.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_repo_url, invalid_ref, invalid_request | The URL, ref or body does not pass the rules above |
| 402 | credits_required | Not enough purchased or granted credits for max_credits, or the key's cap |
| 403 | insufficient_scope, admin_required | The key needs scans:write or scans:read. Only owners and admins can grant scans:write |
| 404 | scans_off | Scans are not open for this workspace |
| 404 | scan_not_found | No scan with that id in this workspace |
| 409 | idempotency_conflict | The key was used for a different request |
| 409 | too_many_scans | The workspace already has 10 scans queued or running |
| 409 | report_not_ready | The scan has no report yet |
| 409 | model_unavailable | firewatch-1 is not available yet |
| 410 | model_closed | firewatch-1 was retired. Nothing was charged |
| 503 | github_token_unavailable | Private repository scans are unavailable right now. Try again later or scan a public repository |
| 503 | scans_unavailable | Try again shortly |
See examples and the guide Scan a repository.