Skip to main content

Scans API

Create a scan​

POST /v1/scans

Uploads code for analysis. This is the only endpoint CI credentials can call. The request is multipart/form-data.

The scan runs while the request is open, so the response already carries the result.

Request​

curl --fail-with-body -X POST "https://api.mushasec.com/v1/scans" \
-H "Authorization: Bearer $MUSHA_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-F "project_id=<project-id>" \
-F "scan_type=full" \
-F "branch=feature/auth" \
-F "pr_id=42" \
-F "commit_hash=<full-sha>" \

In practice you rarely write this by hand: the GitHub action and the generated pipelines build it for you, including added_lines.

Headers​

HeaderRequiredDescription
AuthorizationYesBearer plus a CI credential
Idempotency-KeyYesA UUID. Reusing it returns the original response

Form fields​

FieldRequiredDescription
project_idYesUUID of the project. Must match the credential's project
scan_typeYesfull, or any comma-separated mix of sca, iac and secrets (for example sca,iac)
branchYesBranch name
filesYesThe code as a gzipped tar archive (.tar.gz)
commit_hashNoSHA of the commit being scanned
pr_idNoPull or merge request number. Needed for Musha to post the comment
added_linesNoJSON object of the lines the pull request added. See below

Limits​

LimitValue
Request size50 MB
Files in one request50
Scans per hour, per organizationTrial 30, Starter 100, Team 500, Business 2,000, Enterprise 5,000
Scans per monthTrial 50, Starter 500, Team 5,000, Business and Enterprise unlimited

Reaching a limit returns 429 with a code such as QUOTA_EXCEEDED. When you hit the monthly limit you get a short grace period before new scans pause, and Musha never charges for extra scans. See Pricing.

added_lines​

Tells Musha which lines the pull request added, so it can tell new findings from technical debt. It maps each file path to a list of [start, end] line ranges:

{ "src/api/auth.go": [[10, 24], [40, 41]], "go.mod": [[7, 7]] }

Without it, or if the diff couldn't be computed, Musha falls back to a repository-wide view and does not conclude that the pull request touched nothing.

Response​

201 Created when the scan completed, 500 when it failed.

{
"scan_id": "<scan-id>",
"status": "completed",
"findings_count": 12,
"severity_breakdown": { "critical": 1, "high": 3 },
"blocked": true,
"untracked_count": 2,
"branch": "feature/auth",
"pr_id": "42",
"commit_hash": "<full-sha>"
}

blocked is true when the scan has findings your policy blocks on. The HTTP status does not change with it: a blocked scan is still 201. The GitHub action fails the job on blocked; for other CI, see GitLab CI.


Reading scans​

GET /v1/scans, GET /v1/scans/:id and the related endpoints list and show scans. They serve the app and need a signed-in user, so CI credentials can't call them. Use CI scans in the app.

Scan statuses​

StatusMeaning
pendingWaiting to run
completedFinished; findings are available
failedThe scan failed. See scan_warnings in the scan detail