Scan Endpoint
Submit source files for security analysis and receive findings in one synchronous response.
POST /v1/scan
Analyzes source files for AI agent logic flaws and security risks. File contents travel in the JSON body: the API never reads paths on your machine.
The legacy POST /api/v1/scan endpoint is a multipart contract used internally by the CLI (it needs a request metadata part with a contract version plus files parts). Do not integrate against it directly; use POST /v1/scan with JSON as documented here, or the CLI to scan a directory.
Request
Headers:
| Header | Required | Value |
|---|---|---|
Authorization | Yes | Bearer YOUR_API_KEY |
Content-Type | Yes | application/json |
Body:
| Field | Type | Required | Description |
|---|---|---|---|
files | array | Yes | One entry per file: { "path": "src/agent.py", "content": "<file text>" }. path is used for display and framework detection; keep the repository-relative path. |
policy | string | No | balanced (default), low-noise, comprehensive, governance, or eu-ai-act. See Security Policies. |
output | string | No | summary (default), detailed, or sarif. summary and detailed return the same body today; sarif adds a sarif object. |
Limits (server defaults):
| Limit | Value |
|---|---|
| Request body | 100 MB |
| Per-file content | 5 MB |
| Files per request | 10,000 |
Paths under tests/, examples/, docs/, and similar fixture directories are treated as test infrastructure and produce no findings. Send real source paths.
Supported file types: Python (.py), JavaScript (.js, .jsx), TypeScript (.ts, .tsx), Go (.go), YAML (.yaml, .yml), JSON (.json), and agent config formats (n8n, Flowise, Langflow, Dify exports).
Example Request
# Scan one file. The API never reads your disk: send file contents in the JSON body.
curl -X POST https://api.inkog.io/v1/scan \
-H "Authorization: Bearer $INKOG_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile code ./src/agent.py \
'{files: [{path: "src/agent.py", content: $code}], policy: "balanced", output: "detailed"}')"To scan a whole directory, use the CLI (npx -y @inkog-io/cli scan .), which walks the tree, redacts secrets locally, and applies .gitignore. Language examples that send several files at once are in Code Examples.
Response
Captured from a live scan (trimmed to one finding):
{
"success": true,
"scan_id": "0ed02144-783e-4edb-9303-a64e2823aa84",
"risk_score": 83,
"summary": {
"critical": 4,
"high": 11,
"low": 0,
"medium": 1,
"total": 16
},
"files_scanned": 8,
"findings": [
{
"id": "IR-70",
"pattern_id": "system_prompt_leak",
"pattern": "System Prompt Leakage (OWASP LLM07)",
"severity": "HIGH",
"confidence": 0.85,
"file": "src/assistants.py",
"line": 70,
"column": 15,
"message": "System prompt contains material that should not leak (credentials, PII, internal instructions reflected from user input) or is being logged \u2026",
"cwe": "CWE-209, CWE-532",
"cvss": 5.5,
"owasp_category": "LLM07",
"category": "data_exposure",
"risk_tier": "risk_pattern",
"finding_type": "vulnerability",
"remediation_hint": "No credentials in system prompts. No user-input echo. No plaintext logging of the prompt.",
"compliance_mapping": {
"eu_ai_act_articles": [
"Article 15.4"
],
"nist_categories": [
"GOVERN 4.2",
"MEASURE 2.7"
],
"iso_42001_clauses": [
"9.2"
],
"owasp_items": [
"LLM07"
],
"cwe_ids": [
"CWE-209",
"CWE-532"
]
}
}
],
"quick_wins": [
{
"priority": 85,
"action": "Remove dangerous eval/exec - use safe parsing",
"pattern_id": "exec_eval",
"severity": "CRITICAL",
"file": "src/dev_agent.py",
"line": 69
}
]
}Response Fields
| Field | Type | Description |
|---|---|---|
success | boolean | Whether the scan completed |
scan_id | string | Scan identifier; use with GET /v1/scans/{id}/diff |
risk_score | integer | Overall risk score (0-100) |
summary | object | total, critical, high, medium, low finding counts |
files_scanned | integer | Files analyzed |
files_skipped | integer | Present when files were skipped (unsupported type or fixture path) |
findings | array | Finding objects (below) |
quick_wins | array | Up to five prioritized findings with a suggested action (present when there are findings) |
partial_results | boolean | Present and true when the scan hit its time budget; warning explains what to do |
sarif | object | Present when output is sarif |
Finding Object
| Field | Type | Description |
|---|---|---|
id | string | Finding identifier within the scan |
pattern_id | string | Rule id, for example sql_injection |
pattern | string | Rule title |
severity | string | CRITICAL, HIGH, MEDIUM, or LOW |
confidence | number | 0.0 to 1.0 |
file, line, column | string, integer, integer | Location |
message | string | What was found and why it matters |
code_snippet | string | Source excerpt around the location |
cwe | string | CWE identifiers, for example CWE-89, CWE-564 |
owasp_category | string | OWASP web Top 10 category when applicable (for example A03:2021) |
category | string | injection, governance, resource_exhaustion, and so on |
risk_tier | string | vulnerability, risk_pattern, or hardening |
remediation_hint, remediation_steps | string, array | How to fix it |
compliance_mapping | object | owasp_items (OWASP LLM Top 10 ids), eu_ai_act_articles, nist_categories, iso_42001_clauses, cwe_ids; keys are omitted when the rule has no mapping |
Severity Levels
| Severity | Description |
|---|---|
CRITICAL | Immediate risk, fix before deployment |
HIGH | Significant risk, fix before release |
MEDIUM | Moderate risk, fix in normal cycle |
LOW | Minor risk, address when convenient |
Errors
Errors are JSON with code and error:
{ "code": "auth_required", "error": "Authorization header required" }| HTTP status | code | Cause |
|---|---|---|
| 400 | invalid_request | Malformed JSON, empty files, unknown policy or output, or a file over 5 MB |
| 401 | auth_required | Missing or invalid API key |
| 413 | payload_too_large | Body over 100 MB |
| 429 | rate limited | Retry after the Retry-After header |
| 503 | worker_unavailable | Analysis worker restarting; retry |
GET /v1/scans/{id}/diff
Compare a scan with the previous scan of the same agent to detect new and fixed findings. Useful for CI/CD regression tracking.
Request
Headers:
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer YOUR_API_KEY |
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
base | string | No | UUID of specific base scan to compare against (defaults to previous scan) |
Response
{
"success": true,
"diff": {
"base_scan_id": "550e8400-e29b-41d4-a716-446655440000",
"base_scan_time": "2024-01-15T10:30:00Z",
"head_scan_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"head_scan_time": "2024-01-16T14:22:00Z",
"summary": {
"total_new": 2,
"total_fixed": 1,
"total_unchanged": 15,
"new_by_severity": { "CRITICAL": 1, "HIGH": 1 },
"fixed_by_severity": { "HIGH": 1 },
"base_risk_score": 450,
"head_risk_score": 480,
"risk_delta": 30
},
"new_findings": [],
"fixed_findings": [],
"unchanged_findings": []
}
}Summary Fields
| Field | Type | Description |
|---|---|---|
total_new | integer | Number of new findings in this scan |
total_fixed | integer | Number of findings fixed since baseline |
total_unchanged | integer | Findings present in both scans |
new_by_severity | object | Breakdown of new findings by severity |
fixed_by_severity | object | Breakdown of fixed findings by severity |
base_risk_score | integer | Risk score of the baseline scan |
head_risk_score | integer | Risk score of the current scan |
risk_delta | integer | Change in risk score (positive = worse) |
First Scan Behavior
If this is the first scan for an agent (no previous scan exists), the response will contain:
base_scan_id: nullsummary.total_new: All findings in the current scansummary.total_unchanged: 0
Example Request
# Compare with previous scan (auto-detected)
curl https://api.inkog.io/v1/scans/6ba7b810-9dad-11d1-80b4-00c04fd430c8/diff \
-H "Authorization: Bearer YOUR_API_KEY"
# Compare with specific baseline scan
curl "https://api.inkog.io/v1/scans/6ba7b810-9dad-11d1-80b4-00c04fd430c8/diff?base=550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer YOUR_API_KEY"Regression Detection
The diff endpoint includes helper fields for regression detection:
| Status | Condition | Description |
|---|---|---|
REGRESSION | new_by_severity.CRITICAL > 0 OR new_by_severity.HIGH > 0 | New critical/high severity findings |
IMPROVED | No regression AND (fixed_by_severity.CRITICAL > 0 OR fixed_by_severity.HIGH > 0) | Fixed critical/high findings without new ones |
CHANGED | New findings but only MEDIUM/LOW severity | Changes but not critical |
UNCHANGED | total_new == 0 AND total_fixed == 0 | No changes |