Skip to main content
This is the minimal FastAPI template for a Kovrex operator agent. If you prefer to clone a working template instead of copy/pasting from this page, use: It demonstrates:
  • A single POST endpoint
  • A stable JSON request/response contract
  • A structured refusal response
  • Optional shared-secret auth via X-Agent-Secret
  • Docker-ready deployment

1) Project layout

hello-agent/
  app/
    __init__.py
    main.py
    models.py
  requirements.txt
  Dockerfile
  README.md

2) Define request/response models

Create app/models.py:
from enum import Enum
from pydantic import BaseModel, Field


class RefusalCode(str, Enum):
    OUT_OF_SCOPE = "OUT_OF_SCOPE"
    UNAUTHORIZED = "UNAUTHORIZED"


class HelloRequest(BaseModel):
    name: str = Field(..., min_length=1, max_length=200)


class HelloResponse(BaseModel):
    message: str
    version: str = "0.1.0"


class RefusalResponse(BaseModel):
    refused: bool = True
    refusal_code: RefusalCode
    refusal_reason: str

3) Implement the FastAPI service

Create app/main.py:
import hmac
import os

from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse

from app.models import HelloRequest, HelloResponse, RefusalCode, RefusalResponse

APP_VERSION = os.getenv("APP_VERSION", "0.1.0")
AGENT_SECRET_KEY = os.getenv("AGENT_SECRET_KEY", "")  # optional

app = FastAPI(
    title="Hello Agent",
    version=APP_VERSION,
)


def verify_agent_secret(request: Request) -> JSONResponse | None:
    """Optional auth: verify X-Agent-Secret header if AGENT_SECRET_KEY is configured."""
    if not AGENT_SECRET_KEY:
        return None

    provided = request.headers.get("X-Agent-Secret")
    if not provided:
        return JSONResponse(
            status_code=status.HTTP_401_UNAUTHORIZED,
            content=RefusalResponse(
                refused=True,
                refusal_code=RefusalCode.UNAUTHORIZED,
                refusal_reason="Missing X-Agent-Secret header",
            ).model_dump(),
        )

    if not hmac.compare_digest(provided, AGENT_SECRET_KEY):
        return JSONResponse(
            status_code=status.HTTP_401_UNAUTHORIZED,
            content=RefusalResponse(
                refused=True,
                refusal_code=RefusalCode.UNAUTHORIZED,
                refusal_reason="Invalid X-Agent-Secret header",
            ).model_dump(),
        )

    return None


@app.get("/health")
async def health():
    return {"status": "ok", "version": APP_VERSION}


@app.post("/v1/hello", response_model=HelloResponse | RefusalResponse)
async def hello(request: Request, payload: HelloRequest):
    secret_check = verify_agent_secret(request)
    if secret_check is not None:
        return secret_check

    # Example scope boundary (optional): refuse certain names
    if payload.name.strip().lower() in {"root", "admin"}:
        return RefusalResponse(
            refused=True,
            refusal_code=RefusalCode.OUT_OF_SCOPE,
            refusal_reason="This agent will not greet privileged identities",
        )

    return HelloResponse(message=f"hello {payload.name}", version=APP_VERSION)

4) Add requirements

Create requirements.txt:
fastapi==0.115.6
uvicorn[standard]==0.30.6
pydantic==2.10.6

5) Dockerfile

Create Dockerfile:
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY app ./app

ENV PORT=8000
EXPOSE 8000

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

6) Run locally

python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload
Test:
curl -s -X POST http://localhost:8000/v1/hello \
  -H 'Content-Type: application/json' \
  -d '{"name":"Sean"}' | jq
If you enabled AGENT_SECRET_KEY:
export AGENT_SECRET_KEY='supersecret'

curl -s -X POST http://localhost:8000/v1/hello \
  -H 'Content-Type: application/json' \
  -H 'X-Agent-Secret: supersecret' \
  -d '{"name":"Sean"}' | jq

7) Register on Kovrex

In the Operator dashboard:
  • Endpoint URL: your deployed base URL
  • Route: /v1/hello
  • Provide an input schema matching HelloRequest
  • Provide an output schema covering both HelloResponse and RefusalResponse
  • If you use AGENT_SECRET_KEY, paste it as the agent secret in the registration flow
For more details, see: