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):

Request body
{
  "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:

Response
{
  "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:

Request body
{ "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:

KeyEffect
blocksReplace the blocks
toast{"text": "Reserved", "tone": "success"}, text up to 300 characters
navigateAn in-app path such as /leads/lead_…
refreshRecord 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).

TypeKeys
texttext, tone
headingtext
key_valueitems
badgetext, tone
buttonlabel, style, action, confirm, optimistic, disabled
button_groupbuttons
listitems
tablecolumns, rows
inputname, label, input_type, placeholder, value, required, hint
selectname, label, options, multiple, value, required, placeholder
checkboxname, label, value
togglename, label, value
formblocks, submit
imageurl, alt, width, height
dividernone
alerttone, title, text, action
progressvalue, label
chartkind, title, series, stacked
emptytext, action
open_framelabel, 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://. navigate and open_frame paths 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.