# Fermi Agent Quickstart

This page is a fully self-contained cold-start guide for AI agents. Follow it end-to-end and you will be making calibrated probability estimates in under 60 seconds.

> **Base URL note:** the curl and Python examples below use `$BASE_URL` as a placeholder. Set it to the host you fetched this file from — `https://fermi.krobar.ai` for production, `https://stag-fermi.krobar.ai` for staging. Don't mix environments: accounts created on staging are not valid on production.

## What Fermi is

Fermi is a paid REST + MCP API that takes a natural-language question and returns a calibrated probability distribution. It is a decision-support tool for agents that need quantitative reasoning under uncertainty.

**Use Fermi when you need:**
- A probability distribution (not just a point estimate) for an unknown quantity
- A calibrated confidence interval for a forecast (90% by default, configurable)
- Structured reasoning and assumptions alongside the estimate
- A repeatable, audit-trailed answer (each estimate has an ID and can be retrieved later)

**Do NOT use Fermi for:**
- Real-time data lookups (it is not a search engine)
- Deterministic calculations (use a calculator or code interpreter)
- Financial, medical, or legal advice (estimates are decision-support only)

## Authentication

Fermi supports three principal types:

1. **API key** (recommended for agents) — sign up via `POST /api/v1/accounts`, pass the returned key as the `X-API-Key: <key>` header on every request. **Do NOT** put API keys in `Authorization: Bearer` — that header is reserved for JWTs and will return 401 if you try to use it with a raw API key.
2. **JWT bearer** — for interactive users via `Authorization: Bearer <jwt>`. Only signed JWTs are valid here; raw API keys will be rejected. Agents generally don't need this path.
3. **Anonymous** — no headers required, limited to 1 tier-1 estimate per day per IP/user-agent fingerprint. Intended for one-off experimentation before signup.

### Cold-start signup

```
curl -X POST $BASE_URL/api/v1/accounts
```

Response:
```json
{
  "account_id": "acc_...",
  "api_key": "<raw-key-shown-only-once>",
  "created_at": "2026-04-11T..."
}
```

**Save the `api_key` value immediately** — it is only shown once (hashed in the database). Pass this value in the `X-API-Key` header on all subsequent requests. New accounts receive 3 starter credits, enough for 3 tier-1 estimates.

## Buying credits

Credits are $0.50 each. Buy any quantity. When your starter credits run out (402 response), purchase more via Stripe:

### Check pricing (no auth required)

```
curl $BASE_URL/api/v1/billing/pricing
```

Returns: `{ "unit_price_cents": 50, "currency": "usd" }`

### Browser checkout (humans)

```
curl -X POST $BASE_URL/api/v1/billing/checkout \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "credits": 10 }'
```

Returns `{ "checkout_url": "https://checkout.stripe.com/..." }`. Open the URL in a browser to complete payment. The card is saved for future agent auto-recharge.

### API-driven purchase (agents with a saved card)

After at least one browser checkout has saved a card, agents can purchase credits programmatically:

```
curl -X POST $BASE_URL/api/v1/billing/purchase-credits \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "credits": 10 }'
```

Returns `{ "credits_purchased": 10, "new_balance": 13, "payment_status": "succeeded" }`.

Also available as the MCP `buy_credits` tool (pass `api_key` and `credits` as tool arguments).

### Agent auto-recharge flow

1. Agent calls `POST /api/v1/estimates` and gets a 402 response
2. Agent calls `POST /api/v1/billing/purchase-credits` with `{"credits": 10}`
3. On success, agent retries the original estimate request

## Making an estimate

### Endpoint
`POST /api/v1/estimates`

### Required headers
- `X-API-Key: <key>` (or omit for anonymous tier)
- `Idempotency-Key: <uuid>` — **required**. A UUIDv4 you generate. Deduplicates retries.
- `Content-Type: application/json`

### Required body fields (EstimateRequest)

| Field | Type | Description |
|---|---|---|
| `question` | string | The natural-language question, non-empty |
| `requested_tier_id` | int (1\|2\|3) | Tier 1 = fast sync, Tier 2 = grounded sync, Tier 3 = deep research async |
| `unit` | string | The unit the answer should be in (e.g. `USD`, `people`, `meters`) |
| `target_horizon` | string | When the estimate applies (e.g. `6 months`, `2030`, `current`) |
| `disclaimer_acknowledged` | bool | **Must be `true`** — confirms you understand estimates are probabilistic decision-support only |

### Optional body fields

| Field | Type | Default | Description |
|---|---|---|---|
| `context` | string | null | Extra context to condition the estimate |
| `coverage_probability` | float | `0.9` | Confidence interval width (0, 1). `0.9` means 90% CI |
| `source_inputs` | list | `[]` | Structured inputs (see schema below) |
| `callback_url` | string | null | Webhook URL — the completed/failed result is POSTed here with an `X-Fermi-Signature` HMAC header, so you don't need to poll for async tiers |
| `provider` | string | null | LLM provider selector (`"openai"` or `"anthropic"`). Omit to use the server default. |
| `model` | string | null | Model ID for the selected provider. Omit to use the default. |

### `source_inputs` schema

Each item in `source_inputs` is a structured input object:

| Field | Type | Required | Description |
|---|---|---|---|
| `source_type` | string | **yes** | `"qualitative_text"`, `"spreadsheet"`, or `"structured_file"` |
| `content` | string | one of content/structured_data | Text content (also accepts alias `content_text`) |
| `structured_data` | object or list | one of content/structured_data | Dict or list of dicts |
| `title` | string | no | Label for the source |
| `file_name` | string | no | Original filename if from a file upload |
| `media_type` | string | no | MIME type |
| `provenance` | string | no | `"user"` (default), `"agent"`, or `"file_upload"` |

Example:
```json
"source_inputs": [
  {
    "source_type": "qualitative_text",
    "title": "Market context",
    "content": "The company reported Q3 revenue of $2.1B..."
  }
]
```

### Example: curl

```
API_KEY="<your key from signup>"
IDEM=$(python3 -c 'import uuid; print(uuid.uuid4())')

curl -X POST $BASE_URL/api/v1/estimates \
  -H "X-API-Key: $API_KEY" \
  -H "Idempotency-Key: $IDEM" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "What will the price of Bitcoin be in 6 months?",
    "requested_tier_id": 1,
    "unit": "USD",
    "target_horizon": "6 months",
    "disclaimer_acknowledged": true
  }'
```

### Example: Python (httpx)

```python
import os
import uuid
import httpx

BASE_URL = os.environ.get("FERMI_BASE_URL", "https://fermi.krobar.ai")

# 1. Sign up
signup = httpx.post(f"{BASE_URL}/api/v1/accounts").json()
api_key = signup["api_key"]

# 2. Make an estimate
resp = httpx.post(
    f"{BASE_URL}/api/v1/estimates",
    headers={
        "X-API-Key": api_key,
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "question": "What will the price of Bitcoin be in 6 months?",
        "requested_tier_id": 1,
        "unit": "USD",
        "target_horizon": "6 months",
        "disclaimer_acknowledged": True,
    },
    timeout=60.0,
)
resp.raise_for_status()
estimate = resp.json()

# 3. Interpret
print(f"Point estimate: ${estimate['point_estimate']:,.0f}")
print(f"90% CI: ${estimate['estimation_interval']['low']:,.0f} – "
      f"${estimate['estimation_interval']['high']:,.0f}")
print(f"Distribution: {estimate['distribution_family']} "
      f"{estimate['distribution_parameters']}")
print(f"Reasoning: {estimate['reasoning_summary']}")
```

## Response schema (EstimateSuccessResponse)

```json
{
  "estimate_id": "est_...",
  "request_id": "req_...",
  "tier_id": 1,
  "created_at": "2026-04-11T12:34:56Z",
  "estimation_interval": {"low": 35000.0, "high": 140000.0},
  "coverage_probability": 0.9,
  "distribution_family": "log_normal",
  "distribution_parameters": {"mu": 11.156, "sigma": 0.421},
  "point_estimate": 72000.0,
  "range_low": 35000.0,
  "range_high": 140000.0,
  "confidence_level": "medium",
  "reasoning_summary": "Given current price levels, volatility...",
  "assumptions": ["No major regulatory shocks", "..."],
  "credits_charged": 1,
  "disclaimer": "Informational only. Not financial advice."
}
```

### Key fields
- **`estimation_interval`**: `{low, high}` — the confidence interval at `coverage_probability`
- **`distribution_family`**: one of `normal`, `log_normal`, `uniform`, `constant`, `beta`, `triangle`, `gamma`, `exponential`, `poisson`
- **`distribution_parameters`**: family-specific (e.g. `{mean, std}` for normal, `{mu, sigma}` for log_normal)
- **`point_estimate`**: single best-guess value (typically the median or mean of the distribution)
- **`credits_charged`**: how many credits this call consumed

## Error handling

| Status | Meaning | What to do |
|---|---|---|
| `200` | Success (sync tier 1/2) | Parse response |
| `202` | Accepted (async tier 3) | Poll `GET /api/v1/estimates/{estimate_id}` until status is `completed` or `failed` |
| `402` | Payment Required — out of credits | Buy credits: `POST /api/v1/billing/purchase-credits` with `{"credits": N}` |
| `422` | Unprocessable Entity | Check required fields, especially `disclaimer_acknowledged: true` and `Idempotency-Key` header |
| `429` | Too Many Requests | Anonymous tier limited to 1/day — sign up for an account |
| `5xx` | Server error | Retry with same `Idempotency-Key` (it's safe — the request will be deduplicated) |

## MCP alternative (for agents that prefer MCP)

Fermi exposes two MCP surfaces:

1. **Real MCP SSE transport** at `$BASE_URL/mcp/sse/` — connect with the [Python `mcp` SDK](https://github.com/modelcontextprotocol/python-sdk) or any MCP client. The server registers a single `estimate` tool with the same arguments as the REST body. **Pass your API key as the `api_key` tool argument**, not as an HTTP header (SSE tool calls don't carry per-call HTTP headers).
2. **HTTP-mirror adapter** at `/mcp/tools` (list) and `/mcp/tools/estimate` (call) — same JSON contract over plain REST, for clients that can't speak MCP.

See `/.well-known/mcp.json` for the authoritative machine-readable descriptor.

## Webhook notifications (alternative to polling)

For tier 3 (async) estimates, instead of polling `GET /api/v1/estimates/{estimate_id}`, you can provide a `callback_url` in the request body. When the estimate completes or fails, Fermi POSTs the result to your URL with:

- `Content-Type: application/json`
- `X-Fermi-Signature: sha256=<hmac>` — HMAC-SHA256 of the body, signed with the webhook secret
- `X-Fermi-Event: estimate.completed` or `estimate.failed`

The body is the same JSON as the poll response (all estimate fields on success, `error_detail` on failure). Delivery is retried up to 3 times with exponential backoff.

## Other useful endpoints

- `GET /api/v1/estimates/tiers` — list tier pricing and availability
- `GET /api/v1/estimates/history` — your recent estimates (auth required)
- `GET /api/v1/estimates/{estimate_id}` — retrieve a previous estimate by ID
- `PUT /api/v1/estimates/{estimate_id}/actual` — record the actual outcome (helps calibration analysis)
- `POST /api/v1/accounts/{account_id}/api-keys` — generate additional API keys for your account
- `GET /api/v1/billing/pricing` — credit pricing (no auth)
- `POST /api/v1/billing/checkout` — create a Stripe Checkout session (auth required)
- `POST /api/v1/billing/purchase-credits` — buy credits with saved card (auth required)
