Skip to main content
Resource descriptions, service descriptions, and legal document content are stored as HTML strings produced by the TipTap editor. Native apps must not treat these fields as plain text or as a generic WebView dump — images and videos use custom nodes with data-* attributes. This guide is the contract for rendering that HTML in iOS, Android, and other non-web clients.

Where this HTML appears

Locale selection follows Accept-Language. The flat attribute is the resolved translation; *_i18n is the full map (admin only).
The same HTML shape is used in all three places. Implement one renderer and reuse it.

Storage format

Content is HTML, not TipTap JSON. Custom nodes serialize to semantic tags plus data-* attributes so non-JS clients still have a fallback. Allowed tags after server sanitization include h1h3, p, br, strong, b, em, i, u, s, ul, ol, li, table, thead/tbody/tr/th/td, a, img, div, and iframe (video hosts only). script, style, and javascript: URLs are stripped on save.

Images

Images persist as a normal <img> with extra metadata:

Native rendering

  1. Render src with an image view. If src is root-relative (/storage/...), prefix the API origin.
  2. Honor data-width: treat percentages as a fraction of the content column, not the device width if you have padded margins.
  3. Apply data-align as horizontal alignment of the image block.
  4. Use alt for VoiceOver / TalkBack.
  5. Do not require data-image-id to display the image. It is for lifecycle tracking on the backend.
Prefer the src already present in the HTML. You do not need a second request to /api/v1/images/{id} just to show the picture.

Videos

Videos are not stored as <video> or as a live iframe. The editor persists a fallback link:
Older or sanitized content may instead look like:
Detect a video node if any of these are true:
  • a[data-video-embed] or *[data-video-embed]
  • a.video-embed / div.video-embed
  • iframe whose src host is an allowed provider (see below)

Attributes

Supported providers

Only HTTPS embed hosts from that list are accepted by the API. Reject any other iframe host. YouTube thumbnail fallback when data-thumbnail-url is missing:
Vimeo and Loom thumbnails are only available when data-thumbnail-url is present (resolved via oEmbed at edit time).

How to render videos in a native app

Match the web TipTap renderer: thumbnail + play button first, load the player only after a tap.
1

Parse the node

Read data-src / href as the watch URL, data-embed-url as the player URL, and data-thumbnail-url as the poster. If you only have an iframe, derive the watch URL from the embed src.
2

Draw the poster

Show the thumbnail as a 16:9 cover image. If no thumbnail is available, use a neutral grey background (#6B7280). Center a circular play control (dark translucent disc, white triangle) on top.
3

Handle the tap

Preferred: replace the poster with an in-app player / WKWebView / WebView loaded with data-embed-url (add autoplay=1 after the user gesture). Fallback: open the watch URL in the system browser or YouTube / Vimeo / Loom app.
4

Always keep the plain URL

Under the poster, render the watch URL as a tappable text link. Signed PDFs do the same. This keeps the video reachable when embeds are blocked, offline, or inaccessible.

Layout

  • Default aspect ratio: 16:9 (data-aspect-ratio wins when present).
  • Width: fill the content column; do not exceed it.
  • The play control should stay visually centered on the poster.
  • Use title (or “YouTube / Vimeo / Loom video”) as the accessibility label of the play control.

Do not

  • Autoplay or preload the iframe on bind. That wastes bandwidth and breaks data-saving modes.
  • Execute arbitrary iframe hosts. Stick to the allow-list above.
  • Treat the anchor text (Link to video) as the only UI. Replace the node with the poster + URL treatment.

Rich content may also include:
Render files as a download / preview row. Render entity links as in-app navigation to the referenced resource, service, plan, or bundle using data-entity-model + data-entity-id (slug).

PDF rendering

Signed legal-document PDFs cannot play video. The backend transforms TipTap HTML before TCPDF:
  • Images are embedded (resolved from data-image-id when the file is available).
  • Videos become a static 16:9 poster (thumbnail + play button, or grey if no thumbnail) and the plain watch URL as a clickable link under it.
Native print / share flows should follow the same rule if they generate their own PDF.

Minimal detection cheat sheet

Parse with a real HTML parser (NSAttributedString + custom HTML, Android Html is not enough). Walk the DOM, swap image/video nodes for native views, and render the remaining safe tags as text.