AI page generation

Teach an AI client how to create landing pages with pages, sections, and rows through the API.

Give the AI a detailed brief

AI results depend on the instructions you provide. Describe your audience, goals, required content, tone, calls to action, constraints, and design direction. Vague or incomplete instructions can produce generic, inaccurate, or otherwise sub-optimal results.

Core idea

A page is the publishable destination. A section is an ordered layout band on that page. A row is a content component inside a section.

For AI-generated landing pages, prefer creating one page blueprint in a single request. The blueprint endpoint creates the page, its sections, and each section's rows in one transaction, so a failed blueprint does not leave partial content behind.

  • Use pages for URL slugs, SEO fields, and publish status.
  • Do not create or assign tags. Tags are optional user-managed metadata and are outside AI page-building workflows.
  • Use sections for layout choices such as hero, split_pane, cards, rows_in_columns, slider, pricing, navbar, and raw_html.
  • Use rows for repeated or nested content such as card items, offer plans, testimonials, collapsibles, split slides, image items, and navbar_link items.
  • Use templates for reusable custom layouts that still need editable content. A template supplies Liquid HTML/CSS/JS and a field_schema; sections and rows supply the actual content and template_data.
  • If the page or Page Shell uses body-only export, the receiving site supplies the head. Make sure that host/import layout loads Bootstrap 5.3.8 CSS and bootstrap.bundle.min.js so generated Bootstrap utility classes, navbars, dropdowns, and collapses work after import.
  • The API token chooses the organization automatically. Do not send an organization_id.

Authentication

Customers should create an organization API token in the app and give it to their AI client. The API accepts either an Authorization bearer token or X-Api-Token. Every request is scoped to the token's organization.

curl https://example.com/api/v1/schema \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Generation workflow

An AI client should follow the same sequence for every landing page request.

  • Ask the human for design direction before drafting when the brief is vague: audience, offer, tone, desired sections, CTAs, visual style, and available images.
  • Fetch GET /api/v1/schema before generating JSON. Use the returned fields and enum values as the contract.
  • Never send tag, tag_id, tags, tag_ids, taggable, or taggables fields. AI API workflows reject them because tags are user-managed.
  • Read ai_layout_contract and choose one ai_layout_preset from the schema before drafting sections. Treat rejected layout patterns as hard failures, not style preferences.
  • Fetch GET /api/v1/templates to inspect templates already copied into the organization.
  • Fetch GET /api/v1/template_library to browse default templates. If a default template fits, copy it with POST /api/v1/template_library/:id/copy, then customize the copied template instead of editing the default.
  • For a general page request, plan a mixed composition: built-in sections and rows for the page skeleton, plus owned or copied Liquid-backed sections/rows for 1-3 distinctive visual moments such as the hero, proof band, metrics band, feature showcase, or custom card row.
  • End generated landing pages and marketing pages with an actual Footer Liquid section template from owned templates or the default template library. Prefer copied defaults such as section_footer_columns, section_footer_newsletter, or section_footer_directory when available. Render 3-4 populated columns with one compatible Liquid row per column, and store every link in that row's ordered actions collection.
  • Do not make the whole page built-in if available Liquid templates can make the first draft more polished, and do not make the whole page Liquid-backed because humans still need predictable editable structure.
  • Account for billing state. New organizations may be on a seven-day trial with a three-page creation limit and unlimited exports. Prefer revising an existing draft during trial experimentation instead of creating disposable pages.
  • Choose a landing page outline: navbar, hero, benefit sections, feature cards, proof, pricing, FAQ, and closing CTA are common patterns.
  • Map each outline block to a section type_of. Add rows only when the section type expects repeated child components.
  • If the page needs customer analytics, put the provider install script in page head_scripts or shared Page Shell head_scripts, then set CTA/link event fields on the sections and rows that render clickable actions.
  • For clickable destinations, send an ordered actions array containing label, url, style, and optional event_name and event_params.
  • Use safe analytics event names such as demo_cta_clicked, pricing_plan_clicked, footer_link_clicked, or signup_started. Use JSON object params with stable metadata such as placement, campaign, plan_tier, variant, or component_key. Do not send names, emails, phone numbers, form values, raw HTML, Liquid, long copy, or secrets.
  • When rebuilding a Figma mockup or source page, reuse an owned or copied library Liquid template when it fits. Create a new section or row template only for layout patterns that built-in components and library templates cannot express. Define field_schema for editable text, colors, URLs, badges, labels, and layout toggles, then choose one render mode per section or row: type_of for built-in rendering, or template_id plus template_data for Liquid rendering.
  • When a section uses a Liquid template_id, every child row must also use a Liquid row template_id. In the section Liquid, render child row templates with row.rendered_html.
  • Use a varied component mix. Do not generate every page as only hero, cards, and text. When the content calls for it, include split_pane, rows_in_columns, slider, pricing, navbar, collapsible rows, testimonial rows, offer rows, image rows, split rows, and card variants such as grid, icon, feature_text, and numbered_step.
  • Choose variation intentionally: use split_pane for a major text/media explanation, slider with slider_variant image and slider_behavior auto for visual proof, slider with slider_variant mixed and slider_behavior auto for compact testimonial cards, slider with slider_variant split_rows and slider_behavior controls for full-width media-backed stories, pricing for plans or workflow comparisons, rows_in_columns for process steps, and collapsible rows for FAQ/search objections.
  • When product screenshots, photos, badges, or avatars are available, choose section and row types that actually render attached media instead of hiding the assets in records that ignore images.
  • Use Bootstrap utility classes in section_classes, row_classes, header_classes, body_classes, subheader_classes, and image_styles to make cards and sections feel intentionally designed.
  • For body-only exports, do not assume the exported fragment carries Bootstrap assets. The host/import page head must provide Bootstrap 5.3.8 CSS and bootstrap.bundle.min.js.
  • Give generated pages more breathing room between sections. Use section_classes to add generous top and bottom padding on non-navbar sections, and make the first hero/content section fill more of the first laptop viewport when the design calls for a strong opening.
  • Prevent visual overlap. Do not place sections, cards, badges, divider accents, media, or text on top of each other. Reserve explicit padding, margin, grid gap, and min-height for decorative elements, and avoid negative margins or absolute positioning unless the parent layout leaves enough space at desktop and mobile widths.
  • Prevent clipped cards and rows. Avoid fixed card heights, overflow hidden, and narrow rows_in_columns layouts that stack too much copy in one column. If row content length varies, let cards grow naturally, use overflow: visible, and choose columns/rows_per_column values that give each card enough width and vertical room.
  • Prevent unreadable copy. When setting background_color, header_text_color, body_text_color, Bootstrap bg-* / text-* classes, or raw_html inline color styles, keep text/background contrast at 4.5:1 or higher.
  • Send POST /api/v1/page_blueprints with one page blueprint or a batch array of page blueprints. Each blueprint should include slug, metadata, status, sections, and rows. If adapting a worked example that uses pages: [...], the endpoint can create that batch directly when the organization has page capacity.
  • Read layout_warnings in the blueprint response. If warnings mention normalized spacing, clamped columns, or balanced column_index values, use the returned IDs to inspect and revise the affected sections instead of assuming the first draft is done.
  • Treat the generated page as an editable first draft. The human may still upload final images, move sections, reorder rows, adjust CTAs, and refine the design in the StaticPagesApi editor.
  • Keep initial pages in draft unless the human explicitly asks to publish. Use active when the page should render publicly.
  • After creation, inspect the response IDs and use incremental section or row endpoints for revisions.
  • When revising existing content, update in place. Use PATCH /api/v1/sections/:section_id for section changes and PATCH /api/v1/rows/:row_id for row changes. Do not delete and recreate, replace an entire record, or send a new page blueprint just to change copy, styling, template_data, order, deployment_key, analytics settings, or other editable fields.

Liquid template workflow

Use this workflow when a design needs custom HTML structure but the content still needs to be editable by humans through section and row forms.

  • Create one Template for each reusable layout pattern instead of one giant page template.
  • Before creating a template from scratch, call GET /api/v1/templates and GET /api/v1/template_library. Copy a default template with POST /api/v1/template_library/:id/copy when it is close enough, then customize the copy.
  • Template keys are unique per organization and templateable_type. When creating a template, send a clear base key and use the key returned by the API, because the API may append _2, _3, or another suffix if that key already exists.
  • Use templateable_type Section for whole page bands and Row for repeated cards, slides, FAQ items, stats, or custom subcomponents.
  • Declare every extra editable value in field_schema. Supported field types are text, textarea, url, number, boolean, color, and select. Add optional help_text for editor guidance and group fields under content, media, actions, appearance, or template.
  • Keep fixed structure in template_html and template_css. Put page-specific copy, URLs, colors, labels, and repeated item content in section fields, row fields, and template_data.
  • Scope every CSS selector to a unique template root class such as .tpl-feature-band or .customer-proof-grid. Do not write broad selectors like h2, p, .card, .btn, .container, or body in template_css.
  • Prefer template-owned classes over global utility assumptions for AI-generated custom layouts. If you use Bootstrap classes, use them for layout helpers only and keep the custom visual rules under the template root class.
  • Design template layouts so decorative accents, badges, floating panels, cards, media, and text cannot collide. Prefer normal document flow, grid/flex gap, padding, and min-height over negative margins. If absolute positioning is necessary, keep the positioned element inside a parent that reserves enough space at mobile and desktop widths.
  • Do not force reusable template cards into fixed heights unless every content state has been accounted for. Avoid overflow: hidden on text/card containers, prefer min-height over height, and let cards expand naturally when row.body_html, row.header, or row.data values are longer than the sample content.
  • Add category, keywords, sample_data, and library_component metadata so humans and AI clients can find, preview, and reuse the template later. Template keywords are search descriptors and do not affect rendering.
  • Use section.header, section.subheader, section.body_html, section.data.*, rows, rows_by_group, rows_by_column, row.header, row.body_html, row.rendered_html, and row.data.* in Liquid.
  • When a section template loops through rows, prefer row.rendered_html so each child Liquid row template controls its own markup.
  • When a row is just a link, use its first ordered action for the visible label and destination.
  • When a template renders editable actions, loop through section.actions or row.actions and include action.analytics_attributes so analytics and link_target behavior are both preserved. These Liquid collections contain active actions only.
  • After the template exists, create or update sections and rows with template_id and template_data. Do not also send type_of on the same section or row. If a section uses template_id, its child rows must use row template_id values too.
{
  "templateable_type": "Section",
  "key": "figma_stats_band",
  "name": "Figma stats band",
  "template_html": "<section class=\"stats-band\"><p>{{ section.data.eyebrow }}</p><h2>{{ section.header }}</h2>{% for row in rows %}<article><strong>{{ row.header }}</strong>{{ row.body_html }}</article>{% endfor %}</section>",
  "template_css": ".stats-band { padding: 6rem 0; } .stats-band article { border-radius: 8px; }",
  "category": "Proof",
  "keywords": ["stats", "proof", "marketing"],
  "library_component": true,
  "field_schema": {
    "fields": [
      { "key": "eyebrow", "label": "Eyebrow", "type": "text", "default": "Results" }
    ]
  },
  "default_data": {
    "eyebrow": "Results"
  },
  "sample_data": {
    "section": {
      "header": "Results worth shipping"
    },
    "rows": [
      { "header": "4x", "subheader": "Reusable", "body_html": "<p>Turn one layout into a library component.</p>" }
    ]
  }
}

Blueprint endpoint

POST /api/v1/page_blueprints accepts one JSON object with page fields at the top level, a top-level array of page objects, or an object with pages: [ ... ]. Each page can include a sections array, and each section can include its own rows array.

The only required field is slug. Unknown fields are ignored by Rails strong parameters, but clients should still rely on GET /api/v1/schema so generated payloads stay compatible.

{
  "slug": "summer-launch",
  "meta_title": "Summer Launch",
  "meta_description": "A focused landing page for the summer campaign.",
  "status": "draft",
  "sections": [
    {
      "type_of": "hero",
      "status": "active",
      "order": 1,
      "header": "Launch your summer menu faster",
      "header_type": "h1",
      "body": "<p>Create a polished campaign page from reusable content blocks.</p>",
      "actions": [
        { "label": "Start now", "url": "/signup", "style": "primary", "position": 1 }
      ]
    },
    {
      "type_of": "cards",
      "status": "active",
      "order": 2,
      "header": "What the page includes",
      "header_type": "h2",
      "cards_per_row": "3",
      "rows": [
        {
          "type_of": "card",
          "card_variant": "grid",
          "header": "Offer highlights",
          "body": "<p>Showcase seasonal products, services, or promotions.</p>",
          "order": 1
        },
        {
          "type_of": "card",
          "card_variant": "grid",
          "header": "Social proof",
          "body": "<p>Add quotes, ratings, outcomes, or customer wins.</p>",
          "order": 2
        },
        {
          "type_of": "card",
          "card_variant": "grid",
          "header": "Clear action",
          "body": "<p>Guide visitors toward one primary conversion.</p>",
          "actions": [
            { "label": "Book a demo", "url": "/demo", "style": "primary", "position": 1 }
          ],
          "order": 3
        }
      ]
    }
  ]
}

Recommended section patterns

These combinations give AI clients reliable landing page structures without needing custom HTML.

  • Hero: use a hero section with header_type h1, concise body, and one primary CTA. Keep the H1 near clamp(2rem, 3.25vw, 3rem) and primary H2 section headings near clamp(1.75rem, 3vw, 2.25rem); avoid display-1 through display-4, and use page-specific settings or classes instead of changing a shared default template.
  • Feature grid: use a cards section with card rows, usually card_variant grid, header_body, icon, or numbered_step.
  • Benefit split: use split_pane for one major image-supported message.
  • Testimonials: use cards or slider with slider_variant mixed, slider_behavior auto, and testimonial rows for compact proof cards.
  • Pricing: use a pricing section with offer rows only.
  • FAQ: use cards, rows_in_columns, or slider with slider_variant mixed, slider_behavior controls, and collapsible rows.
  • Navigation: use a navbar section with navbar_link rows only. One action is a direct link; multiple actions make the first label a dropdown heading and the rest dropdown links.
  • Footer: use a footer Liquid section with one row per column and multiple ordered actions for that column's vertical link list.
  • Publishing story: use slider with slider_variant split_rows, slider_behavior controls, and split rows when each destination or workflow step needs its own full-width visual explanation.
  • Visual proof strip: use slider with slider_variant image and image rows with image_variant slider_item when the page has multiple screenshots, destination diagrams, logos, or generated visuals.
  • Workflow comparison: use pricing with offer rows when comparing plans, modes, packages, or publishing workflows.
  • Process: use rows_in_columns with card rows using card_variant numbered_step only when there is exactly one card per column. For longer sequences, use a cards section with cards_per_row so cards wrap as separate grid items instead of stacking full-height cards inside one column.
  • Custom embeds: use raw_html sparingly when no built-in section or row type fits.

Recreating an existing page

When an AI is asked to reproduce an existing landing page, treat the source page as a design reference and rebuild it with this system's components instead of pasting the whole page into one raw_html block.

  • Start by inventorying the source page into bands: navbar, hero, feature grid, media section, proof, FAQ, pricing, and closing CTA.
  • Map each band to a native section first. For example, use navbar with navbar_link rows, cards with card rows using card_variant grid for repeated feature cards, text for simple copy bands, and rows_in_columns for asymmetric text/media layouts.
  • Use rows for repeated content and per-item styling. Repeated cards, nav links, offers, testimonials, and collapsibles should become rows rather than hand-coded markup.
  • If the design requires a custom layout, create a database-backed Liquid template for that section or row pattern and attach it with template_id. Keep the content editable with normal section and row fields plus template_data.
  • Use raw_html only for the pieces the current renderers cannot express faithfully, such as overlapping screenshot stacks, custom badge clusters, special callout markup, or third-party embeds.
  • Even when recreating a source design with overlapping visual motifs, do not let content physically collide. Recreate the impression with layered backgrounds, contained media frames, padding, and reserved space so headings, cards, CTAs, badges, and dividers remain readable at desktop and mobile widths.
  • Keep raw_html scoped and local. A raw row inside a rows_in_columns section is better than one page-sized raw_html section because the surrounding page still teaches the system about layout structure.
  • If a source page has real media, preserve it visibly. Use attached images for native image-capable components, and use raw_html image tags only when the component layout needs remote images or a custom visual arrangement.
  • After building, verify the rendered output contains the expected sections, rows, media, CTA links, and card classes. If possible, inspect the page visually at desktop and mobile widths.

Worked example: MenuScouter about

This example records the component-first reconstruction used for the MenuScouter about page. Keep it as a learning artifact for future AI page builds.

The important lesson is not to paste the full source page into one raw_html section. Preserve the page structure with native sections and rows, then use raw_html only where the component system cannot faithfully express the visual layout.

  • Source reference: https://menuscouter.com/about
  • Navbar: use a navbar section with navbar_link rows and attach the real logo image to the section.
  • Hero: use rows_in_columns so the left column can hold native section copy and CTA fields while the right column holds a raw_html screenshot stack.
  • Feature grid: use a cards section with card rows using card_variant grid. Put Bootstrap polish in section_classes, row_classes, body_classes, and image_styles where the renderer supports them.
  • Screenshot narrative: use rows_in_columns with a native text/content row on the left and one native image row per screenshot on the right.
  • Why/callout band: use rows_in_columns with a native text row and a raw_html callout only for the custom blue box/list treatment.
  • Closing CTA: use ordered actions with app_store and google_play styles instead of raw HTML buttons. Add text-center and spacing classes so the action renderer centers and styles the generated Bootstrap buttons.
  • Scoped CSS: keep source-matching CSS inside the smallest raw_html row that needs it, using a page-specific class prefix.
  • Verification: render the page and check section count, row count, navbar, hero copy, screenshots, feature cards, generated CTA buttons, and CTA links.
# Component map from the MenuScouter about reconstruction.
[{:order=>1,
  :section=>"navbar",
  :rows=>["navbar_link"],
  :raw_html=>false,
  :lesson=>
   "Use the native navbar section because it renders a brand image plus navbar_link rows."},
 {:order=>2,
  :section=>"rows_in_columns",
  :rows=>["raw_html"],
  :raw_html=>"phone screenshot stack only",
  :lesson=>
   "Use the section for two-column structure and section-level CTA fields for buttons; use raw HTML only for overlapping phone screenshots."},
 {:order=>3,
  :section=>"cards",
  :rows=>["card(grid)", "card(grid)", "card(grid)"],
  :raw_html=>false,
  :lesson=>
   "Use card rows with card_variant grid for repeated feature cards so Bootstrap classes and card rendering stay native."},
 {:order=>4,
  :section=>"rows_in_columns",
  :rows=>["text", "image", "image", "image"],
  :raw_html=>false,
  :lesson=>
   "Use rows_in_columns for text/media composition. Each screenshot should be its own native image row with an attachment and image_styles."},
 {:order=>5,
  :section=>"rows_in_columns",
  :rows=>["text", "raw_html"],
  :raw_html=>"custom callout box only",
  :lesson=>
   "Use a native text row for prose and raw HTML only for the blue callout list that needs source-matching markup."},
 {:order=>6,
  :section=>"text",
  :rows=>[],
  :raw_html=>false,
  :lesson=>
   "Use a text section with section-level app_store and google_play CTA fields; center and style the generated buttons with Bootstrap classes on the section/header/body."}]

Worked example: StaticPagesApi homepage

This example records the component decisions used to recreate the StaticPagesApi homepage from the hardcoded Slim page as pages, sections, and rows.

The important lesson is to let section controls and purpose-built row types carry the design. Use raw_html only for genuinely custom visuals, and create a new row type when existing components force the wrong markup order or wrapper.

  • Source template: app/views/dynamic_pages/show.html.slim
  • Page slug: staticpagesapi-home
  • Hero: use rows_in_columns with section-level header, subheader, body, CTA fields, leading_cols, and vertical_text_alignment. The right console remains a scoped raw_html row because it is custom pre/code mockup markup.
  • Eyebrow: store 'Static pages your business can keep' as section.subheader and use static-home-eyebrow in subheader_classes so the renderer places it above the h1.
  • Feature cards: use card rows with card_variant icon because the design needs icon label, h3, and paragraph without a Bootstrap card wrapper.
  • Process cards: use card rows with card_variant numbered_step and num_value for the red badge. Avoid raw_html spans for numbers.
  • Practical cards: use plain text rows because existing text rendering already matches the desired h3 plus paragraph card body.
  • Spacing: add reusable section_classes such as static-home-hero-roomy and static-home-section-roomy, then define the CSS once. Make these classes win over Bootstrap utility defaults when necessary.
  • Footer: attach the logo to image_desktop and use native CTA fields. Do not hardcode fingerprinted asset paths in raw_html.
  • Verification: render each section and check the actual HTML shape: icon cards should not include class="card", process/practical sections should render one Bootstrap row with one column per card, and footer images should come from ActiveStorage.
# Component map from the StaticPagesApi homepage reconstruction.
[{:order=>1,
  :section=>"rows_in_columns",
  :rows=>["raw_html", "raw_html"],
  :section_classes=>"static-home static-home-hero static-home-hero-roomy",
  :controls=>
   {:leading_cols=>6,
    :text_alignment=>"left",
    :vertical_text_alignment=>"center"},
  :lesson=>
   "Use rows_in_columns for the hero because it preserves the two-column structure. Render the eyebrow from subheader before the h1 when subheader_classes includes static-home-eyebrow. Use section controls for vertical centering instead of custom wrapper markup."},
 {:order=>2,
  :section=>"cards",
  :rows=>["card(icon)", "card(icon)", "card(icon)"],
  :section_classes=>
   "static-home static-home-section static-home-section-roomy bg-white",
  :controls=>{:cards_per_row=>3},
  :lesson=>
   "Use card rows with card_variant icon when the visual design needs an icon label before the header and no nested Bootstrap .card wrapper. Do not force card_variant grid when its partial renders the wrong HTML shape."},
 {:order=>3,
  :section=>"rows_in_columns",
  :rows=>
   ["card(numbered_step)",
    "card(numbered_step)",
    "card(numbered_step)",
    "card(numbered_step)"],
  :section_classes=>
   "static-home static-home-section static-home-section-roomy static-home-how",
  :controls=>
   {:leading_cols=>5,
    :columns=>2,
    :rows_per_column=>1,
    :text_alignment=>"left"},
  :lesson=>
   "Use card rows with card_variant numbered_step for process cards. The number belongs in num_value, not raw_html. A 2x2 grid requires columns 2 and rows_per_column 1 so each card gets its own Bootstrap column."},
 {:order=>4,
  :section=>"rows_in_columns",
  :rows=>["text", "text", "text", "text"],
  :section_classes=>
   "static-home static-home-section static-home-section-roomy bg-white",
  :controls=>
   {:leading_cols=>4,
    :columns=>2,
    :rows_per_column=>1,
    :text_alignment=>"left"},
  :lesson=>
   "Use existing text rows when the desired component is just a styled wrapper with h3 plus paragraph. Creating a new row type is unnecessary when the renderer already matches the HTML shape."},
 {:order=>5,
  :section=>"text",
  :rows=>[],
  :section_classes=>"static-home static-home-final text-center",
  :attachments=>["image_desktop: static_pages_api_logo.svg"],
  :lesson=>
   "Use a text section with native CTA fields for the footer. Attach the logo as image_desktop instead of embedding a hardcoded /assets path in raw_html, then use section-level styling and CTA class overrides to match the design."}]

Worked example: static marketing pages

These examples document the three generated StaticPagesApi marketing pages: static-page-generator, marketing-landing-page-builder, and export-static-html-to-s3.

They are useful learning material because they deliberately vary the available components instead of repeating a hero, card grid, and CTA. Use them as patterns for SEO pages, campaign pages, and publishing workflow pages.

  • Use the keyword target to choose the page slug, meta title, and first h1. The page slug should be literal and search-readable.
  • Use a shared navbar section with navbar_link rows across related marketing pages to create an internal topical cluster.
  • Do not make every page the same shape. Use split_pane for product explanation, slider with slider_variant image and slider_behavior auto for visual proof, slider with slider_variant mixed and slider_behavior controls for testimonials, pricing for workflow comparisons, slider with slider_variant split_rows and slider_behavior auto for destination stories, rows_in_columns for process steps, and collapsible rows for FAQ/search objections.
  • For FAQ sections that need a visual, use rows_in_columns with a leading image row and main collapsible rows instead of a cards grid. This creates a left image/right accordion layout while keeping each question as a native row.
  • Images are attached to image_desktop fields after blueprint creation. The API blueprint recreates the structure; assets can be uploaded through the editor or attached by a Rails task.
  • For S3/static-hosting pages, explain both the output and the transport: exported static HTML plus signed webhook delivery.
  • Verify by rendering each generated page. Component selection is only correct if the renderer actually applies the fields you stored.
# Component maps and a reproducible blueprint skeleton for the generated marketing pages.
# Add image_desktop attachments after creation for hero, card grid variants, image slider_item variants, split, and testimonial rows.
MARKETING_STATIC_PAGE_EXAMPLES = [{:slug=>"static-page-generator",
  :title=>"Static Page Generator for Fast Marketing Pages | StaticPagesApi",
  :search_target=>
   "static page generator, static website generator, export static HTML",
  :components=>
   [{:section=>"navbar",
     :rows=>["navbar_link", "navbar_link", "navbar_link"],
     :lesson=>
      "Use navbar for cross-page topical links so each generated marketing page reinforces the same SEO cluster."},
    {:section=>"hero",
     :controls=>
      {:hero_type=>"split", :image_desktop=>"export dashboard image"},
     :lesson=>
      "Use split hero when the first viewport needs both keyword-led copy and a product visual."},
    {:section=>"split_pane",
     :rows=>["image(text)", "image(text)"],
     :controls=>{:leading_cols=>5, :text_alignment=>"left"},
     :lesson=>
      "Use split_pane for explanatory product copy, then use image_text rows for compact supporting points."},
    {:section=>"slider",
     :rows=>
      ["image(slider_item)",
       "image(slider_item)",
       "image(slider_item)",
       "image(slider_item)"],
     :controls=>{:slider_variant=>"image", :slider_behavior=>"auto"},
     :lesson=>
      "Use slider with slider_variant image and slider_behavior auto for visual proof that the same static output can support several publishing destinations."},
    {:section=>"cards",
     :rows=>["card(grid)", "card(grid)", "card(grid)"],
     :controls=>{:cards_per_row=>3},
     :lesson=>
      "Use card rows with card_variant grid for scannable benefits with optional CTA links."},
    {:section=>"text",
     :rows=>[],
     :lesson=>
      "Use a text section with native CTA fields for the final conversion band."}]},
 {:slug=>"marketing-landing-page-builder",
  :title=>"Marketing Landing Page Builder with Static HTML Export",
  :search_target=>
   "marketing landing page builder, campaign page builder, static landing page builder",
  :components=>
   [{:section=>"hero",
     :controls=>{:hero_type=>"split", :image_desktop=>"builder blocks image"},
     :lesson=>
      "Use the hero to explain the platform-independent promise: build pages here, keep the final HTML portable."},
    {:section=>"cards",
     :rows=>["card(grid)", "card(grid)", "card(grid)"],
     :controls=>{:cards_per_row=>3},
     :lesson=>
      "Use image-backed card rows with card_variant grid for SEO pages, campaign pages, and export pages."},
    {:section=>"slider",
     :rows=>["testimonial", "testimonial", "testimonial"],
     :controls=>{:slider_variant=>"mixed", :slider_behavior=>"controls"},
     :lesson=>
      "Use testimonial rows in a controlled slider for proof without making the page too long."},
    {:section=>"pricing",
     :rows=>["offer", "offer", "offer"],
     :lesson=>
      "Use pricing with offer rows to compare workflows, even when the cards describe plans or modes instead of dollar prices."},
    {:section=>"cards",
     :rows=>["collapsible", "collapsible", "collapsible", "collapsible"],
     :controls=>{:cards_per_row=>2},
     :lesson=>
      "Use collapsible rows for FAQ content so search-targeted objections are represented without overwhelming the page."},
    {:section=>"text",
     :rows=>[],
     :lesson=>
      "Close with a CTA that repeats the core value proposition and points to page creation."}]},
 {:slug=>"export-static-html-to-s3",
  :title=>"Export Static HTML to S3, Your Website, or Any Host",
  :search_target=>
   "export static HTML to S3, static HTML export, static page webhook",
  :components=>
   [{:section=>"hero",
     :controls=>
      {:hero_type=>"split", :image_desktop=>"signed webhook export image"},
     :lesson=>
      "Use a visual export diagram because this page is about publishing mechanics more than page authoring."},
    {:section=>"slider",
     :rows=>["split", "split", "split"],
     :controls=>{:slider_variant=>"split_rows"},
     :lesson=>
      "Use slider with slider_variant split_rows when several destination stories each need their own text and image pairing."},
    {:section=>"rows_in_columns",
     :rows=>
      ["card(numbered_step)",
       "card(numbered_step)",
       "card(numbered_step)",
       "card(numbered_step)"],
     :controls=>{:columns=>4, :rows_per_column=>1},
     :lesson=>
      "Use card rows with card_variant numbered_step for the export pipeline: export, sign, receive, deploy."},
    {:section=>"rows_in_columns",
     :rows=>
      {:leading=>["image"],
       :main=>["collapsible", "collapsible", "collapsible", "collapsible"]},
     :controls=>
      {:leading_cols=>5,
       :text_alignment=>"left",
       :vertical_text_alignment=>"top"},
     :lesson=>
      "Use rows_in_columns when FAQ content needs a supporting visual: put an image row in the leading group for the left column and collapsible rows in the main group for expandable questions on the right."},
    {:section=>"text",
     :rows=>[],
     :lesson=>
      "Close with webhook setup because that is the highest-intent next action for this page."}]}]


MARKETING_STATIC_PAGE_BLUEPRINT = {:shared_nav_rows=>
  [{:type_of=>"navbar_link",
    :actions=>
     [{:label=>"Static generator",
       :url=>"/static-page-generator",
       :style=>"link",
       :position=>1}]},
   {:type_of=>"navbar_link",
    :actions=>
     [{:label=>"Marketing pages",
       :url=>"/marketing-landing-page-builder",
       :style=>"link",
       :position=>1}]},
   {:type_of=>"navbar_link",
    :actions=>
     [{:label=>"Export anywhere",
       :url=>"/export-static-html-to-s3",
       :style=>"link",
       :position=>1}]}],
 :pages=>
  [{:slug=>"marketing-landing-page-builder",
    :meta_title=>"Marketing Landing Page Builder with Static HTML Export",
    :meta_description=>
     "Create campaign pages, service pages, SEO landing pages, and product launch pages with reusable components, then export static HTML anywhere.",
    :status=>"active",
    :sections=>
     [{:type_of=>"hero",
       :order=>2,
       :hero_type=>"split",
       :header=>
        "Marketing landing pages that do not get trapped in a platform",
       :header_type=>"h1",
       :body=>
        "<p>Create product launches, service pages, SEO pages, comparison pages, and paid campaign pages with reusable components. Export the approved page as static HTML for the destination you control.</p>",
       :actions=>
        [{:label=>"Start a campaign page",
          :url=>"/pages/new",
          :style=>"primary",
          :position=>1},
         {:label=>"Connect the API",
          :url=>"/docs/api-connection",
          :style=>"secondary",
          :position=>2}]},
      {:type_of=>"cards",
       :order=>3,
       :header=>"Use the right component for each marketing job",
       :cards_per_row=>3,
       :body=>
        "<p>Mix visual cards, media rows, sliders, testimonials, pricing, and FAQs without hand-coding each campaign page.</p>",
       :rows=>
        [{:type_of=>"card",
          :card_variant=>"grid",
          :header=>"SEO pages",
          :body=>
           "<p>Target service, category, comparison, and location searches with focused static landing pages.</p>"},
         {:type_of=>"card",
          :card_variant=>"grid",
          :header=>"Campaign pages",
          :body=>
           "<p>Launch offers with hero messaging, benefit grids, proof, and a direct CTA.</p>"},
         {:type_of=>"card",
          :card_variant=>"grid",
          :header=>"Export pages",
          :body=>
           "<p>Ship finished pages to S3, your existing website, a CDN, or a webhook receiver.</p>"}]},
      {:type_of=>"slider",
       :slider_variant=>"mixed",
       :slider_behavior=>"controls",
       :order=>4,
       :header=>"Why this works for lean marketing teams",
       :rows=>
        [{:type_of=>"testimonial",
          :header=>"Portable by default",
          :subheader=>"Campaign manager",
          :num_value=>5,
          :body=>"<p>The page can launch without waiting on app deploys.</p>"},
         {:type_of=>"testimonial",
          :header=>"Portable by default",
          :subheader=>"Founder-led team",
          :num_value=>5,
          :body=>
           "<p>We can generate a draft through the API and still polish it manually.</p>"},
         {:type_of=>"testimonial",
          :header=>"Portable by default",
          :subheader=>"Technical marketer",
          :num_value=>5,
          :body=>
           "<p>The static export lets our existing website stay the source of truth.</p>"}]},
      {:type_of=>"pricing",
       :order=>5,
       :header=>"Pick the page workflow",
       :body=>
        "<p>Use the same builder for quick human-authored pages, API-generated pages, and webhook-published static exports.</p>",
       :rows=>
        [{:type_of=>"offer",
          :header=>"Manual",
          :subheader=>"Editor-built",
          :body=>"<p>Best for one-off campaign pages and fast revisions.</p>",
          :actions=>
           [{:label=>"Use this flow",
             :url=>"/docs/api-connection",
             :style=>"primary",
             :position=>1}]},
         {:type_of=>"offer",
          :header=>"API",
          :subheader=>"Generated",
          :body=>
           "<p>Best when an AI client or internal tool creates structured page drafts.</p>",
          :actions=>
           [{:label=>"Use this flow",
             :url=>"/docs/api-connection",
             :style=>"primary",
             :position=>1}]},
         {:type_of=>"offer",
          :header=>"Webhook",
          :subheader=>"Published",
          :body=>
           "<p>Best when exports need to land in S3, your website, or a deployment pipeline.</p>",
          :actions=>
           [{:label=>"Use this flow",
             :url=>"/docs/webhook-setup",
             :style=>"primary",
             :position=>1}]}]},
      {:type_of=>"cards",
       :order=>6,
       :header=>"Marketing page questions",
       :cards_per_row=>2,
       :rows=>
        [{:type_of=>"collapsible",
          :header=>"Can I target SEO terms with these pages?",
          :collapsed_status=>"open",
          :body=>
           "<p>Yes. Create focused pages with slug, meta title, meta description, structured sections, and static HTML output.</p>"},
         {:type_of=>"collapsible",
          :header=>"Can I use AI to create pages?",
          :collapsed_status=>"closed",
          :body=>
           "<p>Yes. API tokens authenticate clients, the schema tells them which fields to use, and blueprints create pages with sections and rows.</p>"},
         {:type_of=>"collapsible",
          :header=>"Can I export to an existing site?",
          :collapsed_status=>"closed",
          :body=>
           "<p>Yes. Use a signed webhook receiver to write the exported HTML into your site or deployment system.</p>"},
         {:type_of=>"collapsible",
          :header=>"Can I create more than card grids?",
          :collapsed_status=>"closed",
          :body=>
           "<p>Yes. Use split panes, image sliders, testimonial sliders, pricing offers, collapsibles, navbars, and custom row layouts.</p>"}]}]}]}

Bootstrap styling fields

Treat Bootstrap 5.3.8 classes as the primary styling language for generated pages. The model fields are designed to let AI clients improve layout, spacing, contrast, card depth, text hierarchy, and image framing without custom templates.

  • When exporting body only, StaticPagesApi does not include the head, CSS, or JavaScript. The receiving site must load Bootstrap 5.3.8 CSS and bootstrap.bundle.min.js in its head/layout before rendering the imported body fragment.
  • Use section_classes for section-level bands and spacing such as py-5, bg-light, bg-white, bg-primary-subtle, text-center, gap-3, and align-items-stretch.
  • Prefer roomier vertical rhythm for marketing pages: add generous top and bottom padding to each non-navbar section with section_classes. For hero/opening sections, combine classes such as min-vh-100, d-flex, align-items-center, py-5, and a background class so the first content section fills more of the laptop viewport. For later sections, use py-5 plus any available roomy custom class such as static-home-section-roomy when the stylesheet defines it.
  • Use row_classes for card shells and repeated components such as border-0, rounded-4, shadow-sm, bg-white, d-flex, and mb-3. Use h-100 only when each card is a separate grid item in a cards/pricing layout; never use it on multiple siblings stacked vertically inside one rows_in_columns column.
  • Use header_classes, subheader_classes, and body_classes for typography and spacing such as fw-bold, lead, text-body-secondary, text-center, mx-auto, mb-3, mb-4, px-4, and pb-4.
  • Use image_styles for consistent media framing such as width:100%; height:300px; object-fit:contain; background:#f8f9fa; padding:1rem;
  • Avoid layouts that rely on elements overlapping to look correct. If you add badges, divider bars, floating panels, or decorative accents, give the surrounding section enough padding or min-height so they never cover headings, cards, CTA buttons, or neighboring sections.
  • Be careful with h-100 and overflow-hidden on cards. Use them only when the card body is known to fit; for AI-generated copy or templated rows with variable body lengths, prefer natural height, overflow-visible behavior, and enough grid gap so content is not clipped.
  • After selecting Bootstrap classes, verify the target partial actually applies that field. If a row type ignores row_classes or image_styles, choose a different row type or update the partial before relying on those fields.

Media-aware component choice

Before finalizing a blueprint, match each available asset to a component that renders it visibly. If an app has screenshots, logos, badges, avatars, or product photos, the page should use them in image-capable sections or rows.

  • Use hero or split_pane when a primary screenshot or product image should anchor the first viewport.
  • Use card rows with image-capable variants, image rows, testimonial, split, or slider image rows when each repeated item needs its own image.
  • Avoid collapsible, navbar_link, and offer for image-led content unless the visual is intentionally omitted; those components are mainly text or structure.
  • If the first selected component does not render image_desktop or image_mobile in its partial, switch to a component that does before creating or updating the page.
  • Prefer attached images over hand-built visual mockups when real product screenshots are available.

Compatibility rules

Some section types expect specific row types. Following these rules avoids content that saves correctly but renders awkwardly.

  • pricing sections should only contain offer rows.
  • navbar sections should only contain navbar_link rows.
  • slider sections with slider_variant image should contain image rows with image_variant slider_item.
  • slider sections with slider_variant split_rows render split rows as slides.
  • rows_in_columns sections work best with text, card, image, split, collapsible, or raw_html rows.
  • text, hero, split_pane, and raw_html sections can stand alone without rows.
  • When a row or section has attached media, verify the chosen type renders that attachment in the corresponding partial.
  • When creating slider_variant split_rows, attach an image to every split row. Use placeholder images if production screenshots or photos are not ready.
  • When a card needs visual polish, verify the chosen row partial applies row_classes, body_classes, and image_styles before storing Bootstrap classes there.

Incremental editing endpoints

After a blueprint is created, AI clients can inspect and revise individual records instead of rebuilding the whole page.

  • GET /api/v1/pages/:page_id/sections lists sections placed on a page.
  • GET /api/v1/templates lists Liquid templates owned by the organization and safe to attach/edit.
  • GET /api/v1/template_library lists default templates an AI can browse before creating new custom templates.
  • POST /api/v1/template_library/:id/copy copies a default template into the token organization as an editable copy.
  • POST /api/v1/templates creates a custom Liquid section or row template.
  • Choose one renderer per section or row: use type_of for built-in renderers, or template_id plus template_data for Liquid-backed renderers. Do not send both.
  • If a section uses a Liquid template_id, every row inside that section must also use a Liquid row template_id. Do not mix built-in row type_of values inside Liquid-backed sections.
  • POST /api/v1/pages/:page_id/sections creates one section on an existing page.
  • PATCH /api/v1/pages/:page_id/sections/reorder with section_ids changes page section order.
  • GET /api/v1/sections/:section_id returns one section.
  • PATCH /api/v1/sections/:section_id updates the Section record everywhere it is used. page_id only changes placement context in the response; it does not scope content edits to one page.
  • PATCH /api/v1/pages/:page_id/sections/reorder updates only that page’s placement order.
  • DELETE /api/v1/sections/:section_id with page_id removes that page placement when other placements remain. Deleting the final placement deletes the Section and its child rows.
  • GET /api/v1/sections/:section_id/rows lists rows in a section.
  • POST /api/v1/sections/:section_id/rows creates one row.
  • PATCH /api/v1/sections/:section_id/rows/reorder with row_ids changes row order. Include row_group when reordering one group.
  • GET, PATCH, and DELETE /api/v1/rows/:row_id inspect, update, or delete one row.

Trial-aware behavior

A customer AI should conserve trial usage and explain limits clearly when it hits them.

  • Seven-day trial organizations can create three pages and export as many times as needed before subscribing.
  • Do not create multiple test pages to explore layouts. Create one draft page, revise it, and use templates/sections/rows to iterate.
  • Exports are unlimited during trial, but use page preview/editing for visual iteration when possible.
  • If the API returns HTTP 402 Active subscription or trial required, the trial is expired or billing is inactive.
  • If the API returns HTTP 402 Trial page limit reached, stop creating pages and ask the user whether to subscribe, reuse an existing page, or delete a draft.
  • When the user is ready for production publishing, direct them to the subscription plans so the page creation limit no longer applies.

Prompt contract for customer AIs

Customers can give this instruction block to an AI builder connected to the API.

You are generating landing pages for this website. First call GET /api/v1/schema and use only the returned fields and enum values. Also call GET /api/v1/templates for templates already owned by the organization and GET /api/v1/template_library for default templates. Reuse owned templates when they fit; copy close default templates with POST /api/v1/template_library/:id/copy before customizing them; create new templates through POST /api/v1/templates only when no owned or default template fits. For a general page request, build a mixed composition: use built-in section and row type_of values for the reliable page skeleton, then use owned or copied Liquid-backed sections/rows for 1-3 distinctive visual moments such as the hero, proof band, metrics band, feature showcase, or custom card row. End generated landing pages and marketing pages with an actual Footer Liquid section template from owned templates or the default template library, preferably a copied section_footer_columns, section_footer_newsletter, or section_footer_directory template when available. Choose one renderer per section or row: use type_of for built-in renderers, or template_id plus template_data for Liquid-backed renderers, never both. If a section uses a Liquid template_id, every row inside that section must also use a Liquid row template_id; do not mix built-in row type_of values inside Liquid-backed sections. Do not make the whole page built-in when templates can improve the first draft, and do not make the whole page Liquid-backed because humans still need predictable editable structure. Build pages as JSON blueprints with pages at the top level, ordered sections inside sections, and ordered rows inside sections when repeated components are needed. Prefer built-in section and row type_of values over raw_html. When a Figma mockup or reference page needs a custom layout, use database-backed Liquid templates, declare editable values in field_schema, and attach those templates to sections or rows with template_id and template_data. Do not create one giant page-sized HTML blob. Use a variety of compatible section and row types; do not make every page the same hero/cards/text pattern. Consider hero, split_pane, rows_in_columns, cards, slider, pricing, navbar, card, image, testimonial, offer, collapsible, split, navbar_link, and raw_html where the content fits. Use slider settings intentionally: slider_variant mixed, cards, image, or split_rows controls the slide content; slider_behavior auto creates an animated compact track, while controls creates full-width slides with left/right buttons. Use slider_variant cards only with card rows and any supported card_variant; use slider_variant split_rows only with split rows that each have a real or placeholder image_desktop attachment; use slider_behavior auto for compact card/testimonial carousels; use slider_behavior controls with split_rows or other full-slide content, not compact card or testimonial rows; do not put Bootstrap col-* width classes in slider row_classes because the slider item owns row/card width; do not use vertical auto-margin classes such as mt-auto, mb-auto, my-auto, or m-auto inside compact card/testimonial sliders because they create large empty gaps inside cards. card_variant grid, icon, header_body, inline_image, feature_text, numbered_step, or centered_top_icon; image_variant text, slider_item, or caption. Use Bootstrap utility classes in section_classes, row_classes, header_classes, body_classes, subheader_classes, and image_styles to make sections and cards polished; verify the selected partial applies those fields before relying on them. Keep heading scale restrained: target clamp(2rem, 3.25vw, 3rem) for H1 and clamp(1.75rem, 3vw, 2.25rem) for primary H2 section headings. Do not use Bootstrap display-1 through display-4; use page-specific copied-template settings or classes rather than shrinking shared default templates. Add generous vertical spacing between non-navbar sections through section_classes. For the first hero/content section, use section_classes such as min-vh-100 d-flex align-items-center py-5 with an appropriate background class when the page needs a strong laptop-screen opening; for later sections, use py-5 plus any available roomy custom class such as static-home-section-roomy when defined. Do not overlap sections, cards, badges, dividers, media, or text on top of each other. Reserve enough padding, margin, grid gap, and min-height for every decorative or positioned element, and avoid negative margins or absolute positioning unless the parent creates clear space for it at desktop and mobile widths. Do not let cards or rows get chopped off: avoid fixed heights, hidden overflow, and cramped rows_in_columns settings that stack too much content in narrow columns; let cards grow naturally and use overflow: visible when content length varies. When real screenshots, product photos, badges, logos, or avatars are available, choose section and row types that visibly render attached media; do not put images on components whose partials ignore them. When a media-backed component is required but final imagery is unavailable, attach a placeholder image instead of leaving the media pane empty. Keep new pages draft unless the user explicitly asks to publish. Respect trial limits: trial organizations last seven days, can create three pages, and can export as many times as needed. Prefer editing existing draft pages during trial experimentation. If an API call returns HTTP 402, explain that the organization needs an active trial/subscription or has reached a trial limit, then ask whether to subscribe or reuse existing content. Do not send organization_id because the API token scopes every request to the correct organization.