ETC Collector provides a REST API for programmatic security auditing.
http://localhost:8443/api/v1
The API uses two layers of authentication:
- GUI Access Token — protects admin endpoints (config, token creation) and the web GUI
- JWT Token (RS256) — protects audit and data endpoints
The GUI access token is generated via etc-collector gui-token reset. Only a SHA-256 hash is stored on disk — the plaintext token is shown once and never saved.
Protected endpoints: POST /api/v1/auth/token, /api/v1/admin/*
How to pass the token:
# Via header (recommended)
curl -H "X-GUI-Token: etcsec_gt_..." http://localhost:8443/api/v1/admin/config
# Via query parameter
curl http://localhost:8443/api/v1/admin/config?gui_token=etcsec_gt_...Verify a GUI token:
Endpoint: POST /api/v1/auth/gui-token/verify
Request:
{
"token": "etcsec_gt_..."
}Response:
{
"valid": true
}If no GUI token is configured on the server, the response includes "required": false and all requests pass through.
The server requires an RSA key pair for signing and validating JWT tokens. Generate keys before starting:
mkdir -p keys
openssl genrsa -out keys/private.pem 2048
openssl rsa -in keys/private.pem -pubout -out keys/public.pemFor Docker, mount the keys directory as a volume: -v ./keys:/app/keys:ro
Endpoint: POST /api/v1/auth/token
Authentication: Requires GUI access token
Request:
{
"service": "my-integration",
"duration": "24h"
}Response:
{
"token": "eyJhbGciOiJSUzI1NiIs...",
"expiresAt": "2024-01-16T10:30:00Z"
}Example:
curl -X POST http://localhost:8443/api/v1/auth/token \
-H "Content-Type: application/json" \
-H "X-GUI-Token: etcsec_gt_..." \
-d '{"service":"my-app","duration":"24h"}'Include the token in the Authorization header:
curl -H "Authorization: Bearer YOUR_TOKEN" \
http://localhost:8443/api/v1/audit/adEndpoint: POST /api/v1/auth/token/validate
Request:
{
"token": "eyJhbGciOiJSUzI1NiIs..."
}Response:
{
"valid": true,
"subject": "system",
"service": "my-app",
"expiresAt": "2024-01-16T10:30:00Z"
}Endpoint: GET /api/v1/auth/token/info
Authentication: Required
Response:
{
"subject": "system",
"service": "my-app",
"issuedAt": "2024-01-15T10:30:00Z",
"expiresAt": "2024-01-16T10:30:00Z",
"jti": "550e8400-e29b-41d4-a716-446655440000"
}Endpoint: GET /health
Response:
{
"status": "ok",
"timestamp": "2026-04-05T10:00:00Z",
"version": "3.0.5",
"edition": "community"
}These endpoints require the GUI access token.
Endpoint: GET /api/v1/admin/config
Authentication: GUI token required
Response:
{
"server": { "host": "0.0.0.0", "port": 8443 },
"ldap": {
"configured": true,
"url": "ldaps://dc.example.com:636",
"bindDN": "CN=svc-audit,CN=Users,DC=example,DC=com",
"baseDN": "DC=example,DC=com",
"tlsVerify": true,
"connected": true
},
"azure": { "configured": false },
"features": { "networkProbes": false },
"auth": { "hasKeys": true, "tokenLifetime": "720h0m0s" }
}Secrets (bindPassword, clientSecret) are never returned.
Endpoint: PUT /api/v1/admin/config/ldap
Authentication: GUI token required
Request:
{
"url": "ldaps://dc.example.com:636",
"bindDN": "CN=svc-audit,CN=Users,DC=example,DC=com",
"bindPassword": "P@ssw0rd",
"baseDN": "DC=example,DC=com",
"tlsVerify": true
}Omit
bindPasswordto keep the existing password.
Response:
{ "success": true, "message": "LDAP configuration updated" }Example:
curl -X PUT http://localhost:8443/api/v1/admin/config/ldap \
-H "Content-Type: application/json" \
-H "X-GUI-Token: etcsec_gt_..." \
-d '{"url":"ldaps://dc.example.com:636","bindDN":"CN=svc-audit,...","baseDN":"DC=example,DC=com"}'Endpoint: POST /api/v1/admin/config/ldap/test
Authentication: GUI token required
Request:
{
"url": "ldaps://dc.example.com:636",
"bindDN": "CN=svc-audit,CN=Users,DC=example,DC=com",
"bindPassword": "P@ssw0rd",
"baseDN": "DC=example,DC=com",
"tlsVerify": true
}Response:
{ "success": true, "message": "Connection successful" }Does not save the configuration — use PUT /admin/config/ldap to persist.
Endpoint: DELETE /api/v1/admin/config/ldap
Authentication: GUI token required
Response:
{ "success": true, "message": "LDAP configuration removed" }Endpoint: GET /api/v1/info/providers
Authentication: Required
Response:
{
"providers": [
{
"type": "ldap",
"connected": true
}
]
}Endpoint: GET /api/v1/info/capabilities
Authentication: Required
Response:
{
"detectorCount": 277,
"features": {
"ldap": true,
"networkProbes": false,
"sysvol": true
},
"version": "3.0.9"
}Endpoint: GET /api/v1/audit/ad/status
Authentication: Required
Response:
{
"status": "ready",
"provider": "ldap"
}Endpoint: POST /api/v1/audit/ad
Authentication: Required
Request:
{
"includeDetails": false,
"async": false,
"networkProbes": false
}Response:
{
"success": true,
"provider": "ad",
"audit": {
"summary": {
"objects": {
"users": 1234,
"groups": 567,
"computers": 890
},
"risk": {
"score": 72.5,
"rating": "low",
"findings": {
"critical": 12,
"high": 25,
"medium": 38,
"low": 12,
"total": 87
}
}
},
"accounts": {
"status": { "findings": [...], "total": 5 },
"privileged": { "findings": [...], "total": 3 }
},
"computers": { "findings": [...], "total": 8 },
"security": {
"passwords": { "findings": [...], "total": 10 },
"kerberos": { "findings": [...], "total": 4 }
},
"permissions": { "findings": [...], "total": 15 },
"domainConfig": {
"domainInfo": { ... },
"passwordPolicy": { ... },
"kerberosPolicy": { ... }
},
"metadata": {
"provider": "ad",
"domain": {
"name": "contoso.local",
"baseDN": "DC=contoso,DC=local"
},
"execution": {
"timestamp": "2024-01-15T10:30:00Z",
"duration": "2m15s"
}
}
}
}Each finding in the category arrays has this structure:
{
"type": "PASSWD_NOTREQD",
"severity": "critical",
"category": "accounts",
"title": "User does not require password",
"description": "Users with PASSWD_NOTREQD flag set",
"count": 3,
"affectedEntities": [
{
"name": "Guest",
"type": "user",
"details": { ... }
}
]
}
affectedEntitiesis only included whenincludeDetails: truein the request.
Example:
TOKEN="your-token-here"
curl -X POST http://localhost:8443/api/v1/audit/ad \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"includeDetails": false,
"async": false
}'For long-running audits, use async mode:
Request:
{
"async": true
}Response:
{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"status": "running"
}Endpoint: GET /api/v1/audit/jobs
Authentication: Required
Response:
{
"jobs": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "ad",
"status": "completed",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:23Z"
}
]
}Endpoint: GET /api/v1/audit/jobs/:id
Authentication: Required
Example:
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8443/api/v1/audit/jobs/550e8400-e29b-41d4-a716-446655440000Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "ad",
"status": "completed",
"createdAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:35:23Z",
"result": { ... }
}Job statuses: pending, running, completed, failed.
Error Response Format:
{
"error": "error_code",
"message": "Human-readable error description"
}HTTP Status Codes:
200- Success201- Created202- Accepted (async job started)400- Bad Request401- Unauthorized404- Not Found500- Internal Server Error503- Service Unavailable
import requests
# Create token
response = requests.post(
"http://localhost:8443/api/v1/auth/token",
json={"service": "python-client", "duration": "24h"}
)
token = response.json()["token"]
# Run audit
headers = {"Authorization": f"Bearer {token}"}
response = requests.post(
"http://localhost:8443/api/v1/audit/ad",
headers=headers,
json={"async": False}
)
result = response.json()
risk = result["audit"]["summary"]["risk"]
print(f"Score: {risk['score']} ({risk['rating']})")
print(f"Findings: {risk['findings']['total']}")const axios = require('axios');
async function runAudit() {
// Create token
const tokenResponse = await axios.post(
'http://localhost:8443/api/v1/auth/token',
{ service: 'nodejs-client', duration: '24h' }
);
const token = tokenResponse.data.token;
// Run audit
const auditResponse = await axios.post(
'http://localhost:8443/api/v1/audit/ad',
{ async: false },
{ headers: { Authorization: `Bearer ${token}` } }
);
const risk = auditResponse.data.audit.summary.risk;
console.log(`Score: ${risk.score} (${risk.rating})`);
console.log(`Findings: ${risk.findings.total}`);
}
runAudit();# Create token
$tokenBody = @{
service = "powershell-client"
duration = "24h"
} | ConvertTo-Json
$tokenResponse = Invoke-RestMethod -Uri "http://localhost:8443/api/v1/auth/token" `
-Method Post -Body $tokenBody -ContentType "application/json"
$token = $tokenResponse.token
# Run audit
$headers = @{
"Authorization" = "Bearer $token"
"Content-Type" = "application/json"
}
$auditBody = @{ async = $false } | ConvertTo-Json
$result = Invoke-RestMethod -Uri "http://localhost:8443/api/v1/audit/ad" `
-Method Post -Headers $headers -Body $auditBody
$risk = $result.audit.summary.risk
Write-Host "Score: $($risk.score) ($($risk.rating))"
Write-Host "Findings: $($risk.findings.total)"| Severity | Score | Description |
|---|---|---|
| Critical | 9.0-10.0 | Immediate exploitation possible |
| High | 7.0-8.9 | Significant security weakness |
| Medium | 4.0-6.9 | Configuration issues |
| Low | 1.0-3.9 | Minor issues |
| Info | 0.0 | Informational only |
-
Token Security:
- Store tokens securely (environment variables, secret managers)
- Use short expiration times for automated scripts
- Rotate tokens regularly
-
Async Mode:
- Use async mode for large domains (>10,000 objects)
- Poll job status every 5-10 seconds
- Implement timeout (5-10 minutes)
-
Error Handling:
- Check HTTP status codes
- Implement retry logic with exponential backoff
- Log errors with sufficient context
-
Performance:
- Use
includeDetails: falseunless full data is needed - Run audits during off-peak hours
- Consider caching results
- Use
For full documentation including CLI reference, installation guides, and vulnerability catalog:
- COLLECTOR.md — Complete technical reference
- docs/vulnerabilities/ — Vulnerability catalogs
- GitHub: https://github.com/etcsec-com/etc-collector