> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Recipe Serve

> HTTP server for recipe endpoints

# Recipe Serve

The `praisonai serve recipe` command starts an HTTP server that exposes recipe endpoints for remote invocation.

## Quick Start

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Start server on default port (8765)
praisonai serve recipe

# Start on custom port
praisonai serve recipe --port 8000

# Start with authentication
praisonai serve recipe --auth api-key
```

## Command Options

| Option             | Description                           | Default   |
| ------------------ | ------------------------------------- | --------- |
| `--port <num>`     | Server port                           | 8765      |
| `--host <addr>`    | Server host                           | 127.0.0.1 |
| `--auth <type>`    | Auth type: none, api-key, jwt         | none      |
| `--api-key <key>`  | API key for authentication            | -         |
| `--reload`         | Enable hot reload (dev mode)          | false     |
| `--preload`        | Preload all recipes on startup        | false     |
| `--recipes <list>` | Comma-separated recipe names to serve | all       |
| `--config <path>`  | Path to serve.yaml config file        | -         |

## Security

### Host Binding Safety

By default, the server binds to `127.0.0.1` (localhost only). **Binding to `0.0.0.0` (all interfaces) requires authentication.**

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# This will be REFUSED (no auth on public interface)
praisonai serve recipe --host 0.0.0.0

# This works (auth enabled)
praisonai serve recipe --host 0.0.0.0 --auth api-key
```

<Warning>
  A typo in the `auth:` field (e.g. `apikey`, `api_key`, `APIKey`) refuses to start on a non-localhost bind rather than silently downgrading to no auth. This is intentional — see [PR #3926](https://github.com/MervinPraison/PraisonAI/pull/3926).
</Warning>

### Authentication Modes

#### Supported Auth Modes

The recipe server accepts exactly three auth modes, defined as a single source of truth in `praisonai.recipe.serve`:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
SUPPORTED_AUTH_TYPES = frozenset({"none", "api-key", "jwt"})
```

`create_app(config)` validates the `auth` value at construction time and raises `ValueError` for anything else — including typos (`apikey`, `apiKey`), empty strings (`""`), and non-strings (`False`, `[]`).

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai.recipe.serve import create_app

# Rejected: raises ValueError
create_app({"auth": "apikey"})
# ValueError: Unsupported recipe server auth mode 'apikey'.
#             Expected one of: api-key, jwt, none

# Accepted
create_app({"auth": "api-key", "api_key": "my-secret-key"})
```

Any non-localhost bind refuses to start unless `auth` is one of the supported non-`none` modes (or `--api-key` is passed on the CLI). The CLI runs its checks in this order:

<Steps>
  <Step title="Load config">
    Read `serve.yaml` (or CLI flags) into the config.
  </Step>

  <Step title="Validate auth mode">
    Reject any `auth` value outside `SUPPORTED_AUTH_TYPES` before binding.
  </Step>

  <Step title="Bind guard">
    Refuse a non-localhost bind that resolves to `auth: none` with no `--api-key`.
  </Step>

  <Step title="Serve">
    Start the server only after all checks pass.
  </Step>
</Steps>

#### API Key Authentication

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Start with API key auth
praisonai serve recipe --auth api-key --api-key my-secret-key

# Or use environment variable
export PRAISONAI_API_KEY=my-secret-key
praisonai serve recipe --auth api-key
```

Clients must include the `X-API-Key` header:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
curl -H "X-API-Key: my-secret-key" http://localhost:8765/v1/recipes
```

## Configuration File

Create a `serve.yaml` file for persistent configuration:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# serve.yaml
host: 127.0.0.1
port: 8765
auth: api-key
api_key: your-secret-key  # or use PRAISONAI_API_KEY env var

# Optional: limit which recipes are served
recipes:
  - my-recipe
  - another-recipe

# Optional: preload recipes on startup
preload: true

# Optional: CORS configuration
cors_origins: "*"
```

Use the config file:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai serve recipe --config ./serve.yaml
```

### Configuration Precedence

1. CLI flags (highest priority)
2. Environment variables
3. Config file
4. Defaults (lowest priority)

## API Endpoints

### Health Check

```
GET /health
```

Response:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "status": "healthy",
  "service": "praisonai-recipe-runner",
  "version": "2.7.1"
}
```

### List Recipes

```
GET /v1/recipes
GET /v1/recipes?tags=audio,video
```

Response:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "recipes": [
    {
      "name": "my-recipe",
      "version": "1.0.0",
      "description": "Recipe description",
      "tags": ["audio", "video"]
    }
  ]
}
```

### Describe Recipe

```
GET /v1/recipes/{name}
```

Response:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "name": "my-recipe",
  "version": "1.0.0",
  "description": "Recipe description",
  "requires": {
    "packages": [],
    "env": ["OPENAI_API_KEY"]
  },
  "config_schema": {},
  "outputs": []
}
```

### Get Recipe Schema

```
GET /v1/recipes/{name}/schema
```

Response:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "name": "my-recipe",
  "version": "1.0.0",
  "input_schema": {},
  "output_schema": []
}
```

### Run Recipe

```
POST /v1/recipes/run
Content-Type: application/json

{
  "recipe": "my-recipe",
  "input": {"query": "Hello"},
  "config": {},
  "options": {"dry_run": false}
}
```

Response:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "ok": true,
  "run_id": "run-abc123",
  "recipe": "my-recipe",
  "version": "1.0.0",
  "status": "success",
  "output": {"result": "..."},
  "metrics": {"duration_sec": 1.5},
  "trace": {
    "run_id": "run-abc123",
    "session_id": "session-xyz",
    "trace_id": "trace-123"
  }
}
```

### Stream Recipe (SSE)

```
POST /v1/recipes/stream
Content-Type: application/json

{
  "recipe": "my-recipe",
  "input": {"query": "Hello"}
}
```

Response (Server-Sent Events):

```
event: started
data: {"run_id": "run-abc123", "recipe": "my-recipe"}

event: progress
data: {"step": "loading", "message": "Loading recipe..."}

event: progress
data: {"step": "executing", "message": "Running workflow..."}

event: completed
data: {"run_id": "run-abc123", "status": "success"}
```

## Examples

### Development Mode

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Start with hot reload for development
praisonai serve recipe --reload
```

### Production Mode

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Production with auth and preloading
praisonai serve recipe \
  --host 0.0.0.0 \
  --port 8000 \
  --auth api-key \
  --preload
```

### Using with Docker

```dockerfile theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
FROM python:3.11-slim
RUN pip install praisonai[serve]
COPY serve.yaml /app/
WORKDIR /app
CMD ["praisonai", "recipe", "serve", "--config", "serve.yaml"]
```

### Client Examples

#### curl

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Health check
curl http://localhost:8765/health

# List recipes
curl http://localhost:8765/v1/recipes

# Run recipe
curl -X POST http://localhost:8765/v1/recipes/run \
  -H "Content-Type: application/json" \
  -d '{"recipe": "my-recipe", "input": {"query": "Hello"}}'

# With auth
curl -X POST http://localhost:8765/v1/recipes/run \
  -H "Content-Type: application/json" \
  -H "X-API-Key: my-secret-key" \
  -d '{"recipe": "my-recipe", "input": {"query": "Hello"}}'
```

#### Python

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import requests

# Run recipe
response = requests.post(
    "http://localhost:8765/v1/recipes/run",
    json={
        "recipe": "my-recipe",
        "input": {"query": "Hello"}
    },
    headers={"X-API-Key": "my-secret-key"}
)
result = response.json()
print(result["output"])
```

#### JavaScript

```javascript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
const response = await fetch("http://localhost:8765/v1/recipes/run", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "my-secret-key"
  },
  body: JSON.stringify({
    recipe: "my-recipe",
    input: { query: "Hello" }
  })
});
const result = await response.json();
console.log(result.output);
```

## Environment Variables

| Variable               | Description                |
| ---------------------- | -------------------------- |
| `PRAISONAI_API_KEY`    | API key for authentication |
| `PRAISONAI_SERVE_HOST` | Default host               |
| `PRAISONAI_SERVE_PORT` | Default port               |

## Troubleshooting

### Port Already in Use

```
Error: [Errno 48] Address already in use
```

**Solution**: Use a different port or stop the existing process:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai serve recipe --port 8766
# Or
lsof -i :8765 | grep LISTEN | awk '{print $2}' | xargs kill
```

### Missing Dependencies

```
Error: Serve dependencies not installed. Run: pip install praisonai[serve]
```

**Solution**: Install serve extras:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
pip install praisonai[serve]
```

### Auth Required for Public Binding

```
Error: Auth required for non-localhost binding. Use --api-key or set a supported auth mode (api-key, jwt) in the config.
```

**Solution**: Enable authentication:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai serve recipe --host 0.0.0.0 --auth api-key
```

### Unsupported Auth Mode

```
ValueError: Unsupported recipe server auth mode 'apikey'. Expected one of: api-key, jwt, none
```

**Solution**: Use an exact supported mode (`none`, `api-key`, or `jwt`). Typos like `apikey`, `api_key`, or `apiKey` fail closed rather than downgrading to no auth.
