API Reference

Endpoint map and auth behavior.

Data portability export

GET /api/data-export?scope=personal|workspace and POST /api/data-export create the same non-mutating ZIP snapshot; POST accepts { "scope": "personal" } or { "scope": "workspace" }. Only admins may request the workspace scope. GET is intended for browser-native streamed downloads that do not buffer the archive in JavaScript. The archive contains complete JSON and per-entity CSV (including scheduled automation definitions, personal/group chat messages, agent runs, and installed data-bearing bundled-module datasets), ICS, VCF, document and referenced media binaries, a manifest, and a README. Personal scope includes automations created by the requester; workspace scope includes all definitions. Prompts, triggers, schedules, workflow graphs, and delivery targets remain portable while webhook, Bearer, and other structured access tokens are redacted. manifest.module_export lists included installed modules and datasets. Complete JSON and CSV artifacts are serialized row by row into temporary files before Archiver reads them, avoiding a second whole-export string in the web-process heap. Personal exports include Social Media library files referenced by the requester's posts; workspace exports include the complete Social Media media library, including unused uploads. Documents referenced by exported Tax Manager receipts, generations, and export jobs are included only when the requester retains normal shared/owner visibility. Requester-owned generated-image links in personal chat messages and module records are resolved into portable files while prompt, provider, model, storage-path, and image metadata remain redacted from the exported asset rows. Social-media session data and OAuth states are excluded with all other authentication secrets. A convenience XLSX is included only while its bounded row/cell/input-size plan is safe to materialize; manifest.tabular_formats.xlsx records inclusion or the limit that caused omission. Personal exports redact private Team Chat run recovery/client context and tool results belonging to other participants. Legacy open Team Chat membership rows retain joined_at; boundary_ambiguous=true identifies rows whose pre-history rejoin gaps cannot be reconstructed, instead of treating read-receipt updated_at activity as a join boundary. Authentication secrets and private agent memory are excluded. The response is application/zip with Cache-Control: no-store. See Daten-Portabilität und Exit-Plan for archive semantics and the master/sync distinction.

This page inventories the HTTP surface of Clapilot: first-party app APIs, bundled-module APIs, hub/distribution APIs, and the internal native-runtime endpoints the app talks to.

POST /api/hub/fleet/instances/:id/actions accepts the existing stop, start, and redeploy actions. The admin-only set-shell-tools action additionally requires boolean shellToolsEnabled; it persists the capability policy for that one Fleet instance and redeploys it. If the instance is stopped, connector 1.1.0 or newer recreates the deployment with --no-start so the policy change does not start the tenant. Older or unreported connector versions are rejected with 409 fleet_connector_update_required before a job is queued; re-running the served Fleet connector installer downloads the current connector and safely restarts its user service. Omitting the boolean is rejected with 400.

POST /api/hub/fleet/agents/heartbeat uses the per-machine HMAC contract and returns { ok, connector: { latestVersion }, remoteAgent: { enabled, label } }. latestVersion is the bundled connector's x.y.z version or null when the Hub cannot read it. remoteAgent reflects the machine's persisted opt-in and current name. Probe payloads may include codexVersion; an explicit string persists it, while explicit null or an empty string clears it. Older connectors that omit the key leave the stored value unchanged.

PATCH /api/hub/fleet/machines/:id accepts remoteAgentEnabled (or remote_agent_enabled) as a boolean in addition to the existing machine fields. GET /api/hub/fleet/connector/files/:name serves only clapilot-fleet-connector.mjs, clapilot-fleet-event-outbox.mjs, and agent-orchestrator-codex-remote-runner.mjs; all other names return 404. The legacy single-file GET /api/hub/fleet/connector endpoint remains available for old installers.

GET /api/clapilot-code/install serves the dependency-free clapilot-code installer with the request-facing instance origin substituted into its login instruction. It requires no interactive session or API key, but returns 404 unless app_settings.developer_mode_enabled is true.

GET /api/clapilot-code/files/:name serves exactly clapilot-code.mjs from the image's scripts/ directory. The literal allowlist rejects every other filename with 404. The route requires no interactive session or API key and shares the install route's Developer-mode 404 gate. The downloaded CLI uses a separately created inference:execute instance API key for model requests; the code-download route never accepts or exposes that key.

The /api/modules/agent-orchestrator/api/remote-runners/* family accepts either the existing Hub shared-secret HMAC or a Fleet machine HMAC. For Fleet authentication, x-clapilot-instance-id must be that machine's UUID and the signature uses its per-machine secret. Fleet credentials are rejected for every other module API path.

  • Source of truth: route handlers under src/app/api/**/route.ts (bundled-module endpoints are served through src/app/api/modules/[slug]/api/[...endpointPath]/route.ts from bundled-modules/<slug>/), plus the native clapilot-agent service for /internal/*.

  • /api/modules/agent-orchestrator/api/sessions and /api/modules/agent-orchestrator/api/sessions/:id

    • Session payloads include usageJson and runDiagnostics from the latest associated agent_runs row. Failed-run diagnostics are therefore available consistently to web, iOS, and macOS detail views.
  • Organization: endpoints are grouped by feature area (Auth, Documents, Tasks, E-Mail, Chat, integrations, Hub, Admin, module platform), followed by the internal native-runtime section.

  • Not every endpoint carries a full request/response spec here; entries prioritize auth level, methods, and behavioral contracts that agents and clients rely on. Agent-facing tool contracts live in Agent Tool Contracts.

Product telemetry

POST /api/telemetry/events stores a validated product event for the authenticated user. The endpoint accepts metadata only:

{
  "event_name": "module_opened",
  "session_id": "browser-session-id",
  "module": "athlete-brand-matching",
  "route": "/modules/athlete-brand-matching",
  "success": true,
  "duration_ms": 120,
  "error_code": null,
  "properties": {
    "flow": "athlete-brand-matching",
    "step": "opened",
    "module_slug": "athlete-brand-matching"
  }
}

Unknown event names are rejected. Content-like or secret-like properties are dropped before storage.

GET /api/telemetry/report?from=<iso>&to=<iso> returns aggregate usage and friction metrics for the authenticated user. Admins may pass user=<uuid|email|name> (legacy user_id=<uuid> is still accepted) to scope the report to a specific user.

GET /api/telemetry/product-health?from=<iso>&to=<iso>&user=<uuid|email|name> returns the internal Product Health report for admins only. The response reuses product_events aggregates and adds last activity, active user/session lists, latest event metadata, and a timeseries array (bucketInterval of hour or day) for the tracking dashboards under /admin/hub. The user filter accepts a UUID for an exact match or a free-text fragment matched against email and display name; legacy user_id=<uuid> is still accepted. Non-admin users receive 401 or 403 from the server-side role check.

POST /api/telemetry/hub-sync (admin only) triggers an immediate push of pending local product events, model-usage logs, chat transcripts, and the due cached Subscription Usage snapshot to the configured remote hub. The response preserves the combined { ok, skipped, synced, error } fields and adds the individual results as { productTelemetry, modelUsage, chatTranscripts, subscriptionUsage }, each with { ok, skipped, synced, error }. Subscription Usage keeps its own persisted 15-minute report lease, so this action does not bypass its provider-refresh limit. The same combined sync runs automatically in the background after events are recorded when hub_mode is remote.

POST /api/hub/telemetry/ingest (hub instances only, HMAC-signed) accepts telemetry batches from connected instances: { instanceUrl, events: [{ id, createdAt, userId, userEmail, userName, sessionId, eventName, module, route, properties, durationMs, success, errorCode, platform }] }. Events are stored idempotently in hub_instance_product_events keyed by (instance_host, source_event_id), and the sender is auto-registered as a monitored instance with discovery source telemetry.

PATCH /api/hub/health/instances/[id] with issueTelemetryCredential: true lets a hub admin issue or reset a random per-instance telemetry credential. The plaintext value is returned only in that admin response, while the hub stores only its hash, binds it to the visible sender identity, and disables monitoring for explicit re-approval. The credential is copied to the customer instance through the remote-hub settings and stored there encrypted via POST /api/telemetry/hub-credential; GET returns only whether it is configured. Reissuing provides the authenticated recovery path after token loss.

POST /api/hub/telemetry/enroll validates an already admin-issued credential against the stable x-clapilot-instance-id; fleet-wide HMAC authentication alone cannot create or replace the binding.

POST /api/hub/telemetry/ingest and POST /api/hub/telemetry/model-usage/ingest additionally require x-clapilot-instance-credential. The credential must match both the hub-side hash and the immutable sender identity approved for that monitored instance, so one spoke cannot submit monitoring telemetry for another host.

POST /api/hub/telemetry/model-usage/ingest (hub instances only, HMAC-signed and per-instance authenticated) accepts { instanceUrl, logs: [...] }, with each log limited to { id, createdAt, userId, userEmail, userName, providerType, providerSlug, providerLabel, requestedModel, effectiveModel, requestKind, transport, status, inputTokens, outputTokens, totalTokens, cacheCreationInputTokens, cacheReadInputTokens, durationMs }. Prompt/session data, endpoints, errors, raw usage payloads, prompt-layer details, and metadata are not accepted by this contract. The hub validates UUIDs/timestamps, clamps non-negative integer counters, truncates text fields, stores rows idempotently in hub_instance_model_usage_logs by (instance_host, source_log_id), and returns { ok, received, accepted, inserted }.

POST /api/hub/subscription-usage/ingest (hub instances only, HMAC-signed and per-instance authenticated) accepts { instanceUrl, snapshot }. The sender normalizes the cached Codex, Claude Code, Grok, and Ollama snapshot before transmission; credentials and arbitrary provider metadata are never accepted. The Hub validates and normalizes the snapshot again, then replaces the latest row for that instance in hub_instance_subscription_usage_snapshots. The endpoint returns { ok, accepted, checkedAt }.

GET /api/hub/telemetry/instances (admin only) lists the local instance plus, in local hub mode, all monitored instances with telemetry summaries (totalEvents, eventsLast7d, activeUsersLast7d, tokensLast7d, lastEventAt) and health/monitoring metadata. Monitoring metadata includes the persisted classification, separate periodic status/summary/check time, telemetry state (never, fresh, stale), latest hub receipt time, 24-hour error rate/operation count, and per-host thresholds. tokensLast7d is the sum of total_tokens reported during the previous seven days.

PATCH /api/hub/health/instances/[id] (admin only) updates only the supplied instance credentials and monitoring fields, so concurrent edits do not restore stale values. Monitoring fields are classification (kunde|eigen|dev|intern), monitoringEnabled, telemetryStaleAfterHours (default 26), errorRateThreshold as a fraction (default 0.15), errorRateMinOperations (default 20), and the one-shot issueTelemetryCredential operation described above. Enabling monitoring explicitly approves the current target for scheduled outbound probes; auto-discovered targets start disabled. Invalid classification, threshold, identity, and credential responses are localized from the request UI language. This admin contract is intentionally not exposed as an agent tool.

POST /api/hub/chat-transcripts/ingest (hub instances only, HMAC-signed) accepts chat transcript batches from connected instances: { instanceUrl, records: [{ id, source, createdAt, sessionKey, sessionTitle, channelType, model, userId, userEmail, userName, messages, metadata: { toolTrace, ... } }] } with source in personal_chat|group_chat|agent_run. messages accepts user, assistant, and tool roles. Tool-calling assistants use { role: "assistant", content: "", tool_calls: [{ id, type: "function", function: { name, arguments: {} } }] }; tool results use { role: "tool", tool_call_id, name, content }. Camel-case toolCalls/toolCallId is also accepted, but function arguments must be JSON objects. Max 200 records per request and 200 messages per record; inline data: base64 URIs are stripped server-side and message content is capped. Invalid tool-call messages are discarded. Records are stored idempotently in hub_instance_chat_transcripts keyed by (instance_host, source, source_record_id); the response is { ok, received, accepted, inserted }.

GET /api/hub/chat-transcripts/stats (admin only) returns { hub_mode, chat_transcript_sync_enabled, instances: [{ instanceHost, isLocal, records, sessions, lastReceivedAt, sources: [{ source, records, sessions, lastReceivedAt }] }], totals }, always including the local instance and, in local hub mode, all instances that have synced transcripts.

GET /api/hub/chat-transcripts/export?instances=<csv>&sources=<csv>&from=<iso>&to=<iso>&format=native|messages (admin only) streams an application/x-ndjson download combining hub-synced spoke transcripts and the hub's own local chats (instances=local restricts to local data). format=native remains the full-fidelity session archive, now including tool messages and a session-level toolTrace: "complete"|"unavailable"; it never drops records. format=messages emits training samples with a leading current-persona system message, complete user/tool/assistant turns, and a top-level tools schema list whenever calls are present. Samples are windowed at complete-turn boundaries with a target maximum of 32 messages; a single larger turn remains intact. Records whose tool trace cannot be linked or is incomplete are dropped, and traceable segments on either side become separate samples. Rows synced before metadata.toolTrace existed are treated as unavailable.

A tool-bearing format=messages line has this shape:

{"tools":[{"type":"function","function":{"name":"calendar_list_events","description":"…","parameters":{"type":"object","properties":{"start":{"type":"string"}},"required":["start"]}}}],"messages":[{"role":"system","content":"Du bist Angela, der Clapilot-Copilot. …"},{"role":"user","content":"Prüfe meinen Kalender"},{"role":"assistant","content":"","tool_calls":[{"id":"call_1","type":"function","function":{"name":"calendar_list_events","arguments":{"start":"2026-07-24"}}}]},{"role":"tool","tool_call_id":"call_1","name":"calendar_list_events","content":"[]"},{"role":"assistant","content":"Es gibt keine Termine."}]}

The training contract has three strict rules: function.arguments is a JSON object, never a JSON-encoded string; every role:"tool" message has string content (structured source values are serialized); and tools is a top-level sibling of messages. Call IDs are renumbered per row and every row ends with a non-empty assistant response. Rows without calls omit tools.

Adding dryRun=1 runs the same selected messages pipeline without streaming a file and returns JSON: { ok, format, conversations, messages, toolCalls, toolMessages, conversationsWithTools, droppedTurnsUntraceable, sessionsWithDroppedTurns, runsWithIncompleteTrace, toolSchemasFromRegistry, toolSchemasDerived, derivedToolNames, suspiciousWithoutToolCall }. derivedToolNames is capped at 100 names; suspiciousWithoutToolCall counts emitted rows whose assistant text refers to likely calendar, mail, task, document, or participant data but has no tool result message.

GET /api/hub/telemetry/instances/[id]/report?from=<iso>&to=<iso>&user=<uuid|email|name> (admin only) returns the per-instance tracking report. id=local reads local product_events and agent_model_request_logs; a monitored instance UUID reads the hub-synced product and model-usage tables (requires local hub mode). The response shape matches the Product Health report plus an instance descriptor and a tokenUsage section:

{
  "tokenUsage": {
    "totals": {
      "requests": 42,
      "inputTokens": 120000,
      "outputTokens": 18000,
      "totalTokens": 138000,
      "cacheReadTokens": 24000,
      "cacheWriteTokens": 5000
    },
    "byModel": [
      {
        "providerType": "openai",
        "providerSlug": "openai-main",
        "providerLabel": "OpenAI",
        "model": "gpt-5.1",
        "requests": 42,
        "inputTokens": 120000,
        "outputTokens": 18000,
        "totalTokens": 138000
      }
    ],
    "byProvider": [],
    "timeseries": [
      { "bucket": "2026-07-17T10:00:00.000Z", "inputTokens": 120000, "outputTokens": 18000, "totalTokens": 138000 }
    ]
  }
}

The model and provider arrays contain at most 15 entries ordered by total tokens. Token aggregation includes all request statuses and uses the same hour/day bucket interval as product-event activity.

Auth behavior

  • protected APIs return 401 when unauthenticated
  • admin routes enforce requireAdminRole() checks
  • session context resolves from clapilot_session cookie
  • native runtime service auth uses short-lived Bearer tokens for module runtime APIs; remote slave/client module calls may also use Hub HMAC headers derived from CLAPILOT_HUB_SHARED_SECRET

Desktop updates

The Sparkle update endpoints are public (auth: none). They expose signed release metadata and archives while keeping the private-repository GitHub token on the server.

  • GET /api/desktop-updates/{app}/appcast.xml
    • app must be clapilot or remote-runner; other values return 404
    • returns the newest matching appcast-clapilot.xml or appcast-remote-runner.xml from up to ten recent GitHub releases as application/xml; charset=utf-8
    • returns 404 when no recent release contains that appcast, 503 when GITHUB_RELEASES_TOKEN is not configured, and 502 when GitHub cannot be reached successfully
  • GET /api/desktop-updates/{app}/download/{filename}
    • filename must be one traversal-free path segment ending in .zip or .delta
    • clapilot accepts Clapilot-* assets but excludes Clapilot-Remote-Runner-*; remote-runner accepts only Clapilot-Remote-Runner-*
    • streams the exact matching asset from up to ten recent releases as application/octet-stream, with Content-Disposition and Content-Length when known; the server does not buffer the full archive
    • returns 404 for an invalid/unknown asset, 503 when the server token is missing, and 502 for an upstream GitHub failure

Endpoint groups

Auth

  • /api/auth/login
    • POST: rate-limited per client IP and per email with exponential backoff (in-memory per app instance). Once the free failed-attempt budget is exhausted, further attempts return 429 with a localized error and a Retry-After header (seconds); the lockout doubles per additional failure up to 15 minutes. A successful login clears the email counter. X-Forwarded-For/X-Real-IP are only honored when CLAPILOT_TRUST_PROXY_HEADERS=true (set this only when the app is reachable exclusively through a proxy that overwrites those headers); otherwise the per-IP dimension is disabled entirely — spoofable headers cannot bypass the limiter, and no shared bucket exists that an attacker could lock to deny login to everyone. Without trusted headers, protection is per-email only. Known tradeoff: anyone who knows an email address can force that account into repeated lockouts (bounded at 15 minutes per episode); this is the standard cost of per-account throttling. The login form surfaces the localized 429 message including the retry window
  • /api/auth/logout
  • /api/auth/me
  • /api/auth/update
  • /api/profile
    • GET: return the signed-in user profile metadata used by /profil, including display_name, email, avatar_url, per-user chat_preferences, normalized memoryPreferences.share_with_team (default true; sharing is opt-out), and agent_pet_key (bubbles by default). chat_preferences.speech_to_text_provider is device or openai_realtime and defaults to device
    • POST: update the signed-in user profile picture with { avatar_url }; accepts null to remove the stored picture and currently expects a PNG/JPG/GIF/WebP data URL payload. The same route also accepts { chat_preferences } for per-user chat UI preferences such as show_tool_calls, ui_language, and speech_to_text_provider, { memoryPreferences: { share_with_team } } for the new-memory team-sharing preference (enabled by default; explicit false opts out), and { agent_pet_key } for the chat activity Pet selection
  • /api/profile/pets
    • GET: return built-in and signed-in-user custom Profile Pets used by Settings -> Profile -> Pet; custom pets point at authenticated generated-image URLs
    • POST: register a generated image as a custom Profile Pet with { key|agent_pet_key, label|name, preview_image_id|image_id, activity_image_id?, description?, still_prompt?, animation_prompt?, select_for_profile? }. If activity_image_id is omitted or points to a static image, Clapilot derives a transparent looping laptop-working GIF from preview_image_id for the pet activity state.
  • /api/automation-result-targets
    • GET: list selectable result-delivery targets for automations. Returns Haupt-Chat, Teamchat #general, the signed-in user's accessible Teamchat channels/groups, approved Telegram/WhatsApp/Signal/iMessage DMs linked to the signed-in user, and approved Telegram/Slack/WhatsApp/Signal/iMessage/instance-bridge groups
  • /api/auth/agent/system-token (machine-token mint endpoint)
    • POST: mint a short-lived Bearer token for system/skill callers authenticated via x-clapilot-agent-system-secret; accepts scopes[], optional ttlSeconds, and optional subject to issue a user-scoped token for user-owned APIs such as app integrations
  • /api/push/devices
    • POST: register or refresh one signed-in Apple account/instance subscription with { installationId, instanceId, token, platform, bundleId, environment, deviceName?, appVersion? }. installationId is stable for the app installation; token rotation updates the shared installation record without replacing its other subscriptions
    • DELETE: unregister only the signed-in account/instance subscription with { installationId, instanceId, platform, bundleId }; other accounts on the installation remain subscribed
    • APNs payloads include the subscription's instance_id and an allowlisted target_route. Delivery selects only subscriptions whose user still exists in the current instance, and invalid APNs tokens deactivate the installation
  • /api/user/menu-preferences
    • GET: return the signed-in user's persisted sidebar/menu layout preferences (version: 1, groups[] with key, itemOrder, hiddenItems)
    • POST: save { preferences } with the same schema; invalid shapes return 400

Onboarding

  • /api/onboarding
    • GET: return whether the first-login onboarding flow is enabled plus the signed-in user's persisted onboarding state (status, currentStep, timestamps, and optional templateSetup marker).
    • POST: advance, complete, skip, or restart the flow with { action, currentStep?, ui_language? }. Progress is stored in user_profiles.onboarding_state_json.
  • /api/onboarding/templates
    • POST: queue background personalized-template generation from the optional onboarding step with { document_ids, ui_language? }. Validates that the documents are visible to the signed-in user, caps the batch at 10, and enqueues a one-off, user-owned agent task (via createScheduledTask) that reads each document and authors a reusable Canvas template. The agent's final reply is delivered to the user (chat + push); the call also marks onboarding_state_json.templateSetup as queued.

Documents

  • /api/documents
    • GET: list document metadata lazily for the Dokumente UI, scoped to shared documents plus the signed-in user's private documents, including filename suggestion metadata (original_file_name, suggested_file_name, auto_renamed), extracted tax fields (amount_cents, currency, datum), and related_mandanten. Supports limit, offset, search, mandant_id, kategorie, folder_view (all, none, google-drive, microsoft-365, folder), folder_id, sort_field, sort_direction, and optional include_facets=1 for sidebar counts. mandant_id matches the primary dokumente.mandant_id or any document_mandanten relation. document_id returns metadata for one record so deep-linked previews can open without loading the full archive.
  • /api/documents/upload
    • POST: accepts multipart file, typ, and titel plus optional document metadata including mandant_id, datum, amounts, and folder_id. When supplied, folder_id must be a valid UUID for an existing document_folders row; invalid or missing folder targets return 400, and the inserted dokumente row stores the folder reference.
  • /api/documents/inbox
    • POST: accepts multipart files plus optional folder_id, repeated directories entries to recreate dropped folder trees in documents, and AI-processing controls for large batches: processing_mode (full, index_only, sample), processing_confirmed=1 for confirmed full processing, and optional processing_limit for sample batches. Large full-processing uploads return 409 with requiresProcessingConfirmation until explicitly confirmed.
  • /api/documents/folders
    • GET: list folder tree rows visible to the signed-in user, including the per-user private Persönlich folder
    • POST: create a folder with { name, parent_id?, visibility_scope? }; omitted scope creates a shared folder
  • /api/documents/folders/[id]
    • PATCH: rename or re-parent a folder with { name?, parent_id? }
    • DELETE: delete a folder and lift contained documents/subfolders one level up
  • /api/documents/[id]
    • GET: download the stored document file as an attachment
    • PATCH: update document metadata with any subset of { folder_id, mandant_id, typ, titel, beschreibung, datum, amount_cents|amount, currency, quartal, jahr, kategorie, related_mandant_ids }. related_mandant_ids replaces the non-primary document relations while preserving the primary mandant_id relation.
    • PUT: replace the stored file bytes for an existing document and refresh document indexing metadata (max 100 MB). PDF documents keep the strict guard (request Content-Type must be application/pdf and the body must carry the %PDF- magic bytes); non-PDF documents accept a raw body whose Content-Type matches the stored mime type (application/octet-stream bypasses the mime check). Used by the native Apple iPad signing flow (PencilKit annotations) and by the macOS Documents folder sync to push local file edits
    • DELETE: delete the document record and its stored file
  • /api/documents/[id]/export-pdf
    • POST: export a Word Editor-compatible document as A4 PDF, save the generated PDF as a sibling document record in Clapilot, and return the created document metadata plus a download URL
  • /api/documents/[id]/preview
  • /api/documents/[id]/thumbnail
    • GET: authenticated PNG thumbnail of the first PDF page (rendered via pdftoppm, cached for 300s); used by document list/grid tiles
  • /api/documents/[id]/analysis
    • GET: return AI-side document extraction status plus extracted preview text, extraction metadata, generated bullet summary, and filename suggestion metadata for the document preview sidebar; the route also ensures a background index job exists when the document has a file path
  • /api/documents/[id]/workflow
    • GET: ensure + return the document workflow { automation }. Once extraction status is processed, Clapilot analyzes the extracted text and AUTO-EXECUTES the detected actions (no separate approval step): it returns a 1-2 sentence summary, context_label, document_kind, matched mandant, an array task_actions (a single document can yield several tasks — e.g. one per next step in a meeting summary), a calendar_action, a follow_up_action, and highlights. Each created/updated task carries task_id and action_kind (created or updated). Tasks are created in aufgaben with source_type='document' (linking the document back into each task); calendar entries use source_origin='document'. Before creating, a suggested task is matched against existing open tasks for the same client and tasks already linked to the document — a strong title match UPDATES that task (repointing a manual task to the document, or appending the document link to an email-sourced task) instead of creating a duplicate. The analysis + execution is cached per document via a content signature, so repeated reads do not re-run the model or re-create tasks; it also emits an aufgaben UI mutation reload when tasks are created/updated. automation is null until extraction is processed. The document detail view (web + Apple) renders this as the "Workflow" timeline section. There is no POST approval route — actions are applied automatically.

beA

  • /api/bea/import
    • POST: authenticated multipart upload for one beA export ZIP via file or the first files entry. Requires the admin feature flag app_settings.bea_import_enabled; it is disabled by default. The route parses XML metadata and attachments, stores files under _inbox/bea, upserts bea_nachrichten by nachrichten_id, upserts imported attachment rows in dokumente with source_type='bea', enqueues document indexing, and starts a native background run with sessionKey=system:bea-inbox:process.

Admin terminal

  • /api/admin/terminal/sessions
    • POST: admin-only and requires app_settings.developer_mode_enabled=true plus the bundled terminal module to be active. Creates a short-lived node-pty Bash session in the Clapilot web container and returns { sessionId, cwd, shell, cols, rows, pid }.
  • /api/admin/terminal/sessions/[id]/stream
    • GET: admin-only SSE stream for terminal output events. The stream sends JSON messages with type=ready|output|exit|error.
  • /api/admin/terminal/sessions/[id]/input
    • POST: admin-only input write for an existing terminal session with { data }. Input chunks are capped server-side.
  • /api/admin/terminal/sessions/[id]/resize
    • POST: admin-only PTY resize with { cols, rows }.
  • /api/admin/terminal/sessions/[id]
    • DELETE: admin-only close for an existing terminal session. Idle and max-age cleanup also close forgotten sessions.

Terminal UI is rendered by the bundled module at /modules/terminal; module discovery hides it while Developer mode is disabled. These endpoints remain first-party app APIs because they manage process-local PTY sessions. Sessions are not resumable after container restart. Session lifecycle events are recorded in admin_terminal_audit_events without storing terminal input/output content.

Wiki

  • /api/wiki/pages
    • GET: list active Wiki pages visible to the signed-in user or a user-scoped agent system token. Supports search, limit, offset, and include_archived=true; searches title, summary, Markdown content, and tags.
    • POST: create a manual Wiki page with { title, slug?, markdown_content?, summary?, tags? }; creates immutable revision 1 and returns { page }.
  • /api/wiki/pages/[id]
    • GET: load one Wiki page by UUID, slug, or stable topic_key, including provenance/evidence metadata and revision/proposal counts.
    • PATCH: make a manual edit with any subset of { title, slug, markdown_content, summary, tags, status, change_summary }; appends an immutable revision. The client cannot relabel the edit as generated or replace provenance fields.
    • DELETE: archive one Wiki page and append an archive revision; returns { page, archived: true }.
  • /api/wiki/pages/[id]/revisions
    • GET: list immutable page revisions newest first; supports limit and offset.
  • /api/wiki/pages/[id]/revisions/[revisionId]/revert
    • POST: admin-only. Restores a historical snapshot as a new manually owned revision; the historical row is never changed.
  • /api/wiki/proposals
    • GET: list proposals; supports page_id, comma-separated state, limit, and offset. The response includes can_review for the signed-in admin UI.
    • POST: create a generated draft with { target_page_id?, topic_key?, slug?, title, markdown_content, summary?, tags?, source_type?, source_refs, assertion_refs, evidence_refs, evidence_checked_at?, metadata?, proposed_by_ref? }. Assertion/evidence refs are exact objects containing at least { kind, id }. Exact duplicates or semantic candidates at/above the fixed wiki-semantic-v1 Jaccard threshold (0.78) return 409; the signature uses title, summary, and H1-H3 headings and is identical in the web helper, native publisher, and migration backfill.
  • /api/wiki/proposals/[id]
    • GET: read one proposal.
    • PATCH: admin-only review with { decision: "approved" | "rejected", review_note? }. Approval does not mutate the live page. Approval rejects private, terminal, conflicted, unresolved-conflict, or evidence-pending canonical assertions. Candidate assertions promoted by this review enqueue projection work under each assertion's real instance_key.
  • /api/wiki/proposals/[id]/publish
    • POST: admin-only. Atomically publishes an approved, non-stale proposal, appends its immutable revision, and marks the proposal published.

Wiki read/manual-write and proposal-create APIs accept normal signed-in user cookies and short-lived Bearer tokens minted by /api/auth/agent/system-token with modules:api:read or modules:api:write. Review, publish, and revert require a signed-in admin. Generated producers should create proposals instead of calling the manual page mutation route; manually protected pages reject unreviewed generated writes at both helper and database boundaries. Authenticated chat/Live Voice wiki_upsert_page calls are explicitly user-directed: the tool proxy sends source_type=manual and metadata.userDirected=true, producing a protected manual revision. Autonomous Dreaming remains proposal-only.

Notes and dictation

  • /api/notizen-audio
    • POST: authenticated multipart upload for a note page voice attachment; requires file, noteId, and pageId, accepts optional transcript, durationMs, contentText, and contentHtml, stores the audio bytes, and returns { attachment, transcriptText }
  • /api/notizen-audio/[id]
    • GET: authenticated download/stream endpoint for one stored note voice attachment owned by the signed-in user
  • /api/modules/notizen/api/notes/[id]/duplicate-local
    • POST: clone a read-only source note into a normal editable local note; returns the new { note, pages, assets?, duplicated_from_note_id } payload used by web, Apple, and agent flows
  • Notizen note/folder/page payloads now also expose source metadata for synced imports:
    • notes: source_kind, source_external_id, source_synced_at, is_read_only, source_metadata
    • folders: system_key, is_read_only
    • pages: source_external_id, source_metadata
  • read-only source notes reject direct note/page mutation and note-audio uploads; the supported write path is the duplicate-local endpoint above

Bundled module APIs

  • /api/modules/canvas/api/files
    • GET: list recent signed-in-user Canvas .html files recursively from .clapilotaicore/canvas, with optional limit, search, and folder
    • POST: create a new Canvas file with { title?, path?, folder?, content_html? }; omitted path generates a unique .html filename and creates missing subfolders as needed
  • /api/modules/canvas/api/files/[...path]
    • Add ?shared=1 to read or update a shared Canvas file in the instance-wide shared scope. Shared files are collaborator-writable; delete remains owner-only.
    • GET: load one Canvas file and return { file, content_html }
    • PUT/PATCH/POST: replace one Canvas file with { title?, content_html|contentHtml|html }
    • DELETE: delete one Canvas file
  • /api/modules/canvas/api/folders
    • POST: create a Canvas subfolder with { path }
  • /api/modules/canvas/api/templates
    • GET: list signed-in-user Canvas templates from .clapilotaicore/canvas-templates, with optional limit and search
    • POST: create a template from JSON { name, description?, kind?, source_text?, template_html? } or from multipart upload field file for PDF, Word, Excel, or CSV sources; PDF uploads are converted into image-backed template HTML with placeholder overlays for detected dynamic values, and other supported sources are stored with an extracted preview plus draft placeholder HTML for agent refinement
  • /api/modules/canvas/api/templates/[id]
    • GET: load one Canvas template including { template_html, fields, source_text_preview }
    • PUT/PATCH/POST: update a Canvas template with { name?, description?, kind?, template_html|templateHtml|html?, fields? }
    • DELETE: delete one Canvas template and its stored uploaded source when present
  • /api/modules/canvas/api/templates/[id]/files
    • POST: render a saved template into a new Canvas .html file with { title?, path?, folder?, data? }, replacing {{field}} placeholders from data
  • /api/modules/canvas/api/preview/[...path]
    • GET: serve one Canvas HTML file as a no-store preview response for sandboxed display
  • /api/modules/canvas/api/folders
    • GET: list Canvas folder paths, including empty folders
    • POST: create a Canvas folder with { path }
  • /api/modules/canvas/api/folders/[...path]
    • PUT/PATCH: rename a Canvas folder with { path }, moving contained Canvas files under the new folder path
    • DELETE: delete a Canvas folder and its contained Canvas files/subfolders
  • /api/modules/canvas/export-pdf
    • POST: render submitted Canvas HTML (title, optional path, content_html, optional idempotency_key) into a private PDF Documents entry. Optional print controls are page_format (A4, A3, A5, Letter, Legal), orientation (portrait, landscape), and margin (narrow, normal, wide, or 050 millimeters; margin_mm is the numeric alias). If none are supplied, an existing document @page rule is preserved; HTML without @page retains the edge-to-edge A4 portrait default. Each explicit print control overrides its matching page descriptor. The response includes the created document metadata, documentUrl, downloadUrl, and best-effort pages. HTML explicitly marked with data-clapilot-document="a4" and .page containers uses the same fixed A4 boxes as the Canvas preview; page overflow or incompatible page geometry returns 422 without creating a document.
    • repeated requests with the same authenticated owner and idempotency_key return the original Documents row with deduped=true; agent calls derive this key from the durable run ID plus normalized export arguments so restart replay cannot create a duplicate PDF mutation
  • /api/canvas-style
    • GET/PUT/POST: read or update the instance-wide Canvas style guide (brand colors, fonts, heading/body sizes, box/table styling, default logo) stored in canvas_style_settings; the same store backs the canvas_get_style_settings / canvas_update_style_settings agent tools
  • /api/modules/cases/api/health
    • GET: module health for the bundled Cases module and schema readiness
  • /api/modules/cases/api/options
    • GET: compact Mandanten and user/lawyer options for case forms
  • /api/modules/cases/api/cases
    • GET: list legal cases/matters with optional q/search, status, mandant_id, assigned_user_id, practice_area, limit, and offset
    • POST: create a legal case with title, optional case_number, status, priority, practice_area, mandant_id, assigned_user_id, court/reference fields, conflict-check fields, dates, and description
  • /api/modules/cases/api/cases/:id
    • GET/PATCH/DELETE: read, update, or delete one legal case
  • /api/modules/cases/api/cases/:id/overview
    • GET: case summary plus party, key-date, communication, and linked-entity counts
  • /api/modules/cases/api/cases/:id/timeline
    • GET: merged case timeline across communication logs, key dates, and linked documents/tasks/calendar/email/note records; supports filter, limit, and offset
  • /api/modules/cases/api/cases/:id/parties
    • GET/POST/PATCH/DELETE: manage case parties such as client, opposing party, opposing counsel, court, witnesses, experts, insurance, and other contacts
  • /api/modules/cases/api/cases/:id/key-dates
    • GET/POST/PATCH/DELETE: manage case deadlines, hearings, filings, limitation dates, appointments, and review dates; rows may reference a linked calendar event
  • /api/modules/cases/api/cases/:id/communications
    • GET/POST: list or add communication log entries for email, phone, meeting, letter, portal, fax, or internal notes
  • /api/modules/cases/api/cases/:id/links
    • GET/POST/DELETE: link or unlink existing document, task, calendar_event, email, draft, or note entities to a case
  • /api/modules/cases/api/linkable
    • GET: search existing link targets with type=documents|tasks|calendar and optional q
  • /api/modules/social-media/api/* (bundled Social Media module; replaces the legacy /api/modules/linkedin/api/* surface)
    • GET health, GET bootstrap (legacy Settings LinkedIn card contract), GET accounts, POST accounts/mastodon, POST accounts/bluesky, DELETE accounts/:platform/:id (legacy DELETE accounts/:id = linkedin)
    • GET/POST posts, GET/PATCH/DELETE posts/:id, POST posts/:id/publish|schedule|unschedule, POST posts/publish-due (worker/service entry, cross-user). GET posts accepts status, q, limit (1-200), and a non-negative offset; deterministic ordering allows the UI and agent bulk workflow to enumerate every reviewable page. POST posts/:id/review-action accepts { action, language?, requestId?, expectedWeeklyPrompt? } for draft and ready_for_review posts and claims/completes one guarded, idempotent agent rewrite; the claim atomically rechecks that status, while stale/orphaned in_progress jobs are restored before a replacement claim. For action=regenerate, bulk callers pass the confirmed focus in expectedWeeklyPrompt; generation uses that exact snapshot, then final persistence takes a short FOR UPDATE strategy-row lock and applies only when the stored value still matches. A mismatch discards the model output, restores the claimed post's prior review status, and returns { code: "weekly_focus_changed" } with 409 so the caller aborts the remaining batch. No database lock spans the external model call. Failed/cancelled IDs require a fresh requestId, failures restore the pre-claim status only while that job remains the current active claim, target/text changes cancel late completion, and media-only updates do not conflict. POST posts/:id/media atomically and idempotently appends one validated media item without replacing concurrent draft edits.
    • POST generate (AI draft { brief, tone?, platforms?, language? } -> { title, content, hashtags[] })
    • GET media, POST media/import (kind: upload | video-studio | livestream-asset), GET media/file?path=, DELETE media?path=
    • GET|POST oauth/start?platform=linkedin|youtube&accountType=&returnTo= and GET oauth/complete; oauth/complete is the only public (unauthenticated) endpoint of this module, allowlisted in src/app/api/modules/[slug]/api/[...endpointPath]/route.ts
    • details: Social Media
  • /api/modules/whiteboard/api/* (bundled Whiteboard module; boards are visible to their owner plus, when the owner enables team visibility, to every signed-in user — delete/unshare stay owner-only)
    • GET health
    • GET/POST boards, GET/PATCH/DELETE boards/:id, POST boards/:id/duplicate
    • POST boards/:id/items, PATCH/DELETE boards/:id/items/:itemId
    • full-scene PATCH writes validate item ids and reject scenes larger than about 15 MB; agent item creation assigns missing ids/z-order and auto-places omitted coordinates
    • details: Whiteboard

Video Studio AI

These first-party routes are authenticated and workspace-global: every signed-in user reads, edits, and deletes the same shared set of AI projects, scenes, characters, and voices. owner_user_id is written at creation as creator attribution only and is never used as a read/write filter. Project detail responses are snapshots containing project, ordered scenes, and linked characters; the project-list response intentionally contains project summaries only.

  • /api/video-studio/models
    • GET: list enabled video-generation providers without secrets and return the configured/default provider-model pair. When app_settings.media_model_catalog.video is non-empty, providers[].models contains exactly those catalog entries grouped by provider and default is the catalog entry marked isDefault; otherwise the route merges configured models, short-timeout best-effort live discovery, and each provider's default_model, and omits providers that still have no selectable models. The response also includes musicProviders/musicDefault and image: { entries, default }. Image entries come from media_model_catalog.image; when that catalog is empty, the image section contains only the current effective image-generation provider/model.
  • /api/video-studio/characters
    • GET: return { characters } for the whole workspace. Each character exposes portraitStatus (generating, ready, or failed), portraitError, voiceSampleUrl, voiceSampleUpdatedAt, and metadata-backed voiceId; the workspace-relative voice path is not exposed through this public JSON route. Legacy rows with a portrait resolve as ready, while rows without a portrait resolve as failed. A generating row older than ten minutes is reconciled to failed when listed.
    • POST: create { character } from JSON { name, description?, prompt|appearance_prompt?, generatePortrait?, voice_id? } or multipart fields plus optional image, voice, voice_id, and elevenlabs_voice_id/elevenlabs_voice_name (the character's assigned ElevenLabs voice). A voice upload must be MP3, WAV, M4A, OGG, or FLAC and no larger than 25 MB; it is validated before character creation. The row and source uploads are persisted first and the response returns immediately with portraitStatus=generating; canonical portrait generation and appearance-prompt derivation continue in the background unless portrait generation is disabled.
  • /api/video-studio/characters/[id]
    • PATCH: update any of { name, description, appearance_prompt, voice_id, elevenlabs_voice_id, elevenlabs_voice_name } and return { character }. voice_id is the optional preset name used by providers such as xAI; elevenlabs_voice_id assigns the character's ElevenLabs voice (preselected for scene voice changes, resolved automatically by video_studio_change_scene_voice, and used as the dubbing TTS voice on an ElevenLabs runtime); an empty string clears either. A changed appearance_prompt starts background portrait regeneration and returns immediately with portraitStatus=generating.
    • DELETE: delete the character and return { ok }.
  • /api/video-studio/characters/[id]/portrait
    • POST: start canonical portrait regeneration with optional { prompt } and return the character immediately with portraitStatus=generating.
  • /api/video-studio/characters/[id]/voice
    • GET: stream the character's voice sample with its stored audio content type and private one-hour browser caching.
    • POST: replace the voice sample with multipart field audio (MP3, WAV, M4A, OGG, or FLAC, max. 25 MB) and return { character }.
    • DELETE: remove the stored voice sample and return { character }.
  • /api/public/video-studio/characters/[id]/voice?token=...
    • GET: stream a token-authorized character voice sample for provider ingestion. Video Studio creates these links only for Kie Seedance submissions, stores token hashes in character metadata, and invalidates public-access metadata whenever the sample is replaced or removed.
  • /api/video-studio/ai/projects
    • GET: return { projects } ordered newest first. Summaries include sceneCount, project status, imageModel, and versions, but not scene snapshots or a clipReady count. A storyboard_generating project older than ten minutes with no scenes is reconciled to failed when listed.
    • POST: persist a project from { prompt, target_duration_seconds?, scene_count?, character_ids?, aspect_ratio?, provider_slug?, model?, image_provider_slug?, image_model?, title?, voice_conditioning?, dubbing?, use_end_frames? }, transition it to storyboard_generating, and return its snapshot immediately with 201. metadata.voiceConditioning defaults to true, metadata.dubbing defaults to false, and the selected/effective image model is stored in metadata.imageModel; storyboard and missing start-frame generation continue as one server-side background chain. Character IDs may also be supplied as any workspace character's portrait_image_id or source_image_id and are resolved to the character ID; any remaining unknown IDs return 400 and no project is created.
  • /api/video-studio/ai/projects/[id]
    • GET: return the project snapshot.
    • PATCH: while draft, storyboard_ready, ready, or failed, update { title?, prompt?, image_provider_slug?, image_model?, aspect_ratio?, voice_conditioning?, dubbing?, use_end_frames? } and return { project }. The image provider/model pair is resolved and stored together. aspect_ratio accepts only 16:9 or 9:16 and is restricted to draft, storyboard_ready, or failed; ready/running projects expose it read-only. Voice settings are booleans stored in project metadata.
    • DELETE: delete the project and return { ok }.
  • /api/video-studio/ai/projects/[id]/storyboard
    • POST: transition an existing project to storyboard_generating, return the refreshed snapshot immediately, and regenerate its storyboard plus missing start frames as one server-side background chain.
  • /api/video-studio/ai/projects/[id]/scenes
    • POST: while draft, storyboard_ready, ready, or failed, append an empty ai scene at max(scene_index)+1 with pending status, continuesPrevious=false, and the closest supported default duration for the project's stored model. Returns the complete snapshot. A structural edit changes ready back to storyboard_ready.
  • /api/video-studio/ai/projects/[id]/scenes/order
    • POST: accept { sceneIds }, require the array to contain every current project scene exactly once, and atomically assign contiguous indexes in that order. The immediate (project_id, scene_index) uniqueness constraint is protected by a negative temporary-index phase before final indexes are written. Returns the complete snapshot.
  • /api/video-studio/ai/projects/[id]/frames
    • POST: generate every missing AI-scene start frame and return the refreshed snapshot.
  • /api/video-studio/ai/projects/[id]/generate
    • POST: start eligible per-scene video jobs after every HTML/block scene is already clip_ready; optional { retryFailedOnly, restart, bundle_only, regenerate_frames, provider_slug, model } can restrict submission, restart the project, and override the stored video selection. bundle_only=true requires every scene to be clip_ready and only re-runs the final concat into a new version without resubmitting any provider job. regenerate_frames=true clears every AI scene's start/end frames and clips, then chains frame generation, clip generation, and bundling in the background. A changed provider/model is persisted before fan-out and every AI-scene duration_seconds is re-snapped to that model's supported values. restart=true on a ready project detaches every prior AI clip, preserves start frames, clears prior dub metadata, and submits a complete new run. Each clip uses the stored project aspect_ratio; it is not supplied again in this request. With voice conditioning enabled, OpenAI-compatible providers receive up to three workspace samples as multipart reference audio, Kie Seedance receives up to three tokenized public input.reference_audio_urls when a public base URL exists, and xAI receives one voice_id only for an unambiguous single scene preset. Returns the refreshed snapshot.
  • /api/video-studio/ai/projects/[id]/status
    • GET: poll/reconcile provider jobs, advance scene states, claim pending dubbing work, and run ffmpeg concatenation when all scenes are ready. When dubbing is enabled, dialogue scenes remain clip_ready while metadata.dubStatus advances through pending|dubbing|done|failed; concat waits for done and uses metadata.dubPath. Dubbing claims older than 15 minutes fail. Each concat writes videos/<output_slug>-v<N>.mp4 plus its matching thumbnail and appends metadata.versions[] using SQL now(); finalVideoPath/thumbnailPath point to the latest version. A legacy unversioned final is adopted as v1 before the next concat writes v2.
  • /api/video-studio/ai/projects/[id]/cancel
    • POST: cancel the active project state, reset generating scenes, and return the snapshot in storyboard_ready.
  • /api/video-studio/ai/projects/[id]/versions/[version]
    • DELETE: delete one non-latest tracked MP4/thumbnail pair and remove its metadata entry. The latest version cannot be deleted through this route.
  • /api/video-studio/ai/scenes/[id]
    • PATCH: update { video_prompt?, script?, duration_seconds?, character_ids?, continues_previous?, kind?, html_source? }; continues_previous is stored in scene metadata, is forced to false for scene 1, and lets start-frame generation reference the immediately preceding ready AI-scene frame. kind=html with html_source.videoSlug validates and links an existing Video Studio gallery slug. Block-rendered sources use html_source.blocks[], renderedVideoSlug, and renderStatus through the dedicated render route below. Character IDs may also be supplied as any workspace character's portrait_image_id or source_image_id and are resolved to the character ID; any remaining unknown IDs return 400 and the scene is not updated. Returns { scene }.
    • DELETE: while the project is structurally editable, delete the scene, require at least one scene to remain, and renumber the rest contiguously in one transaction using temporary negative indexes. Returns the complete snapshot.
  • /api/video-studio/ai/scenes/[id]/render-blocks
    • POST: accept ordered { blocks: [{ slug, durationSeconds?, textOverrides?: [{ find, replace, scope? }], contentPrompt? }] }, validate every slug and override find value against the installed Video Studio block catalog, optionally fill declared slots through the configured storyboard-model candidates, persist a token-guarded clip_generating claim, and return 202 { scene }. scope is html, js, or both; omission defaults to both. Explicit overrides win over AI-filled values for the same slot. The background chain invokes the Video Studio module's render handler in-process with project aspect, block durations, and flattened scoped overrides; the handler composes the normal manifest and renders HyperFrames into deterministic videos/ai-scene-<scene-id-prefix>.mp4. Success probes the MP4 and writes renderedVideoSlug, renderStatus=ready, duration_seconds, and metadata.htmlRenderDurationSeconds. A matching-token failure is localized; a block render older than 15 minutes without a result reconciles to failed.
  • /api/video-studio/ai/scenes/[id]/dub
    • POST: require clip_ready and at least one non-empty script line, atomically claim metadata.dubPreview.status=generating, and return 202 { scene } while character-aware speech synthesis and timeline sequencing continue in the background. Success stores a workspace-relative M4A audioPath with status=ready; failure stores status=failed and error. Project polling marks a claim stale after ten minutes.
    • GET: for an authenticated workspace user, stream the ready preview as audio/mp4 with private no-store caching. Byte ranges are intentionally unnecessary for these short preview files.
    • DELETE: delete the preview file and remove only metadata.dubPreview, then return { scene }.
  • /api/video-studio/ai/scenes/[id]/dub/apply
    • POST: require a ready preview, remux it onto the current scene clip while ducking existing audio to -30 dB, atomically replace the deterministic applied MP4, keep the scene clip_ready, and store both the compatibility dubStatus=done/dubPath fields and metadata.dubApplied. The applied file becomes the scene's active clip and concat source. A ready project returns to storyboard_ready so the next generation creates a new version. Returns { scene }.
  • /api/video-studio/ai/scenes/[id]/voice-change
    • POST: accept { voice_id, voice_name? } for a clip_ready scene, atomically claim metadata.voiceChange.status=generating, and return 202 { scene } while the background chain extracts the current clip audio (applied dub clip when present), converts it with the ElevenLabs speech-to-speech voice changer, remuxes the converted track over the clip, and stores a workspace-relative replacement clipPath with status=ready (failure stores status=failed and error; claims older than ten minutes reconcile to failed). Concatenation prefers a ready voice-change clip over the dub/raw clip, and a ready project returns to storyboard_ready.
    • DELETE: while not generating, delete the converted clip, remove metadata.voiceChange, and return { scene } with the original audio active.
  • /api/video-studio/ai/voices
    • GET: return { voices: [{ voiceId, name, category, previewUrl, isCustom }] } from the configured ElevenLabs account — own/custom voices (cloned, generated, professional) first, then provider default voices. Errors surface the upstream ElevenLabs message.
    • GET: stream the active applied MP4 to authenticated workspace users with normal video range support.
  • /api/video-studio/ai/block-content
    • POST: accept { block: { slug, durationSeconds?, textOverrides? }, content_prompt }, load the block's curated or HTML-extracted textSlots, and ask the storyboard LLM route for strict JSON whose keys exactly equal the slot keys. Missing, extra, empty, non-string, refusal-shaped, or malformed output fails closed; success returns the canonical block selection with { find, replace, scope } overrides for user review before rendering.
  • /api/video-studio/ai/scenes/[id]/frame
    • POST: generate or edit the scene start frame with { prompt, mode: "generate" | "edit", image_provider_slug?, image_model? }; returns { scene }. Overrides apply to this call only; omission uses project.imageModel. If the chosen provider cannot edit images, edit routing falls back to an edit-capable configured provider while generation continues to honor the exact project selection. The result is dimension-probed and, when its ratio misses the project's canonical frame ratio by more than five percent, scale-to-cover center-cropped without padding and imported as a new lineage-linked generated image before the scene is updated.
  • /api/video-studio/ai/scenes/[id]/retry
    • POST: resubmit one failed AI scene and return { scene }. Optional { provider_slug, model } overrides the provider/model for this scene submission only; with no override the stored project selection is used, while a provider-only override uses that provider's default model. The project default is never changed. Duration support is resolved against the effective model and duration_seconds is persisted only when it must be snapped so the UI matches the submitted clip. The used selection is returned as clipProviderSlug/clipModel and stored in metadata.lastClipModel. Optional { force: true } also permits a clip_ready scene: the old generated_video_id is detached, a ready project transitions back to generating, and the existing reconcile/concat path rebuilds the final MP4 once all clips are ready.

Social media video generation

  • /api/social-media/video-generation
    • POST: submit a text-to-video job through the configured livestream media-generation providers with { prompt, title?, model?, provider_slug?, duration_seconds? }; returns 202 { assetId, status: "generating" }
  • /api/social-media/video-generation/[id]
    • GET: poll one generation job; returns { assetId, status: "generating" | "ready" | "failed", mediaPath, thumbnailPath, error }; ready assets are attached to posts via the module's media/import endpoint with kind: "livestream-asset"

reMarkable integration

  • /api/integrations/remarkable/status
    • GET: return the signed-in user's reMarkable connection status, last sync time, last error, and enabled service flags
  • /api/integrations/remarkable/connect
    • POST: exchange the one-time 8-character reMarkable device code for persisted device/user tokens
    • DELETE: disconnect the signed-in user's reMarkable connection
  • /api/integrations/remarkable/sync
    • POST: perform a manual pull sync into Notizen and Dokumente; notebook .rm pages are converted into Notizen scribble_data, while PDF-only documents stay in Dokumente as previewable files under remarkable/pdf/...

X integration

  • /api/integrations/x/oauth/start
    • POST: start the signed-in user's X OAuth flow; accepts optional scope_presets[] and now defaults to identity plus tweet read/write plus media-write scopes so user-scoped posting and media upload can be enabled from the same connection flow
  • /api/integrations/x/oauth/complete
    • GET: OAuth callback endpoint used by X after consent; persists or refreshes the signed-in user's X tokens and account metadata
    • POST: manual callback-complete helper for pasted callback URLs/codes in the settings UI
  • /api/integrations/x/oauth/status
    • GET: return the signed-in user's X connection status, granted scopes, reconnect requirement, token expiry metadata, and last OAuth/API error
  • /api/integrations/x
    • DELETE: disconnect the signed-in user's X integration and revoke stored tokens where possible
  • /api/integrations/x/me
    • GET: return the connected X account profile from users/me
  • /api/integrations/x/posts
    • GET: list the connected X account's own posts with optional max_results, pagination_token, exclude_replies, exclude_retweets, since_id, and until_id
    • POST: create a new X post with { text?, reply_to_tweet_id?, quote_tweet_id?, media_ids?, tagged_user_ids? }; accepts text-only, media-only, or mixed text+media posts, but rejects quote-posts with attached media and tagged_user_ids without media_ids; text-only posts require tweet read/write and users scopes, while media posts also require media.write
  • /api/integrations/x/posts/[id]
    • GET: load one X post by id
    • DELETE: delete one X post by id
  • /api/integrations/x/media
    • POST: multipart upload endpoint for one or more media files from the connected X account; accepts repeated file parts (or files), optional repeated alt_text, optional media_category, and optional shared, uploads to X, waits for async media processing when needed, and returns the uploaded media_id values for later POST /api/integrations/x/posts calls
  • /api/integrations/x/mentions
    • GET: list mentions for the connected X account with the same timeline query options as /api/integrations/x/posts
  • /api/integrations/x/timeline
    • GET: list the connected X account's reverse-chronological home timeline with the same timeline query options as /api/integrations/x/posts
  • /api/integrations/x/users/by-username/[username]
    • GET: resolve one public X user profile by username

Tasks

  • /api/aufgaben
    • GET: list tasks for the signed-in user session, scoped to shared boards plus the user's private boards, with optional board_id, mandant_id, configured status key, prioritaet, faellig, zugewiesen, wiedervorlage, tag, umsatzrelevant, sort, limit, and view query params; sort accepts frist_desc, wiedervorlage, or deal_wert. alle_offen means every status whose category is not done. The default view=full includes normalized attachment URLs. view=compact keeps list fields but omits attachment payloads and the source context snapshot, view=summary returns aggregate total/urgent/overdue and per-status counts, and view=assignee_counts returns the filtered count map used by the task sidebar.
    • POST: create a new task; optional status must be a currently configured key, otherwise the first open category status is used. Manual tasks persist the same traceability shape with a manual source label, context snapshot, optional CRM/follow-up/deal fields, and optional attachments[] entries using the shared chat attachment shape (type, name, size, mimeType, optional data/filePath/url).
  • /api/aufgaben/statuses
    • GET: return the workspace-global ordered status definitions as { statuses: [{ key, label, category, sort_order, is_system }] }.
    • POST: create a status with { label, category }; the server generates a unique stable ASCII key and appends it to the order.
  • /api/aufgaben/statuses/[key]
    • PATCH: rename with { label }, change a non-system category with { category }, or set { sort_order }. The three system keys keep their locked categories.
    • DELETE: delete a non-system status with ?reassign_to=<key>; task and scheduled-task rows are reassigned transactionally first.
  • /api/aufgaben/statuses/order
    • PUT: reorder every status with { keys: string[] }; the array must contain each configured key exactly once.
  • /api/aufgaben/boards
    • GET: list shared boards, the signed-in user's private Privat board, and preset board templates (akquise, marketing, finanzen) so users can quickly create common task board structures
    • POST: create a board with { name, visibility_scope? } or create a preset board with { template_key: "akquise" | "marketing" | "finanzen" }; omitted scope creates a shared board
  • /api/aufgaben/boards/[id]
    • PATCH: rename a board with { name }
    • DELETE: delete a board; requires { target_board_id } so contained tasks are moved to the target board instead of being orphaned
  • /api/aufgaben/assignees
  • /api/aufgaben/[id]
    • GET: load one task plus optional traceability metadata when the task originated from inbox automation; the payload includes source type, a deep link back to the original email, a short context snapshot, and normalized attachments[] with authenticated preview/download URLs for stored attachments. Attachment URLs prefer the configured public_base_url.
    • PATCH: update task fields, optional CRM/follow-up/deal fields, and replace attachments[] with the normalized shared attachment shape; status must be a currently configured task status key.
  • /api/aufgaben/[id]/attachments/[index]
    • GET: authenticated inline/download endpoint for one stored task attachment; resolves the metadata index from the task's attachments[] and streams persisted data: bytes or redirects a stored url
  • /api/aufgaben/[id]/comments
    • GET: list task comments with author metadata, mentions, and normalized attachments[] with authenticated preview/download URLs for stored comment attachments
    • POST: create a task comment with text, optional attachments[], or both; an attachment-only comment is valid
  • /api/aufgaben/[id]/comments/[commentId]/attachments/[index]
    • GET: authenticated inline/download endpoint for one stored task-comment attachment, scoped through the parent task's board permissions
  • /api/aufgaben/[id]/delegate
  • /api/scheduled-tasks
    • GET: list scheduled tasks from DB metadata synchronized against native runtime jobs, including protected native system automations and bundled automations such as the Agent Orchestrator Supervisor for admins. Reads overlay current agent_jobs enabled/next-run/last-run state so system rows remain accurate even when the compatibility metadata row is stale. Bundled/system rows include protection metadata, are shown in their own UI section, and cannot be deleted; returns publicBaseUrl so the UI can render absolute webhook URLs
    • POST: create an automation with either trigger_kind="schedule" plus schedule fields, or one of the event triggers new_mail, new_document, new_calendar_entry, webhook; accepts optional execution_scope="user"|"team" (team is admin-only and runs as the built-in global_team_service principal while created_by remains the audit creator; team runs clear creator-bound user/chat/UI context and do not replay conversation history between runs), optional action="agent_prompt"|"performance_check" (default agent_prompt) — when action="performance_check" the automation is a deterministic instance performance/health probe (maps to the clapilotPerformanceCheck job payload; prompt becomes optional) and an optional performance_config object (windowHours, maxFailureRatePct, maxInteractiveP90Ms) sets the lookback window and pass/fail thresholds; accepts optional profileImageUrl, optional model to pin the runtime model, optional mailbox_scope="all"|"personal"|"agent" for new_mail, optional webhook_token for pre-generated webhook URLs, optional notify_target to assign the automation's implicit destination (main_session, main_session plus sessionId for a concrete web chat, team_chat for Teamchat #general, team_chat plus roomId for a selected Teamchat channel/group, or approved channel_approval Telegram/Slack/WhatsApp/Signal/iMessage groups or Telegram/Slack/WhatsApp/Signal/iMessage DMs), and optional notify_result_mode="always"|"informational"|"errors"|"never" to control whether run results are posted there. Also accepts optional workflow_config for the node editor (schema_version:1, trigger/agent/output nodes, edges, node positions). The compatibility fields still define the primary trigger/agent/output; additional event/webhook trigger nodes can dispatch the same automation, and additional output nodes fan out result delivery to multiple validated targets. Agent nodes accept config.specialized_agent_id, config.model, config.prompt (per-step work order for chained agents), and config.skill_keys (max 12 installed-skill keys injected additively into that agent's run, for default and specialized agents). Multiple agent nodes are executed as a linear chain along agent -> agent edges; normalization enforces linearity (one incoming/outgoing agent edge per node, no cycles) by dropping violating edges. The first chain agent uses the flat prompt; each later agent receives the previous agent's output plus its own config.prompt. For saved workflows, output nodes are authoritative for result delivery; output nodes wired from an intermediate agent deliver that step's result, unwired outputs deliver the final chain result, and removing all output nodes makes the automation run-log-only. always posts all results including background status, informational posts failures and only user-relevant contextual success updates, errors posts failures only, and never posts nothing. Automation priority is no longer configurable and new rows default internally to mittel. Time-based automations still create native runtime jobs and accept 1-10080 minute intervals, event-triggered automations stay app-managed rows and normally dispatch direct native runs when events arrive; new_mail fires after mail AI analysis and respects the configured mailbox scope, the protected document post-extraction new_document automation persists revisions into scheduled_task_event_queue for sequential native draining and provider-limit resumption, other new_document automations receive enriched context after document AI processing, and each webhook delivery runs in a fresh event-scoped session when /api/automation-webhooks/{token} is called. Webhook runs never replay stored history or a prior session summary, so an already oversized legacy automation session is bypassed automatically; event session keys are deterministic for retries and capped at 500 characters.
  • /api/scheduled-tasks/[id]
    • GET: load one scheduled task plus recent runtime runs/events for its automation session
    • PATCH: toggle enabled state, manually trigger immediate execution via run_now=true (without mutating schedule timing), or update title, prompt, admin-only execution_scope, optional profile avatar URL, optional pinned model, optional mailbox_scope for new_mail, optional webhook_token for webhook, optional workflow_config node graph, notify_target, notify_result_mode, metadata, trigger kind, and supported schedules; switching between schedule and event modes converts the automation between native-job-backed and app-managed execution as needed. Automation priority is no longer part of the editable API surface
    • DELETE: remove the native runtime job when present and delete the DB row; preinstalled system and bundled automations are protected and can only be paused, not deleted
  • /api/scheduled-tasks/profile-icon
    • POST: signed-in image-generation helper for the automation editor; accepts { titel?/title?, prompt?, trigger_kind?/triggerKind?, schedule_mode?/scheduleMode? }, generates a square automation avatar through the configured image-generation runtime using the Clapilot tint palette/style guide, stores it as a generated image asset, and returns { profile_image_url, asset } so the caller can save the URL on the automation
  • /api/automation-webhooks/[token]
    • GET/POST/PUT/PATCH: public token URL for one enabled webhook automation. Dispatches only the matching automation and forwards method, query params, selected request headers, and parsed JSON/form/text body as the event payload
  • /api/internal/automation-events
    • POST: internal-only endpoint guarded by x-clapilot-agent-secret or Authorization: Bearer <internal-secret>; dispatches event-triggered automations for new_mail, new_document, or new_calendar_entry. The protected document post-extraction automation durably acknowledges new_document after enqueue even if runtime-job reconciliation is temporarily unavailable; identical pending/processed payloads deduplicate, while changed payloads advance the queue revision.

Widgets (routes remain under /api/mini-apps)

  • /api/mini-apps
    • GET: list the current user's installed Widgets, optionally filtered by search and dashboard_only=true
    • POST: create a structured Widget with { name, description?, widget_definition?, latest_data? }
    • Widgets use widget_definition with supported types stats, list, table, notice, or sections
    • if widget_definition is omitted, Clapilot infers a structured widget from latest_data
  • /api/mini-apps/[id]
    • GET: load one Widget
    • PATCH: update { name?, description?, widget_definition? }
    • DELETE: delete the Widget
  • /api/mini-apps/[id]/data
    • PATCH: update the latest data payload with { data, source? }
    • when richer collection payloads arrive, basic inferred Widgets can auto-upgrade their structured layout during this data update
  • /api/mini-apps/[id]/dashboard
    • PATCH: update personal dashboard placement, per-widget dashboard settings, and visibility for the current user with { dashboard_visible?, dashboard_settings?, dashboard_x?, dashboard_y?, dashboard_w?, dashboard_h?, dashboard_z? }
  • /api/dashboard/layout
    • GET: load the current user's built-in dashboard widget layout map
    • PATCH: update one built-in widget placement for the current user with { widget_id, x?, y?, w?, h?, z? }

Email and drafts

  • /api/emails
    • GET: read-only list of unified personal mailbox messages from the persisted inbox cache when available, with a provider read fallback for an empty cache or explicit search. Accepts folder (INBOX, ARCHIVE, SENT, DRAFTS, TRASH, or a provider-specific folder key such as microsoft-folder:{id}), search, limit, offset, and optional search_scope (all default for searches, folder for folder-scoped search). Global searches walk available IMAP/iCloud folders, use all-mail Gmail search, and use Microsoft Graph message search so Outlook custom folders and subfolders can be returned. Inbox summary rows include mailbox_provider (imap, gmail, microsoft, or apple) / mailbox_address when a source mailbox is known and can also return optional sender_image_url plus sender_image_fit (cover or contain) when the sender maps to a matched Mandant profile or logo
    • POST: explicit personal-inbox refresh used by the E-Mail refresh action. Accepts the same folder/search/paging fields in JSON, refreshes enabled providers, updates persisted summaries, and applies matching Gmail/IMAP inbox filter rules. Provider mutations therefore do not occur during normal GET navigation.
  • /api/emails/folders
    • GET: list personal mailbox folder options for the E-Mail UI. The response includes the unified system folders, custom IMAP folders from the configured Kanzlei mailbox, and nested Microsoft 365 / Outlook folders with provider-specific keys, depth, unread counts where available, and mailbox address metadata.
    • GET /api/emails/filter-rules: list the signed-in user's persistent sender/keyword rules. POST adds a case-insensitive pattern and DELETE ?id= removes one. Matching Gmail and IMAP inbox messages are moved to Nicht relevante mails and marked read during inbox synchronization; the destination label/folder is created on demand.
  • /api/email-senders
    • GET: return selectable senders for manual compose, currently the configured local IMAP/SMTP identity plus enabled personal and Agent Google Gmail identities and Apple Mail
  • /api/emails/automation/backfill
    • POST: authenticated catch-up trigger for personal IMAP inbox rows that are still unanalysed; accepts { ids: string[] }, acknowledges queued work with 202 / { queued: true }, then loads cached or IMAP-backed message detail and runs the same prepared-answer workflow in the background batch. The inbox UI uses the per-message automation endpoint for Gmail, Microsoft 365 Mail, and Apple iCloud rows so provider-backed visible messages can also be analysed without opening the detail view first.
  • /api/emails/[id]
    • GET: loads one personal mailbox message plus prepared-answer metadata (automation) for the email detail flow, visible attachment metadata, linked private dokumente records for attachments, timing badges, detected tasks/deadlines/highlights, synced shared mandant context, friendly retry-safe failure states, detected sender language (detected_language: de, en, or it), and an optional prepared_document / document_action payload only when the workflow created a new private document draft, not merely because the mail contained an attachment; accepts folder. The automation payload can include assignment_suggestion with confidence, alternatives, and linked attachment document ids so low-confidence Mandanten/Vorgang matches stay reviewable. Inline/signature images such as image001.png are filtered out of the normal attachment list before import. Visible personal attachments are imported idempotently into the user's private Persönlich folder with source_type='email_attachment' and a stable source_id, then queued for document indexing; scanned PDFs fall back from pdftotext to local page OCR before optional vision extraction.
  • /api/emails/[id]/automation
    • GET: return the current prepared-answer automation state for one personal mailbox message as { automation } (status, summary, context_label, reply_reason, draft, task_action, calendar_action, highlights, processing); accepts folder and resolves provider-prefixed Gmail / Microsoft 365 / Apple iCloud message ids as well as cached IMAP messages. Clients poll this endpoint for progressive processing.stage updates (contextattachmentsanalysisdraftdone). automation.source_attachments[] entries can carry analysis_summary / extracted_text_preview once extraction finished, and automation.metrics includes attachments_considered (whether all analyzable attachments were content-extracted before drafting), attachments_pending_count, attachments_total_count, and attachments_skipped_count.
    • POST: ensure the prepared-answer workflow has run for the message (summary, Mandant context, reply decision, optional auto-generated reply draft with the user's outgoing signature appended) and return the same { automation } payload; reuses the persisted automation when it is already final. Consumed by the web inbox detail flow and the native Apple Mail detail view.
  • /api/emails/[id]/assignment
    • POST: confirm or change the Mandanten/Vorgang assignment for one prepared email workflow. Accepts { mailbox_scope, mailbox_email, mandant_id, document_ids? }, updates email_thread_automations.mandant_id, records a confirmed assignment_suggestion, and assigns the listed imported attachment documents to the same Mandant.
  • /api/emails/[id]/attachments/[index]
    • GET: authenticated on-demand download for one visible personal mailbox attachment; accepts folder, resolves provider-prefixed message IDs for Gmail, Microsoft 365 Mail, and Apple iCloud Mail, and streams the selected attachment with a download content disposition instead of storing attachment bytes in the mailbox cache. The index is based on the filtered visible attachment list, not raw inline MIME parts.
  • /api/emails/send
    • POST: send a manually composed message through the selected sender. Local identities use SMTP/IMAP sent-folder storage; Gmail identities use the connected Google Workspace Gmail send permission; Apple Mail identities use iCloud SMTP/IMAP with the saved app-specific password
  • /api/emails/[id]/delegate
    • POST: create or reuse a prepared reply draft for the selected personal mailbox message; accepts folder. The source message language is detected across supported UI languages and persisted as source_language; generated replies default to that same language.
  • /api/emails/[id]/actions
    • POST: apply email-detail quick actions such as create_task, create_calendar, mark_read, mark_unread, archive, delete, or move; accepts folder and optional { targetFolder }
    • Gmail personal mailbox rows support mark_read, mark_unread, archive, inbox restore, and trash through the Google Gmail gmail.modify scope; moving Gmail rows into provider-specific IMAP/Outlook custom folders remains unsupported because the personal inbox intentionally exposes Gmail as a label-backed mailbox, not as a foreign folder tree
    • Microsoft 365 / Outlook rows now support mark_read, mark_unread, archive, delete, and move into system folders plus provider-specific custom folder ids through Microsoft Graph Mail.ReadWrite; existing users connected before this scope upgrade must reconnect Microsoft 365 once to grant write access
    • successful mailbox mutations now also persist a UI mutation event so /emails can patch/flash the affected row in place, and create_task additionally emits an aufgaben refresh mutation for open task views
  • /api/emails/batch-actions
    • POST: apply mailbox mutations to multiple personal inbox rows in one request; accepts { action: "mark_read" | "mark_unread" | "delete" | "move", items: [{ id, folder }], targetFolder? } and returns per-message success/failure rows plus aggregate successCount / failureCount for optimistic inbox rollback handling. Uses the same provider paths as /api/emails/[id]/actions, including IMAP, Gmail, Microsoft 365, and Apple iCloud Mail
  • /api/drafts
    • POST: create a personal email draft with { von, an, cc?, betreff, inhalt, inhalt_html?, mandant_id?, quell_email_id?, agent_mailbox_email?, source_language?, reply_language? }. For personal drafts, the current user's configured outgoing email signature is appended before persistence unless the body already ends with that signature. When an HTML signature is configured, Clapilot stores inhalt_html as the HTML alternative and keeps inhalt as the plaintext fallback; uploaded signature logos remain embedded data images in drafts and are converted to CID inline assets during send. The Athlete-Brand Matching module uses this endpoint for outreach drafts, then deep-links to /emails?tab=entwuerfe&draft=:id for review and sending
  • /api/drafts/[id]
    • PATCH: updates draft fields (an, cc, betreff, inhalt, inhalt_html). Passing { reply_language: "de" | "en" | "it" } on an auto-generated source-email draft regenerates the reply from the cached original message in the requested supported language, preserving the user's outgoing signature for personal drafts.
    • POST /api/drafts/:id/send: sends a reviewed draft idempotently. SMTP success and the IMAP Sent-folder copy are persisted separately with a stable Message-ID. The response includes delivery.smtp, delivery.sentCopy, delivery.sentFolder, and delivery.messageId; an append failure returns ok: true plus a warning, and repeating the request retries only the copy. IMAP Sent discovery uses \\Sent SPECIAL-USE and common IONOS/Gmail/Outlook aliases; IMAP_SENT_FOLDER and AGENT_EMAIL_SENT_FOLDER provide explicit mappings.
  • /api/drafts/[id]/send
    • POST: sends a draft and now returns only user-facing failures for the prepared-answer UX
  • /api/email-recipient-suggestions
    • GET: signed-in compose autocomplete with q and limit (1-200, default 100); merges Mandanten, app users, and previously used draft recipients into deduplicated suggestions
  • /api/emails/[id]/inline/[contentId]
    • GET: authenticated inline image (CID) delivery for one personal mailbox message so HTML bodies can render embedded images; the agent mailbox mirror is /api/angela/emails/[id]/inline/[contentId]
  • /api/internal/emails/automation/personal
    • POST: internal-only endpoint guarded by x-clapilot-agent-secret; runs the personal-inbox auto-analysis batch (runPersonalEmailAutomationBatch) and returns { started, skipped, failed }; no-ops with disabled: true when email_auto_analysis_enabled is off
  • /api/email-settings
    • GET: load the current user's personal mailbox credentials state plus outgoing email signature fields (outgoing_email_signature, outgoing_email_signature_html). If no saved signature exists, Clapilot can refresh outgoing_email_signature_suggestion from recent sent personal drafts and connected IMAP/Gmail/Microsoft/Apple Mail sent mail samples.
    • POST: update personal mailbox credentials, plaintext outgoing_email_signature, and sanitized outgoing_email_signature_html. Sanitized HTML signatures may include normal links (https, mailto, tel) and uploaded PNG/JPG/GIF/WebP logos as data images up to the send-time inline asset limit; unsupported data images are removed. Saving mailbox credentials can also populate a signature suggestion from recent sent mail; callers should treat suggestions as reviewable user-facing text, not as an automatically accepted signature.

Call & Fax Agent

  • /api/call-agent/config
    • GET: admin-only load of the singleton SIP/router configuration record plus provider-specific Realtime model dropdown options derived from configured ClapilotAICore provider rows
    • POST: admin-only upsert of the singleton SIP/router configuration record, returning the saved config and refreshed Realtime model dropdown options
    • config now includes published_ip and outbound_published_ip so inbound and outbound NAT/public-address routing can be managed in the UI instead of only via env vars
    • config now also persists realtime_provider, realtime_model, and realtime_voice so the phone worker can target either OpenAI Realtime or Google Gemini Live
    • config also persists fax capability on the same SIP line: fax_enabled, fax_station_id, fax_header_text, fax_transport_mode, fax_supports_inbound, and fax_supports_outbound
    • config now also persists approved_incoming_numbers; only those inbound caller numbers get the full internal Call Agent tool surface, while all other inbound calls are limited to general public information about clapilot.com
  • /api/call-agent/status
    • GET: signed-in runtime status snapshot combining the persisted worker state with the active SIP identity and feature toggles
    • when inbound calling is enabled, the native worker now keeps a live SIP listener registered and exposes its current registration state here
    • the status payload includes realtime_provider in addition to the active realtime_model and realtime_voice
    • shared-line runtime status now also includes active_kind, active_fax_id, and fax feature toggles so the UI and agents can respect the voice-or-fax lock
    • the status payload now also exposes sip_local_port, published_ip, outbound_published_ip, and expected_inbound_udp_ports so inbound forwarding/listener mismatches can be diagnosed from the product UI
    • if CLAPILOT_CALL_AGENT_SIP_LOCAL_PORT is set in env, the status payload exposes that effective runtime port even if the stored config record still contains a different sip_local_port
  • /api/call-agent/calls
    • GET: signed-in recent call history from call_agent_calls
    • each call row may include a generated post-call summary in metadata_json.summary
    • live-call summaries are generated from captured caller transcripts, assistant transcripts, and in-call tool results when available
  • /api/call-agent/faxes
    • GET: signed-in recent fax history from call_agent_faxes, including linked document and mandant labels when available
    • outbound rows end in sent only after the native g711 fax bridge reports a successful transmission; failed real sends stay failed with audit metadata
  • /api/call-agent/faxes/[id]
    • GET: signed-in fax detail with fax audit events from call_agent_fax_events
  • /api/call-agent/faxes/send
    • POST: signed-in outbound fax enqueue request for either an existing document_id or plain text_content; the native worker reuses the Call & Fax Agent SIP line, rejects the request while the shared line is already active with voice or fax, and in fax_transport_mode=g711 renders the transmission into TIFF before starting a real outbound SIP fax call
  • /api/call-agent/faxes/[id]/retry
    • POST: signed-in retry endpoint for failed, blocked, or cancelled faxes
  • /api/call-agent/faxes/[id]/cancel
    • POST: signed-in cancel endpoint for queued or active outbound faxes
  • /api/call-agent/customers
    • GET: signed-in customer lookup for the Call Agent module; searches Mandanten by name, company, phone, or email so the call UI can prefill the target number
  • /api/contacts/import/vcf
    • POST: signed-in multipart VCF import used by Settings -> Contacts; accepts file plus optional ui_language, parses standard vCard fields (FN, N, ORG, EMAIL, TEL, ADR), creates matching mandanten rows without triggering per-contact web enrichment, skips exact email duplicates already present in mandanten, and returns { parsed, created, skipped, failed, results[] }
  • /api/mandanten
    • GET: signed-in Mandanten list for the overview page; supports q, typ, activity, openTasks, deadlines (today, week, month), sort (first_name, last_name, organization, updated, created), and limit. Name sorting uses company/organization names for non-person clients, and the overview persists the selected order in the URL. The response returns the list metadata used by the Priorität / Aufgaben / Mails / Typ / Aktivität table view, including unread_email_count, unreplied_email_count, one direct mail target (email_link_mailbox_scope, email_link_id), and today/deadline context (today_deadline_count, today_deadline_title, due_soon_task_count, next_due_task_title, stale_unreplied_email_count, stale_unreplied_email_subject) so the UI can filter by Frist heute and deep-link into customer-specific work without a second API call
    • POST: signed-in Mandanten create path; after insert, Clapilot now attempts an optional web-profile match and persists website_url, logo_url, and profile_image_url only when the result clears the built-in confidence checks and the admin feature toggle mandant_profile_web_crawl_enabled is enabled. The shared enrichment path prefers Brave when available, but automatically falls back to headless browser search when Brave is missing or rate-limited
    • agent-driven mandanten_* tool writes now emit mandanten.client.updated UI mutation events so /mandanten list/detail views can refresh and highlight in place
  • /api/mandanten/[id]
    • GET: signed-in Mandanten detail fetch used by the manual create/edit form when an existing client is opened for editing
    • PATCH: signed-in Mandanten manual update path for the shared create/edit form; updates the core Stammdaten fields (typ, name, vorname, firmenname, adresse, telefon, email, steuernummer, rechtsform, branche) and returns the updated record
  • /api/mandanten/search
    • GET: signed-in lightweight Mandanten typeahead with q and limit (default 6); used by pickers and reference autocompletes
  • /api/mandanten/duplicates
    • POST: signed-in duplicate check before create/update with any of { typ, name, vorname, firmenname, email, exclude_id }; returns likely duplicate rows
  • /api/mandanten/[id]/ai-summary
    • GET: signed-in AI-generated customer summary for the Mandant detail page; cached, with force=1 to regenerate
  • /api/mandanten/[id]/emails
    • GET: signed-in list of up to 250 mailbox messages matched to the Mandant's known addresses for the customer communication tab
  • /api/mandanten/[id]/enrichment
    • POST: signed-in manual rerun for one Mandant’s web research; returns the refreshed Mandant row including enrichment_status, enrichment_source, enrichment_started_at, enrichment_last_checked_at, enrichment_error, and enrichment_suggestion; returns 409 when the admin feature toggle disables profile crawling. With body action: "accept_suggestion" it applies the pending review suggestion (filling only empty website_url/logo_url/profile_image_url fields, status matched); with action: "dismiss_suggestion" it clears the pending suggestion (status not_found); both action variants work independently of the crawl toggle
  • /api/mandanten/[id]/overview
    • GET: signed-in compact detail-page overview payload for /mandanten/[id]; returns actionable counters for offene/überfällige Aufgaben, Fristen heute bzw. diese Woche, unread/unreplied communication counts, workflow counters for linked calendar events, documents, notes, and email automation agent runs, the preferred direct mail target (email_link_mailbox_scope, email_link_id), and the latest matched mail so the default Vorgang tab can render a rule-based work-first summary without loading the full timeline
  • /api/mandanten/[id]/timeline
    • GET: signed-in merged customer timeline feed across received mails (email_thread_automations + mailbox cache), sent replies (email_drafts), created tasks, created calendar entries, and uploaded documents; accepts filter, limit, and offset for timeline pagination in the Mandant detail page
  • /api/aufgaben/live
    • GET: signed-in SSE bridge for Postgres NOTIFY updates on aufgaben; used by the task board to refetch changed tasks directly from DB state so status/column moves animate in place even when the initiating mutation did not originate from the visible chat stream
  • /api/admin/mandanten/enrichment
    • GET: admin-only status counts for Mandanten web research (pending, running, matched, not_found, skipped, error)
    • POST: admin-only backfill runner for existing Mandanten; accepts limit, optional statuses[], and optional force and processes the selected rows through the shared enrichment path with Brave plus headless-browser fallback; returns 409 when the admin feature toggle disables profile crawling
  • /api/call-agent/calls/start
    • POST: signed-in outbound call enqueue request; forwards to the native clapilot-agent call worker
    • active outbound calls now use the native RTP/live-audio bridge when CLAPILOT_CALL_AGENT_LIVE_AUDIO_ENABLED is not disabled
    • live caller transcription defaults to German (CLAPILOT_CALL_AGENT_TRANSCRIPTION_LANGUAGE=de) so phone-call transcripts stay in German unless intentionally overridden
    • the payload can now include a selected customer_id plus per-call allow_customer_info and allow_documents flags
    • live phone calls now forward Realtime function calls into /api/agent-runtime/tool-proxy using the initiating signed-in user as execution context
    • the live bridge now follows the Call Agent setting realtime_provider, using OpenAI Realtime for openai and Gemini Live for google_gemini
  • /api/call-agent/calls/[id]/end
    • POST: signed-in hang-up request for the selected active call; records ended_by from the current user
  • calls and faxes share the same line lock; voice start requests fail immediately while active_kind = fax
  • inbound calls are created directly by the native worker in call_agent_calls when the registered SIP listener receives a call
  • inbound calls now persist an access-policy marker in call_agent_calls.metadata_json; approved inbound callers receive full tool access through the built-in global_team_service principal, while unapproved callers stay in a public-info-only mode
  • inbound faxes currently enter call_agent_faxes only through the external/native receive handoff endpoint POST /internal/call-agent/faxes/receive, which writes the fax into mandanten/_inbox/..., links a placeholder dokumente row, and then hands it into the existing inbox/document processing flow
  • /api/call-agent/test-connection
    • POST: admin-only SIP registration probe through the native pjsua path
  • native runtime endpoints under /internal/call-agent/* mirror the same fax surface for faxes, faxes/:id, faxes/send, faxes/:id/retry, faxes/:id/cancel, and faxes/receive

Agent mailbox

  • /api/angela/emails
    • GET: list agent mailbox messages; the dedicated Agent Google account's Gmail is preferred when enabled, with the configured agent IMAP mailbox retained as fallback. Accepts folder, search, limit, and optional force=1. Agent Gmail message IDs use the collision-safe gmail:agent: prefix. Inbox summary rows share the same optional sender_image_url and sender_image_fit enrichment as /api/emails
  • /api/angela/emails/[id]
    • GET: load one agent mailbox message plus automation metadata and linked dokumente records for visible attachments; accepts folder. Inline/signature images are filtered out before import. Visible attachments are imported idempotently into Dokumente with source_type='email_attachment' and a stable source_id
  • /api/angela/emails/[id]/attachments/[index]
    • GET: authenticated on-demand download for one visible shared agent mailbox attachment; accepts folder and resolves Agent Gmail or IMAP attachments
  • /api/angela/emails/[id]/delegate
    • POST: create or reuse a prepared reply draft for the selected agent mailbox message; accepts folder. The source language is stored with the draft and generated agent-mailbox replies default to that language.
  • /api/angela/emails/[id]/actions
    • POST: apply agent mailbox quick actions such as create_task, create_calendar, mark_read, mark_unread, archive, delete, or move; accepts folder and optional { targetFolder }. Agent Gmail mutations use the dedicated account's gmail.modify grant
  • /api/angela/emails/batch-actions
    • POST: apply agent-mailbox batch mutations with { action: "mark_read" | "mark_unread" | "delete" | "move", items: [{ id, folder }], targetFolder? }; responses include per-message results plus aggregate counts so web and Apple clients can optimistically update and refetch on partial failure
  • /api/angela/overview
    • GET: aggregate KI-Assistent dashboard data including running email jobs, recent autonomous activity, synchronized scheduled native jobs, and per-task log entries for the expandable dashboard cards

Hub catalog and distribution

  • /api/module-store/catalog
    • GET: signed-in module hub catalog for /modules; resolves the hub target from Admin Hub, serves the local hub registry directly in hub_mode=local, and otherwise proxies the configured remote hub
  • /api/module-store/publish
    • POST: admin-only publish of a workspace module to the configured hub target; writes into the local hub registry when hub_mode=local
  • /api/module-store/install
    • POST: admin-only install of one module version from the configured hub target into the local workspace
  • /api/skill-store/catalog
    • GET: signed-in skill hub catalog for /skills; resolves the hub target from Admin Hub, serves the local hub registry directly in hub_mode=local, and otherwise proxies the configured remote hub
  • /api/skill-store/publish
    • POST: admin-only publish of a workspace skill to the configured hub target; writes into the local hub registry when hub_mode=local
  • /api/skill-store/install
    • POST: admin-only install of one skill version from the configured hub target into the local workspace
  • /api/widget-store/local
    • GET: signed-in local widget list for /modules?tab=mini-apps, plus is_admin for publish/install controls
  • /api/widget-store/catalog
    • GET: signed-in widget hub catalog for /modules?tab=mini-apps; resolves the hub target from Admin Hub, serves the local hub registry directly in hub_mode=local, and otherwise proxies the configured remote hub
  • /api/widget-store/publish
    • POST: admin-only publish of one local structured widget to the configured hub target; writes into the local hub registry when hub_mode=local
  • /api/widget-store/install
    • POST: admin-only install of one widget version from the configured hub target into the local widget registry by slug
  • /api/agent-store/catalog
    • GET: signed-in specialized-agent hub catalog for /modules?tab=agents and Settings -> Agent -> Spezialisierte Agenten; resolves the hub target from Admin Hub, serves the local hub registry directly in hub_mode=local, and otherwise proxies the configured remote hub
  • /api/agent-store/publish
    • POST: admin-only publish of one local specialized agent to the configured hub target as a portable JSON definition; exported data excludes embed deployments, API keys, secrets, and runtime permission bypass
  • /api/agent-store/install
    • POST: admin-only install or update of one specialized-agent version from the configured hub target into the shared specialist catalog; install forces allowRuntimePermissionBypass=false
  • /api/v1/modules
    • GET: local hub catalog endpoint for published modules, available only when the instance runs in hub mode
  • /api/v1/modules/publish
    • POST: signed local hub publish endpoint for modules
  • /api/v1/modules/[slug]/[version]/download
    • GET: local hub artifact download endpoint for one published module version
  • /api/v1/skills
    • GET: local hub catalog endpoint for published skills, available only when the instance runs in hub mode
  • /api/v1/skills/publish
    • POST: signed local hub publish endpoint for skills
  • /api/v1/skills/[slug]/[version]/download
    • GET: local hub artifact download endpoint for one published skill version
  • /api/v1/widgets
    • GET: local hub catalog endpoint for published widgets, available only when the instance runs in hub mode
  • /api/v1/widgets/publish
    • POST: signed local hub publish endpoint for widgets
  • /api/v1/widgets/[slug]/[version]/download
    • GET: local hub artifact download endpoint for one published widget version
  • /api/v1/agents
    • GET: local hub catalog endpoint for published specialized agents, available only when the instance runs in hub mode
  • /api/v1/agents/publish
    • POST: signed local hub publish endpoint for specialized-agent JSON snapshots
  • /api/v1/agents/[slug]/[version]/download
    • GET: local hub artifact download endpoint for one published specialized-agent version

Chat and calendar

  • /api/chat/transcription/realtime/session

    • POST (authenticated): accepts { ui_language?: "de" | "en" | "it" } and requires the signed-in profile to have chat_preferences.speech_to_text_provider="openai_realtime"
    • also requires app_settings.api_live_transcribe_enabled=true; resolves the OpenAI API-key Realtime provider and compatible model selected in Settings -> ClapilotAICore -> Audio, then creates a transcription-only client secret with 24 kHz PCM input, automatic spoken-language detection, balanced-delay streaming, explicit turn commit, a two-minute connection TTL, and a hashed per-user safety identifier
    • returns { client_secret, expires_at, model, webrtc_url, websocket_url } with Cache-Control: no-store; it never returns the long-lived provider key. websocket_url intentionally has no model query because the transcription model is already bound inside the transcription session; passing it as a Realtime conversation model causes the upstream connection to be rejected.
    • web uses webrtc_url; Apple clients use websocket_url and send base64 PCM chunks. Provider/configuration failures are localized and surfaced instead of silently falling back to remote transcription
  • /api/chat

    • normal chat keeps shell/bash snippets inside the normal agent prompt instead of intercepting them server-side
    • provider/model identity questions such as openai oder anthropic?, welches llm?, or bist du gpt-5.4? are answered deterministically from the current session runtime instead of relying on model self-reporting
    • the agent decides from full message context whether to execute a local command or just analyze/explain it
    • direct commands like whoami, pwd, git status --short, /run <command>, and fenced bash blocks are treated as normal user input, not as transport-level shortcuts
    • local shell execution and structured package installs now flow through the runtime agent tool path (exec_command, package_install)
    • Teamchat/group-chat turns can additionally bind the built-in global_team_service execution principal while still keeping persisted history on the room-scoped session key
    • attachment payloads still carry uploaded file bytes for supported image/document types, and may now also include optional hidden filePath metadata so web, floating chat, and Apple chat clients can reference the original file location without exposing that path in the visible transcript UI
    • personal history responses from GET /api/chat/sessions/:sessionId/messages replace persisted inline attachment data URLs with authenticated /api/chat/sessions/:sessionId/messages/:messageId/attachments/:attachmentIndex URLs. This keeps large historical images, files, and audio out of the history JSON while web, iOS, and macOS load the same user-owned bytes on demand.
    • personal-chat document uploads remain active runtime context for up to four subsequent text-only user turns in the same session. The backend rehydrates the most recent stored file bytes for the agent run without copying them into each follow-up row; a new upload, explicit document/reply reference, or conversation-reset phrase replaces or clears that inherited context
  • personal-chat attachment payloads may include type: "audio" with base64 data, mimeType, name, optional durationMs, and optional hidden filePath; /api/chat stores the audio as a user-owned chat asset, runs STT on the backend, injects the transcript into the normal agent turn, and marks the turn for an assistant audio reply. Web and native file pickers classify imported WAV, MP3, M4A, and OGG files as audio attachments (up to 25 MB); in the Apple chat composer this payload can also be produced by pressing and holding the mic button, while a short mic tap remains speech-to-text dictation.

  • team-chat/group-chat turns sent through /api/chat may now also include type: "audio"; the backend runs the same STT step for prompt assembly and persists the recorded user audio inline on the room message so the team-chat timeline can keep rendering it after reload

  • finalized Team Chat agent and automation replies resolve owned audio references from .clapilot/agent-media/<owner>/... into persisted type: "audio" attachment metadata before local paths are removed. Automation delivery carries the persisted service-principal ID as the media owner, matching first-party TTS/image creation instead of borrowing a creator or viewer user scope. First-party TTS creation records owner, canonical path, MIME type, size, and SHA-256 provenance in agent_media_assets; derived audio assembled by ffmpeg/HyperFrames must call media_register_audio once after writing the final file, which records or idempotently verifies the same provenance without synthesizing or uploading anything. Delivery requires that immutable record in addition to owner-directory containment and rejects traversal, symlink, hard-link, replacement, and type/size mismatches. The resolver accepts absolute, workspace-relative, and Markdown-link references and caps the combined audio payload at 15 MB across at most four files. Owned ![... ](/api/generated-images/<id>) automation output is materialized into a persistent Team Chat image attachment at the same delivery boundary; the authenticated source URL is removed from the stored reply so room viewers never depend on the automation creator's user scope. History responses expose stable authenticated attachment URLs instead of repeating audio Base64 bytes; web and native Apple Team Chat timelines load those URLs on demand through their playback controls.

    • personal audio-origin turns now persist message_meta.audioReplyRequested = true, while the final assistant text reply can additionally persist message_meta.assistantAudio with the generated TTS attachment metadata (type, name, size, mimeType, url, optional transcript, optional durationMs)
    • assistant TTS audio replies are currently still limited to personal /chat; team-chat/group-chat audio turns currently return the normal text assistant reply only
    • personal chat and assistant-backed team-chat turns sent through /api/chat no longer support a deterministic /image <prompt> transport shortcut; image-related text remains normal chat input, and the runtime agent may dynamically call images_generate or images_edit when the full conversation context warrants it
    • when a current /api/chat turn includes uploaded image attachments, the backend imports those images as user-owned generated-image assets and passes their ids through clientContext.currentImageAttachmentAssetIds, allowing a later dynamic images_edit tool call to target the latest chat image without bypassing the agent decision
    • personal and team-chat requests may include documentReferences[] (document id/title/type plus optional metadata) from the # reference autocomplete flow; /api/chat persists those refs in chat_nachrichten.message_meta.documentReferences for personal chat and in chat_group_messages.message_meta.documentReferences for group-room Angela turns, then injects document summaries/excerpts into the current prompt as prioritized document context
    • personal-chat and team-chat Angela turns may also include replyReference (messageId, messageRole, authorName, text); /api/chat persists that quoted-message snapshot in message metadata and injects it back into the current prompt. The server also loads images/files from the quoted row's stored attachment JSON and from markdown image links to generated-image assets or workspace files, attaches materialized media only to the agent run, and passes existing generated-image asset ids through to image/video tools without duplicating those assets
    • pending team-chat Angela turns now also persist chat_group_messages.message_meta.assistantToolStatuses while tools are still running so every room viewer can see the same live progress state in the sidebar Angela card
    • persisted assistantToolStatuses entries (personal chat_nachrichten and team chat_group_messages message meta) carry { id, label, state, toolName? }; toolName is the raw runtime tool identifier (for example Bash, emails_list_messages) and lets clients pick the matching icon in the chat tool-call timeline log. The retained list keeps the most recent 24 tool calls per assistant turn; web chat renders the latest five by default and reveals the older retained calls on demand
    • while a model is streaming a thinking block before its visible answer, web and Apple chat surfaces keep the latest ten rendered lines visible by default and provide an inline disclosure for the full block; collapsing again returns to the latest-ten-line view
    • clapilot.tool SSE frames may include event.todoList: Array<{ id, label, status }> with status = pending|active|done; each frame replaces the current checklist, and todo-producing tools are omitted from the generic tool-status list
    • personal and team assistant rows persist the latest checklist as message_meta.assistantTodos, including pending writes and the final write without forcing unfinished items to done; chat history and floating-chat history hydrate the same field after reload
    • one enabled specialized @agentHandle mention in a personal chat turn routes that turn into the mentioned specialist; personal chat accepts one specialist target per turn. In Team Chat, every explicitly mentioned invited specialist is queued in parallel as one exclusive target set, so Angela does not also produce a duplicate reply
    • team-chat room specialists are stored as room-scoped invitations with reply_mode=mention_only|all_messages; unmentioned team-chat turns fan out to every invited enabled specialist whose mode is all_messages, using the same detached specialist pending/finalization flow as explicit @agentHandle mentions
    • team-chat specialist mentions run with the current user request plus a compact visible room-context bundle of the previous relevant team-chat messages (currently capped at 10), including prior named specialist replies; Angela/main-agent turns keep the normal broader team-chat transcript replay
    • the floating personal chat now uses the same specialized @agentHandle composer flow as /chat, so specialists can also be invoked from the sidebar widget
    • specialized @agentHandle mentions are now detached background tasks: /api/chat persists the pending specialist bubble immediately, returns control to the parent chat route without blocking on the specialist run, finalizes that same bubble later when the specialist task completes, and now persists live assistantToolStatuses updates for those backend specialist runs as tools start/finish
    • direct specialist replies are persisted as separate assistant rows instead of only reusing the original personal user-turn antwort; personal specialist rows use message_origin = assistant_specialist|assistant_specialist_delegated, async main-agent follow-up rows use message_origin = assistant_async_callback, while team-chat rows reuse sender_display_name plus assistant identity in message_meta
    • specialist assistant rows may carry message_meta.assistantAgentId, assistantAgentHandle, assistantAgentName, optional delegatedByAgentId, optional delegatedByAgentName, and invocationType = mention|delegation so the frontend can render named assistant bubbles consistently after reload
    • when the main agent delegates via delegate_to_specialized_agent, the tool now returns immediately with a queued task payload; once the specialist finishes, the backend asynchronously triggers a second main-agent run and persists that later follow-up as a separate assistant message
    • when the main agent fans out via spawn_clapilot_subagents, the tool creates one clapilot_subagent_tasks row per generic worker inside a clapilot_subagent_batches row, persists visible pending worker bubbles using message_origin = assistant_subagent in personal chat, and triggers one final async main-agent callback using message_origin = assistant_subagent_callback after every worker is completed or failed
    • when no explicit personal sessionId is provided, /api/chat now resolves or creates the user’s main personal session instead of falling back to the latest active custom session
    • the first user turn in a fresh custom personal session now triggers automatic session naming from that first prompt; manual session renames stay authoritative and are not overwritten later
    • group-chat requests accept groupChat=true plus optional roomId so /team-chat can keep the shared team room separate from future room-based chat variants while still reusing the KI-Assistent streaming path
    • each streamed turn records its native runtime gateway key as message_meta.assistantRunSessionKey on the pending user-turn/room row; because the runtime stores the chat message id as the agent_runs idempotency key, history reads reconcile stuck placeholders against the backing run: a terminal run without a delivered answer clears the placeholder within ~2 minutes (message_meta.assistantRunExpired), and a run that completed while no web producer was alive (e.g. web container restart mid-turn) back-fills its output_text as the reply (message_meta.assistantRunRecovered) instead of losing the answer; rows without run linkage keep the coarse created_at/updated_at staleness backstop
    • persisted KI-Assistent replies in personal chat sessions now also trigger Apple push notifications through APNs when device registration is configured
  • /api/chat/steer

    • signed-in endpoint used by the web chat queued-message "send now" control
    • POST with { sessionId, groupChat?, message?, attachments? } resolves the same native runtime sessionKey as /api/chat, then forwards to POST /internal/runs/steer
    • succeeds for running OpenAI-Codex subscription bridge turns through Codex app-server turn/steer
    • succeeds for running Claude subscription bridge turns by writing a realtime user message into the active claude -p --input-format stream-json --output-format stream-json process
    • also supports live steering for active native/embedded-PI runs: during a tool call the follow-up is injected into that tool result; during model generation the runtime aborts the active provider request, keeps the original stream alive, and immediately starts a continuation provider step with the follow-up
    • native steering is acknowledged only while a provider/tool step owns a guaranteed delivery path; the small transition/finalization gap returns 409 with reason=native_boundary_transition, leaves the browser message queued, and lets the normal queue dispatcher send it as the next turn
    • when no live steering channel exists, the native runtime checks the latest persisted agent_runs row instead of claiming that no native engine exists: still-running rows return 409 with reason=run_not_steerable and remain queued, while terminal rows that still own the pending chat placeholder return retryAsNewTurn=true with reason=run_interrupted|run_finished; an older completed run cannot trigger recovery for a newer turn still in preflight
    • for retryAsNewTurn=true, the web endpoint immediately reconciles the terminal run's stale assistant placeholder; the chat page and floating widget abort any stale browser stream, reload history, and let the existing queue dispatcher start the message as a new turn
    • assistant-origin automation/system alerts are persisted into the same personal chat timeline and reuse the same APNs delivery path
    • persisted KI-Assistent replies in group-chat rooms now trigger Apple push notifications through APNs when device registration is configured
  • /api/chat/stop

    • signed-in endpoint used by the chat composer stop control (web chat page and floating widget)
    • POST with { sessionId, groupChat? } resolves the same native runtime sessionKey as /api/chat/steer, finalizes every pending assistant placeholder in that personal session or team room (assistant_pending = FALSE, stop notice, message_meta.assistantRunStopped = true), and forwards to POST /internal/runs/abort for the session's default gateway key plus every distinct message_meta.assistantRunSessionKey recorded on the cleared placeholders
    • works without a live browser stream: after a reload or runtime restart the stop control still clears the stuck turn and cancels the backing agent_runs rows, so the composer unblocks immediately
    • responds { ok, stoppedMessages, agentAbort: { ok, aborted: { orchestrator, native, dbRuns, terminated }, errors[] } }; agentAbort.terminated is true only when every targeted runtime confirms that its exact live run stopped (or no live/persisted run remains). An unreachable runtime or a pending cancellation keeps the outer placeholder-cleanup response successful but returns agentAbort.ok = false, terminated = false, and a runtime error in errors[].
  • /api/chat/audio/[id]

    • GET: authenticated byte-range-capable streaming endpoint for one stored personal chat audio asset owned by the signed-in user; used for both recorded user audio messages and assistant TTS replies in web and Apple chat timelines
  • /api/chat/link-preview

    • GET: signed-in URL metadata extraction (url query param) returning { url, final_url, title, description, image_url, site_name } for chat link preview cards
  • /api/chat/group/directory

    • GET: signed-in list of the user's accessible team-chat rooms as a compact directory payload (used by pickers such as automation result targets and share flows)
  • /api/chat/group/messages

    • GET: load room history from chat_group_messages for the requested room (room=<room-id>), including message_meta.documentReferences for referenced documents/images, message_meta.replyReference for quoted replies, optional message_meta.assistantToolStatuses for in-flight Angela work, and update the current member heartbeat. Large inline agent avatars in message_meta.assistantAgentProfileImageUrl are replaced with stable entity URLs (/api/specialized-agents/:id/profile-image or /api/scheduled-tasks/:id/profile-image) at the shared persistence boundary. Owned agent-audio attachments similarly expose authenticated /api/chat/group/messages/:messageId/attachments/:attachmentIndex URLs instead of returning their bytes in every history poll.
    • GET /api/specialized-agents/:id/profile-image and GET /api/scheduled-tasks/:id/profile-image: authenticated, cacheable delivery for stored inline avatar data. Team Chat references include a content-hash v parameter, allowing the browser to retain matching private responses as immutable cache entries while an avatar change automatically produces a new URL. Unversioned legacy references keep the short revalidation policy. Oversized inline values are decoded only at this delivery boundary and are not copied into new Team Chat messages.
    • GET /api/chat/group/messages/:messageId/attachments/:attachmentIndex: authenticated delivery for one stored agent-audio attachment. Access requires membership in the message's room; the server revalidates the creation-time database provenance, attachment owner, path containment, file identity, single-link state, type, size, and content hash before reading it through a no-follow file handle.
    • POST: persist a human room message for the requested room; used for direct messages in /team-chat, accepts the same attachment JSON shape used by the chat UIs (including optional hidden filePath metadata) plus optional documentReferences[] and optional replyReference, and now also fans out Apple push notifications to the other room participants
  • /api/chat/group/rooms

    • GET: load the signed-in user’s team-chat room list, the member directory including heartbeat presence (is_online, last_seen_at), and a typingByRoom map with short-lived room typing indicators. The list contains active memberships plus every non-deleted public channel; app admins additionally see every private channel, but never other users' direct or group_dm rooms. Each room includes is_member; discovered non-member rooms report unread_count: 0 and the real active member_count. The web and native Apple clients additionally combine this with the enabled specialized-agent catalog for direct specialist DMs and channel invitation search without treating specialists as human room members.
    • POST: create a team-chat room. Channels accept kind: "channel_public"|"channel_private", default to public in the first-party UI, and start with the creator plus explicitly supplied memberUserIds instead of every global user. All new channels/groups start without invited specialists. Passing specializedAgentId opens or creates a signed-in-user direct specialist room backed by a persisted agent:<handle> room default.
    • GET /api/chat/group/rooms/[id]: return the accessible room's active human members, agent_to_agent_enabled, the built-in mainAgent membership/reply mode, and invited specialized agents. Opening a public channel auto-joins a signed-in non-member; app admins may inspect private channels without becoming members. Private channels remain membership-only for regular users.
    • PATCH /api/chat/group/rooms/[id]: channel owners, room admins, and app admins can set visibility: "public"|"private"; direct/group rooms reject visibility changes. clapilot-members (#general) is public by default, but admins may make it private, which suspends auto-join and enables member exclusion; while it is public, memberUserIds changes remain rejected because auto-join includes everyone. Room managers can also replace active human membership with memberUserIds, set agentToAgentEnabled for public/private channels, invite/update/remove the main agent, or independently invite/update/remove specialized agents. The creator remains a member, removed people lose private-room visibility, presence and mention eligibility, and can be re-invited safely.
  • /api/chat/group/rooms/[id]

    • GET: load one accessible room plus human members[], built-in mainAgent, and room-scoped specializedAgents[] invitations; the room and agent records expose mention_only|all_messages reply policy
    • PATCH: update room metadata/member lists, optional visibility: "public"|"private", and optional agentToAgentEnabled; manage the main agent with { action: "inviteMainAgent"|"updateMainAgent"|"removeMainAgent", replyMode? }; or manage specialist invitations with { action: "inviteSpecializedAgent"|"updateSpecializedAgent"|"removeSpecializedAgent", specializedAgentId, replyMode? }. Channel owners, room admins, and app admins can manage channels even when an app admin is not a member. Agent settings and visibility apply only to channel_public and channel_private. The default general channel starts public; admins may make it private to suspend auto-join and manage exclusions, and switching it back to public resumes auto-join for everyone.
  • /api/chat/group/typing

    • POST: refresh or clear the signed-in user’s short-lived room typing heartbeat with { roomId, active }; active=false removes the typing marker immediately
  • /api/chat/group/room-config

    • GET: return the effective main-agent runtime model for the requested team-chat room (room=<room-id>), combining the persisted room override with the current runtime session model lookup, plus mainAgent membership/reply policy and the room-scoped specializedAgents[] invitation list used by team-chat mention autocomplete
    • PATCH: update the requested team-chat room model via { roomId, model }; null or omitted model clears back to the room default, specialist agent:<handle> refs are ignored for team rooms, and the route also best-effort syncs the runtime session model so the next team-chat turn uses the new selection immediately
  • /api/specialized-agents

    • GET: signed-in users receive the enabled shared specialist catalog for direct specialist DMs and room invitation search (id, handle, name, description, optional profile_image_url, enabled, sort_order); team-chat channel mention autocomplete filters this catalog to the active room's invited specialists. Admins receive the full admin-managed catalog including prompt, optional default_model_ref, profile_image_url, channel link state (telegram_channel_enabled, telegram_allow_without_approval, has_telegram_bot_token, telegram_bot_token_hint, whatsapp_channel_enabled, whatsapp_allow_without_approval, whatsapp_number), skill_keys, allowed_tool_names, allowed_auth_resource_keys, include_core_memory_tools, personal_memory_enabled, allow_runtime_permission_bypass, bundled metadata, and resolved local skill metadata. Bundled specialists such as agent-orchestrator-supervisor and pet-creator appear in their own UI section, can be edited for allowed fields such as model/prompt, but cannot be deleted or re-keyed.
    • POST: admin-only create path for one shared specialized agent with { handle, name, description?, profileImageUrl?, prompt?, defaultModelRef?, telegramChannelEnabled?, telegramAllowWithoutApproval?, telegramBotToken?, clearTelegramBotToken?, whatsappChannelEnabled?, whatsappAllowWithoutApproval?, skillKeys?, allowedToolNames?, allowedAuthResourceKeys?, includeCoreMemoryTools?, personalMemoryEnabled?, allowRuntimePermissionBypass?, enabled?, sortOrder? }. personalMemoryEnabled defaults to true; P1 persists and forwards it but does not expose a UI control. If profileImageUrl is omitted or invalid, Clapilot stores a deterministic built-in SVG fallback avatar derived from the agent name/handle/description. Uploaded profile pictures may be PNG, JPG/JPEG, or WebP data URLs up to 2 MB. Telegram bot tokens are stored encrypted and are never returned in cleartext. whatsappNumber is not user-entered; the native runtime derives and persists it from the QR-linked WhatsApp account.
    • Runtime session requests carry this setting as specializedAgent.personalMemoryEnabled; changing it alters tool scope and invalidates reusable bridge-session scope hashes.
  • /api/specialized-agents/[id]

    • GET: admin-only fetch for one specialized agent including resolved local skill metadata and the same prompt/tool/auth/approval fields used by the specialist editor.
    • PATCH: admin-only update path for any subset of the same specialist definition fields; prompt, tool allowlist, auth scope, and permission-bypass changes are persisted on the specialist record rather than a separate side table.
    • DELETE: admin-only delete path for user-created specialists; bundled specialists return 400 because shipped system workflows reference them by stable bundled_key
  • /api/specialized-agents/[id]/channels/whatsapp/auth

    • GET: admin-only status for the specialist's dedicated WhatsApp Web session; returns the same shape as /api/agent-runtime/channels/whatsapp/auth, including linked, connected, derived selfE164, authDir, and any active QR login state.
    • POST: admin-only specialist WhatsApp auth actions with { action: "start" | "wait" | "logout", force?, timeoutMs? }; start generates a QR code, wait checks whether the QR scan completed, and logout removes the specialist-scoped WhatsApp auth state. The native runtime stores the derived linked number back on the specialist after successful login.
  • /api/specialized-agents/[id]/channel-approvals

    • GET: admin-only list of Telegram/WhatsApp approval rows scoped to that specialist only; these rows are excluded from the general ClapilotAICore -> Kanäle approval queues.
    • POST: admin-only approval decision with { id, status } where status is approved, denied, or pending. Specialist approvals do not require a Clapilot user mapping; inbound runs execute in the specialist envelope with the specialist prompt, tool allowlist, auth scopes, default model, and permission mode.
  • /api/specialized-agents/profile-icon

    • POST: admin-only image-generation helper for the specialized-agent editor; accepts { name?, handle?, description?, prompt? }, generates a square profile avatar through the configured image-generation runtime using the Clapilot tint palette/style guide, stores it as a generated image asset, and returns { profile_image_url, asset } so the caller can save the URL on the agent. Image-generation transport errors are normalized to a user-facing retry/upload message instead of exposing provider internals.
  • /api/specialized-agents/[id]/embed

    • GET: admin-only fetch for one specialist's external embed deployment; returns the stored deployment plus a placeholder HTML snippet template using the current Clapilot base URL. The deployment includes public_allowed_tool_names[], which is the only tool allowlist used by public website/API-key runs.
    • PUT: admin-only upsert path for { publicSlug, enabled, runtimeAccessMode, publicAllowedToolNames, supportsFileAttachments, allowedOrigins, welcomeMessage }; also ensures a restricted agent_service_principals row exists for that public specialist deployment. runtimeAccessMode is forced to public_safe; publicAllowedToolNames is filtered to public-safe tools and is independent from the specialist's internal allowed_tool_names, Core Memory, auth scopes, and runtime-bypass settings.
  • /api/specialized-agents/[id]/embed/keys

    • POST: admin-only create path for one publishable embed API key tied to the specialist deployment; accepts optional { label, expiresAt } and returns { api_key, plain_text_key, snippet, deployment }, where plain_text_key is shown only once. Each key also owns a stable session_id mapping so external callers can omit a session header and still continue the key-bound specialist session.
  • /api/specialized-agents/[id]/embed/keys/[keyId]

    • DELETE: admin-only revoke path for one specialist embed API key
  • /api/specialized-agents/embed-abuse

    • GET: admin-only overview of blocked/rate-limited public embed traffic; accepts optional agentId and limit (default 80) and returns recent abuse events plus per-deployment counters for the specialist embed settings UI
  • specialized-agent hub snapshots

    • schema: { kind: "clapilot.specialized-agent", schema_version: 1, handle, name, description, prompt, default_model_ref, skill_keys, allowed_tool_names, allowed_auth_resource_keys, include_core_memory_tools }
    • intentionally omitted: public embed config, API keys, secrets, service principals, and runtime permission bypass
  • /api/public/agents/[slug]

    • OPTIONS: CORS preflight for public/embed agent metadata requests
    • GET: public metadata endpoint for one enabled embedded specialist; requires X-Clapilot-Embed-Key, checks the deployment origin allowlist against the incoming Origin, and returns { agent, deployment } for widget bootstrapping without a normal user session, including deployment flags such as runtime_access_mode and supports_file_attachments
  • /api/public/agents/[slug]/messages

    • OPTIONS: CORS preflight for public/embed message requests
    • POST: public message endpoint for one enabled embedded specialist; requires X-Clapilot-Embed-Key, checks the same origin allowlist, accepts { sessionId?, message }, runs the specialist in isolated public-embed scope, and returns { sessionId, agent, reply, run } so a website widget can keep lightweight continuity without using the normal personal/team chat routes. If sessionId is omitted, the endpoint uses the API key's stored session_id mapping instead of creating a new random session.
    • the endpoint now also applies server-side safety and abuse guards before any model run:
      • obvious prompt-injection attempts targeting hidden instructions/system prompts are blocked
      • obvious requests for secrets, credentials, or tokens are blocked
      • obvious requests for internal/private workspace activity are blocked
      • per deployment + IP limits cap both the number of new sessions per 24 hours and the message frequency per 10 minutes / 24 hours
    • blocked public requests return a safe assistant reply instead of executing the specialist, and rate-limited JSON responses include Retry-After
  • /api/v1/models

    • GET: OpenAI-compatible model list for a generated specialist embed API key supplied as Authorization: Bearer <key> or X-Clapilot-Embed-Key. The key is scoped to one enabled specialist deployment, so the response lists only aliases for that specialist such as the public slug, handle, agent:<handle>, and clapilot/<slug>. /v1/models is also supported as a compatibility alias for clients that expect the standard OpenAI path at the domain root.
  • /api/v1/chat/completions

    • POST: OpenAI-compatible chat-completions endpoint for generated specialist embed API keys. Accepts normal { model, messages, stream?, user? } payloads and returns OpenAI-style chat.completion JSON or text/event-stream chunks.
    • model must match one of the aliases returned by /api/v1/models; the API key determines the underlying specialist, prompt, public embed tool allowlist, service principal, and public-safe runtime envelope.
    • /v1/chat/completions is also supported as a compatibility alias for clients that expect the standard OpenAI path at the domain root.
    • server-side execution defaults to the public embed guardrails: no authenticated user context, only public_allowed_tool_names[] from the embed deployment, no fallback to internal specialist tools, no stored auth scopes, no Codex/Claude subscription bridge routing, prompt/secret/internal-data safety checks, and deployment/IP rate limits. An empty public allowlist means exactly zero tools. X-Clapilot-Session-Id or user may override the lightweight session key for rate-limit continuity; when both are omitted, the API key's stored session_id mapping is used so the same key continues the same specialist session. Safety checks inspect the latest user message only, so upstream system/developer instructions that mention secrets or API keys do not trigger the public-safe canned reply by themselves. Non-streaming responses include clapilot_session_id, and both streaming and non-streaming responses expose X-Clapilot-Session-Id.
    • publishable embed/API keys cannot switch to the normal internal specialist runtime. Full internal specialist capability requires an authenticated signed-in Clapilot route, not these public endpoints.
    • vision input supports OpenAI-style image_url content parts with data:image/...;base64,... values and raw base64 image fields such as image_base64, base64, or data when paired with type: "input_image" / type: "image".
    • when an allowed public specialist uses images_generate, generated images are stored under the embed service principal and returned as tokenized public links. Non-streaming responses include these links in choices[0].message.content and in the extension field clapilot_images[].
    • the specialist settings dialog shows these exact OpenAI-compatible paths in Externe Kanaele -> Web / UI Embed -> API Keys alongside the generated key. The API uses /api/v1/chat/completions as the stable path and selects the specialist through the OpenAI model field.
  • /api/public/generated-images/:id?token=...

    • GET: public-safe generated image download used by public specialist/API and Team Chat image output. The image id alone is not enough; the per-image token must match the hash stored in the asset metadata.
  • /api/public/generated-videos/:id?token=...

    • GET: public-safe ready-video download used by Team Chat. The raw token is returned only in the generated link; its SHA-256 hash and room scope are stored in generated_videos.metadata.public_access.
  • /api/specialized-agents/overview

    • GET: signed-in lightweight overview for the end-user-friendly Agenten segment inside /geplante-aufgaben?view=agents; returns { summary, agents, running, is_admin } with per-agent counts, optional profile_image_url, last activity, running-session counters, active specialist sessions, and per-agent recent_runs[] history previews for expandable row drill-down. Non-admin responses scope running/history entries to the caller’s own personal specialist runs plus accessible team-chat rooms instead of exposing global chat content
  • /api/agent-runtime/tool-catalog

    • GET: admin-only native tool-catalog endpoint for the specialized-agent settings UI; returns the current ClapilotAICore tool inventory with normalized { name, description, category, parameterNames, requiredParameterNames, isCore, isMainAgentOnly, selectableForSpecialists, selectableReason }
    • the response also includes core_tool_names, specialist_blocked_tool_names, and selectable_tools so the admin UI can render a searchable picker instead of a freeform allowlist textarea
  • /api/agent-runtime/auth-catalog

    • GET: admin-only specialized-agent auth/access catalog endpoint; returns { auth_resources, default_selected_keys } for the current provider/runtime auth scopes (for example image generation, TTS/STT, OpenAI, Gemini, GitHub, Brave, and Perplexity) so the settings UI can render an explicit access picker instead of implicit global secret access
  • /api/chat/sessions

    • GET: list personal chat sessions for the signed-in user, with the main session first and other pinned sessions sorted ahead of the remaining recents
    • POST: with ensureScope=<key> (e.g. video-studio), get-or-create a stable, non-main feature workspace session keyed per (scope, user) with a deterministic chat-<scope>-<user> id (kept separate from the main session); with createFresh=true, create a new custom personal session; without either, resolve or create the user’s main personal session
  • /api/chat/sessions/[id]

    • GET: load one personal session
    • PATCH: update { title?, model?, isPinned? }; setting title is treated as a manual rename and prevents later auto-title overwrites, while isPinned toggles persisted session pinning for custom personal sessions
    • DELETE: delete one custom personal session; the main personal session is protected and returns a validation error instead of being removed
    • POST /api/chat/sessions/[id]/reset: clear one personal session's visible message history, rotate its runtime session_user, and best-effort reset the attached agent runtime session so the next turn starts with fresh context while keeping the chat session itself
  • /api/chat/models

    • GET: returns signed-in user's available chat models as { models }
    • model entries include runtimeProvider and supportsSteering; the web chat uses supportsSteering to show queued-message direct steering only for currently steerable models such as OpenAI-Codex, Claude subscription rows, or non-specialized models when the active core is embedded_pi (the internal adapter behind the Agent Orchestrator clapilot-code harness)
  • /api/chat/live/session

    • returns provider-specific live connection metadata based on the configured Realtime provider/model selection
    • OpenAI API responses use the GA Realtime API: /realtime/client_secrets for the short-lived client_secret and /realtime/calls for the WebRTC SDP handshake, with realtime_api: "ga" in the response; OpenAI-family responses also include websocket_url so native Apple clients connect to the configured Realtime provider URL instead of hard-coding OpenAI
    • OpenAI-compatible or Azure responses may return realtime_api: "beta" when routed through their provider-specific legacy-compatible Realtime surface
    • Google Gemini responses include transport: "gemini_websocket" plus websocket_url, access_token, instructions, and Gemini tool declarations
  • /api/chat/live/relay/session

    • POST: authenticated Apple Watch live entrypoint; creates the provider Realtime session server-side, opens the upstream provider WebSocket from Clapilot, and returns transport: "clapilot_relay" plus relay_session_id
    • GET /api/chat/live/relay/[sessionId]/events: authenticated SSE stream of upstream Realtime JSON events back to the watch
    • POST /api/chat/live/relay/[sessionId]/input: accepts one Realtime JSON event from the watch, including input_audio_buffer.append, tool responses, and cancellation events, then forwards it to the upstream provider WebSocket
    • DELETE /api/chat/live/relay/[sessionId]: closes the server-held upstream WebSocket; relay sessions also expire automatically after inactivity
  • /api/chat/live/tools

    • live tool execution may return triggerReload, refreshTopic, uiActions[], and mutationEventId
    • includes navigate_user_to_page, which resolves validated internal Clapilot routes and emits navigation.open so chat/live agents can move the user directly into pages like E-Mail detail, calendar event, document folder (/dokumente?folder=:folder_id), documents preview/editor, tasks, Mandanten, Website Canvas, or other internal app destinations
    • includes google_meet for Live Voice so spoken sessions can join, inspect, speak in, transcribe, summarize, and leave managed Google Meet browser participants. Join first validates the meeting space through the Meet REST API with the dedicated Agent Google account. The session payload reports accountEmail plus browserAccountMode: signed_in when the persistent managed-browser profile already has Google login cookies, or guest_fallback when OAuth access was verified but Google browser login is still absent. Successful join automatically requests captions and starts the server-side voice-to-voice bridge when enabled and remote Meet audio is available
    • the live tool catalog now includes exec_command for direct shell/bash execution inside the native runtime workspace
    • the live tool catalog now includes mini_apps_* and widgets_* operations for listing, creating, updating, and filling Widgets from chat/live-agent flows
  • widget tool calls stay structured-only and never accept raw HTML; widget_definition can be omitted on create so Clapilot infers a native layout from latest_data

    • the live tool catalog includes notizen_* operations for folder, note, and page management inside the Notizen module, including notizen_duplicate_local for read-only reMarkable imports
    • the live and native tool catalogs now include faxes_list, faxes_get, faxes_send, faxes_retry, and faxes_cancel for the shared Call Agent fax workflow
    • the native runtime tool proxy mirrors calendar CRUD through calendar_list_events, calendar_get_event, calendar_create_event, calendar_update_event, and calendar_delete_event; list results defensively collapse provider duplicates by canonical calendar, external event ID, and instance start time before briefings or agents receive them; calendar_create_event accepts optional calendar_name to create directly in a matching Apple/iCloud calendar and returns an error when the Apple calendar name is missing or ambiguous; create/update accept attendees: string[] and send Google invitations via sendUpdates=all (an empty update list removes all guests), and also accept create_google_meet or conference_type="google_meet" to create/add a Google Meet link. Agent-created Google events prefer the dedicated Agent Google account configured under App Connections and fall back to the user's connected account. Google updates use events.patch with stored ETag/If-Match, returning conflicts instead of overwriting newer remote changes.
    • the native runtime tool proxy mirrors Aufgaben board/task operations through aufgaben_list_boards, aufgaben_create_board, aufgaben_list_statuses, aufgaben_manage_statuses, aufgaben_list_tasks, aufgaben_get_task, aufgaben_create_task, aufgaben_move_status, aufgaben_assign_task, aufgaben_update_task, and aufgaben_delete_task; status filters and mutations accept the workspace's configured keys, invalid keys return the valid list, board/task reads are scoped to shared boards plus the authenticated user's private boards, board listing includes the preset templates akquise, marketing, and finanzen, and task create/update tools accept the structured CRM/follow-up/deal fields used by Akquise and partner workflows
    • the live/native tool catalogs include cases_list, cases_get, cases_create, cases_update, cases_delete, cases_link_entity, cases_unlink_entity, cases_add_communication, and cases_add_key_date for the optional bundled Cases module
    • the native runtime tool proxy mirrors mailbox and draft flows through emails_list_messages, emails_get_message, emails_apply_message_action, emails_list_drafts, emails_get_draft, emails_create_draft, emails_update_draft, emails_delete_draft, and emails_send_draft; personal emails_list_messages searches are global across available mail folders unless the tool call supplies a folder, which keeps folder-scoped searches explicit, and emails_apply_message_action uses the same action routes as the E-Mail UI for read/unread/move/archive/delete actions
    • the live/native Website Canvas bridge exposes website_sync_repo so chat/live agents can refresh the active repo clone to the latest remote fast-forward state
    • long-running website_ensure_session and website_apply_change calls use POST /api/modules/website-canvas/api/operation/{ensure|apply} and return an operation ID with HTTP 202; GET /api/modules/website-canvas/api/operation/:operationId provides idempotent queued/running/succeeded/failed status, optionally waiting up to 20 seconds through waitMs, and an unknown ID is explicitly not_started. Native provider loops allow up to 60 all-passive operation polls beyond their normal mutation-step budget, covering the Website Canvas 20-minute execution window without permitting unbounded polling. One checkout-scoped queue serializes async operations with direct ensure/sync/chat/commit/push mutations against the same shared repo or isolated checkout. Explicit idempotency keys are additionally fingerprinted with the authenticated caller and normalized payload. forceNewSession ignores a supplied existing session and creates a fresh isolated checkout. Completed records stay queryable for eight hours. Failed operations release their keys immediately for retry; successful operations retain a five-minute same-key deduplication window.
    • the native/live tool surfaces include copilot_ui_render; it returns a structured copilot.ui.render action and persists normalized assistant UI payloads under message_meta.assistantUiElements so web chat, floating chat, and the Apple native chat client can render CopilotKit-style response UI inline
    • the native runtime tool proxy mirrors document workflow operations through documents_create, documents_list_folders, documents_create_folder, documents_update_folder, documents_delete_folder, documents_update, documents_move, documents_delete, documents_create_share, and documents_revoke_share; document reads are scoped to shared documents plus the authenticated user's private documents, and documents_update accepts extracted tax fields plus related_mandant_ids for multi-party document assignment. documents_create can create Word/Excel/Markdown records directly in Root-Dokumente even when the stored file_path ends up under _inbox/..., and accepts template_key="vollmacht", mandant_id, plus optional matter, deadline, and location to generate a Mandant-linked standard power-of-attorney Word draft through Word Editor
    • the live/native tool surfaces now also mirror physical-post workflows through postal_mail_list, postal_mail_get, postal_mail_send, and postal_mail_refresh
    • request body also accepts optional userUtterance so the backend can reuse the last spoken user request during fallback delegation
    • lookup-oriented direct-tool misses can auto-fallback server-side into clapilot_delegate before the result is returned to Realtime
    • delegated execution routes through the native clapilot-agent runtime backend
    • current structured UI mutation examples: excel.cells.updated, excel.sheet.updated, word.document.updated
  • /api/ui-mutation-events

    • GET: authenticated SSE stream of persisted user-scoped UI mutation events emitted by live tools, native tool proxy executions, and chat context actions
    • GET ?format=json&since=<cursor>&topic=<topic>: bounded JSON cursor read for native clients; returns events[] plus the next cursor, supports since=latest initialization without replay, and filters server-side so an open native Aufgaben view does not repeatedly download the full task collection
    • each SSE item includes a stable id / mutationEventId, optional topic, triggerReload, actions[], and createdAt
    • the authenticated web app shell consumes the SSE stream, while Apple clients consume the lightweight cursor form and refetch only affected task IDs, so visible pages can react to agent-driven mutations without a hard refresh
  • /api/documents/[id]/share

    • GET: return the active public share metadata for one document, if present
    • POST: create or reuse a cryptographically random public share link for one document; accepts optional expires_in (24h, 7d, permanent) or an absolute expiry timestamp
    • DELETE: revoke the active public share link for one document
  • /share/[hash]

    • GET: public file delivery endpoint for an active document share; validates expiry and active state, increments access_count, and applies a simple per-IP rate limit before streaming the original file without requiring login

Physische Post (E-POST)

  • /api/postal-mail
    • GET: list recent physical-post jobs from the shared E-POSTBUSINESS outbound queue; accepts limit (1-100, default 20)
  • /api/postal-mail/[id]
    • GET: return one physical-post job including persisted event history
  • /api/postal-mail/send
    • POST: send an existing PDF document as physical post; V1 rejects non-PDF inputs and stores provider status snapshots locally
    • registered_letter accepts exactly the provider options Einschreiben, Einwurf Einschreiben, Einschreiben Rückschein (common synonyms are normalized; unknown values return invalidRegisteredLetter)
    • country is only for international mail (German uppercase ISO 3166-1 country names, e.g. ÖSTERREICH); domestic values like DE/Deutschland are dropped before submission
    • test-mode submissions attach the configured epost_test_email as provider testEMail, so the rendered test letter is mailed back instead of printed
  • /api/postal-mail/[id]/refresh
    • POST: refresh one physical-post job by polling the provider status endpoint
  • /api/internal/postal-mail/sync
    • POST: internal endpoint (agent-secret header) used by the preinstalled Postal Mail Status Sync system automation to refresh all open physical-post jobs (statuses submitted, processing, in_print_center) in batches
  • /api/postal-mail/admin/sms-request
    • POST: admin-only bootstrap step to request the E-POST SMS code
  • /api/postal-mail/admin/set-password
    • POST: admin-only bootstrap step to set the provider password and persist the returned secret into shared app settings
  • /api/postal-mail/admin/test-connection
    • POST: admin-only provider connectivity check using the current shared E-POST settings
  • /api/calendar
    • GET: returns the signed-in user's entries for the requested from / to interval. Entry colors are normalized as lowercase six-digit hex values without palette quantization and are resolved through the same display-color helper used by the Kalender server-rendered initial state and installed web app.
    • GET /api/calendar/calendars: lists writable calendar targets for the create-entry modal, including the local Clapilot calendar and connected Apple/iCloud CalDAV calendars
    • POST /api/calendar/ai-prefill: authenticated Kalender modal helper. Sends the natural-language description to ClapilotAICore as a tool-free structured extraction run and returns { prefill } with title, description, location, optional calendarName, start/end, and all-day state. The endpoint does not use deterministic client parsing and returns a validation error instead of inventing missing required details.
    • POST: creates a calendar entry; accepts optional calendar_name to write the event into a matching Apple/iCloud calendar first, mirroring the native calendar_create_event tool behavior; accepts create_google_meet / conference_type="google_meet" to create the event in Google Calendar with generated conferenceData and return google_conference_url plus provider-agnostic conference_url.
  • /api/calendar/ical
    • GET: exports the authenticated user's calendar entries for the requested from / to range as a downloadable text/calendar .ics file
    • POST: imports iCalendar data from JSON { content }, JSON { url }, raw text/calendar, or multipart .ics upload; webcal:// feed URLs are normalized to https:// before server-side fetches; recurring events are expanded into bounded local entries, deduped by iCal UID through external_id + sync_source='ical', and stored as mirrored calendar rows
  • /api/calendar/ical/subscriptions
    • GET: lists the signed-in user's saved iCal subscriptions and their sync status
    • POST: creates or updates a saved iCal feed subscription with name, feed_url, optional color, optional context_label, enabled, and sync_interval_minutes; webcal:// feed URLs are normalized to https://; iCal subscriptions are read-only mirrors (sync_mode = read_only) and feeds are refreshed by the iCal subscription poller or when the Kalender page opens a due range
  • /api/calendar/ical/subscriptions/[id]
    • PATCH: updates a saved iCal subscription
    • DELETE: removes the subscription and deletes its mirrored imported entries
  • /api/calendar/ical/subscriptions/[id]/sync
    • POST: immediately syncs one saved iCal subscription for the signed-in user
  • /api/calendar/ical/subscriptions/sync
    • POST: syncs the signed-in user's enabled subscriptions, with due_only defaulting to true
  • /api/internal/calendar/ical-subscriptions/sync-due
    • POST: internal-only endpoint guarded by x-clapilot-agent-secret; the container iCal poller calls it to refresh due saved iCal subscriptions in the background
  • /api/integrations/apple/status
    • GET: returns the signed-in user's Apple iCloud connection status and service toggles for CalDAV calendar, CardDAV contacts, and iCloud Mail, including the optional mail_address
    • PATCH: updates Apple iCloud service toggles (calendar_enabled, contacts_enabled, mail_enabled)
  • /api/integrations/apple/connect
    • POST: connects or updates Apple iCloud using apple_id, an app-specific password, optional display_name, optional mail_address, and service toggles; Clapilot validates CalDAV/CardDAV discovery before saving when those services are enabled
    • DELETE: disconnects the signed-in user's Apple iCloud credentials
  • /api/integrations/apple/calendar/sync
    • POST: syncs Apple iCloud CalDAV calendars into local calendar_entries with sync_source = apple; ordinary non-recurring Apple events can be edited, deleted, or moved between local Clapilot and Apple/iCloud calendar targets from Clapilot and written back via CalDAV
  • /api/integrations/apple/contacts
    • GET: reads Apple iCloud CardDAV contacts for the signed-in user, with optional q and max query parameters
  • /settings/contacts
    • user-facing Contacts settings entry for contact import sources; the first supported import source is VCF upload into mandanten, while Apple iCloud, Google Contacts, and Microsoft 365 contact imports are presented as future source slots tied to the existing app-connection model
  • /api/calendar/live
    • GET: authenticated SSE stream backed by Postgres LISTEN/NOTIFY on calendar_entries
    • accepts from / to so only changes intersecting the visible calendar range are forwarded
    • intended for background refetch/diff animation of created, moved, updated, or deleted events without a hard refresh
  • /api/calendar/[id]
    • calendar entries now support optional deadline metadata fields source_type, source_origin, source_label, detected_date, and mandant_id
    • calendar payloads include the unified sync fields external_id, sync_source, sync_status, and linked_entities; Microsoft/Outlook imports are exposed as sync_source='outlook' mirrored records, and iCal imports use sync_source='ical'
    • calendar payloads include provider-agnostic conference_url and conference_provider fields for online meetings; the Kalender UI also extracts Teams/Meet/Zoom/Webex links from location and description as a fallback for Apple/iCal imports
    • entries with source_type = frist render with deadline countdowns, critical badges, and a deadline-only filter in the Kalender UI
    • calendar payloads also expose optional mandant_id so Mandant-linked events can round-trip through the customer timeline and agent-created calendar flows
    • PATCH accepts calendar_name for local/Apple events to move the entry between the local Clapilot calendar and a matching Apple/iCloud calendar; it also accepts create_google_meet / conference_type="google_meet" for existing Google-synced events and stores the generated Meet link in google_conference_url and conference_url.
  • /api/generated-images/generate
    • POST: generate a persisted image asset via the default entry in app_settings.media_model_catalog.image when that catalog is non-empty, otherwise via the global ClapilotAICore image-generation provider/model default. Callers may pass optional provider_slug plus model to select one exact enabled runtime provider without fallback; explicit per-request values are not replaced by the catalog. Recognized model-family overrides without provider_slug remain supported for compatibility. Returns asset metadata plus a renderable markdown image reference. Agent tool calls that include source_image_path are treated as source-image edits for compatibility so local reference images do not degrade into text-only generation.
  • /api/generated-images/edit
    • POST: edit a persisted, uploaded, current chat-attachment, selected Notizen, or workspace-local source_image_path image source via an edit-capable ClapilotAICore image provider and return a new persisted asset. Optional provider_slug plus model selects one exact provider without fallback. Workspace paths are constrained to the configured workspace root, imported as source assets, and recorded in generated-image metadata. If no explicit provider was requested and the configured image-generation default is an OpenAI-compatible text-to-image-only model such as Ideogram, edit calls bypass that provider and fall back to OpenAI-Codex, OpenAI API, or Gemini image routing. When the selected provider is OpenAI-Codex, Clapilot routes through chatgpt.com/backend-api/codex/responses with the hosted image_generation tool and stored Codex OAuth instead of requiring OPENAI_API_KEY.
  • /api/generated-images/import
    • POST: import an uploaded/external image (up to 20 MB) as a user-owned generated-image asset and return asset metadata plus renderable markdown; used when chats and tools need a persisted asset id for a non-generated source image
  • /api/generated-images/[id]
    • GET: stream the authenticated user-owned generated image binary
  • /api/generated-videos/generate
    • POST: start a persisted text-to-video or image-to-video job through the configured AI media video provider (media_generation_provider_configs). In addition to prompt, callers can pass a user-owned image_id/source_image_id or workspace-local source_image_path; workspace sources are imported into generated_images, ownership is checked, and the source asset lineage is stored in generated_videos.metadata. Image-to-video is currently implemented for xAI Grok: image inputs are sent to /videos/generations as a base64 data URI in image.url, and the source aspect ratio is preserved by default. OpenAI-compatible video providers additionally accept reference-based generation: last_frame_image_id/last_frame_path, reference_image_ids/reference_image_paths (9 images max including the start image), reference_video_paths (3 max), reference_audio_paths (3 max), and the fit (auto/cover/contain/stretch), ref_detail (match/max) and use_video_audio controls. Files are sent as repeated multipart fields (ref_image, ref_video, ref_audio, last_frame); workspace-local reference paths are constrained to the workspace root. Caps are enforced server-side before the request, and reference inputs sent to a non-OpenAI-compatible provider are rejected. Under fit=auto the configured size acts as a pixel budget rather than the literal output geometry, so the response size field is not authoritative. When no image is supplied and the configured default is the image-only grok-imagine-video-1.5, Clapilot selects the configured text-capable grok-imagine-video model instead. Returns the generated-video asset metadata, provider response, and an initial poll result. Async jobs stay generating until status polling downloads the provider output.
  • /api/generated-videos/[id]/status
    • GET: read and, when still generating, poll a generated-video job by id. Any authenticated user may poll any job so workspace-global Video Studio storyboards reconcile for non-creators. Ready jobs include videoUrl and videoMarkdown. Chat-started async jobs also create a hidden one-shot wake-up tied to the originating web/Team Chat target; it silently reschedules status checks and posts the terminal result without requiring another user message.
  • /api/generated-videos/[id]
    • GET: stream a generated video binary by id once status is ready. Any authenticated user may fetch any generated video so shared Video Studio storyboards render for non-creators; creation still stamps the requesting user as owner_user_id. Markdown links to this route render as inline, controllable video players in assistant chat messages.
  • /api/integrations/google/oauth/start
    • POST: starts Google OAuth. Accepts scope_presets / scopes, optional account_type="user"|"agent", optional redirect_path, and optional include_granted_scopes (default false). Workspace connections keep historical Google grants isolated so previously approved YouTube and Drive permissions are not merged into an incompatible consent request. The dedicated agent preset uses Calendar, Gmail, Drive, and Meet-space-read scopes. Other presets include gmail, meet, youtube_live_chat, and youtube_live_chat_write.
  • /api/integrations/google/oauth
  • /api/integrations/google/oauth/complete
  • /api/integrations/google/oauth/status
    • GET: returns connection state, granted scopes, connected account info, and per-service enablement flags
    • PATCH: updates one or more per-user Google service toggles via service_settings
  • /api/integrations/google/meet/browser-login
    • GET: returns the authenticated user's active managed Agent Google browser-login status; screenshot=1 returns the current managed-browser viewport as a non-cached PNG
    • POST: starts or controls the one-time managed-browser Google sign-in with action=start|click|type|press|refresh|close. The route never returns browser cookies and keeps the resulting Google web session in the account-isolated persistent Meet profile
  • /api/integrations/google/calendar/sync
  • /api/integrations/google/contacts
  • /api/integrations/google/docs-sheets
  • /api/integrations/google/drive/sync
    • POST: synchronizes Drive files into Dokumente; accepts optional account_type="agent" to use the dedicated Agent Google identity and its isolated Drive subtree
  • /api/integrations/microsoft/oauth/start
    • POST: starts Microsoft OAuth. Accepts scope_presets / scopes as before plus optional redirect_path for same-origin app paths such as /?onboarding_step=1; when present, the OAuth callback redirects back to that path with microsoft_oauth result parameters instead of defaulting to /settings/app-verbindungen.
  • /api/integrations/microsoft/oauth
  • /api/integrations/microsoft/oauth/complete
  • /api/integrations/microsoft/oauth/status
    • GET: returns connection state, granted scopes, connected account info, and per-service enablement flags including Microsoft Mail and OneDrive
    • PATCH: updates one or more per-user Microsoft service toggles via service_settings
  • /api/integrations/x/oauth/start
  • /api/integrations/x/oauth
  • /api/integrations/x/oauth/complete
  • /api/integrations/x/oauth/status
    • GET: returns connection state, granted scopes, connected account info, and per-service enablement flags
    • PATCH: updates one or more per-user X service toggles via service_settings
  • /api/integrations/microsoft/calendar/sync
    • POST: imports enabled Microsoft calendar events into calendar_entries; accepts optional from, to, max_results, and calendar_id.
  • /api/integrations/microsoft/contacts
  • /api/integrations/microsoft/files
  • /api/integrations/microsoft/drive/sync
    • POST: imports enabled Microsoft OneDrive files into the virtual Microsoft 365 document folder; the sync walks nested OneDrive folders up to max_files and upserts matching dokumente rows.

Heartbeat

  • /api/heartbeat/settings
    • GET: read the heartbeat config, job status, and open watchlist for ?scope=user (own config) or ?scope=teamchat (admin only); status includes lastRunAt, nextRunAt, lastDeliveryStatus (delivered / suppressed / error), and lastError
    • POST: save { scope, config } with config = { enabled, intervalMinutes, instructions, historyLimit, dndStart?, dndEnd?, sessionId? | roomId? }; the runtime reconciles the schedule within about a minute
  • /api/heartbeat/trigger
    • POST: run the heartbeat immediately with { scope } (teamchat scope requires admin); proxies to the native runtime POST /internal/heartbeat/trigger and returns { ok, delivered, suppressed, text, outputPreview, watchlistAdded, watchlistResolved, runId } without shifting the regular schedule
  • /api/heartbeat/watchlist
    • DELETE: remove one open watch item with ?scope=user|teamchat&id=<uuid> (teamchat scope requires admin); returns the remaining watchlist

Admin and app settings

  • /api/admin/users
    • GET: list all users plus merged user_profiles role/display-name/avatar data and users.last_login_at for the admin settings page
    • POST: create a user and return a one-time temporary password for the new account
    • PATCH: update a user's role, update profile/account fields with { action: "update-profile", userId, email, display_name, avatar_url }, set a password directly with { action: "set-password", userId, password }, or with { action: "reset-password", userId } rotate the password and return a new one-time temporary password
    • DELETE: remove a user after clearing legacy non-cascading references in mandanten, dokumente, aufgaben, and notizen; self-delete is rejected
  • /api/admin/backup/export
    • POST: admin-only export that generates a ZIP download containing database.sql from pg_dump, workspace/** from the full configured Clapilot workspace directory, and a small manifest.json
  • /api/admin/backup/import
    • POST: admin-only multipart import for a previously exported backup ZIP; requires backup file plus confirmation text, restores database.sql with psql --single-transaction, and replaces the configured workspace directory from workspace/**; older documents/** backups are still accepted and only replace the shared mandanten/ document tree
  • /api/admin/demo-data
    • GET: admin-only demo-seed status for the current admin user, including current seeded record counts, mailbox/document preflight, active scenario (steuerberaterkanzlei or rechtsanwaltskanzlei), scenario options, last run metadata, and the shipped demo storylines for the active scenario
    • POST: admin-only demo-seed execution; accepts { action, scenarioId } where scenarioId is one of steuerberaterkanzlei or rechtsanwaltskanzlei
    • the rechtsanwaltskanzlei scenario seeds 20 distinct synthetic legal demo documents across Kanzlei/Berufsrecht, Zivilrecht, Verkehrsrecht, Arbeitsrecht, Familienrecht, and M&A/Due-Diligence workflows; fixtures are first-party demo texts with fiktive parties, dates, file metadata, and workflow-ready deadlines/tasks
    • reset_and_seed performs a destructive demo reset for the current admin user's seeded Mandanten, Dokumente, Aufgaben, Kalender, Drafts, Automationen, mailbox cache rows, and tagged IMAP demo mails before rebuilding the selected scenario; seeded dates are generated relative to the reset time and kept in the active month where possible, so demo deadlines and recording flows do not age into stale fixed dates
    • seeded automations may include scenario-specific draftSubject and draftBodyText copy so demo replies can contain concrete legal/tax reasoning instead of generic acknowledgements
    • reset_emails, reset_tasks, and generate_activity always operate on the currently active seeded scenario, even if a different scenarioId is sent, so partial resets cannot accidentally mix two different Kanzlei demos
    • wipe_operational_data deletes all app-local Mandanten, Dokumente, Aufgaben, Kalender, local email cache rows, drafts, automations, document folders, and the physical files under the shared document storage across the instance while keeping users, roles, passwords, global settings, and external IMAP mailbox contents unchanged
  • /api/admin/agent-runtime/files
    • GET: list editable native runtime files (clapilotaicore.json, workspace markdown, native state markdown)
    • PUT: update the selected native runtime file; JSON writes are validated and normalized
  • /api/admin/agent-runtime/restart
    • POST: returns 410 because the packaged OpenClaw gateway restart path was removed; restart native services via Docker/deployment tooling instead
  • /api/admin/agent-runtime/terminal
    • GET: list builtin native runtime diagnostics only
    • POST: run builtin diagnostics like node-version, ls-state-dir, show-clapilotaicore-json, and db-host-check
  • /api/admin/anthropic/oauth/start
    • POST: deprecated for Anthropic setup-token auth and now returns an instructional error; Anthropic-Claude expects a finished claude setup-token value instead of a browser callback flow
  • /api/admin/anthropic/oauth/complete
    • POST: accepts a Claude setup-token (sk-ant-oat01-...), validates it, and stores it as the subscription secret for Anthropic-Claude
  • /api/admin/anthropic/oauth/clear
    • POST: admin-only removal of the stored Anthropic-Claude subscription secret/setup-token
  • /api/admin/openai-codex/oauth/start
    • POST: starts a short-lived Codex app-server chatgptDeviceCode login and returns login_id, verification_url, user_code, and expires_at; no localhost callback or pasted redirect URL is required
  • /api/admin/openai-codex/oauth/status
    • POST: accepts login_id plus provider_slug, returns pending while OpenAI authorization is outstanding, and on completion imports the Codex-managed credential, synchronizes the native Codex auth stores, and saves the encrypted provider secret
  • /api/admin/openai-codex/oauth/complete
    • POST: legacy compatibility endpoint for completing an already-started authorization-code flow from a callback URL/query; current web and Apple provider settings use device-code status polling instead
  • /api/admin/rag/status
    • GET: return RAG health, queue diagnostics, the resolved embedding provider/model currently used by native memory + document RAG, current document vision fallback config, and vision-capable model options derived from Provider & Modelle
    • POST: update the explicit document vision fallback toggle and selected vision model ref
  • /api/admin/rag/reindex
  • /api/app-settings
    • GET: returns global app settings for the current user role; responses include memory_dreaming_model as an exact provider/model ref or "" for automatic Dreaming-model selection, main_agent_tool_restrictions_enabled (default false) for the explicit main-agent opt-in, and main_agent_disabled_tool_names as the stored per-tool denylist. Admins also receive email_auto_analysis_enabled, email_analysis_model, and email_auto_process_blocklist for the mail automation flow
    • POST: admin-only update of global app settings; accepts memory_dreaming_model as an exact provider/model ref or an empty string, returning 400 for malformed refs, plus main_agent_tool_restrictions_enabled and main_agent_disabled_tool_names to activate and configure exact main-agent tool restrictions. Unknown tool names are discarded against the canonical runtime catalog. It also accepts email_auto_analysis_enabled, email_analysis_model, and email_auto_process_blocklist to control automatic personal inbox analysis, the preferred workflow model, and the global sender/domain skip list for automatic mail processing
    • persists global app integrations including github_token, the optional github_pr_review_token, Google OAuth client credentials, Microsoft OAuth client credentials plus tenant hint, X OAuth client credentials, LinkedIn OAuth client credentials, plus legacy fallback audio provider secrets used outside native provider rows
    • GET: return persisted global app toggles including document-processing mode, native runtime base URL, chat transport mode, the global search-provider choice (default_search_provider), the SearXNG instance URL (searxng_search_base_url), the first-login onboarding toggle (user_onboarding_enabled), the Mandanten profile crawl toggle (mandant_profile_web_crawl_enabled), the LiteLLM base URL (litellm_api_base_url), and admin-only key presence flags such as has_brave_search_api_key, has_perplexity_api_key, has_litellm_api_key, has_linkedin_oauth_client_id, has_linkedin_oauth_client_secret, has_x_oauth_client_id, and has_x_oauth_client_secret
    • GET: admin responses still include legacy chat_tts_provider, chat_tts_model, chat_tts_voice, has_google_gemini_api_key, and has_elevenlabs_api_key fields for compatibility, but the active global TTS/STT/image defaults now live under /api/agent-runtime/config
    • POST: update persisted global app toggles and global provider keys used outside per-provider runtime config, including the X OAuth client id/secret used by the X app connection flow, the LinkedIn OAuth client id/secret used by the bundled Social Media module's LinkedIn platform, optional Gemini and ElevenLabs fallback API keys, the global search-provider choice, Brave Search / Perplexity API keys, and searxng_search_base_url used by native web-search/enrichment paths, the LiteLLM base URL/API key used by the LiteLLM usage inspector, the first-login onboarding toggle, the Mandanten profile crawl toggle, and the admin-only Developer mode toggle
  • /api/app-connections/github
    • GET: admin-only list of named GitHub integrations with masked tokens and update timestamps (used by Website Canvas, Agent Orchestrator, and Issue Reporter target selection)
    • POST: admin-only manage action with { action: "create" | "update" | "delete", name, token?, previous_name? }
  • /api/app-connections/gitlab
    • GET: admin-only list of named GitLab integrations with instance URL, masked token presence, and update timestamps
    • POST: admin-only manage action with { action: "create" | "update" | "delete", name, token?, base_url?, previous_name? }; base_url supports GitLab.com and self-managed GitLab
  • /api/integrations/browser-use/settings
    • GET: admin-only Browser Use Cloud configuration status; returns only { configured } and never returns the saved key
    • POST: admin-only save or removal of the encrypted Browser Use API key via { api_key?, clear_api_key? }; accepted keys start with bu_
  • /api/developer/e2e
    • GET: admin-only endpoint that requires app_settings.developer_mode_enabled=true; returns the in-instance Developer E2E suites and model options used by Settings -> Developer
    • POST: admin-only execution endpoint for one suite with { suiteId, model?, keepArtifacts? }; runs against the current Clapilot instance and returns a structured result with suite status, runtime session/run identifiers, model, cleanup state, per-check results, output preview, and runtime metadata
    • webchat-history-replay-context seeds stale completed webchat agent_runs plus persisted synthetic transcript wrappers, executes one current live webchat turn through ClapilotAICore, and verifies the current run stores only the latest user message, caps stored-history replay, reports dropped synthetic history turns, and avoids stale marker output
    • canvas-create-edit-file sends the two German chat requests for creating a tax-declaration Canvas file and then editing it to Max MusterFrau with a 4000 EUR refund; it verifies the created HTML file exists, contains the global Canvas style colors, and is edited in place with the requested data
    • by default suites delete their seeded agent_runs, agent_events, agent_context_messages, agent_session_state, and suite-owned generated Canvas files; keepArtifacts=true leaves them behind for diagnostics
  • Native runtime /internal/runs
    • accepts optional idempotencyKey / messageId for per-session turn deduplication; webchat sends the persisted user message row id
    • serializes turns per sessionKey; queued requests stay pending and emit a lifecycle event with phase="queued" before the normal start/end stream completes
  • /api/litellm/usage
    • GET: admin-only proxy to the configured LiteLLM /user/daily/activity endpoint; returns normalized totals plus day-by-day model/provider/api-key breakdowns for Settings -> ClapilotAICore -> LiteLLM
  • /api/litellm/logs
    • GET: admin-only proxy to the configured LiteLLM /spend/logs endpoint with summarize=false; returns normalized individual spend-log rows plus raw metadata payloads for drill-down inspection
  • /api/subscription-usage
    • GET: admin-only live subscription/quota snapshot for Codex, Claude Code, Grok, and Ollama, with 60-second server caching and normalized progress-window data for the Settings -> ClapilotAICore -> Subscription Usage page
    • include_connected=1 returns { checkedAt, hubMode, reportIntervalMs, staleAfterMs, instances } for the compact Settings -> Hub -> Subscription Usage page. instances[0] is the API Hub itself; on a local Hub, later entries are the latest pushed snapshots from connected instances. Loading this aggregate does not contact remote instances or their providers
    • each windows[] entry includes the stable upstream key, a normalized English label, usedPercent, utilization, resetAt, and limitWindowSeconds. Codex labels are derived from limitWindowSeconds instead of assuming primary_window is five hours or secondary_window is seven days; absent upstream windows are omitted. Claude recognizes the Fable 5 weekly bucket as seven_day_overage_included and labels it 7d Fable 5
    • Claude Code live usage is served from Clapilot-owned credentials only. Full Claude OAuth credentials use Anthropic's OAuth endpoints (/api/oauth/usage for windows, /api/oauth/profile for the plan) with the Claude OAuth beta header and a Claude Code user agent. Claude web-session credentials use Claude's claude.ai/api/organizations/.../usage path with the stored sessionKey plus full browser cookie header when Cloudflare requires it. The credential chain is: CLAPILOT_CLAUDE_OAUTH_TOKEN, the Claude CLI login home $CLAUDE_CLI_HOME/.claude/.credentials.json written by the settings Claude Auth flow (source claude_cli_home), Anthropic provider rows with auth_mode=oauth_token, app_settings.anthropic_oauth_token, and ~/.claude/.credentials.json
    • full Claude Code OAuth credentials (scopes include user:profile) are refreshed automatically against platform.claude.com/v1/oauth/token when expired, and rotated tokens are written back to the credentials file shared with the agent runtime CLI
    • bare sk-ant-oat01-... setup-token provider rows first run the Docker-local Claude CLI usage probe with the same CLAUDE_CODE_OAUTH_TOKEN bridge environment used for inference. If the CLI does not return subscription windows, the endpoint checks Anthropic's OAuth usage endpoint only as diagnostics; Anthropic rejects setup-tokens there with 403 user:profile
    • credential candidates holding the same token are deduplicated per snapshot, and scope-rejected tokens are remembered in-process for 6 hours so auto-refreshing panels do not repeatedly hit (and rate-limit) the upstream usage endpoint
    • Grok usage reuses an xAI provider configured with auth_mode=oauth_token, refreshes that OAuth credential through the existing xAI flow when needed, and reads the current subscription credit percentage/reset from grok.com's GrokBuildBilling/GetGrokCreditsConfig gRPC-web endpoint
    • Ollama Cloud usage reads the plan, account, session/hourly percentage, weekly percentage, and reset timestamps from https://ollama.com/settings. Ollama API keys authenticate inference and return per-request metrics, but do not expose these account plan windows. The required browser Cookie header is stored in the encrypted Ollama provider secret bundle and remains eligible when that provider has no routed models; CLAPILOT_OLLAMA_COOKIE is an optional deployment override
  • /api/admin/developer/api-keys
    • GET: admin-only list of instance API-key metadata plus subscription_usage_url, notifications_url, memory_url, tools_url, issue_reports_url, and the current Issue Reporter app/repository catalog, built from the configured app_settings.public_base_url; requires app_settings.developer_mode_enabled=true and never returns key hashes or plaintext secrets
    • POST: admin-only create path with { name, scopes, expires_at?, allowed_repositories? }; accepts subscription_usage:read, notifications:read, memory:read, memory:write, inference:execute, and the high-privilege tools:execute scope in any non-empty combination. issue_reports:write must be the key's only scope and requires at least one full repository from the current Issue Reporter catalog. Public-client issue keys use the clp_public_ prefix. The plaintext key is returned once, while only its SHA-256 hash and display prefix are persisted. Notification, memory, and tool execution are bound to the admin user who created the key
  • /api/admin/developer/api-keys/[keyId]
    • DELETE: admin-only immediate revocation for an active instance API key; requires Developer mode
  • /api/v1/subscription-usage
    • GET: versioned device/API-client endpoint returning the same 60-second-cached normalized four-provider snapshot as /api/subscription-usage
    • by default the response contains only the subscription accounts connected to the instance serving the API. Add include_connected=1 (or include_connected=true) on a local Hub to receive the aggregate { checkedAt, hubMode, reportIntervalMs, staleAfterMs, instances }, including the Hub followed by every connected instance that has reported a valid snapshot
    • aggregate entries contain { id, host, sourceInstanceId, instanceUrl, isLocal, checkedAt, reportedAt, receivedAt, stale, snapshot }. A report becomes stale after 45 minutes; the stored snapshot remains visible so clients can distinguish missing data from an outdated report
    • clients must use each window's label and limitWindowSeconds rather than assigning durations by array position or by Codex primary_window / secondary_window; upstream providers can temporarily remove or reorder quota windows
    • authenticate with Authorization: Bearer clp_live_... (preferred) or X-API-Key; the key must be active, unexpired, and grant subscription_usage:read
    • returns stable JSON auth errors with 401 for missing/invalid/expired/revoked keys and 403 for a missing scope or disabled Developer mode; successful requests update the key's last_used_at
    • examples: curl -H 'Authorization: Bearer clp_live_...' https://your-instance.example/api/v1/subscription-usage and, on a Hub, curl -H 'Authorization: Bearer clp_live_...' 'https://your-hub.example/api/v1/subscription-usage?include_connected=1'
  • /api/v1/notifications
    • GET: read-only polling endpoint for the API-key creator's durable message notification inbox. It returns personal assistant replies and Team Chat messages with the same clapilot_type, title/body, message_id, session_id / room_id, sender_name, and author_kind routing metadata used by Apple push notifications
    • authenticate with an active clp_live_... key granting notifications:read; Developer mode remains the instance-wide kill switch. A key without an associated creating user is rejected with 403 user_scope_required
    • accepts limit=1..100 (default 20) and an opaque after cursor. Responses contain { notifications, has_more, next_cursor, poll_after_ms }; clients should retain next_cursor, pass it as after on the next poll, and continue immediately while has_more=true, otherwise waiting at least poll_after_ms
    • the first request without after returns the latest page in chronological order. Polling is non-destructive and does not change chat read state; in this contract, "unread" means notification events newer than the consumer's saved cursor
    • example: curl -H 'Authorization: Bearer clp_live_...' 'https://your-instance.example/api/v1/notifications?limit=20', followed by ...?after=<next_cursor>
  • /api/v1/memory
    • GET: creator-bound semantic search over approved, active native memory visible to the user who created the API key. Requires memory:read, a non-empty query of at most 1,000 characters, and optional limit=1..20 (default 6). Results contain { id, title, content, score, memory_scope, visibility_scope, source_type, retrieval_mode }
    • POST: submit explicit durable memory with a memory:write key and JSON { content, title?, visibility_scope? }. content is required and limited to 20,000 characters; title is limited to 200 characters; visibility_scope may be private or team. The native storeManualMemory path applies content safety, audience derivation, user sharing preferences, content-hash deduplication, assertion status, embeddings, and the Learning review path rather than inserting raw memory rows
    • safe, non-conflicting facts may return 201 with an approved memory id. Deduplicated submissions return 200. Content requiring review returns 202 with id=null, assertion_status="candidate", and needs_review=true; it does not become searchable until approved. Stable response fields are { memory: { id, assertion_id, assertion_status, needs_review, deduplicated, title, memory_scope, visibility_scope } }
    • Developer mode, key expiration/revocation, and the creating user remain enforced on every request. A memory:write key does not imply memory:read, and a memory:read key does not imply memory:write
    • rate limits are enforced atomically in PostgreSQL per API key: reads allow 120 requests per 10 minutes and 5,000 per day; writes allow 60 requests per 10 minutes and 500 per day. 429 rate_limited responses include Retry-After
    • examples: curl -H 'Authorization: Bearer clp_live_...' 'https://your-instance.example/api/v1/memory?query=project%20preferences&limit=6' and curl -X POST -H 'Authorization: Bearer clp_live_...' -H 'Content-Type: application/json' --data '{"title":"Project preference","content":"Prefer live runtime evidence.","visibility_scope":"private"}' 'https://your-instance.example/api/v1/memory'
  • /api/v1/memory/[memoryId]
    • GET: retrieve one approved, active memory by UUID with memory:read. The runtime applies the same creator-bound visibility check as search and returns 404 memory_not_found when the entry does not exist or is not visible to that key
    • successful responses contain { memory: { id, title, content, memory_scope, visibility_scope, source_type, line_start, line_end, total_lines } }; internal metadata and source paths are not exposed
    • exact reads share the memory:read per-key rate-limit buckets with semantic search
  • /api/v1/tools/catalog
    • GET: returns the native runtime's curated coding_core tool profile as OpenAI function-tool definitions shaped as { tools: [{ type: "function", function: { name, description, parameters } }] }. The catalog includes creator-bound memory, context, Knowledge, Learning, web-search, session/status, and scoped task-comment tools; it does not expose the broad business-action catalog
    • authenticate exactly like /api/v1/tools/execute with an active private clp_live_... key granting tools:execute. Developer mode remains the instance-wide gate. Catalog reads do not consume the tool-execution rate limit
    • example: curl -H 'Authorization: Bearer clp_live_...' 'https://your-instance.example/api/v1/tools/catalog'
  • /api/v1/tools/execute
    • POST: creator-bound remote execution for the same agent-tool catalog exposed by clapilot-cli. Authenticate with an active private clp_live_... key granting tools:execute and send { tool_name, arguments?, client_context?, ui_language? }
    • the server derives userId, sessionKey, and originSessionKey exclusively from the verified key. Client-supplied identity, service-principal, channel, run, and session fields are ignored, preventing a key from changing its creator-bound scope
    • calls reuse the native tool-proxy dispatcher, module availability checks, main-agent restrictions, approval workflows, UI mutation publication, and error contract. Tool-level failures remain HTTP 200 with top-level ok=false; authentication, malformed input, payload limits, and rate limits use normal 4xx responses
    • tools:execute is intentionally high privilege and includes read operations, mutations, outbound-capable tools, and exec_command. Grant it only to trusted server-side clients or coding agents, store it outside source control, set an expiration, and revoke it when no longer needed
    • request bodies are limited to 512 KiB. Rate limits are atomic per key: 300 calls per 10 minutes and 10,000 per day; 429 rate_limited includes Retry-After
    • example: curl -X POST -H 'Authorization: Bearer clp_live_...' -H 'Content-Type: application/json' --data '{"tool_name":"documents_list","arguments":{"limit":5}}' 'https://your-instance.example/api/v1/tools/execute'
  • /api/v1/inference/models
    • GET: OpenAI-compatible model list for stateless instance inference. Authenticate with Authorization: Bearer clp_live_...; the active private key must grant inference:execute, and Developer mode must be enabled
    • returns { "object": "list", "data": [{ "id": "provider-slug/model-id", "object": "model", "owned_by": "Provider label", "clapilot": { "tools": true } }] }. clapilot.tools is a non-standard capability flag indicating whether that model's resolved transport forwards client-supplied tool definitions. IDs are the same configured model references used by ClapilotAICore and include only models the inference provider layer can route without the server-side session/agent loop
    • example: curl -H 'Authorization: Bearer clp_live_...' 'https://your-instance.example/api/v1/inference/models'
  • /api/v1/inference/chat/completions
    • POST: OpenAI-compatible, stateless provider passthrough for remote CLIs and coding clients. Authenticate with an active private clp_live_... key granting inference:execute; provider credentials remain inside the Clapilot instance and are never returned to the client

    • accepts { model, messages, tools?, tool_choice?, temperature?, max_tokens?, max_completion_tokens?, stream?, stop?, response_format? }. model is one of the IDs returned by /api/v1/inference/models; messages uses the OpenAI chat-completions format. When both token-limit fields are present, max_completion_tokens takes precedence. Unknown request properties are ignored. n > 1 returns 400 unsupported_n; the legacy functions property returns 400 legacy_functions_not_supported and clients must use tools

    • non-streaming responses use the OpenAI chat.completion shape: { id, object, created, model, choices: [{ index, message: { role, content, tool_calls? }, finish_reason }], usage? }. Provider/auth/quota failures use { error: { message, type, code } } with an appropriate 4xx/5xx status and never include provider credentials, provider base URLs, or the internal agent secret. Supplying a non-empty tools array for a model whose clapilot.tools capability is false returns 400 with type: "invalid_request_error" and code: "tools_unsupported_for_model"; the request is rejected before any provider call rather than silently dropping tool definitions

    • stream=true returns text/event-stream with OpenAI chat.completion.chunk data records and a final data: [DONE]. Text deltas are forwarded as they arrive on streaming-capable provider transports. If a provider buffers tool calls, the completed tool_calls delta is emitted as one chunk before the terminal finish-reason chunk

    • this endpoint is deliberately not the Clapilot agent loop: it does not create agent runs or sessions, load Clapilot memory, or execute tools. Client-supplied tool definitions are forwarded to the selected model and any returned tool_calls must be executed by the client, which may then send tool results in a subsequent stateless request

    • request bodies are limited to 1 MiB. The model-list and completion endpoints share atomic per-key inference:execute limits of 300 requests per 10 minutes and 10,000 per day; 429 rate_limit_exceeded includes Retry-After

    • non-streaming example:

      curl -X POST \
        -H 'Authorization: Bearer clp_live_...' \
        -H 'Content-Type: application/json' \
        --data '{"model":"provider-slug/model-id","messages":[{"role":"user","content":"Reply with one short sentence."}]}' \
        'https://your-instance.example/api/v1/inference/chat/completions'
      
    • streaming example: add "stream":true to the JSON body and use curl -N so chunks are displayed without client-side buffering

  • /api/v1/issue-reports
    • POST: public-client issue intake for JSON requests authenticated with an active clp_public_... key granting the isolated issue_reports:write scope. app and title are required. The app is resolved to its full repository and must be present in the key's immutable allowed_repositories; checking only the repository basename is not sufficient
    • accepted optional fields are details, reporter_email, platform, route_path, route_url, context, installation_id, app_version, build_number, os_version, device_model, locale, and image_attachments. Attachments are base64-encoded PNG, JPEG, HEIC, HEIF, or WebP images, limited to three images, 5 MB each and 10 MB combined; the complete streamed JSON body is capped at 15 MB
    • accepted reports always enter hub_reported_issues with status open. They never directly create GitHub issues or Agent Orchestrator tasks; an admin must review and approve them in the Hub Issue Reporter queue
    • rate limits are enforced atomically in PostgreSQL per API key and per hashed client IP (10-minute and daily buckets). 429 responses include Retry-After. Send a stable unique Idempotency-Key for retries; repeating it with the same key returns the original report with created=false
    • the mobile key is a public identifier and abuse-limiting credential, not a confidential client secret. Prefer a B2C backend or App Attest/DeviceCheck exchange that mints short-lived report credentials; repository scoping, moderation, rate limits, expiration, and revocation remain defense in depth
  • /api/email-processing-blocklist
    • POST: admin-only helper endpoint used by the /emails row/detail action menus; extracts the sender address from the selected message and upserts it into app_settings.email_auto_process_blocklist, preserving optional reason metadata such as manual or auto-classified:marketing
  • /api/agent-runtime/assistant-message
    • POST: internal runtime-only endpoint guarded by x-clapilot-agent-secret; persists an assistant-origin automation/system message into the user’s main personal chat session, Teamchat #general, a concrete Teamchat roomId, or an approved external channel depending on the provided target payload
  • /api/agent-runtime/channel-mirror
    • POST: internal runtime-only endpoint guarded by x-clapilot-agent-secret; mirrors one external Telegram/WhatsApp/Slack/Signal/iMessage/instance-bridge group message into an explicitly mapped Team Chat room. Participant requests use { kind: "participant", channelType, roomId, text, sender: { key?, name?, username? }, eventId?, attachments? }; agent requests use { kind: "agent", channelType, roomId, text, agentName?, attachments? }. Returns { ok: true, messageId, roomId }. The endpoint rejects empty or unknown rooms instead of falling back to Teamchat #general and does not append agent replies to runtime session context.
  • /api/agent-runtime/channel-audio-transcription
    • POST: internal runtime-only endpoint guarded by x-clapilot-agent-secret; accepts { filePath, mimeType? } for an audio file already persisted inside the shared workspace, transcribes it through the configured STT runtime, and returns { ok: true, transcript, provider, providerType, model }. It is used by native channel ingestion before an agent run and never returns provider credentials.
  • /api/agent-runtime/request-logs
    • GET: admin-only list of persisted native model request logs with provider, model, duration, status, derived token counts, estimated prompt-layer attribution (promptLayerTokens), and raw usage/metadata payloads for the ClapilotAICore Logs inspector
  • /api/agent-runtime/learning
    • GET: admin-only list of learning objects, recent learning audit events, and grouped stats for the ClapilotAICore Learning inspector and exception-review queue
    • POST: admin-only create path for controlled durable facts, procedure/skill proposals, or hot memory snapshots; this records an initial learning audit event and creates pending approval state when required
  • /api/agent-runtime/learning/[id]
    • GET: admin-only detail view for one learning object with linked approval decisions and audit events
  • /api/agent-runtime/learning/[id]/decision
    • POST: admin-only approval ledger path for approved, rejected, changes_requested, revoked, or auto_approved_by_policy decisions; it updates the object's lifecycle state, appends an audit event, and backs both versioned system-policy activation and Learning settings exception-review actions
  • /api/build-info
    • GET: return runtime build metadata for both the web app container (clapilot) and the native agent service (clapilotAgent) so settings pages can detect version drift between the two services
  • /api/issue-reporter
    • POST: create an issue report using the configured Issue Reporter target (github, task_board, local_hub, or remote_hub) and attach the current page context, build info, and active chat transcript. JSON and multipart requests may send app as a repository basename without its owner prefix, for example app=clapilot-website; the backend resolves the full repository and mapped Task Board from the Agent Orchestrator repository matrix. Missing app remains backward-compatible and routes as clapilot. Unknown or ambiguous basenames are rejected. The request also accepts optional platform (web_ios_mac, web, ios, mac, or general) and multipart images[]. Created GitHub issues and Task Board tasks use the reporter summary as their title and record the Issue Reporter source, app, and repository in runtime context. The GitHub target uses the named Issue Reporter GitHub integration selected in Settings -> Issue Reporter; Hub approvals retain the submitted app mapping.
  • /api/issue-reporter/apps
    • GET: admin-only app catalog for the Issue Reporter selector. Local Hub instances return their own Agent Orchestrator repository-to-board mappings; remote-Hub spokes fetch the catalog from the configured Hub through the signed /api/hub/issues/apps contract and fall back to local mappings when the remote endpoint is unavailable.
  • /api/hub/status
    • GET: admin-only hub-mode status for the current normal Clapilot instance
  • /api/hub/validate
    • POST: signed hub handshake endpoint; when the sender includes instance_url, the local hub now auto-discovers or refreshes that instance in the monitored health list
  • /api/hub/connection-test
    • POST: admin-only connectivity test against the configured hub target (local or remote mode); validates URL/secret before saving hub settings
  • /api/hub/channel-bridge/rooms
    • POST: HMAC-signed peer-room discovery for instance channel bridges; verifies the fleet hub signature (x-clapilot-instance-id, x-clapilot-ts, x-clapilot-signature) on the raw body, accepts {}, and returns this instance's mappable team-chat rooms as { rooms: [{ id, name, kind }] }. Available on every instance, not only in hub mode, so both bridge sides can serve it
  • /api/hub/channel-bridge/register
    • POST: HMAC-signed peer-side bridge registration; accepts { action: "upsert" | "remove", bridgeId, peerInstanceId, peerInstanceLabel, peerBaseUrl, peerRoomId, peerRoomLabel, localRoomId, localRoomLabel? }. upsert validates localRoomId against the mappable-room list, creates or updates the local approved instance_bridge channel-approval row with role peer, and enables the instance_bridge channel config; remove deletes the local approval row by metadata.bridge_id
  • /api/hub/issues/report
    • POST: signed inbound issue intake used when another Clapilot instance reports into this instance running in hub mode; also auto-discovers or refreshes the sending instance in the monitored health list when instance_url is present
  • /api/hub/issues/apps
    • POST: signed app-catalog endpoint used by remote-Hub spokes to render the same repository selector as the local Hub without exposing the catalog publicly
  • /api/hub/issues
    • GET: admin-only list of issue reports received by this hub-mode instance; accepts optional repository-basename app and status filtering. Supported status values are open, approved, denied (including legacy dismissed rows), github_failed, github_queued, task_failed, and resolved. The response also returns the configured app/repository/Task Board options for the Issue Reporter selector.
  • /api/hub/issues/[id]
    • GET: admin-only detail payload for one inbound issue report, including transcript and attachment URLs
    • PATCH: admin-only review action for one inbound issue report; { "action": "approve" } creates either a GitHub issue or an agent task depending on the hub instance's Settings -> Issue Reporter target and stores the resulting GitHub metadata or Aufgabe id, while { "action": "deny" } marks the report as denied without forwarding
  • /api/hub/health/instances
    • GET: admin-only list of monitored Clapilot tenants for this hub-mode instance, including manually added and auto-discovered rows plus discovery metadata such as source_instance_id, instance_url, discovery_source, and last_seen_at
    • POST: admin-only add a monitored tenant with optional admin credentials for login verification
  • /api/hub/health/instances/[id]
    • PATCH: admin-only update one monitored tenant
    • DELETE: admin-only remove one monitored tenant
  • /api/hub/health/instances/check
    • POST: admin-only run health checks for one or all monitored tenants
  • /api/hub/fleet/instances
    • GET: admin-only Fleet instance inventory in local Hub mode; encrypted environment content is removed from responses
    • POST: creates a new Fleet instance from name, optional machineId, sipEnabled, whitelisted string overrides, and optional provisioningSettings.mainAgentToolRestrictionsEnabled (default true). Provisioning settings are persisted separately from raw environment overrides. The generated instance environment seeds the restriction flag exactly once, so later changes inside the spawned instance are not overwritten on restart
  • /api/local-db

Module platform

  • /api/module-store/local
    • GET: signed-in local module inventory for /modules; returns all, effective, redacted paths for non-admins, and an installed boolean on every entry. Workspace/managed entries are always installed; bundled entries reflect the instance-wide DB policy and overrides, and uninstalled bundled entries remain in all but are excluded from effective. Manifest metadata includes icon, categories (store category keys, multiple per module), and hiddenInMenu; each entry also carries iconFile when the module ships an icon image (icon.png/icon.svg/icon.webp/icon.jpg) in its root, served via /api/modules/[slug]/assets/<iconFile>
  • /api/module-store/local/[slug]
    • GET: return only one effective module manifest for the module runtime page, with the same developer/admin visibility checks, localization, and non-admin path redaction as the full inventory. The module page uses this endpoint so renderer selection does not wait for the complete store payload.
  • /api/module-store/catalog
  • /api/module-store/publish
  • /api/module-store/install
  • /api/module-store/install-bundled
    • POST: admin-only install of a non-fixed bundled module. Runs unapplied bundled SQL migrations, upserts module_installs.installed = true, and synchronizes the legacy disabled file; it no longer copies bundled source into the workspace
  • /api/module-store/deactivate-bundled
    • POST: admin-only uninstall of a non-fixed bundled module. Upserts module_installs.installed = false, synchronizes the legacy disabled file, and removes a marked workspace clone left by the former copy-based install flow
  • /api/module-store/delete
  • /api/module-store/set-icon
    • POST: admin-only update of the persisted module manifest icon in module.json
  • /api/module-store/set-menu-visibility
    • POST: admin-only update of module.json.hiddenInMenu; keeps the module active while removing or restoring its automatic sidebar menu entry
  • /api/modules/scaffold/new
  • /api/modules/[slug]/assets/[...assetPath]
  • /api/modules/[slug]/api/[...endpointPath]
  • /api/modules/[slug]/storage
    • All module runtime endpoints resolve only installed/effective modules and return 404 with {"error":"module_not_installed"} when the slug is unavailable

Appointment booking module API highlights:

  • /api/appointments/overview
    • GET: signed-in overview for the Termine module with settings, appointment types, availability windows, and upcoming appointments for an optional from/to date range
  • /api/appointments/settings
    • GET, PATCH: signed-in booking settings including timezone, slot step, minimum notice, booking horizon, and public embed copy/enabled state
  • /api/appointments/types
    • GET, POST: signed-in appointment type list and upsert for fields such as name, duration_minutes, optional price_cents / price_currency, buffers, color, and active state
  • /api/appointments/availability
    • GET, POST, DELETE /api/appointments/availability/[id]: signed-in weekly bookable day/time windows, optionally scoped to one appointment type
  • /api/appointments/slots
    • GET: signed-in free-slot preview for one appointment type and date range; returns available slots only, with existing appointments used only as blockers
  • /api/appointments/days
    • GET: signed-in per-day free-slot availability (days array of { date, free_count } plus the resolved from/to/horizon_end range) for one appointment type; used for calendar-style day pickers
  • /api/appointments/appointments
    • GET, POST, PATCH /api/appointments/appointments/[id]/status, DELETE /api/appointments/appointments/[id]: signed-in appointment listing, internal booking, status update, confirmation, completion, and cancellation. Status values are pending, booked, cancelled, and completed; confirming a pending appointment by setting status: "booked" sends the customer confirmation email when the agent mailbox SMTP settings are configured.
  • /api/public/book-appointment/config, /api/public/book-appointment/days, /api/public/book-appointment/slots, /api/public/book-appointment/appointments
    • unauthenticated public embed API for active appointment types, per-day free-slot availability, free slots, and requesting one selected free slot. Public bookings require customer_email, create pending appointments, send the customer a request-received email when the agent mailbox SMTP settings are configured, and never return existing appointments or customer records.
  • /embed/book-appointment
    • public iframe-ready booking UI for websites with a month calendar for the date and a time grid for the selected day; use a width up to about 1040px and a height around 680px to show the date/time picker and contact details side by side

Native agent tool proxy appointment contracts:

  • appointments_list_types: public-safe list of active appointment types, durations, and optional prices
  • appointments_list_free_slots: public-safe free-slot lookup
  • appointments_list_free_days: public-safe per-day free-slot availability for day/week overviews
  • appointments_book: public-safe booking mutation for a selected free slot; requires customer_email, creates a pending appointment request, and triggers the request-received customer email when mail transport is configured

Athlete-Brand Matching module API highlights under /api/modules/athlete-brand-matching/api:

  • GET /state: returns the signed-in user's module-local athlete, brand, review, follow-up, and outreach state plus persistence metadata
  • PUT /state or POST /state: replaces the signed-in user's module-local state; used by the iframe after manual capture, CSV/JSON import, review decisions, matching/outreach updates, and follow-up edits

Excel Editor module API highlights under /api/modules/excel-canvas/api:

  • GET /docs/:id: returns raw cells plus a workbook-aware sheetSnapshot payload for merges, hidden rows/columns, style metadata, comments, hyperlinks, and unsupported feature warnings
  • PATCH /docs/:id: accepts plain cell updates or workbook-aware operations[] batches. Operation vocabulary: set_cells, set_styles (target ref/range/cells plus style and optional full replace), merge, resize, hide_show, insert_delete, and set_pane (xSplit, ySplit).

Agent Orchestrator module API highlights under /api/modules/agent-orchestrator/api:

  • GET /tools
  • POST /repos
  • GET /status
  • GET /poll
  • POST /config
  • GET /webhook/:token
  • POST /webhook/:token
  • POST /orchestrator/start
  • POST /orchestrator/stop
  • GET /remote-runners
  • POST /remote-runners/security-preflight
  • POST /remote-runners/heartbeat
  • POST /remote-runners/claim
  • GET /remote-runners/[runnerId]/codex-sessions
  • GET /remote-runners/[runnerId]/codex-sessions/[sessionId]
  • POST /remote-runners/[runnerId]/codex-sessions/[sessionId]/follow-up
  • POST /remote-runners/jobs/[id]/events
  • GET /jobs
  • POST /jobs; repository jobs accept forgeProvider, forgeIntegrationName, forgeBaseUrl, and cloneUrl, but authenticated remotes are derived server-side from the resolved named connection so caller-controlled origins never receive stored credentials. Detached jobs may set canonical provider: "clapilot-code" plus a non-subscription catalog model to run through Clapilot's in-process coding loop (pi, embedded_pi, embedded-pi, and clapilot_code remain accepted aliases), detached Codex jobs may set executionTarget: "remote" so a connected remote Codex runner claims the work over the pull-based remote-runner API, and Codex or Claude jobs may set codexGoalEnabled: true to prepend /goal <task goal> to the first turn. Local Codex, Claude Code, and Clapilot Code jobs receive the restricted coding_core tool profile: repository shell/edit capabilities plus read-only Clapilot context, memory, Knowledge, Learning, search, and status tools. The only business mutation is aufgaben_add_comment, and it succeeds only when the coding session is bound to the exact originating Symphony task; memory writes, generic catalog dispatch, and every other business-action tool remain excluded
  • GET /jobs/[id]
  • GET /jobs/[id]/stream
  • GET /jobs/[id]/workspace-files?q=...&limit=... to search bounded, Git-ignore-aware relative paths in an owned local job workspace. Linked jobs use the linked session cwd; remote jobs return unavailable
  • GET /jobs/[id]/workspace to inspect an owned local job workspace through a bounded read-only snapshot: relative file inventory plus Git branch, porcelain status, unstaged diff, and staged diff. The endpoint accepts no command input, applies trusted-root and ownership checks, and is unavailable for remote jobs
  • POST /jobs/[id]/follow-up to continue a detached job with another prompt. Session-backed jobs resume the linked background session; plain CLI jobs, including Claude CLI jobs, run the follow-up in the original job workspace. Remote Codex jobs are requeued for the same remote runner workspace. Local jobs accept text, optional attachments[], validated relative fileReferences[], or a combination; remote jobs reject file references because their workspaces are not server-local
  • DELETE /jobs/[id]
  • GET /sessions?view=summary|full&limit=...; summary is the default and omits embedded event/chat-history arrays while keeping compact status, model, activity, and latest-output previews. view=full remains available for compatibility and diagnostics; clients should load one selected session through its detail endpoint instead of polling full lists
  • POST /sessions with the initial session turn payload; accepts text, optional attachments[], or both, canonical provider: "clapilot-code" plus model for the internal embedded_pi adapter, plus optional codexGoalEnabled: true for Codex or Claude sessions. Repository sessions also accept forgeProvider, forgeIntegrationName, forgeBaseUrl, and cloneUrl; named-connection and GitLab sessions use the embedded runtime so provider credentials remain session-scoped.
  • GET /sessions/[id]
  • DELETE /sessions/[id]
  • GET /sessions/[id]/workspace-files?q=...&limit=... to search bounded, Git-ignore-aware relative paths in the owned local session cwd
  • GET /sessions/[id]/workspace to inspect an owned local coding-session workspace through the same bounded read-only file and Git snapshot contract
  • POST /sessions/[id]/turns to continue an interactive session; accepts text, optional attachments[], validated relative fileReferences[], or a combination. Referenced files are inspected from the active cwd rather than uploaded into the turn; the request is rejected if a selected path is stale or no longer resolves inside that cwd
  • GET /sessions/[id]/stream; optional replay=0 sends the initial session snapshot without re-emitting every historical event after it, which is the preferred selected-session reconnect contract
  • POST /sessions/[id]/fork
  • POST /sessions/[id]/archive

Remote runner heartbeats may include activeJobIds[], authenticated active claim proofs as activeJobs[] entries shaped like { id, claimToken }, codexSessions[], codexSessionsScannedAt, codexSessionScanError, and codexSessionDetails[]; heartbeat and claim responses may include cancelJobIds[]. Every session summary and nested detail session carries harness: "codex" | "clapilot-code"; the server defaults an absent field from older runners to "codex". Active claim proofs let the module reconcile an in-flight remote assignment after its own process restarts without accepting a claim from a different runner. Cancellation-aware Node runners identify as codex-remote-runner/0.2.2 or newer, and the macOS runner identifies as clapilot-remote-runner-mac/0.2.0 or newer. POST /remote-runners/security-preflight returns 200 when a shell-policy transition is safe, or 409 with REMOTE_RUNNER_UPDATE_REQUIRED or REMOTE_RUNNER_CANCELLATION_PENDING while active remote work cannot yet be safely drained. The runner builds its session snapshot from state_*.sqlite rows plus CLI/Desktop history files under the selected Codex home (session_index.jsonl and sessions/**/rollout-*.jsonl), and from Clapilot Code JSONL sessions under ~/.clapilot-code/sessions or the configured override. The server stores the latest per-runner snapshot in memory. GET /remote-runners/[runnerId]/codex-sessions returns the latest session summaries for the selected machine. GET /remote-runners/[runnerId]/codex-sessions/[sessionId] returns cached transcript detail when available; otherwise it records a detail request and returns requested: true, then the pull-based runner includes the transcript excerpt in a later heartbeat.

POST /remote-runners/[runnerId]/codex-sessions/[sessionId]/follow-up uses the same authenticated module browser/admin context as the sibling remote-runner inspector calls and accepts { message: string, model?: string }. It returns { jobId, status }, creates a queued job pinned to the selected runner, and stores the scanned session cwd and harness for execution. Empty messages return 400; unknown runners or sessions return 404; an instance with CLAPILOT_SHELL_TOOLS_ENABLED=false returns 403 with CLAPILOT_SHELL_TOOLS_DISABLED. Resume assignments use mode: "resume-session", resumeSessionId, resumeCwd, resumeHarness: "codex" | "clapilot-code", and reuseWorkspace: false. Codex assignments invoke codex exec resume; an explicit request model is forwarded while an omitted model preserves the session model. Clapilot Code assignments use command: "clapilot-code" with arguments: ["exec", "resume", sessionId, prompt]; the runner resolves the installed launcher, adds --cd, and relies on the model already saved in the session. Codex resumes require codex-remote-runner/0.2.3 or newer, Clapilot Code resumes require codex-remote-runner/0.2.4 or newer, and clapilot-remote-runner-mac/* is excluded from both.

GET /status now also returns richer tracked-PR follow-up observability, including the latest tracked scan results plus recent handled/failure entries for guarded PR comment triage and follow-up dispatches. Each repoConfigs[] entry can include githubTriggerMode, githubWebhookPath, and githubWebhookUrl so Settings -> Agent Orchestrator can show a repo-specific tokenized GitHub webhook endpoint. Enabled repository automations may additionally carry prReviewModel, issueObserverModel, and mentionObserverModel objects shaped as { provider: "codex" | "claude" | "clapilot-code", model: string }; the concrete model implies the runtime harness, and Main-CI fixes reuse issueObserverModel.

GET /status also returns effectiveMaxConcurrentAgents and a capacity object with active and synchronously reserved run counts, available slots, the current blocking reason, and total/free/reserved/per-run memory figures in MiB. The configured maximum defaults to one safe local run. Every local Symphony or repository-automation dispatch is gated by the shared slot count and the instance memory reserve before asynchronous preparation begins; stopped CLI jobs terminate their complete process group so coding subprocesses cannot survive as orphan workers.

POST /orchestrator/start and POST /orchestrator/stop now persist the Symphony enabled state in app_settings.agent_orchestrator_symphony_enabled. GET /status returns enabled; when disabled, the poll loop and manual poll endpoint do not dispatch aufgaben candidates or retry queued Symphony tasks. GitHub automations are controlled separately by the per-repository automation matrix, so issue observer, PR review, mention observer, and tracked-PR follow-up can continue even when Symphony task-board dispatch is disabled.

Symphony aufgaben dispatch is scoped by the default board in app_settings.agent_orchestrator_symphony_task_board_id plus repo-specific task-board mappings stored in app_settings.agent_orchestrator_repo_automation_config. POST /config accepts taskBoardId/symphonyTaskBoardId for the default board and accepts repoConfigs[] entries with optional taskBoardId; GET /status returns taskBoardId, taskBoardName, observedTaskBoardIds, and repoTaskBoardMappings. When neither a default board nor a repo board mapping is configured, no aufgaben candidates are dispatched. Tasks in mapped repo boards inherit the GitHub repo from the board mapping and use that repository's concrete issueObserverModel for the coding job, including the implied harness; legacy rows without a model use the global issue-observer provider fallback. Tasks in the default board still need repo:owner/name in their description before Symphony starts a coding job.

Container bootstrap does not start Symphony by default; deployments that intentionally want boot-time activation must set CLAPILOT_AGENT_ORCHESTRATOR_BOOTSTRAP_ENABLED=true.

Symphony repository tasks now require PR traceability in the generated pull request body: Requested-by: Symphony task ... plus Clapilot task: .../aufgaben/{id}. The URL uses the configured public app URL from app_settings.public_base_url or public URL environment aliases (PUBLIC_BASE_URL, CLAPILOT_PUBLIC_BASE_URL, NEXT_PUBLIC_APP_URL, CLAPILOT_EXTERNAL_URL) and falls back to the app-relative task path; internal service URLs such as CLAPILOT_BASE_URL=http://clapilot:3000 are not used for this public PR link. Symphony and issue-observer PRs must also include a ## Testing Instructions section with concrete validation commands or manual checks; the web PR live-browser check and iOS/Mac Codex E2E workflow extract that section from the PR body and pass it into their Codex prompt context. The job record retains symphonyTaskId, and local coding MCP session keys append :symphony-task:{id}. The aufgaben_add_comment contract accepts { id, comment }, requires that full UUID to match the scoped origin, and writes a deduplicated Symphony agent comment for progress, implementation evidence, blockers, or questions; shared-board writes publish an Aufgaben reload. After detecting an opened PR, Symphony also writes its canonical URL, PR number, and open status as a system comment on the originating task independently of optional tracked-PR registration. The visible PR-link comment is generated in the task creator's persisted UI language (de, en, or it), with private-board owner and German fallbacks. A dedicated transaction takes a typed UUID advisory lock before the URL lookup and insert, making retries and concurrent completion paths idempotent while different PR URLs accumulate.

For regression tasks with exact session.*.failed signatures, the persisted job record also retains the linked PR and observation phase. The three-hour production window begins at the confirmed merge timestamp rather than PR creation, is reconciled after runtime restarts, and prevents both job cleanup and task completion while it is pending.

POST /config persists one GitHub/GitLab automation matrix (repoConfigs) plus review, issue-observer, mention-observer, and CI settings. Each entry records forgeProvider, integrationName, and forgeBaseUrl in addition to the existing trigger and feature fields, plus the optional per-automation model objects described above. Legacy global prReviewProvider, issueObserverProvider, and mentionObserverProvider values remain accepted as fallbacks for older rows without concrete models. POST /repos accepts connectionKeys (github:<name> or gitlab:<name>) and returns the union with provider, source connection, clone URL, and base URL metadata; legacy GitHub integrationNames remain accepted. POST /webhook/:token accepts GitHub repository webhooks and GitLab project webhooks, normalizes their issue, pull/merge-request, discussion/note, check/pipeline, status, and push event families, and scopes dispatch to the matching provider and repository.

Issue observer runs now close a GitHub issue as soon as implementation is picked up and comment that the observer is working on it, preventing another scan from starting a second implementation for the same issue. Existing issue sessions are reconciled through the persisted agent_events.event_data.sessionId linkage, and active issue-session conflicts are treated as already in progress instead of reopening the issue. Manual/detached jobs that explicitly target a GitHub issue use the same issue-level lock and are rejected while an implementation session is active or once an open PR already closes the issue. When the run produces a PR, the observer comments the PR link on the already-closed issue; if the run fails and no PR can be recovered, it reopens the issue and applies the normal failure backoff.

Tracked PR follow-up comment triage can return follow_up, reply, or ignore. reply posts a GitHub PR conversation comment for direct questions, status requests, and clarification comments that do not require a code change; follow_up remains reserved for low-risk code fixes on the existing PR branch and may include a follow-up result reply_body so the orchestrator posts a PR conversation reply after the branch update.

Apple native share workflow endpoints:

  • GET /api/apple/share-targets/telegram returns approved Telegram group targets that the iOS share workflow can present after a user shares a file into Clapilot.
  • POST /api/apple/share-targets/telegram/send accepts authenticated multipart form data with approvalId, optional message, and files[]. The route stages the uploaded files briefly in the workspace, sends them through ClapilotAICore's approved Telegram channel delivery, and removes the temporary staged files after the delivery attempt.
  • The native Apple share workflow also presents Issue melden for image/screenshot imports. That path reuses POST /api/issue-reporter with multipart images[], native Apple page context, and the user-entered text as the issue summary/details. The dedicated web and Apple issue reporter screens additionally expose the optional affected-platform selector described above.

News module API highlights under /api/modules/news/api:

  • GET /items
    • lists persisted news tiles; accepts limit, search, source_kind, athlete_name, review_status, and refresh=true to force an RSS sync before reading
  • POST /items
    • creates or idempotently upserts one news item for manual workflows, automations, and agent tool calls; clipping payloads may include athlete_name, project_name, partner_name, rating (1-3), relevance, source_reach, article_type, review_status, report_preview_url, screenshot_url, and import_source
  • PATCH /items/:id
    • updates a news item and its Presseclipping metadata for manual review and report preparation
  • GET/POST /settings
    • reads or updates RSS auto-ingest behavior and feed import limits
  • GET/POST/PUT/DELETE /rss-sources
    • manages configured RSS sources for the module
  • POST /rss-sync
    • forces an immediate RSS synchronization run

Accounting module API highlights under /api/modules/accounting/api:

  • GET /bootstrap
    • returns Mandanten, Kategorien, linked Dokumente, period-filtered entries, and the current accounting report snapshot for the selected client
  • GET /categories
    • lists all accounting categories
  • POST /categories
    • creates a custom accounting category
  • PATCH /categories/:id
    • updates one accounting category
  • GET /entries
    • lists accounting entries with mandant_id, period_kind, year, optional month / quarter, and optional direction/type filters
  • POST /entries
    • creates one accounting row with category/document linkage plus netto/steuer/brutto values
  • PATCH /entries/:id
    • updates one accounting row
  • DELETE /entries/:id
    • deletes one accounting row
  • GET /reports
    • generates the current bookkeeping and VAT-style period report including totals, category split, and VAT-code breakdown

Finanzen / Steuer-Manager module API highlights under /api/modules/tax-manager/api (UStVA transfer aids and EÜR drafts; full calculation and field details live in the module documentation):

  • GET /
    • returns module identity, legal notice, and the active endpoint inventory
  • GET /bootstrap
    • ensures the global Steuerbelege document folder and returns its UUID as tax_folder_id together with Mandanten, the owner's 50 most recent generations, per-user settings, and the legal notice
  • GET /settings, PUT /settings, POST /settings
    • reads or upserts the authenticated owner's { agent_model_ref }; empty values inherit the normal runtime default
  • GET /candidate-documents
    • requires mandant_id plus period selection and returns accessible beleg, rechnung, and ust documents for that Mandant and period
  • GET /overview-documents
    • returns all visible beleg, rechnung, and ust documents plus visible documents in the global Steuerbelege folder. Supports optional title search q, mandant_id, and limit (default 200, maximum 500), and returns tax_folder_id with each document's titel, type/date/amount/currency, Mandant identity, and folder flag
  • GET /generations, POST /generations
    • lists owner-scoped UStVA/EÜR generation history or creates a generation from { kind, period_kind, year, period_index?, mandant_id?, source, document_ids[] } with a heuristic line-item baseline
  • GET /generations/:id, DELETE /generations/:id
    • loads one generation with all source-document line items or deletes it with its dependent line items
  • POST /generations/:id/line-items
    • bulk-upserts per-document extraction/classification results and sets the generation to extracted; monetary fields are non-negative integer cents
  • POST /generations/:id/finalize
    • deterministically recalculates UStVA/EÜR totals and Prüfhinweise, stores report HTML, and sets the generation to finalized; it can be rerun after corrections
  • GET /generations/:id/html, GET /generations/:id/pdf
    • returns the finalized stored HTML or an on-demand PDF; both return 409 until finalization
  • GET /health
    • returns the lightweight module health status

Dedicated Steuer-Manager agent trigger (outside the bundled-module API base, runs in the Next.js app context):

  • POST /api/modules/tax-manager/run-agent
    • body: { generation_id }. Requires an authenticated owner of the generation. Resolves the enabled steuer-manager specialist, creates a dedicated chat session, enqueues a background task, marks the generation extracting, and returns { ok, task_id, generation_id, chat_session_id, pending_message_id, agent }. The per-user module model is stored on the session and passed as the task fallback. Migration 007 clears the bundled specialist default; an admin-set specialist default intentionally takes precedence over the per-user fallback. The specialist reads every source document, submits classifications, and invokes deterministic finalization.
  • GET /api/modules/tax-manager/run-agent?task_id=…
    • authenticated status poll for the task returned by POST. Returns task timestamps/error state, running, tool_statuses, and an optional reply_preview for the generating step.

The current agent tools are tax_manager_list_generations, tax_manager_get_generation, tax_manager_submit_extraction, and tax_manager_finalize_generation. Their detailed schemas and live-voice exposure are documented in Agent Tool Contracts.

Native runtime machine-token scopes (for /api/modules/[slug]/api/...):

  • modules:api:read for GET/HEAD/OPTIONS
  • modules:api:write for mutating methods

Skill platform

  • /api/skill-store/local
  • /api/skill-store/catalog
  • /api/skill-store/publish
  • /api/skill-store/install
  • /api/skill-store/delete
  • /api/skill-store/agent-skill

GET /api/skill-store/local returns local/effective skill entries plus computed setup metadata:

  • requiredToolNames[]: optional specialist tool permissions declared by skill frontmatter
  • requiredAuthResourceKeys[]: optional specialist auth/API scopes declared by skill frontmatter
  • setup.status: ready, needs_setup, or disabled
  • setup.unmetRequirements[]: unmet env / app_setting requirements
  • setup.installRecipe: optional one-click setup action for the admin UI

POST /api/skill-store/delete deletes one local non-bundled, human-authored skill directory by id. Bundled and agent-authored skills are explicitly protected; agent-authored skills must be archived.

POST /api/skill-store/agent-skill is admin-only and updates the lifecycle of one workspace origin: agent skill by dir_name. It accepts status (active, draft, or archived) and/or pinned; it never deletes files or mutates human/bundled skills.

Widget platform

  • /api/mini-apps
  • /api/mini-apps/[id]
  • /api/mini-apps/[id]/data
  • /api/mini-apps/[id]/dashboard
  • /api/widget-store/local
  • /api/widget-store/catalog
  • /api/widget-store/publish
  • /api/widget-store/install

POST /api/widget-store/publish publishes the current structured widget snapshot as a versioned hub artifact.

POST /api/widget-store/install downloads a published widget snapshot, creates or updates the shared local catalog entry by slug, and installs it for the current user while preserving that user's dashboard placement on re-install where possible.

Specialized agent platform

  • /api/specialized-agents
  • /api/specialized-agents/[id]
  • /api/agent-store/catalog
  • /api/agent-store/publish
  • /api/agent-store/install

POST /api/agent-store/publish serializes a specialist into a versioned hub JSON snapshot for the Store's Special Agents tab and the admin specialist settings. POST /api/agent-store/install downloads that snapshot and creates or updates a local specialist by handle, while keeping deployment-specific access such as embed API keys and permission bypass out of the imported state.

Mixture of Agents

Admin-only. Manages moa/<slug> virtual-model presets that combine one aggregator model with N reference models. See Mixture of Agents.

  • GET /api/agent-runtime/moa-presets{ presets: AgentMoaPreset[] }.
  • POST /api/agent-runtime/moa-presets with { slug, label, enabled, aggregatorModelRef, referenceModelRefs, settings }{ preset }. Returns 400 for invalid input (bad slug, missing aggregator/references, or a MoA ref used as aggregator/reference).
  • DELETE /api/agent-runtime/moa-presets?slug=<slug>{ ok: true, slug }.

All three proxy to the native runtime's GET/POST/DELETE /internal/moa-presets. AgentMoaPreset.settings carries referenceMaxTokens, maxReferenceTurns, referenceTimeoutMs, synthesisInstruction, and exposeReferenceOutputs. Enabled presets are surfaced in listModels() as selectable moa/<slug> models.

Model Routing

Admin-only. Manages route/<slug> virtual models used by native request classification and model selection.

  • GET /api/agent-runtime/routing-models{ routing_models: AgentRoutingModel[], capabilities: string[], properties: string[] }.
  • POST /api/agent-runtime/routing-models with { slug, label, description, enabled, members, settings }{ routing_model }. Each member has { model_ref, capabilities: string[], properties: string[], note: string }.
  • DELETE /api/agent-runtime/routing-models?slug=<slug>{ ok: true, slug }.

The capability and property arrays returned by GET are the runtime taxonomy the settings UI uses for its member chips. Enabled Routing Models are exposed to model catalogs as route/<slug> when global Routing Models are enabled. Management through agent/chat tools is intentionally not exposed in this iteration; this API backs the admin web UI only.

Agent-focused usage map

  • documents: /api/documents, /api/documents/upload, /api/documents/inbox, /api/documents/folders, /api/documents/folders/[id], /api/documents/[id], /api/documents/[id]/preview, /api/documents/[id]/analysis
  • mini apps: /api/mini-apps, /api/mini-apps/[id], /api/mini-apps/[id]/data, /api/mini-apps/[id]/dashboard
  • emails (user): /api/emails*, /api/drafts*
  • emails (agent): /api/angela/emails* and scripts/agent-email-poller.mjs
  • modules: /api/module-store/* and /api/modules/[slug]/*
  • cases module: /api/modules/cases/api/*
  • call agent: /api/call-agent/*

Detailed flow and tool contract:

Internal native runtime

The native clapilot-agent service exposes internal-only endpoints used by the app runtime client:

  • GET /health
  • GET /metrics
  • GET /internal/models
    • returns runtime providers, memory description, and chat model entries; model entries include runtimeProvider and supportsSteering
  • GET /internal/provider-models?slug=<provider-slug>
  • GET /internal/sessions
  • POST /internal/sessions/model
  • POST /internal/chat/completions
  • POST /internal/responses
  • POST /internal/runs
    • streams NDJSON lifecycle, tool, assistant, and completion events for the native agent loop
    • accepts optional servicePrincipalId / servicePrincipalSlug alongside userId so execution identity can differ from the persisted room/thread history
    • accepts optional timeoutSeconds to place an explicit per-run cap on native execution; when omitted, the Claude CLI bridge path is no longer hard-limited to 180s
    • current native tool events may include exec_command, package_install, web_search, context_search, context_get, memory_search, memory_get, memory_grep, memory_describe, memory_expand, knowledge_search, knowledge_get_entity, knowledge_neighbors, knowledge_explain_claim, learning_search, learning_get_object, session_status, tool_catalog_search, tool_catalog_expand, tool_execute, and Clapilot-owned mutation tools proxied through the app/runtime bridge
  • POST /internal/runs/steer
    • internal-only direct steering endpoint for a currently running hidden Codex subscription bridge turn, Claude CLI bridge turn, or native/embedded-PI run
    • accepts { sessionKey, message?, attachments? }, locates the matching :subscription-bridge:codex orchestrator session, and forwards text/base64 attachments through Codex app-server turn/steer
    • for Claude subscription bridge runs, writes a realtime user-message event to the active Claude CLI stream-json stdin pipe
    • for native/embedded-PI runs, appends a LIVE USER STEER notice to an active tool result or, when the provider is generating without a tool call, aborts the current provider HTTP request and immediately continues the same run with the new instruction; the original NDJSON stream stays open through that continuation so pending animation and final delivery remain intact
    • returns 409 when the current run is idle, in its transition/finalization gap, or otherwise lacks a steerable active turn; native transition responses use reason=native_boundary_transition and are deliberately not acknowledged so the browser queue cannot lose them
  • POST /internal/runs/abort
    • internal-only user-stop endpoint for whatever run currently backs a chat session key
    • accepts { sessionKey }; interrupts a running :subscription-bridge:codex orchestrator turn through Codex app-server turn/interrupt, aborts an active native/embedded-PI provider request (or flags an active tool call to stop at its boundary) and finalizes it as cancelled (error_code = user_abort), and sweeps every queued/running agent_runs row for the session key — covering runs whose executing process died in a runtime restart
    • after requesting cancellation, waits for the exact captured live run to terminate. Confirmed termination returns HTTP 200 with { ok: true, aborted: { orchestrator, native, dbRuns, terminated: true } }; an unconfirmed tool/provider stop or failed persistence sweep returns HTTP 409 with { ok: false, aborted: { orchestrator, native, dbRuns, terminated: false }, error }. Both outcomes log a run.aborted agent event.
  • GET /internal/learning
    • returns passive learning-object dashboard data: recent objects, recent audit events, and grouped stats
  • GET|POST /internal/learning-objects
    • lists or creates durable facts, procedure/skill proposals, and hot memory snapshots in the native learning ledger
  • POST /internal/learning-objects/retrieve
    • internal runtime retrieval path for prompt-ready approved learning objects; enforces approval, expiry, visibility, subject matching, per-run limits, and a token budget before returning the Approved learned context prompt block
  • POST /internal/learning-objects/extract
    • internal diagnostics/runtime path for conservative post-response extraction; creates canonical durable facts or low-risk procedure drafts from shared-fact/explicit-memory input. The opt-out policy activates eligible evidence-backed facts and preferences immediately; exceptions remain candidates.
  • POST /internal/learning-objects/curate
    • internal diagnostics/runtime path for the targeted deterministic safety pass. Optional assertionIds / assertion_ids limits the scan to Learning projections for those canonical assertions and returns targetedAssertionIds; Dreaming v2 uses this targeted path immediately after each completed audience partition. Supported Dream assertions receive system-policy approval, while unsupported Dream assertions receive a terminal system-policy rejection instead of remaining in the human-review queue. The protected scheduled Learning Curator uses a separate model-backed runtime path configured through the job payload fields model, batchSize, and minimumRejectConfidence.
  • GET /internal/learning-objects/:id
    • returns one learning object with linked approvals and audit events
  • POST /internal/learning-objects/:id/decision
    • records approval-state decisions and appends matching audit events
  • GET /internal/learning-objects/:id/audit
    • returns audit events for one learning object
  • GET|POST|PATCH|DELETE /internal/jobs
    • scheduled automations only; heartbeat jobs (job_type = 'heartbeat') are owned by the heartbeat module and excluded from this listing
  • POST /internal/heartbeat/trigger
    • runs a configured heartbeat immediately with { scope: "user" | "teamchat", userId? } and returns { ok, delivered, suppressed, text, outputPreview, runId }; the regular interval schedule is left untouched
  • GET|POST /internal/orchestrator-sessions
  • GET|DELETE /internal/orchestrator-sessions/:id
  • POST /internal/orchestrator-sessions/:id/turns
  • GET /internal/orchestrator-sessions/:id/stream
  • POST /internal/orchestrator-sessions/:id/fork
  • POST /internal/orchestrator-sessions/:id/archive
  • POST /internal/channels/:channel/inbound
    • accepts the native telegram, slack, whatsapp, signal, imessage, and instance_bridge channel types
    • instance_bridge payloads arrive pre-verified (the app route checks the fleet hub HMAC signature before proxying), are deduplicated by the payload eventId, and are matched against an existing approved bridge row; kind: "participant" messages mirror with the original sender name and may trigger the local main agent per the room's reply settings, while kind: "agent" messages mirror as agent messages and never trigger a local agent run
    • Telegram can stream natively: one visible reply message is created and then updated in place while deltas arrive
    • Telegram photo and document attachments are normalized into native input_image / input_file message parts before the run
    • Telegram forum/group topics now use chat.id:message_thread_id|root as the internal thread key, so agent memory/session scope is topic-aware while approvals still stay bound to the parent chat/group
    • approved group thread bindings may also carry the built-in global_team_service execution principal so the bot can run with service-level rights while preserving thread-local history
  • POST /api/agent-runtime/channel-mirror
    • internal-secret-only channel-to-Team-Chat display-copy endpoint for participant and agent messages
    • accepts optional attachments (maximum 10) with { type: "image" | "file" | "audio", name, mimeType, relativePath, size?, durationMs? }; relativePath must be workspace-relative and traversal-free
    • requires message text or at least one valid attachment; readable files contained by the configured workspace become URL-backed Team Chat attachments, and images receive the same URL as preview
    • participant mirrors also dispatch room-invited specialized agents (one or more explicit @handle mentions → exclusive parallel dispatch to that invited target set, no mention → all all_messages invitees) and return { specialistDispatched, suppressChannelReply }; suppressChannelReply: true tells the channel runtime to skip its own default-agent reply for that inbound message
    • final-result-equivalent agent mirrors may set automationDelivery: true together with the persisted automation runId; only this explicit signal suppresses the mirror itself through durable run-and-room deduplication. Explicit tool sends from a scheduled run use automationRun: true with the same runId to tag each independently persisted mirror for later correlation; they do not reserve or suppress one another. A duplicate final result returns { ok: true, duplicate: true, run_id, roomId }. Normal channel runs may carry a run ID for tracing but never enter either automation boundary.
  • POST /api/agent-runtime/channel-audio-transcription
    • internal-secret-only bridge from clapilot-agent channel ingestion to the configured STT runtime
    • accepts a traversal-safe workspace filePath plus optional mimeType; the existing agent-media workspace guard rejects paths outside the shared workspace
    • returns the transcript and resolved provider/model metadata, while provider credentials remain server-side
  • POST /internal/channels/send
    • internal-only outbound send path used by ClapilotAICore tool proxy for approved Telegram/Slack/WhatsApp/Signal/iMessage/instance-bridge sends from normal chat runs
    • instance_bridge sends resolve the bridge approval row, build the signed bridge message payload, and POST it to the peer instance's /api/agent-runtime/channels/instance_bridge/inbound; bridge targets receive raw text plus structured sender fields instead of the [Name via Clapilot] / [AgentName] text framing, and attachments degrade to name-only placeholders in v1
    • successful sends return { channel, recipient, text, mediaCount, targetLabel, approvalId, subjectKey, groupRoomId, mirroredRoomId }; groupRoomId identifies the Team Chat room mapped by the matched approval, while mirroredRoomId is present only after the outbound message was actually persisted in that room. The channel_send_message tool proxy separately normalizes these fields to snake_case for its public tool result.
    • messageOrigin: "team_chat_forward" sends to the provider without running the normal outbound Team Chat mirror; Telegram payloads containing both text and media[] send the text first and then the media, while other origins retain media-first ordering
    • every successful explicit tool call is mirrored independently, including multiple sends from one scheduled automation. Structured send metadata travels separately from the truncated human tool preview and is retained across native stream recovery. Persisted Team Chat mirrors carry the stable automation run ID; after an ambiguous mirror timeout, the automatic final-result path waits briefly for that row and suppresses itself only if the row appears. If it does not appear within the bounded wait, the configured final result is delivered normally. Database-backed final-result delivery reservations can be reclaimed after failure or a stale in-progress reservation; approved external-channel reservations remain at-most-once.
  • GET /internal/channels/whatsapp/auth
    • returns native WhatsApp Web linking/runtime status including linked identity, auth dir, reconnect state, and any active QR-login session
  • POST /internal/channels/whatsapp/auth
    • accepts { action: "start" | "wait" | "logout", force?, timeoutMs? }
    • start generates a QR-backed login session and returns a qrDataUrl when pairing is needed
    • wait polls for scan completion against the active backend-owned login session
    • logout clears the persisted native WhatsApp Web auth state under .clapilotaicore
  • POST /internal/packages/install
    • internal-only structured installer endpoint used by ClapilotAICore tool proxy; accepts kind = apt|brew|node|go|uv plus package-specific fields and executes the install inside the clapilot-agent container
  • GET /internal/memory/knowledge-graph
    • internal/admin diagnostics path for structured knowledge graph search; accepts query, limit, optional dreamId, optional instanceKey (default default), scope = personal|team|channel|all, and userId for the personal scope. It returns { entities, claims, edges, diagnostics } from the matching instance's agent_knowledge_* rows. The active graph is maintained automatically from approved/current assertion projections; dreamId narrows claims/edges by source_refs and entities by matching evidence from that Memory Dreaming run.
    • agent bootstrap and agent-facing knowledge_* tools still use the runtime visibility model; this all-scope behavior is limited to the internal/admin diagnostics path
  • POST /internal/memory/knowledge-graph/extract
    • internal/admin diagnostic repair path for rebuilding/backfilling graph items from approved canonical assertions associated with a dream; accepts { dreamId, memoryIds?, model? }, prunes prior graph source refs/evidence for that dream before re-extraction, and returns inserted/upserted entity, claim, edge, source-memory, and cleanup counts. Normal graph maintenance does not require this endpoint.
  • POST /internal/memory/retention
    • internal/admin maintenance path for the memory retention sweep; accepts optional { enabled?, supersededAfterDays?, evidenceMaxPerItem?, contextMessagesEnabled?, contextMessagesAfterDays? } overrides and returns purge/prune counts; also runs automatically after each nightly Memory Dreaming job
  • POST /internal/memory/reembed
    • internal/admin maintenance path for the embedding backfill; accepts optional { limit? }, re-embeds memories whose chunks were stored with a different embedding strategy/model than the active provider, and returns { reembeddedCount, candidateCount, targetKey }; skipped when no embedding provider is configured
  • GET /internal/memory/profile?userId=<uuid>&refresh=<0|1>
    • internal/admin diagnostics path for the precomputed per-user memory profile; returns { profile } with staticFacts (user-entity knowledge claims plus durable user facts), dynamicContext (recent user memories), the composed profileText injected at bootstrap, build stats, and builtAt; refresh=1 forces a rebuild instead of serving the cached agent_user_profiles row
  • POST /internal/tools/execute
    • internal-only native tool execution endpoint used when internal Clapilot services need the same native tools as /internal/runs
    • currently covers exec_command, web_search, context_search, context_get, memory_search, memory_store, memory_get, memory_grep, memory_describe, memory_expand, knowledge_search, knowledge_get_entity, knowledge_neighbors, knowledge_explain_claim, learning_search, learning_get_object, session_status, tool_catalog_expand, and Clapilot-owned module/document tools proxied through /api/agent-runtime/tool-proxy, including notizen_* and notizen_duplicate_local. Broad context_search results use reciprocal-rank fusion, canonical assertion/topic identities, confidence tie weight, and a per-source reservation before the final limit.

Admin config for the native runtime is exposed through Clapilot itself:

  • GET /api/agent-runtime/config
  • POST /api/agent-runtime/config
    • returns and stores { providers, channels, model_routing }; both methods also return warnings, including { providerSlug, providerLabel } entries when an enabled fallback chain has no enabled provider slug outside its own failure domain
    • each provider's explicit enabled value is authoritative. Disabled providers retain their encrypted credentials and model configuration but are excluded from model catalogs, selection, and fallback routing. Enabled providers may have no text models when they are used only for audio, image, embedding, or other non-chat capabilities; text defaults and fallback routing consider only compatible configured text models.
    • provider enable/disable transitions are recorded transactionally in agent_provider_config_audit_events with the provider slug, authenticated admin when available, change source, previous/next state, and model/label context
    • provider rows accept provider_type=ollama; local base URLs require no API key, while https://ollama.com uses the encrypted api_key. The optional ollama_session_cookie and clear_ollama_session_cookie fields update only the browser session used for Subscription Usage. Admin responses expose masked presence/hint metadata and never return the cookie value
    • successful config writes invalidate the Subscription Usage snapshot and last-good cache so newly saved, replaced, or removed provider credentials are applied on the next usage request
    • rejects enabled Codex OAuth provider rows that contain non-OpenAI chat models (for example grok-4.5); Grok chat models must be configured on an xai provider
    • saving an enabled OpenAI API-key or Codex OAuth provider performs a minimal write preflight against every configured chat transport that runtime routing can use (/chat/completions and/or /responses; Codex OAuth uses the Codex Responses base). Saving or activating an OpenAI-compatible provider performs a one-token /chat/completions capability probe for each configured model and persists token-free metadata.modelAvailability results. Definitive model-load failures exclude only that model from catalogs/default routing; inconclusive connectivity failures remain routable. A failed provider-wide preflight clears default eligibility.
    • each channel accepts enabled, allow_direct_messages, encrypted credential fields, and provider-specific settings; settings.mention_only = true makes approved group/channel traffic run only when Clapilot is explicitly mentioned
    • model_routing.priority is an ordered list of fully qualified model refs such as openai/latest for the newest Codex OAuth GPT coding model, claude-default/latest for the newest Claude subscription coding model (claude-opus-5), concrete pins such as openai/gpt-5.6-sol, openai/gpt-5.6-terra, openai/gpt-5.6-luna, openai/gpt-5.5, claude-default/claude-opus-5, or claude-default/claude-fable-5, or provider-specific refs such as google-gemini-default/gemini-2.5-flash
    • model_routing.routing_enabled is a boolean feature gate for route/<slug> virtual models and defaults to false
    • model_routing.default_routing_model_slug is the Routing Model slug used when a request does not explicitly select a model; an empty string leaves the existing provider/model default path unchanged
    • latest refs are moving Clapilot aliases resolved from the enabled provider catalog at execution time; concrete model IDs remain explicit pins
    • the first entry is the global default model for native runs; later entries are cross-provider fallbacks
    • model_routing.embedding_provider_slug and model_routing.embedding_model select the provider/model used for native memory embeddings and the document RAG indexer/retriever
    • model_routing.realtime_provider_slug and model_routing.realtime_model store the separate Realtime provider/model choice for live/call audio routing
    • model_routing.tts stores the global TTS provider slug, model, and voice used for async chat audio replies and other server-side speech synthesis defaults. Agent media_tts_speak accepts exact provider_slug and model overrides; explicit slugs must resolve to that enabled provider and are never replaced by the default. OpenAI-compatible providers are supported through their configured base_url plus /audio/speech; the model may be an exact gateway alias such as chatterbox, and authless compatible endpoints omit the bearer header.
    • model_routing.stt stores the global STT provider slug and model used for chat audio uploads and note dictation transcription defaults. Agent media_stt_transcribe accepts exact provider_slug and model overrides and passes the resolved runtime config into the outbound request instead of resolving the default again. OpenAI-compatible rows call their configured base_url at /audio/transcriptions, accept an exact/manual model alias, send the stored key as a bearer token when present, and omit authorization for authless local endpoints.
    • model_routing.image_generation stores the global image-generation provider slug and model used by /api/generated-images/* and agent image tools. Both image endpoints/tools accept optional exact provider_slug and model overrides; explicit providers never fall back to the global default, while legacy recognized model-family overrides select the matching provider type. OpenAI API-key rows use /images/*; OpenAI-compatible rows call the selected provider base_url with the OpenAI-compatible /images/generations path, and only use /images/edits when the provider metadata explicitly marks image edits as supported; OpenAI-compatible text-to-image defaults are skipped for edit calls only when no explicit provider was requested. OpenAI-Codex OAuth rows use the Codex Responses image_generation tool with gpt-image-2-class models. Agent images_edit can import workspace-local source_image_path inputs before handing image bytes to the selected provider.
    • media_generation_provider_configs stores AI media provider configuration for video and music. app_settings.media_model_catalog stores curated video, image, and music arrays of { providerSlug, model, isDefault }; saves require enabled compatible providers and normalize every non-empty list to exactly one default. A non-empty capability catalog overrides its legacy single default, while an empty list preserves the old behavior. Each enabled OpenAI-compatible runtime provider with a base URL is mirrored into the video-provider selector and uses the OpenAI Videos lifecycle: multipart POST /videos, GET /videos/{id} polling, and authenticated or authless GET /videos/{id}/content download. Those rows inherit the mapped runtime provider's base URL by default; the optional settings.videoBaseUrl overrides it for the whole video lifecycle, and settings.videoAuthDisabled suppresses the Authorization header. Use the override when the video backend is reachable directly but chat runs through a gateway — job ids issued by a gateway are encoded routing tokens and are not interchangeable with the backend's own ids, so submit, status, and content must all target one host. Both keys survive the automatic runtime-provider sync and are editable under Settings -> ClapilotAICore -> AI media -> Video generation. GET and POST /api/media-generation/providers return the catalog alongside provider rows, augment compatible video rows with available_models from the mapped runtime provider's live /internal/provider-models discovery, and augment xAI rows from /video-generation-models; model_discovery_error reports fallback to saved/manual models without invalidating the current selection. The chat-facing videos_generate/videos_status path uses the video catalog default when no explicit provider/model is supplied, accepts image_id/source_image_path for image-to-video, persists jobs and source-image lineage in generated_videos, and streams ready files through /api/generated-videos/[id]. livestream_generate_music applies the same rule to the music catalog. Explicit tool/API provider and model arguments still win. Live Stream Studio keeps using livestream_generate_video for livestream assets.
    • model_routing.non_specialized_agent_core stores the default agent-core selection for providers without their own specialized bridge: native or embedded_pi; Agent Orchestrator exposes the latter as the clapilot-code coding harness
    • embedded_pi keeps the chosen provider/model transport, but runs normal agent turns with the selected runtime tool profile and expanded tool-loop budget; Agent Orchestrator coding runs explicitly select coding_core, which keeps shell/package primitives plus read-only Clapilot recall and excludes shared-memory writes and business mutations
    • model_routing.adaptive_routing configures native per-request model/profile routing:
      • mode: off, shadow (default), or apply
      • candidate_limit: maximum number of ordered model_routing.priority entries considered, clamped to 2..8
      • exploration_rate: bounded exploration probability, clamped to 0..0.25
      • min_samples: observations required before learned outcomes receive their full configured influence
      • switch_margin: minimum score advantage required to leave the base/recent session model
      • route_harness: whether apply mode may choose between Clapilot-code Assistant (native) and Coding (embedded_pi)
    • explicit request/session model or profile choices, specialized agents, and Mixture-of-Agents presets bypass adaptive application. Shadow/apply decisions and technical outcomes are persisted in agent_adaptive_route_decisions.
    • routing-model (route/<slug>) selections are not bypassed but scoped: learned outcomes may reorder members within the routing model's tie band (bounded by min_samples and switch_margin, capability tags always win); the decision row records the routing model in routing_model_slug with the static pick as base and the outcome pick as recommendation
  • GET /api/agent-runtime/adaptive-routing/decisions
    • admin-only read endpoint backing the adaptive-routing data view in Settings -> ClapilotAICore -> Model Routing
    • accepts ?limit= (1–200, default 50) for the recent-decisions list
    • returns summary (total, last-7-days, outcome-status counts, protected count, recommended vs. applied switches, routing-model totals and agreement count), stats (per task class and selected model over the 90-day learning window: samples, completed, failed, average success score, average duration), and decisions (most recent rows from agent_adaptive_route_decisions incl. routing_model_slug, base/recommended/selected model, protection/decision reason, and technical outcome)
    • Codex OAuth provider metadata can include modelReasoningEfforts, a map from configured model id to an explicit Codex effort (minimal, low, medium, high, xhigh, max, or ultra). Missing entries keep the model's Codex-advertised default. Explicit per-session Agent Orchestrator effort overrides the provider value.
  • GET /api/agent-runtime/channel-approvals
    • returns pending, approved, and denied native channel approval records in approvals plus rooms, the admin's non-archived mappable public/private team-chat channels and group rooms, for the ClapilotAICore settings UI
  • POST /api/agent-runtime/channel-approvals
    • admin-only approval decision endpoint; accepts { id, status } where status is approved, pending, or denied
    • DM approvals should additionally send { linkedUserId }; the runtime then maps the external DM thread onto that user's canonical main web-chat session key
    • group approvals can additionally send { groupRoomId, mirrorToChannel? }; non-empty room ids must identify an existing, non-deleted, non-direct team-chat room, and the runtime then maps the external group thread onto that room's session key, defaulting to clapilot-members; mirrorToChannel persists the opt-in reverse-mirroring flag as metadata.mirror_to_channel
    • already approved entries can be re-saved with a different mapping, or reset back to pending to block replies again without deleting the approval record
  • GET /api/agent-runtime/channel-bridges
    • admin-only list of this instance's instance_bridge channel-approval rows (both initiator and peer roles) with resolved local room names, for the ClapilotAICore Channels settings UI
  • POST /api/agent-runtime/channel-bridges
    • admin-only bridge creation on the initiating instance; accepts { peerInstanceId, peerRoomId, localRoomId } where peerInstanceId is a hub_monitored_instances id or a fleet instance
    • the server resolves the peer base URL and label, generates the shared bridge_id, calls the peer's signed POST /api/hub/channel-bridge/register, and only on success creates the local approval row with role initiator; a failed peer call returns 502 and creates nothing locally
  • POST /api/agent-runtime/channel-bridges/peer-rooms
    • admin-only server-side signed call to the selected peer's POST /api/hub/channel-bridge/rooms; accepts { peerInstanceId } and returns the peer's mappable room list for the create dialog
  • DELETE /api/agent-runtime/channel-bridges/[id]
    • admin-only bridge removal; deletes the local approval row and sends a best-effort signed remove to the peer — peer failure is logged and the local delete still succeeds
  • POST /api/agent-runtime/channel-response
    • internal-only Clapilot channel execution bridge used by clapilot-agent; runs approved channel messages through the same Clapilot delegation path that powers broader app actions
    • this route executes directly through the native ClapilotAICore run path; legacy gateway fallback is compatibility-only
    • now fail-closed: requires a valid x-clapilot-agent-secret header and refuses requests when no internal secret is configured
  • POST /api/agent-runtime/channels/:channel/inbound
    • optional public webhook ingress for external channel providers; the app forwards the payload to native clapilot-agent /internal/channels/:channel/inbound
    • imessage accepts only BlueBubbles new-message webhooks and requires a password or guid query parameter matching the encrypted iMessage channel credential; mismatches return 403
    • instance_bridge is not a blind proxy: the fleet hub HMAC signature (x-clapilot-instance-id, x-clapilot-ts, x-clapilot-signature) is verified on the raw body before proxying, a bad or missing signature returns 401, and GET is not supported for this channel. Unknown bridges are rejected without creating pending approvals. The signed bridge message wire payload posted by the sending instance's agent service is:
{
  "bridgeId": "…",
  "originInstanceId": "…",
  "originRoomId": "…",
  "eventId": "<originInstanceId>:<chat_group_messages.id>:<participant|agent>",
  "kind": "participant" | "agent",
  "senderName": "Alexander Deutsch",
  "senderKey": "user:<uuid>",
  "agentName": "Clapilot",
  "text": "…",
  "attachments": [{ "name": "a.pdf" }],
  "sentAt": "ISO-8601"
}
  • agentName is present only for kind: "agent"; attachments carries names only in v1 (the receiving side renders 📎 <name> placeholders)
  • GET /api/agent-runtime/channels/whatsapp/inbound
    • public WhatsApp Cloud / Meta webhook verification handshake; validates hub.verify_token against the native channel settings JSON (webhook_verify_token, webhookVerifyToken, verify_token, or verifyToken) and returns the plain hub.challenge
  • GET /api/agent-runtime/channels/whatsapp/auth
    • admin-only native WhatsApp Web status endpoint used by the ClapilotAICore Channels settings UI
  • POST /api/agent-runtime/channels/whatsapp/auth
    • admin-only QR-login control endpoint for native WhatsApp Web; proxies start, wait, and logout actions into clapilot-agent
  • GET /api/agent-runtime/provider-models?slug=<provider-slug>
    • loads the current provider model catalog so the ClapilotAICore UI can populate the provider model dropdowns
    • the dedicated Audio and AI Media routing tabs consume this live provider catalog directly for realtime/TTS/STT/image model selection instead of reusing only the manually added chat-model entries. OpenAI-compatible TTS additionally keeps the model field editable, because gateway aliases do not have to contain a tts marker and some speech-only servers do not implement /models.
    • Gemini provider catalogs can feed both media routing and chat routing; chat selectors exclude obvious Gemini media-only model ids while text-capable Gemini models run through the native generateContent adapter
    • OpenAI Codex OAuth providers use the local Codex app-server model/list catalog for this lookup, including the current GPT-5.6 Sol/Terra/Luna lineup, GPT-5.5, GPT-5.4, GPT-5.4 Mini, subscription-only GPT-5.3 Codex Spark, advertised per-model speed tiers, supported reasoning efforts, and each model's default reasoning effort; OpenAI API-key providers still use the OpenAI /models endpoint and do not receive Spark as a shipped default
  • Anthropic providers backed by a Claude setup-token use the shipped Claude subscription catalog, including claude-opus-5 and claude-fable-5, and execute chat requests through the local Claude CLI bridge rather than direct Anthropic /v1/messages calls
    • the runtime now persists one hidden Claude bridge session id per Clapilot session_key, so follow-up turns resume the same Claude Code conversation until the session model is changed or cleared
    • AWS Bedrock providers use the Bedrock bearer-token/API-key path in the supported admin flow; the loader attempts to return both foundation-model ids and Bedrock inference profiles, but manual model entry may still be required when Bedrock control-plane discovery is unavailable on that auth path
    • Azure OpenAI providers normalize the configured resource URL onto /openai/v1, keep requests under that path, and only append apiVersion when it is a preview/date-style value instead of v1; the runtime accepts both Azure API-key auth and OpenAI-style bearer auth for Azure v1
    • when the upstream model list exposes token limits, Clapilot forwards optional per-model metadata such as contextWindow / outputTokenLimit
    • session execution does not call this endpoint live; it uses persisted provider config first, then the shipped OpenAI/Anthropic model-limit fallback catalog, and only then the generic 32,000 / 4,096 emergency defaults
    • provider discovery still understands legacy openai-codex/... refs, but Codex OAuth GPT models are exposed as canonical openai/gpt-* refs; standard native chat may select OpenAI-Codex, execute it through the Codex bridge, and now keep a hidden reusable Codex thread per Clapilot session_key for follow-up turns
    • direct OpenAI/Azure GPT-5.6 API models (gpt-5.6, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna) execute through /responses even for text-only turns; this preserves the supported reasoning-plus-tools path instead of using Chat Completions with GPT-5.6's reasoning default
    • provider config metadata may also include modelExtendedContextWindows, a per-model boolean map that re-enables the larger public OpenAI fallback profile for eligible Codex-auth models when admins explicitly turn it on
  • GET /api/agent-runtime/provider-status?slug=<provider-slug>
    • admin-only provider health endpoint for the ClapilotAICore settings UI
    • returns whether the saved provider is configured and whether auth/connectivity is currently working; failed OpenAI write preflights return status: "missing_scope" when applicable, plus missingScopes and a token-free repairHint. OpenAI Codex setups are allowed to validate from the stored OAuth credential and do not fail just because a stale OpenAI API key is also present, AWS Bedrock setups validate against the configured region plus the stored Bedrock API key, and Azure OpenAI setups validate against the normalized Azure v1 endpoint plus stored API key
  • GET /api/agent-runtime/memory
    • returns native memory diagnostics including shared workspace path, mounted legacy compatibility state path, detected bootstrap files, detected memory/**/*.md files, detected legacy session transcript files, embedding backend/model, retrieval tuning, native pre-compaction memory-flush settings, sync state, current native DB layer/chunk counts, Memory v2 assertion/evidence/embedding/outbox health, visibility-scope counts, active/superseded memory-state counts, Memory Dreaming counts, structured knowledge graph counts, typed memory-relation counts, per-user profile counts, and lossless context graph counts
    • the Runtime Memory UI now explicitly distinguishes durable-memory ownership (Clapilot Native) from context-window maintenance ownership (native provider loop vs embedded_pi) and shows recent Memory Dreaming runs plus the Knowledge Graph inspector
    • the shared bootstrap-file editor uses this endpoint's promptFiles inventory but is shown on the dedicated Bootstrap-Dateien settings subpage next to Runtime Memory
  • GET|PUT /api/agent-runtime/bootstrap-files
    • admin-only read/update endpoints for the native runtime bootstrap/prompt files backing the Bootstrap-Dateien settings subpage
  • POST /api/agent-runtime/memory
    • accepts optional { mode: "workspace" | "compatibility" | "all" }
    • default workspace mode runs an idempotent synchronization from the shared workspace memory/**/*.md tree into native agent_memories + agent_memory_chunks
    • compatibility mode migrates persisted legacy transcript memory from OPENCLAW_STATE_DIR/agents/*/sessions/*.jsonl into the native recall store, reusing sessions.json metadata when available to preserve original session keys
  • GET /api/agent-runtime/memory/dreams
    • admin-only list of recent Memory Dreaming runs with status, trigger, input/output refs, stats, model, and rollback state
  • POST /api/agent-runtime/memory/dreams
    • admin-only manual Memory Dreaming v2 run; accepts optional { triggerKind, force, model }, validates strict structured output, applies the versioned deterministic automatic assertion policy, and creates every Wiki change only as a review-required draft
    • after each completed audience partition, the runtime immediately curates that partition's returned assertionIds before starting the next one and processes the matching projection work. Safe, durable, evidence-backed facts, preferences, decisions, constraints, and procedures can therefore reach recall and extend the Knowledge Graph without human review or the recurring curator schedule; unsupported, sensitive, transient, time-bounded, conflicting, or insufficiently supported Dream assertions are automatically rejected. The response includes an aggregate curation result plus partitionCurations. If a Dream persists but immediate curation/projection fails, the response keeps the Dream result and returns curation.ok=false; the Runtime Memory UI reports this as a partial failure instead of a successful graph build
    • an explicit model on a manual run may be a configured Codex or Claude subscription model and is honored exactly for that Dream and its graph extraction; scheduled runs continue to exclude subscription bridges, and unavailable explicit models fail closed without provider fallback
    • the Memory settings UI sends the current global priority model by default and can override it per manual run; malformed, empty, truncated, or ungrounded structured output is rejected and the partition is failed/skipped without a deterministic fallback or Wiki publication.
  • POST /api/agent-runtime/memory/dreams/{id}/rollback
    • admin-only rollback for one dream run. Dreaming v2 compensates candidates, system-policy activations that have not received a human decision, drafts, outbox items, projections, and conflicts. A human-reviewed assertion/proposal or a published proposal blocks rollback. Legacy dream-v1 rows retain the older inserted-memory removal and superseded-source restoration behavior.
  • GET /api/agent-runtime/memory/knowledge-graph
    • admin-only proxy for structured knowledge graph diagnostics; accepts query, limit, optional dreamId, and scope = personal|team|channel|all (default personal), returning entities, claims, edges, and coherence diagnostics for that inspector view. The proxy derives the personal owner id from the authenticated admin session rather than accepting it from the browser. The active graph is maintained automatically from approved/current assertion projections; the UI can also filter the selected scope to one Memory Dreaming run.
  • POST /api/agent-runtime/memory/knowledge-graph
    • admin-only diagnostic repair path for graph rebuild/backfill; forwards { dreamId, memoryIds?, model? } to the native runtime and is not required during normal automatic graph maintenance
  • Learning object contracts for durable facts, procedure/skill proposals, hot memory snapshots, approval/audit state, visibility scopes, and token-cost attribution now have admin/native storage endpoints, an internal approved-learning prompt retrieval path, read-only agent tools (learning_search, learning_get_object) for approved visible objects, unified read access through context_search / context_get, an internal conservative extraction path, opt-out safe fact/preference activation, and a configurable model-backed scheduled curator. There are still no agent-facing learning mutation tools; activation, curation, and exception review remain runtime/control-plane paths. See Clapilot-Agent Learning Contracts.
  • GET /api/agent-runtime/sessions
    • admin-only session diagnostics endpoint for the ClapilotAICore Sessions settings page
    • without query params, returns the runtime session list from agent_session_state enriched with chat-session mapping, user labels, runtime path / maintenance owner, last-run status, aggregate run/event counts, lossless context counters, shared-fact counters, memory-flush/compaction counts, last compaction marker, last observed prompt-budget telemetry, and chat_nachrichten message counts where a matching chat_sessions row can be resolved; channel-bound interactive Agent Orchestrator / Codex app-server sessions from agent_external_sessions are also surfaced when they share the same Clapilot session_key
    • with sessionKey=<runtime-session-key>, returns one session plus recent agent_runs, recent agent_events, resolved app chat transcript rows from chat_nachrichten, raw bootstrap_meta, raw state_json, optional external-session metadata from agent_external_sessions, shared-memory stats, lossless-context stats, normalized promptBudgetStats, normalized compactionStats, normalized memoryFlushStats, and the current safeguard strategy snapshot used by the Sessions inspector
    • the Sessions UI now shows both the native session runtimePath and the resolved execution harness/bridge from externalSession metadata, including the preferred external harness session id when a Codex or Claude subscription bridge is attached
    • the Agent Orchestrator web module also consumes this endpoint to surface Clapilot-code (embedded_pi) and Claude bridge sessions inside its activity rail. Claude bridge details use the same turn-card timeline format as interactive Codex output and include a footer composer for same-session follow-ups; Clapilot-code entries retain same-session follow-ups through the native runtime path.
    • promptBudgetStats includes model-limit provenance so the inspector can distinguish explicit provider limits, shipped model fallbacks, and the generic emergency fallback
    • compactionStats / memoryFlushStats include the current maintenance owner metadata for the native session layer
  • POST /api/agent-runtime/sessions?sessionKey=...
    • admin-only runtime-session follow-up endpoint used by the Agent Orchestrator module for Claude CLI bridge sessions
    • accepts { message?, attachments?, model?, preferSteer?, steerOnly? }; attachments[] use the same base64/data-url shape as orchestrator session attachments
    • with preferSteer/steerOnly, forwards first to POST /internal/runs/steer so active Claude CLI bridge streams can receive the prompt through stdin; otherwise it starts a normal native runtime turn with stored history enabled, which resumes the persisted Claude bridge session for the same sessionKey
  • GET /api/media-generation/providers
    • admin-only settings endpoint for media-generation providers and the curated catalog. Returns redacted Gemini video/music and Kie.ai video/music configs including capability, enabled flag, base URL, model list, live-discovered available_models, default model, settings JSON, and API-key hint. Gemini media uses the configured Gemini Runtime provider key instead of a second media-specific key. Kie.ai video model options include Veo 3 presets plus kling-3.0/video, bytedance/seedance-2, bytedance/seedance-2-fast, and bytedance/seedance-2-5.
  • POST /api/media-generation/providers
    • admin-only update endpoint for those provider configs and optional catalog. API keys are encrypted before storage; leaving api_key empty preserves the stored key unless clear_api_key=true. Catalog entries must reference enabled compatible providers, duplicate provider/model pairs are removed, and exactly one default is assigned per non-empty capability list.
  • POST /api/internal/livestream/topup
    • internal-only endpoint called by clapilot-streamer with x-clapilot-agent-secret plus Authorization: Bearer <internal-secret>. It wakes ClapilotAICore with queue/buffer context when the stream's agent top-up loop is due and logs agent.topup.* events.
  • POST /api/internal/livestream/media-generation/poll
    • internal-only endpoint called by clapilot-streamer with x-clapilot-agent-secret plus Authorization: Bearer <internal-secret>. It polls pending Kie.ai livestream media jobs by stored task ID, downloads completed provider result files into the shared media output directory, updates asset status/provenance, and records completion/failure events.
  • POST /api/internal/livestream/youtube-chat/poll
    • internal-only endpoint called by clapilot-streamer with x-clapilot-agent-secret plus Authorization: Bearer <internal-secret>. When YouTube chat ingest is enabled, it uses the linked Google OAuth user with YouTube readonly scope to discover the active broadcast, poll liveChatMessages, persist deduplicated chat messages, classify viewer questions/music/video/topic wishes into audience requests, and update chat poll health on the livestream channel.
  • POST /api/agent-runtime/tool-proxy
    • internal-only bridge for Clapilot-owned tool delegation
    • authentication accepts either the configured global internal secret or a random run-scoped execution capability issued to an active queued, running, or cancelling run. The run-scoped capability is sent in x-clapilot-agent-secret and requires a body sessionKey; when originSessionKey is present, the capability is bound to that originating session so same-identity delegated calls can still target another session. Any supplied userId, servicePrincipalId, or servicePrincipalSlug must match the persisted originating-session identity. Malformed, expired, cross-session, or identity-mismatched capabilities return 401. Shell children receive only the scoped capability, never the global internal secret.
    • session scope is resolved from persisted runtime state, not from a group:-looking key. Team execution requires a live room plus the persisted global_team_service principal; actor_user_id retains the verified triggering room member only for personal integrations and audit fields. Persisted active service-principal sessions are also valid without a Team Chat room for userless system automations such as Morning Briefing TTS; this does not grant Team Chat identity, and caller-asserted global principals without persisted session binding remain rejected. originSessionKey binds CLI/MCP/rescue calls to their originating identity: Team Chat delegation must retain room, principal, and actor, while system automation delegation must retain the same persisted service-principal ID.
    • proxies Clapilot UI tools plus native agent_todo_update, exec_command, web_search, context_search, context_get, memory_search, memory_get, memory_grep, memory_describe, memory_expand, knowledge_search, knowledge_get_entity, knowledge_neighbors, knowledge_explain_claim, learning_search, learning_get_object, session_status, package_install, issue_reporter_create, calendar_*, aufgaben_*, scheduled_tasks_*, specialized_agents_*, notizen_* including notizen_duplicate_local, documents_*, google_drive_list_files, mandanten_*, cases_*, excel_*, word_*, google_meet, livestream_*, and x_create_post into the correct backend path; direct MCP calls and tool_execute use the same native proxy registry; native provider loops and MCP bridges can discover these concrete tools through tool_catalog_search and execute bridge-discovered tools through tool_execute without exposing the entire concrete catalog as model-facing functions up front; google_drive_list_files uses the dedicated Agent Google OAuth connection directly and never requires browser login; web_search uses the ClapilotAICore Search Providers setting and returns result titles, URLs, snippets, provider, and fallback attempts; context_search is the preferred unified read-only retrieval path across Learning, Wiki, native memory, exact session history/summaries, and the Knowledge Graph, while context_get reads one source-qualified hit; learning_search and learning_get_object are read-only and return only approved, visible, prompt-eligible Learning objects; x_create_post publishes through the current user's connected X/Twitter OAuth account, validates the requested post shape against the exact required X scopes before calling the API, and returns tweet_id plus url only after the X API confirms creation; clapilot_context_status returns the active ClapilotAICore mediaDefaults plus mediaDefaultsGuidance so agents treat configured TTS as a Clapilot runtime route rather than inspecting raw provider secret fields; google_meet supports setup_status, join, status, speak, start_transcription, transcript, audio_transcript, start_voice, voice_status, stop_voice, stop_transcription, create_summary_document, and leave for managed browser-participant Google Meet sessions, with join automatically requesting captions and starting the server-side Realtime voice-to-voice bridge when admitted and enabled in Live Voice settings, audio_transcript recording/transcribing short incoming-audio fallback blocks, speak using configured chat TTS to play one-shot audio into the Meet microphone stream, and transcription before writing summary documents into Clapilot; livestream_* covers Live Stream Studio status, YouTube chat audience requests, clip briefs, local HTML/SVG+Gemini-TTS video rendering, rendered asset registration, approval, queueing, RTMPS start/stop desired-state changes, agent top-up configuration, and media-generation provider job submission; specialized_agents_list, specialized_agents_get, specialized_agents_create, and specialized_agents_update expose admin-only, main-agent-only specialist catalog management without accepting channel-token secrets; scheduled_tasks_create and scheduled_tasks_update now accept optional specialized_agent_id in addition to plain model pinning, optional notify_result_mode for result delivery, and trigger_kind="webhook" for token-backed inbound triggers; specialist-bound automations inherit the selected agent's default model instead of exposing a second execution-model override
    • returns top-level ok in sync with the nested JSON tool output, so ClapilotAICore treats normalized HTTP-200 tool failures as failures; handled validation failures retain code="tool_error" unless the tool supplies a more specific code; successful aufgaben_* mutations on shared boards and shared-board creation fan out one audience-scoped UI mutation row visible to every user's live stream for both user-bound and privileged userless sessions, while private-board mutations remain scoped to the owning user; shared-to-private moves use an owner-excluded audience removal plus the full update on the owner's stream; Team Chat tasks retain source_type="team_chat" and an origin-room source ID decoded by web and Apple clients
    • documents_get now enriches reads with Word Editor text for supported writing files when available and otherwise falls back to document-analysis preview text instead of metadata-only responses
    • fail-closed: requires a valid global internal secret or active run-scoped capability in x-clapilot-agent-secret; requests without either credential are rejected even when no global secret is configured
  • /api/livestream/studio
    • GET: admin-only Live Stream Studio summary with the default YouTube channel config, redacted stream-key state, queued clips, assets, scanned video files from the shared livestream media directory, recent FFmpeg runs, recent events, YouTube chat messages, detected audience requests, queue buffer seconds, approved buffer seconds, and current asset.
    • POST: admin-only action endpoint. Supports update_channel, set_agent_direction, run_agent_topup, create_asset, update_asset, enqueue_asset, enqueue_media_file, delete_media_file, reorder_queue, update_queue_item, set_stream_state, update_youtube_chat, and poll_youtube_chat. Stream keys are encrypted before storage; update_channel also persists agent_topup_* controls; update_youtube_chat links the current admin's Google OAuth connection for YouTube chat ingest; set_agent_direction stores the natural-language stream briefing and can immediately wake the livestream agent; file queueing/deletion is restricted to videos inside the shared livestream media directory; delete_media_file blocks active pending/playing queue references and archives linked assets after removing the file; asset queueing requires an approved asset with a rendered media_path; update_queue_item can set loop_count so a queue row stays in the loop and appears that many times per playlist cycle.
  • /api/livestream/availability
    • GET: authenticated lightweight reachability endpoint for the sidebar. Returns the livestream streamer status, last heartbeat, heartbeat age, stale threshold, reason, and available=true only when clapilot-streamer has written a fresh non-offline heartbeat.
  • /api/livestream/media
    • GET: admin-only inline video preview endpoint for files inside the shared livestream media directory. Accepts path, validates that it resolves inside the media directory, and supports byte ranges so the Studio's video-library tiles can use native browser playback controls without exposing arbitrary filesystem reads.
  • /api/clapilotaicore/live-voice-settings
    • GET: authenticated Realtime and Live Voice settings payload for the ClapilotAICore Audio settings page, including the general Realtime provider/model routing dropdown options and provider readiness (enabled, API-key presence, and Live Transcribe readiness), api_live_transcribe_enabled, api_live_transcribe_model, compatible live-transcription model options, Google Meet enablement, optional Meet-specific Realtime model override, Meet-specific voice, and default Meet participant display name
    • POST: admin-only update endpoint for those Live Voice settings; API Live Transcribe can only be enabled when the selected Realtime provider is enabled, is an OpenAI API-key provider with a stored key, and the model is compatible with live browser transcription. Text-model preflight health does not gate audio-only use. Invalid configuration returns a localized 400 response instead of persisting an unusable gate. The general Realtime provider/model pair is persisted in app_settings.native_model_routing; API transcription values use app_settings.api_live_transcribe_enabled and api_live_transcribe_model; Google Meet values use app_settings.google_meet_live_voice_enabled, google_meet_live_model, google_meet_live_voice, and google_meet_agent_display_name
  • /api/chat/transcription/realtime/session
    • POST: authenticated two-minute OpenAI Realtime transcription client-secret creation. Disabled, missing-key, wrong-auth-mode, or administrator-disabled provider configuration returns localized 409 realtime_transcription_configuration_error; upstream OpenAI failures retain the upstream status, while unexpected session-creation failures return 502.
  • /api/clapilotaicore/dgx-telemetry-settings
    • GET: admin-only load of the DGX telemetry endpoint settings for Settings -> ClapilotAICore -> DGX Cluster; returns the raw saved URL, effective URL, default URL, and edit capability
    • POST: admin-only update of app_settings.dgx_telemetry_api_base_url; accepts { api_base_url }, allows an empty value for the default endpoint, and validates HTTP/HTTPS URLs
  • /api/clapilotaicore/dgx-telemetry/stats
    • GET: admin-only proxy to the configured upstream /api/stats; returns the JSON snapshot as-is, including schema v2 { schema_version, has_logo, clusters: [{ cluster_id, cluster_name, up, nodes, static, hist, ...metrics }] }, and maps unreachable upstreams to 502 { error: "upstream_unreachable" }; the web consumer also accepts the legacy schema v1 flat snapshot
  • /api/clapilotaicore/dgx-telemetry/health
    • GET: admin-only proxy to the saved upstream /api/health; passes through schema v2 { ok, schema_version, clusters: [{ cluster_id, up, model, endpoint }] } with status 200 when any cluster is serving or 503 when none are serving
    • POST: admin-only test-connection proxy for an unsaved { api_base_url } override; passes through the same upstream JSON and 200/503 status
  • /api/clapilotaicore/dgx-telemetry/schema
    • GET: admin-only proxy to the configured upstream /api/schema; maps unreachable upstreams to 502 { error: "upstream_unreachable" }
  • /api/clapilotaicore/dgx-telemetry/stream
    • GET: admin-only SSE pass-through proxy to the configured upstream /api/stream; returns text/event-stream and lets the browser reconnect manually when the upstream is unavailable

Automation webhook durability

GET|POST|PUT|PATCH /api/automation-webhooks/{token} returns run_id only after the corresponding agent_runs reservation and webhook payload queue row commit atomically. Repeated provider delivery IDs return the original run. Runtime termination leaves auditable terminal fields and the queue retries the payload; result delivery is idempotent by run_id.

Video Studio voiceover

GET /api/video-studio/voiceover?slug=<project> returns the persisted voice, provider, scene timings, editable texts, and generated segment metadata for an admin-owned Video Studio project. POST /api/video-studio/voiceover accepts action=generate-all, generate-segment, or remix, together with slug, provider (openai, gemini, or openai_compatible), voice, optional owned-character UUID voice_character_id, and the scene segments. When voice_character_id is set, the character must have an uploaded sample and the provider must be openai_compatible; the sample is supplied to every synthesized segment as the voice-clone reference. Generation resolves credentials through Clapilot's configured server-side TTS runtime. The remix action performs audio-only FFmpeg muxing; refreshBase=true snapshots a newly visual-rendered MP4 first.

Model training and evaluation

All routes below require an administrator session and run in the Node.js runtime. Upstream network/timeouts return 502 { "error": "upstream_unreachable", "detail": "..." }. Other upstream failures return upstream_error; a one-run-at-a-time conflict from job creation returns 409 { "error": "training_already_running", "detail": "..." }.

MethodRouteRequestResponse
GET/api/clapilotaicore/model-training/settings-{api_base_url,effective_api_base_url,default_api_base_url,can_edit}
PUT/api/clapilotaicore/model-training/settings{api_base_url}; empty means the defaultSame settings payload
GET/api/clapilotaicore/model-training/stats-{up,ts,node:{ram_total_gb,ram_avail_gb,ram_used_gb,gpu_util,gpu_temp,gpu_watts},docker,active,total_runs,completed,failed,current,last}
GET/api/clapilotaicore/model-training/runs-TrainingRun[], including history: [[step,loss]], progress, ETA, state, paths, and hyperparameters
GET/api/clapilotaicore/model-training/jobs-FineTuneJob[] (the upstream list wrapper is normalized away)
POST/api/clapilotaicore/model-training/jobs{training_file,suffix?,hyperparameters:{targets?,rank?,alpha?,learning_rate?,seq_len?,batch_size?,grad_accum?,max_steps?,epochs?}}FineTuneJob
GET/api/clapilotaicore/model-training/jobs/:jobId-FineTuneJob
GET/api/clapilotaicore/model-training/jobs/:jobId/events-FineTuneJobEvent[]
POST/api/clapilotaicore/model-training/jobs/:jobId/cancel-Cancelled/cancelling FineTuneJob
POST/api/clapilotaicore/model-training/datasets/uploadMultipart file{id,lines}
POST/api/clapilotaicore/model-training/datasets/from-chat-export`{instances?:string[],sources?:('personal_chat''group_chat'
GET/api/clapilotaicore/model-training/compare/models-{eval:{up,running,models,base,adapters},gateway}
POST/api/clapilotaicore/model-training/compare/start-Upstream start result
POST/api/clapilotaicore/model-training/compare/stop-Upstream stop result
POST/api/clapilotaicore/model-training/compare/load{run_id}{loaded}
POST/api/clapilotaicore/model-training/compare/chat{model,messages,max_tokens?,temperature?,seed?}{model,content,seconds,tok_per_s} plus upstream metadata
POST/api/clapilotaicore/model-training/compare/verify{run_id?:string,model?:string,prompt?,max_tokens?}{base:{content,seconds,tok_per_s},adapter:{...},identical}
GET/api/clapilotaicore/model-training/streamSSEEvery three seconds: data: {stats,runs}; upstream failures use an SSE error event

The verify route resolves ft:<run_id> and the current base model from the models endpoint. Both requests use the same prompt, temperature 0, and fixed seed. identical: true is an inert-adapter warning, not success.