POST /scan/bulk
Synchronous bulk-scan endpoint. Accepts up to 50 SKILL.md files in one call — by URL, by pre-fetched content, or a mix. Built primarily for CI integrations like the SkillOx GitHub Action; same scanner engine as the single-skill POST /scan, just batched + sync.
Need to scan one skill at a time? Use
POST /scan — it's queued + Turnstile-gated for anonymous browser callers. /scan/bulk is sync, headless, and rate-limited per IP.Request
POST https://api.skillox.io/scan/bulk
Content-Type: application/json
{
"urls": [
"https://raw.githubusercontent.com/foo/bar/main/SKILL.md"
],
"files": [
{
"path": "skills/leaky/SKILL.md",
"content": "---\nname: leaky\n---\n…"
}
],
"persist": false
}Fields
urls— array of HTTP(S) URLs. The server fetches each one (15s timeout, 100 KB body cap). Optional iffilesis present.files— array of{ path, content }objects.pathis a display label only (it shows up in annotations);contentis the raw SKILL.md body. Max 100 KB per file. Optional ifurlsis present.persist— boolean, defaultfalse. When true, each scan is written to thescanstable and ascanIdis returned per item (addressable at/scan/:idand/r/:id). Default off because CI noise shouldn't clog the public report-card view.
Combined urls + files cannot exceed 50 items per request. At least one must be present.
Response · 200 OK
200 OK
{
"summary": {
"total": 2,
"passed": 1,
"failed": 1,
"caution": 0,
"errors": 0
},
"results": [
{
"ok": true,
"input": { "kind": "url", "url": "https://raw.githubusercontent.com/…/SKILL.md" },
"grade": "A",
"score": 95,
"skillName": "frontend-design",
"skillVersion": null,
"findingsCount": 1,
"findings": [
{ "ruleId": "no-manifest", "severity": "med", "title": "No capability manifest declared", "line": null }
],
"scanDurationMs": 1
},
{
"ok": true,
"input": { "kind": "file", "path": "skills/leaky/SKILL.md" },
"grade": "F",
"score": 0,
"skillName": "leaky",
"skillVersion": "0.1.0",
"findingsCount": 5,
"findings": [
{ "ruleId": "env-var-harvesting", "severity": "crit", "title": "Skill references secret env var $ANTHROPIC_API_KEY", "line": 9 }
],
"scanDurationMs": 0
}
],
"remaining": 49
}Summary fields
passed— items graded A or Bcaution— items graded Cfailed— items graded D or Ferrors— items that couldn't be fetched or scanned
Per-item result fields
ok— boolean.true= scanned successfully (grade present),false= fetch or scan errored (seeerror+detail)input— echo of what you sent, useful when items are processed concurrentlygrade,score— the standard A–F grade + 0-100 scorefindings— slim summary array. Each entry hasruleId,severity,title, andline(when available). Full excerpt context lives on the per-skill Report Card; CI integrations don't usually need it
Limits
- 50 items per request (combined urls + files)
- 100 KB per file content / fetched response body
- 15s fetch timeout per URL
- 50 calls per 24 h per IP — counts the call, not the items inside it. A separate rate bucket from
/scan, so a heavy bulk user doesn't lock themselves out of the single-skill endpoint or vice-versa.
Errors
- 400 invalid_request — both
urlsandfilesempty, items over the cap, or a file content exceeded 100 KB - 429 rate_limited — see Limits. The
Retry-Afterheader is the seconds until the next window opens
Per-item failures (URL fetch failed, content too large, scan errored) don't fail the whole call — they come back as { ok: false, error: "fetch_failed" | "content_too_large" | "scan_failed" } entries in the results array so successful items still return.
If you're using this from GitHub Actions, the SkillOx Action wraps this endpoint with PR annotations + summary comments — drop in two lines of YAML and you're done.