openapi: 3.0.3
info:
  title: ArticlesQ API
  version: 1.0.0
  description: >-
    Read your generated articles programmatically — power blogs, mobile apps, or
    any frontend with a single HTTPS call. Push articles you generated yourself
    with `POST /articles` and let ArticlesQ host them (handy when you want
    backlinks-exchange participation but bring your own writer).


    ## Quick start


    1. Open **Settings → Integrations & API** for your project in the ArticlesQ
    dashboard and create an API key.

    2. Pass it as a Bearer token: `Authorization: Bearer aq_live_...`

    3. List your articles: `GET /articles`

    4. Fetch a single article: `GET /articles/{slug}`

    5. Push your own: `POST /articles` with markdown content

    6. Update an article: `PATCH /articles/{slug}` (e.g. tweak the meta
    description)


    ## Key concepts


    - Each API key is scoped to exactly one **project**. The same key can never
    see another project's articles.

    - Pagination uses simple `page` + `pageSize` query params. Default page size
    is 20, max is 100.

    - The article `content` field is markdown.

    - Slugs are unique within a project — POST returns `409 slug_conflict` if
    you try to reuse one.
servers:
  - url: https://articlesq.com/api/v1
    description: Production
tags:
  - name: Articles
    description: Read articles generated for the project this API key is scoped to.
components:
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      description: "ArticlesQ API key sent as `Authorization: Bearer aq_live_...`.
        Each key is scoped to a single project."
  schemas:
    ApiError:
      type: object
      properties:
        error:
          type: object
          properties:
            title:
              type: string
              example: Unauthorized
            status:
              type: integer
              example: 401
            code:
              $ref: "#/components/schemas/ApiErrorCode"
            message:
              type: string
              example: Missing Authorization header
            instance:
              type: string
              example: /api/v1/articles
            details:
              type: object
              additionalProperties:
                type: object
          required:
            - title
            - status
            - code
            - message
      required:
        - error
    ApiErrorCode:
      type: string
      enum:
        - missing_auth
        - invalid_auth_format
        - invalid_api_key
        - api_key_expired
        - insufficient_scope
        - not_found
        - route_not_found
        - validation_failed
        - slug_conflict
        - internal_error
      example: missing_auth
    Pagination:
      type: object
      properties:
        page:
          type: integer
          example: 1
          description: Current page number (starting from 1).
        limit:
          type: integer
          example: 20
          description: Maximum number of items returned per page.
        total:
          type: integer
          example: 100
          description: Total number of matching items.
        totalPages:
          type: integer
          example: 5
          description: Total number of pages for the current limit.
        hasNext:
          type: boolean
          example: true
          description: Whether another page exists after the current one.
        hasPrev:
          type: boolean
          example: false
          description: Whether another page exists before the current one.
      required:
        - page
        - limit
        - total
        - totalPages
        - hasNext
        - hasPrev
    FaqItem:
      type: object
      properties:
        q:
          type: string
          example: What is the best CRM for early-stage startups?
          description: The question. Plain text, no markdown.
        a:
          type: string
          example: For teams under 20 people, lightweight tools like HubSpot Free or Folk
            are usually a better fit than enterprise CRMs.
          description: The answer. Plain text, no markdown.
      required:
        - q
        - a
    Article:
      type: object
      properties:
        id:
          type: string
          example: clx1abc234...
        slug:
          type: string
          example: best-crm-for-startups
        title:
          type: string
          example: Best CRM for Startups in 2026
        keyword:
          type: string
          example: best crm for startups
        metaDescription:
          type: string
          nullable: true
          example: A practical guide to choosing the right CRM when you're under 50
            employees.
        content:
          type: string
          description: Markdown body of the article.
          example: |-
            # Introduction

            If you're a startup founder...
        coverImage:
          type: string
          nullable: true
          description: Public URL of the generated cover image. Null while pending or if
            image generation failed.
          example: https://cdn.articlesq.com/images/proj_abc/best-crm-for-startups/cover.jpeg
        faq:
          type: array
          items:
            $ref: "#/components/schemas/FaqItem"
          description: Structured FAQ as plain-text Q/A pairs. Empty array when the
            article has no FAQ. By default the project also appends this FAQ as
            markdown to `content`; toggle `inlineFaqInContent` on the project
            (or pass `?inlineFaq=false`) to receive `content` without the FAQ
            section.
        status:
          $ref: "#/components/schemas/ArticleStatus"
        datePublished:
          type: string
          nullable: true
          example: 2026-05-23T00:00:00.000Z
          description: Public publish date (schema.org `datePublished`) as an ISO 8601
            string. Null when no specific date is set. Editable from the
            dashboard.
        dateModified:
          type: string
          nullable: true
          example: 2026-06-01T00:00:00.000Z
          description: Last meaningful content update (schema.org `dateModified`) as an
            ISO 8601 string. Null until the article is edited after being
            published.
      required:
        - id
        - slug
        - title
        - keyword
        - metaDescription
        - content
        - coverImage
        - faq
        - status
        - datePublished
        - dateModified
    ArticleStatus:
      type: string
      enum:
        - draft
        - published
    ListArticlesResponse:
      type: object
      properties:
        articles:
          type: array
          items:
            $ref: "#/components/schemas/Article"
        pagination:
          $ref: "#/components/schemas/Pagination"
      required:
        - articles
        - pagination
    ArticleResponse:
      type: object
      properties:
        article:
          $ref: "#/components/schemas/Article"
      required:
        - article
    CreateArticleBody:
      type: object
      properties:
        slug:
          type: string
          minLength: 1
          pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
          example: best-crm-for-startups
          description: URL slug. Must be lowercase kebab-case and unique within the project.
        title:
          type: string
          minLength: 1
          example: Best CRM for Startups in 2026
          description: Article title — also used as the SEO title tag.
        keyword:
          type: string
          minLength: 1
          example: best crm for startups
          description: Primary keyword the article targets.
        content:
          type: string
          default: ""
          example: |-
            # Introduction

            If you're a startup founder...
          description: Markdown body of the article. Defaults to an empty string when
            omitted — useful when creating a stub to fill in later.
        metaDescription:
          type: string
          example: A practical guide to choosing the right CRM when you're under 50
            employees.
          description: SEO meta description, recommended < 155 characters.
        status:
          type: string
          enum:
            - draft
            - published
          example: draft
          description: Defaults to `draft`.
        faq:
          type: array
          items:
            $ref: "#/components/schemas/FaqItem"
          description: Structured FAQ as plain-text Q/A pairs. Server trims whitespace,
            drops fully-empty pairs, and rejects half-filled ones (only `q` or
            only `a`).
          example:
            - q: What is the best CRM for early-stage startups?
              a: For teams under 20 people, lightweight tools like HubSpot Free or Folk are
                usually a better fit than enterprise CRMs.
      required:
        - slug
        - title
        - keyword
    UpdateArticleBody:
      type: object
      properties:
        title:
          type: string
          example: 10 Link Building Strategies That Actually Work
          description: Article title. Empty strings are rejected.
        slug:
          type: string
          example: link-building-strategies
          description: URL-friendly slug. Empty strings are rejected. Must be unique
            within the project. Can only be changed while the article is in
            draft status.
        content:
          type: string
          example: |-
            # Introduction

            Updated body...
          description: New markdown body. Empty strings are rejected — you can clear other
            fields, but the article body must stay non-empty.
        metaDescription:
          type: string
          example: Learn proven link building strategies to boost your domain authority
            and organic traffic.
          description: SEO meta description, recommended < 155 characters.
        faq:
          type: array
          items:
            $ref: "#/components/schemas/FaqItem"
          description: Replace the structured FAQ. Server trims whitespace, drops
            fully-empty pairs, rejects half-filled ones. Pass `[]` to remove all
            items. Omit to leave it unchanged.
      additionalProperties: false
  parameters: {}
paths:
  /articles:
    get:
      operationId: listArticles
      tags:
        - Articles
      summary: List articles
      description: Returns a paginated list of articles for the project the API key is
        scoped to. The article body is included for each item.
      security:
        - ApiKeyAuth: []
      parameters:
        - schema:
            type: integer
            minimum: 1
            default: 1
            example: 1
          required: false
          name: page
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
            example: 20
          required: false
          name: limit
          in: query
        - schema:
            type: boolean
            default: false
            example: false
            description: By default only `published` articles are returned. Set to `true` to
              also include `draft` articles (e.g. to preview work in progress).
          required: false
          description: By default only `published` articles are returned. Set to `true` to
            also include `draft` articles (e.g. to preview work in progress).
          name: includeDrafts
          in: query
      responses:
        "200":
          description: Articles retrieved successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListArticlesResponse"
              examples:
                withResults:
                  value:
                    articles:
                      - &a1
                        id: clx1abc234abcdef0123456789
                        slug: best-crm-for-startups
                        title: Best CRM for Startups in 2026
                        keyword: best crm for startups
                        metaDescription: A practical guide to choosing the right CRM when you're under
                          50 employees.
                        content: |-
                          # Introduction

                          If you're a startup founder evaluating CRM tools...
                        status: draft
                        datePublished: 2026-05-23T00:00:00.000Z
                        dateModified: null
                    pagination:
                      page: 1
                      limit: 20
                      total: 1
                      totalPages: 1
                      hasNext: false
                      hasPrev: false
                empty:
                  value:
                    articles: []
                    pagination:
                      page: 1
                      limit: 20
                      total: 0
                      totalPages: 0
                      hasNext: false
                      hasPrev: false
        "400":
          description: Invalid query parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "401":
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
              examples:
                missingAuth: &a2
                  value:
                    error:
                      title: Unauthorized
                      status: 401
                      code: missing_auth
                      message: Missing Authorization header
                      instance: /api/v1/articles
                invalidApiKey: &a3
                  value:
                    error:
                      title: Unauthorized
                      status: 401
                      code: invalid_api_key
                      message: Invalid API key
                      instance: /api/v1/articles
                apiKeyExpired:
                  value:
                    error:
                      title: Unauthorized
                      status: 401
                      code: api_key_expired
                      message: API key expired
                      instance: /api/v1/articles
        "403":
          description: API key lacks the required scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
              examples:
                insufficientScope:
                  value:
                    error:
                      title: Forbidden
                      status: 403
                      code: insufficient_scope
                      message: "Missing required scope: articles:read"
                      instance: /api/v1/articles
    post:
      operationId: createArticle
      tags:
        - Articles
      summary: Create an article
      description: Creates a new article in the project the API key is scoped to. Use
        this when you generate content with your own agents and want ArticlesQ
        to host it (e.g. for the backlinks exchange). The article body is
        markdown.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateArticleBody"
            examples:
              minimal:
                summary: Minimal — slug, title, keyword only
                value:
                  slug: best-crm-for-startups
                  title: Best CRM for Startups in 2026
                  keyword: best crm for startups
              full:
                summary: Full — with markdown body, meta description, and status
                value:
                  slug: best-crm-for-startups
                  title: Best CRM for Startups in 2026
                  keyword: best crm for startups
                  content: |-
                    # Introduction

                    If you're a startup founder evaluating CRM tools...
                  metaDescription: A practical guide to choosing the right CRM when you're under
                    50 employees.
                  status: draft
      responses:
        "201":
          description: Article created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArticleResponse"
              examples:
                created:
                  value:
                    article: *a1
        "400":
          description: Invalid request body
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "401":
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
              examples:
                missingAuth: *a2
                invalidApiKey: *a3
        "403":
          description: API key lacks the required scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
              examples:
                insufficientScope:
                  value:
                    error:
                      title: Forbidden
                      status: 403
                      code: insufficient_scope
                      message: "Missing required scope: articles:write"
                      instance: /api/v1/articles
        "409":
          description: An article with the requested slug already exists in this project
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
              examples:
                slugConflict:
                  value:
                    error:
                      title: Error
                      status: 409
                      code: slug_conflict
                      message: An article with that slug already exists in this project.
                      instance: /api/v1/articles
  /articles/{slug}:
    get:
      operationId: getArticle
      tags:
        - Articles
      summary: Get article by slug
      description: Returns a single article by slug, scoped to the project the API key
        authorises. Slugs are unique within a project.
      security:
        - ApiKeyAuth: []
      parameters:
        - schema:
            type: string
            example: best-crm-for-startups
          required: true
          name: slug
          in: path
        - schema:
            type: boolean
            example: false
            description: Whether to append the FAQ as a `## FAQ` markdown section to
              `content`. `true` forces inlining, `false` returns `content`
              without it. Omit to use the project's `inlineFaqInContent` default
              (on by default). The structured `faq` field is always returned
              either way.
          required: false
          description: Whether to append the FAQ as a `## FAQ` markdown section to
            `content`. `true` forces inlining, `false` returns `content` without
            it. Omit to use the project's `inlineFaqInContent` default (on by
            default). The structured `faq` field is always returned either way.
          name: inlineFaq
          in: query
        - schema:
            type: boolean
            default: false
            example: false
            description: By default a `draft` article returns 404 on this endpoint — only
              `published` articles are fetchable. Set to `true` to fetch a draft
              (e.g. to preview work in progress).
          required: false
          description: By default a `draft` article returns 404 on this endpoint — only
            `published` articles are fetchable. Set to `true` to fetch a draft
            (e.g. to preview work in progress).
          name: includeDrafts
          in: query
      responses:
        "200":
          description: Article retrieved successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArticleResponse"
              examples:
                found:
                  value:
                    article: *a1
        "401":
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
              examples:
                missingAuth: *a2
                invalidApiKey: *a3
        "403":
          description: API key lacks the required scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
              examples:
                insufficientScope:
                  value:
                    error:
                      title: Forbidden
                      status: 403
                      code: insufficient_scope
                      message: "Missing required scope: articles:read"
                      instance: /api/v1/articles/{slug}
        "404":
          description: Article not found in this project
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
              examples:
                notFound:
                  value:
                    error:
                      title: Not Found
                      status: 404
                      code: not_found
                      message: Article not found
                      instance: /api/v1/articles/best-crm-for-startups
    patch:
      operationId: updateArticle
      tags:
        - Articles
      summary: Update an article
      description: Update editable fields on an article — currently `content` and
        `metaDescription`. Send only the fields you want to change. Returns the
        full updated article.
      security:
        - ApiKeyAuth: []
      parameters:
        - schema:
            type: string
            example: best-crm-for-startups
          required: true
          name: slug
          in: path
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateArticleBody"
            examples:
              metaOnly:
                summary: Update only the meta description
                value:
                  metaDescription: Updated meta description with the latest pricing details.
              contentAndMeta:
                summary: Update both the body and the meta description
                value:
                  content: |-
                    # Introduction

                    New, better intro...
                  metaDescription: Practical CRM picks tested by real founders.
      responses:
        "200":
          description: Article updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArticleResponse"
              examples:
                updated:
                  value:
                    article:
                      id: clx1abc234abcdef0123456789
                      slug: best-crm-for-startups
                      title: Best CRM for Startups in 2026
                      keyword: best crm for startups
                      metaDescription: Updated meta description with the latest pricing details.
                      content: |-
                        # Introduction

                        If you're a startup founder evaluating CRM tools...
                      status: draft
                      datePublished: 2026-05-23T00:00:00.000Z
                      dateModified: null
        "400":
          description: Invalid request body
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "401":
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
              examples:
                missingAuth: *a2
                invalidApiKey: *a3
        "403":
          description: API key lacks the required scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
              examples:
                insufficientScope:
                  value:
                    error:
                      title: Forbidden
                      status: 403
                      code: insufficient_scope
                      message: "Missing required scope: articles:write"
                      instance: /api/v1/articles/{slug}
        "404":
          description: Article not found in this project
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
              examples:
                notFound:
                  value:
                    error:
                      title: Not Found
                      status: 404
                      code: not_found
                      message: Article not found
                      instance: /api/v1/articles/best-crm-for-startups
