REST API Automation
Maintained by: Aether365 Team Audience: Developers and DevOps engineers Scope: Using the Aether365 API for automation and custom integrations
The Aether365 REST API lets you integrate scan results into your existing security tooling, automate reporting, and trigger scans programmatically.
Getting Started
- Generate an API key in Settings > API Keys (requires a plan with API access)
- Use the key (
ak_live_...) as aBearertoken in all requests againsthttps://api.aether365.io - See the API Reference for all available endpoints
Common Automation Patterns
Trigger a scan and wait for results
This pattern is useful in CI/CD pipelines where you want to block deployment if security posture drops below a threshold.
python
import requests
import time
API_KEY = "ak_live_your-api-key"
BASE = "https://api.aether365.io"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# Trigger a compliance scan
resp = requests.post(f"{BASE}/tenants/me/scans",
json={"scanType": "compliance"},
headers=HEADERS)
scan = resp.json()["data"]
scan_id = scan["id"]
print(f"Scan started: {scan_id}")
# Poll until the scan reaches a terminal state
while True:
resp = requests.get(f"{BASE}/scans/{scan_id}", headers=HEADERS)
scan = resp.json()["data"]
if scan["status"] in ("completed", "failed", "cancelled", "expired"):
break
print(f"Status: {scan['status']} - waiting...")
time.sleep(30)
if scan["status"] != "completed":
print(f"Scan did not complete: {scan['status']}")
exit(1)
# The API does not return a score; derive a pass rate from the counters
total = scan["passCount"] + scan["failCount"]
pass_rate = (scan["passCount"] / total * 100) if total else 0.0
print(f"Pass rate: {pass_rate:.1f}%")
if pass_rate < 80:
print(f"Pass rate {pass_rate:.1f}% is below threshold (80%). Failing pipeline.")
exit(1)
print("Security check passed")Retrieve high-impact failures from the latest scan
python
import requests
API_KEY = "ak_live_your-api-key"
BASE = "https://api.aether365.io"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# Get the most recent scan; the list endpoint has no status filter,
# so check the status client-side
scans = requests.get(f"{BASE}/tenants/me/scans?limit=1",
headers=HEADERS).json()["data"]
if not scans or scans[0]["status"] != "completed":
print("No completed scan found")
exit(0)
scan_id = scans[0]["id"]
# Fetch all results in one request (plain array, no pagination)
results = requests.get(f"{BASE}/scans/{scan_id}/results",
headers=HEADERS).json()["data"]
# High-impact failures (severity L2)
failures = [r for r in results
if r["result"] == "Failed" and r["severity"] == "L2"]
print(f"High-impact failures: {len(failures)}")
for r in failures:
print(f" [{r['testId']}] {r['title']}")GitHub Actions: scan on schedule
yaml
name: Aether365 Security Scan
on:
schedule:
- cron: '0 6 * * MON' # Every Monday at 06:00 UTC
workflow_dispatch:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- name: Trigger scan
id: trigger
run: |
RESPONSE=$(curl -s -X POST https://api.aether365.io/tenants/me/scans \
-H "Authorization: Bearer ${{ secrets.AETHER365_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"scanType": "compliance"}')
SCAN_ID=$(echo $RESPONSE | jq -r '.data.id')
echo "scan_id=$SCAN_ID" >> $GITHUB_OUTPUT
- name: Wait for completion
run: |
SCAN_ID=${{ steps.trigger.outputs.scan_id }}
for i in $(seq 1 30); do
STATUS=$(curl -s https://api.aether365.io/scans/$SCAN_ID \
-H "Authorization: Bearer ${{ secrets.AETHER365_API_KEY }}" \
| jq -r '.data.status')
echo "Status: $STATUS"
[ "$STATUS" = "completed" ] && break
case "$STATUS" in
failed|cancelled|expired) echo "Scan did not complete" && exit 1 ;;
esac
sleep 30
done
- name: Check pass rate
run: |
SCAN_ID=${{ steps.trigger.outputs.scan_id }}
RATE=$(curl -s https://api.aether365.io/scans/$SCAN_ID \
-H "Authorization: Bearer ${{ secrets.AETHER365_API_KEY }}" \
| jq '.data | if (.passCount + .failCount) > 0
then (.passCount * 100 / (.passCount + .failCount))
else 0 end')
echo "Pass rate: $RATE%"
if (( $(echo "$RATE < 75" | bc -l) )); then
echo "Pass rate below threshold"
exit 1
fiPowerShell: export results to CSV on a schedule
powershell
$ApiKey = $env:AETHER365_API_KEY
$Headers = @{ Authorization = "Bearer $ApiKey" }
# Get the latest completed scan (filter on status client-side)
$Scans = Invoke-RestMethod -Uri "https://api.aether365.io/tenants/me/scans?limit=10" -Headers $Headers
$Latest = $Scans.data | Where-Object { $_.status -eq "completed" } | Select-Object -First 1
$ScanId = $Latest.id
# Fetch all results in one request (this endpoint is not paginated)
$Resp = Invoke-RestMethod -Uri "https://api.aether365.io/scans/$ScanId/results" -Headers $Headers
$AllResults = $Resp.data
# Export to CSV
$AllResults | Export-Csv -Path "scan_$(Get-Date -Format 'yyyy-MM-dd').csv" -NoTypeInformation
Write-Host "Exported $($AllResults.Count) results"Pagination
Paginated list endpoints such as GET /tenants/me/scans accept page and limit query parameters:
bash
curl "https://api.aether365.io/tenants/me/scans?page=2&limit=50" \
-H "Authorization: Bearer ak_live_..."The meta object in paginated responses includes:
| Field | Description |
|---|---|
total | Total number of items |
page | Current page number |
limit | Items per page |
There is no totalPages field; compute it as ceil(total / limit).
GET /scans/{scanId}/results is not paginated - it returns the complete result array in one response with no meta object.
Error Handling
All API errors return a consistent structure:
json
{
"success": false,
"error": {
"code": "SNAKE_CASE_ERROR_CODE",
"message": "Human-readable description"
}
}Always check the success field before reading data. See Error Codes for all error codes.