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
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 testDefine 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.
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 })),
]);
},
},
});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.
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);
});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.
// 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.
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.
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.
// 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.
| Tier | You write | Coline provides | Runs on |
|---|---|---|---|
| tree | Handlers returning ui.* trees | Native rendering, theming, actions | Web, desktop, mobile, Kairo chat |
| react | React components (main.tsx) | Sandboxed runtime, @colineapp/ui, build pipeline | Web and desktop |
| web | Pages on your own origin | Signed embeds | Web 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.
- Platform fee. Coline keeps 15% of each transaction; the rest is paid out to your connected account on Stripe's standard payout schedule.
- Refunds are handled by Coline. Workspaces can request a refund of their latest subscription charge within 7 days, granted at Coline's discretion. Refunds are deducted from your payout balance. Apps must not implement their own refund, billing, or payment flows for Coline purchases.
- Cancellation. Subscriptions cancel at period end and access runs to period close. Uninstalling does not cancel a subscription — billing stays with the workspace until they cancel it.
API Reference
All endpoints available to your app via the API client or direct HTTP.
Members1→
List workspace members
/api/v1/workspaces/{workspaceSlug}/members
Notes5→
List workspace notes
/api/v1/workspaces/{workspaceSlug}/notes
Create note
/api/v1/workspaces/{workspaceSlug}/notes
Get note
/api/v1/workspaces/{workspaceSlug}/notes/{noteId}
Update note
/api/v1/workspaces/{workspaceSlug}/notes/{noteId}
Delete note
/api/v1/workspaces/{workspaceSlug}/notes/{noteId}
Docs5→
List workspace docs
/api/v1/workspaces/{workspaceSlug}/docs
Create doc
/api/v1/workspaces/{workspaceSlug}/docs
Get doc
/api/v1/workspaces/{workspaceSlug}/docs/{docId}
Update doc
/api/v1/workspaces/{workspaceSlug}/docs/{docId}
Delete doc
/api/v1/workspaces/{workspaceSlug}/docs/{docId}
Drives & Files5→
List workspace drives
/api/v1/workspaces/{workspaceSlug}/drives
List drive files
/api/v1/workspaces/{workspaceSlug}/drives/{driveId}/files
Get file
/api/v1/workspaces/{workspaceSlug}/files/{fileId}
Update file
/api/v1/workspaces/{workspaceSlug}/files/{fileId}
Delete file
/api/v1/workspaces/{workspaceSlug}/files/{fileId}
Channels3→
List channels
/api/v1/workspaces/{workspaceSlug}/channels
List channel messages
/api/v1/workspaces/{workspaceSlug}/channels/{channelId}/messages
Send channel message
/api/v1/workspaces/{workspaceSlug}/channels/{channelId}/messages
Direct Messages3→
List direct messages
/api/v1/workspaces/{workspaceSlug}/dms
List DM messages
/api/v1/workspaces/{workspaceSlug}/dms/{dmId}/messages
Send DM message
/api/v1/workspaces/{workspaceSlug}/dms/{dmId}/messages
Messages9→
Get message
/api/v1/workspaces/{workspaceSlug}/messages/{messageId}
Edit message
/api/v1/workspaces/{workspaceSlug}/messages/{messageId}
Delete message
/api/v1/workspaces/{workspaceSlug}/messages/{messageId}
Get thread
/api/v1/workspaces/{workspaceSlug}/messages/{messageId}/thread
Reply to thread
/api/v1/workspaces/{workspaceSlug}/messages/{messageId}/thread
Add reaction
/api/v1/workspaces/{workspaceSlug}/messages/{messageId}/reactions/{emoji}
Remove reaction
/api/v1/workspaces/{workspaceSlug}/messages/{messageId}/reactions/{emoji}
Pin message
/api/v1/workspaces/{workspaceSlug}/messages/{messageId}/pin
Unpin message
/api/v1/workspaces/{workspaceSlug}/messages/{messageId}/pin
Calendar24→
List calendars
/api/v1/workspaces/{workspaceSlug}/calendar/calendars
Create calendar
/api/v1/workspaces/{workspaceSlug}/calendar/calendars
Update calendar and preferences
/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}
Archive calendar
/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}
List calendar changes
/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}/changes
Restore calendar
/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}/restore
List calendar grants
/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}/grants
Create or update calendar grant
/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}/grants
Delete calendar grant
/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}/grants
Import iCalendar archive
/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}/import
Export iCalendar archive
/api/v1/workspaces/{workspaceSlug}/calendar/calendars/{calendarId}/export
List combined schedule
/api/v1/workspaces/{workspaceSlug}/calendar/my-schedule
Search calendars
/api/v1/workspaces/{workspaceSlug}/calendar/search
List calendar events
/api/v1/workspaces/{workspaceSlug}/calendar/events
Create calendar event
/api/v1/workspaces/{workspaceSlug}/calendar/events
Get calendar event
/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}
Update calendar event
/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}
Delete calendar event
/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}
List event reminders
/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}/reminders
Replace event reminders
/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}/reminders
Respond to invitation
/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}/response
List proposed times
/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}/counter-proposals
Propose a new time
/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}/counter-proposals
Respond to a proposed time
/api/v1/workspaces/{workspaceSlug}/calendar/events/{eventId}/counter-proposals
Search1→
Search workspace
/api/v1/workspaces/{workspaceSlug}/search
Taskboards9→
List taskboards
/api/v1/workspaces/{workspaceSlug}/taskboards
List taskboard tasks
/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks
Create task
/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks
Get task
/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks/{taskId}
Update task
/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks/{taskId}
Delete task
/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks/{taskId}
Batch create tasks
/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks/batch-create
Batch update tasks
/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks/batch-update
Batch delete tasks
/api/v1/workspaces/{workspaceSlug}/taskboards/{taskboardId}/tasks/batch-delete
Store3→
List store apps
/api/v1/apps
Get store app
/api/v1/apps/{appKey}
List approved app versions
/api/v1/apps/{appKey}/versions
Publisher3→
Register app
/api/v1/apps
Create app version
/api/v1/apps/{appKey}/versions
Submit app version for review
/api/v1/apps/{appKey}/versions/{versionId}/submit
Auth3→
Authorize OAuth client
/api/v1/oauth/authorize
Exchange OAuth authorization code
/api/v1/oauth/token
Resolve OAuth userinfo
/api/v1/oauth/userinfo
AI2→
List Tab models
/api/v1/tab/models
Create OpenAI-compatible Tab chat completion
/api/v1/tab/chat/completions
Workspace installs15→
List workspace apps and catalog
/api/v1/workspaces/{workspaceSlug}/apps
Install workspace app
/api/v1/workspaces/{workspaceSlug}/apps
Get installed app
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}
Uninstall workspace app
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}
Get granted app permissions
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/permissions
Update granted app permissions
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/permissions
List app secrets
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/secrets
Create or rotate app secret
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/secrets
Delete app secret
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/secrets/{secretId}
List app OAuth connections
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/oauth-connections
Create or update app OAuth connection
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/oauth-connections
Delete app OAuth connection
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/oauth-connections/{connectionId}
List app deliveries
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/deliveries
Send test app delivery
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/deliveries
Replay app delivery
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/deliveries/{deliveryId}/replay
Runtime10→
List app-backed files
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/files
Create an app-backed file
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/files
Execute a hosted app action
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/actions
Read app-backed file document
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/files/{fileId}
Update app-backed file document
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/files/{fileId}
Delete app-backed file
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/files/{fileId}
Create app notification
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/notifications
Upsert app index documents
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/index-documents
Delete app index documents
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/index-documents
Emit app ambient events
/api/v1/workspaces/{workspaceSlug}/apps/{appKey}/ambient/events
Identity1→
List my workspaces
/api/v1/me/workspaces
Sheets9→
List workspace sheets
/api/v1/workspaces/{workspaceSlug}/sheets
Create sheet
/api/v1/workspaces/{workspaceSlug}/sheets
Get sheet
/api/v1/workspaces/{workspaceSlug}/sheets/{sheetId}
Update sheet
/api/v1/workspaces/{workspaceSlug}/sheets/{sheetId}
Delete sheet
/api/v1/workspaces/{workspaceSlug}/sheets/{sheetId}
Create sheet view
/api/v1/workspaces/{workspaceSlug}/sheets/{sheetId}/views
Update sheet view
/api/v1/workspaces/{workspaceSlug}/sheets/{sheetId}/views/{viewId}
Delete sheet view
/api/v1/workspaces/{workspaceSlug}/sheets/{sheetId}/views/{viewId}
Import CSV into sheet
/api/v1/workspaces/{workspaceSlug}/sheets/{sheetId}/import
Uploads4→
Initiate multipart upload
/api/v1/workspaces/{workspaceSlug}/uploads/initiate
Get upload part URL
/api/v1/workspaces/{workspaceSlug}/uploads/part
Upload part
/api/v1/workspaces/{workspaceSlug}/uploads/part
Complete multipart upload
/api/v1/workspaces/{workspaceSlug}/uploads/complete