Calling Native APIs

Calling OpenClacky's Native APIs

Many features users ask for are already natively supported by the OpenClacky main service — sessions, trash, skills, memories, cron tasks, billing, media generation… each has a ready-made HTTP endpoint. An extension panel doesn't need to rebuild these capabilities — it can call the main service's endpoints directly.

A typical case: an extension with a delete feature that wants deleted files to be recoverable from file recovery. There's no need to implement a separate trash system — hand the file to OpenClacky's trash on delete, and call POST /api/trash/restore to restore it, and the file appears in the official file-recovery UI. When a need arises, check here first for a ready-made endpoint.


How to call: the panel shares the host's origin

A Web UI panel (view.js) and the official OpenClacky interface run on the same page, the same origin. So a panel can fetch("/api/...") the main service directly, and the browser carries the access key automatically — no extra handling needed:

// Inside a panel's view.js, fetch a main-service endpoint directly
const res  = await fetch("/api/skills");
const data = await res.json();
console.log("installed skills:", data.skills);

This section covers the frontend panel calling main-service endpoints. An extension's backend handler.rb should not fetch these endpoints — to drive sessions from the backend, use the white-listed methods (create_session / submit_task / dispatch_to_session, see HTTP API Extensions). The endpoints listed here are for the panel frontend.

All endpoints live under /api/ and return JSON. Below are the endpoints extensions may safely call, grouped by capability.


Sessions

Method Path Purpose
GET /api/sessions List all sessions
GET /api/sessions/:id A single session's detail
GET /api/sessions/:id/messages The session's message history
GET /api/sessions/:id/files Files in the session's working directory
GET /api/sessions/:id/git/:action The session's git status/diff, etc.
GET /api/sessions/:id/time_machine The session's time-machine snapshots
POST /api/sessions Create a session
PATCH /api/sessions/:id/model Switch the session's model
PATCH /api/sessions/:id/working_dir Change the session's working directory

Create a session POST /api/sessions:

const res = await fetch("/api/sessions", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    name: "My new session",       // required
    agent_profile: "general",     // optional, defaults to general
    working_dir: "/path/to/dir",  // optional, defaults to current working dir
    model_id: "…"                 // optional, a model id from GET /api/config
  }),
});
const { session } = await res.json();   // 201 → { session: {...} }

PATCH /api/sessions/:id/model and /working_dir mutate a running session — these are operations with side effects. Call them only when the user explicitly asks; they should not silently alter a session.


Trash (file recovery)

Deleting files through OpenClacky's trash makes them restorable from the official file recovery UI — exactly the path for "an extension that deletes, with restore support."

Method Path Purpose
GET /api/trash List files in the trash (add ?project=<path> to filter a project)
POST /api/trash/restore Restore a single file to its original location
GET /api/trash/sessions List sessions in the trash
POST /api/trash/sessions/restore Restore a deleted session

Restore a file POST /api/trash/restore:

await fetch("/api/trash/restore", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    project_root:  "/path/to/project",           // project root the file belongs to
    original_path: "/path/to/project/notes.md",  // the file's original path
  }),
});
// 200 → { ok: true, restored_file, message }
// If a file already exists at the original location, returns 422 — no overwrite

GET /api/trash returns:

{
  "ok": true,
  "files": [
    { "original_path": "…", "file_size": 1234, "deleted_at": "…",
      "project_root": "…", "project_name": "…" }
  ],
  "projects": [ { "project_root": "…", "file_count": 3, "total_size": 4567 } ],
  "total_count": 3
}

Skills, Agents, Channels, MCP

Method Path Purpose
GET /api/skills Installed skills
GET /api/agents Available agents
GET /api/sessions/:id/skills Skills available in a session
GET /api/agents/:id/skills Skills bound to an agent
GET /api/providers Available models / providers
GET /api/channels Configured IM channels
GET /api/mcp Configured MCP servers

All read-only — suited for "what capabilities are installed" overview panels.


Memories

Method Path Purpose
GET /api/memories List long-term memories
POST /api/memories Write a memory

Write a memory POST /api/memories:

await fetch("/api/memories", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    filename: "my-note.md",   // must end in .md, no path separators
    content:  "# Title\nBody…",
  }),
});
// 201 → { ok: true, memory: {...} }
// Already exists → 409

Cron tasks

Method Path Purpose
GET /api/cron-tasks List cron tasks
POST /api/cron-tasks Create a cron task

Create a cron task POST /api/cron-tasks:

await fetch("/api/cron-tasks", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    name:    "Daily briefing",       // required
    content: "Summarize today's news…", // required, the task prompt
    cron:    "0 9 * * *",            // required, standard 5-field cron
    enabled: true,                   // optional, defaults to true
  }),
});
// 201 → { ok: true, name }

Billing / usage

Method Path Purpose
GET /api/billing/summary Usage summary
GET /api/billing/daily Usage by day
GET /api/billing/records Detailed records
GET /api/billing/sessions Usage by session

Suited for "how much did I spend this month" usage panels — all read-only.


Files

Method Path Purpose
POST /api/upload Upload a file (multipart, field name file)
POST /api/file-action Open / download a file on the local machine

Open a file POST /api/file-action:

await fetch("/api/file-action", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    path:   "/path/to/file.pdf",
    action: "open",   // "open" default app | "reveal" show in file manager | "download"
  }),
});

Media generation (billed)

Method Path Purpose
POST /api/media/image Generate an image
POST /api/media/video Generate a video
POST /api/media/audio/speech Text-to-speech

These endpoints incur real charges. They should be called only on an explicit user action (e.g. clicking a "Generate" button) — not automatically inside a panel's render, and not in a loop, or they will keep burning the user's quota.


A full example: a delete panel with trash restore

// panels/trash-tool/view.js
Clacky.ext.ui.mount("session.aside", (container, ctx) => {
  const btn = document.createElement("button");
  btn.className = "btn-primary";      // reuse the official theme
  btn.textContent = "Restore last deleted file";
  btn.addEventListener("click", async () => {
    // 1. Look up the most recently trashed file
    const trash = await (await fetch("/api/trash")).json();
    const latest = trash.files[0];
    if (!latest) return Clacky.Notify.info("Trash is empty");

    // 2. Restore it
    const res = await fetch("/api/trash/restore", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        project_root:  latest.project_root,
        original_path: latest.original_path,
      }),
    });
    const out = await res.json();
    Clacky.Notify.info(out.ok ? "Restored" : out.error);
  });
  container.appendChild(btn);
}, { tab: { id: "trash-tool", label: () => "Trash" } });

When deleting a file, have the extension's delete logic move the file into OpenClacky's trash (instead of a raw rm), and it can be restored from the official file-recovery UI or from the panel — no wheel-reinventing.


Boundaries: what's not here

This page lists endpoints extensions may safely call. Destructive, system-level, or authorization endpoints (permanent deletion, changing global config, restarting the server, brand licensing, installing/disabling extensions, etc.) are not open to extensions — they belong to the main framework's own control plane. If a need has no matching endpoint here, it likely falls outside an extension's scope — rethink the approach, or implement it in your own backend via HTTP API Extensions.