REST API Reference
Base URL: http://localhost:8080/api/v1
All protected endpoints require Authorization: Bearer <token>.
JWT access tokens and API keys use the same header format:
Authorization: Bearer <access_token_or_easm_api_key>
Auth
| Method | Path | Description |
|---|---|---|
| POST | /auth/register |
Create account |
| POST | /auth/login |
Login, returns token pair |
| POST | /auth/refresh |
Refresh access token |
| GET | /me/me |
Current user |
| GET | /me/users |
List users (admin) |
| GET | /me/api-keys |
List own API keys |
| POST | /me/api-keys |
Create API key |
| DELETE | /me/api-keys/{id} |
Revoke API key |
POST /auth/login
{ "email": "user@example.com", "password": "secret" }
→ { "tokens": { "access_token": "…", "refresh_token": "…" }, "user": { … } }
POST /me/api-keys
Create an API key:
{
"name": "Assets read key",
"permissions": ["read:assets"]
}
Response:
{
"key": "easm_...",
"api_key": {
"id": "...",
"name": "Assets read key",
"key_prefix": "easm_...",
"permissions": ["read:assets"]
}
}
The raw key is only returned once when created.
Organizations
| Method | Path | Description |
|---|---|---|
| GET | /organizations |
List orgs (admin: all; others: assigned) |
| POST | /organizations |
Create org |
| GET | /organizations/{id} |
Get org |
| PATCH | /organizations/{id} |
Update org |
| DELETE | /organizations/{id} |
Delete org |
| GET | /organizations/{id}/members |
List members |
| POST | /organizations/{id}/members |
Add member |
Scopes
| Method | Path | Description |
|---|---|---|
| GET | /organizations/{id}/scopes |
List scopes |
| POST | /organizations/{id}/scopes |
Create scope (→ pending) |
| PATCH | /scopes/{scope_id} |
Update scope |
| POST | /scopes/{scope_id}/approve |
Approve (admin) |
| POST | /scopes/{scope_id}/reject |
Reject (admin) |
POST /organizations/{id}/scopes
{ "type": "domain", "value": "example.com", "description": "Main domain" }
Scans
| Method | Path | Description |
|---|---|---|
| GET | /organizations/{id}/scans |
List scans |
| POST | /organizations/{id}/scans |
Create & queue scan |
| GET | /scans/{scan_id} |
Get scan |
| POST | /scans/{scan_id}/cancel |
Cancel scan |
| GET | /scans/{scan_id}/jobs |
List scan jobs |
POST /organizations/{id}/scans
{ "profile": "default", "scope_ids": ["<scope-uuid>"] }
Profiles are returned by GET /api/v1/scan-profiles and are defined in backend/configs/config.yaml.
Assets
| Method | Path | Description |
|---|---|---|
| GET | /organizations/{id}/assets |
List assets (paginated) |
| GET | /organizations/{id}/assets/graph |
Get asset graph |
| GET | /organizations/{id}/assets/stats |
Count by type |
| GET | /assets/{asset_id} |
Get single asset |
Query params for list: type, page, page_size
API key example: read:assets
Use an API key as a Bearer token. For example, with:
easm_b3d070d84cf7c121927d0bcbed23fcc287aaa29575f8d5dffe54919588458e0b
List assets for an organization:
curl "http://localhost:8080/api/v1/organizations/<organization_id>/assets?page=1&page_size=20" \
-H "Authorization: Bearer easm_b3d070d84cf7c121927d0bcbed23fcc287aaa29575f8d5dffe54919588458e0b"
Filter by asset type:
curl "http://localhost:8080/api/v1/organizations/<organization_id>/assets?type=domain&page=1&page_size=20" \
-H "Authorization: Bearer easm_b3d070d84cf7c121927d0bcbed23fcc287aaa29575f8d5dffe54919588458e0b"
Get the asset graph:
curl "http://localhost:8080/api/v1/organizations/<organization_id>/assets/graph" \
-H "Authorization: Bearer easm_b3d070d84cf7c121927d0bcbed23fcc287aaa29575f8d5dffe54919588458e0b"
Replace <organization_id> with the UUID of the organization to query.
Vulnerabilities
| Method | Path | Description |
|---|---|---|
| GET | /organizations/{id}/vulnerabilities |
List vulns (paginated) |
| GET | /organizations/{id}/vulnerabilities/stats |
Count by severity |
| GET | /vulnerabilities/{id} |
Get single vuln |
| PATCH | /vulnerabilities/{id}/status |
Update status |
| POST | /vulnerabilities/{id}/retest |
Request retest |
PATCH /vulnerabilities/{id}/status
{ "status": "confirmed", "note": "Verified manually" }
Valid status transitions:
- new → confirmed | false_positive
- confirmed → fixed | accepted_risk | retest_required
- fixed → reopened | retest_required
- false_positive / accepted_risk → reopened
Health check
GET /health
→ { "status": "ok", "time": "2026-04-29T…" }
Error format
All errors return JSON:
{ "error": "description" }
Exposure Changes
| Method | Path | Description |
|---|---|---|
| GET | /organizations/{id}/changes |
List exposure change timeline events |
| GET | /organizations/{id}/changes/summary |
Summary counters for recent changes |
| GET | /assets/{asset_id}/changes |
History for a single asset |
List query params: change_type, entity_type, severity, asset_id, vulnerability_id, scan_id, date_from, date_to, limit, offset.
Summary query params: days defaults to 7.
Example summary response:
{
"days": 7,
"total": 21,
"asset_discovered": 10,
"service_discovered": 4,
"url_discovered": 5,
"path_discovered": 0,
"certificate_discovered": 0,
"vulnerability_discovered": 2,
"file_collected": 0,
"critical": 1,
"high": 3
}
Exposure changes are read-only through the public API. They are generated internally when new assets, vulnerabilities, vulnerability status transitions, and file artifacts are persisted.