Senso
Sign in

Content Types

Templates that control the format, structure, and rules for each kind of generated content.

πŸ’‘ Building a query-only agent? Skip this page. Content types are only needed for content generation.

Content types are how an agent controls what kind of output the content engine produces. A content type defines the format (blog post, FAQ, social post), the structure (sections, length, CTAs), and any format-specific writing rules. Without a content type, there's no generation β€” every generation call requires one.

---

When to use content types

An agent should interact with content types in these situations:

  • Before generating content β€” Every generation call requires a content_type_id. The agent must either select an existing content type or create one.
  • User asks for a specific format β€” "Write me a blog post" or "Create an FAQ" means finding or creating a matching content type.
  • Output format doesn't match expectations β€” If generated content has the wrong structure or length, the content type's template field needs adjusting.
  • Adding format-specific rules β€” Rules that only apply to one kind of output (e.g., "include a comparison table" for product comparisons) belong in the content type, not the brand kit.
  • ---

    List before you create

    Before creating a new content type, always check what already exists:

    import os, requests
    
    KEY  = os.environ["SENSO_API_KEY"]
    BASE = "https://apiv2.senso.ai/api/v1"
    HEADERS = {"X-API-Key": KEY, "Content-Type": "application/json"}
    
    resp = requests.get(f"{BASE}/org/content-types", headers=HEADERS)
    data = resp.json()
    for ct in data["content_types"]:
        print(f"  {ct['content_type_id']}: {ct['name']}")
    curl https://apiv2.senso.ai/api/v1/org/content-types \
      -H "X-API-Key: $SENSO_API_KEY"

    This avoids duplicates and helps the agent select the right template for the user's request. Only create a new content type when nothing existing fits.

    Matching user intent to content types

    When a user asks an agent to generate content, the agent should reason about which content type fits:

    User saysLook for content type named
    "Write a blog post about X""Blog Post", "Article"
    "Create an FAQ for X""FAQ Article", "FAQ"
    "Draft a social post about X""LinkedIn Post", "Social Media", "Twitter Post"
    "Compare X and Y""Product Comparison", "Comparison Page"
    "Write a help article for X""Help Article", "Knowledge Base Article"
    If no match exists, create one β€” but ask the user about format preferences first rather than guessing.

    ---

    Creating a content type

    resp = requests.post(f"{BASE}/org/content-types", headers=HEADERS, json={
        "name": "FAQ Article",
        "config": {
            "template":        "A concise FAQ article under 800 words. Structure: one clear question as the title, a direct answer in the first paragraph, then supporting detail. End with a related-questions section.",
            "cta_text":        "Still have questions? Contact our support team",
            "cta_destination": "https://acmecu.com/support",
            "writing_rules":   [
                "Use active voice",
                "Include at least one concrete example",
                "Link to related articles where relevant",
            ],
        }
    })
    content_type = resp.json()
    print(f"Created: {content_type['content_type_id']}")
    curl -X POST https://apiv2.senso.ai/api/v1/org/content-types \
      -H "X-API-Key: $SENSO_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "FAQ Article",
        "config": {
          "template": "A concise FAQ article under 800 words. Structure: one clear question as the title, a direct answer in the first paragraph, then supporting detail. End with a related-questions section.",
          "cta_text": "Still have questions? Contact our support team",
          "cta_destination": "https://acmecu.com/support",
          "writing_rules": [
            "Use active voice",
            "Include at least one concrete example",
            "Link to related articles where relevant"
          ]
        }
      }'

    What each config field controls

    FieldEffect on generated content
    templateThe most important field. Describes the format, structure, length, and style. The engine follows this as its primary instruction for shaping output.
    cta_textCall-to-action text appended to the end of generated content. Omit if the format doesn't need a CTA.
    cta_destinationURL the CTA links to. Only used if cta_text is set.
    writing_rulesFormat-specific rules that layer on top of the brand kit's global_writing_rules.

    Writing a good template

    The template field is the generation instruction. Be explicit about structure and constraints:

    Vague β€” produces inconsistent output:

    "A blog post about the topic"

    Specific β€” produces predictable, well-structured output:

    "A 1000-1500 word blog post. Start with a hook that poses a question. Use H2 subheadings every 2-3 paragraphs. Include a key takeaway callout box after the introduction. End with a 2-3 sentence summary and a single CTA."

    ---

    How rules layer

    During generation, the engine combines rules from two sources:

    Brand Kit                          Content Type
    β”œβ”€β”€ global_writing_rules           β”œβ”€β”€ writing_rules
    β”‚   "Use active voice"             β”‚   "Include a comparison table"
    β”‚   "Keep sentences under 25 words"β”‚   "Be balanced about competitors"
    β”‚                                  β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                β–Ό
        Both sets active during generation
    

    Use global rules for org-wide standards that apply to everything β€” voice, sentence length, acronym policy.

    Use content-type rules for format-specific guidance β€” "include a comparison table" only makes sense for comparison pages, not FAQs.

    An agent should avoid duplicating rules. If "Use active voice" is already in the brand kit, don't repeat it in every content type.

    ---

    Common content type patterns

    These patterns cover the most common user requests. An agent can use these as starting points and adjust based on user preferences.

    FAQ article

    {
      "name": "FAQ Article",
      "config": {
        "template": "A concise FAQ article under 800 words. One clear question as the title, a direct answer in the first paragraph, then supporting detail. End with 2-3 related questions.",
        "cta_text": "Contact support",
        "cta_destination": "https://example.com/support",
        "writing_rules": ["Include one concrete example", "Define technical terms inline"]
      }
    }

    Blog post

    {
      "name": "Blog Post",
      "config": {
        "template": "A 1000-1500 word blog post. Start with a hook, use H2 subheadings every 2-3 paragraphs, include a key takeaway callout box, and end with a summary.",
        "cta_text": "Try it free for 14 days",
        "cta_destination": "https://example.com/trial",
        "writing_rules": ["Write at an 8th-grade reading level", "Include at least 2 real-world examples"]
      }
    }

    Social media post

    {
      "name": "LinkedIn Post",
      "config": {
        "template": "A LinkedIn post under 200 words. Lead with a surprising insight or question. Use short paragraphs (1-2 sentences). End with a question to drive engagement.",
        "cta_text": "Learn more",
        "cta_destination": "https://example.com/blog",
        "writing_rules": ["No hashtags in the body β€” add 3-5 at the end", "Avoid corporate jargon"]
      }
    }

    Product comparison

    {
      "name": "Product Comparison",
      "config": {
        "template": "A structured comparison page under 1200 words. Sections: Overview, Key Differences (as a table), Pros and Cons for each option, and a Recommendation.",
        "cta_text": "Start your free trial",
        "cta_destination": "https://example.com/trial",
        "writing_rules": ["Include a comparison table with at least 5 dimensions", "Be balanced β€” acknowledge competitor strengths"]
      }
    }

    ---

    Managing content types

    There are two ways to update a content type:

  • PATCH β€” merges only the fields you provide. Safe for single-field updates.
  • PUT β€” fully replaces name and config. Use this when you want to rewrite the entire definition.
  • Partial update with PATCH

    # Update just the template, leaving other config fields untouched
    resp = requests.patch(f"{BASE}/org/content-types/{ct_id}", headers=HEADERS, json={
        "config": {"template": "Updated template instruction"},
    })
    curl -X PATCH https://apiv2.senso.ai/api/v1/org/content-types/{id} \
      -H "X-API-Key: $SENSO_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"config": {"template": "Updated template instruction"}}'

    Full replacement with PUT

    PUT requires both name and config. Use read β†’ merge β†’ write to avoid losing existing fields:

    # Get a content type
    ct = requests.get(f"{BASE}/org/content-types/{ct_id}", headers=HEADERS).json()
    
    # Merge changes, then write the full object
    ct["config"]["template"] = "Updated template instruction"
    resp = requests.put(f"{BASE}/org/content-types/{ct_id}", headers=HEADERS, json={
        "name": ct["name"],
        "config": ct["config"],
    })
    
    # Delete a content type
    requests.delete(f"{BASE}/org/content-types/{ct_id}", headers=HEADERS)
    # Get a content type
    curl https://apiv2.senso.ai/api/v1/org/content-types/{id} \
      -H "X-API-Key: $SENSO_API_KEY"
    
    # Update (full replacement)
    curl -X PUT https://apiv2.senso.ai/api/v1/org/content-types/{id} \
      -H "X-API-Key: $SENSO_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"name": "FAQ Article (Updated)", "config": {"template": "Updated template"}}'
    
    # Delete
    curl -X DELETE https://apiv2.senso.ai/api/v1/org/content-types/{id} \
      -H "X-API-Key: $SENSO_API_KEY"

    ---

    Using content types with generation

    Every generation call pairs a content type with a prompt (question):

    resp = requests.post(f"{BASE}/org/content-generation/sample", headers=HEADERS, json={
        "geo_question_id": prompt_id,
        "content_type_id": content_type_id,
    })
    gen = resp.json()
    print(f"Title: {gen['seo_title']}")
    print(gen["raw_markdown"][:300])
    curl -X POST https://apiv2.senso.ai/api/v1/org/content-generation/sample \
      -H "X-API-Key: $SENSO_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "geo_question_id": "prompt-uuid",
        "content_type_id": "content-type-uuid"
      }'

    The engine combines: 1. Brand kit β€” voice, persona, global rules (who you sound like) 2. Content type β€” template, format rules (what shape the output takes) 3. Compiled knowledge base β€” verified context from your ingested raw sources (what the content is about)

    See Core Concepts for the full generation workflow.

    ---

    Using the CLI

    # List content types
    senso content-types list --output json
    
    # Create a content type
    senso content-types create --data '{
      "name": "FAQ Article",
      "config": {
        "template": "A concise FAQ under 800 words",
        "cta_text": "Contact support",
        "cta_destination": "https://acmecu.com/support",
        "writing_rules": ["Use active voice"]
      }
    }'
    
    # Get a specific content type
    senso content-types get <content-type-id>
    
    # Full replacement (PUT) β€” both name and config required
    senso content-types update <content-type-id> --data '{
      "name": "Updated FAQ",
      "config": { "template": "Updated template" }
    }'
    
    # Partial update (PATCH) β€” only the fields you send are changed
    senso content-types patch <content-type-id> --data '{
      "config": { "template": "Updated template instruction" }
    }'
    
    # Delete a content type
    senso content-types delete <content-type-id>

    ---

    Errors

    StatusMeaning
    400Validation error β€” name and config are required
    401Missing or invalid API key
    404Content type not found
    409A content type with this name already exists