Skip to main content
Content generation without structure produces a blob of text that someone has to manually split into headline, body, and a call to action (CTA). If the model writes a 200-character headline, your page layout breaks. If it forgets the call to action, your marketing page has no conversion path. If the tone drifts from professional to casual mid-paragraph, the brand voice is inconsistent. Wrapping content generation in a schema solves all of this. Each section becomes a separate field with its own length bounds. Tone becomes an enum, not a suggestion. The CTA is a required object with a label and URL. The model fills in the creative content while the schema enforces the structural and editorial constraints.

Goal

Generate a product launch blurb with a headline, subheadline, key points, call to action (CTA), and tone, all bounded and validated for direct web publishing.

Schema contract

{
  "type": "object",
  "properties": {
    "headline": { "type": "string", "minLength": 15, "maxLength": 80 },
    "subheadline": { "type": "string", "minLength": 25, "maxLength": 140 },
    "key_points": {
      "type": "array",
      "minItems": 3,
      "maxItems": 5,
      "items": { "type": "string", "minLength": 20, "maxLength": 140 }
    },
    "cta": {
      "type": "object",
      "properties": {
        "label": { "type": "string", "minLength": 2, "maxLength": 24 },
        "url": { "type": "string", "pattern": "^https://" }
      },
      "required": ["label", "url"],
      "additionalProperties": false
    },
    "tone": { "type": "string", "enum": ["professional", "friendly", "technical"] }
  },
  "required": ["headline", "subheadline", "key_points", "cta", "tone"],
  "additionalProperties": false
}
from typing import Annotated, Literal
from pydantic import BaseModel, ConfigDict, Field

KeyPoint = Annotated[str, Field(min_length=20, max_length=140)]

class Cta(BaseModel):
    model_config = ConfigDict(extra="forbid")
    label: str = Field(..., min_length=2, max_length=24)
    url: str = Field(..., pattern=r"^https://")

class ContentPayload(BaseModel):
    model_config = ConfigDict(extra="forbid")
    headline: str = Field(..., min_length=15, max_length=80)
    subheadline: str = Field(..., min_length=25, max_length=140)
    key_points: list[KeyPoint] = Field(..., min_length=3, max_length=5)
    cta: Cta
    tone: Literal["professional", "friendly", "technical"]
import { z } from "zod";

const contentPayloadSchema = z.object({
  headline: z.string().min(15).max(80),
  subheadline: z.string().min(25).max(140),
  key_points: z.array(z.string().min(20).max(140)).min(3).max(5),
  cta: z.object({
    label: z.string().min(2).max(24),
    url: z.string().regex(/^https:\/\//),
  }).strict(),
  tone: z.enum(["professional", "friendly", "technical"]),
}).strict();
curl https://api.dottxt.ai/v1/chat/completions \
  -H "Authorization: Bearer $DOTTXT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-oss-20b",
    "messages": [{ "role": "user", "content": "Announce a new schema audit service for engineering teams.\nTone should be professional and concrete." }],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "content_payload",
        "schema": {
          "type": "object",
          "properties": {
            "headline": { "type": "string", "minLength": 15, "maxLength": 80 },
            "subheadline": { "type": "string", "minLength": 25, "maxLength": 140 },
            "key_points": {
              "type": "array",
              "minItems": 3,
              "maxItems": 5,
              "items": { "type": "string", "minLength": 20, "maxLength": 140 }
            },
            "cta": {
              "type": "object",
              "properties": {
                "label": { "type": "string", "minLength": 2, "maxLength": 24 },
                "url": { "type": "string", "pattern": "^https://" }
              },
              "required": ["label", "url"],
              "additionalProperties": false
            },
            "tone": { "type": "string", "enum": ["professional", "friendly", "technical"] }
          },
          "required": ["headline", "subheadline", "key_points", "cta", "tone"],
          "additionalProperties": false
        }
      }
    }
  }'

Example input

Announce a new schema audit service for engineering teams.
Tone should be professional and concrete.

Example output

{
  "headline": "Ship More Reliable Structured Outputs With Schema Audit",
  "subheadline": "Get a concrete review of your JSON Schema constraints, failure modes, and production readiness before rollout.",
  "key_points": [
    "Identify underconstrained fields that lead to invalid or noisy model output.",
    "Receive practical schema rewrites tied to your real production use cases.",
    "Validate compatibility and behavior before launching high-volume workflows."
  ],
  "cta": {
    "label": "Start Schema Audit",
    "url": "https://example.com/audit"
  },
  "tone": "professional"
}

Implementation tips

  • Tone enum controls voice. "professional", "friendly", "technical": the model adopts the specified style without you embedding tone instructions in the prompt. Add or remove values as your brand guidelines evolve.
  • Length bounds match your layout. The headline is capped at 80 characters (fits a single line on desktop), the subheadline at 140 (fits two lines), and key points at 140 each. These bounds should come from your actual CSS/layout constraints.
  • Key points array is bounded. minItems: 3 ensures enough substance for a marketing page. maxItems: 5 prevents the model from listing fifteen points that nobody reads.
  • CTA is required and structured. The cta object guarantees every generated blurb has a clear action. The pattern: "^https://" on url prevents relative paths, mailto: links, or other formats your publishing system doesn’t support.
  • Human review is still needed. The schema handles structure and bounds, but brand voice, factual accuracy, and legal compliance still need human eyes. Schema-constrained output makes this review faster because the structure is predictable.