# Decision API

- Canonical URL: https://docs.fairvisor.com/docs/reference/api/
- Section: docs
- Last updated: n/a
> HTTP API reference for the Fairvisor Edge decision service.


Fairvisor Edge exposes a minimal HTTP API on port **8080**.

## Endpoints

| Endpoint | Method | Purpose |
|---|---|---|
| `/v1/decision` | `POST` | Evaluate a request against the active policy bundle |
| `/v1/debug/session` | `POST` | Start per-user debug session (when debug secret is configured) |
| `/v1/debug/logout` | `POST` | End per-user debug session |
| `/livez` | `GET` | Liveness probe — always returns `200 ok` |
| `/readyz` | `GET` | Readiness probe — `200 ok` if a bundle is loaded |
| `/metrics` | `GET` | Prometheus metrics |

---

## POST /v1/decision

The primary endpoint. Evaluates the incoming request headers against the active policy bundle and returns `200` (allow) or `429` (reject).

### Request

| Header | Required | Description |
|---|---|---|
| `X-Original-Method` | Yes | HTTP method of the original request (`GET`, `POST`, …) |
| `X-Original-URI` | Yes | Full URI of the original request, including query string |
| `X-Original-Host` | Conditionally | Original request host. Required when using selector `hosts`. |
| `Authorization` | No | Bearer JWT; used for `jwt:<claim>` descriptor keys |
| `X-Forwarded-For` | No | Client IP chain; used for `ip:address` and `ip:country` keys |
| `X-ASN-Type` | No | Optional fallback input for `ip:type` when set by gateway/proxy |
| `X-Tor-Exit` | No | Optional override for `ip:tor` (`1/0`, `true/false`, `yes/no`) |
| Any custom header | No | Used when a policy references `header:<name>` limit keys |

The request body is not consumed.

### Response: 200 Allow

```http
HTTP/1.1 200 OK
RateLimit-Limit: 1000
RateLimit-Remaining: 847
RateLimit-Reset: 1
RateLimit: "per-org-rps";r=847;t=1
```

Body is not part of the public contract (current implementation returns empty body on allow).

### Response: 429 Reject

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 15
X-Fairvisor-Reason: token_bucket_exceeded
RateLimit-Limit: 1000
RateLimit-Remaining: 0
RateLimit-Reset: 15
RateLimit: "api-limits";r=0;t=15
```

Body: implementation-dependent (do not rely on it).

### Debug session headers (optional)

When debug session is active, Fairvisor also returns debug headers such as:

- `X-Fairvisor-Debug-Policy`
- `X-Fairvisor-Debug-Rule`
- `X-Fairvisor-Debug-Reason`

See [Debug Session](/docs/reference/debug-session/).

### Response: 503 No bundle

If no policy bundle is loaded (e.g., immediately after startup before SaaS delivers config):

```http
HTTP/1.1 503 Service Unavailable
X-Fairvisor-Reason: no_bundle_loaded
```

---

## GET /livez

Always returns `200` while the nginx process is running. Use as a Kubernetes `livenessProbe`.

```http
HTTP/1.1 200 OK

ok
```

---

## GET /readyz

Returns `200` when a policy bundle is loaded and the engine is ready to process requests. Returns `503` during startup before the first bundle is applied.

```http
HTTP/1.1 200 OK

{"status":"ready","policy_version":"...","policy_hash":"...","last_config_update":1736940000}
```

```http
HTTP/1.1 503 Service Unavailable

{"status":"not_ready","reason":"no_policy_loaded"}
```

Use as a Kubernetes `readinessProbe` to hold traffic until the edge is ready.

---

## GET /metrics

Returns Prometheus-format metrics.

```http
HTTP/1.1 200 OK
Content-Type: text/plain; version=0.0.4

# HELP fairvisor_decisions_total Total decisions
# TYPE fairvisor_decisions_total counter
fairvisor_decisions_total{action="allow",reason="all_rules_passed",policy="policy-a",route="/api/v1"} 1542
fairvisor_decisions_total{action="reject",reason="token_bucket_exceeded",policy="policy-a",route="/api/v1"} 23
...
```

See [Metrics](/docs/reference/metrics/) for the full metric catalogue.

---

## Mode differences

| Aspect | `decision_service` | `reverse_proxy` |
|---|---|---|
| Host/method/URI source | `X-Original-Host`, `X-Original-Method`, `X-Original-URI` | nginx `$host`, `$request_method`, `$uri` |
| Response headers | Written directly to the decision response | Stored in `ngx.ctx`; written by `header_filter_by_lua_block` |
| Request body handling | Not consumed | Consumed/forwarded to backend |

In `reverse_proxy` mode, callers do not call `/v1/decision` directly — Fairvisor Edge acts as a transparent proxy that enforces policy on every proxied request.

---

## Authentication

The decision endpoint itself has no authentication. It is an internal service; expose it only to trusted callers (nginx `internal` location, Envoy cluster, VPC-internal network). Do not expose port 8080 to the public internet.

