Scan API
The Scan API lets you start security and functional test scans, retrieve detailed results, list historical scans, and export reports in multiple formats.
Authentication
All scan endpoints require your API key. The tenant is resolved automatically from the key — no separate tenant ID header is needed.
X-API-Key: nat_pk_your_api_key_hereBase URL
https://api.nat-testing.io/api/v1Endpoints
Start a scan
Starts a new scan against the specified target. Returns immediately with a scan_id — poll the results endpoint to check status.
POST /api/v1/scanRequest body:
| Field | Type | Required | Description |
|---|---|---|---|
url | string | ✅ | Target API base URL |
spec_url | string | OpenAPI/Swagger spec URL (recommended) | |
spec_content | string | Base64-encoded spec content (alternative to spec_url) | |
auth.type | string | bearer, header, basic, oauth2, none | |
auth.token | string | Bearer token value | |
auth.header_name | string | Header name for header auth type | |
auth.header_value | string | Header value for header auth type | |
auth.username | string | Username for basic auth | |
auth.password | string | Password for basic auth | |
auth.oauth2_token_url | string | OAuth2 token endpoint | |
auth.oauth2_client_id | string | OAuth2 client ID | |
auth.oauth2_client_secret | string | OAuth2 client secret | |
options.concurrency | integer | Parallel request count (default: 5) | |
options.timeout | integer | Per-request timeout in seconds (default: 30) | |
options.fail_on | string | Severity threshold for failure: critical, high, medium, low | |
options.exclude | string[] | Path patterns to exclude |
curl -X POST https://api.nat-testing.io/api/v1/scan \
-H "X-API-Key: $NAT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.example.com",
"spec_url": "https://api.example.com/openapi.json",
"auth": {
"type": "bearer",
"token": "eyJhbGciOiJSUzI1NiJ9..."
},
"options": {
"concurrency": 5,
"timeout": 30,
"fail_on": "high"
}
}'Response 202 Accepted:
{
"scan_id": "scan_abc123xyz",
"status": "running",
"created_at": "2025-01-15T10:30:00Z"
}Error responses:
| Status | Code | Description |
|---|---|---|
402 | QUOTA_EXCEEDED | Monthly scan quota exhausted |
429 | CONCURRENT_LIMIT | Too many scans running simultaneously |
401 | UNAUTHORIZED | Missing or invalid API key |
400 | INVALID_REQUEST | Malformed request body |
Get scan results
Returns the full results for a completed scan. Poll this endpoint until status is completed.
GET /api/v1/scan/{id}/resultsPath parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Scan ID returned by POST /api/v1/scan |
curl https://api.nat-testing.io/api/v1/scan/scan_abc123xyz/results \
-H "X-API-Key: $NAT_API_KEY"Response 200 OK (in-progress):
{
"scan_id": "scan_abc123xyz",
"status": "running",
"progress_percent": 45
}Response 200 OK (completed):
{
"scan_id": "scan_abc123xyz",
"status": "completed",
"passed": 10,
"failed": 2,
"total": 12,
"elapsed_seconds": 45.32,
"agent_count": 3,
"tasks": [
{
"task_id": "task_001",
"name": "BOLA check on /api/users/{id}",
"status": "failed",
"severity": "high",
"evidence": "User A could access User B's record via /api/users/42"
}
],
"dom_snapshots": [...],
"screenshots": [...],
"visual_regression": {
"comparisons": [...],
"diffs_detected": 1
},
"accessibility": {
"pages_scanned": 12,
"violations": [
{
"rule": "color-contrast",
"impact": "serious",
"elements": 3,
"description": "Insufficient color contrast ratio"
}
],
"compliance_score": 87.5
},
"performance": {
"pages_tested": 12,
"metrics": [
{
"page": "/dashboard",
"lcp_ms": 1850,
"fid_ms": 45,
"cls": 0.08,
"ttfb_ms": 220
}
],
"performance_score": 72.1
}
}Result fields:
| Field | Type | Description |
|---|---|---|
passed | integer | Number of test tasks that passed |
failed | integer | Number of test tasks that failed |
total | integer | Total number of test tasks |
elapsed_seconds | float | Total scan duration |
agent_count | integer | Number of AI agents used |
tasks | array | Individual test task results |
visual_regression | object | Visual diff comparisons (Pro/Team only) |
accessibility | object | Accessibility audit results (Pro/Team only) |
performance | object | Core Web Vitals and performance scores (Pro/Team only) |
The visual_regression, accessibility, and performance sections are only populated on Pro and Team plans.
List scans
Returns a paginated list of all scans for your tenant, ordered by creation date (most recent first).
GET /api/v1/scansQuery parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
per_page | integer | 20 | Results per page (max 100) |
status | string | Filter by status: running, completed, failed |
curl "https://api.nat-testing.io/api/v1/scans?page=1&per_page=20" \
-H "X-API-Key: $NAT_API_KEY"Response 200 OK:
{
"scans": [
{
"scan_id": "scan_abc123xyz",
"status": "completed",
"url": "https://api.example.com",
"passed": 10,
"failed": 2,
"total": 12,
"created_at": "2025-01-15T10:30:00Z",
"completed_at": "2025-01-15T10:31:15Z"
}
],
"total": 47,
"page": 1,
"per_page": 20,
"total_pages": 3
}Export scan report
Downloads a scan report in the specified format.
GET /api/v1/scan/{id}/exportPath parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Scan ID |
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | ✅ | Export format: html, json, csv |
# Export as HTML
curl "https://api.nat-testing.io/api/v1/scan/scan_abc123xyz/export?format=html" \
-H "X-API-Key: $NAT_API_KEY" \
-o report.html
# Export as JSON
curl "https://api.nat-testing.io/api/v1/scan/scan_abc123xyz/export?format=json" \
-H "X-API-Key: $NAT_API_KEY" \
-o report.json
# Export as CSV
curl "https://api.nat-testing.io/api/v1/scan/scan_abc123xyz/export?format=csv" \
-H "X-API-Key: $NAT_API_KEY" \
-o report.csvResponse: Binary file content with the appropriate Content-Type header:
| Format | Content-Type |
|---|---|
html | text/html |
json | application/json |
csv | text/csv |
Rate limiting
All scan endpoints are rate-limited per API key. When you exceed your plan's request rate, the API returns 429 Too Many Requests.
Rate limit headers returned on every response:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per minute for your plan |
X-RateLimit-Remaining | Requests remaining in the current window |
Retry-After | Seconds to wait before retrying (only on 429 responses) |
Rate limits by plan:
| Plan | Requests/minute |
|---|---|
| Free | 10 |
| Pro | 60 |
| Team | 200 |
| Enterprise | 1,000 |
Example 429 response:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 12
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
{
"error": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 12 seconds.",
"retry_after": 12
}See Pricing & Plans for a full breakdown of rate limits and quotas by plan.
See also
- API Quickstart — step-by-step guide to running your first scan
- Usage & Quotas API — monitor scan usage and quota limits
- CI/CD Integration guide — automate scans in your pipeline
- Common Errors — fix 401, 402, 429, and 503 errors