openapi: 3.0.3

info:
  title: BotTalk Publishers API
  description: |
    REST API for publishers to manage articles and audio generation.

    Authentication uses a Bearer token passed in the `Authorization` header.
    Tokens are **project-scoped** — each token is tied to a specific project in BotTalk.
    There is no need to specify a project ID in requests; it is resolved automatically from the token.

    Two token types exist:
    - **API token** — full access (read + write). Required for POST, PUT, DELETE.
    - **API Readonly token** — read-only access. Accepted on GET endpoints only.
  version: 1.0.0
  contact:
    url: https://bottalk.io/docs/api/

servers:
  - url: https://bottalk.io
    description: Production

security:
  - bearerAuth: []

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API token from project settings

  schemas:
    Article:
      type: object
      properties:
        id:
          type: string
          description: Article slug (internal ID)
          example: a1b2c3d4
        externalId:
          type: string
          nullable: true
          description: Unique ID for external control. If an article with this ID already exists, a 400 error is returned.
          example: my-article-001
        title:
          type: string
          example: Breaking News Title
        text:
          type: string
          example: Full article text content...
        prompt:
          type: string
          nullable: true
          description: Individual prompt for audio generation (valid for Gemini Vertex model)
        added:
          type: integer
          description: Unix timestamp when article was created
          example: 1708870400
        updated:
          type: integer
          nullable: true
          description: Unix timestamp of last processing
          example: 1708871000
        status:
          type: string
          enum:
            - waiting
            - processing
            - ready
            - errored
            - deleted
            - retry
            - fallback
            - no_money
            - limit_reached
            - rejected
            - parsed
            - recrawling
          description: |
            Article processing status:
            - `ready` — article is ready and audio file exists
            - `processing` — audification is in progress
            - `waiting` — article is waiting to be audified
            - `retry` — TTS provider returned an error, will retry automatically
            - `rejected` — article rejected by audio automation rules
            - `errored` — error which prevented audification
            - `deleted` — article has been deleted
            - `fallback` — using fallback TTS provider
            - `no_money` — insufficient balance
            - `limit_reached` — daily article limit reached
            - `parsed` — article parsed, awaiting audification
            - `recrawling` — article content is being re-fetched
          example: ready
        url:
          type: string
          nullable: true
          description: Original article URL
          example: https://example.com/my-article
        isRaw:
          type: boolean
          description: Whether article text is raw (no HTML stripping)
          example: false
        voice:
          type: string
          description: |
            TTS voice name. Available voices:
            `echo`, `fable`, `alloy`, `onyx`, `nova`, `shimmer` (support `de-DE`, `en-US`),
            `de-DE-ConradNeural`, `de-DE-KatjaNeural` (support `de-DE`)
          example: shimmer
        lang:
          type: string
          description: |
            Voice language/locale. Must match the selected voice.
            Examples: `de-DE`, `en-US`
          example: en-US
        type:
          type: array
          nullable: true
          description: Content type scores from comprehend analysis (only if detected)
          items:
            type: object
            properties:
              Name:
                type: string
                example: TYPE_NACHRICHT
              Score:
                type: number
                format: float
                example: 0.203
          example:
            - Name: TYPE_NACHRICHT
              Score: 0.203
            - Name: TYPE_KOMMENTAR
              Score: 0.148
        audio:
          type: string
          nullable: true
          description: Full audio MP3 URL (only when ready)
          example: https://audio.bottalk.io/abc123.mp3
        audio_duration:
          type: number
          nullable: true
          description: Full audio duration in seconds
          example: 42.5
        audio_preview:
          type: string
          nullable: true
          description: Preview audio MP3 URL (only when ready)
          example: https://audio.bottalk.io/preview_abc123.mp3
        audio_preview_duration:
          type: number
          nullable: true
          description: Preview audio duration in seconds
          example: 15.0
        error:
          type: string
          nullable: true
          description: Last error message (only when status=errored)
        errors:
          type: array
          nullable: true
          description: Error history (only when status=errored)
          items:
            type: object
            properties:
              error:
                type: string
              date:
                type: string
                format: date-time
                example: "2024-02-25 12:00:00"

    ArticleListItem:
      type: object
      description: Abbreviated article object returned in list responses
      properties:
        id:
          type: string
          example: a1b2c3d4
        external_id:
          type: string
          nullable: true
          example: my-article-001
        title:
          type: string
          example: Breaking News Title
        text:
          type: string
          example: Full article text content...
        added:
          type: integer
          example: 1708870400
        updated:
          type: integer
          nullable: true
          example: 1708871000
        status:
          type: string
          example: ready
        url:
          type: string
          nullable: true
          example: https://example.com/my-article
        isRaw:
          type: boolean
          example: false
        voice:
          type: string
          example: shimmer
        lang:
          type: string
          example: en-US
        audio:
          type: string
          nullable: true
          example: https://audio.bottalk.io/abc123.mp3
        audio_duration:
          type: number
          nullable: true
          example: 42.5
        audio_preview:
          type: string
          nullable: true
          example: https://audio.bottalk.io/preview_abc123.mp3
        audio_preview_duration:
          type: number
          nullable: true
          example: 15.0

    ArticleCreateRequest:
      type: object
      required:
        - title
        - text
      properties:
        title:
          type: string
          description: Article title
          example: Breaking News Title
        text:
          type: string
          description: Article body text (HTML allowed if parse=true)
          example: Full article text or HTML content...
        url:
          type: string
          description: Original article URL
          example: https://example.com/my-article
        externalId:
          type: string
          description: Unique ID for external control. If an article with this ID already exists, a 400 error is returned.
          example: my-article-001
        voice:
          type: string
          description: |
            TTS voice name. Must be used together with `lang`.
            Available voices: `echo`, `fable`, `alloy`, `onyx`, `nova`, `shimmer` (support `de-DE`, `en-US`),
            `de-DE-ConradNeural`, `de-DE-KatjaNeural` (support `de-DE`)
          example: shimmer
        lang:
          type: string
          description: |
            Voice language/locale. Must be used together with `voice`.
            Examples: `de-DE`, `en-US`
          example: en-US
        parse:
          type: boolean
          description: If true, HTML is stripped and text is cleaned before processing
          default: false
        isRaw:
          type: boolean
          description: If true, text is treated as raw (no additional processing)
          default: false
        prompt:
          type: string
          description: Individual prompt for audio generation (valid for Gemini Vertex model)
        labels:
          type: array
          description: List of labels to attach to this article
          items:
            type: string
          example:
            - sports
            - breaking

    ArticleUpdateRequest:
      type: object
      required:
        - title
        - text
      properties:
        title:
          type: string
          example: Updated Article Title
        text:
          type: string
          example: Updated article body text...
        url:
          type: string
          example: https://example.com/my-article
        externalId:
          type: string
          description: Update the external ID (must be unique)
          example: my-article-001
        voice:
          type: string
          example: shimmer
        lang:
          type: string
          example: en-US
        parse:
          type: boolean
          default: false
        isRaw:
          type: boolean
        prompt:
          type: string
          nullable: true
          description: Individual prompt for audio generation (valid for Gemini Vertex model). Set to null to remove.
        labels:
          type: array
          description: Full replacement of labels (existing labels not in list will be removed)
          items:
            type: string
          example:
            - sports

    SuccessResult:
      type: object
      properties:
        result:
          type: string
          enum: [ok]
          example: ok

    ErrorResult:
      type: object
      properties:
        result:
          type: string
          enum: [error, fail]
          example: error
        message:
          type: string
          example: Article not found

paths:
  /api:
    get:
      summary: API documentation redirect
      description: Redirects to the API documentation page
      operationId: apiRedirect
      security: []
      responses:
        "302":
          description: Redirect to https://bottalk.io/docs/api/

  /api/articles:
    get:
      summary: List articles
      description: Returns a paginated list of articles for the authenticated project. Deleted articles are excluded.
      operationId: getArticles
      parameters:
        - name: limit
          in: query
          description: Number of articles to return (min 10, max 100)
          schema:
            type: integer
            minimum: 10
            maximum: 100
            default: 10
        - name: offset
          in: query
          description: Number of articles to skip
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        "200":
          description: List of articles
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: string
                    example: ok
                  count:
                    type: integer
                    example: 25
                  articles:
                    type: array
                    items:
                      $ref: "#/components/schemas/ArticleListItem"
        "403":
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"

    post:
      summary: Create article
      description: |
        Creates a new article and queues it for audio generation.
        Requires a full API token (not readonly).
        If `externalId` is provided it must be unique across all articles.
      operationId: createArticle
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ArticleCreateRequest"
            examples:
              minimal:
                summary: Minimal request
                value:
                  title: My Article
                  text: Article body text goes here.
              full:
                summary: Full request
                value:
                  title: My Article
                  text: "<p>HTML content here</p>"
                  url: https://example.com/my-article
                  externalId: my-article-001
                  voice: shimmer
                  lang: en-US
                  parse: true
                  isRaw: false
                  prompt: Read in a calm and professional tone.
                  labels:
                    - sports
                    - breaking
      responses:
        "200":
          description: Article created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: string
                    example: ok
                  id:
                    type: string
                    description: Slug of the newly created article
                    example: a1b2c3d4
        "400":
          description: Validation error or duplicate externalId
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"
        "403":
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"

  /api/articles/{articleId}:
    get:
      summary: Get article by ID
      description: Returns full article details by its internal slug ID.
      operationId: getArticle
      parameters:
        - name: articleId
          in: path
          required: true
          description: Article slug (internal ID returned on create)
          schema:
            type: string
          example: a1b2c3d4
      responses:
        "200":
          description: Article found
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: string
                    example: ok
                  article:
                    $ref: "#/components/schemas/Article"
        "200 (not found)":
          description: Article not found (returns 200 with error result)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"
        "403":
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"

    put:
      summary: Update article by ID
      description: |
        Updates article content and triggers audio re-generation.
        Requires a full API token (not readonly).
        Labels are fully replaced — any existing labels not present in the request will be removed.
      operationId: updateArticle
      parameters:
        - name: articleId
          in: path
          required: true
          description: Article slug (internal ID)
          schema:
            type: string
          example: a1b2c3d4
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ArticleUpdateRequest"
      responses:
        "200":
          description: Article updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: string
                    example: ok
                  id:
                    type: string
                    example: a1b2c3d4
        "400":
          description: Validation error or duplicate externalId
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"
        "401":
          description: Article belongs to a different project
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"
        "403":
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"

    delete:
      summary: Delete article
      description: |
        Marks the article as deleted. Deleted articles are excluded from list responses.
        Requires a full API token (not readonly).
      operationId: deleteArticle
      parameters:
        - name: articleId
          in: path
          required: true
          description: Article slug (internal ID)
          schema:
            type: string
          example: a1b2c3d4
      responses:
        "200":
          description: Article deleted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessResult"
        "404":
          description: Article not found or already deleted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"
        "403":
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"

  /api/articles_external_id/{externalId}:
    get:
      summary: Get article by external ID
      description: Returns full article details by the external ID you provided on creation.
      operationId: getArticleByExternalId
      parameters:
        - name: externalId
          in: path
          required: true
          description: Your external article identifier
          schema:
            type: string
          example: my-article-001
      responses:
        "200":
          description: Article found
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: string
                    example: ok
                  article:
                    $ref: "#/components/schemas/Article"
        "200 (not found)":
          description: Article not found (returns 200 with error result)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"
        "403":
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"

    put:
      summary: Update article by external ID
      description: |
        Updates article by external ID and triggers audio re-generation.
        Requires a full API token (not readonly).
      operationId: updateArticleByExternalId
      parameters:
        - name: externalId
          in: path
          required: true
          description: Your external article identifier
          schema:
            type: string
          example: my-article-001
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ArticleUpdateRequest"
      responses:
        "200":
          description: Article updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: string
                    example: ok
                  id:
                    type: string
                    example: a1b2c3d4
        "400":
          description: Validation error or duplicate externalId
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"
        "404":
          description: Article not found by externalId
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"
        "403":
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"

  /api/articles_external_url/{externalUrl}:
    get:
      summary: Get article by URL
      description: Returns full article details by the original article URL.
      operationId: getArticleByExternalUrl
      parameters:
        - name: externalUrl
          in: path
          required: true
          description: Original article URL (URL-encoded)
          schema:
            type: string
          example: https%3A%2F%2Fexample.com%2Fmy-article
      responses:
        "200":
          description: Article found
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: string
                    example: ok
                  article:
                    $ref: "#/components/schemas/Article"
        "200 (not found)":
          description: Article not found (returns 200 with error result)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"
        "403":
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResult"
