Skip to main content

Repository scans

firewatch-1 checks a GitHub repository for vulnerable dependencies, fresh CVEs and risky code, with a fix for each finding

note

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.

StatusBeta
EndpointsPOST /api/v1/scans, GET /api/v1/scans/{id}, GET /api/v1/scans/{id}/report, POST /api/v1/scans/{id}/cancel
MCP toolssmore_start_repo_scan, smore_get_repo_scan
Scopesscans:write, scans:read
Price$3 / 1M input tokens, $0.30 / 1M cached input tokens, $15 / 1M output tokens
LimitsNone 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
FieldRules
repo_urlRequired. Exactly https://github.com/<owner>/<repo>, optionally ending in .git: no port, user, query or fragment. Anything else is 400 invalid_repo_url
refOptional branch, tag or 40-character commit; default is the repository's default branch. A bad ref is 400 invalid_ref
idempotency_keyRequired. 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_creditsRequired, 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_tokenOptional, 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
modelOptional. 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
}
FieldMeaning
statusqueued, running, complete, partial, failed or cancelled. partial means some batches could not finish
stagecloning, dependency_review, advisory_check, source_review, counter_review or rendering
progressbatches_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_deductedWhat the scan took when it started
credits_usedAn estimate from the tokens so far while it runs, and the final charge once it ends. Never more than credits_deducted
heartbeat_atThe last sign of life. A scan with no heartbeat for 15 minutes fails with heartbeat_lost and is refunded in full
error_codeWhy 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_sLeft out once the scan ends
report_readyWhether the report can be downloaded
summarynull 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_large and 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 queued for 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​

StatusCodeMeaning
400invalid_repo_url, invalid_ref, invalid_requestThe URL, ref or body does not pass the rules above
402credits_requiredNot enough purchased or granted credits for max_credits, or the key's cap
403insufficient_scope, admin_requiredThe key needs scans:write or scans:read. Only owners and admins can grant scans:write
404scans_offScans are not open for this workspace
404scan_not_foundNo scan with that id in this workspace
409idempotency_conflictThe key was used for a different request
409too_many_scansThe workspace already has 10 scans queued or running
409report_not_readyThe scan has no report yet
409model_unavailablefirewatch-1 is not available yet
410model_closedfirewatch-1 was retired. Nothing was charged
503github_token_unavailablePrivate repository scans are unavailable right now. Try again later or scan a public repository
503scans_unavailableTry again shortly

See examples and the guide Scan a repository.