DocsREST API
Reference

REST API Reference

Trigger scans, poll for status, fetch reports, and query findings programmatically. Base URL: https://nixer.polsia.app/api

Authentication

All API requests require an API key passed as a Bearer token in the Authorization header:

Authorization header
Authorization: Bearer YOUR_NIXER_API_KEY

Get your API key from the Nixer dashboard. The free tier allows 3 scans/hour per IP. Authenticated requests have higher limits depending on your plan.

Endpoints

POST /api/scanner/scan

Trigger a new scan. Returns immediately with a scan ID — poll GET /api/scanner/scan/:id for status.

Request body

FieldTypeRequiredDescription
input_typestringYes"github_repo" or "agent_config"
input_valuestringYesGitHub repo URL or agent config JSON string
severity_thresholdstringNo"low", "medium", "high", "critical". Default: "high"
detectorsarrayNoList of detector names to enable. Defaults to all.

Response

Response 202 Accepted
{
  "id": "scan_a1b2c3d4e5",
  "status": "pending",
  "created_at": "2026-06-05T17:00:00Z"
}
GET /api/scanner/scan/:id

Poll scan status. Returns "pending", "running", or "complete". When complete, includes the full findings payload.

Response 200 OK (complete)
{
  "id": "scan_a1b2c3d4e5",
  "status": "complete",
  "score": 62,
  "grade": "C",
  "report_url": "https://nixer.polsia.app/reports/scan_a1b2c3d4e5",
  "findings": [
    {
      "id": "f-1",
      "rule_id": "nixer/cicd/unpinned-action",
      "severity": "high",
      "title": "Unpinned Third-Party GitHub Action",
      "file_path": ".github/workflows/ci.yml",
      "line_number": 18,
      "description": "Action tj-actions/changed-files@v35 is pinned to a mutable tag.",
      "remediation": "Pin to a full commit SHA: tj-actions/changed-files@abc123..."
    }
  ]
}
GET /api/scanner/scan/:id/sarif

Returns the SARIF 2.1.0 document for a completed scan. Content-Type: application/json.

Code examples

curl — trigger and poll

shell
# Trigger scan
SCAN_ID=$(curl -s -X POST https://nixer.polsia.app/api/scanner/scan \
  -H "Authorization: Bearer $NIXER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input_type":"github_repo","input_value":"https://github.com/your-org/your-repo"}' \
  | jq -r .id)

echo "Scan ID: $SCAN_ID"

# Poll until complete
while true; do
  STATUS=$(curl -s "https://nixer.polsia.app/api/scanner/scan/$SCAN_ID" \
    -H "Authorization: Bearer $NIXER_API_KEY" | jq -r .status)
  echo "Status: $STATUS"
  [ "$STATUS" = "complete" ] && break
  sleep 5
done

# Fetch SARIF
curl -s "https://nixer.polsia.app/api/scanner/scan/$SCAN_ID/sarif" \
  -H "Authorization: Bearer $NIXER_API_KEY" > nixer.sarif

Node.js

scan.js
const BASE = 'https://nixer.polsia.app/api';
const KEY  = process.env.NIXER_API_KEY;

async function scan(repoUrl) {
  // Trigger
  const res = await fetch(`${BASE}/scanner/scan`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ input_type: 'github_repo', input_value: repoUrl }),
  });
  const { id } = await res.json();

  // Poll
  while (true) {
    await new Promise(r => setTimeout(r, 3000));
    const poll = await fetch(`${BASE}/scanner/scan/${id}`, {
      headers: { 'Authorization': `Bearer ${KEY}` },
    });
    const data = await poll.json();
    if (data.status === 'complete') {
      console.log(`Score: ${data.score} (${data.grade})`);
      console.log(`Findings: ${data.findings.length}`);
      return data;
    }
  }
}

scan('https://github.com/your-org/your-repo').catch(console.error);

Python

scan.py
import os, time, requests

BASE = "https://nixer.polsia.app/api"
KEY  = os.environ["NIXER_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}"}

def scan(repo_url):
    # Trigger
    r = requests.post(f"{BASE}/scanner/scan", headers=HEADERS, json={
        "input_type": "github_repo",
        "input_value": repo_url,
    })
    scan_id = r.json()["id"]
    print(f"Scan ID: {scan_id}")

    # Poll
    while True:
        time.sleep(3)
        data = requests.get(f"{BASE}/scanner/scan/{scan_id}", headers=HEADERS).json()
        if data["status"] == "complete":
            print(f"Score: {data['score']} ({data['grade']})")
            print(f"Findings: {len(data['findings'])}")
            return data

scan("https://github.com/your-org/your-repo")

Rate limits

PlanScans per hourConcurrent scans
Free (unauthenticated)3 / IP1
Free (API key)102
Pro ($49/mo)1005
EnterpriseUnlimitedUnlimited

Rate limit headers are returned on every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. On limit exceeded: HTTP 429 with Retry-After.

Edit this page on GitHub