API documentation
Start scans, read results and plug them into CI. Authenticate with an API key in the `X-API-Key` header (create one under Settings → Security; scopes: scan:read, scan:write, report:read, project:read). Responses use `{ "success": true, "data": … }`; errors use `{ "success": false, "error": "…" }`. Every request counts against your plan's per-minute (per key) and per-day (per workspace) limits.
Machine-readable spec: /api/v1/openapi.json (OpenAPI 3.1.0, version 1.0.0)
Quick start
# 1. start a scan (the Idempotency-Key makes a retry safe)
curl -X POST $WCS_URL/api/v1/scans \
-H "X-API-Key: $WCS_API_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: build-1234" -H "X-WCS-Client: cli" \
-d '{"url":"https://example.com","level":"quick"}'
# 2. wait until "status" is "complete" (send If-None-Match with the last ETag: unchanged answers 304)
curl -H "X-API-Key: $WCS_API_KEY" $WCS_URL/api/v1/scans/SCAN_ID
# 3. read the result with a pass/fail verdict
curl -H "X-API-Key: $WCS_API_KEY" "$WCS_URL/api/v1/scans/SCAN_ID/report?failUnder=80&failOn=critical"Command line
npx @wcs/cli scan https://example.com --fail-under 80 --fail-on critical --format sarif --output wcs.sarif
# exit code 0 = passed, 1 = did not pass, 2 = error (bad key, quota, network)GitHub Actions
- uses: YOUR_ORG/wcs-tools/packages/github-action@v1
with:
url: https://example.com
api-url: https://your-wcs-host
api-key: ${{ secrets.WCS_API_KEY }}
fail-under: 80
fail-on: critical
sarif-file: wcs.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with: { sarif_file: wcs.sarif }Limits
- Requests: per API key per minute and per workspace per day, by plan (Pro 120 and 5,000; Teams 300 and 20,000; Enterprise 1,000 and no daily cap). Every response carries
RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset; at the limit you get 429 with Retry-After. - Scans: every scan started through the API counts against the same monthly quota as scans started in the app.
- Polling: use
If-None-Match with the ETag from the last response; an unchanged scan answers 304. Checking every 5 to 10 seconds is plenty. - Scopes: a key can only call endpoints for its scopes; the required scope is shown on each endpoint.
Endpoints
post/api/v1/scans
Start a scan scope: scan:write
Counts against the monthly scan quota like any other scan. Send an `Idempotency-Key` to make retries safe. Set `X-WCS-Client` (`cli`, `github-action`) so usage can be reported by source.
| Parameter | In | Notes |
|---|
| Idempotency-Key | header | Same key and same body within 24 hours returns the first response (header `Idempotent-Replayed: true`). Same key with a different body returns 422. |
| X-WCS-Client | header | one of cli, github-action, github-app, other |
Responses: 201, 400, 401, 402, 403, 409, 422, 429
get/api/v1/scans
List scans scope: scan:read
| Parameter | In | Notes |
|---|
| limit | query | default 20 |
| cursor | query | |
| projectId | query | |
| status | query | one of queued, crawling, scanning, generating_report, complete, failed, cancelled |
Responses: 200, 400, 401, 403, 429
get/api/v1/scans/{scanId}
Get a scan scope: scan:read
Poll with `If-None-Match` set to the last `ETag`: an unchanged scan answers `304` with no body.
| Parameter | In | Notes |
|---|
| scanId * | path | |
| If-None-Match | header | |
Responses: 200, 304, 401, 403, 404, 429
get/api/v1/scans/{scanId}/report
Get a finished scan as JSON, SARIF or JUnit scope: report:read
`format=summary` (default) is a compact result with a pass/fail verdict for `failUnder` (minimum score) and `failOn` (fail if an issue of this severity or worse exists). `sarif` returns SARIF 2.1.0; `junit` returns JUnit XML. The HTTP status is 200 whether or not the verdict is a pass.
| Parameter | In | Notes |
|---|
| scanId * | path | |
| format | query | one of summary, json, sarif, junit. default summary |
| failUnder | query | |
| failOn | query | one of critical, high, medium, low, none. default none |
Responses: 200, 401, 403, 404, 409, 429
get/api/v1/projects
List projects scope: project:read
Responses: 200, 401, 403, 429
get/api/v1/projects/{projectId}/findings
List tracked findings of a project scope: project:read
| Parameter | In | Notes |
|---|
| projectId * | path | |
| state | query | one of open, acknowledged, ignored, accepted_risk, false_positive, fixed |
| severity | query | one of critical, high, medium, low, info |
| limit | query | default 50 |
Responses: 200, 401, 403, 404, 429
Using the results in CI
format=sarif returns SARIF 2.1.0 for code-scanning dashboards. Findings point at web pages, not repository files, so they appear in the Security tab of the repository but cannot be shown as line annotations. format=junit returns one test suite per scanner; with failOn set, issues of that severity or worse are reported as failures.