App platform
Frames and the SDK
A frame is your own web page shown inside Kweko in a sandboxed iframe. It gets its context, theme and API access from Kweko through the @kweko/sdk browser package.
How frames load
- The frame's address is your app's backend URL plus the contribution's
path, for examplehttps://app.example.com+/kweko/lead-tab. Kweko adds no query parameters: everything about the workspace, member and record arrives through the SDK afterinit(). - The iframe is sandboxed with
allow-scripts allow-forms allow-popups, without same-origin access, withreferrerpolicy="no-referrer"and no extra permissions. Your page cannot read Kweko's cookies or storage, and Kweko never gives it a token. - Call
kweko.init()within 3 seconds of loading; otherwise Kweko shows a "slow to load" notice with Retry and Open in new tab. - Frames are served from your origin: your page and your backend handle their own sign-in and data. Never put your client secret in a frame.
Using the SDK
import { kweko } from "@kweko/sdk"
const ctx = await kweko.init({ autoResize: true })
// ctx.workspace { id, name, currency, timezone }, ctx.member { id, name, locale, role },
// ctx.entity { type: "lead", id } on record extension points, ctx.locale, ctx.theme
const { status, data } = await kweko.api.get(`/v1/leads/${ctx.entity!.id}`)
await kweko.api.post("/v1/notes", { entity_type: "lead", entity_id: ctx.entity!.id, text: "Shipped" })
kweko.ui.toast({ text: "Order sent", tone: "success" })
kweko.navigate.openRecord({ type: "lead", id: ctx.entity!.id! })
const off = kweko.events.on("lead.stage_changed", () => reload())
kweko.theme.onChange((t) => console.log(t.mode))
kweko.dock.setBadge(3) // dock.widget frames| Call | What it does |
|---|---|
kweko.init({ applyTheme?, autoResize? }) | Handshake. Returns the context. Applies Kweko's theme tokens unless applyTheme: false. |
kweko.context | The context after init(), else null |
kweko.ui.resize({ height }), kweko.ui.autoResize() | Set the frame's height (80 to 4000 px), or follow the page's height |
kweko.ui.toast({ text, tone? }) | Show a Kweko toast (success, danger or info; text up to 200 characters) |
kweko.navigate.to(path) | Open an in-app path such as /inbox |
kweko.navigate.openRecord({ type, id }) | Open a lead, contact or company |
kweko.api.get / post / patch / delete (path, body?), kweko.api.call(method, path, body?) | Call the Kweko API; resolves to { status, data } |
kweko.events.on(type, handler) | Live events about the current record ("*" for all); returns an unsubscribe function |
kweko.theme.onChange(handler) | Called when the member switches light and dark |
kweko.dock.setBadge(count) | A count on the dock widget (0 to 999) |
Requests to Kweko time out after 15 seconds and reject with a KwekoError that has a code.
API calls from a frame
kweko.api does not send a token from your page. Kweko runs the request for you, as your app's installation, on behalf of the member who is looking at the frame:
- Paths must start with
/v1/. Methods: GET, POST, PATCH, PUT and DELETE. - The request has the scopes the workspace granted to your app, and never more than the member's own role allows.
- It needs the
ui:extendscope and at least one frame contribution (no_framesotherwise). - App platform endpoints (
/v1/apps), API keys (/v1/api-keys) and webhooks (/v1/webhooks) cannot be called this way. - At most 10 calls can be in flight from one installation; they count against your app's own rate limit and daily quota, never the workspace's API keys.
- In the workspace's history the calls appear as "Your app for Member name".
For work that is not tied to a member looking at a screen (syncs, background jobs), call the API from your server with the OAuth access token instead.
Live events
kweko.events.on(type, handler) receives events about the record the frame is showing, for the event types it subscribed to (up to 50 subscriptions). The handler gets { type, entity_type, entity_id }; fetch the record through kweko.api for details.
Theme
init() sets Kweko's design tokens as CSS variables on your :root, so the frame can match Kweko in light and dark:
body { background: var(--kw-bg); color: var(--kw-fg); font-family: var(--kw-font); }
button.primary { background: var(--kw-primary); color: var(--kw-primary-fg); border-radius: var(--kw-radius); }Tokens: --kw-bg, --kw-surface, --kw-sunken, --kw-fg, --kw-fg-2, --kw-fg-3, --kw-primary, --kw-primary-hover, --kw-primary-fg, --kw-border, --kw-success, --kw-danger, --kw-warning, --kw-info, --kw-radius, --kw-radius-lg, --kw-font, --kw-space.
The protocol
The SDK is a thin layer over window.postMessage with the protocol kweko-apps/1. Your frame sends init, resize, navigate, openRecord, toast, api, subscribe, unsubscribe and dock.badge messages (each with an id when it expects a reply); Kweko answers with reply (id, ok, result, error) and pushes event and theme messages. Because the sandboxed frame has no origin, both sides post to "*" and check the sending window instead: Kweko only accepts messages from your iframe, and the SDK only from its parent. Use the SDK rather than the raw protocol where you can; its source is short and readable.