SaaS Connection

When FAIRVISOR_SAAS_URL is set, the edge instance runs a persistent connection loop to Fairvisor SaaS. This page documents the full protocol.

Overview

Edge startup
  └─ Register  →  POST /api/v1/edge/register
  └─ Pull config  →  GET /api/v1/edge/config
  └─ ACK  →  POST /api/v1/edge/config/ack
  └─ [request traffic begins]
  └─ Heartbeat loop (every HEARTBEAT_INTERVAL seconds)
  └─ Config poll loop (every CONFIG_POLL_INTERVAL seconds)
  └─ Event flush loop (every EVENT_FLUSH_INTERVAL seconds)

All API calls use:

  • Authorization: Bearer <FAIRVISOR_EDGE_TOKEN>
  • Content-Type: application/json
  • Base URL: FAIRVISOR_SAAS_URL

Step 1 — Registration

On startup, the edge registers itself with SaaS:

POST /api/v1/edge/register
{
  "edge_id":  "edge-prod-us-east-1",
  "version":  "0.1.0",
  "timestamp": 1736940000
}

On success (2xx): heartbeat, config, and event timers are initialised. On failure: startup fails. The container exits non-zero.

Step 2 — Initial config pull

Immediately after registration, the edge pulls its policy bundle:

GET /api/v1/edge/config
Response Meaning
200 OK New bundle in response body; parse, compile, and apply
304 Not Modified Bundle unchanged (no-op)
401 / 403 Credential problem; stop retrying

The bundle JSON structure is the same as a local policy.json. After successful application, the edge sends an ACK.

Step 3 — Config ACK

POST /api/v1/edge/config/ack
{
  "edge_id":   "edge-prod-us-east-1",
  "version":   "0.1.0",
  "hash":      "sha256:abcdef…",
  "status":    "applied",   // or "rejected"
  "error":     null,        // rejection reason if status="rejected"
  "timestamp": 1736940001
}

The ACK communicates whether the edge successfully loaded the bundle or rejected it (e.g., validation failure, schema mismatch).

Heartbeat loop

Every FAIRVISOR_HEARTBEAT_INTERVAL seconds (default 5 s):

POST /api/v1/edge/heartbeat
{
  "edge_id":      "edge-prod-us-east-1",
  "version":      "0.1.0",
  "policy_hash":  "sha256:abcdef…",
  "uptime":       3612,
  "timestamp":    1736943612
}

Response may include:

Field Meaning
server_time SaaS wall clock (used for clock skew detection)
config_update_available: true Trigger an immediate config pull

If config_update_available is true, the edge pulls the config outside its normal poll interval.

Config poll loop

Every FAIRVISOR_CONFIG_POLL_INTERVAL seconds (default 30 s), the edge polls for config changes using a conditional request:

GET /api/v1/edge/config
If-None-Match: <current_bundle_hash>

This avoids re-parsing an unchanged bundle. When 304 is returned, no action is taken.

Event batching

Decision events (every allow/reject decision, with metadata) are buffered in memory and flushed every FAIRVISOR_EVENT_FLUSH_INTERVAL seconds (default 60 s):

POST /api/v1/edge/events
Idempotency-Key: batch_<edge_id>_<timestamp>_<seq>
{
  "edge_id":              "edge-prod-us-east-1",
  "events":               [ ... ],        // up to 100 per batch
  "clock_skew_suspected": false,
  "clock_skew_seconds":   0
}
  • Max buffer size: 1 000 events (older events are dropped when full)
  • Max batch size: 100 events per flush
  • Idempotency key prevents double-counting on retry

Retry and backoff

All SaaS API calls use exponential backoff:

delay = min(2^attempt, 60 seconds) + random_jitter

Non-retriable responses (401, 403, 404) clear the retry state immediately. Retriable responses (5xx, network errors) schedule a retry with backoff.

SaaS circuit breaker

The edge maintains a circuit breaker for SaaS connectivity:

State Trigger Behaviour
CLOSED Normal All SaaS calls proceed
OPEN 5 consecutive failures SaaS calls suppressed; edge continues with cached bundle
HALF_OPEN 30 s after opening One probe call allowed
CLOSED 2 successes in HALF_OPEN Returns to normal

When the circuit is open, the edge continues enforcing the last known bundle. The fairvisor_saas_reachable metric is set to 0.

Clock skew detection

If |local_time − server_time| > 10 seconds, the edge sets clock_skew_suspected: true in subsequent event flushes. This is informational — enforcement is not affected.

Security considerations

  • FAIRVISOR_EDGE_TOKEN is sent as a Bearer token on every SaaS request. Rotate it from the Fairvisor dashboard.
  • The config bundle can optionally be signed. If a signing key is configured, the edge rejects bundles that fail signature verification.
  • Bundle versioning is monotonically increasing — the edge rejects a bundle with a lower version than the current one.

Graceful shutdown behavior

On worker shutdown, the edge attempts to flush buffered SaaS events before exit.

  • Runtime sets worker_shutdown_timeout to 35s.
  • Shutdown handler calls SaaS flush_events() before final worker termination.
  • If the process is hard-killed before graceful timeout, in-memory buffered events may be lost.

Operational recommendation: use termination grace period of at least 35 seconds for SaaS-connected deployments.