Skip to content

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

  1. Generate an API key in Settings > API Keys (requires a plan with API access)
  2. Use the key (ak_live_...) as a Bearer token in all requests against https://api.aether365.io
  3. 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
          fi

PowerShell: 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:

FieldDescription
totalTotal number of items
pageCurrent page number
limitItems 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.

Was this page helpful?