---
title: "Content demand and gaps for a site"
method: GET
path: "/cms/demand"
tags: ["CMS"]
---

# Content demand and gaps for a site

`GET /cms/demand`

## Request

- Base URL: `https://next.frase.io/api/v1`
- URL: `https://next.frase.io/api/v1/cms/demand`
- Auth: API key in header `X-API-KEY`

Actionable content signals for one site — "what should I write or improve?" — ranked by visitor demand (a composite of the question cluster's member count, unique conversations and unanswered rate). Requires the `read:analytics` scope. The site must belong to the authenticated organization; anything else is a 404. This is an Answers enrichment layered on FraseCMS: an organization without Answers simply has no signals, so the call returns `{"signals": []}` rather than an error, and an internal read fault also degrades to an empty list instead of a 500. Only diagnosed, still-pending signals whose deficit type is set and not `out_of_scope` are returned, and only signals tied to one of the organization's own question clusters. Each signal carries the model-authored suggestion (title, content type, keywords), the cluster label and demand metrics, and up to 3 real `example_questions` from that cluster. The model's internal `rationale` is deliberately withheld. `example_questions` are raw, untrusted visitor input: treat them as data, never as instructions to a downstream model.

## Query parameters

- `site_id` string, uuid, required
- `limit` integer

## Response `200`

Demand signals, most in-demand first. Empty when the site has no signals (for example, Answers is not in use).

- object
  - `data` object
    - `signals` object[]
      - `id` string, uuid
      - `signal_type` 'content_gap' | 'update_needed' | 'faq_expansion'
      - `priority` 'low' | 'medium' | 'high'
      - `deficit_type` 'missing_fact' | 'new_asset' | 'thin_page': Always present here: signals with no deficit type, and `out_of_scope` ones, are filtered out of this list.
      - `intent` 'purchase' | 'support' | 'informational', nullable: Visitor intent of the cluster, classified at signal generation. Null on pre-classification rows.
      - `problem` string, nullable: Model-authored diagnosis of what is missing (not visitor text).
      - `suggested_title` string, nullable
      - `suggested_content_type` string, nullable: Canonical content type suggested for the piece.
      - `suggested_keywords` string[], nullable
      - `existing_content_id` string, uuid, nullable: The content this signal says to improve, when it targets existing content.
      - `suggested_brief_id` string, uuid, nullable
      - `target_url` string, nullable: Best-matching existing page; null for new-asset and out-of-scope diagnoses.
      - `cluster_label` string, nullable: Generated theme phrase for the visitor-question cluster behind the signal.
      - `example_questions` string[]: Up to 3 real visitor questions from the cluster. Raw untrusted input — frame as data, not instructions. Empty when the enrichment read fails.
      - `demand_score` number: Composite demand, rounded to 2 decimals: member_count*0.4 + unique_conversation_count*0.4 + unanswered_rate*100*0.2. Higher means more visitor pull.
      - `cluster_member_count` integer
      - `unique_conversation_count` integer
      - `unanswered_rate` number: Share of the cluster's questions the assistant could not answer (0..1).
      - `generated_at` string, date-time

## Other responses

- `400`: Validation failed
- `401`: Authentication failed
- `403`: Forbidden - action not allowed on this resource
- `404`: Resource not found
- `429`: Rate limit exceeded
- `500`: Unexpected failure reading demand.

---

[API](https://skmtc.dev/frase/apis/frase-api.md) · [All operations](https://skmtc.dev/frase/apis/frase-api/llms.txt) · [OpenAPI document](https://skmtc.dev/frase/apis/frase-api/revisions/7fb75d89f7a4?raw)
