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

# Web API

> Core platform backend. Handles authentication, organization management, credential vault, and acts as the gRPC proxy for all dashboard operations.

## Overview

The `web-api` is the primary API backend for the Rapida dashboard. Every request from the browser — authentication, organization setup, assistant management, credential storage — goes through this service. It also acts as the gRPC proxy for downstream services, validating JWT tokens before forwarding requests.

<CardGroup cols={3}>
  <Card title="Port" icon="server">
    `9001` — HTTP · gRPC · gRPC-web (cmux)
  </Card>

  <Card title="Language" icon="code">
    Go 1.25
    Gin (REST) + gRPC
  </Card>

  <Card title="Storage" icon="database">
    PostgreSQL `web_db`
    Redis (session cache)
  </Card>
</CardGroup>

<Info>
  All gRPC-web requests from the browser, except for real-time audio, are routed through `web-api`. The service validates the JWT and proxies the request to the correct downstream service using typed gRPC clients from `pkg/clients/`.
</Info>

***

## Components

<AccordionGroup>
  <Accordion title="Authentication & Session Management">
    Handles user registration, login, password recovery, OAuth 2.0 flows, and JWT issuance.

    | Feature         | Detail                                                 |
    | --------------- | ------------------------------------------------------ |
    | Token type      | JWT (signed with `SECRET`, shared across all services) |
    | Token storage   | Client-side (Authorization header)                     |
    | Session cache   | Redis DB 1                                             |
    | OAuth providers | Google, GitHub, Microsoft (configured per deployment)  |
    | Token expiry    | Configurable; default 24 hours                         |
  </Accordion>

  <Accordion title="Organization & Project Hierarchy">
    Every resource in Rapida is scoped to an `Organization → Project` hierarchy. The web-api enforces this scoping at the gRPC interceptor level.

    ```
    Organization
    └── Project
        ├── Assistants
        ├── Knowledge Bases
        ├── Integrations
        └── Webhooks
    ```

    Each entity stores `organization_id` and `project_id` via the `Organizational` base model. The gRPC auth interceptor rejects any request where the JWT's organization claim does not match the target resource.
  </Accordion>

  <Accordion title="Credential Vault">
    Provider API keys and OAuth tokens are encrypted with AES-256 before storage. The encryption key is derived from `SECRET`. The vault is the source of truth for all provider credentials — `integration-api` reads from it at call time.

    | Operation    | Behavior                                                              |
    | ------------ | --------------------------------------------------------------------- |
    | Store key    | AES-256-GCM encrypt → write to `web_db`                               |
    | Retrieve key | Read from `web_db` → decrypt in-memory → forward to `integration-api` |
    | Rotate key   | Replace ciphertext; existing calls in flight are unaffected           |
    | Audit        | Every vault read/write is logged with user ID and timestamp           |
  </Accordion>

  <Accordion title="Internal gRPC Proxy">
    The web-api acts as a proxy for all dashboard gRPC calls. It validates the JWT, extracts the organization context, and forwards to the correct downstream service.

    | gRPC Path Prefix                               | Forwarded To              |
    | ---------------------------------------------- | ------------------------- |
    | `/web_api`                                     | Local (web-api owns this) |
    | `/vault_api`                                   | Local (web-api owns this) |
    | `/workflow_api` · `/assistant_api`             | `assistant-api:9007`      |
    | `/knowledge_api`                               | `assistant-api:9007`      |
    | `/tool_api` · `/endpoint_api` · `/webhook_api` | `endpoint-api:9005`       |
    | `/provider_api` · `/integration_api`           | `integration-api:9004`    |
    | `/connect_api`                                 | Local (OAuth connector)   |
    | `/document_api`                                | `document-api:9010`       |
    | `/lead_api`                                    | Local                     |
  </Accordion>

  <Accordion title="Entity and Data Model">
    All entities compose base GORM models:

    | Base Model       | Fields                                       |
    | ---------------- | -------------------------------------------- |
    | `Audited`        | `id` (Snowflake), `created_at`, `updated_at` |
    | `Mutable`        | `status`, `created_by`, `updated_by`         |
    | `Organizational` | `project_id`, `organization_id`              |

    IDs are generated as Snowflake IDs in the `BeforeCreate` GORM hook — no UUID dependency. The Snowflake generator is initialized at service startup using the service instance ID.
  </Accordion>
</AccordionGroup>

***

## Request Flow

```mermaid theme={null}
sequenceDiagram
    participant Browser as Browser (Dashboard)
    participant Nginx as Nginx :8080
    participant Web as web-api :9001
    participant PG as PostgreSQL (web_db)
    participant Redis as Redis
    participant DS as Downstream Service

    Browser->>Nginx: gRPC-web POST /<service>_api.*
    Nginx->>Web: Forward (all non-talk_api paths)
    Web->>Web: JWT interceptor — validate token
    Web->>PG: Check organization + project scope
    alt Local service
        Web->>PG: Execute operation
        Web->>Redis: Update cache
    else Downstream proxy
        Web->>DS: gRPC forward with org context
        DS-->>Web: Response
    end
    Web-->>Browser: gRPC-web response
```

***

## Configuration

Edit `docker/web-api/web.yml` before starting the service. The variable names below map to nested YAML keys, so `POSTGRES__HOST` corresponds to `postgres.host`.

### Required variables

| Variable                   | Required | Default                    | Description                                  |
| -------------------------- | -------- | -------------------------- | -------------------------------------------- |
| `SECRET`                   | ✅ Yes    | `rpd_pks`                  | JWT signing secret — must match all services |
| `POSTGRES__HOST`           | ✅ Yes    | `postgres`                 | PostgreSQL host                              |
| `POSTGRES__DB_NAME`        | ✅ Yes    | `web_db`                   | Database name                                |
| `POSTGRES__AUTH__USER`     | ✅ Yes    | `rapida_user`              | Database user                                |
| `POSTGRES__AUTH__PASSWORD` | ✅ Yes    | —                          | Database password                            |
| `REDIS__HOST`              | ✅ Yes    | `redis`                    | Redis host                                   |
| `INTEGRATION_HOST`         | ✅ Yes    | `integration-api:9004`     | integration-api gRPC address                 |
| `ENDPOINT_HOST`            | ✅ Yes    | `endpoint-api:9005`        | endpoint-api gRPC address                    |
| `ASSISTANT_HOST`           | ✅ Yes    | `assistant-api:9007`       | assistant-api gRPC address                   |
| `DOCUMENT_HOST`            | ✅ Yes    | `http://document-api:9010` | document-api HTTP address                    |

### Tuning variables

| Variable                         | Default       | Description                              |
| -------------------------------- | ------------- | ---------------------------------------- |
| `LOG_LEVEL`                      | `debug`       | `debug` · `info` · `warn` · `error`      |
| `ENV`                            | `development` | `development` · `staging` · `production` |
| `POSTGRES__MAX_OPEN_CONNECTION`  | `10`          | Database connection pool size            |
| `POSTGRES__MAX_IDEAL_CONNECTION` | `10`          | Idle connections to keep open            |
| `REDIS__MAX_CONNECTION`          | `5`           | Redis connection pool size               |
| `ASSET_STORE__STORAGE_TYPE`      | `local`       | `local` · `s3` · `azure`                 |

### Full environment file

```env theme={null}
# ── Service identity ──────────────────────────────────────────────
SERVICE_NAME=web-api
HOST=0.0.0.0
PORT=9001
LOG_LEVEL=debug
SECRET=rpd_pks
ENV=development

# ── PostgreSQL ────────────────────────────────────────────────────
POSTGRES__HOST=postgres
POSTGRES__PORT=5432
POSTGRES__DB_NAME=web_db
POSTGRES__AUTH__USER=rapida_user
POSTGRES__AUTH__PASSWORD=rapida_db_password
POSTGRES__MAX_OPEN_CONNECTION=10
POSTGRES__MAX_IDEAL_CONNECTION=10
POSTGRES__SSL_MODE=disable

# ── Redis (second-level GORM cache) ───────────────────────────────
POSTGRES__SLC_CACHE__HOST=redis
POSTGRES__SLC_CACHE__PORT=6379
POSTGRES__SLC_CACHE__DB=1
POSTGRES__SLC_CACHE__MAX_CONNECTION=10

# ── Redis ─────────────────────────────────────────────────────────
REDIS__HOST=redis
REDIS__PORT=6379
REDIS__MAX_CONNECTION=5

# ── Asset storage ─────────────────────────────────────────────────
ASSET_STORE__STORAGE_TYPE=local
ASSET_STORE__STORAGE_PATH_PREFIX=/app/rapida-data/assets/web

# ── Internal service addresses ────────────────────────────────────
INTEGRATION_HOST=integration-api:9004
ENDPOINT_HOST=endpoint-api:9005
ASSISTANT_HOST=assistant-api:9007
WEB_HOST=web-api:9001
DOCUMENT_HOST=http://document-api:9010
UI_HOST=https://localhost:3000
```

<Warning>
  `SECRET` is used for JWT signing **and** credential vault encryption. All services must share the same value. Changing it in production will invalidate all active sessions and make stored vault credentials unreadable. Rotate carefully.
</Warning>

***

## Running

<Tabs>
  <Tab title="Docker Compose">
    ```bash theme={null}
    # Start web-api and its dependencies
    make up-web

    # Follow logs
    make logs-web

    # Rebuild after code changes (no cache)
    make rebuild-web

    # Open a shell in the container
    make shell-web
    ```
  </Tab>

  <Tab title="From Source">
    Requires Go 1.25, PostgreSQL 15, and Redis 7 running locally.

    ```bash theme={null}
    # Uses env/web.yml by default when run from the repo root
    go run cmd/web/web.go
    ```

    If you need a different config file, point `ENV_PATH` at it:

    ```bash theme={null}
    ENV_PATH=docker/web-api/web.yml go run cmd/web/web.go
    ENV_PATH=env/web.yml go run cmd/web/web.go
    ```
  </Tab>
</Tabs>

***

## Database Migrations

Migrations run automatically at service startup using [golang-migrate](https://github.com/golang-migrate/migrate). Migration files are in `api/web-api/migrations/` and follow sequential naming:

```
000001_initial_schema.up.sql
000001_initial_schema.down.sql
000002_add_oauth_tokens.up.sql
```

To run manually during local development:

```bash theme={null}
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest

migrate \
  -path api/web-api/migrations \
  -database "postgresql://rapida_user:rapida_db_password@localhost:5432/web_db?sslmode=disable" \
  up
```

***

## Health & Observability

| Endpoint          | Purpose                                                     |
| ----------------- | ----------------------------------------------------------- |
| `GET /readiness/` | Reports whether the service is ready (DB + Redis connected) |
| `GET /healthz/`   | Liveness probe                                              |

```bash theme={null}
curl http://localhost:9001/readiness/
```

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Service exits immediately on startup">
    The most common cause is PostgreSQL not yet healthy. Check `make logs-web` and confirm the `postgres` container is `Up (healthy)`.

    ```bash theme={null}
    docker compose ps postgres
    make logs-web | head -40
    ```
  </Accordion>

  <Accordion title="JWT validation fails across services">
    All services must share the same secret value. Confirm `secret` is identical in `docker/web-api/web.yml`, `docker/assistant-api/assistant.yml`, `docker/integration-api/integration.yml`, and `docker/endpoint-api/endpoint.yml`.
  </Accordion>

  <Accordion title="OAuth login redirects fail">
    * Ensure the relevant `oauth2` keys are set in `docker/web-api/web.yml`.
    * Verify the redirect URI registered with the OAuth provider exactly matches `UI_HOST`.
  </Accordion>

  <Accordion title="gRPC proxy returns 'service unavailable'">
    * The target downstream service must be running and healthy.
    * Verify `INTEGRATION_HOST`, `ENDPOINT_HOST`, `ASSISTANT_HOST` point to reachable addresses.
    * Check `make status` to confirm all containers are `Up`.
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Assistant API" icon="mic" href="/opensource/services/assistant-api">
    Voice orchestration service that web-api proxies to.
  </Card>

  <Card title="Integration API" icon="plug" href="/opensource/services/integration-api">
    Provider credentials used by the vault.
  </Card>

  <Card title="Installation Guide" icon="rocket" href="/opensource/installation">
    Deploy the full platform with Docker Compose.
  </Card>

  <Card title="Architecture" icon="network" href="/opensource/architecture">
    Full system topology and routing.
  </Card>
</CardGroup>
