Strukturierte Briefinhalte (blocks)
blocks ist die strukturierte Alternative zu content: typisierte Absaetze, Ueberschriften, Label-Wert-Zeilen, Tabellen mit echter Spaltenrechnung, Summenzeilen und benannte Stile, damit ein Agent eine Rechnung oder eine Preisliste baut statt Fliesstext von Hand zu formatieren. Jedes Element bleibt in begrenzten, geprueften Bahnen, ein strukturierter Brief kann DIN 5008 oder die Zustellzonen also nie verletzen.
- 1
content oder blocks, nie beides
Ein Brief traegt seinen Text entweder als content (Fliesstext, wie bisher) oder als blocks (strukturiert: Tabellen, Ueberschriften, Summenzeilen). Genau eines von beiden, nie beides und nie keines von beiden. blocks steht als Feld auf letter_create_draft, order_send (inline) und letter_preview zur Verfuegung. Ein optionales styleDefs-Objekt auf Dokumentebene benennt wiederverwendbare Stile, auf die Bloecke und Zellen per Name verweisen.
- 2
Das Vokabular
Zwoelf Blocktypen, alle ab Tag eins verfuegbar: Fliesstext, Formulare (checkboxRow, fillLine, keyValue mit valueRule), das Tabellen-Arbeitspferd, Summenzeilen, Layout-Container (columns, box) und ein einziger manueller Paginierungsbaustein, pageBreak. Alles jenseits davon bleibt automatisch: table/totals/box/checkboxRow/fillLine halten sich beim Seitenumbruch bereits selbst zusammen. Groesse, Farbe und Schriftrolle stylst du je Absatz, Ueberschrift, Zelle oder Zeile per style/styleOverride (Schritt 4); ein Wort mitten im Satz hervorheben geht per Inline-Markup (Schritt 5).
paragraph text Fliesstext, align links/mittig/rechts/blocksatz, versteht inline **fett**/*kursiv* heading text Ueberschrift, level 1..3, versteht inline **fett**/*kursiv* keyValue rows Label/Wert-Zeilen (z. B. Rechnungsnummer / Datum), bis 40 Zeilen, valueRule zieht eine Schreiblinie unter jeden Wert checkboxRow options Frage mit Ankreuzkaestchen, bis 6 Optionen, optionsAlign rechts oder inline, Kaestchen als Vektorgrafik fillLine segments Ein bis vier Schreiblinien nebeneinander, labelPosition inline-left oder below (z. B. "Ort, Datum") table columns, rows Das Arbeitspferd: bis 20 Spalten, 100 Datenzeilen, colspan/rowspan, Format eur/date/plain, Kopfzeile wiederholt sich ueber Seitenumbrueche, Zellentext versteht inline **fett**/*kursiv* totals lines Label/Betrag-Zeilen, bis 20, emphasis fuer die Endsumme, bleibt beim Seitenumbruch zusammen columns flows (2..3) Parallele Spaltenzuege aus einfachen Bloecken (+ box) box blocks (1..40) Umrandeter/gefuellter Container, z. B. ein Hinweiskasten, darf einen columns-Block enthalten image attachmentId PNG/JPG, vorher per attachment_upload_image hochgeladen spacer lines Leerzeilen, 1..20 pageBreak (keine Felder) Harter Seitenumbruch, nur auf oberster Ebene erlaubt
- 3
Geld ist immer Ganzzahl-Cent
Jeder Betrag ist eine Ganzzahl in Cent, niemals eine Kommazahl. 32,00 EUR ist 3200, nicht 32 oder 32.00. Eine Tabellenspalte mit "format": "eur" rendert den Zellwert (oder das amountCents-Feld eines Zellobjekts) als formatierte Waehrung, z. B. 3.653,30 €. Eine Tabellenzelle ist eine nackte Zeichenkette (Text), eine nackte Ganzzahl (Cent, nur in einer eur-Spalte sinnvoll), null (leere Zelle) oder ein Objekt { text?, amountCents?, align?, colspan?, rowspan?, style?, styleOverride? } fuer alles jenseits von reinem Text.
- 4
styleDefs: benannte Stile plus Inline-Override
Ein Dokument kann oben ein styleDefs-Objekt tragen, bis zu 24 Eintraege: { "accent": { "font": "sans", "sizePt": 10, "color": "primary", "bold": true } }. Bloecke und Zellen verweisen per style-Feld auf einen Namen oder tragen einen kleinen Inline-styleOverride mit derselben Form. Aendert man einen styleDef, stylt sich das ganze Dokument neu. Eingebaute Stile (immer verfuegbar, auch ohne eigenes styleDefs-Objekt): default, bold, muted, small, accent, heading1, heading2, heading3, tableHeader, tableCell, totals, totalsEmphasis, caption. Grenzen: font ist default/serif/sans/mono (default ist Times, wie bei content-Briefen), sizePt 7 bis 16 (der Wert 7 ist bewusst ein Schritt unter dem Fliesstext-Kleindruck, fuer Formularlegenden und Bildunterschriften), color ist ein Paletteschluessel (primary/primaryDark/ink/muted oder die Grauskala gray0 bis gray100) ODER eine eigene Marken-Hexfarbe im Format "#rrggbb" (nur sechsstellig klein- oder grossgeschrieben, kein Kurzformat, keine CSS-Namen). FrankKi prueft jede Textfarbe, Paletteschluessel wie Hex gleichermassen, auf Lesbarkeit: mindestens Kontrast 3.0:1 auf weissem Papier, ab Schriftgroesse unter 9pt sogar 4.5:1. Ein unsichtbarer Stil wird als Fehler mit gemessenem Kontrastwert und Korrekturhinweis abgelehnt, nie stillschweigend gedruckt. Farben, die keinen Text tragen (box.borderColor, box.background, table.borders.color, table.zebraColor), nehmen dieselben Paletteschluessel oder Hexwerte, aber ohne Kontrastpruefung: eine blasse Fuellung ist gewollt, sie muss nur eine gueltige Farbe sein.
- 5
Inline-Markup: **fett** und *kursiv*
Fliesstext versteht eine winzige Markdown-Teilmenge, sonst nichts: **fett** wird fett, *kursiv* wird kursiv, ***beides*** wird fett und kursiv. Das gilt fuer paragraph.text, heading.text und Tabellen-zellentext (String-Zelle oder { "text": "..." }); ueberall sonst (checkboxRow.text, fillLine-Labels, keyValue-Label/-Wert, totals-Labels, Bildunterschriften) bleibt der Text buchstaeblich. Ein Wert, den eine eur- oder date-Spalte selbst formatiert hat, traegt ebenfalls kein Markup, weil ihn niemand von Hand geschrieben hat. Zwei Regeln machen das Verhalten vorhersagbar: ein Sternchen oeffnet nur, wenn direkt danach kein Leerzeichen steht, und schliesst nur, wenn direkt davor keines steht, "3 * 4 * 5" bleibt also reiner Text; und ein Oeffner ohne passenden Schliesser derselben Laenge wird woertlich gedruckt, "**fett*" ergibt genau "**fett*". Es gibt kein Escape-Zeichen und kein weiteres Markdown (keine Links, kein Code, keine Listen, kein _kursiv_ mit Unterstrich). Groesse, Farbe und Schriftrolle bleiben Sache von style/styleOverride, Inline-Markup fuegt ausschliesslich fett und kursiv hinzu.
{ "type": "paragraph", "text": "Bitte ueberweise den Betrag bis zum **15.03.2026**, Verwendungszweck *RE-2026-014*." } - 6
Formularbausteine: checkboxRow, fillLine, keyValue mit valueRule
Drei Bausteine machen aus einem Brief etwas, das ein Empfaenger von Hand ausfuellen kann. Ihre Formularelemente sind immer Vektorgrafik (Rechtecke und Linien), nie ein Schriftzeichen, damit ein Kaestchen auf jedem Drucker ein Kaestchen bleibt und nicht zum Fallback-Tofu-Rechteck wird. checkboxRow zeichnet eine Frage mit Ankreuzkaestchen (1 bis 6 Optionen), optionsAlign "right" (Standard) setzt die Kaestchen rechtsbuendig auf die erste Textzeile, "inline" laesst sie direkt hinter dem Text weiterlaufen; checked: true zeichnet ein Kaestchen mit Kreuz, und eine checkboxRow wird nie ueber einen Seitenumbruch getrennt. fillLine zieht ein bis vier Schreiblinien nebeneinander, labelPosition "inline-left" setzt das Label vor die Linie auf dieselbe Grundlinie, "below" zeichnet erst die Linie und darunter ein kleines Label, das klassische "Ort, Datum" unter einer Unterschriftszeile; widthPercent folgt derselben Alles-oder-nichts-Regel wie bei columns. keyValue mit valueRule: true zieht zusaetzlich eine Schreiblinie unter jede Wertspalte, fuer Zeilen wie "Land: ______ Steuernummer: ______", ohne einen zweiten Blocktyp zu brauchen.
{ "type": "checkboxRow", "text": "Bist du mit der elektronischen Zustellung einverstanden?", "options": [ { "label": "Ja" }, { "label": "Nein", "checked": true } ] } { "type": "fillLine", "labelPosition": "below", "segments": [ { "label": "Ort, Datum" }, { "label": "Unterschrift" } ] } { "type": "keyValue", "valueRule": true, "rows": [ { "label": "Land" }, { "label": "Steuernummer (TIN)" } ] } - 7
pageBreak und Verschachtelung
pageBreak ist der einzige manuelle Paginierungsbaustein: der naechste Block beginnt auf einer neuen Seite. Er ist nur auf oberster Ebene erlaubt, nicht in box oder columns, ein pageBreak dort ist eine benannte Ablehnung mit genau diesem Satz in details, kein stiller Fehlschlag. Er wird ohne Fehler ignoriert (der Brief rendert trotzdem), wenn er der erste Block des Dokuments ist oder direkt auf einen anderen pageBreak folgt, weil eine leere erste Seite nie gemeint sein kann. Verschachtelung hat genau zwei legale Formen, beide zwei Ebenen tief: columns > box > einfacher Block, oder box > columns > einfacher Block. Ein columns-Block innerhalb einer box darf dabei nur einfache Bloecke tragen, nie eine weitere box, alles andere ist ebenfalls eine benannte Ablehnung mit dem genauen Pfad.
{ "type": "box", "background": "#f5f0ea", "blocks": [ { "type": "columns", "flows": [ { "blocks": [ { "type": "paragraph", "text": "Absender" } ] }, { "blocks": [ { "type": "paragraph", "text": "Empfaenger" } ] } ] } ] } - 8
Limits, alle an einem Ort
Grosszuegig, aber hart begrenzt. Die Seitenzahl bleibt zusaetzlich separat gedeckelt und wird pro Seite berechnet, dryRun (auf order_send und letter_preview) zeigt Seitenzahl und Preis, bevor eine grosse Tabelle wirklich verschickt wird.
Bloecke pro Dokument (verschachtelt mitgezaehlt) 200 Tabellenzeilen 100 Tabellenspalten 20 Zeichen pro Tabellenzelle 2000 Bilder pro Dokument 10 Serialisierte Nutzlast 256 KB Zeichen pro Absatz 4000 Zeichen pro Ueberschrift 200 Zeichen pro Bildunterschrift 300 Label-Zeichen (keyValue/totals/Tabellenspalte) 120 Wert-Zeichen (keyValue) 300 Zeilen pro keyValue-Block 40 Zeilen pro totals-Block 20 Optionen pro checkboxRow 6 Zeichen Fragetext (checkboxRow) 600 Zeichen pro Checkbox-Label 60 Segmente pro fillLine 4 Breite je fillLine-Segment (Prozent) 5 .. 100 Betrag (Cent) -1.000.000.000 .. 1.000.000.000 Schriftgroesse (styleDef) 7 .. 16pt Benannte Stile pro Dokument 24
- 9
Fehler sind maschinell behebbar
Eine ungueltige blocks-Nutzlast kommt nie als nackter Zod-Fehler zurueck. Es ist ein VALIDATION_ERROR (400) mit einer details-Liste aus { path, code, message, hint }-Eintraegen, z. B. "blocks[2].rows[4].sum: expected integer cents" mit einem Hinweis, der die Korrektur konkret benennt. Ziel ist eine Reparaturschleife in einer Runde: Hinweis lesen, genau den path korrigieren, erneut senden. Die volle Beschreibung von VALIDATION_ERROR steht im Fehlerkatalog.
- 10
Die Gestaltungsschleife
Der schnellste Einstieg ist nicht das leere Blatt, sondern eine kuratierte Vorlage. template_list markiert sie mit curated: true, Blockvorlagen zusaetzlich mit hasBlocks: true. template_get darauf liefert das vollstaendige blocksTemplate plus styleDefs zum Kopieren und Anpassen. Danach letter_preview mit den angepassten blocks aufrufen: liefert die ersten Seiten als Bild direkt im Ergebnis, dazu Seitenzahl und Preisvorschau, ohne dass etwas berechnet oder versendet wird. blocks nachbessern, letter_preview erneut aufrufen, bis das Layout sitzt. Eine Runde dauert Sekunden. Dann mit template_save als Entwurf sichern und mit template_release freigeben, oder fuer einen einmaligen Brief direkt letter_create_draft und order_send. Briefkopf, Fusszeile und Farben kommen aus deinem Briefdesign und werden um deine blocks herum gesetzt, du zeichnest sie nicht selbst nach. Sobald eine Vorlage gespeichert ist, prueft template_preview sie mit Beispielwerten, so wie sie jeder Kollege gerade sieht: fuer ein noch unveraendertes blocks-Layout bleibt letter_preview das richtige Werkzeug, fuer eine bereits gespeicherte Vorlage template_preview.
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" } } - 11
Blockvorlagen: dieselben blocks, gespeichert
Eine Vorlage ist kein zweites Inhaltsmodell. blocksTemplate ist dasselbe blocks-Array, nur mit {{platzhalter}} in den Textfeldern, plus dieselbe styleDefs-Map. Eine Vorlage traegt contentTemplate (Fliesstext) ODER blocksTemplate (strukturiert), nie beides, genau wie der Brief selbst. Die kuratierte FrankKi-Bibliothek deckt Rechnung klassisch, Rechnung modern, Mahnung, Zahlungserinnerung, Preisliste, Honorarnote, Angebot und Bericht mit Tabellen ab: jeder Partner darf sie lesen, keiner sie ueberschreiben, sie sind zum Kopieren da. Merge-Felder sind typisiert: text (Standard), date (ISO JJJJ-MM-TT), number, currency (ganzzahlige Cent) und rows. Ein rows-Feld ist der variable Teil einer Vorlage: der Tabellenblock, der es zeichnet, nennt es mit rowsFrom statt feste Zeilen zu tragen, und beim Anwenden uebergibst du eine Liste von Objekten mit den Spaltenschluesseln dieser Tabelle. Jedes rows-Feld muss von genau einer Tabelle gebunden sein und umgekehrt, template_save weist alles andere mit dem genauen Pfad zurueck. Entwuerfe sind frei: anlegen, ueberschreiben und beliebig oft in der Vorschau ansehen kostet nichts und versendet nichts. Nur template_release ist gebremst, und zwar allein durch das Vier-Augen-Prinzip: hat dein Konto mehr als einen aktiven Nutzer, gibt ein Mensch im Dashboard frei und die Antwort enthaelt den Link dorthin.
// 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 } ] } - 12
Drei ausgearbeitete Agenten-Ablaeufe
Erstens, "Rechnung fuer Juni an Mandant X": mandant_search findet den Mandanten, document_create baut aus den Positionen (ganzzahlige Cent) die Rechnung, rechnet Netto, USt-Saetze und Brutto nach, prueft die Pflichtangaben nach § 14 UStG und vergibt auf Wunsch die Nummer aus deinem Nummernkreis, order_send verschickt sie. Statt order_send legt approval_submit den Brief einem Menschen zur Freigabe vor. Zweitens, "Baue mir eine Preisliste im Stil meines Briefdesigns": die Gestaltungsschleife aus Schritt 10. Drittens, "Mahne Rechnung 2026-014 an": document_list findet die Rechnung, document_get liefert einen fertigen referencePrefill-Block, den du unveraendert als document.references in ein document_create mit documentType mahnung einsetzt, dann order_send. Die Mahnung traegt danach den Bezug zur Rechnung, und document_list findet beide Seiten der Kette.
"Rechnung fuer Juni an Mandant X" mandant_search -> document_create (rechnung) -> order_send "Baue mir eine Preisliste im Stil meines Briefdesigns" template_list -> template_get (curated: true) -> blocks anpassen -> letter_preview -> anpassen -> letter_preview -> template_save -> template_release "Mahne Rechnung 2026-014 an" document_list -> document_get (liefert referencePrefill) -> document_create (mahnung, references) -> order_send
- 13
Vollstaendige Referenz per MCP
Die MCP-Ressource frankki://blocks-guide liefert dieselbe Referenz maschinenlesbar direkt im Protokoll (resources/list, resources/read): Vokabular, alle Tabellenoptionen, dieselben Limits, die Vorlagen- und rows-Regeln, ein kompaktes Beispiel und dieselben drei Ablaeufe. Ein Agent ohne Browser-Zugriff kommt damit ohne diese Seite aus. Fuer den Belegteil (Rechnung, Mahnung, Zahlungserinnerung, Gutschrift, ZUGFeRD und XRechnung) steht die E-Rechnung-Doku daneben.
Tool-Referenz ansehen →Belege und E-Rechnung (ZUGFeRD) →Fehlerkatalog ansehen →
Vertrauen und Live-Status
Maschinenlesbare Servicefakten und der aktuelle Betriebsstatus der Partner-Schnittstelle stehen jederzeit offen zur Verfügung, ohne Login.