Architecture

Content API Contracts for AI App Builders

Jay Callicott··7 min read

AI app builders are getting much better at connecting to real systems. Lovable can wire Supabase tables, auth, storage, and edge functions through chat. Bolt.new now defaults new Claude Agent projects to Bolt Database, with Supabase as an alternative. Base44 separates app integrations, workspace OpenAPI integrations, and account-level MCP connections for builder context.

That is useful progress, but it does not remove the biggest production risk in AI-built sites: the frontend changes faster than the content model.

When a generated app hardcodes page copy, pricing cards, FAQs, feature grids, or hero blocks in components, every content change becomes a code change. When an AI agent invents a new JSON shape every time you ask for a page variation, your app becomes fragile in a different way. The fix is a content API contract: a stable agreement between the visual frontend and the system that owns editable content.

If you are evaluating a headless CMS for AI apps, a Lovable backend, a bolt.new CMS, or an MCP CMS, this contract matters more than the specific builder you start with.

What a content API contract is

A content API contract defines the shape, meaning, and lifecycle of content that your app can render. It is not just an endpoint. It answers practical questions:

  • What fields does a landing page expose?
  • Which fields are required, optional, localized, or repeatable?
  • What block types can appear in a page builder region?
  • What image sizes and alt text are expected?
  • How does preview differ from published content?
  • What happens when a field is renamed or deprecated?

For an AI-generated app, the contract is the boundary that keeps the generated UI from becoming the content database.

type LandingPage = {
  slug: string
  title: string
  seo: {
    title: string
    description: string
  }
  hero: {
    headline: string
    subhead?: string
    image?: ImageAsset
    cta?: LinkAction
  }
  sections: PageSection[]
}

type PageSection =
  | FeatureGridSection
  | TestimonialSection
  | FaqSection
  | RichTextSection

This gives the AI builder a clear job: render the contract well. It gives the CMS a separate job: manage the content safely.

Why AI-built apps drift without contracts

AI app builders optimize for immediate implementation. That is exactly what makes them powerful. Ask for a homepage and you get a homepage. Ask for a pricing page and you get a pricing page. Ask for a case study template and the agent often creates whatever shape feels convenient in that moment.

That speed creates drift:

  • A features array in one component uses title, description, and icon.
  • Another page uses name, body, and emoji.
  • FAQs are copied into three files with slightly different field names.
  • The pricing table has business logic mixed with marketing copy.
  • Images have no consistent alt text or focal point metadata.

This is not a Lovable, Bolt.new, Base44, or v0 problem. It is a boundary problem. Databases, API connectors, and MCP tools can help wire systems together, but they do not automatically define the editorial model your team needs.

A visual editor headless CMS solves the ownership problem only if the frontend agrees to consume predictable content shapes. Otherwise marketers get a UI, but developers still get brittle rendering logic.

Design the contract around editable decisions

The common mistake is modeling content around the current component tree. That makes the CMS mirror a single generated implementation. A better approach is to model around decisions the content team actually needs to make.

For a campaign landing page, the editable decisions might be:

  • Page URL and SEO metadata
  • Hero message, image, and call to action
  • Reusable proof points
  • Feature or benefit sections
  • Customer quotes
  • FAQ entries
  • Form embed or conversion target

The frontend can still choose the visual treatment. The CMS should not need to know whether the feature grid uses cards, tabs, or an accordion unless that choice is truly editorial.

Here is a simple GraphQL query shape that works well for generated React or Next.js apps:

query LandingPageBySlug($slug: String!) {
  landingPage(slug: $slug) {
    slug
    title
    seoTitle
    seoDescription
    hero {
      headline
      subhead
      image {
        url
        alt
        width
        height
      }
      cta {
        label
        href
      }
    }
    sections {
      __typename
      ... on FeatureGrid {
        heading
        items {
          title
          body
        }
      }
      ... on FaqList {
        heading
        questions {
          question
          answer
        }
      }
    }
  }
}

The contract is explicit enough for TypeScript, preview, and testing, but flexible enough for the AI builder to generate multiple layouts.

Use MCP for setup, not as the runtime contract

Model Context Protocol is becoming the standard way for AI assistants to use external tools and data. The current MCP spec supports stdio and Streamable HTTP transports, and AI builders are adopting MCP-style connectors to pull in docs, issues, project context, and third-party tools.

That is excellent for setup. An agent can use MCP tools to create a CMS space, define content types, import example content, generate API credentials, and scaffold frontend integration code.

But your production app should usually read content through GraphQL, JSON:API, or another stable delivery API, not through the builder chat context.

The split is cleaner:

  • MCP tools: provisioning, schema creation, content import, environment setup, agent workflows
  • CMS APIs: runtime content delivery, preview, published reads, cache invalidation
  • Visual editor: marketer-owned page composition and content changes
  • Frontend code: rendering, routing, analytics, personalization, and product logic

Decoupled.io follows this pattern. MCP tools help AI assistants work with the CMS, while GraphQL, JSON:API, and the visual editor provide the durable content surface your app uses after deployment.

Make the generated frontend defensive

AI-generated components often assume perfect data because their first version was built from mock content. A CMS-backed app needs stricter rendering habits.

At minimum, generated code should handle:

  • Missing optional fields
  • Empty repeatable sections
  • Images without focal point data
  • Unknown section types
  • Draft content in preview mode
  • Published content in cached mode

A practical fetch wrapper can enforce the basics:

export async function getLandingPage(slug: string) {
  const res = await fetch(process.env.CMS_GRAPHQL_URL!, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.CMS_API_TOKEN}`,
    },
    body: JSON.stringify({
      query: LANDING_PAGE_QUERY,
      variables: { slug },
    }),
    next: { revalidate: 60 },
  })

  if (!res.ok) {
    throw new Error(`CMS request failed: ${res.status}`)
  }

  const json = await res.json()

  if (!json.data?.landingPage) {
    return null
  }

  return json.data.landingPage as LandingPage
}

Then the page can decide what to do with missing content:

export default async function Page({ params }: { params: { slug: string } }) {
  const page = await getLandingPage(params.slug)

  if (!page) {
    notFound()
  }

  return <LandingPageRenderer page={page} />
}

This is boring code, which is the point. The contract should make content delivery predictable so the creative work can happen in layout, messaging, and experimentation.

Version your contract before AI multiplies it

Once a team starts using AI builders heavily, one app becomes five. Marketing launches microsites. Product creates onboarding flows. Sales asks for calculators. Support wants a knowledge base. If every generated app invents its own content contract, you get integration sprawl quickly.

Version the important parts early:

  • Treat content type names as public API names.
  • Add new fields before removing old fields.
  • Keep deprecated fields available until deployed clients stop using them.
  • Generate TypeScript types from the live schema when possible.
  • Add simple contract tests for important pages and blocks.
  • Document which fields are editorial and which are implementation details.

This is where decoupled Drupal is useful. Drupal has mature structured content, revisions, permissions, and API delivery. Decoupled.io packages that into a managed headless CMS, so teams can keep the editorial model stable while Lovable, Bolt.new, Base44, v0, or a hand-written Next.js app iterate on the frontend.

How to prompt an AI builder for the right boundary

The prompt matters. Do not ask the builder to "add a CMS" as a vague feature. Tell it to implement a specific content contract and keep content outside the component tree.

Use a prompt like this:

Connect this app to a headless CMS using the provided GraphQL endpoint.
Do not hardcode editable copy, page sections, images, FAQs, testimonials, or CTAs.
Create TypeScript types for the LandingPage contract.
Render known section types defensively.
If a section type is unknown, skip it and log the type in development.
Use preview mode only when CMS_PREVIEW_TOKEN is present.
Keep product state and user data separate from CMS content.

That gives the agent an architecture, not just a task. It also makes review easier because you can inspect whether the generated code respected the boundary.

CTA

AI app builders are strongest when they generate interfaces against a stable contract. They are weakest when the app itself becomes the content system.

If you are building with Lovable, Bolt.new, Base44, v0, or Replit Agent, start by defining the content API your frontend will consume. Then let the CMS own drafts, previews, media, page composition, and editorial permissions.

Decoupled.io is built for that split: managed Drupal content, visual page building, MCP tooling for AI workflows, and runtime delivery through GraphQL or JSON:API. Start with AI builder integration, then review MCP tools and getting started when you are ready to connect a generated app to real content.