RylvoRylvo
API Documentation

Rylvo API Reference

One endpoint. Full agentic control. Run a bot turn — prompts, guardrails, tools, and knowledge-base retrieval — on your own LLM key, in minutes.

Quick Start

Video coming soon

Click to preview (placeholder)

Make your first API call in under a minute. You need a botId and an API key from the dashboard.

bash
curl -X POST https://rylvo.com/api/v1/bot-run \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "botId": "your_bot_id",
    "sessionId": "session_001",
    "message": "I can't access my account."
  }'

Access Model

Video coming soon

Click to preview (placeholder)

Rylvo exposes three surfaces. Knowing which one to use for what avoids confusion when integrating a backend, connecting an AI editor, or operating the platform day-to-day.

Public RESTBackend runtime

Call from your backend with your org API key (Authorization: Bearer rylv_…). This is the runtime surface you ship in production.

  • POST /api/v1/bot-run (scope: respond)
  • POST /api/v1/kb/query (scope: kb:read)
  • POST /api/v1/kb/documents (scope: kb:write)
  • /health, /ready, /live, /flags (unauthenticated)
MCP / AI EditorExternal AI tools

Connect Claude Code, Cursor, Windsurf, Zed, or Codex directly to Rylvo. OAuth 2.1 + PKCE, org-scoped tokens.

  • POST /mcp (Streamable HTTP)
  • /oauth/* registration + token exchange
  • 128 tools for bots, prompts, KB, skills, connectors
  • OpenAPI 3.1 spec at /openapi.json
DashboardConfiguration & ops

Manage from app.rylvo.com/dashboard. Many features are also reachable through the MCP / AI Editor surface.

  • Bots, Prompts, Guardrails, Knowledge Base
  • Workspace Architect, Bot Chat, Bot Tests
  • Edge Cases, Red Team, Automation, Reports
  • Mission Control, Agent Evolution, Skills, MCP Hub
  • Billing, Team, API Keys, Build Intelligence

Sections below marked with Dashboard-only do not yet have a dedicated public REST endpoint. They remain fully functional inside the dashboard, through the MCP / AI Editor surface, and their behavior affects the /api/v1/bot-run runtime.

Authentication

Video coming soon

Click to preview (placeholder)

All API requests require an API key sent in the X-API-Key header. Create keys from your dashboard.

http
X-API-Key: rylv_abc123...

Security: Never expose API keys in client-side code. Always call the API from your backend.

Provider Keys (BYOK)Dashboard-only

Video coming soon

Click to preview (placeholder)

Every bot chat on Rylvo runs on your own LLM provider key. Rylvo routes the call server-side and logs a full trace for observability, but adds zero markup on token costs — you pay your LLM provider directly. Manage keys in Settings → LLM Provider Keys.

Bot Chat, Group Chat, Bot Tests

Route to the customer's own provider key. Requires either the matching direct-provider key (e.g. Anthropic for anthropic/* models) or an OpenRouter universal key. Without a key the UI blocks the chat and surfaces a CTA.

Workspace Architect

Uses Rylvo's own server-side key - you don't need to add anything to run the Architect. Only the bots it creates need BYOK before you can chat with them.

Key resolution order

  1. Auto (default) - the model's native provider key when added (direct to e.g. api.anthropic.com), otherwise your OpenRouter key.
  2. Always OpenRouter - every chat runs on your OpenRouter key, even when a direct key exists.
  3. Native keys only - OpenRouter is never used; models without a direct key are locked.
  4. Per-bot override available in the Bot Hub. When no key can serve a model, the chat UI names exactly which key(s) would unlock it.

Supported providers

  • OpenRouter - universal, covers every model
  • OpenAI - openai/*
  • Anthropic - anthropic/* (native Messages API adapter)
  • Google Gemini - google/* (native generateContent adapter)
  • DeepSeek - deepseek/*
  • xAI (Grok) - x-ai/*
  • Kimi / Qwen / GLM - kimi/*, qwen/*, zhipu/*
  • Mistral - mistralai/*
  • Groq - groq/* (needs its own key; not on OpenRouter)

Access & security

  • Write/delete restricted to org admins.
  • Read allowed for all org members so operators can chat with bots.
  • Keys never leave Firestore + the route handler - never exposed to page HTML.
  • Key rotation automatically resets verification state.

Test before you ship

The Settings page has a Test button next to every saved key. It sends a 4-token ping (< $0.001) to the provider and reports auth / rate-limit / network errors inline, plus round-trip latency. Use Test all to re-verify every configured key at once - ideal after a key rotation.

Bot Tagging (Mandatory)

Video coming soon

Click to preview (placeholder)

Required: Every runtime request must include a botId (or a groupId for an agent group). Requests with neither are rejected with a 400 error.

Bots are the primary organizational unit for your API usage. Each bot represents a distinct agent or use case (e.g. "Support Triage Bot", "Sales Qualifier Bot"). Create bots from your dashboard, then use the bot ID to tag every API call.

How it works

1

Create a bot

Go to Dashboard → Bots → New Bot. Give it a name and description.

2

Copy the bot ID

Each bot gets a unique ID. Copy it from the bot card.

3

Tag every API request

Include "botId": "your_bot_id" in the request body.

4

View per-bot analytics

Filter traces, analytics, and billing by bot.

Example request with botId

json
{
  "botId": "bot_abc123def456",
  "sessionId": "sess_001",
  "message": "I can't access my account."
}

Benefits of bot tagging

Per-bot analytics

View metrics, latency, and error rates per bot

Trace grouping

Filter and search traces by bot name

Key association

Link API keys to specific bots

Access control

Manage permissions per bot

Bot Chat PlaygroundDashboard-only

Video coming soon

Click to preview (placeholder)

Test a bot's behavior end-to-end before pointing production traffic at it. The playground at /dashboard/bots/[botId]/chat assembles the full runtime exactly like /api/v1/bot-run does: system prompts, placeholders, guardrails, connectors, KB connections, and evolution rules.

Every turn captures

  • Active prompt IDs + resolved placeholders
  • Guardrails evaluated (and which triggered)
  • Connectors invoked + request/response bodies
  • Evolution rules injected into the system prompt
  • Active flow step + transitions evaluated (if a flow is attached)
  • Token usage + cost (runs on your own BYOK key)

Good for

  • Reproducing a failure from a trace replay
  • Iterating on a prompt before promoting to active
  • Verifying a new guardrail actually fires
  • Comparing two bots side-by-side (/dashboard/bots/[botId]/compare)

Each playground turn shows the prompt stack, guardrail decisions, connector calls, evolution rules applied, the active flow step (if a flow is attached), operator whispers, token usage, and cost - all alongside the message itself.

Bot TestsDashboard-only

Video coming soon

Click to preview (placeholder)

Define reusable test cases per bot and run them on demand or via an Automation schedule. Manage at /dashboard/bots/[botId]/tests.

A test case specifies

  • Input message (single-turn) or a scripted multi-turn conversation
  • Expected output text / behaviour
  • Guardrails that should or must not trigger
  • Assertions on output text (contains, regex, semantic similarity)
  • Optional pre-populated structured_state

A test run produces

  • Pass/fail per case with diff against expectations
  • Token usage & cost for the whole run
  • Full trace links for every case (for debugging)
  • A summary you can attach to a report or alert channel

Failed test cases can be converted into Edge Cases so the engine tracks them through the detect → classify → fix → verify loop.

Agent Groups (Multi-Bot Teams)Dashboard-only

Video coming soon

Click to preview (placeholder)

Compose multiple bots into a team where one bot routes, delegates, and merges results from specialists. Useful for workflows that span domains (e.g. billing + technical support) or require parallel drafts.

Group modes

  • Router - one bot picks the best specialist per turn
  • Sequential - bots run in a fixed pipeline
  • Parallel - bots run simultaneously, a merger summarises
  • Round-robin - alternates bots across turns

Each member contributes

  • Its own prompts, guardrails, connectors, KB scope
  • An attached role/persona (e.g. billing_specialist)
  • Its own trace - the group trace links all of them

Group chats record which member bots contributed to each turn and how routing decisions were made, so you can debug who said what and why.

Endpoints

Video coming soon

Click to preview (placeholder)

MethodPathDescription
POST/api/v1/bot-runRun one bot or agent-group turn (scope: respond)
POST/api/v1/kb/queryQuery a knowledge-base connection (scope: kb:read)
POST/api/v1/kb/documentsUpload documents to a KB source (scope: kb:write)
GET/healthLiveness (process alive)
GET/readyReadiness (can handle requests)
GET/liveUptime probe
GET/flagsRollout flags

The model catalog, traces, analytics, and connector CRUD are managed from the dashboard and the MCP / AI Editor surface — they are not exposed as public REST endpoints today.

Runtime (Bot Turn)

Video coming soon

Click to preview (placeholder)

POST /api/v1/bot-run is the core of Rylvo. Send a message (plus optional session + history) and the engine runs one full agentic turn, returning the assistant reply with full telemetry. It runs on your own BYOK key.

Prompt assembly

Bot prompts + skills

Guardrails

Block / rewrite / escalate

Tools & connectors

Live tool calls

KB retrieval

Grounded answers

Request Format

Video coming soon

Click to preview (placeholder)

json
{
  "botId": "bot_abc123def456",
  "sessionId": "sess_001",
  "message": "The display shows error E42.",
  "conversationHistory": [
    { "role": "user", "content": "Hi, my device won't start." },
    { "role": "assistant", "content": "I can help. What does the screen show?" }
  ],
  "endUserExternalId": "user_8842",
  "endUserDisplayName": "Jordan Lee",
  "channel": "api"
}

Required Fields

FieldTypeDescription
botIdstringBot identifier (from Dashboard → Bots). Either botId or groupId is required.
messagestringThe end user's current message

Optional Fields

FieldTypeDescription
groupIdstringAgent-group id (run a multi-bot team instead of a single bot)
conversationHistoryobject[]Prior turns: { role: 'user' | 'assistant', content }
sessionIdstringConversation/session id; echoed back as conversationId
endUserExternalIdstringStable end-user id in your namespace (≤256). Required for display name / email.
endUserDisplayNamestringHuman-readable name shown in Traces (≤200)
endUserEmailstringEnd-user email for support handoff (validated)
channelstringapi (default) | widget | whatsapp | slack | unknown
modelstringOverride model (e.g. openai/gpt-4.1-mini, anthropic/claude-sonnet-4.6)
maxTokensnumberMax output tokens for this turn
temperaturenumberSampling temperature for this turn
disableToolsbooleanDisable tool/connector calls for this turn
maxToolIterationsnumberCap the tool-call loop iterations
responseFormatobjectStructured output: {type:"json_object"} or {type:"json_schema", json_schema:{...}} — constrains the final reply (OpenAI-compatible models only)
streambooleanToken streaming via Server-Sent Events: token events as the reply generates, then a final result event with the standard JSON body (single-bot only)

Optional header Idempotency-Key: <uuid> — re-sending the same key for the same API key replays the stored response (no double-billing / re-running tools).

Response Format

Video coming soon

Click to preview (placeholder)

json
{
  "response": "I've logged error E42. Let me pull up the matching fix steps.",
  "conversationId": "sess_001",
  "runId": "run_8f3a21c9",
  "model": "openai/gpt-4.1-mini",
  "latencyMs": 1840,
  "tokens": { "prompt": 1203, "completion": 88, "total": 1291 },
  "guardrails": {
    "inputBlocked": false,
    "outputRewritten": false,
    "blockedBy": null,
    "escalated": false,
    "escalatedBy": null
  },
  "toolCalls": [],
  "augmentation": { "rulesApplied": 0, "skillsMatched": 1, "episodicRecallCount": 0, "userModelApplied": false },
  "sessionEnd": null,
  "kb": null,
  "pendingApproval": null
}

Key Response Fields

FieldDescription
responseThe assistant's reply text to show the user
runIdUnique id for this turn — find it in Dashboard → Traces
conversationIdEchoes the request sessionId
modelThe model that actually served the turn
latencyMsServer-side turn latency (ms)
tokens{ prompt, completion, total } token counts
guardrails{ inputBlocked, outputRewritten, blockedBy, escalated, escalatedBy }
toolCallsTools invoked this turn (name, disposition, latency, cost)
sessionEndSet when the bot ended the session ({ reason, summary? })
kbKB grounding telemetry incl. citations (when retrieval ran)
pendingApprovalPresent when a tool call awaits operator approval

Flows

Video coming soon

Click to preview (placeholder)

Flows are an optional guided/strict conversation graph attached to a bot (bot.flowId) or an agent group (dashboard/MCP-managed) that shapes its runtime behaviour — they are not part of the /api/v1/bot-run request or response. A flow is a set of steps: the engine keeps a per-session pointer to the current step, injects that step's goal into the system prompt, scopes its tools, and advances when a transition fires.

A step carries

goalInjected into the system prompt as this step's instruction
collectData fields to slot-fill before advancing
allowed_toolsTool scope for the step (all / none / a list)
transitionsEdges to the next step, evaluated every turn

Transition kinds

alwaysFires unconditionally
data_completeAll of the step's collect fields are gathered
intentThe classified intent matches
conditionA DSL expression over the turn context is true
tool_resultA named tool ran successfully this turn

Error Handling

Video coming soon

Click to preview (placeholder)

400

Invalid Request

Malformed JSON, missing message, missing botId/groupId, or a bad field shape.

401

Authentication Error

Missing or invalid API key.

403

Forbidden

Key lacks the scope, is bot-bound to a different bot, or the org is suspended.

404

Not Found

Bot, group, or resource not found.

429

Rate Limited

Per-key rate limit exceeded — see the Retry-After header.

502 / 503

Engine Unavailable

Runtime engine unreachable, not configured, or temporarily disabled.

Engine-side failures (e.g. a missing BYOK provider key) pass through the engine's status code with its own code / error.

json
// Error response format
{
  "error": "One of `botId` or `groupId` is required.",
  "code": "invalid_request"
}

Multi-Turn Example

Video coming soon

Click to preview (placeholder)

Keep a stable sessionId and pass the running transcript in conversationHistory so the bot has context.

Turn 1 - User reports issue

bash
curl -X POST https://rylvo.com/api/v1/bot-run \
  -H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"botId":"bot_abc123","sessionId":"conv_001","message":"My subscription renewal failed."}'

Turn 2 - User provides order number (include prior turns)

bash
curl -X POST https://rylvo.com/api/v1/bot-run \
  -H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"botId":"bot_abc123","sessionId":"conv_001","message":"My order is ORD-98765-XYZ","conversationHistory":[{"role":"user","content":"My subscription renewal failed."},{"role":"assistant","content":"I can help — what is your order number?"}]}'

Turn 3 - User wants escalation

bash
curl -X POST https://rylvo.com/api/v1/bot-run \
  -H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"botId":"bot_abc123","sessionId":"conv_001","message":"I want to talk to a real person."}'

Webhooks

Video coming soon

Click to preview (placeholder)

/api/v1/bot-run is synchronous — the turn result is returned in the response. For async notifications (escalations, stage changes, verification failures, tool results), register an Event Connector (Dashboard → Connectors, or via the MCP surface). Rylvo POSTs each subscribed event to your endpoint with the auth method and HMAC signing secret you configure on the connector. Subscribable event types are listed under Connectors.

Rate Limits

Video coming soon

Click to preview (placeholder)

POST /api/v1/bot-run is rate-limited per API key — 60 requests/minute by default, overridable per key (Dashboard → API Keys). Bot chat launched from the dashboard uses your own BYOK key and is NOT rate-limited by Rylvo; the platform AI features (Agent Evolution, Self-Improving Prompts, Red Team) also run on your own LLM key and are free — see pricing.

PlanMonthlyRequests / Month
Free$02,500
Lite$40100,000
Pro$100500,000
Team$400500,000
EnterpriseCustomCustom

Rate-limit headers on every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After on 429.

Billing & Usage

Video coming soon

Click to preview (placeholder)

Rylvo's billing model is simple: a monthly subscription plan covers platform capacity (bots, prompts, guardrails, KB sources, connectors, observability, SLA), and every AI feature runs on your own LLM key (BYOK) for free.

Your LLM key (BYOK)

Every AI feature — free

Bot chat, Workspace Architect, Agent Evolution, Self-Improving Prompts, Edge Case Engine, Red Team, broadcasts, embeddings — all run on your own provider key with zero markup. No Credits, no wallet, no monthly grant.

Prepaid wallet (optional)

Conversation-storage overage

A prepaid USD wallet covers only conversation-storage overage beyond your plan's included pool and wallet-funded retention add-ons. Dodo charges plan subscriptions separately. Top up from $5 at Dashboard → Billing. AI features never draw from it.

Subscription plans

PlanPriceHeadline limitsNotes
Free$01 bot · 7-day retentionBYOK bot chat + AI features, all free
Lite$403 bots · 14-day retention · 100k req/moSolo builder toolkit; no Advanced AI suite or Broadcasts
Pro$10025 bots · 30-day retention · 500k req/moAdvanced AI suite + Broadcasts
Team$40025 bots · 90-day retention · 500k req/moPro + you + 2 seats included ($40/extra seat) + dedicated support
EnterpriseCustomNegotiatedSSO, multi-region, priority routing

Advanced AI suite (Pro+): Agent Evolution, Self-Improving Prompts, Edge Case Engine, Red Team, Mission Control, Audit Log Export, Custom Policy Blocks. Broadcasts (Pro+): multi-channel outbound campaigns. Lite keeps Advanced Analytics, BYODB, Test Suites, MCP Hub, Webhooks, and Build Intelligence.

Token cost (BYOK — informational)

Bot turns run on your own provider key, so Rylvo does not bill per token — you pay your LLM provider directly. Each turn still records its token cost on the trace for observability and per-bot budget tracking:

text
trace.cost_usd = (input_tokens  × model.price_per_input_token)
               + (output_tokens × model.price_per_output_token)

Response headers on /api/v1/bot-run

HeaderMeaning
X-RateLimit-LimitThe key's per-minute request capacity
X-RateLimit-RemainingRequests left in the current rate-limit window
X-RateLimit-ResetUnix seconds when the window resets
Retry-AfterSeconds to wait before retrying (on 429)
Idempotent-Replaytrue when a stored idempotent response is replayed

Subscription state Dashboard-only

The billing subscription is a singleton document in Firestore. State transitions are driven by provider webhooks plus an hourly lifecycle cron. Only the downgraded status turns paid features off. Legacy plans are migrated to the canonical set at read-time.

Plan gates

  • requireFeature — feature locked or downgraded
  • requireQuota — usage ≥ plan cap
  • requireProviderKey — AI feature needs your own LLM key (BYOK, free)
  • requireStorageEventsBudget — storage pool exhausted
  • Returns PlanAccessError with upgradeTarget

Durable outbox architecture

  • API routes publish to a durable billingOutbox before Pub/Sub
  • Cloud Function billingOutboxWorker is the only account writer
  • Atomic transactions with idempotency + negative clamp
  • Daily billingReconciliation runs ledger integrity checks
  • Port-based design: all billing code depends on abstract interfaces

Per-bot budget cap

Each bot can carry a monthly spend cap. The gate sums the bot's observed (BYOK) cost from the canonical observability store via sum_cost_by_bot and pauses the bot when the cap is exceeded. Set caps in the bot settings.

Top-up & ledger Dashboard-only

The prepaid wallet (conversation-storage overage + retention add-ons) tops up in any amount from $5 to $10,000 at Dashboard → Billing. Every debit and credit is recorded in an immutable transactions ledger you can review and export from the Billing page (per-bot breakdown, model breakdown, date range, CSV).

Bot turns are BYOK/api/v1/bot-run does not draw from the prepaid wallet or return 402 for balance. The wallet only covers conversation-storage overage and wallet-funded retention add-ons; Dodo charges the plan separately.

Traces & Analytics

Video coming soon

Click to preview (placeholder)

Every decision is traced to a single canonical store — Postgres rylvo-obs (observability_traces observations scores). The legacy Firestore chatTraces dual-write was retired. You can replay any past turn, list every run in a session, and pull rollup analytics per company from the dashboard. The engine endpoints below back those views — they are internal (dashboard / MCP), not part of the public REST surface.

Internal endpoints (power the dashboard)

MethodPathDescription
GET/v1/traces/{trace_id}/replayFull replay payload for one trace (prompt stack, guardrails, tool calls, per-stage timings)
GET/v1/sessions/{session_id}/traces?limit=50List every run in a session, newest first
GET/v1/analytics/companies/{company_id}/overview?limit=500&top_n=10Escalation rate, avg latency, and token/cost rollups over the window
GET/v1/analytics/companies/{company_id}/runs?limit=100Recent individual runs for a company

Write paths

  • Engine: make_observability_ingest_hook (live channels)
  • Playground / Architect: adapters → POST /v1/observability/ingest
  • Rich operator context on metadata.turn_context
  • Hierarchical observations: spans, generations, tools, guardrails, retriever, agent

Read paths

  • Dashboard traces: canonical-backed projectors
  • Analytics: analytics-rows Postgres aggregates
  • Billing: sum_cost_by_bot (per-bot cap)
  • Audience / GDPR: canonical customer rows

Datetime trap: observability started_at columns are TIMESTAMP WITHOUT TIME ZONE. Always use naive UTC at the repo boundary; tz-aware datetimes are rejected by asyncpg.

Replay example

bash
curl https://rylvo.com/api/v1/traces/tr_def456/replay \
  -H "X-API-Key: YOUR_API_KEY"

AnalyticsDashboard-only

Video coming soon

Click to preview (placeholder)

Analytics are dashboard-driven and backed by the canonical observability store (Postgres), not raw Firestore scans. Manage from Dashboard → Analytics.

Analytics Rooms

Customizable dashboards with a widget grid. Create, save, and share rooms.

  • Default room auto-seeded on first visit
  • Bot/org-scoped date range per room
  • Viewer+ read, operator+ create/edit
  • Share via deep link

Widgets

Build from a catalog or pick from templates. Each widget has its own date range, bot scope, and visualization.

  • Request volume, token breakdown, latency
  • Model usage, cost, verification health
  • Escalation rate, recent traces, cost by bot/model/conversation
  • Template gallery for common dashboards

Data sources

Aggregates from the canonical trace store and the billing ledger.

  • Canonical traces via analytics-rows
  • Internal (dashboard / MCP): GET /v1/analytics/companies/{id}/overview
  • Internal (dashboard / MCP): GET /v1/analytics/companies/{id}/runs
  • Transactions feed for cost widgets

Date ranges

Preset or custom windows.

  • 1h, 24h, 7d, 30d, 90d, all time
  • Custom start/end day boundaries
  • Per-widget range overrides

Plan gating

Advanced Analytics is a Lite+ feature. Free orgs see a feature upgrade prompt; creating custom rooms and editing widgets requires operator+ on Lite or above.

Analytics RoomsDashboard-only

Named, customizable report dashboards with draggable chart cards, per-room filters (bot, env, date range), and collaborative sharing across the org. Powered by a Langfuse-style widget engine with 4 data sources, 19 metrics, 11 breakdowns, and 7 chart types. Manage from Dashboard → Analytics.

Room document

  • name (1-200 chars), createdBy (UID)
  • filters: botId (string | "all"), env (all|test|production), dateRange (1h|24h|7d|30d|90d|all)
  • charts: array of AnalyticsRoomChartConfig
  • Real-time Firestore listener for room list updates

Chart card config

  • type: 17 legacy types + custom_widget
  • position (0-indexed), title? override
  • width: half | full  |  height: compact | normal | tall
  • color: 8 colors (blue, emerald, amber, red, purple, pink, cyan, neutral)

Custom widget engine (Langfuse-style)

  • 4 views: traces, tool_calls, cost, runs
  • 19 metrics: count, unique_users, total_cost, p95_cost, total_tokens, avg_latency, pass_rate, error_rate, etc.
  • 11 breakdowns: none, bot, model, env, status, user, channel, source, day, hour, week
  • 7 chart types: line, bar_v, bar_h, big_number, pie, histogram, pivot
  • Filters: 9 columns, 7 operators (=, !=, in, not_in, contains, starts_with, >, <)
  • topN: limit for breakdown charts (default 10)

Default room seed (7 widgets)

  • Total Traces (big_number, blue) + Total Cost (big_number, emerald)
  • P95 Latency (big_number, amber) + Error Rate (big_number, red)
  • Traces Over Time (line, blue, breakdown: day)
  • Cost by Model (bar_h, emerald, breakdown: model, topN: 8)
  • Verification Distribution (pie, emerald, breakdown: status)

Dashboard templates (4 categories)

  • cost — spend tracking, token economics, billing (9 widgets)
  • performance — latency, throughput, bot + tool perf
  • usage — volume, users, channels, conversations
  • quality — verification pass/fail, errors, guardrails

Widget rendering pipeline

  • 1. Load data (traces, runs, transactions, bots)
  • 2. resolveWidget() converts legacy types to WidgetConfig
  • 3. applyRoomScope() injects room's botId filter
  • 4. WidgetRenderer dispatches to chart component
  • 5. computeWidget() / computeHistogram() aggregates
  • Legacy types with no equivalent render "retired" card

RBAC & plan limits

  • Read: any org member (viewer+) — rooms are org-wide
  • Create/Update/Delete: admin+ only
  • Feature: advancedAnalytics — Lite+ required
  • Free: feature upgrade prompt; analytics rooms need Lite+
  • No per-room count quota — unlimited within plan tier

Dashboard UI features

  • Room sidebar (real-time, rename/delete menu, + New Room)
  • Drag-and-drop chart reordering (position updated on drop)
  • Room customizer modal (name, filters, chart management)
  • Widget builder + templates + saved widget library
  • Date range override (per-user, localStorage)
  • Responsive: 2 columns desktop, 1 column mobile

API endpoints (Python FastAPI)

  • GET /v1/analytics-rooms/{company_id} — list all rooms (sorted by updatedAt desc)
  • GET /v1/analytics-rooms/{company_id}/{room_id} — get single (404 if not found)
  • POST /v1/analytics-rooms/{company_id} — create (201, { room_id, ok })
  • PUT /v1/analytics-rooms/{company_id}/{room_id} — update (partial)
  • DELETE /v1/analytics-rooms/{company_id}/{room_id} — delete (idempotent)

All require authorize_org. Create: name (1-200), filters?, charts?, created_by?. Events: analytics_room_created / updated / deleted.

Firestore shape

text
organizations/{orgId}/analyticsRooms/{roomId}
  |- id, name (1-200), createdBy
  |- filters: { botId, env, dateRange }
  |- charts: [{ id, type, position, title?,
  |              width?, height?, color?,
  |              customWidget?, savedWidgetId? }]
  +- createdAt, updatedAt: Timestamp

WidgetConfig (inline in customWidget):
  view: traces | tool_calls | cost | runs
  metric: count | total_cost | p95_latency | error_rate | ... (19 total)
  breakdown: none | bot | model | env | status | day | hour | week | ...
  chartType: line | bar_v | bar_h | big_number | pie | histogram | pivot
  filters: [{ id, column, op, value }]
  topN?: number (default 10)

SDKs & Libraries

Video coming soon

Click to preview (placeholder)

Python

python
import requests

resp = requests.post(
    "https://rylvo.com/api/v1/bot-run",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "botId": "bot_abc123def456",
        "sessionId": "sess_001",
        "message": "I can't access my account.",
    },
)

data = resp.json()
print(data["response"])
print(f"run {data['runId']} · {data['tokens']['total']} tokens")

JavaScript / Node.js

javascript
const res = await fetch("https://rylvo.com/api/v1/bot-run", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer YOUR_API_KEY",
  },
  body: JSON.stringify({
    botId: "bot_abc123def456",
    sessionId: "sess_001",
    message: "I can't access my account.",
  }),
});

const data = await res.json();
console.log(data.response);
console.log(data.runId, data.tokens.total, "tokens");

PromptsDashboard-only

Video coming soon

Click to preview (placeholder)

Prompts are the instructions your agents follow at runtime. Create them from the Prompts dashboard, assign them to bots, and optionally enable self-improving optimization.

Key Concepts

Agent Categories

9 categories: System, Response Composer, Stage Classifier, Action Selector, Escalation Classifier, Session Summarizer, Verifier, Retrieval, Custom

Placeholders

Use {{key}} syntax for dynamic values resolved at runtime (company name, tone, language, etc.)

Self-Improving

Toggle self-improving prompts to let the system generate, evaluate, and promote better prompt variants

Bot Assignment

Assign prompts to specific bots, or leave shared across all bots

Creating a Prompt

1

Choose an agent category

Select what type of agent this prompt controls (e.g., Response Composer, Verifier).

2

Write your prompt content

Use {{placeholder_key}} syntax for dynamic values. Templates are provided for each category.

3

Add placeholders

Define keys, labels, default values, and whether they're required. Use the template library for common ones.

4

Assign to a bot (optional)

Link the prompt to a specific bot, or leave it shared. Tag requests with prompt_id in the API.

5

Enable self-improving (optional)

Toggle auto-optimization in the prompt detail panel. Choose a strategy, metric, and frequency.

Placeholder Syntax

Placeholders use double-brace syntax: {{key}}. At runtime, they are resolved with values from the API request or defaults.

text
You are a support agent for {{company_name}}.

Greet the user as {{user_name}} in a {{tone}} tone.
Respond in {{language}}. Keep replies under {{max_length}} tokens.

{{context}}

Built-in Placeholder Templates

KeyDefaultDescription
{{company_name}}Acme CorpThe customer's company name
{{user_name}}JohnThe end user's display name
{{product_name}}Widget ProThe product being discussed
{{industry}}TechnologyThe customer's industry vertical
{{tone}}professionalResponse tone (formal, casual, empathetic)
{{language}}EnglishResponse language
{{max_length}}256Maximum response length in tokens
{{context}}-Additional context for the prompt

Version History

Every edit to a prompt creates a new version. Versions are tracked as manual or auto_optimized. You can view, compare, and promote any version to active from the prompt detail panel.

Prompt inheritance

Fork a prompt by setting a parentPromptId. The child inherits the base template and can add its own overrides while keeping the lineage tracked in version history. This is useful for per-bot or per-group variations of a shared prompt.

Dependency graph

The Prompts dashboard has an interactive SVG graph showing how prompts, bots, and agent groups relate to each other.

Graph layout

  • Bots on the left → Prompts in the center → Agent groups on the right
  • Edges show bot prompt assignments via prompt.botId
  • Org-level prompts (botId = null) connect to an org virtual node
  • Parent → child inheritance drawn within the prompt column
  • Group-shared prompts connect to their agent group

Interactions

  • Click a prompt → open prompt detail
  • Click a bot → open bot page
  • Hover any node → tooltip with full name and metadata
  • Scroll/pinch to zoom, drag to pan
  • Zoom/fit buttons for quick navigation

Prompt usage analytics

The prompt detail panel shows real-time usage metrics from the canonical trace store: trace volume, cost, latency, and A/B run history. Use this to decide which version is performing best before promoting it.

Resource VersioningDashboard-only

Video coming soon

Click to preview (placeholder)

Every runtime-affecting resource is versioned - not just prompts. Prompts, guardrails, connectors, evolution rules, and KB blueprints all track an immutable version history plus the source of each edit. You can diff, preview, promote, and rollback from the dashboard.

Edit sources

human

Manual edit by a teammate from the dashboard

architect

Applied via Workspace Architect (conversational setup / edit mode)

auto_generated

Produced by the Self-Improving optimizer or Agent Evolution rule generator

fix_proposal

Accepted from an Edge Case Engine fix proposal

system

Internal migration or default seed

red_team

Created in response to a red-team finding

What's versioned

Bots
Prompts
Guardrails
Connectors
Evolution Rules
KB Blueprints

What you can do from the dashboard

  • View the full edit history for any resource (who, what, when, why)
  • Diff any two versions side by side
  • Preview a version before promoting it to active
  • Promote any past version back to active in one click (rollback)
  • Filter history by source - manual, architect, auto-generated, etc.

Every runtime decision records which version was active when it ran, so you can correlate performance changes with specific edits - not just which resource was used, but which version of it.

Self-Improving PromptsDashboard-only

Video coming soon

Click to preview (placeholder)

Rylvo implements research-backed automatic prompt optimization. When enabled, the system continuously generates, evaluates, and selects better prompt variants using real performance data.

How It Works

1. Collect

The system gathers performance metrics from real API requests that use this prompt - success rate, verification pass rate, user satisfaction signals, and latency.

2. Generate

An optimizer LLM analyzes scored prompt history and proposes N improved variants. Each variant preserves all placeholders and the core intent, but tries a different improvement strategy.

3. Evaluate

Each variant is scored against your chosen metric. Scoring uses structural analysis, constraint quality, and historical performance patterns.

4. Select

The best-performing variant is identified. It must beat the current prompt by at least the improvement threshold (default 2%) to qualify.

5. Promote or Suggest

If auto-promote is on, the winner automatically becomes the active prompt. Otherwise, it appears in version history for manual review.

Optimization Strategies

Four research-backed strategies are available. Each uses a different approach to prompt improvement.

OPRO

Yang et al., 2023 - Google DeepMind

Optimization by PROmpting. Feeds scored (prompt, performance) history to an LLM which proposes improved variants. Core insight: LLMs can act as optimizers when given examples with scores.

Best when you have version history with diverse scores.

APE

Zhou et al., 2023 - University of Toronto

Automatic Prompt Engineer. Generates diverse candidates using multiple improvement strategies (explicit instructions, examples, restructuring, guardrails, simplification), evaluates each, selects the best.

Best for new prompts with little history.

DSPy Bootstrap

Khattab et al., 2023 - Stanford

Compiles the best-performing patterns from history into optimized few-shot demonstrations. Extracts what works and bakes it directly into the prompt as examples.

Best when high-scoring versions exist to learn from.

PromptBreeder

Fernando et al., 2023 - Google DeepMind

Self-referential evolution. Applies mutation-prompts (make concise, add constraints, restructure, add reasoning, add role-playing) to evolve the task-prompt. The mutations themselves can evolve.

Best for creative exploration of prompt space.

Configuration Options

OptionDefaultDescription
StrategyOPROWhich optimization algorithm to use
Evaluation MetricSuccess Ratesuccess_rate | verification_pass | user_satisfaction | latency
Run FrequencyDailyhourly | daily | weekly | manual
Auto-PromoteOffAutomatically promote winning variants to active
Improvement Threshold2%Minimum improvement required to qualify as a winner
Max Variants Per Run5Number of candidate variants generated per optimization run
Min Sample Size50Minimum API requests before first optimization run

Enabling Self-Improvement

1

Open the prompt detail panel

Click on any prompt from the Prompts page to view its details.

2

Expand 'Self-Improving Prompts'

Click the panel header to expand the optimization settings.

3

Toggle 'Enable Auto-Improvement'

This activates the optimization loop for this prompt.

4

Choose your strategy

Select OPRO, APE, DSPy Bootstrap, or PromptBreeder depending on your needs.

5

Set your metric and frequency

Choose what to optimize for and how often to run.

6

Decide on auto-promote

Turn on to automatically deploy winning variants, or leave off for manual review.

Prompt API Usage

Video coming soon

Click to preview (placeholder)

Prompts are assigned to a bot in the dashboard (or via the MCP surface), versioned, and resolved automatically when the bot runs. /api/v1/bot-run does not accept a prompt_id orprompt_overrides — to change which prompt a bot uses, update its prompt assignment.

How prompts resolve at runtime

1

Resolve

The bot's assigned prompts load their active versions.

2

Substitute

All {{placeholder}} values are filled from bot/org defaults.

3

Inject

The resolved text becomes part of the system prompt for the turn.

4

Track

The trace records which prompt versions were active (drives analytics + Self-Improving Prompts).

Change or A/B a prompt

  • Update a bot's prompt assignment in Dashboard → Bots (or the Prompts page).
  • Use Self-Improving Prompts to A/B variants and auto-promote winners.
  • Every turn's trace records the resolved prompt version for analytics and optimization.

Edge Case Engine Dashboard-only

Automatically detect, classify, and fix bot failure modes

Video coming soon

Click to preview (placeholder)

The Edge Case Engine is a closed-loop system that monitors your bot traces, automatically discovers failure patterns, classifies them into 12 categories, and suggests fixes. It uses a 5-stage pipeline: Observe → Detect → Classify → Fix → Verify. Manage from Dashboard → Edge Cases. Pro+ plan feature.

3 detection mechanisms

  1. Passive rule-based — scans trace signals: verification_failed, missed_escalation_signal, execution_blocked, has_errors, no_evidence, safety_override
  2. LLM-based scanPOST /v1/ai/{org}/edge-case-scan sends trace summaries to an LLM (default openai/gpt-4.1-mini). 10 scans/min per org, runs free on your own LLM key (BYOK)
  3. Red Team adversarial testing — 13 built-in strategies with static + LLM-generated inputs, runs synchronously on Cloud Run with per-result checkpointing

12 edge case categories

  • Hallucination — response not grounded in KB evidence
  • Policy Violation — broke company/workflow policy
  • Missed Escalation — should have escalated but didn't
  • Wrong Tool — selected incorrect tool
  • Missing Tool Call — should have called tool but skipped
  • Wrong Stage — misidentified workflow stage
  • Stale Memory — used outdated session facts
  • Unknown Intent — intent not covered by workflow
  • Tone Violation — doesn't match brand guidelines
  • PII Leak — exposed sensitive personal info
  • Loop Detected — bot stuck in repetitive pattern
  • Custom — user-defined category

Status lifecycle & severity

5 severity levels (critical=5, high=4, medium=3, low=2, info=1) sorted by weight.

Status flow:

open → acknowledged → fix_proposed → fix_applied → resolved

Alternative: dismissed (false positive) or deleted (permanent removal).

Fix proposals

6 fix types with confidence scores (0.0–1.0) and impact ratings:

  • guardrail_add — auto-generate a new guardrail
  • prompt_edit — proposed before/after prompt text
  • threshold_adjust — change a parameter value
  • escalation_rule — add or modify escalation logic
  • workflow_expand — extend workflow coverage
  • custom — manual steps

Lifecycle: proposed → applied / rejected / reverted

13 Red Team strategies

  • policy_probe, hallucination_bait, tool_confusion, escalation_boundary
  • injection_attempt, context_overflow, mcp_abuse, pii_extraction
  • multi_turn_coercion, jailbreak_dan, function_leak, billing_bypass, kb_poisoning

Tuning: 240s run budget, 45s per-turn timeout, 6 concurrent, 25 tests/strategy max. Guardrails fire as in production; billing/governance skipped (synthetic runs free).

Deduplication & RBAC

Deterministic ID from SHA256(botId::category::title). Duplicate scans increment occurrenceCount.

  • edgeCases.view — viewer+
  • edgeCases.manage — admin+
  • guardrails.view — viewer+
  • guardrails.manage — admin+
  • redTeam.view — viewer+
  • redTeam.run — admin+

4 dashboard tabs

Edge Cases

Filter by status/category/severity/bot, search, create/edit/resolve/dismiss/delete, assign

Red Team

Create runs, select strategies, configure, execute synchronously, view results, create edge cases from failures

Guardrails

View/create/edit/toggle/delete, conditions, actions, priorities, trigger stats, version history

Fix Proposals

View/create/apply/reject/revert with regression test linking

Firestore shape

text
organizations/{orgId}/edgeCases/{edgeCaseId}
  |- title, description, category, severity, status
  |- source: trace_mining / red_team / manual / guardrail_trigger
  |- botId, botName, traceIds[], occurrenceCount
  |- sampleInput, sampleOutput, expectedBehavior
  |- fixProposalId, guardrailId, tags, assignedTo
  +- firstSeenAt, lastSeenAt, resolvedBy, resolvedAt

organizations/{orgId}/fixProposals/{proposalId}
  |- edgeCaseId, fixType, title, description
  |- impact, confidence, status
  |- details: { guardrailConfig?, promptChanges?, thresholdChanges?, customSteps? }
  |- regressionTestIds[]
  +- appliedBy, appliedAt, createdBy, createdAt

organizations/{orgId}/redTeamRuns/{runId}
  |- name, botId, strategies[], status
  |- config: { testsPerStrategy, maxConcurrent, timeoutMs, ... }
  |- results: { totalTests, passedTests, failedTests, edgeCasesFound }
  +- edgeCaseIds[], startedAt, completedAt

organizations/{orgId}/redTeamRuns/{runId}/results/{resultId}
  |- strategy, testInput, botResponse, passed
  |- failureCategory, failureReason, severity, edgeCaseId
  |- guardrailResults[], toolCalls[], traceId, latencyMs
  +- model, tokens, createdAt

Guardrails Dashboard-only

Programmable safety rules that run on every bot response

Video coming soon

Click to preview (placeholder)

Guardrails are runtime rules that check bot inputs/outputs and take action when conditions are met. They can be created manually or auto-suggested by the Edge Case Engine when it detects failure patterns.

Guardrail Types

  • Input Filter - Validates user input before processing
  • Output Filter - Validates bot response before delivery
  • Fact Check - Verifies claims against knowledge base
  • Policy Check - Ensures compliance with company policies
  • PII Detection - Detects and redacts sensitive data
  • Tone Check - Validates tone matches brand
  • Loop Breaker - Breaks repetitive conversation patterns
  • Escalation Override - Forces escalation on conditions
  • Tool Gate - Controls tool access in specific contexts

Actions

  • Block - Prevent response, return fallback message
  • Warn - Flag for review but allow response through
  • Rewrite - Auto-modify response to comply
  • Escalate - Trigger human handoff immediately
  • Log - Record for monitoring, no action taken

Each guardrail has conditions (field + operator + value) and a fallback message shown when triggered.

Conditions & evaluation

A guardrail condition is built from a trace/runtime field, an operator, and a value. Examples:verification.status == fail,output.pii_detected == true, orloop.count > 3. Guardrails are evaluated in priority order; block and escalate actions short-circuit the rest.

Lifecycle & audit

Versioned

Every edit is a new version tagged manual, fix_proposal, red_team, system, or auto_generated

Experiment-gated

Risky changes can be deployed behind a flag before full promotion

Trace audit

Every guardrail decision is recorded in the trace observations

Guardrail Audit

Scheduled automation task reviews effectiveness and suggests tuning

Red Team Testing Dashboard-only

Proactively discover vulnerabilities with adversarial tests

Video coming soon

Click to preview (placeholder)

Red Team runs generate adversarial inputs against your bot to find failures before users do. Select a bot, choose attack strategies, and the engine generates test cases automatically. Results are converted into edge cases with severity levels.

13 built-in attack strategies

  • Policy Probe - Tests policy boundary conditions with edge-case inputs
  • Hallucination Bait - Questions with no KB answer to test grounding
  • Tool Confusion / MCP Abuse - Inputs that trigger wrong or unsafe tool selection
  • Escalation Boundary - Tests the edge of escalation thresholds
  • Injection Attempt / Jailbreak (DAN) - Prompt injection and jailbreak attempts
  • Context Overflow - Long conversations that stress memory limits
  • PII Extraction / Function Leak - Attempts to surface private data or system internals
  • Multi-Turn Coercion, Billing Bypass, KB Poisoning

Automation & Reports Dashboard-only

Schedule scans, connect channels, and generate automated reports

Video coming soon

Click to preview (placeholder)

The Automation page at /dashboard/automation lets you set up recurring jobs, notification channels, and automated analytics reports. Everything runs on your chosen schedule — no manual intervention needed after setup. 4 tabs: Overview, Scheduled Tasks, Reports, Settings.

10 scheduled task templates

Cron jobs that run automatically via Cloud Function (every 5 min):

  • trace_scan — auto-scan traces for edge cases
  • red_team_run — periodic adversarial testing
  • guardrail_audit — review guardrail effectiveness
  • complaint_watch — monitor complaint-risk conversations
  • dev_review — review failed runs & patterns
  • conversation_quality — sample conversations for quality review
  • cost_usage_watch — review recorded token spend & cost
  • kb_gap_analysis — find knowledge gaps in trace outcomes
  • connector_health — audit connector/MCP health
  • test_suite_run — run a test suite when the engine integration is configured

9 cadences: hourly → monthly. Rich schedule config (interval/weekly/monthly with IANA timezone). Max 25 tasks/invocation, 3 concurrent/org, 90s timeout. Selected workers can add optional BYOK analysis; reports are configured separately.

5 notification channels

Where alerts and reports get sent. Delivery via durable outbox with exponential backoff retry:

  • Slack — webhook integration
  • Email — direct to team inboxes
  • Discord — webhook integration
  • Microsoft Teams — webhook integration
  • Custom Webhook — any HTTPS endpoint

Events: edge_case.created, red_team.completed, report.generated, etc. Scope: global or linked to specific bots.

13 report sections

  • executive_summary, edge_cases_overview, edge_cases_by_category/severity
  • guardrail_performance, red_team_results, fix_proposals_summary
  • improvements_timeline, bot_comparison, complaints_and_escalations
  • developer_followups, automation_coverage, recommendations (AI)

13 data sources, 5 working output formats (html_email, slack_blocks, teams_card, discord_embed, markdown). 5 recipient filters (all_members, admins_only, owners_only, operators_only, custom). Optional LLM formatting with custom model, system prompt, temperature, max tokens. The reserved PDF option does not currently generate a PDF file.

RBAC & plan limits

  • automation.view — viewer+
  • automation.generate_report — operator+
  • automation.manage_tasks — admin+
  • automation.manage_channels — admin+
  • automation.manage_reports — admin+

Free: 0 / Lite: 10ch, 10tasks, 2configs, 25reports/mo / Pro: 25, 25, 5, 50 / Team: 25, 25, 5, 100 / Enterprise: unlimited.

Firestore shape

text
organizations/{orgId}/scheduledTasks/{taskId}
  |- name, type, cadence, enabled, botId
  |- config: { traceLimit?, redTeamStrategies?, aiConfig?, scheduleConfig? }
  |- notificationChannelIds[], lastRunAt, lastRunStatus, nextRunAt
  +- runCount, failCount

organizations/{orgId}/notificationChannels/{channelId}
  |- name, type, enabled, config, events[], botIds[], scope
  +- lastUsedAt, lastStatus

organizations/{orgId}/reportConfigs/{configId}
  |- name, cadence, sections[], dataSources[], filters
  |- recipientFilter, customRecipientEmails[], notificationChannelIds[]
  |- aiConfig?, outputFormats[], scheduleConfig?
  +- lastGeneratedAt, nextGenerateAt

organizations/{orgId}/reports/{reportId}
  |- configId, status, sections[], data, distributedTo[]
  |- llmFormattedContent?, pdfUrl?, tokenUsage?
  +- periodStart, periodEnd, generationDurationMs

organizations/{orgId}/scheduledTaskRuns/{runId}
  |- taskId, taskType, status, summary, details
  +- startedAt, completedAt

Agent Evolution Dashboard-only

Closed-loop system that learns from real traffic and safely rewrites the agent

Video coming soon

Click to preview (placeholder)

While the Edge Case Engine catches individual failures, Agent Evolution looks at patterns across runs and updates the agent itself. It runs a six-phase loop - observe, detect, infer, generate, inject, verify - and writes new rules, prompt variants, or guardrail tweaks into versioned resources. Every change is reversible.

Phases

  1. Observe - sample recent traces for the bot
  2. Detect - cluster failures & near-misses
  3. Infer - reviewer LLM names the pattern + root cause
  4. Generate - draft a rule, prompt edit, or guardrail
  5. Inject - attach to the bot behind an experiment flag
  6. Verify - measure effect; auto-promote, keep, or roll back

What gets written

  • Evolution rules (injected into the system prompt conditionally)
  • Prompt version updates (marked auto_generated)
  • Guardrail tweaks (threshold, condition, fallback text)
  • Reviewer insights (narrative explanations for the team)
  • Effectiveness scores per rule (controls auto-promote)

Dashboard tabs

Overview

Bot health score, recent improvements, active experiments, and run summary

Insights

Detected patterns, trend cards, and recommended actions

Failures

Failing traces grouped by root cause; promote to Edge Cases

Rulebook

Active and pending evolution rules with experiment flags and rollback

Improve

Run a manual improvement cycle on selected traces or prompts

Dialectic model

Reviewer model settings for critique and refinement

Settings

Enable/disable modules, trace filters, cadence, and notifications

Run logs

History of every evolution run with status, cost, and artifacts

Self-Improving Prompts

A focused Agent Evolution workflow for prompts. It analyzes prompt-version performance from live traces, generates proposed revisions with explanations, evaluates them against held-out traces, and publishes winning versions as new prompt versions (not live until you promote them). It runs on your own LLM key (BYOK) and is free.

Every proposed change is gated behind an experiment flag with measurable success criteria. Anything that doesn't move the needle is rolled back automatically; anything that does becomes a permanent versioned change you can inspect, fork, or revert.

Agent Skills Dashboard-only

Reusable capability packages that extend your bots

Video coming soon

Click to preview (placeholder)

Agent Skills are modular, reusable, versioned, and measurable capability packages that can be attached to any bot in your organization. When a skill is active, its system instructions are conditionally injected into the bot's system prompt based on trigger conditions — natural-language descriptions matched against the conversation. Skills can be created manually, auto-promoted from evolution rules, imported, or suggested by the reviewer engine. Manage from Dashboard → Agent Skills.

4 skill types (1 runtime-supported)

  • prompt_template — reusable instruction pattern injected when triggered ✓ runtime
  • tool_chain — ordered sequence of tool calls with parameter mapping future
  • code_snippet — programmatic capability executed in one turn future
  • workflow — multi-step procedure with conditional branches future

Trigger matching (deterministic, no LLM)

score = 0.4 × (matched_keywords / total) + 0.6 × (trigger_hits / total)

  • Activate when score ≥ 0.25 (default), cap at 8 skills/turn
  • Keywords extracted from trigger conditions (max 30)
  • Optional semantic matching: embedding cosine similarity at weight 0.3
  • Always-on skills (no triggers) activate at score 1.0, sort first

6 skill sources

  • manual — operator creates via dashboard
  • generated_from_rules — auto-promoted from rule clusters (opt-in)
  • imported — imported from another bot/org
  • architect — created by Workspace Architect
  • reviewer_suggested — suggested by reviewer engine
  • prebuilt — installed from the Skill Library catalog

Runtime injection & safety

  • Matched skills added to BotPromptInputs.skill_instructions
  • Merged into system prompt under "## ACTIVATED SKILLS"
  • Safety scan for prompt injection patterns before rendering (warns, never blocks)
  • Usage recorded post-turn via SkillUsageEventDoc
  • Confidence: score × 0.9 (success) or × 0.2 (failure) minus guardrail penalty

Scoping & lifecycle

  • Bot-scoped — botId = specific bot, only that bot loads it
  • Org-level — botId = null, every bot loads it
  • Runtime loads bot-scoped first (priority), then org-level
  • Status: draft → active → disabled → archived
  • Any status can be rolled back to a previous version

Versioning, rollback & auto-promotion

  • Every change creates a SkillVersionDoc (7 change types)
  • Rollback restores instructions + status + triggers, resets counters
  • Auto-promotion: opt-in per-bot, cluster size ≥ 3 (configurable 2–10)
  • Promoted skills start as draft (requires operator review)
  • Effectiveness: usageCount, successCount, avgConfidence, lastUsedAt

RBAC & plan limits

  • evolution.edit_config — admin+ (manage skills)
  • Viewers can see but not create/edit/delete/status/rollback

Free: 3 / Lite: 5 / Pro: 25 / Team: 25 / Enterprise: unlimited

Dashboard tabs

  • Skills — list with search, status filter, bot selector, expandable cards with metrics
  • Library — browse 50 prebuilt skills across 15 categories, search/filter, one-click install
  • Settings — enable Skills Layer, auto-promote toggle, cluster size slider

Enable the Skills Layer toggle in Skills → Settings to allow auto-promotion.

Skill Library (Prebuilt Catalog)

A curated catalog of 50 ready-made skills across 15 categories covering both single-agent and multi-agent bot templates (Customer Support, Sales, Compliance, Data Collection, Conversation Design, Knowledge & Escalation, E-commerce, Healthcare, HR & Internal, Finance & Insurance, IT & Technical, Multi-Agent, Content & Marketing, Real Estate & Education, Utilities). Browse in the Skills → Library tab, click "Add" to install a fork into your org.

Fork model

  • Installing creates a copy you own (source: "prebuilt")
  • No auto-updates — edit freely after install
  • Starts as draft — review before activating
  • Org-level by default (botId = null, all bots)

Guardrails

  • Duplicate prevention — can't install the same catalog skill twice
  • Quota — installs count against the org's max_skills plan limit
  • Batch install — "Install All" button respects quota
  • Installed skills are normal skills — edit, clone, delete, rollback

Firestore shape

text
organizations/{orgId}/agentSkills/{skillId}
  |- skillId, orgId, botId (null = org-level), botName
  |- name, slug, description, skillType, source, status
  |- systemInstructions (max 8000), triggerConditions[], triggerKeywords[]
  |- requiredTools[], toolChain[], generatedFromRuleIds[]
  |- version, previousVersionId
  |- usageCount, successCount, failureCount, avgConfidence, lastUsedAt
  +- tags, metadata, createdBy, createdAt, updatedAt

organizations/{orgId}/agentSkills/{skillId}/versions/{versionId}
  |- version, changeType, previousInstructions, newInstructions
  |- previousStatus, newStatus, changeReason
  +- usageCountAtChange, changedBy, createdAt

organizations/{orgId}/agentSkills/{skillId}/usageEvents/{eventId}
  |- eventId (turnId:skillId), turnId, outcome, confidence, score
  +- createdAt, aggregated

Mission Control Dashboard-only

Live human oversight for every production conversation

Video coming soon

Click to preview (placeholder)

Mission Control is the live operations surface at /dashboard/mission-control. Operators see every active conversation in real time, can intervene at any level, and the system keeps an immutable audit trail that satisfies EU AI Act Article 14 (human oversight) requirements. Real-time via Firestore onSnapshot listeners (no WebSocket/SSE).

6 intervention types

  • Whisper — inject system instruction (customer doesn't see), injected into prompt via fetch_active_whispers
  • Takeover — operator becomes respondent, bot paused
  • Pause — freeze the bot, customer sees wait message
  • Kill — terminate session immediately, send fallback (irreversible)
  • Resume — resume a paused/taken-over conversation
  • Flag — flag for review without interrupting

Blocking interventions (pause/takeover/kill) trigger the Python engine pre-gate (evaluate_mission_control_gate) which blocks the bot from responding. Rationale required for pause/takeover/kill.

Approvals & SLAs

3 urgency tiers with timeouts:

  • immediate — 15s timeout (low-risk informational)
  • standard — 120s (data access, PII-related)
  • extended — 900s (financial, legal, compliance)

Lifecycle: pending → approved / denied / expired / auto_denied. ExpiresAt computed server-side. Decisions attached to the trace.

11 alert triggers

Per-conversation (inline after each bot turn):

  • risk_score_above, guardrail_blocked, guardrail_triggered_n_times
  • conversation_duration_above, no_operator_monitoring, approval_queue_size_above
  • pattern_detected (regex), budget_threshold_crossed

Server-tick (scheduled job every minute):

  • bot_turn_failed_n_in_window, byok_key_unhealthy, wallet_balance_below

Severity: critical/high/medium/low. Cooldown (default 300s). Suppression: snooze + quiet hours (IANA timezone). Alert instances idempotent via groupKey (ruleId:conversationId).

Operator presence

  • 4 states: online / away / busy / offline
  • 30s heartbeat, stale after 3 min → auto-offline
  • Shift tracking: shiftStart / shiftEnd
  • Capacity: maxConcurrentMonitors (default 5)
  • Stats: totalInterventions, totalApprovals, avgResponseTimeMs
  • Cross-tab localStorage leader election for sweep

Risk scoring & compliance

  • Risk score 0–100, 5 levels: critical / high / medium / low / minimal
  • Risk signals: source, name, score, details, timestamp
  • Compliance modes: standard / eu_ai_act / soc2 / hipaa / custom
  • Append-only oversight audit log (every operator action)
  • Compliance export (CSV/JSON) with signed URLs
  • Retention: plan-based (7d/30d/90d/unlimited), overridable

RBAC

  • missionControl.view — viewer+
  • missionControl.intervene — operator+
  • missionControl.resolve_approval — operator+
  • missionControl.ack_alert — operator+
  • missionControl.manage_rules — admin+
  • missionControl.edit_settings — admin+
  • missionControl.delete_conversation — admin+

7 dashboard tabs

Live Feed

Real-time conversation list (max 100), detail panel with transcript + interventions + risk signals, quick actions, assign operator

Alerts

Active (bulk ack, snooze, resolve), snoozed, history, rules CRUD with evidence chips

Channels

Per-channel connectivity health, error rates, retry queue

Analytics

KPIs, charts by type/severity/status, operator leaderboard (7d/30d/90d)

Audit Log

Append-only event log, filters, compliance export (CSV/JSON)

Operators

Team roster with status, role, shift indicator, stats

Settings

Enable/disable, approval urgency, auto-monitor, compliance mode, retention, messages

API endpoints

  • POST /api/mission-control/intervene — create intervention (operator+)
  • POST /api/mission-control/revert — revert intervention (operator+)
  • POST /api/mission-control/alert-rules — create/update alert rule (admin+)
  • POST /api/mission-control/dispatch — push operator message to channel (operator+)
  • POST /api/mission-control/delete-conversation — hard-delete + cascade (admin+)

Firestore shape

text
organizations/{orgId}/liveConversations/{conversationId}
  |- botId, conversationId, status, riskScore, riskLevel
  |- riskSignals[], messageCount, durationMs, lastMessagePreview
  |- guardrailTriggeredCount, assignedOperatorId, monitoringOperatorIds[]
  |- channel, isProduction, tags, flaggedReason
  +- startedAt, lastMessageAt, endedAt

organizations/{orgId}/operators/{uid}
  |- status: online/away/busy/offline, maxConcurrentMonitors
  |- totalInterventions, totalApprovals, avgResponseTimeMs
  +- shiftStart, shiftEnd, lastActiveAt

organizations/{orgId}/interventions/{interventionId}
  |- type, operatorId, rationale, payload, outcome
  |- riskScoreAtTime, messageCountAtTime, idempotencyKey
  +- createdAt, resolvedAt

organizations/{orgId}/approvalRequests/{requestId}
  |- actionType, urgency, timeoutSeconds, status
  |- riskScoreAtTime, riskSignals[], conversationContext
  +- requestedAt, expiresAt, resolvedBy, resolvedAt

organizations/{orgId}/alertRules/{ruleId}
  |- trigger, severity, config, botId, notificationChannelIds[]
  |- cooldownSeconds, snoozedUntil, quietHours
  +- lastTriggeredAt, triggerCount

organizations/{orgId}/alertInstances/{alertId}
  |- ruleId, status, groupKey, fireCount, evidence[]
  +- firstFiredAt, lastFiredAt, acknowledgedAt, resolvedAt

organizations/{orgId}/oversightAudit/{entryId}
  |- eventType, operatorId, conversationId, botId
  +- details, riskScoreAtTime, timestamp

organizations/{orgId}/missionControl/config
  |- enabled, defaultApprovalUrgency, autoMonitorHighRisk
  |- complianceMode, auditRetentionDays, maxOperatorsPerConversation
  +- pauseMessage, takeoverMessage

Flows

Guided/strict conversation graphs that steer a bot through a sequence of steps — each step carries a goal (injected into the system prompt), data to collect, an allowed-tools scope, and transitions the engine advances as the conversation progresses. Flows are org-scoped, attached to a bot via bot.flowId or an agent group via group.flowId, evaluated per turn by the engine, and not part of the /api/v1/bot-run request or response. Gated by the flows plan feature (Pro+).

Flow document (FlowDoc)

  • name, description
  • mode: guided | strict
  • start_step_id: entry step (must be defined)
  • steps: FlowStep[]
  • enabled: boolean

Step structure (FlowStep)

  • id (unique), name
  • goal: injected into the system prompt for this step
  • collect: data fields to slot-fill before advancing
  • allowed_tools: string[] | "all" | "none"
  • allowed_agent_id: sub-agent for this step (groups)
  • terminal: ends the flow

Transition

  • to: target step id
  • kind: always | data_complete | intent | condition | tool_result
  • intent (kind=intent), expr (kind=condition)
  • tool (kind=tool_result): fires when it ran successfully this turn

Runtime state (FlowState)

  • flow_id, current_step_id
  • visited: step ids seen this session
  • done: flow reached a terminal step
  • One FlowState per session, advanced each turn

Graph validation

  • At least one step; step ids unique
  • start_step_id must be a defined step
  • Every transition target must exist
  • intent / condition / tool_result carry their field
  • All steps reachable from start (BFS)

Bot & group attachment

  • Bot: bots/{botId}.flowId
  • Group: agentGroups/{groupId}.flowId
  • Attach via /attach so the field is not dropped by the model write-path
  • Engine reads flowId off the raw doc per turn

Runtime execution (per turn)

  • 1. Read flowId + load the flow (flow_loader.load_flow)
  • 2. Resolve the session's FlowState (start on first turn)
  • 3. Inject the step goal; scope allowed_tools; list collect
  • 4. advance() evaluates transitions and moves to the next step
  • 5. A terminal step marks the flow done

Versioning

  • Each update snapshots the prior revision
  • GET .../versions lists prior revisions
  • Restore = load a revision into the editor and Save
  • Best-effort — a snapshot failure never blocks the save

Plan gating (flows feature)

  • flows is a Pro+ feature gate (require_feature)
  • Not a per-count quota — no max_flows limit
  • Free / Lite: not available
  • Pro / Team / Enterprise: available

RBAC

  • Read: any org member
  • Create / Update / Delete / Attach: requires the flows feature
  • All endpoints run authorize_org (org membership)
  • Written only via engineFetch — never Firestore directly

API endpoints (Python FastAPI, internal)

  • POST /v1/flows/{company_id} — create flow (validates the graph)
  • GET /v1/flows/{company_id} — list ({ flows, total })
  • GET /v1/flows/{company_id}/{flow_id} — get single
  • PUT /v1/flows/{company_id}/{flow_id} — update (snapshots prior revision)
  • GET /v1/flows/{company_id}/{flow_id}/versions — list revisions
  • DELETE /v1/flows/{company_id}/{flow_id} — delete
  • POST /v1/flows/{company_id}/{flow_id}/attach — bind to bot or group
  • POST /v1/flows/{company_id}/detach — unbind from bot or group

All endpoints require authorize_org(request, company_id). Mutations require the flows feature (Pro+). Internal (dashboard-managed), not public REST.

Firestore shape

text
organizations/{orgId}/flows/{flowId}
  |- name, description
  |- mode: guided | strict
  |- start_step_id: string
  |- steps: [{ id, name, goal, collect[],
  |            allowed_tools, allowed_agent_id?,
  |            transitions: [{ to, kind, intent?, expr?, tool? }],
  |            terminal }]
  +- enabled: bool

organizations/{orgId}/bots/{botId}
  +- flowId: string | null

organizations/{orgId}/agentGroups/{groupId}
  +- flowId: string | null

DatasetsDashboard-only

Video coming soon

Click to preview (placeholder)

Datasets are curated collections of traces or end-user profiles for analysis, review, and team collaboration. Manage from Dashboard → Datasets.

Trace datasets

Pin canonical traces into a shared collection for debugging, regression testing, or qualitative review.

  • Pin from the Traces page or browse unpinned traces inside the dataset
  • Trace source: canonical, legacy, or dashboard
  • Inline trace replay + detail drawer
  • Shared comment thread with the Traces page
  • Soft-archive or hard-delete a dataset

People datasets

Group end-user profiles for segmentation, cohort review, or export.

  • Collect end-user profiles from Audience
  • Read-only detail view per profile
  • Reuse for broadcast audience targeting

Retention & quotas

PlanMax datasetsMax preserved traces
Free150
Lite105,000
Pro5025,000
Team5025,000
Enterprise

Permissions

  • Viewer+ — view datasets
  • Operator+ — create, edit, archive, pin/unpin traces
  • Admin — hard-delete dataset and all pinned items

Deleting a dataset removes the pins and the dataset doc. Pinned traces themselves are then subject to the normal org retention policy, so delete carefully.

Email DeliveryDashboard-only

Video coming soon

Click to preview (placeholder)

Rylvo handles its own transactional emails (welcome, team invites, scheduled reports, operational alerts) automatically. Templates are branded and theme-aware - there is nothing for you to set up.

To send your own emails on workflow events (e.g. notify a human when a session escalates), use an Event Connector or add an Email channel in Automation.

OAuth ClientsDashboard-only

Server-to-server OAuth 2.1 authorization server for MCP (Model Context Protocol) client authentication. Enables AI editors (Claude Code, Cursor, Windsurf, Zed) to authenticate with Rylvo and access the MCP tools API. Implements RFC 6749, 7636 (PKCE), 7591 (DCR), 7009 (revocation), 7662 (introspection), 8414 (metadata), 8707 (audience binding), 9728 (protected resource). Manage from Dashboard → Integrations.

Client registry document

  • clientId: "client_" + 12 random base64url chars
  • clientName (1-200), redirectUris[]
  • grantTypes: authorization_code, refresh_token
  • tokenEndpointAuthMethod: none | client_secret_post
  • clientSecretHash: SHA-256 hash (null for public clients)
  • Global collection (not org-scoped); plaintext secret never persisted

Client types & secret management

  • Public (PKCE-only): no secret issued, authMethod="none"
  • Confidential: 32-byte random base64url secret, authMethod="client_secret_post"
  • Secret returned ONCE at DCR response; only SHA-256 hash stored
  • Constant-time comparison (timingSafeEqual) prevents timing attacks

Grant types & token flow

  • Authorization Code (PKCE required): authorize → consent → token exchange
  • Refresh Token Rotation: atomic Firestore transaction (revoke old, issue new)
  • Org ID embedded in self-describing token: rfr_{base64url(orgId)}~{random}
  • CSRF protection: HMAC-SHA256 on consent form

Token lifetimes

  • Access token: 24 hours (RS256 JWT, aud-bound to https://rylvo.com/mcp)
  • Refresh token: 30 days sliding window (rotated on each use)
  • Refresh token family ceiling: 365 days absolute
  • Authorization code: 10 minutes

Scopes (3)

  • rylvo:read — view bots, analytics, conversations, KB
  • rylvo:write — create/update bots, prompts, knowledge, settings
  • rylvo:admin — manage org settings, members, integrations
  • Default: "rylvo:read rylvo:write"

Security features

  • RS256 JWT signing with key versioning (OAUTH_JWT_PRIVATE_KEY_PEM in prod)
  • PKCE S256 required (plain rejected in production)
  • Audience binding (RFC 8707) prevents confused-deputy attacks
  • CSP: nonce-locked, HSTS, X-Frame-Options DENY
  • Revoked JTI tracking: Firestore + in-memory LRU (max 10k)

Caching strategy

  • Client metadata: 5 min TTL, max 5,000 entries
  • JWT validation: negative (5s spam) + positive (2s revocation-aware), max 10k each
  • Firebase session: 30s TTL, max 2,000 entries
  • JTI revocation: in-memory LRU, max 10,000 entries

RBAC & plan limits

  • oauthClients.view — viewer+ (all members)
  • oauthClients.revoke — admin+ (admin or owner)
  • OAuth collections are server-only (Admin SDK writes)
  • NOT plan-gated — available to all plans (free → enterprise)
  • Composite index required on oauthRefreshTokens.clientId

API endpoints (OAuth 2.1 standard)

  • GET /.well-known/oauth-authorization-server — RFC 8414 metadata (30/min per IP)
  • GET /.well-known/jwks.json — RFC 7517 JWKS
  • GET /oauth/authorize — authorization endpoint (consent HTML, 30/min per IP)
  • POST /oauth/authorize/consent — consent submission (15/min per user)
  • POST /oauth/token — token endpoint (10/min per client_id)
  • POST /oauth/register — Dynamic Client Registration (5/min per IP)
  • POST /oauth/revoke — token revocation (10/min per IP)
  • POST /oauth/introspect — token introspection (30/min per IP)

Dashboard: GET /api/v1/oauth-clients/{orgId} (list), POST /api/v1/oauth-clients/{orgId}/{clientId}/revoke (admin+).

Firestore shape

text
oauthClients/{clientId}  (global, not org-scoped)
  |- clientId, clientName (1-200), redirectUris[]
  |- grantTypes[], tokenEndpointAuthMethod, scope
  |- clientSecretHash (SHA-256 | null for public)
  +- createdAt: number (unix seconds)

oauthAuthCodes/{codeHash}  (keyed by SHA-256 hash)
  |- orgId, userId, clientId, redirectUri, scope
  |- codeChallenge, codeChallengeMethod
  |- expiresAt (10 min TTL), used, consumedAt?
  +- createdAt

organizations/{orgId}/oauthRefreshTokens/{tokenHash}
  |- userId, clientId, scope
  |- expiresAt (30d sliding), familyMaxExpiresAt (365d ceiling)
  |- revoked, revokedAt?
  +- createdAt
  Token format: rfr_{base64url(orgId)}~{random}

organizations/{orgId}/oauthRevokedJtis/{jti}
  |- revokedAt
  +- expiresAt (24h, access token TTL)

TTL policies REQUIRED: authCodes (10m), refreshTokens (30d), revokedJtis (24h)

Webhook Connectors

Video coming soon

Click to preview (placeholder)

Connectors let Rylvo call your systems during workflow execution. Register webhook endpoints for tool calls, state sync, or event notifications - Rylvo handles auth, retry, and signing.

Three connector types

Tool

Execute actions on your systems (create case, look up order, check inventory)

State Sync

Read/write workflow state from your DB before and after decisions

Event

Receive notifications on escalation, stage change, verification failure

Register a tool connector

bash
curl -X POST https://rylvo.com/api/v1/connectors \
  -H "X-API-Key: YOUR_KEY" -H "Content-Type: application/json" \
  -d '{
    "company_id": "comp_001",
    "connector_type": "tool",
    "name": "Salesforce Case Creator",
    "auth": {
      "auth_type": "api_key",
      "api_key_header": "X-Salesforce-Key",
      "api_key_value": "your_salesforce_api_key"
    },
    "tool_config": {
      "tool_name": "create_salesforce_case",
      "tool_description": "Create a case in Salesforce",
      "input_schema": { "type": "object", "properties": { "issue_summary": { "type": "string" } } }
    },
    "endpoint": { "url": "https://your-app.com/rylvo/tools/create-case", "method": "POST" }
  }'

API endpoints

MethodPathDescription
POST/v1/connectorsCreate connector
GET/v1/connectors/{company_id}List connectors
GET/v1/connectors/{company_id}/{id}Get detail
PATCH/v1/connectors/{company_id}/{id}Update
DELETE/v1/connectors/{company_id}/{id}Delete
POST.../{id}/activateActivate
POST.../{id}/deactivateDeactivate
POST.../{id}/testTest endpoint
POST/v1/connectors/generate-secretGenerate HMAC secret
GET/v1/connectors/{company_id}/tools/availableList webhook tools

7 authentication methods

noneapi_keybearer_tokenhmac_sha256basicoauth2_client_credentialscustom_header

10 subscribable event types

workflow.decision.completedworkflow.decision.failedworkflow.escalation.triggeredworkflow.stage.changedtool.execution.completedtool.execution.failedstate.updatedverification.failedsession.startedsession.closed

Connector Python SDK

Video coming soon

Click to preview (placeholder)

Typed convenience methods for all connector operations. Sync and async variants included.

python
from core.connectors.sdk import ConnectorSDK

sdk = ConnectorSDK(base_url="https://rylvo.com", api_key="rylv_...")

# Register a tool connector
connector = sdk.create_tool_connector(
    company_id="comp_001",
    name="Salesforce Case Creator",
    endpoint_url="https://your-app.com/rylvo/tools/create-case",
    tool_name="create_salesforce_case",
    tool_description="Create a case in Salesforce",
    auth_type="api_key",
    api_key_header="X-Salesforce-Key",
    api_key_value="your_salesforce_api_key",
)

# Test + activate
test = sdk.test_connector("comp_001", connector["connector_id"])
if test["success"]:
    sdk.activate("comp_001", connector["connector_id"])

# List available tools
tools = sdk.list_available_tools("comp_001")
print(f"{tools['total']} webhook tools available")

Async variant

python
from core.connectors.sdk import AsyncConnectorSDK

async with AsyncConnectorSDK(base_url="...", api_key="...") as sdk:
    connector = await sdk.create_event_connector(
        company_id="comp_001",
        name="Slack Alerts",
        endpoint_url="https://hooks.slack.com/...",
        event_types=["workflow.escalation.triggered"],
    )
    await sdk.activate("comp_001", connector["connector_id"])

Error handling

ConnectorNotFoundError(404)Connector does not exist
ConnectorAuthError(401/403)Invalid or missing API key
ConnectorValidationError(422)Invalid connector configuration
ConnectorSDKError(5xx)Base class for all SDK errors

ChannelsDashboard-only

Multi-channel bot deployment configuration. 13 channel types supported — deploy your bot to website widgets, WhatsApp, Slack, Telegram, Discord, MS Teams, Messenger, Instagram, SMS, LINE, Email, or custom webhooks. Multiple configs per bot are allowed, but only one enabled per channel type. Includes health monitoring, secret encryption, webhook idempotency, and race-condition protection. Manage from Dashboard → Channels.

13 channel types

  • widget — Website chat (origins, accent color, greeting, position, branding)
  • whatsapp — Meta Cloud API (phoneNumberId, verifyToken)
  • slack — (teamId, botUserId, signingSecret, channels[])
  • telegram — (botId, webhookSecret, allowedChatIds, collectFeedback)
  • discord — (applicationId, publicKey, guildId, commandName)
  • msteams — (appId, appPasswordPrefix, tenantId)
  • messenger — (pageId, verifyToken, appSecretPrefix)
  • instagram — (igUserId, verifyToken, appSecretPrefix)
  • sms_twilio — (accountSid, fromNumber, messagingServiceSid?)
  • whatsapp_twilio — (same as SMS, connector adds whatsapp: prefix)
  • line — (channelId, channelSecret, channelAccessToken, botUserId)
  • email_inbound — (inboxAddress, fromAddress, replyMode, Resend)
  • custom — (inboundSecret, label?)

Setup status state machine

  • needs_setup → verified, deprecated (incomplete; cannot enable)
  • verified → live, deprecated (test passed; can enable)
  • live → verified, deprecated (enabled and receiving)
  • deprecated → verified, needs_setup (restorable)
  • enabled only allowed when setupStatus in [verified, live]
  • First successful test auto-promotes to live (if no sibling enabled)

One-enabled-per-type rule

  • Enforced at API layer via Firestore transactions (runTransaction)
  • On create/patch with enabled=true: query for existing enabled config with same botId + channelType
  • Returns 409 Conflict if duplicate found
  • Firestore rules enforce immutability of botId and channelType

Health monitoring

  • Status: ok | error | idle
  • 3+ consecutive errors flips status to "error"
  • Notification fanout on first error and at 3-in-a-row; 15-min cooldown
  • Append-only event log (last 10 events, ring buffer) eliminates RMW hotspot
  • Tracks inboundCount, outboundCount, errorCount, consecutiveErrors

Secret storage & redaction

  • Full tokens in env vars (e.g. TELEGRAM_BOT_TOKEN_{prefix})
  • Encrypted tokens in Firestore as AES-256-GCM v1: blobs
  • Token prefixes (first 8 chars) stored in plaintext for display
  • GET responses strip encrypted fields before sending to client
  • Resolution order: env var → encrypted Firestore → legacy plaintext

Webhook idempotency

  • 24-hour fingerprint cache at /dedup/{fingerprint}
  • Format: {channel}:{platform_id} (e.g. telegram:12345)
  • Platform retries are safe — duplicates silently ack'd
  • TTL: 24 hours (Firestore TTL policy)

Inbound webhook flow (14 steps)

  • 1-4: Buffer body, parse, early ack, pre-config signature check
  • 5-8: Kill switch, channel lookup, org suspension, per-org rate limit (60 cap / 120/min)
  • 9-12: Post-config ack, parse to NormalizedInbound, session resolution, dedup check
  • 13-14: Record inbound, execute bot turn + send reply (3-retry) + record outbound/error
  • All synchronous within webhook request (15s timeout)
  • Discord special case: returns type:5 (thinking) immediately, runs in background

RBAC & plan limits

  • channels.view — viewer+ (read)
  • channels.manage — admin+ (create/update/delete)
  • channels.test — operator+ (test connection)
  • channels.rotate_secret — admin+ (rotate secrets)
  • No per-plan channel limits — unlimited per plan
  • Health subcollection is read-only for clients (server-only writes)

API endpoints (BFF Next.js)

  • POST /api/channels — create channel config (admin)
  • GET /api/channels?orgId=…&botId=…&channelType=…&enabled=… — list (viewer)
  • GET /api/channels/{configId} — get single (viewer)
  • PATCH /api/channels/{configId} — update (admin)
  • DELETE /api/channels/{configId} — delete (admin)
  • POST /api/channels/test — test connection (operator)

Webhook endpoints: /api/channels/{telegram,whatsapp,slack,discord,sms,whatsapp_twilio,line,messenger,instagram,msteams,email_inbound,custom}

Firestore shape

text
organizations/{orgId}/botChannelConfigs/{configId}
  |- botId (immutable), botName, channelType (immutable)
  |- enabled, requireExplicitConsent, defaultConsentScope
  |- config: <channel-specific config object>
  |- setupStatus: needs_setup | verified | live | deprecated
  |- lastTestedAt?, lastVerifiedAt?, apiKeyId?
  |- createdBy, createdAt, updatedAt

organizations/{orgId}/botChannelConfigs/{configId}/health/main
  |- status: ok | error | idle
  |- lastInboundAt, lastOutboundAt, lastErrorAt
  |- lastErrorMessage (500 chars max)
  |- inboundCount, outboundCount, errorCount, consecutiveErrors
  |- recentEvents[]: { ts, kind: inbound|outbound|error, summary } (last 10)
  |- updatedAt, lastNotifiedAt?

organizations/{orgId}/botChannelConfigs/{configId}/dedup/{fingerprint}
  |- createdAt, expireAt (24h TTL)

organizations/{orgId}/botChannelConfigs/{configId}/health/events/log/{docId}
  |- (append-only event log, eliminates RMW hotspot)

organizations/{orgId}/channelMediaAssets/{assetId}
  |- expiresAt (max 30 days), GCS blob reference

MCP HubDashboard-only

Video coming soon

Click to preview (placeholder)

The MCP Hub is Rylvo's Model Context Protocol registry - a curated catalog of MCP servers your bots can use as tools. Manage from Dashboard → MCP Hub.

What you can do

  • Discover & install MCP servers
  • Connect external servers with HTTP, SSE, or stdio transports
  • Store credentials securely (encrypted at rest)
  • Allow specific servers per bot
  • See invocation logs & per-server analytics
  • Submit your own server for review

Built-in controls

  • Approval workflow before a server can be used in production
  • Outbound OAuth 2.1 (PKCE + DCR) for user-facing servers
  • Cost tiers (low / medium / high) surfaced before invocation
  • Health checks & auto-disable on repeated failure
  • Resume tokens for long-running tool calls

Build Intelligence: You can also expose the entire Rylvo workspace as an MCP server to Claude Code, Cursor, Windsurf, Zed, or Codex. See the Build Intelligence section below or visit /build-intelligence.

Build IntelligenceDashboard-only

Video coming soon

Click to preview (placeholder)

Build Intelligence lets AI editors and agents control Rylvo directly. Connect Claude Code, Cursor, Windsurf, Zed, or Codex CLI via MCP and give them 120+ tools to build bots, prompts, guardrails, skills, knowledge-base connections, connectors, channels, and more. Manage the connection from Dashboard → Build Intelligence or visit /build-intelligence.

Supported clients

  • Claude Code — remote HTTP install command + /mcp authorization
  • Cursor — mcp.json remote server configuration
  • Windsurf — mcp_config.json remote server configuration
  • Zed — context_servers settings entry
  • Codex CLI — codex mcp add + codex mcp login
  • Other Streamable HTTP clients — verify OAuth support with the client

Security model

  • OAuth 2.1 with PKCE (S256) — browser flow on first install
  • RS256 JWT access tokens, 24-hour TTL
  • Refresh tokens rotate on every exchange
  • Every call is org-scoped: token org_id must match the resource
  • A leaked token can act only in its bound org until expiry or revocation

Install examples

shell
# Claude Code
claude mcp add --transport http rylvo https://rylvo.com/mcp
# Then run /mcp inside Claude Code and authenticate
json
// Cursor — .cursor/mcp.json or ~/.cursor/mcp.json
{
  "mcpServers": {
    "rylvo": { "url": "https://rylvo.com/mcp" }
  }
}
json
// Windsurf — ~/.codeium/windsurf/mcp_config.json
{
  "mcpServers": {
    "rylvo": { "serverUrl": "https://rylvo.com/mcp" }
  }
}
json
// Zed — settings.json
{
  "context_servers": {
    "rylvo": { "url": "https://rylvo.com/mcp" }
  }
}
shell
# Codex CLI
codex mcp add rylvo --url https://rylvo.com/mcp
codex mcp login rylvo --scopes rylvo:read,rylvo:write

Tool surface

ResourceReadWriteDelete
Botsbots.list, bots.getbots.create, bots.updatebots.delete
Promptsprompts.list, prompts.getprompts.create, prompts.update, prompts.set_optimizationprompts.delete
Guardrailsguardrails.list, guardrails.getguardrails.create, guardrails.updateguardrails.delete
Teststests.list_cases, tests.list_runs, tests.get_run_resultstests.create_case, tests.run_suitetests.delete_case
Connectorsconnectors.list, connectors.getconnectors.create, connectors.update, connectors.testconnectors.delete
Channelschannels.list, channels.getchannels.create, channels.update, channels.register_webhook
MCP Serversmcp_servers.list, mcp_servers.getmcp_servers.register, mcp_servers.updatemcp_servers.delete
Skillsskills.list, skills.getskills.create, skills.updateskills.delete
Knowledge Basekb.list_connections, kb.get_connectionkb.create_connection, kb.update_connection, kb.add_sourcekb.delete_connection
Agent Groupsagent_groups.listagent_groups.create, agent_groups.add_botagent_groups.delete
Automationsautomations.list_tasks, broadcasts.listautomations.create_task, broadcasts.createautomations.delete_task, broadcasts.delete

Macro tools

  • scaffold_bot_from_description — create a bot + system prompt + starter guardrail from a brief
  • clone_bot — duplicate a bot and optionally copy linked resources
  • attach_mcp_server — register an external MCP server, write its tools, and link it to bots

Entitlements still apply: MCP does not bypass product access or quotas. Each tool action is checked against its scope, workspace permissions, plan availability, validation, and the same limits shown in Billing & Usage.

Workspace ArchitectDashboard-only

Video coming soon

Click to preview (placeholder)

The Architect is a conversational setup agent that turns a plain-English brief into a working workspace: bots, prompts, guardrails, connectors, KB connections, evolution rules, and tests. It runs at /dashboard (Architect tab) and also powers natural-language edit mode and bot cloning.

8-phase agent loop

1. Intent

Capture goal, industry, tone, compliance constraints

2. Research

Pull org profile defaults, relevant industry templates, prior sessions

3. Blueprint

Propose bots + workflow + guardrails + KB + connectors

4. Review

User approves / edits the blueprint in natural language

5. Generate

Write prompts, guardrails, connectors, rules, KB stubs

6. Verify

Auto-run bot tests on the new bot(s) and score readiness

7. Apply

Commit as versioned resources, tagged source=architect

8. Handoff

Summarise what was built, link to Mission Control & Playground

Other architect capabilities

  • Edit mode - change an existing bot with a sentence ("make it more empathetic, prefer email over phone")
  • Bot cloning - duplicate a bot with modifications (industry, language, tone)
  • Bot testing - architect can run tests and iterate until a readiness threshold is met
  • Industry templates - 20+ starting points (SaaS support, fintech KYC, healthcare intake, etc.)
  • Org profile prefill - reuses defaults you set once in your organization profile

Knowledge BaseDashboard-only

Video coming soon

Click to preview (placeholder)

Connect your data sources to Rylvo for grounded, evidence-backed decisions. The KB system provides hybrid retrieval with 12 pre-built blueprints covering common enterprise patterns.

How it works

1

Add a source

Upload documents, connect APIs, or point to URLs. 16 source types supported.

2

Pick a blueprint

Choose from 12 retrieval blueprints (FAQ, Policy Lookup, Troubleshooting, etc.) or create custom.

3

Create a connection

A connection links a source to a blueprint with specific pipeline settings.

4

Query in playground

Test retrieval quality before going live. Tune chunking, reranking, and thresholds.

Built-in blueprints

FAQ RetrievalPolicy LookupTroubleshooting GuideProduct CatalogLegal / ComplianceOnboarding DocsMedical ReferenceFinancial AdvisoriesHR HandbookTechnical ManualsSales PlaybookCustom Pipeline

Dashboard features

Connections

Create, monitor, pause, and delete knowledge base connections

Sources

Manage 16 source types - upload, cloud storage, SaaS, databases, API, web crawl

Blueprints

Pre-built and custom retrieval pipeline configurations

Playground

Test queries, inspect retrieved chunks, tune settings live

Performance

Retrieval quality metrics, latency, and hit rates

Settings

Auto-tune, chunking config, reranking, and threshold tuning

Retrieval stack

1

Vector store

Qdrant stores chunked embeddings. Keyword + semantic search are combined, then reranked for relevance.

2

BYOK decryption

If a source or org uses a BYOK provider key, it is decrypted at retrieval time before calling the embedding model.

3

Retrieval trace

Every KB call is recorded in the canonical observability store as one clean retriever observation per connection, with latency, score, and strategy.

4

Citations

Grounded answers include source references with chunk IDs so you can audit what the bot actually saw.

Conversation StorageDashboard-only

Video coming soon

Click to preview (placeholder)

Bring Your Own Database (BYODB). Every conversation event Rylvo processes can be pushed to a database or webhook endpoint you own — so your conversations live permanently in infrastructure you control. Rylvo also maintains a time-bounded operational copy for Mission Control, traces, agent evolution, and edge-case detection. Manage from Dashboard → Conv. Storage.

Connections

Add one or more storage targets. Each target receives a copy of every conversation event.

  • PostgreSQL, Supabase, Neon, PlanetScale
  • MySQL, Turso, MongoDB, Redis
  • Webhook (none / API key / Bearer / HMAC auth)
  • Credentials encrypted at rest

Schema Designer

Map Rylvo's conversation fields to your own table columns or JSON keys.

  • Custom field mapping per target
  • Preview the output schema and test DDL before going live
  • Drivers only INSERT — you run the DDL on your side

Sync Logs + Dead Letter

Per-event push history with status, latency, and error details.

  • Success / failure / retry status per target
  • Webhook retry with exponential backoff (0.5 s → 1 s → 2 s)
  • Inspect the exact payload that was sent
  • Dead Letter Queue for terminal failures
  • Live / Test toggle isolates playground traffic

Retention

How long Rylvo keeps its own operational copy of your conversations.

Free7 days
Lite14 days
Pro30 days
Team30 days
EnterpriseNegotiable

Production / Test isolation

Every storage event carries a source discriminator:channel (live), playground (dashboard chat), or test_suite (automated eval). Live Saves, Sync Logs, and Dead Letter all have a Production / Test toggle so you can inspect playground traffic separately.

Live (channel)

  • Deployed-channel traffic: Telegram, WhatsApp, widget, API, etc.
  • Writes to base tables (e.g. rylvo_conversations)
  • Uses the production circuit-breaker key
  • Billed normally

Test (playground)

  • Dashboard Bot Chat / Group Chat turns
  • Writes to suffixed tables (default *_test)
  • Configurable per-target suffix or optional testDatabaseName
  • Separate circuit-breaker key — missing test tables never affect live dispatch
  • Webhook payloads include source and isTest
  • Billed exactly like production events

Run the test DDL first. Drivers only INSERT; they never auto-create tables. The Schema Designer emits the production DDL and a matching *_test DDL. Create the test tables before flipping playground traffic to a target.

Your database is the source of truth. Rylvo's copy is an operational cache — not a backup. Use the Connections tab to wire up your own database for permanent storage. Extending Rylvo-side retention increases storage costs but does not replace your own database.

Audience HubDashboard-only

Video coming soon

Click to preview (placeholder)

The central operator workspace for managing every end-user across every bot in your organization. Replace scattered user lists with a scalable hub: paginated people browsing, saved filter segments, aggregate analytics, consent governance, CSV import/export, and retention policy controls. Manage from Dashboard → Audience.

People

Paginated, server-driven list of end-users with 50-row default and 200-row maximum cursor pages.

  • Filter by channel, consent scope, forgotten status, or free-text search
  • Bot-scoped filtering with data-collection field predicates
  • Full person page with Overview, Sessions, Profiles, Activity, and Audit tabs
  • Bulk tags, consent, sessions, removal, export, merge, and eligible broadcast actions
  • Anonymous widget captures and test identities stay separate from production People
  • Keyboard shortcuts: / search, j/k navigate, Enter inspect

Audience Rooms

Saved filter presets that act like persistent views. Create rooms for common cohorts and share them with your team.

  • Save any filter combination as a named room
  • Dirty indicator when current filters diverge
  • Pins & Discussion panel per room
  • Deep-linkable via ?room= URL param

Segments

Reusable filters that resolve into concrete member lists for broadcasts and exports.

  • Basic predicates: all users, active within N days, by channel, min conversations
  • Profile predicates: checkpoint achieved, field value, completeness threshold
  • Live preview with match count before saving
  • One-click link to bot broadcast tab

Insights

Cached aggregate analytics computed from a snapshot (6h TTL). Org-wide or bot-scoped.

  • Onboarding funnel, channel mix, consent breakdown
  • Engagement depth, activity recency, hour-of-day heatmap
  • Field-fill rates, top tags, completeness histogram
  • Bot leaderboard (org-wide only)

Imports & Exports

Bring legacy CRM contacts in and export segments for your data warehouse.

  • CSV import wizard: upload → map columns → preview → commit (idempotent)
  • Segment export to CSV (up to 5,000 rows)
  • 15 MB file limit plus a plan-specific row limit

Consent & Privacy

Govern how end-user data is collected and retained.

  • Consent scope tiles: none, short_term_only, personalize, full
  • GDPR forgotten-users queue with deep-links
  • Retention policy: effective days, shorten override, extend add-ons
  • The scheduled retention sweep purges expired records

Profiles & Schema

Org-wide field catalog + per-bot schema cards. Spot naming drift before it ruins dashboards.

Settings

Retention policy, add-on tiers, and governance. Admin-only configuration.

People DatasetsDashboard-only

Named collections of end-users (not traces). Stored in the same datasets collection as trace datasets, discriminated by kind === "people". Created from Audience Room pins or directly from the People page. No retention/export-lock machinery (unlike trace datasets). Quota-enforced via maxDatasets plan limit. Manage from Dashboard → Datasets → People.

Dataset document

  • name (1-200 chars), description (max 2000)
  • kind: "people" (discriminator; legacy docs without kind = "trace")
  • peopleCount: denormalized end-user count
  • retentionPolicy: { mode: "inherit" } (not enforced for people)
  • archived: soft-delete flag

Item document

  • externalId (1-500 chars, end-user identifier)
  • source: "audience_room" | "people_page"
  • addedBy, addedByName, addedAt
  • snapshot: channel, displayName, consentScope, turnCount, lastSeenAtMs, firstSeenAtMs
  • Doc ID = externalId → add/remove is idempotent

Kind discriminator: people vs trace

  • people: groups EndUserDoc profiles, no retention enforcement, no export lock
  • trace: groups TraceView traces, active retention, export lock
  • Both in same Firestore collection (datasets)
  • Filtering: .filter(r => r.data.kind === "people")

3 creation flows

  • From Audience Room pins: createPeopleDatasetFromRoomPins() — fetches all pins, source="audience_room"
  • From People Datasets page: create empty dataset, add people individually
  • From People page: addPeopleToDataset() bulk add, source="people_page"

Quota enforcement (maxDatasets)

  • Free: 1 | Lite: 10 | Pro: 50 | Team: 50 | Enterprise: unlimited (-1)
  • Transactional count check (race-safe inside Firestore transaction)
  • Archived datasets excluded from count; only kind="people" counted
  • Error: PlanAccessError code="quota_exceeded", status 429

RBAC

  • Read: any org member (viewer+)
  • Create: operator+ (must set createdBy = auth.uid, name required)
  • Update: operator+  |  Archive: operator+
  • Delete: admin+ (hard delete + all items in chunks of 500)
  • Items: read = member; create = operator+; delete = operator+

Idempotent operations

  • Add item: returns { added: false } if already exists
  • Remove item: returns { removed: false } if doesn't exist
  • peopleCount incremented/decremented atomically
  • Cannot add people to archived dataset

Relationships & audit

  • Audience Rooms: pins → dataset items via createPeopleDatasetFromRoomPins()
  • Broadcasts: NOT currently used as broadcast targets (future: "dataset" target type)
  • Audit: people_dataset.created / updated / archived / deleted / item_added / item_removed
  • Resource type: "people_dataset"

API endpoints (Python FastAPI)

  • POST /v1/people-datasets/{company_id} — create dataset (201, quota-checked)
  • PUT /v1/people-datasets/{company_id}/{dataset_id} — update name/description
  • POST /v1/people-datasets/{company_id}/{dataset_id}/archive — archive (soft-delete)
  • DELETE /v1/people-datasets/{company_id}/{dataset_id} — hard delete + all items
  • POST /v1/people-datasets/{company_id}/{dataset_id}/items — add person (idempotent)
  • POST /v1/people-datasets/{company_id}/{dataset_id}/items/{external_id}/remove — remove person

All require authorize_org. Add item: external_id (1-500), source?, added_by, added_by_name, snapshot?.

Firestore shape

text
organizations/{orgId}/datasets/{datasetId}
  |- name (1-200), description (max 2000)
  |- kind: 'people' (discriminator)
  |- createdBy, createdByName
  |- peopleCount: number (denormalized)
  |- traceCount: 0 (not used for people)
  |- retentionPolicy: { mode: 'inherit' } (not enforced)
  |- tags: [], archived: boolean
  +- createdAt, updatedAt: Timestamp

organizations/{orgId}/datasets/{datasetId}/items/{externalId}
  |- externalId (1-500), source: audience_room | people_page
  |- addedBy, addedByName, addedAt
  +- snapshot: { externalId, channel, displayName?,
                  consentScope, turnCount, lastSeenAtMs?, firstSeenAtMs? }

Quota: maxDatasets (Free: 1 | Lite: 10 | Pro: 50 | Team: 50 | Enterprise: -1)
  Count excludes archived; only kind='people' counted

BroadcastsDashboard-only

Outbound multi-channel messaging — send the same campaign to WhatsApp templates, SMS, email, and in-app from one composer. Audience uploads (CSV / XLSX / paste), platform-derived rate caps, per-recipient delivery tracking, and audit-grade history are built in. Manage from Dashboard → Broadcasts.

5-step composer

Audience → Channels → Content → Schedule → Review. Platform rules are surfaced at audience-pick time, never at delivery time.

  • 4 audience sources: saved segment, upload, paste, all past chatters
  • Toggleable channels with per-channel rule annotation
  • Template + LLM-enrichment + per-row variables
  • One-time, recurring (hourly → monthly), or "Send now"

12 broadcast channels

Email, in-app, WhatsApp, SMS (Twilio), WhatsApp (Twilio), Telegram, LINE, Slack, Discord, MS Teams, Messenger, Instagram, Web Widget. Each annotated with cold-send verdict and platform cap.

  • WhatsApp: approved-template picker synced from Meta Graph
  • WhatsApp (Twilio): rides Twilio Programmable Messaging with whatsapp: E.164 prefix
  • SMS: opt-in gated, Twilio REST direct from the dispatch route
  • LINE: reply/push via LINE Messaging API; channel secret + access token encrypted at rest
  • Telegram / Web Widget / Discord: cold-send blocked with explainer

Audience uploads

Server-parsed CSV / XLSX (SheetJS) and paste-list with classifier and dedupe. Up to 50k rows per upload; per-row variable columns merge into the template at send time.

  • Auto-detect identifier kind: phone, email, channel id
  • Warnings surfaced before commit
  • Optional "save to audience" mirror into endUsers

Delivery tracking

Every recipient writes a botBroadcastDeliveries row: queued / sent / failed / skipped, plus provider message ids where available.

  • Per-run doc with per-channel sent / failed / skipped counts
  • History drawer groups deliveries by channel + status
  • Audit log: broadcast.created / scheduled / deleted

Rate caps & safety

Token-bucket rate limiter per channel in the cron runner. Caps mirror documented platform ceilings so a hot run can't suspend the underlying account.

  • WhatsApp: 4,800/min (~80/s/number, Meta default)
  • SMS: 60/min (Twilio conservative)
  • Operators can lower per-broadcast, never raise above platform cap

Capabilities & RBAC

Four capability keys gate the feature, mirroring the rest of the dashboard.

  • broadcasts.view — viewer+
  • broadcasts.create — operator+
  • broadcasts.send — admin+
  • broadcasts.delete — admin+

API endpoints

  • POST /api/bots/broadcasts/dispatch — per-recipient × per-channel dispatch (cron + ID token)
  • POST /api/bots/broadcasts/generate-content — LLM enrichment (cron + ID token)
  • POST /api/broadcasts/upload — CSV / XLSX / paste-list parser
  • POST /api/broadcasts/recipients/save-to-audience — idempotent endUsers mirror
  • GET/api/channels/whatsapp/templates?orgId=…&botId=… — Meta-approved template catalog

Firestore shape

text
organizations/{orgId}/botBroadcasts/{broadcastId}
  |- targets.type: "all" | "selected" | "segment" | "uploaded"
  |- targets.uploaded.rows: UploadedRecipient[]
  |- delivery.channels: BroadcastChannel[]
  |- delivery.whatsappTemplate: WhatsAppTemplateRef | null
  |- delivery.rateLimitPerMinute: number | null
  +- deliveries/{deliveryId}
      |- runId, channel, identifier, identifierKind
      |- status: "queued" | "sent" | "failed" | "skipped"
      |- providerMessageId, error
      +- attemptedAt, deliveredAt

organizations/{orgId}/botBroadcastRuns/{runId}
  |- status: "success" | "partial" | "failed"
  |- perChannelStats: { [channel]: { sent, failed, skipped } }
  |- targetCount, sentCount, failedCount, skippedCount
  +- generatedContent, errors[]

Team & RBACDashboard-only

Role-based access control for every dashboard surface. Four ranked roles, email invitations, join requests, granular page-access grants, ownership transfer, and SSO (Enterprise). Manage from Dashboard → Team.

Role hierarchy (ranked)

Four roles with strict rank-based enforcement. No action can target a member at or above your own rank.

  • Owner (rank 3) — full access, billing, transfer ownership, manage SSO
  • Admin (rank 2) — manage bots, keys, team, approvals, view audit log
  • Operator (rank 1) — run bots, tests, interventions, approve requests
  • Viewer (rank 0) — read-only dashboard access

Capability matrix (selected)

  • team.view — viewer+
  • team.invite — admin+
  • team.remove_member — admin+
  • team.change_role — admin+
  • team.transfer_ownership — owner
  • audit.view — admin+
  • audit.export — admin+ (+ Pro plan)
  • org.manage_sso — owner (+ Enterprise)

Invitations (email-based)

Admin+ invites by email. Plan limit enforced at invite and acceptance time. Deterministic invite ID from SHA256 hash. Rate limit: 10 invites/min.

  • Default expiry: 7 days. Status: pending / accepted / expired / cancelled
  • Atomic write: invitation + audit log + org usage counter in one transaction
  • Acceptance: email must match, plan limit re-checked, member doc created
  • Resend / cancel are admin+ actions, both audit-logged

Join requests (cross-org)

Non-members can request to join with a profile (role, workType, interests, useCase). Admins approve or deny with optional reason.

  • Profile captures: founder / engineer / PM / designer / ops / analyst / other
  • Work type: building agents, integrating API, managing workflows, analyzing data, exploring
  • Audit actions: page_access.approved / page_access.denied

Page access grants

Granular elevation for two gated pages: audit_log and billing. Non-admins see a "Request Access" button.

  • Request fans out in-app notification to all admins
  • Approve → pageId added to member's pageAccessGrants array
  • Deny → optional resolverNote. Revoke removes the grant
  • Access: role grants capability OR explicit pageAccessGrant

Role change & removal

Cannot change own role, cannot act on same/higher rank, cannot assign above own rank. Removal is soft-delete only.

  • Demotion enqueues durable outbox event for side-effects (API key revocation)
  • Removal: status → "deactivated", revokes all API keys via outbox
  • Ownership transfer: atomic swap, two audit entries, org.ownerId updated
  • SSO/SAML: Enterprise plan only, owner-only capability

API endpoints

  • POST /api/team/invite — create email invitation (admin+)
  • POST /api/team/invite/accept — accept invitation (any authenticated user)
  • POST/api/team/members/{uid}/role — change member role (admin+)
  • POST /api/team/transfer-ownership — transfer org ownership (owner only)

Firestore shape

text
organizations/{orgId}/members/{uid}
  |- uid, email, displayName
  |- role: owner / admin / operator / viewer
  |- status: active / deactivated
  |- pageAccessGrants?: ("audit_log" | "billing")[]
  |- joinedAt, deactivatedAt?, deactivatedBy?
  +- createdAt, updatedAt

organizations/{orgId}/invitations/{inviteId}
  |- email, role, status, invitedBy, inviterName
  |- expiresAt, acceptedAt?, emailStatus?

organizations/{orgId}/joinRequests/{requestId}
  |- userId, userEmail, orgId, status, profile, requestedRole?
  |- requestedAt, resolvedAt?, resolvedBy?, denialReason?

organizations/{orgId}/pageAccessRequests/{requestId}
  |- pageId, requesterId, requesterEmail, status
  |- requestedAt, resolvedAt?, resolvedBy?, resolverNote?

Audit LogDashboard-only

Immutable, tamper-evident audit trail for every material action across the platform. 200+ action types, two-stage outbox write with HMAC-SHA256 integrity signatures, cursor pagination, and CSV export. Manage from Dashboard → Audit Log.

Two-stage write pattern

Every mutation stages an audit event into the outbox in the same Firestore transaction — atomicity is guaranteed. A Cloud Function drains the outbox into the canonical, read-only audit log.

  • Stage 1: auditOutbox (transactional, same batch as mutation)
  • Stage 2: auditLog (durable, read-only, drained by worker)
  • HMAC-SHA256 integrity signature on every outbox doc
  • Both web (Admin SDK) and Python API write through the same pipeline

Action catalog (200+)

Covers every material action: team, API keys, org, bots, workflows, prompts, tests, edge cases, guardrails, KB, traces, mission control, evolution, automation, MCP Hub, skills, audience, broadcasts, connectors, auth, and setup sessions.

  • 40+ resource types (member, bot, prompt, guardrail, trace, broadcast…)
  • Each entry: actor, action, resourceType, resourceId, details, IP, UA, source
  • Source discriminator: web / api / mcp / system

Filtering & search

Filter by action, resourceType, resourceId, source, date range, actor email, and actor name. Full-text search across all fields including details JSON.

  • Cursor-based pagination (opaque cursor: timestamp + docId)
  • Default 25/page, max 500/request
  • Rate limit: 120 req/min per IP on the BFF endpoint

Export & compliance

CSV export with all fields. Gated by capability and plan.

  • Columns: id, timestamp, actorName, actorEmail, actorId, action, resourceType, resourceId, details
  • Requires audit.export (admin+) AND Pro+ plan
  • Audit logs are immutable once canonicalized
  • Retention follows org-specific policy; GDPR purge via observability endpoints

RBAC

View and export are separately gated. Non-admins can request access via the page-access grant system.

  • audit.view — admin+ OR pageAccessGrants: ["audit_log"]
  • audit.export — admin+ AND Pro+ plan
  • Non-admins see "Request Access" on the audit-log page

Integrity & security

HMAC-SHA256 signature on every outbox doc prevents tampering. Firestore rules block direct writes to auditLog.

  • Signed fields: idempotencyKey, action, resourceType, resourceId, actor, details, source
  • Canonical JSON with sorted keys
  • No direct client writes to auditLog — all go through outbox staging
  • Reader handles both nested actor objects and flat legacy fields

API endpoint (BFF)

  • GET/api/audit?orgId=…&action=…&resourceType=…&startDate=…&endDate=…&q=…&actorEmail=…&actorName=…&cursor=…&limit=… — list audit entries

Auth: Firebase ID token (Bearer). Response: { orgId, total, records[], nextCursor }. Cache-Control: private, max-age=5, stale-while-revalidate=30.

Firestore shape

text
organizations/{orgId}/auditOutbox/{docId}   (transient staging)
  |- idempotencyKey, action, resourceType, resourceId
  |- actor: { uid, email, displayName }
  |- details: { schemaVersion, ...custom }
  |- ipAddress, userAgent, source
  |- processed: boolean, processedAt, error, retryCount
  |- stagedAt, stagedBy, integrity (HMAC-SHA256)

organizations/{orgId}/auditLog/{docId}   (canonical, read-only)
  |- idempotencyKey, action, resourceType, resourceId
  |- actor: { uid, email, displayName }
  |- details: { schemaVersion, ...custom }
  |- ipAddress, userAgent, source
  +- timestamp: Timestamp

Indexes required:
  auditOutbox: (processed, stagedAt)
  auditLog: (action, timestamp desc), (resourceType, timestamp desc),
            (source, timestamp desc), (timestamp desc, __name__ desc)

API KeysDashboard-only

Org-level API key generation, management, and revocation with environment scoping, permission scopes, rate limit overrides, and SHA-256 hashing. Keys authenticate external API requests (bot-run, KB query, KB documents). Manage from Dashboard → API Keys.

API key document

  • name — human-readable label
  • keyPrefix — first 12 chars for display
  • keyHash — SHA-256 of raw key (never store raw)
  • environment — production | staging | development
  • permissions — API scopes array
  • rateLimitOverride — calls/min (null = default 60)
  • botId — optional bot binding

Key generation & hashing

  • Format: rylv_{40 hex chars} (46 chars)
  • Entropy: 20 bytes = 160 bits (crypto.getRandomValues)
  • Hashing: SHA-256 via Web Crypto API
  • Only hash persisted; raw key shown once at creation
  • Prefix (first 12 chars) stored for display identification

Permission scopes (4 + 2 wildcards)

  • respond — POST /api/v1/bot-run + widget-poll
  • kb:read — POST /api/v1/kb/query
  • kb:write — POST /api/v1/kb/documents
  • trace.read — reserved for future
  • * — full access  |  kb:* — all KB
  • Normalization drops unrecognized scopes; defaults to ["respond"]

Authentication flow

  • Headers: Authorization: Bearer | X-API-Key | X-Rylvo-Key
  • SHA-256 hash raw key, collection-group query by keyHash
  • Verify status == "active", extract orgId from path
  • Async update lastUsedAt (non-fatal if fails)
  • Returns AuthIdentity with permissions + rateLimitOverride

CRUD operations

  • create: generate + hash + normalize, batch write (doc + counter + audit)
  • list: filter by status, ordered by createdAt desc
  • update: name, environment, permissions, botId + audit
  • revoke: status → "revoked", decrement counter + audit
  • revokeAllForMember: bulk revoke on member removal
  • hardDelete: physical deletion (provisioning rollback)

Rate limiting

  • Default: 60 calls/minute per key
  • Override via rateLimitOverride field
  • Token bucket via consumeTokenUnified()
  • 429 response with Retry-After header
  • Per-route buckets (bot-run, kb/query, etc.)

RBAC & plan limits

  • apiKeys.view — admin+
  • apiKeys.create — admin+
  • apiKeys.revoke — admin+
  • Free: 1 | Lite: 10 | Pro: 25 | Team: 25 | Enterprise: unlimited
  • Quota enforced in Firestore rules (withinApiKeyQuota)
  • Non-admins see RestrictedPlaceholder

Audit & cleanup

  • Events: api_key.created / updated / revoked (with actor, IP, UA)
  • Bulk revoke logs { count, memberUid }
  • Bot deletion: hard-deletes bound keys, decrements active count only
  • Workspace Architect: auto-creates key with default permissions, rollback via hardDelete
  • Migration route: normalizeApiKeyPermissions backfill (idempotent)

API endpoints (BFF)

  • POST /api/v1/bot-run — execute bot turn (permission: respond, idempotency replay)
  • POST /api/v1/kb/query — query KB (permission: kb:read)
  • POST /api/v1/kb/documents — upload documents (permission: kb:write, max 100)

Auth: API key via Bearer / X-API-Key / X-Rylvo-Key header. Rate limited per key.

Firestore shape

text
organizations/{orgId}/apiKeys/{keyId}
  |- name, keyPrefix (12 chars), keyHash (SHA-256)
  |- environment: production | staging | development
  |- permissions: string[] (respond, kb:read, kb:write, trace.read, *, kb:*)
  |- status: active | revoked
  |- rateLimitOverride: number | null (calls/min)
  |- botId?, botName?
  |- createdBy, createdAt
  |- lastUsedAt?, revokedAt?, revokedBy?

Quota: usage.apiKeyCount vs settings.maxApiKeys
  Free: 1 | Lite: 10 | Pro: 25 | Team: 25 | Enterprise: -1 (unlimited)

ApprovalsDashboard-only

Human-in-the-loop approval workflow for tool execution. Two systems: connector tool approvals (API-level, for webhook/connector tools with requires_approval flag) and MCP tool approvals (MCP Hub, for tools with require_approvalpermission). Mission Control adds urgency-based timeouts and risk scoring. State machine: pending → approved / denied / expired.

Connector tool approval

  • Triggered when tool has safety_restrictions.requires_approval = True
  • approval_id format: aprv_{tool_call_id}
  • Default TTL: 24 hours (86400s)
  • Fire-and-forget: tool returns failure with approval_id immediately
  • Bot/frontend polls status separately; no auto re-invoke on approval

MCP tool approval

  • Permission levels: allow | require_approval | deny
  • Risk-based defaults: low/medium → allow, high/critical → require_approval
  • Default TTL: 10 minutes (600,000 ms)
  • argsPreview (redacted JSON, max ~2KB) + optional llmRationale
  • Real-time Firestore subscriptions for pending approvals

State machine

  • pending → approved (via service.approve)
  • pending → denied (via service.deny with reason)
  • pending → expired (auto-expiry when TTL elapses)
  • Only pending → approved/denied allowed (idempotent)
  • Double-approve/deny returns None (already resolved)
  • Mission Control adds: auto_denied status

Mission Control urgency levels

  • immediate — 15s timeout, low-risk informational
  • standard — 120s timeout, data access / PII-related
  • extended — 900s timeout, financial / legal / compliance
  • Expiry computed server-side (createdAt + timeoutSeconds)
  • Includes riskScoreAtTime + riskSignals[] + conversationContext

ApprovalService methods

  • request_approval() — create pending, persist, log event
  • approve() — verify pending, update, log, idempotent
  • deny() — verify pending, update + reason, log, idempotent
  • list_pending() — query + filter expired + log stale

Expiration & cloud functions

  • cleanupExpiredMcpApprovals — every 5 min, MCP approvals past TTL
  • missionControlExpireApprovals — every 2 min, MC approvals past timeout
  • Both write audit entries for system-initiated expiry
  • Frontend polling: 1-30s exponential backoff, 5-min local timeout

RBAC

  • Connector approvals: read = admin+; write = server-only (rules: false)
  • MC approvals: read = member; create/update = operator+; delete = admin+
  • MCP approvals: read = member; create/update = operator+; delete = admin+
  • resolve_approval capability: operator+ (Mission Control)
  • MCP Hub Approvals page: admin+ for approve/deny buttons

Audit & analytics

  • MC expiry writes audit: eventType "approval_resolved", actor "system_sweep"
  • Analytics: MCApprovalResolvedEvent { request_id, bot_id, action_type, resolution, response_time_ms, urgency }
  • Audit log: approval.requested / approved / denied / expired / resolved
  • Idempotency key on MC approvals for exactly-once semantics

API endpoints (Python FastAPI — connector approvals)

  • POST /v1/approvals/{approval_id}/approve — approve (query: approver_email?)
  • POST /v1/approvals/{approval_id}/deny — deny (query: reason?, approver_email?)
  • GET /v1/approvals/pending?company_id=…&bot_id=…&session_id=… — list pending

GET pending requires authorize_org. 404 if not found or already resolved. MCP approvals managed via dashboard at /dashboard/mcp-hub/approvals.

Firestore shape

text
organizations/{orgId}/approvalRequests/{approvalId}   (connector + Mission Control)
  |- approval_id (aprv_{tool_call_id}), tool_call_id, tool_name
  |- arguments: dict, status: pending | approved | denied | expired
  |- company_id, org_id, bot_id?, session_id, workflow_id?
  |- requested_at, expires_at (24h default)
  |- resolved_at?, resolved_by?, denial_reason?
  |- trace_id, request_id
  |- (MC) urgency, timeoutSeconds, riskScoreAtTime, riskSignals[]
  |- (MC) conversationContext, idempotencyKey?
  +- created_at: Timestamp

organizations/{orgId}/mcpApprovalRequests/{requestId}   (MCP Hub)
  |- requestId, serverId, serverName, toolId, toolName, toolDisplayName
  |- botId, botName, conversationId?, requestedBy, requestedByDisplay?
  |- argsPreview (redacted, ~2KB), llmRationale?
  |- status: pending | approved | denied | expired
  |- reviewedBy?, reviewedByDisplay?, decisionReason?
  |- expiresAt (10 min default), reviewedAt?
  +- createdAt, updatedAt: Timestamp

NotificationsDashboard-only

Unified notification system with 60+ types across 8 categories. In-app notifications delivered via real-time Firestore subscriptions; email via Resend; webhook dispatch to Slack/Discord/Teams. Unified inbox hook merges mentions + in-app alerts across all orgs. Manage from Dashboard → Notifications.

In-app notification document

  • userId (recipient), orgId, type
  • title, message, link?
  • data?: Firestore primitives (string | number | boolean)
  • read: boolean, readAt: Timestamp | null

Trace comment mention document

  • recipientUid, type (trace_mention | comment_reply | room_mention | pin_mention)
  • payload: commentId, entityType, entityId, entityLabel, actorUid, actorName, snippet (~120 chars)
  • Optional: parentRoomId, traceId, traceSource, traceLabel

60+ types across 8 categories

  • team (8): join_request, member_joined, page_access_*
  • mission_control (7): alert_rule, guardrail_escalation, channel_error, byok_unhealthy, wallet_low
  • retention (2): retention_warning, retention_deleted
  • edge_cases (2): edge_case_alert, guardrail_triggered
  • billing (10+): low_balance, credits_low, quota_exceeded, subscription_*
  • broadcasts (1), system (2): broadcast, welcome

Delivery channels (4)

  • In-app: real-time Firestore subscriptions (<100ms)
  • Email: Resend (mention emails, welcome; per-member opt-in)
  • Webhooks: Slack, Discord, Teams, generic (2 retries, 500ms backoff, 8s timeout)
  • Bot tools: send_notification__{slug}__{id} runtime tools (best-effort)

Real-time unified inbox

  • useNotificationInbox(): merges mentions + in-app, scoped to the active org
  • Parallel per-org Firestore subscriptions; other workspaces surface as unread counts + switch affordance
  • Exposes: items[], unreadCount, otherOrgUnread[], loading, markItemRead, markAllRead, deleteItems
  • Max 100 per org per subscription

Lifecycle & triggers

  • Created (read=false) → Mark Read → Delete (hard-delete)
  • Mark all read: capped at 200 docs per call
  • Triggers: guardrail escalations, degraded attachments, observability alerts, join requests, broadcasts, mentions, welcome
  • Engine triggers are fire-and-forget post-hooks (never block bot response)

Notification channels (Mission Control)

  • Types: slack, email, webhook, discord, teams
  • config: webhookUrl?, emails?, headers?
  • events[]: which alert events trigger this channel
  • botIds?: optional bot linking
  • lastUsedAt, lastStatus (success | failed)

RBAC & preferences

  • In-app: read = userId == auth.uid; update = only read/readAt fields; delete = false (via API)
  • Mentions: read = orgMember + recipientUid match; create = actorUid == auth.uid
  • Channels: read = member; create/update/delete = admin+
  • Dispatch API: operator+ required
  • Preferences: notificationPrefs.mentionEmail (default true)

API endpoints

  • POST /api/notifications/create — multi-recipient fan-out (30/min per org, max 500 recipients)
  • POST /api/notifications/delete — permanent deletion (max 100 items, ownership verified)
  • POST /api/notifications/dispatch — Mission Control webhook dispatch (10/min per org, operator+)
  • POST /api/notifications/email — mention emails via Resend
  • POST /api/notifications/welcome — one-time welcome + email (transactional guard)

Create: type (registry-validated), title, message, link?, data?, audience (all_members|admins|{userIds}). Dispatch: channels[], alert{ruleName, severity, trigger, message, botName, conversationId, details}.

Firestore shape

text
organizations/{orgId}/inAppNotifications/{notificationId}
  |- userId, orgId, type (from registry)
  |- title, message, link?, data?
  |- read: boolean, readAt: Timestamp | null
  +- createdAt: Timestamp

organizations/{orgId}/notifications/{notifId}  (trace comment mentions)
  |- recipientUid, type (trace_mention | comment_reply | room_mention | pin_mention)
  |- read: boolean
  +- payload: { commentId, entityType, entityId, entityLabel,
                actorUid, actorName, snippet, parentRoomId?, traceId? }

organizations/{orgId}/notificationChannels/{channelId}  (Mission Control)
  |- name, type (slack|email|webhook|discord|teams), enabled
  |- config: { webhookUrl?, emails?, headers? }
  |- events[], botIds?, lastUsedAt, lastStatus
  +- createdBy, createdAt, updatedAt

organizations/{orgId}/members/{uid}.notificationPrefs:
  { mentionEmail: boolean (default true) }

Ready to integrate?

Create an account, generate an API key, and start making decisions.