Build apps on Coline

Define tools, file types, and surfaces in TypeScript — Coline hosts your logic, renders your UI on every platform, and hands your tools to Kairo. No servers unless you want them.

Getting Started

1

Scaffold an app

One command creates a working app: a Kairo tool, a file type with previews, a home surface, and tests.

npx create-coline-app my-app
cd my-app && npm install && npm test
2

Define your app

defineApp is the whole contract: permissions, surfaces, tools, file types, and handlers in one place. Tools declare an honest effect — the runtime enforces it as a hard ceiling on what their code can do.

app.config.ts
import { z } from "zod/v4";
import { defineApp, defineFileType, defineTool, ui } from "@colineapp/sdk/v2";

const todoFileType = defineFileType({
  metadata: {
    typeKey: "todos.todo",
    name: "Todo",
    indexable: true,
    surfaces: {
      preview: { tier: "tree" },
      inline: { tier: "tree" },
    },
  },
  runtime: {
    renderPreview: (ctx) =>
      ui.stack([
        ui.heading(ctx.file.name),
        ui.badge(String(ctx.document.status ?? "open")),
      ]),
    renderInline: (ctx) => ui.text(ctx.file.name),
    index: {
      title: (doc) => String(doc.title ?? "Todo"),
      body: (doc) => String(doc.notes ?? ""),
    },
  },
});

const createTodo = defineTool({
  name: "todos.create_todo",
  description: "Create a todo with a title.",
  input: z.object({ title: z.string().min(1) }),
  effect: "write", // enforced as a runtime capability ceiling
  execute: async (input, ctx) => {
    const file = await ctx.coline.files.create({
      typeKey: "todos.todo",
      name: input.title,
      document: { title: input.title, status: "open" },
    });
    return {
      output: { fileId: file.fileId },
      card: ui.reference({ kind: "file", id: file.fileId }),
    };
  },
});

export default defineApp({
  key: "todos",
  name: "Todos",
  permissions: ["files.read", "files.write", "ai.tools", "search.index"],
  hosting: { default: "coline" }, // runs on Coline — no servers
  surfaces: { home: { tier: "tree" } },
  files: [todoFileType],
  tools: [createTodo],
  handlers: {
    renderHome: async (ctx) => {
      const { files } = await ctx.coline.files.list({ typeKey: "todos.todo" });
      return ui.stack([
        ui.heading("Todos"),
        ...files.map((file) => ui.fileCard({ title: file.name, fileId: file.fileId })),
      ]);
    },
  },
});
3

Test it

createTestWorkspace runs your app against an in-memory workspace with the same coline.* API — invoke tools, render surfaces, fire schedules, assert on state.

app.test.ts
import { describe, expect, it } from "vitest";
import { createTestWorkspace } from "@colineapp/sdk/testing";
import app from "./app.config";

it("creates a todo through the Kairo tool", async () => {
  const ws = createTestWorkspace(app);
  const result = await ws.invokeTool("todos.create_todo", { title: "Ship it" });
  expect(result.card).not.toBeNull();
  expect(ws.files.byType("todos.todo")).toHaveLength(1);
});
4

Push to Coline

The CLI builds your logic bundle, ships client source when you use React surfaces, and uploads the version. Coline builds, reviews, and runs everything.

# Create a workspace API key with the apps.write scope
# in Workspace Settings → API, then:
export COLINE_API_KEY=col_ws_...

npx coline-app push     # build + upload a version
npx coline-app dev      # watch + re-push on every change

# Versions land in your developer console in draft state.
# Submit for review to list on the store, or install
# privately into your own workspace right away.

The capability API

One coline.* API everywhere your code runs. Every call is scoped to what the installing workspace granted, what the acting user can access, and what your tool's declared effect allows.

anywhere in your app
// The same coline.* API everywhere your code runs —
// tools, render handlers, schedules, hosted or external.

// Files (scoped to what the user AND the app can touch)
const file = await ctx.coline.files.create({
  typeKey: "todos.todo",
  name: "Buy groceries",
  document: { status: "open" },
});
await ctx.coline.files.update({ fileId: file.fileId, document: { status: "done" } });

// App storage — key-value + records with workspace identity
await ctx.coline.storage.kv.set("last-sync", new Date().toISOString());
const contacts = ctx.coline.storage.collection("contacts");
const record = await contacts.insert({ name: "Sarah", tier: "vip" });
const { records } = await contacts.query({
  where: { tier: "vip" },
  orderBy: { field: "name" },
  limit: 50,
});

// Workspace
const { members } = await ctx.coline.members.list();
await ctx.coline.notifications.send({
  title: "Todo completed",
  body: "Buy groceries is done!",
  recipients: [{ userId: members[0].userId }],
});

// External APIs — through Coline, credentials injected server-side
const response = await ctx.coline.net.fetch("https://api.stripe.com/v1/charges", {
  headers: { authorization: "Bearer {{secret:STRIPE_KEY}}" },
});

Scheduled triggers

Declare cron schedules in the manifest and Coline runs your handler on time — no job queues, no workers to deploy. Pair with net.fetch to sync external services on a cadence.

app.config.ts
export default defineApp({
  // ...
  permissions: ["storage.app", "network.external"],
  network: { allowedHosts: ["api.example.com"] },
  triggers: {
    schedules: [{ id: "daily-sync", cron: "0 9 * * 1-5" }],
  },
  handlers: {
    onSchedule: async (ctx) => {
      // Runs as the app principal on Coline's scheduler —
      // your app syncs even when nobody has it open.
      const response = await ctx.coline.net.fetch("https://api.example.com/sync");
      await ctx.coline.storage.kv.set("last-sync", ctx.firedAt);
    },
  },
});

UI Components

Tree surfaces build native Coline UI from your handlers — rendered by Coline on web, desktop, mobile, and in Kairo chat. React surfaces get the full component library in a sandbox.

ui.ts
import { ui, actions } from "@colineapp/sdk/v2";

// Text & typography
ui.heading("My App");
ui.text("Hello world");
ui.text("Something went wrong", { tone: "danger" });
ui.badge("active", { tone: "positive" });
ui.link("View docs", "https://docs.example.com", { external: true });

// Layout
ui.stack([...], { gap: "md" });
ui.row([...], { gap: "sm" });   // horizontal stack shorthand
ui.divider();

// Interactive
ui.button("Save", { action: actions.custom("my-app.save") });
ui.button("Open", { action: actions.openFile("file_123") });
ui.button("Create", { action: actions.createFile({ name: "New", typeKey: "todos.todo" }) });

// Media & code
ui.image({ src: "https://...", alt: "Screenshot", width: 800, height: 400 });
ui.codeBlock("const x = 42;", { language: "typescript" });

// Form inputs (submitted via action handlers)
ui.input({ name: "title", label: "Title", placeholder: "Enter a title…" });
ui.select({
  name: "priority",
  label: "Priority",
  options: [
    { value: "low", label: "Low" },
    { value: "high", label: "High" },
  ],
});

// Data display
ui.table({
  columns: [
    { key: "name", label: "Name" },
    { key: "status", label: "Status" },
  ],
  rows: [{ name: "Sprint planning", status: "Done" }],
});

// Workspace references — resolve to live previews everywhere
ui.reference({ kind: "file", id: "file_123" });
ui.fileCard({ title: "Q4 Report", subtitle: "Updated yesterday", fileId: "file_123" });
ui.userChip({ label: "Alice", userId: "user_123" });
ui.emptyState({ title: "No items yet", description: "Create your first item." });

// Tree surfaces render on web, desktop, mobile, and inside
// Kairo chat — one definition, every platform. Need richer UI?
// Set a surface to { tier: "react", entry: "main.tsx" } and ship
// React components into Coline's sandboxed runtime.

External hosting

Per-capability, not all-or-nothing: keep most of the app on Coline and route specific tools to your own backend. Coline signs every delivery; one endpoint serves them all.

server.ts
// Most apps never need this — hosting: { default: "coline" }
// runs your logic on Coline. Reach for external hosting when a
// capability needs YOUR infrastructure (private databases,
// long-running jobs, existing services).

import { createColineAppHandler } from "@colineapp/sdk/v2";
import app from "./app.config";

const handler = createColineAppHandler({
  app,
  deliverySecret: process.env.COLINE_DELIVERY_SECRET!,
});

// One endpoint serves everything — tools, renders, schedules.
// Next.js: app/coline/app/route.ts
export const POST = handler;

// Manifest change:
//   hosting: {
//     default: "coline",
//     overrides: { "todos.sync_crm": "external" },
//     external: { baseUrl: "https://your-app.example.com" },
//   }

How your app runs

Three UI tiers, one capability contract. Start with tree surfaces — they render everywhere automatically — and reach for React or external hosting only where you need them.

TierYou writeColine providesRuns on
treeHandlers returning ui.* treesNative rendering, theming, actionsWeb, desktop, mobile, Kairo chat
reactReact components (main.tsx)Sandboxed runtime, @colineapp/ui, build pipelineWeb and desktop
webPages on your own originSigned embedsWeb and desktop

Payments & refunds

Paid apps are subscriptions billed through Coline. You connect a payout account, set your prices, and Coline handles checkout, invoices, and entitlements — workspaces get one billing surface, you get a payout.

API Reference

All endpoints available to your app via the API client or direct HTTP.

Members1
GET

List workspace members

/api/v1/workspaces/{workspaceSlug}/members

Notes5
GET

List workspace notes

/api/v1/workspaces/{workspaceSlug}/notes

POST

Create note

/api/v1/workspaces/{workspaceSlug}/notes

GET

Get note

/api/v1/workspaces/{workspaceSlug}/notes/{noteId}

PUT

Update note

/api/v1/workspaces/{workspaceSlug}/notes/{noteId}

DELETE

Delete note

/api/v1/workspaces/{workspaceSlug}/notes/{noteId}

Docs5
GET

List workspace docs

/api/v1/workspaces/{workspaceSlug}/docs

POST

Create doc

/api/v1/workspaces/{workspaceSlug}/docs

GET

Get doc

/api/v1/workspaces/{workspaceSlug}/docs/{docId}

PATCH

Update doc

/api/v1/workspaces/{workspaceSlug}/docs/{docId}

DELETE

Delete doc

/api/v1/workspaces/{workspaceSlug}/docs/{docId}

Drives & Files5
GET

List workspace drives

/api/v1/workspaces/{workspaceSlug}/drives

GET

List drive files

/api/v1/workspaces/{workspaceSlug}/drives/{driveId}/files

GET

Get file

/api/v1/workspaces/{workspaceSlug}/files/{fileId}

PATCH

Update file

/api/v1/workspaces/{workspaceSlug}/files/{fileId}

DELETE

Delete file

/api/v1/workspaces/{workspaceSlug}/files/{fileId}

Channels3
GET

List channels

/api/v1/workspaces/{workspaceSlug}/channels

GET

List channel messages

/api/v1/workspaces/{workspaceSlug}/channels/{channelId}/messages

POST

Send channel message

/api/v1/workspaces/{workspaceSlug}/channels/{channelId}/messages

Direct Messages3
GET

List direct messages

/api/v1/workspaces/{workspaceSlug}/dms

GET

List DM messages

/api/v1/workspaces/{workspaceSlug}/dms/{dmId}/messages

POST

Send DM message

/api/v1/workspaces/{workspaceSlug}/dms/{dmId}/messages

Messages9
GET

Get message

/api/v1/workspaces/{workspaceSlug}/messages/{messageId}

PATCH

Edit message

/api/v1/workspaces/{workspaceSlug}/messages/{messageId}

DELETE

Delete message

/api/v1/workspaces/{workspaceSlug}/messages/{messageId}

GET

Get thread

/api/v1/workspaces/{workspaceSlug}/messages/{messageId}/thread

POST

Reply to thread

/api/v1/workspaces/{workspaceSlug}/messages/{messageId}/thread

PUT

Add reaction

/api/v1/workspaces/{workspaceSlug}/messages/{messageId}/reactions/{emoji}

DELETE

Remove reaction

/api/v1/workspaces/{workspaceSlug}/messages/{messageId}/reactions/{emoji}

PUT

Pin message

/api/v1/workspaces/{workspaceSlug}/messages/{messageId}/pin

DELETE

Unpin message

/api/v1/workspaces/{workspaceSlug}/messages/{messageId}/pin

Calendar24
GET

List calendars

/api/v1/workspaces/{workspaceSlug}/calendar/calendars

POST

Create calendar

/api/v1/workspaces/{workspaceSlug}/calendar/calendars

PATCH

Update calendar and preferences

/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}

DELETE

Archive calendar

/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}

GET

List calendar changes

/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}/changes

POST

Restore calendar

/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}/restore

GET

List calendar grants

/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}/grants

PUT

Create or update calendar grant

/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}/grants

DELETE

Delete calendar grant

/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}/grants

POST

Import iCalendar archive

/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}/import

GET

Export iCalendar archive

/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}/export

GET

List combined schedule

/api/v1/workspaces/{workspaceSlug}/calendar/my-schedule

GET

Search calendars

/api/v1/workspaces/{workspaceSlug}/calendar/search

GET

List calendar events

/api/v1/workspaces/{workspaceSlug}/calendar/events

POST

Create calendar event

/api/v1/workspaces/{workspaceSlug}/calendar/events

GET

Get calendar event

/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}

PATCH

Update calendar event

/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}

DELETE

Delete calendar event

/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}

GET

List event reminders

/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}/reminders

PUT

Replace event reminders

/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}/reminders

PATCH

Respond to invitation

/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}/response

GET

List proposed times

/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}/counter-proposals

POST

Propose a new time

/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}/counter-proposals

PATCH

Respond to a proposed time

/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}/counter-proposals

Search1
POST

Search workspace

/api/v1/workspaces/{workspaceSlug}/search

Taskboards9
GET

List taskboards

/api/v1/workspaces/{workspaceSlug}/taskboards

GET

List taskboard tasks

/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks

POST

Create task

/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks

GET

Get task

/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks/{taskId}

PATCH

Update task

/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks/{taskId}

DELETE

Delete task

/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks/{taskId}

POST

Batch create tasks

/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks/batch-create

POST

Batch update tasks

/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks/batch-update

POST

Batch delete tasks

/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks/batch-delete

Store3
GET

List store apps

/api/v1/apps

GET

Get store app

/api/v1/apps/{appKey}

GET

List approved app versions

/api/v1/apps/{appKey}/versions

Publisher3
POST

Register app

/api/v1/apps

POST

Create app version

/api/v1/apps/{appKey}/versions

POST

Submit app version for review

/api/v1/apps/{appKey}/versions/{versionId}/submit

Auth3
GET

Authorize OAuth client

/api/v1/oauth/authorize

POST

Exchange OAuth authorization code

/api/v1/oauth/token

GET

Resolve OAuth userinfo

/api/v1/oauth/userinfo

AI2
GET

List Tab models

/api/v1/tab/models

POST

Create OpenAI-compatible Tab chat completion

/api/v1/tab/chat/completions

Workspace installs15
GET

List workspace apps and catalog

/api/v1/workspaces/{workspaceSlug}/apps

POST

Install workspace app

/api/v1/workspaces/{workspaceSlug}/apps

GET

Get installed app

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}

DELETE

Uninstall workspace app

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}

GET

Get granted app permissions

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/permissions

PUT

Update granted app permissions

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/permissions

GET

List app secrets

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/secrets

POST

Create or rotate app secret

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/secrets

DELETE

Delete app secret

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/secrets/{secretId}

GET

List app OAuth connections

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/oauth-connections

POST

Create or update app OAuth connection

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/oauth-connections

DELETE

Delete app OAuth connection

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/oauth-connections/{connectionId}

GET

List app deliveries

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/deliveries

POST

Send test app delivery

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/deliveries

POST

Replay app delivery

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/deliveries/{deliveryId}/replay

Runtime10
GET

List app-backed files

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/files

POST

Create an app-backed file

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/files

POST

Execute a hosted app action

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/actions

GET

Read app-backed file document

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/files/{fileId}

PATCH

Update app-backed file document

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/files/{fileId}

DELETE

Delete app-backed file

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/files/{fileId}

POST

Create app notification

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/notifications

POST

Upsert app index documents

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/index-documents

DELETE

Delete app index documents

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/index-documents

POST

Emit app ambient events

/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/ambient/events

Identity1
GET

List my workspaces

/api/v1/me/workspaces

Sheets9
GET

List workspace sheets

/api/v1/workspaces/{workspaceSlug}/sheets

POST

Create sheet

/api/v1/workspaces/{workspaceSlug}/sheets

GET

Get sheet

/api/v1/workspaces/{workspaceSlug}/sheets/{sheetId}

PUT

Update sheet

/api/v1/workspaces/{workspaceSlug}/sheets/{sheetId}

DELETE

Delete sheet

/api/v1/workspaces/{workspaceSlug}/sheets/{sheetId}

POST

Create sheet view

/api/v1/workspaces/{workspaceSlug}/sheets/{sheetId}/views

PATCH

Update sheet view

/api/v1/workspaces/{workspaceSlug}/sheets/{sheetId}/views/{viewId}

DELETE

Delete sheet view

/api/v1/workspaces/{workspaceSlug}/sheets/{sheetId}/views/{viewId}

POST

Import CSV into sheet

/api/v1/workspaces/{workspaceSlug}/sheets/{sheetId}/import

Uploads4
POST

Initiate multipart upload

/api/v1/workspaces/{workspaceSlug}/uploads/initiate

GET

Get upload part URL

/api/v1/workspaces/{workspaceSlug}/uploads/part

POST

Upload part

/api/v1/workspaces/{workspaceSlug}/uploads/part

POST

Complete multipart upload

/api/v1/workspaces/{workspaceSlug}/uploads/complete