Frase Clusters API

Topic clusters that organize content and briefs by theme

GitHub OpenAPI

Operations 6

GET /clusters List topic clusters #
POST /clusters Create a topic cluster #
POST /clusters/suggest Suggest new topic clusters for a site #
GET /clusters/{id} Get a topic cluster #
POST /clusters/{id}/assign Assign content or a brief to a cluster #
POST /clusters/{id}/pillar Elect a cluster's pillar content #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/frase-io:frase-io-clusters-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

frase-io-clusters-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Frase Clusters API
  version: 1.0.0
  description: 'The Frase API provides programmatic access to your SEO content platform. Use it to manage content, research, audits, AI visibility tracking, and more. ## Authentication


    All API requests require authentication using an API key. Include your key in the `X-API-KEY` header:


    ```

    X-API-KEY: sk_live_your_api_key_here

    ```


    For legacy compatibility, the `token` header is also supported. ## Rate Limits


    Three independent limits apply, each with its own headers and error code. Headers are returned on `429` responses, not on successful ones. **Per-minute burst** - 60/min on trial and Starter, 120 Professional, 300 Scale, 1,000 Enterprise. Error code `rate_limit_exceeded`. - `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (epoch seconds)


    **Sustained hourly ceiling** - 2,000/hour trial, 3,000 Starter, 5,000 Professional, 15,000 Scale, 50,000 Enterprise. Error code `hourly_limit_exceeded`. - `X-RateLimit-Hourly-Limit`, `X-RateLimit-Hourly-Remaining`, `X-RateLimit-Hourly-Reset` (epoch seconds)


    **Monthly request budget** - error code `monthly_budget_exceeded`, headers `X-Monthly-Limit`, `X-Monthly-Used`, `X-Monthly-Remaining`. Every `429` carries `Retry-After` in seconds. On the hourly ceiling this can be up to 3,600, so clients should honour it rather than retrying on a short fixed backoff - a retry sooner than `Retry-After` cannot succeed. ## Errors


    The API uses standard HTTP status codes and returns errors in a consistent format:


    ```json

    {

    "error": {

    "type": "authentication_error",

    "code": "invalid_api_key",

    "message": "Invalid API key provided.",

    "doc_url": "https://docs.frase.io/api/errors#invalid_api_key"

    }

    }

    ```


    ## Changelog


    Breaking changes are listed here, newest first. "Breaking" means a field that was always present with a value can now be `null`, or an existing value now means something different — not that a field was removed or renamed. Additive fields are not listed. ### 2026-08-31 — Fact `sources[]` may omit `url`, and minted facts default to `approved`


    - `GET /briefs/{id}/facts`, `PATCH /briefs/{id}/facts/{factId}` → a `sources[]` entry may now carry only `title`, with no `url` or `domain`. Research names a publisher far more often than it captures a link (793 of 801 plan-evidence items in production), and those names were previously discarded rather than returned. Read `url` defensively. - `GET /briefs/{id}/facts` → `approval` on a newly minted fact is now `approved` rather than `pending`. A client polling for `pending` facts to review will see far fewer; rejection is unchanged and remains permanent. Facts the writer asserted in its own output (`provenance: generated`) are still minted `pending`. ### 2026-08-24 — Content Guard''s hasPublishedUrl is now recoverability, not the stored URL


    - `GET /content/{id}/monitoring` → `hasPublishedUrl`: was a judgment on the STORED URL (false for a relative path even when the page was live). It now answers "could Guard watch this article right now" — the stored URL when absolute, or an address the server recovers (CMS inventory; FraseCMS re-derivation from current settings when a live published post exists). `true` no longer implies the stored URL itself is absolute, and a FraseCMS article published before domain setup flips to `true` when setup finishes, with no re-publish. It remains exactly the condition enabling enforces. - `publishedUrlIssue` is now the RESIDUAL after recovery failed, not a raw-column classification — "not_absolute" can also mean the FraseCMS post is not live or FraseCMS is disabled for the site. - `POST /content/{id}/publish` → `url` (frase_cms): now derived from the site''s current CMS settings rather than the adapter''s host-of-the-moment value; the same URL is stored on the content row and sent in the content.published webhook.'
  contact:
    name: Frase Support
    url: https://frase.io
    email: support@frase.io
  license:
    name: Proprietary
    url: https://www.frase.io/company/terms
servers:
- url: https://next.frase.io/api/v1
  description: Production
- url: /api/v1
  description: This host
security:
- ApiKeyAuth: []
- BearerAuth: []
tags:
- name: Clusters
  description: Topic clusters that organize content and briefs by theme
paths:
  /clusters:
    get:
      operationId: listClusters
      summary: List topic clusters
      description: Retrieve the organization's topic clusters, optionally scoped to a site.
      tags:
      - Clusters
      security:
      - ApiKeyAuth: []
      parameters:
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: page_size
        in: query
        schema:
          type: integer
          default: 20
          maximum: 100
      - name: site_id
        in: query
        required: false
        description: Filter to a single site (org-wide clusters are always included)
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: List of clusters
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        name:
                          type: string
                        description:
                          type: string
                          nullable: true
                        color:
                          type: string
                          nullable: true
                        pillar_content_id:
                          type: string
                          format: uuid
                          nullable: true
                        pillar_title:
                          type: string
                          nullable: true
                        site_id:
                          type: string
                          format: uuid
                          nullable: true
                          description: The site this cluster is scoped to, or null for an org-wide cluster.
                        seed_keywords:
                          type: array
                          items:
                            type: string
                          nullable: true
                        content_count:
                          type: integer
                        brief_count:
                          type: integer
                        created_at:
                          type: string
                          format: date-time
                        updated_at:
                          type: string
                          format: date-time
                  pagination:
                    $ref: '#/components/schemas/Pagination'
                  request_id:
                    type: string
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          description: Rate limited, or the org's monthly brief limit blocked minting the refreshed brief (only the FIRST supersession of a brief is billing-exempt; repeats meter normally)
    post:
      operationId: createCluster
      summary: Create a topic cluster
      description: Create a topic cluster. Omit `site_id` for an org-wide cluster, or pass a site your organization owns to scope it. A `site_id` the org does not own returns 404.
      tags:
      - Clusters
      security:
      - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              properties:
                name:
                  type: string
                  description: Required. Cluster name.
                description:
                  type: string
                  description: Optional summary of the cluster's topic.
                color:
                  type: string
                  description: Optional display color.
                seed_keywords:
                  type: array
                  items:
                    type: string
                  description: Optional seed keywords for the cluster.
                site_id:
                  type: string
                  format: uuid
                  description: Optional. Scope the cluster to a site the org owns; omit for an org-wide cluster.
      responses:
        '201':
          description: Cluster created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        format: uuid
                      name:
                        type: string
                      description:
                        type: string
                        nullable: true
                      color:
                        type: string
                        nullable: true
                      site_id:
                        type: string
                        format: uuid
                        nullable: true
                        description: Echoes the scope just written; null for an org-wide cluster.
                      seed_keywords:
                        type: array
                        items:
                          type: string
                        nullable: true
                      created_at:
                        type: string
                        format: date-time
                  request_id:
                    type: string
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          description: Rate limited, or the org's monthly brief limit blocked minting the refreshed brief (only the FIRST supersession of a brief is billing-exempt; repeats meter normally)
  /clusters/suggest:
    post:
      operationId: suggestClusters
      summary: Suggest new topic clusters for a site
      description: Propose new topic clusters for a site, reasoning from the org's existing cluster taxonomy plus unclustered brief topics and content titles as demand signals. Returns an empty array when the site has no signals to reason from.
      tags:
      - Clusters
      security:
      - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - site_id
              properties:
                site_id:
                  type: string
                  format: uuid
                  description: Required. The site to suggest clusters for.
      responses:
        '200':
          description: Ranked cluster suggestions
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                          description: Proposed cluster name.
                        pillar_topic:
                          type: string
                          description: The pillar topic the cluster would be built around.
                        suggested_content:
                          type: array
                          items:
                            type: string
                          description: Suggested supporting article topics.
                        confidence:
                          type: number
                          description: Model confidence in the suggestion, 0-1.
                  request_id:
                    type: string
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          description: Rate limited, or the org's monthly brief limit blocked minting the refreshed brief (only the FIRST supersession of a brief is billing-exempt; repeats meter normally)
  /clusters/{id}:
    get:
      operationId: getCluster
      summary: Get a topic cluster
      description: Retrieve a single cluster with its assigned content and briefs.
      tags:
      - Clusters
      security:
      - ApiKeyAuth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Cluster ID
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Cluster detail
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        format: uuid
                      name:
                        type: string
                      description:
                        type: string
                        nullable: true
                      color:
                        type: string
                        nullable: true
                      pillar_content_id:
                        type: string
                        format: uuid
                        nullable: true
                      site_id:
                        type: string
                        format: uuid
                        nullable: true
                        description: The site this cluster is scoped to, or null for an org-wide cluster.
                      seed_keywords:
                        type: array
                        items:
                          type: string
                        nullable: true
                      created_at:
                        type: string
                        format: date-time
                      updated_at:
                        type: string
                        format: date-time
                      content:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              format: uuid
                            title:
                              type: string
                            status:
                              type: string
                            word_count:
                              type: integer
                              nullable: true
                            is_pillar:
                              type: boolean
                            updated_at:
                              type: string
                              format: date-time
                      briefs:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              format: uuid
                            topic:
                              type: string
                            status:
                              type: string
                            updated_at:
                              type: string
                              format: date-time
                  request_id:
                    type: string
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          description: Rate limited, or the org's monthly brief limit blocked minting the refreshed brief (only the FIRST supersession of a brief is billing-exempt; repeats meter normally)
  /clusters/{id}/assign:
    post:
      operationId: assignToCluster
      summary: Assign content or a brief to a cluster
      description: Assign one or more content rows and/or briefs to a cluster. At least one of `content_id` or `brief_id` is required.
      tags:
      - Clusters
      security:
      - ApiKeyAuth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Cluster ID
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                content_id:
                  type: string
                  format: uuid
                  description: Content to assign. At least one of content_id or brief_id is required.
                brief_id:
                  type: string
                  format: uuid
                  description: Brief to assign. At least one of content_id or brief_id is required.
      responses:
        '200':
          description: Items assigned to the cluster
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      cluster_id:
                        type: string
                        format: uuid
                      assigned:
                        type: array
                        items:
                          type: object
                          properties:
                            type:
                              type: string
                              enum:
                              - content
                              - brief
                            id:
                              type: string
                              format: uuid
                  request_id:
                    type: string
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          description: Rate limited, or the org's monthly brief limit blocked minting the refreshed brief (only the FIRST supersession of a brief is billing-exempt; repeats meter normally)
  /clusters/{id}/pillar:
    post:
      operationId: setClusterPillar
      summary: Elect a cluster's pillar content
      description: Promote a content row to be the cluster's pillar. The content must already be a member of the cluster (`not_a_member` otherwise) and must be an article, not a brief (`brief_not_allowed`). Electing a new pillar displaces the previous one, returned as `displaced_content_id`.
      tags:
      - Clusters
      security:
      - ApiKeyAuth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Cluster ID
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - content_id
              properties:
                content_id:
                  type: string
                  format: uuid
                  description: Required. The cluster member to elect as pillar.
      responses:
        '200':
          description: Pillar elected
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      cluster_id:
                        type: string
                        format: uuid
                      pillar_content_id:
                        type: string
                        format: uuid
                      pillar_title:
                        type: string
                        nullable: true
                      displaced_content_id:
                        type: string
                        format: uuid
                        nullable: true
                        description: The content that was pillar before this call, or null if there was none.
                  request_id:
                    type: string
        '400':
          description: The content is not a cluster member (not_a_member) or is a brief (brief_not_allowed)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          description: Rate limited, or the org's monthly brief limit blocked minting the refreshed brief (only the FIRST supersession of a brief is billing-exempt; repeats meter normally)
components:
  responses:
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: invalid_request_error
              code: resource_not_found
              message: The requested resource was not found.
              doc_url: https://docs.frase.io/api/errors#resource_not_found
            request_id: req_abc123
    Unauthorized:
      description: Authentication failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: authentication_error
              code: invalid_api_key
              message: Invalid API key. Check that you are using the correct key.
              doc_url: https://docs.frase.io/api/errors#invalid_api_key
            request_id: req_abc123
    ValidationError:
      description: Validation failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: invalid_request_error
              code: validation_error
              message: The request body is invalid.
              param: prompt_text
              doc_url: https://docs.frase.io/api/errors#validation_error
            request_id: req_abc123
  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            type:
              type: string
            code:
              type: string
            message:
              type: string
            param:
              type: string
            doc_url:
              type: string
        request_id:
          type: string
    Pagination:
      type: object
      properties:
        page:
          type: integer
        page_size:
          type: integer
        total:
          type: integer
        has_more:
          type: boolean
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY
      description: 'API key for authentication. Get your key from Settings > API Keys. You may also send the same key as `Authorization: Bearer <key>` — see BearerAuth.'
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Frase API key (sk_live_* / sk_test_*) or MCP access token (mcp_at_*)
      description: 'Send either your API key or an MCP access token as `Authorization: Bearer <token>`. An API key sent this way is exactly equivalent to sending it in X-API-KEY — same identity, same scopes, same rate limits — so use whichever your HTTP client makes natural. X-API-KEY takes precedence if you somehow send both. Cookie/session auth is intentionally NOT supported on /api/v1/* endpoints.'