# TuneVault REST API

Read-only access to your Oracle health data, TuneOps tickets, activity logs, and team information. Built for ServiceNow, Grafana, Splunk, and custom dashboards.

Base URL
https://tunevault.app/api/v1
Auth
Bearer token
Format
JSON, ISO 8601 dates

## Authentication

All API requests require an API key generated from Settings → API Keys. Include it in the `Authorization` header:

    Authorization: Bearer tv_api_your_key_here

API keys are company-scoped — they can only access data belonging to the account that generated them. All requests are logged in your activity log.

### Key Security

Treat API keys like passwords. Don't commit them to source control. Use environment variables. Rotate keys immediately if compromised — use Settings → API Keys to revoke and regenerate.

#### Try It — Test Your Key

## Rate Limits

Rate limits apply per API key, per minute. When exceeded, the API returns `429 Too Many Requests` with a `Retry-After` header.

Plan | Requests / Minute | API Access
--- | --- | ---
Enterprise | 100 (configurable) | Full — all endpoints
Business | 60 | Health + TuneOps
Team | 30 | Health data only
Individual | — | No API access

Response headers on every API call: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`

## Error Format

All errors use a consistent envelope:

    {
      "error": {
        "code": "rate_limited",       // machine-readable
        "message": "Rate limit exceeded...",
        "retry_after": 60          // seconds (rate_limited only)
      }
    }

HTTP Status | Code | Meaning
--- | --- | ---
401 | unauthorized | Missing or invalid Authorization header
401 | invalid_api_key | Key not found or revoked
403 | tier_insufficient | Your plan doesn't include this endpoint
404 | not_found | Resource doesn't exist or not accessible
429 | rate_limited | Too many requests — check Retry-After
500 | internal_error | Server error — retry with backoff

## Pagination

Paginated endpoints use cursor-free offset pagination:

    # Request
    GET /api/v1/health/connections?page=2&per_page=25

    # Response envelope
    {
      "data": [...],
      "meta": {
        "total": 142,
        "page": 2,
        "per_page": 25,
        "has_more": true
      }
    }

Max `per_page`: 100. Default: 50.

## Access Tiers

Endpoint Group | Team | Business | Enterprise
--- | --- | --- | ---
Health Data (`/api/v1/health/*`) | ✓ | ✓ | ✓
TuneOps Tickets (`/api/v1/tuneops/*`) | ✗ | ✓ | ✓
Activity Log (`/api/v1/activity/*`) | ✗ | ✗ | ✓
Team (`/api/v1/team/*`) | ✗ | ✗ | ✓

View plans →

## Health Data — Team, Business, Enterprise

GET /api/v1/health/connections

List all Oracle connections with their latest health score. Paginated.

    GET /api/v1/health/connections?page=1&per_page=20
    Authorization: Bearer tv_api_...

    {
      "data": [
        {
          "id": 42,
          "name": "PROD-DB01",
          "host": "10.0.1.10",
          "port": 1521,
          "connection_type": "direct",
          "latest_check_id": 1873,
          "score": 82,
          "last_checked_at": "2026-05-14T03:00:00Z",
          "summary_text": "2 critical findings..."
        }
      ],
      "meta": { "total": 5, "page": 1, "per_page": 20, "has_more": false }
    }

    import requests

    headers = {"Authorization": "Bearer tv_api_YOUR_KEY"}
    r = requests.get("https://tunevault.app/api/v1/health/connections", headers=headers)
    connections = r.json()["data"]

    curl -H "Authorization: Bearer tv_api_YOUR_KEY" \
      "https://tunevault.app/api/v1/health/connections"

GET /api/v1/health/connections/:id/checks/latest

Most recent health check results including all individual check findings.

    curl -H "Authorization: Bearer tv_api_YOUR_KEY" \
      "https://tunevault.app/api/v1/health/connections/42/checks/latest"

    {
      "data": {
        "id": 1873, "score": 82,
        "summary_text": "2 critical findings require attention",
        "created_at": "2026-05-14T03:00:00Z",
        "checks": [
          {
            "check_id": "REDOLOG_SIZE",
            "category": "redo_logs",
            "status": "critical",
            "title": "Redo log size too small",
            "metric_line": "50MB (recommended: ≥ 500MB)",
            "remediation": "ALTER DATABASE ADD LOGFILE...",
            "severity": "critical"
          }
        ]
      }
    }

GET /api/v1/health/fleet

Aggregated fleet status across all connections — ideal for Grafana dashboards and alerting.

    {
      "data": [
        { "id": 42, "name": "PROD", "score": 82, "status": "healthy" },
        { "id": 43, "name": "DR",   "score": 55, "status": "critical" }
      ],
      "summary": {
        "total": 2, "healthy": 1,
        "warning": 0, "critical": 1, "never_run": 0
      }
    }

    # Export fleet status to CSV for Splunk
    import requests, csv, io

    r = requests.get(
      "https://tunevault.app/api/v1/health/fleet",
      headers={"Authorization": "Bearer tv_api_YOUR_KEY"}
    )
    fleet = r.json()

    output = io.StringIO()
    writer = csv.DictWriter(output, fieldnames=["name", "score", "status"])
    writer.writeheader()
    for conn in fleet["data"]:
        writer.writerow({"name": conn["name"], "score": conn["score"], "status": conn["status"]})

## TuneOps Tickets — Business, Enterprise

GET /api/v1/tuneops/tickets

List finding history records (TuneOps tickets). Filterable by status, severity, connection, and date range.

Parameter | Type | Description
--- | --- | ---
status | string | Filter: `open` or `resolved`
severity | string | `critical`, `warning`, `info`
connection_id | integer | Filter to a specific connection
from | ISO 8601 | Start of date range (first_seen_at)
to | ISO 8601 | End of date range
page, per_page | integer | Pagination (max 100 per page)

    curl -H "Authorization: Bearer tv_api_YOUR_KEY" \
      "https://tunevault.app/api/v1/tuneops/tickets?status=open&severity=critical"

    const resp = await fetch(
      '/api/v1/tuneops/tickets?status=open',
      { headers: { Authorization: `Bearer ${apiKey}` } }
    );
    const { data, meta } = await resp.json();

GET /api/v1/tuneops/stats

Aggregate ticket statistics — open counts, resolved this week, avg resolution time.

    {
      "data": {
        "open_count": 7,
        "resolved_this_week": 3,
        "critical_open": 2,
        "warning_open": 5,
        "avg_resolution_hours": 18.4
      }
    }

## Activity Log — Enterprise only

GET /api/v1/activity

Full activity log for the account. Filterable by action type and date range. Useful for SIEM integrations.

    curl -H "Authorization: Bearer tv_api_YOUR_KEY" \
      "https://tunevault.app/api/v1/activity?from=2026-05-01&to=2026-05-14"

    {
      "data": [
        {
          "id": 9001,
          "action_type": "health_check_completed",
          "page_path": "/dashboard",
          "properties": { "connection_id": 42, "score": 82 },
          "occurred_at": "2026-05-14T03:01:22Z"
        }
      ],
      "meta": { "total": 1482, "page": 1, "per_page": 50, "has_more": true }
    }

## Team — Enterprise only

GET /api/v1/team/members

List team members with their roles. Returns empty array for individual accounts.

    curl -H "Authorization: Bearer tv_api_YOUR_KEY" \
      "https://tunevault.app/api/v1/team/members"

GET /api/v1/team/roles

Returns all available roles and their permission sets. Static — no authentication change needed to read.

## Webhooks Coming Soon

### Webhooks are in design

Webhooks aren't implemented yet, but the payload format is documented below so you can design your receiver now. When webhooks ship, no payload changes will be required.

Planned payload format for all webhook events:

    {
      "event": "health_check.completed",
      "timestamp": "2026-05-14T03:00:00Z",
      "api_version": "v1",
      "data": {
        // event-specific payload (see below)
      }
    }

Event types (planned):

Event | Trigger
--- | ---
health_check.completed | A health check run finishes — includes score, top finding, connection ID
tuneops.ticket_created | A new finding appears in TuneOps — includes title, severity, remediation
tuneops.ticket_resolved | A finding is marked resolved — includes resolution time
alert.triggered | Autonomous monitoring alert fires — includes threshold exceeded, connection details
alert.resolved | A previously triggered alert clears