Zum Inhalt springen
FrankKi
iOS Download

Structured letter content (blocks)

blocks is the structured alternative to plain content: typed paragraphs, headings, key-value rows, tables with real column math, totals lines and named styles, so an agent can build an invoice or a price list instead of hand-formatting a wall of text. Every element stays inside bounded, validated rails, so a structured letter can never break DIN 5008 or the delivery clear zones.

  1. 1

    content or blocks, never both

    A letter carries its body either as content (plain text, as before) or as blocks (structured: tables, headings, totals lines). Exactly one of the two, never both and never neither. blocks is available as a field on letter_create_draft, order_send (inline) and letter_preview. An optional document-level styleDefs object names reusable styles that blocks and cells reference by name.

  2. 2

    The vocabulary

    Nine block types, all available from day one. Deliberately NOT included: explicit page-break blocks (pagination is automatic, table/totals/box already keep themselves together) and bold/italic markup inside a paragraph. Style a whole paragraph, heading, cell or line via style/styleOverride instead.

    paragraph   text                     Plain text, align left/center/right/justify, whole paragraph is ONE style (no bold/italic runs inside)
    heading     text                     Heading, level 1..3
    keyValue    rows                     Label/value rows (e.g. invoice number / date), up to 40 rows
    table       columns, rows            The workhorse: up to 20 columns, 100 data rows, colspan/rowspan, format eur/date/plain, header row repeats across page breaks
    totals      lines                    Label/amount lines, up to 20, emphasis for the grand total, kept together across a page break
    columns     flows (2..3)             Parallel flows of leaf blocks (+ box)
    box         blocks (1..40)           Bordered/filled container, e.g. a hint panel
    image       attachmentId             PNG/JPG, uploaded first via attachment_upload_image
    spacer      lines                    Blank lines, 1..20
  3. 3

    Money is always integer cents

    Every amount is an integer in cents, never a float. 32.00 EUR is 3200, not 32 or 32.00. A table column with "format": "eur" renders the cell value (or a cell object's amountCents field) as formatted currency, e.g. 3,653.30 €. A table cell is a bare string (text), a bare integer (cents, only meaningful in an eur column), null (empty cell), or an object { text?, amountCents?, align?, colspan?, rowspan?, style?, styleOverride? } for anything beyond plain text.

  4. 4

    styleDefs: named styles plus inline overrides

    A document may carry a top-level styleDefs object, up to 24 entries: { "accent": { "font": "sans", "sizePt": 10, "color": "primary", "bold": true } }. Blocks and cells reference a style by name via the style field, or supply a small inline styleOverride with the same shape. Changing one styleDef restyles the whole document. Built-in styles (always available, even with no styleDefs object): default, bold, muted, small, accent, heading1, heading2, heading3, tableHeader, tableCell, totals, totalsEmphasis, caption. Bounds: font is default/serif/sans/mono (default is Times, matching plain-content letters), sizePt 8 to 16, color is one of primary/primaryDark/ink/muted or the grayscale gray0 to gray100, never free RGB. FrankKi checks every style for readability (contrast on white paper), an invisible style is rejected with a fix hint, never silently printed.

  5. 5

    Limits, all in one place

    Generous but hard-capped. Page count stays capped separately and is priced per page, dryRun (on order_send and letter_preview) shows the page count and price before a large table actually gets sent.

    Blocks per document (nested children counted)      200
    Table rows                                         100
    Table columns                                       20
    Characters per table cell                          2000
    Images per document                                  10
    Serialized payload                                 256 KB
    Characters per paragraph                            4000
    Characters per heading                               200
    Characters per image caption                         300
    Label characters (keyValue/totals/table column)      120
    Value characters (keyValue)                          300
    Amount (cents)                      -1,000,000,000 .. 1,000,000,000
    Font size (styleDef)                                8 .. 16pt
    Named styles per document                            24
  6. 6

    Errors are machine-fixable

    An invalid blocks payload never comes back as a bare Zod error. It is a VALIDATION_ERROR (400) with a details array of { path, code, message, hint } entries, e.g. "blocks[2].rows[4].sum: expected integer cents" with a hint that spells out the fix. The intent is a one-round repair loop: read the hint, fix the exact path, resend. The full description of VALIDATION_ERROR lives in the error catalog.

  7. 7

    The crafting loop

    The fastest start is not a blank page but a curated template. template_list marks them curated: true, and block-based ones additionally hasBlocks: true. template_get on one returns its full blocksTemplate plus styleDefs to copy and modify. Then call letter_preview with the modified blocks: it returns the first pages as an image directly in the result, plus a page count and a cost estimate, without charging or sending anything. Adjust blocks, call letter_preview again, until the layout is right. A round takes seconds. Then save it with template_save as a draft and release it with template_release, or for a one-off letter go straight to letter_create_draft and order_send. Letterhead, footer and colors come from your letter design and are set around your blocks, so do not redraw them yourself.

    letter_create_draft
    {
      "subject": "Rechnung Juni 2026",
      "blocks": [
        {
          "type": "heading",
          "text": "Rechnung"
        },
        {
          "type": "table",
          "columns": [
            {
              "key": "text",
              "label": "Artikel",
              "width": "grow",
              "align": "left"
            },
            {
              "key": "sum",
              "label": "Summe netto",
              "width": "auto",
              "align": "right",
              "format": "eur"
            }
          ],
          "rows": [
            {
              "text": "Beratung Website Neugestaltung",
              "sum": 32000
            }
          ]
        },
        {
          "type": "totals",
          "lines": [
            {
              "label": "Rechnungssumme",
              "amountCents": 32000,
              "emphasis": true
            }
          ]
        }
      ],
      "senderAddressId": "<senderAddressId>",
      "recipientAddressInline": {
        "name": "Mandant GmbH",
        "street": "Musterstr.",
        "houseNumber": "1",
        "zip": "12345",
        "city": "Musterstadt",
        "country": "DE"
      }
    }
  8. 8

    Block templates: the same blocks, saved

    A template is not a second content model. blocksTemplate is the SAME blocks array, with {{tokens}} left in the text fields, plus the same styleDefs map. A template carries contentTemplate (plain text) OR blocksTemplate (structured), never both, mirroring the letter itself. The curated FrankKi library covers Rechnung klassisch, Rechnung modern, Mahnung, Zahlungserinnerung, Preisliste, Honorarnote, Angebot and Bericht mit Tabellen: every partner may read them, none may overwrite them, they exist to be copied. Merge fields are typed: text (the default), date (ISO YYYY-MM-DD), number, currency (integer cents) and rows. A rows field is the variable-length half of a template: the table block that draws it names it via rowsFrom instead of carrying fixed rows, and on apply you pass a list of objects keyed by the column keys of that table. Every rows field must be bound by exactly one table and vice versa; template_save rejects anything else with the exact path. Drafts are free: creating, overwriting and previewing them as often as you like costs nothing and mails nothing. Only template_release is gated, and only by the four-eyes rule: with more than one active user in your account a human releases it in the dashboard and the response carries the link.

    // template_save -> blocksTemplate
    {
      "type": "table",
      "columns": [
        {
          "key": "text",
          "label": "Leistung",
          "width": "grow"
        },
        {
          "key": "sum",
          "label": "Betrag",
          "width": "auto",
          "align": "right",
          "format": "eur"
        }
      ],
      "rowsFrom": "positionen"
    }
    
    // template_save -> mergeFields
    [
      {
        "key": "positionen",
        "type": "rows",
        "label": "Positionen",
        "required": true
      }
    ]
    
    // template_apply_with_merge_fields -> mergeValues
    {
      "positionen": [
        {
          "text": "Beratung Juni",
          "sum": 32000
        }
      ]
    }
  9. 9

    Three worked agent flows

    First, "Rechnung fuer Juni an Mandant X" ("invoice for June to client X"): mandant_search finds the client, document_create turns the line items (integer cents) into the invoice, re-computes net, VAT rates and gross, checks the mandatory details under § 14 UStG and optionally draws the number from your sequence, order_send sends it. Instead of order_send, approval_submit puts the letter in front of a human first. Second, "Baue mir eine Preisliste im Stil meines Briefdesigns" ("build me a price list in the style of my letter design"): the crafting loop from step 7. Third, "Mahne Rechnung 2026-014 an" ("send a dunning letter for invoice 2026-014"): document_list finds the invoice, document_get returns a ready-made referencePrefill block that you paste unchanged into document.references of a document_create with documentType mahnung, then order_send. The dunning letter then carries the reference to the invoice and document_list finds both sides of the chain.

    "Invoice for June to client X"
       mandant_search  ->  document_create (rechnung)  ->  order_send
    
    "Build me a price list in the style of my letter design"
       template_list  ->  template_get (curated: true)  ->  modify blocks
       ->  letter_preview  ->  adjust  ->  letter_preview  ->  template_save
       ->  template_release
    
    "Send a dunning letter for invoice 2026-014"
       document_list  ->  document_get (returns referencePrefill)
       ->  document_create (mahnung, references)  ->  order_send
  10. 10

    The full reference via MCP

    The MCP resource frankki://blocks-guide serves the same reference machine-readably straight from the protocol (resources/list, resources/read): the vocabulary, all table options, the same limits, the template and rows rules, a compact example and the same three flows. An agent without browser access needs nothing from this page. For the document half (invoice, dunning letter, payment reminder, credit note, ZUGFeRD and XRechnung) the e-invoicing guide sits next to it.

See the tool referenceDocuments and e-invoicing (ZUGFeRD)See the error catalog

Trust and live status

Machine-readable service facts and the current operational status of the partner interface are publicly available at all times, no login needed.