Files
homelab-frontend/API.md
T
Admin Bot f154c5993a
CI / Vet, test, build (push) Canceled after 3m20s
CI / Build and push image (push) Canceled after 0s
docs: update API.md with service account onboarding guide
- Add roles-based auth for service accounts
- Document client_credentials flow
- Add onboarding steps for new services
- Clarify user vs service auth models
2026-09-03 19:27:16 -07:00

19 KiB

API Reference: api.riotpiao.com

Gateway to all homelab services. Single entry point for LLM inference, workflows, queues, memory, and cluster operations.

Base URL: https://api.riotpiao.com


Table of Contents

  1. Health & Status
  2. LLM Services
  3. Workflow Services (Temporal)
  4. Queue Services (SQS)
  5. Memory Services
  6. IAM Services
  7. S3 Services
  8. Authentication
  9. Error Handling

Health & Status

GET /healthz

Liveness probe. Returns immediately without checks.

curl https://api.riotpiao.com/healthz

Response:

{
  "status": "alive"
}

Status Code: 200


GET /readyz

Readiness probe. Returns 200 when gateway is ready (config loaded, connections available).

curl https://api.riotpiao.com/readyz

Response:

{
  "status": "ready"
}

Status Code: 200 (ready) or 503 (not ready)


LLM Services

GET /v1/models

List all available models for inference.

curl https://api.riotpiao.com/v1/models

Response:

{
  "object": "list",
  "data": [
    {
      "id": "reasoning",
      "object": "model",
      "owned_by": "api.riotpiao.com",
      "created": 1700000000
    },
    {
      "id": "ornith:35b",
      "object": "model",
      "owned_by": "api.riotpiao.com",
      "created": 1700000000
    },
    {
      "id": "qwen2.5:3b-instruct",
      "object": "model",
      "owned_by": "api.riotpiao.com",
      "created": 1700000000
    },
    {
      "id": "nomic-ai/nomic-embed-text-v2-moe",
      "object": "model",
      "owned_by": "api.riotpiao.com",
      "created": 1700000000
    },
    {
      "id": "BAAI/bge-reranker-base",
      "object": "model",
      "owned_by": "api.riotpiao.com",
      "created": 1700000000
    }
  ]
}

Status Code: 200


POST /v1/chat/completions

Chat with an LLM model. Supports streaming, tool calling, and multi-turn conversations.

curl -X POST https://api.riotpiao.com/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "reasoning",
    "messages": [
      {"role": "user", "content": "What is 2+2?"}
    ]
  }'

Request Body:

{
  "model": "reasoning",
  "messages": [
    {
      "role": "user",
      "content": "string or array"
    }
  ],
  "temperature": 0.7,
  "top_p": 1.0,
  "max_tokens": 2048,
  "stream": false,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "function_name",
        "description": "What it does",
        "parameters": {
          "type": "object",
          "properties": {},
          "required": []
        }
      }
    }
  ]
}

Response (non-streaming):

{
  "id": "chatcmpl-123",
  "object": "chat.completion",
  "created": 1704000000,
  "model": "reasoning",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "2+2=4"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 10,
    "completion_tokens": 5,
    "total_tokens": 15
  }
}

Response (streaming, SSE format):

data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":"2"}}]}
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":"+"}}]}
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":"2"}}]}
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":"="}}]}
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":"4"}}]}
data: [DONE]

Status Codes:

  • 200 OK
  • 400 Bad Request (invalid model, missing fields, invalid JSON)
  • 500 Internal Server Error (upstream issue)

Available Models:

  • reasoning — DeepSeek-R1-Distill reasoning model (8 concurrent slots)
  • ornith:35b — Ollama 35B model
  • qwen2.5:3b-instruct — Qwen 2.5 3B model

POST /v1/embeddings

Generate text embeddings.

curl -X POST https://api.riotpiao.com/v1/embeddings \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "nomic-ai/nomic-embed-text-v2-moe",
    "input": ["text to embed", "another text"]
  }'

Request Body:

{
  "model": "nomic-ai/nomic-embed-text-v2-moe",
  "input": "string or array of strings",
  "encoding_format": "float"
}

Response:

{
  "object": "list",
  "data": [
    {
      "object": "embedding",
      "embedding": [0.1, 0.2, 0.3, ...],
      "index": 0
    },
    {
      "object": "embedding",
      "embedding": [0.4, 0.5, 0.6, ...],
      "index": 1
    }
  ],
  "model": "nomic-ai/nomic-embed-text-v2-moe",
  "usage": {
    "prompt_tokens": 20,
    "total_tokens": 20
  }
}

Status Codes:

  • 200 OK
  • 400 Bad Request (invalid model or input)
  • 500 Internal Server Error

POST /v1/rerank

Rerank documents by relevance to a query.

curl -X POST https://api.riotpiao.com/v1/rerank \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "BAAI/bge-reranker-base",
    "query": "machine learning",
    "texts": [
      "Machine learning is AI",
      "Python is a language",
      "Deep learning is ML"
    ],
    "top_k": 2
  }'

Request Body:

{
  "model": "BAAI/bge-reranker-base",
  "query": "search query",
  "texts": ["text1", "text2", "text3"],
  "top_k": 2,
  "return_documents": true
}

Response:

{
  "results": [
    {
      "index": 0,
      "score": 0.95,
      "text": "Machine learning is AI"
    },
    {
      "index": 2,
      "score": 0.85,
      "text": "Deep learning is ML"
    }
  ]
}

Status Codes:

  • 200 OK
  • 400 Bad Request
  • 500 Internal Server Error

Workflow Services (Temporal)

Base path: /workflow

All operations use REST with JSON body. Internally translated to gRPC (port 7233).

POST /workflow

Execute a workflow operation via action dispatch.

curl -X POST https://api.riotpiao.com/workflow \
  -H 'Content-Type: application/json' \
  -d '{
    "action": "START_WORKFLOW",
    "namespace": "default",
    "payload": {
      "workflow_id": "order-123",
      "workflow_type": "ProcessOrder",
      "task_queue": "orders_queue",
      "input": {
        "order_id": "123",
        "amount": 99.99
      }
    }
  }'

Supported Actions:

START_WORKFLOW

Start a new workflow execution.

Payload:

{
  "workflow_id": "unique-id",
  "workflow_type": "WorkflowName",
  "task_queue": "queue_name",
  "input": {"field": "value"},
  "options": {
    "workflow_execution_timeout": 3600,
    "workflow_run_timeout": 1800,
    "workflow_task_timeout": 300
  }
}

Response:

{
  "success": true,
  "action": "START_WORKFLOW",
  "data": {
    "workflow_id": "order-123",
    "run_id": "abc123def",
    "start_time": "2026-08-27T..."
  }
}

DESCRIBE_WORKFLOW

Get workflow execution details.

Payload:

{
  "workflow_id": "order-123",
  "run_id": "abc123def"
}

Response:

{
  "success": true,
  "action": "DESCRIBE_WORKFLOW",
  "data": {
    "workflow_id": "order-123",
    "run_id": "abc123def",
    "status": "RUNNING",
    "start_time": "2026-08-27T..."
  }
}

LIST_WORKFLOWS

List workflow executions with filtering.

Payload:

{
  "status": "RUNNING",
  "page_size": 50,
  "next_page_token": ""
}

GET_WORKFLOW_HISTORY

Retrieve workflow execution history (events).

Payload:

{
  "workflow_id": "order-123",
  "run_id": "abc123def",
  "max_events": 100
}

SIGNAL_WORKFLOW

Send a signal to a running workflow (trigger handler).

Payload:

{
  "workflow_id": "order-123",
  "run_id": "abc123def",
  "signal_name": "payment_received",
  "input": {"amount": 99.99}
}

QUERY_WORKFLOW

Query workflow state without modifying it (read-only).

Payload:

{
  "workflow_id": "order-123",
  "run_id": "abc123def",
  "query_type": "get_status"
}

CANCEL_WORKFLOW

Request graceful cancellation.

Payload:

{
  "workflow_id": "order-123",
  "run_id": "abc123def"
}

TERMINATE_WORKFLOW

Stop workflow immediately.

Payload:

{
  "workflow_id": "order-123",
  "run_id": "abc123def",
  "reason": "User cancelled"
}

Other Actions

  • RESET_WORKFLOW
  • UPDATE_WORKFLOW
  • HEARTBEAT_ACTIVITY
  • COMPLETE_ACTIVITY
  • FAIL_ACTIVITY

Status Codes:

  • 200 OK
  • 400 Bad Request (invalid action or fields)
  • 404 Not Found (workflow not found)
  • 503 Service Unavailable (Temporal unreachable)

Queue Services (SQS)

Base path: /sqs (via X-Service header routing)

Operations via ServiceAdapter CRD. Use header-based dispatch:

curl -X GET https://api.riotpiao.com/ \
  -H 'X-Service: sqs' \
  -H 'X-Resource: queue/my-queue/messages'

Supported Resources:

  • queue — List queues
  • queue/{name}/message — Send/receive messages
  • queue/{name}/messages — Batch operations

Memory Services

Base path: / (via X-Service header)

Query and manage semantic memory for knowledge retrieval.

curl -X GET https://api.riotpiao.com/ \
  -H 'X-Service: memory' \
  -H 'X-Resource: query' \
  -G --data-urlencode 'query=explain machine learning' \
  --data-urlencode 'level=L1,L2'

Supported Resources:

  • query — Search memory (GET with params)
  • skill — List/retrieve skills
  • project — List/describe projects
  • ingest — Add new documents (POST, requires memory:write capability)

IAM Services

Base path: / (via X-Service header)

Manage Authentik users, groups, and permissions.

curl -X GET https://api.riotpiao.com/ \
  -H 'Authorization: Bearer <token>' \
  -H 'X-Service: iam' \
  -H 'X-Resource: user'

Supported Resources:

  • user — List/create users
  • user/{id} — Get/update/delete specific user
  • role (group) — Manage groups/roles
  • permission — List permissions
  • flow — List authentication flows

S3 Services

Base path: / (via X-Service header)

Object storage operations via MinIO.

curl -X GET https://api.riotpiao.com/ \
  -H 'X-Service: s3' \
  -H 'X-Resource: bucket/my-bucket/objects'

Supported Resources:

  • bucket — List buckets
  • bucket/{name}/objects — List objects in bucket
  • bucket/{name}/object/{key} — Get/put/delete object

Authentication

Bearer Token (JWT)

All operations except /healthz and /readyz require authentication via Authentik-issued JWT.

curl -H 'Authorization: Bearer <jwt-token>' \
  https://api.riotpiao.com/v1/models

Obtaining Tokens

Service Account (client_credentials flow):

For programmatic access (agents, CI, scripts), use a service account:

# Get credentials from k8s secret (once)
CLIENT_ID=$(kubectl -n portfolio get secret portfolio-agent-oidc -o jsonpath='{.data.CLIENT_ID}' | base64 -d)
CLIENT_SECRET=$(kubectl -n portfolio get secret portfolio-agent-oidc -o jsonpath='{.data.CLIENT_SECRET}' | base64 -d)

# Exchange for access token
TOKEN=$(curl -s -X POST https://authentik.riotpiao.com/application/o/token/ \
  -d "grant_type=client_credentials" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET" \
  -d "scope=openid profile groups permissions memory" | jq -r '.access_token')

# Use token
curl -H "Authorization: Bearer $TOKEN" \
  https://api.riotpiao.com/v1/models

Available service accounts:

  • portfolio-agent — Portfolio app (memory:read on homelab/portfolio projects)
  • memory-agent — Memory service internal (memory:* on all projects)

Human login (authorization_code flow):

Browser-based OAuth2 via Authentik. Used by web apps, not CLI.

1. Redirect to: https://authentik.riotpiao.com/application/o/authorize/
   ?client_id=<app>&response_type=code&redirect_uri=<callback>&scope=openid+profile+groups+permissions
2. User authenticates
3. Exchange code for token at /application/o/token/

JWT Claims

Authentik tokens include these custom claims:

{
  "sub": "hashed-user-id",
  "groups": ["homelab-admins", "llm-admins"],
  "permissions": ["llm:inference", "llm:read", "llm:write"],
  "memory_projects": ["homelab", "portfolio"],
  "memory_visibility": "public",
  "memory_role": "user"
}

Service accounts use roles stored in user attributes. Simpler than groups.

JWT claims (client_credentials flow):

{
  "azp": "portfolio-agent",
  "roles": ["llm:inference", "memory:read"]
}

Gateway checks roles claim:

if hasRole(claims.Roles, "llm:inference") {
    // allow
}

Available roles:

Role Grants access to
llm:inference /v1/chat/completions, /v1/embeddings, /v1/rerank
memory:read Memory queries
memory:write Memory ingest
s3:read S3/MinIO read
s3:write S3/MinIO write

Onboarding a New Service

  1. Add to provisioning script (homelab/scripts/iam/authentik-provision.py):
SERVICE_ACCOUNTS = {
    # ... existing ...
    "my-new-service": {
        "roles": ["llm:inference", "memory:read"],  # what APIs it can call
        "attributes": {
            "memory_projects": ["my-project"],  # optional: memory access scope
            "memory_visibility": "public",
        },
        "secret_ns": "my-namespace",       # k8s namespace for credentials
        "secret_name": "my-service-oidc",  # k8s secret name
    },
}
  1. Run provisioning:
export AUTHENTIK_BOOTSTRAP_TOKEN=$(kubectl -n iam get secret authentik-secrets \
  -o jsonpath='{.data.AUTHENTIK_BOOTSTRAP_TOKEN}' | base64 -d)
python3 scripts/iam/authentik-provision.py
  1. Use credentials in your service:
# Credentials stored in k8s secret
kubectl -n my-namespace get secret my-service-oidc -o yaml
# Contains: CLIENT_ID, CLIENT_SECRET, TOKEN_URL, ISSUER

# Get token
TOKEN=$(curl -s -X POST $TOKEN_URL \
  -d "grant_type=client_credentials" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET" \
  -d "scope=openid roles" | jq -r '.access_token')

# Call API
curl -H "Authorization: Bearer $TOKEN" https://api.riotpiao.com/v1/models

User Auth (Groups → Permissions)

Human users use groups which map to permissions. Used for browser-based apps.

JWT claims (authorization_code flow):

{
  "groups": ["homelab-admins", "llm-admins"],
  "permissions": ["*", "llm:inference", "llm:read", "llm:write"]
}
Group Permissions granted
homelab-admins * (everything)
llm-admins llm:inference, llm:read, llm:write
llm-users llm:inference
memory-users memory:read
memory-writers memory:read, memory:write
poimen-memory-admins memory:*, memory:admin

Memory-specific claims:

Claim Description
memory_projects Projects user can access (["*"] = all)
memory_visibility Max visibility level (public, private)
memory_role Role for memory service (user, admin, portfolio-agent)

Error Handling

Problem Details (RFC 9457)

All error responses use standardized JSON format:

{
  "type": "https://api.example.com/problems/unknown-model",
  "title": "Unknown Model",
  "status": 400,
  "detail": "Model 'gpt-4' is not available. See valid_models for available options.",
  "valid_models": ["reasoning", "ornith:35b", ...]
}

HTTP Status Codes

Status Meaning
200 Success
400 Bad Request (validation error, invalid model, missing fields)
401 Unauthorized (invalid/missing token)
403 Forbidden (insufficient permissions/capability)
404 Not Found (workflow, resource)
409 Conflict (duplicate workflow_id)
429 Too Many Requests (rate limit exceeded)
503 Service Unavailable (backend unreachable)
504 Gateway Timeout (request exceeded timeout)

Error Examples

Unknown Model:

curl -X POST https://api.riotpiao.com/v1/chat/completions \
  -d '{"model":"gpt-4","messages":[]}'

# Returns 400
{
  "type": "https://api.example.com/problems/unknown-model",
  "title": "Unknown Model",
  "status": 400,
  "detail": "Model 'gpt-4' is not available",
  "valid_models": ["reasoning", "ornith:35b", "qwen2.5:3b-instruct", ...]
}

Temporal Unavailable:

curl -X POST https://api.riotpiao.com/workflow \
  -d '{"action":"START_WORKFLOW",...}'

# Returns 503 if Temporal server unreachable
{
  "success": false,
  "error": "TEMPORAL_UNAVAILABLE",
  "message": "Temporal server connection not available"
}

Examples

Complete RAG Pipeline

  1. Query embedding:
curl -X POST https://api.riotpiao.com/v1/embeddings \
  -H 'Authorization: Bearer $TOKEN' \
  -d '{
    "model": "nomic-ai/nomic-embed-text-v2-moe",
    "input": "how do neural networks work"
  }' | jq '.data[0].embedding'
  1. Rerank documents:
curl -X POST https://api.riotpiao.com/v1/rerank \
  -H 'Authorization: Bearer $TOKEN' \
  -d '{
    "model": "BAAI/bge-reranker-base",
    "query": "how do neural networks work",
    "texts": ["doc1", "doc2", "doc3"],
    "top_k": 2
  }'
  1. Chat with context:
curl -X POST https://api.riotpiao.com/v1/chat/completions \
  -H 'Authorization: Bearer $TOKEN' \
  -d '{
    "model": "reasoning",
    "messages": [
      {
        "role": "user",
        "content": "Based on these documents: [top-2 from rerank]\nQuestion: how do neural networks work?"
      }
    ]
  }'

Workflow Orchestration

# Start workflow
WORKFLOW_ID="order-$(date +%s)"
curl -X POST https://api.riotpiao.com/workflow \
  -H 'Authorization: Bearer $TOKEN' \
  -d "{
    \"action\": \"START_WORKFLOW\",
    \"namespace\": \"default\",
    \"payload\": {
      \"workflow_id\": \"$WORKFLOW_ID\",
      \"workflow_type\": \"ProcessOrder\",
      \"task_queue\": \"orders\",
      \"input\": {\"order_id\": \"123\"}
    }
  }"

# Monitor execution
curl -X POST https://api.riotpiao.com/workflow \
  -H 'Authorization: Bearer $TOKEN' \
  -d "{
    \"action\": \"DESCRIBE_WORKFLOW\",
    \"namespace\": \"default\",
    \"payload\": {\"workflow_id\": \"$WORKFLOW_ID\"}
  }"

# Send signal when payment received
curl -X POST https://api.riotpiao.com/workflow \
  -H 'Authorization: Bearer $TOKEN' \
  -d "{
    \"action\": \"SIGNAL_WORKFLOW\",
    \"namespace\": \"default\",
    \"payload\": {
      \"workflow_id\": \"$WORKFLOW_ID\",
      \"signal_name\": \"payment_received\",
      \"input\": {\"amount\": 99.99}
    }
  }"

Rate Limits & Quotas

  • /v1/chat/completions — 8 concurrent slots (reasoning model)
  • /v1/embeddings — 10 concurrent
  • /v1/rerank — 10 concurrent
  • /workflow — 100 concurrent operations
  • Retry-After header set on 429 responses

Timeouts

Endpoint Connect Read Write
/v1/chat/completions 10s 1h 1h
/v1/embeddings 10s 10m 10m
/v1/rerank 10s 10m 10m
/workflow 10s 30s 10s

Support

  • Health: curl https://api.riotpiao.com/healthz
  • Readiness: curl https://api.riotpiao.com/readyz
  • Models: curl https://api.riotpiao.com/v1/models
  • Logs: kubectl -n api logs deployment/homelab-frontend