Authentication
Authenticate Scan API requests with X-API-KEY or a dashboard session.
Threatless has two auth modes. Do not mix them on the same request.
API keys (public Scan API)
Create a key in the dashboard, then send it on every /api/** request:
X-API-KEY: YOUR_API_KEY
X-API-KEY
string required
32-character hex key from API Key.
The filter runs before the controller:
| Situation | Status | Body |
|---|---|---|
| Header missing or blank | 401 | { "error": "X-API-KEY MISSING" } |
| Key does not match a user | 401 | { "error": "X-API-KEY INVALID" } |
CORS allows X-API-KEY and Authorization from configured web origins.
Minimal authenticated request
cURL
curl -i -X POST https://api.trythreatless.com/api/v1/scans \
-H "X-API-KEY: YOUR_API_KEY" \
-F "file=@./suspect.bin"
JavaScript
const body = new FormData()
body.append('file', fileBlob)
const response = await fetch('https://api.trythreatless.com/api/v1/scans', {
method: 'POST',
headers: {
'X-API-KEY': process.env.THREATLESS_API_KEY,
},
body,
})
A 401 body is always JSON with an error string. Do not retry auth failures without rotating the key.
401
{
"error": "X-API-KEY INVALID"
}
Session JWT (dashboard)
Browser routes under /web/** (except /web/auth/** and Stripe webhooks) expect a Bearer access token issued at login. The Nuxt app stores it in the token cookie and refreshes via POST /web/auth/refresh.
Authorization: Bearer <access-jwt>
Access tokens last 15 minutes. Refresh tokens last 15 days and are httpOnly cookies set by the API.
Auth endpoints
| Method | Path | Notes |
|---|---|---|
POST | /web/auth/register | Email sign-up |
POST | /web/auth/login | Email + password |
POST | /web/auth/refresh | Rotate access JWT |
POST | /web/auth/logout | Clear refresh cookie |
POST | /web/auth/email-confirmation | Confirm address |
POST | /web/auth/reset-password/request | Send reset email |
POST | /web/auth/reset-password | Set new password |
GET | /web/auth/google/start | Google OAuth |
GET | /web/auth/google/callback | Google OAuth return |
Never send
X-API-KEY to /web/** and never send a user JWT to /api/v1/scans. Each surface has one auth story.