App platform
Blocks
Blocks are UI as JSON. Kweko asks your backend for them, checks them and draws them with its own components, so they look native, work on phones and follow the member's theme and language.
The blocks request
When a member opens a place where your app contributes blocks (a lead tab, a card on the lead panel, a dock widget…), Kweko sends a signed POST to your backend URL plus the app's blocks path (/kweko/blocks unless you changed it):
{
"context": {
"extension_point": "lead.panel.card",
"contribution_id": "stock",
"installation_id": "inst_01j8za1c2d3e4f5g6h7j8k9m0n",
"workspace": { "id": "ws_…", "name": "Acme", "currency": "UZS", "timezone": "Asia/Tashkent" },
"member": { "id": "member_…", "name": "Aziz Rahimov", "locale": "uz-Latn", "role": "manager" },
"theme": { "mode": "light" },
"locale": "uz-Latn",
"issued_at": "2026-09-27T09:41:12Z",
"entity": { "type": "lead", "id": "lead_…" }
},
"extension_point": "lead.panel.card",
"contribution_id": "stock"
}Headers: Kweko-App (your app id), Kweko-Timestamp and Kweko-Signature, signed like webhooks with your client secret. Verify the signature over the raw body and reject timestamps older than 5 minutes before you trust the context.
Answer within 1 second with 200 and:
{
"blocks": [
{ "type": "heading", "text": "Stock" },
{ "type": "key_value", "items": [ { "label": "Office chair Ergo", "value": { "type": "text", "text": "12 in stock" } } ] },
{ "type": "button", "label": "Reserve", "style": "primary", "action": { "id": "reserve" }, "confirm": "Reserve 2 items?" }
],
"cache_ttl": 30
}cache_ttl (0 to 3600 seconds) lets Kweko reuse the blocks. A slow, failing or invalid answer is not shown: Kweko shows an error in place of your blocks (app_slow, app_error, invalid_blocks).
Actions
When a member presses a button or submits a form in your blocks, Kweko posts to your actions path (/kweko/actions unless changed), signed the same way, with 10 seconds to answer:
{ "context": { … }, "contribution_id": "stock", "action_id": "reserve", "values": { "qty": "2" } }values holds the form fields by name. Composer contributions receive action_id composer.submit with values.text. Answer with any of:
| Key | Effect |
|---|---|
blocks | Replace the blocks |
toast | {"text": "Reserved", "tone": "success"}, text up to 300 characters |
navigate | An in-app path such as /leads/lead_… |
refresh | Record types to reload, for example ["lead"] (up to 10) |
open_frame | {"path": "/kweko/reserve", "title": "Reserve"}: open one of your pages as a frame |
Block types
Generated from the API's validator: each type accepts only these keys (besides type).
| Type | Keys |
|---|---|
text | text, tone |
heading | text |
key_value | items |
badge | text, tone |
button | label, style, action, confirm, optimistic, disabled |
button_group | buttons |
list | items |
table | columns, rows |
input | name, label, input_type, placeholder, value, required, hint |
select | name, label, options, multiple, value, required, placeholder |
checkbox | name, label, value |
toggle | name, label, value |
form | blocks, submit |
image | url, alt, width, height |
divider | none |
alert | tone, title, text, action |
progress | value, label |
chart | kind, title, series, stacked |
empty | text, action |
open_frame | label, path |
Tones (tone) are default, muted, neutral, success, warning, danger and info. Charts (kind) are area, line, bar or donut. A button_group holds up to 3 buttons, and form holds input blocks in blocks with a submit button.
Rules and limits
Kweko validates every response before it reaches a browser. A response that breaks a rule is not shown at all:
- Only the types and keys above. No HTML, scripts, styles or event handlers anywhere.
- Links and images must be
https://.navigateandopen_framepaths must be relative to Kweko or your app, starting with a single/. - At most 64 KB, 200 blocks, nesting 6 deep, 50 table rows, 500 points per chart series and 4000 characters of text.