Lucy AI/Docs
GitHubOpen app

Getting started

  • Introduction
  • Quick start
  • Desktop app

Using Lucy

  • Chat & models
  • Memory
  • Personas
  • Connectors
  • Workflows
  • Voice
  • Security & 2FA
  • Themes & account

Developers

  • Architecture
  • Self-hosting
  • Embedding Lucy
  • HTTP API
  • CLI
  • MCP server
  • Contributing

HTTP API

Lucy's routes live under /api. Most are JSON in / JSON out; chat streams Server-Sent Events. This page covers the public, server-to-server surface.

Authentication

Routes accept one or both of:

  1. Session cookie — the @supabase/ssr cookies a logged-in browser carries on same-origin fetches (also works for a shared-auth app on the same Supabase project).
  2. Lucy API key — Authorization: Bearer lucy_k_... for server-to-server. Create keys in Settings → API Access; only a SHA-256 hash is stored, the full key is shown once, and the prefix (lucy_k_…, 12 chars) is kept for display. Keys map to a user and can be revoked or deleted any time.

Auth is always derived server-side — the request body's user id is never trusted. Which methods a route accepts:

Auth acceptedRoutes
Session or API key/api/chat, /api/memory/*, /api/workflows/* (except the webhook), /api/provider-keys
API key only/api/screening/*
Session only/api/keys
Secret token/api/workflows/triggers/:id/webhook (per-trigger secret, no user auth)
None/api/models

Most routes run only when Supabase is configured (connected mode); without it, auth-gated routes return 401/503.

POST /api/chat — streaming chat

Rate limited (30 req/min/IP → 429 with Retry-After). Returns text/event-stream.

curl -N https://justlucy.ai/api/chat \
  -H "Content-Type: application/json" \
  -H "x-openai-key: sk-..." \
  -d '{"messages":[{"role":"user","content":"Hello"}],"model":"gpt-4o","provider":"openai"}'
data: {"content":"Hello"}
data: {"content":" there!"}
data: [DONE]

Body fields: messages (required), model (required), provider (required), and optional projectId and systemPrompt. Errors stream as data: {"error":"..."} followed by data: [DONE] (the connection stays 200).

Provider keys are resolved in order: request header → the caller's stored encrypted key (Lucy API key auth only) → server env fallback. Env fallbacks (OPENAI_API_KEY, ANTHROPIC_API_KEY, …) are reserved for authenticated callers in connected mode so anonymous visitors can't spend the server's quota.

HeaderProvider
x-openai-keyOpenAI
x-anthropic-keyAnthropic
x-google-keyGoogle
x-deepseek-keyDeepSeek
x-groq-keyGroq
x-mistral-keyMistral
x-xai-keyxAI
x-openrouter-keyOpenRouter

provider: "local" needs no key — pass x-ollama-url / x-lmstudio-url to override the defaults. Send x-memory-enabled: 1 to retrieve and inject the caller's memories (authenticated calls only); the count comes back as a metadata event.

Tool calls run server-side when the authenticated caller has connectors installed (OpenAI-compatible and Anthropic providers). They're emitted as metadata events — arguments are intentionally omitted:

data: {"metadata":{"memoryCount":3}}
data: {"metadata":{"tool_call":{"slug":"github","tool":"create_issue"}}}
data: {"metadata":{"tool_result":{"slug":"github","tool":"create_issue","ok":true}}}

GET /api/models

No auth. Returns { models, byProvider } — all cloud models grouped by provider. Add ?includeLocal=true to probe Ollama/LM Studio and also get a localStatus report (per-server available, url, modelCount).

Memory API (session or API key)

userId is derived from auth; cookie callers get an RLS-scoped client, API-key callers a service client scoped to the key's owner. Returns 401 when unauthenticated.

EndpointPurpose
POST /api/memory/searchSemantic search. Body { query, limit?, projectId? } (limit 1–25, default 5). Returns { results: [{ content, importance }], count }. Rate limited 30/min/IP.
GET /api/memory/listAll of the caller's memories plus a usage summary.
DELETE /api/memory/list?id=…Archive one memory (ownership is verified first).
POST /api/memory/commandSave a fact. Body { kind: "remember" | "global", text, projectId?, conversationId? }.
curl https://justlucy.ai/api/memory/search \
  -H "Authorization: Bearer lucy_k_..." \
  -H "Content-Type: application/json" \
  -d '{"query":"deployment steps","limit":5}'

Workflows API (session or API key)

Durable, server-side runs (connected mode). All run/trigger reads and writes are scoped to the caller; the webhook is the only unauthenticated entry and uses a per-trigger secret instead.

EndpointPurpose
POST /api/workflows/runEnqueue a manual run. Body { definition, inputs?, workflowId? }; definition.nodes must include a Start node (≤500 nodes). Returns { runId }.
GET /api/workflows/runs?workflowId=&limit=List runs (status, timing, error).
GET /api/workflows/runs/:runIdOne run with inputs, outputs, logs.
POST /api/workflows/runs/:runId/cancelCancel — canceled if queued, canceling if running.
GET /api/workflows/triggers?workflowId=List triggers.
POST /api/workflows/triggersCreate a trigger (see below).
PATCH /api/workflows/triggers/:idToggle enabled, rename, or update settings/definition.
DELETE /api/workflows/triggers/:idRemove a trigger.
GET /api/workflows/:id/versionsList published versions (newest first).
POST /api/workflows/:id/versionsPublish the current draft as the next numbered version.

Triggers take { workflowId, type, settings, definition, inputs?, name? }. type is one of:

TypesettingsFires when
cron{ expr, timezone? }The cron slot comes due (a slot can't fire twice).
webhook—The webhook URL is POSTed (a secret is generated on create).
record_event{ table, events }A watched row changes. table ∈ conversations, memories; events ⊆ INSERT, UPDATE, DELETE.

POST /api/workflows/triggers/:id/webhook

No user auth — authenticate with the trigger's secret via ?token=<secret> or an x-webhook-token header. The JSON body is merged over the trigger's stored inputs and becomes the run inputs. Rate limited 60/min/IP; CORS-open.

curl -X POST "https://justlucy.ai/api/workflows/triggers/<id>/webhook?token=<secret>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: abc123" \
  -d '{"ticket_text":"Customer cannot log in"}'

Send an Idempotency-Key header to make retries safe — a repeated key returns the existing { runId, deduped: true } instead of enqueuing again. Trigger-initiated runs retry on failure with exponential backoff; manual runs don't.

Screening API (Lucy API key required)

AI contractor screening for marketplace integrations. All reads are tenant-scoped to the API key's owner — other tenants' screenings 404.

EndpointPurpose
POST /api/screening/startStart a screening. Body needs contractor_company_id, client_company_id, contractor_profile; screening_type defaults to project_screening. Returns { screening_id, status, screening_type }.
GET /api/screening/:idStatus + results.
POST /api/screening/:idSubmit answers. Body { answers: [{ question_id, answer }] }; returns { status, grade, grade_label }.
GET /api/screeningList, filterable by project_id, contractor_company_id, client_company_id, status.

Screenings are graded 1–5 (Not Recommended → Excellent Match) with strengths and concerns.

Key management

EndpointAuthPurpose
POST /api/keysSessionCreate a key; the full key is returned once.
GET /api/keysSessionList your keys (prefix, name, status, timestamps — never the full key).
DELETE /api/keys?id=…&action=revoke|deleteSessionRevoke (deactivate) or permanently delete. Defaults to revoke.
GET/POST /api/provider-keysSession or API keyRead/store your provider keys (AES-256-GCM, server-side).
Embedding LucyCLI