Frase Templates API

Content templates for standardized outlines and formatting

GitHub OpenAPI

Operations 5

POST /templates/apply Resolve a template into a ready-to-use outline #
POST /templates/extract Extract a reusable template from a brief or an article #
GET /templates List templates #
GET /templates/{id} Get template #
DELETE /templates/{id} Delete template #

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-templates-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-templates-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Frase Templates 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: Templates
  description: Content templates for standardized outlines and formatting
paths:
  /templates/apply:
    post:
      operationId: applyTemplate
      summary: Resolve a template into a ready-to-use outline
      description: 'Loads one of your templates and returns it resolved into the shape the brief-creation and playbook flows consume: the outline sections mapped to outline entries (each tagged `source: "template"`), the template''s field defaults, and its boilerplate blocks. Requires the `read:content` scope. Read-only — nothing is created or modified.


        The lookup is organization-scoped and skips soft-deleted rows, and a template that exists but is disabled is refused as well; both refusals come back as 404 in the plain `{ "data": { "error": ... } }` envelope with the reason in `error` (`Template not found` / `This template is currently disabled`). Note the response does not carry the template''s `content_type`.


        The three payload keys are snake_case, but the objects inside them keep the stored camelCase field names.'
      tags:
      - Templates
      security:
      - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - template_id
              properties:
                template_id:
                  type: string
                  format: uuid
                  description: Required. The template to apply. Must be a UUID.
      responses:
        '200':
          description: The resolved template
          headers:
            X-Request-ID:
              schema:
                type: string
              description: Correlation id for this request
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      outline:
                        type: array
                        description: The template's sections mapped to outline entries. Empty array when the template stores no sections.
                        items:
                          type: object
                          description: An outline section. Field names are camelCase here (unlike the snake_case `ContentOutlineSection` used by the brief endpoints).
                          properties:
                            heading:
                              type: string
                            level:
                              type: integer
                              minimum: 1
                              maximum: 6
                            keyPoints:
                              type: array
                              items:
                                type: string
                            suggestedWordCount:
                              type: integer
                            generationMode:
                              type: string
                              enum:
                              - ai
                              - manual
                              description: Defaults to `ai` when the section does not set one.
                            isRequired:
                              type: boolean
                              description: Defaults to true when the section does not set one.
                            source:
                              type: string
                              enum:
                              - template
                              description: Always `template` on this path.
                      field_defaults:
                        allOf:
                        - $ref: '#/components/schemas/TemplateFieldDefaults'
                        description: The template's stored defaults, or `{}` when it has none. May also carry Smart Flow keys (`authoritySource`, `blogFormat`, `guideFormat`, `skillLevel`, `blogContext`, `guideContext`).
                      boilerplate_blocks:
                        type: array
                        description: The template's boilerplate blocks, or `[]` when it has none.
                        items:
                          $ref: '#/components/schemas/BoilerplateBlock'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: The API key lacks the `read:content` scope (`insufficient_scope`), the caller's IP is not allow-listed (`ip_not_allowed`), or the organization's subscription/trial is no longer active.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: 'The template does not exist, is soft-deleted, belongs to another organization, or is disabled. Returned in the plain `{ "data": { "error": ... } }` envelope, not the structured error object.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      error:
                        type: string
              example:
                data:
                  error: Template not found
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: Unhandled failure, including a malformed/non-JSON request body.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      error:
                        type: string
              example:
                data:
                  error: Failed to apply template
  /templates/extract:
    post:
      operationId: extractTemplate
      summary: Extract a reusable template from a brief or an article
      description: 'Derives a template from something you already have and saves it. Requires the `write:content` scope.


        With `source_type: "brief"` the brief''s outline becomes the template sections and the brief''s word count, language, country and resolved tone become the field defaults. With `source_type: "content"` the article''s own brief outline is preferred when it has one; otherwise the HTML (or Markdown) headings of the body are parsed into sections with per-section word counts, falling back to a single `Main Content` section when the body has no headings. Both lookups are organization-scoped and skip soft-deleted rows. Extraction never produces boilerplate blocks — `boilerplate_blocks` always comes back empty.


        This endpoint writes: on success the extracted template is created for your organization (a `Template from {topic or title}` name unless you pass `name`), so 201 means a new template exists and `data.id` is its id. The saved rows are clamped to the content type''s allowed word-count range, while the `template` echoed in the response is the pre-clamp extraction — re-read the template via `GET /templates/{id}` if you need the stored values.


        One special case returns 200 instead of 201: if the source yields no outline sections, nothing is saved and the response carries `saved: false` plus a `message` explaining that sections must be added manually.'
      tags:
      - Templates
      security:
      - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - source_type
              - source_id
              properties:
                source_type:
                  type: string
                  enum:
                  - brief
                  - content
                  description: Required. Whether `source_id` names a brief or a content item.
                source_id:
                  type: string
                  format: uuid
                  description: Required. The brief or content item to extract from. Must be a UUID.
                name:
                  type: string
                  description: Optional name for the saved template. Truncated to 255 characters. Defaults to the generated `Template from ...` name. Ignored on the unsaved (no sections) path.
      responses:
        '200':
          description: Extracted but NOT saved — the source produced no outline sections
          headers:
            X-Request-ID:
              schema:
                type: string
              description: Correlation id for this request
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      template:
                        type: object
                        properties:
                          name:
                            type: string
                          description:
                            type: string
                          content_type:
                            type: string
                            nullable: true
                          outline_sections:
                            type: array
                            description: Always empty on this path.
                            items:
                              $ref: '#/components/schemas/TemplateSectionDef'
                          boilerplate_blocks:
                            type: array
                            items:
                              $ref: '#/components/schemas/BoilerplateBlock'
                          field_defaults:
                            $ref: '#/components/schemas/TemplateFieldDefaults'
                          source_type:
                            type: string
                            enum:
                            - from_brief
                            - from_content
                          source_content_id:
                            type: string
                            format: uuid
                            nullable: true
                      saved:
                        type: boolean
                        enum:
                        - false
                      message:
                        type: string
              example:
                data:
                  template:
                    name: Template from Q3 launch post
                    description: Extracted from brief "Q3 launch post"
                    content_type: null
                    outline_sections: []
                    boilerplate_blocks: []
                    field_defaults: {}
                    source_type: from_brief
                    source_content_id: null
                  saved: false
                  message: Template extracted but not saved — no outline sections found. Add sections manually via POST /templates.
        '201':
          description: Template extracted and saved
          headers:
            X-Request-ID:
              schema:
                type: string
              description: Correlation id for this request
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        format: uuid
                        description: Id of the newly created template.
                      template:
                        type: object
                        properties:
                          name:
                            type: string
                            description: The stored (trimmed) name.
                          description:
                            type: string
                          content_type:
                            type: string
                            nullable: true
                            description: The brief's content type, or the article's type, as stored on the source; null when the source has none.
                          outline_sections:
                            type: array
                            description: The extracted sections BEFORE the save-time word-count clamping.
                            items:
                              $ref: '#/components/schemas/TemplateSectionDef'
                          boilerplate_blocks:
                            type: array
                            description: Always empty — extraction does not produce boilerplate.
                            items:
                              $ref: '#/components/schemas/BoilerplateBlock'
                          field_defaults:
                            allOf:
                            - $ref: '#/components/schemas/TemplateFieldDefaults'
                            description: Word count, language, country, resolved tone and (content sources) meta title format, as extracted before clamping.
                          source_type:
                            type: string
                            enum:
                            - from_brief
                            - from_content
                            description: '`from_brief` for a brief source; `from_content` for a content source, including when the outline was read through the article''s brief.'
                          source_content_id:
                            type: string
                            format: uuid
                            nullable: true
                            description: The source content id, or null for a brief source.
        '400':
          description: 'Either a request-validation failure (`invalid_param` on `source_type` / `source_id`, in the structured error envelope) or a save refusal, which uses the plain `{ "data": { "error": ... } }` envelope: an empty (whitespace-only) name, a name that collides with an existing template in your organization (`A template with this name already exists`), an invalid heading-level sequence, or your plan''s custom-template limit being reached. (An over-long `name` cannot occur - the route truncates to 255 before the check. A non-string `name` is a 500, not a 400.)'
          content:
            application/json:
              schema:
                oneOf:
                - $ref: '#/components/schemas/Error'
                - type: object
                  properties:
                    data:
                      type: object
                      properties:
                        error:
                          type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: The API key lacks the `write:content` scope (`insufficient_scope`), the caller's IP is not allow-listed (`ip_not_allowed`), or the organization's subscription/trial is no longer active.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: 'The brief or content item does not exist, is soft-deleted, or belongs to another organization. Returned in the plain `{ "data": { "error": ... } }` envelope, not the structured error object.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      error:
                        type: string
              example:
                data:
                  error: Brief not found
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: Unhandled failure, including a malformed/non-JSON request body.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      error:
                        type: string
              example:
                data:
                  error: Failed to extract template
  /templates:
    get:
      operationId: listTemplates
      summary: List templates
      description: Retrieve all content templates in your organization. Supports pagination and filtering by content type.
      tags:
      - Templates
      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: content_type
        in: query
        description: Filter templates by content type
        schema:
          type: string
          enum:
          - blog
          - guide
          - landing
          - product
          - comparison
      responses:
        '200':
          description: List of templates
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedTemplates'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
  /templates/{id}:
    get:
      operationId: getTemplate
      summary: Get template
      description: Retrieve a specific content template by ID.
      tags:
      - Templates
      security:
      - ApiKeyAuth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Template ID
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Template details
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Template'
                  request_id:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
    delete:
      operationId: deleteTemplate
      summary: Delete template
      description: Delete a template. System templates cannot be deleted.
      tags:
      - Templates
      security:
      - ApiKeyAuth: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Template ID
      responses:
        '200':
          description: Template deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      deleted:
                        type: boolean
                  request_id:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: System template cannot be deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
components:
  schemas:
    Template:
      type: object
      description: A content template defining outline structure, boilerplate blocks, and field defaults.
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
          nullable: true
        content_type:
          type: string
          enum:
          - blog
          - guide
          - landing
          - product
          - comparison
          nullable: true
        outline_sections:
          type: array
          items:
            $ref: '#/components/schemas/TemplateSectionDef'
        section_count:
          type: integer
          description: Number of sections in the outline
        total_word_count:
          type: integer
          description: Sum of suggested word counts across all sections
        boilerplate_blocks:
          type: array
          items:
            $ref: '#/components/schemas/BoilerplateBlock'
          nullable: true
        field_defaults:
          allOf:
          - $ref: '#/components/schemas/TemplateFieldDefaults'
          nullable: true
        source_type:
          type: string
          nullable: true
          description: Source from which this template was extracted
        is_system:
          type: boolean
          description: Whether this is a system-provided template
        is_default:
          type: boolean
          description: Whether this is the default template
        is_enabled:
          type: boolean
          description: Whether this template is enabled
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
          nullable: true
    PaginatedTemplates:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Template'
        pagination:
          $ref: '#/components/schemas/Pagination'
        request_id:
          type: string
    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
    BoilerplateBlock:
      type: object
      required:
      - id
      - position
      - content
      - label
      description: A reusable content block that can be inserted at specific positions in generated content.
      properties:
        id:
          type: string
          minLength: 1
          description: Unique identifier for the block
        position:
          type: string
          enum:
          - before_section
          - after_section
          - header
          - footer
          description: Where to insert the block
        anchorSectionHeading:
          type: string
          description: Section heading to anchor to (for before_section/after_section positions)
        content:
          type: string
          maxLength: 50000
          description: The boilerplate content (max 50,000 characters)
        label:
          type: string
          minLength: 1
          description: Display label for the block
    TemplateSectionDef:
      type: object
      required:
      - heading
      - level
      description: A section definition within a template outline.
      properties:
        heading:
          type: string
          minLength: 1
          description: The section heading text
        level:
          type: integer
          minimum: 1
          maximum: 6
          description: Heading level (1-6)
        keyPoints:
          type: array
          items:
            type: string
          default: []
          description: Key points to cover in this section
        suggestedWordCount:
          type: integer
          minimum: 0
          default: 200
          description: Suggested word count for this section
        isRequired:
          type: boolean
          default: true
          description: Whether this section is required
        generationMode:
          type: string
          enum:
          - ai
          - manual
          description: Whether AI generates this section or user provides content
    TemplateFieldDefaults:
      type: object
      description: Default field values applied when the template is used.
      properties:
        targetWordCount:
          type: integer
          minimum: 0
          description: Default target word count
        metaTitleFormat:
          type: string
          description: Default meta title format
        suggestedTone:
          type: string
          description: Default suggested tone
        targetLanguage:
          type: string
          description: Default target language
        targetCountry:
          type: string
          description: Default target country
    Pagination:
      type: object
      properties:
        page:
          type: integer
        page_size:
          type: integer
        total:
          type: integer
        has_more:
          type: boolean
  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
    RateLimited:
      description: Rate limit exceeded
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until rate limit resets
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: rate_limit_error
              code: rate_limit_exceeded
              message: Rate limit exceeded. Please retry after 60 seconds.
              doc_url: https://docs.frase.io/api/errors#rate_limit_exceeded
            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
  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.'