openapi: "3.0.3"
info:
  title: PressMatrix Article Import API
  version: "2"
  description: |
    Per-publication REST API for importing and managing article-based content.
    The technical resource name is `stories`, and request/response envelopes use
    the `story` key.

    All endpoints require a publication-scoped API token supplied via
    `Authorization: Token <token>`.

servers:
  - url: https://{host}/api/v2/importer
    variables:
      host:
        default: editor.pressmatrix.com

# ---------------------------------------------------------------------------
# Reusable components
# ---------------------------------------------------------------------------
components:
  securitySchemes:
    ApiToken:
      type: apiKey
      in: header
      name: Authorization
      description: "Format: `Token <token>`"

  parameters:
    organizationId:
      name: organization_id
      in: path
      required: true
      schema:
        type: integer
    publicationId:
      name: publication_id
      in: path
      required: true
      schema:
        type: integer
    storyId:
      name: id
      in: path
      required: true
      schema:
        type: integer

  schemas:
    Story:
      type: object
      properties:
        id:
          type: integer
          readOnly: true
        external_id:
          type: string
          nullable: true
          description: Customer-provided article identifier. Index filters use LIKE matching.
        name:
          type: string
        title:
          type: string
          nullable: true
        preview:
          type: string
          nullable: true
        content:
          type: string
          nullable: true
          description: Article body in Markdown.
        language:
          type: string
          minLength: 2
          maxLength: 2
          example: de
        released_at:
          type: string
          format: date-time
        published:
          type: boolean
          readOnly: true
        published_at:
          type: string
          format: date-time
          nullable: true
          readOnly: true
        created_at:
          type: string
          format: date-time
          readOnly: true
        updated_at:
          type: string
          format: date-time
          readOnly: true
        cents:
          type: integer
          minimum: 0
          description: Price in the smallest currency unit, for example cents for EUR.
        currency:
          type: string
          minLength: 3
          maxLength: 3
          example: EUR
        apple_product_identifier:
          type: string
          nullable: true
        google_product_identifier:
          type: string
          nullable: true
        subscription_required:
          type: boolean
        purchase_required:
          type: boolean
        image_thumbnail_url:
          type: string
          format: uri
          nullable: true
          readOnly: true
        image_feed_url:
          type: string
          format: uri
          nullable: true
          readOnly: true

    StoryInput:
      type: object
      properties:
        external_id:
          type: string
          nullable: true
        name:
          type: string
        title:
          type: string
          nullable: true
        preview:
          type: string
          nullable: true
        content:
          type: string
          nullable: true
        language:
          type: string
          minLength: 2
          maxLength: 2
          example: de
        released_at:
          type: string
          format: date-time
        cents:
          type: integer
          minimum: 0
        currency:
          type: string
          minLength: 3
          maxLength: 3
        apple_product_identifier:
          type: string
          nullable: true
        google_product_identifier:
          type: string
          nullable: true
        subscription_required:
          type: boolean
        purchase_required:
          type: boolean
        image:
          type: string
          format: binary
          description: |
            Uploaded via multipart/form-data. Accepted MIME types: image/jpeg, image/png, image/gif.
            Maximum file size: 5 MB.
      required:
        - name
        - released_at
        - cents
        - currency
        - language

    StoryEnvelope:
      type: object
      properties:
        story:
          $ref: "#/components/schemas/Story"

    StoryIndex:
      type: object
      properties:
        stories:
          type: array
          items:
            $ref: "#/components/schemas/Story"
        current_page:
          type: integer
        total_pages:
          type: integer
        total_count:
          type: integer
        per_page:
          type: integer
        next_page:
          type: integer
          nullable: true
        prev_page:
          type: integer
          nullable: true

    ValidationErrors:
      type: object
      additionalProperties:
        type: array
        items:
          type: string
      example:
        name:
          - "can't be blank"
        language:
          - "is not included in the list"

    BaseError:
      type: object
      properties:
        base:
          type: array
          items:
            type: string
      example:
        base:
          - "Article cannot be published"

security:
  - ApiToken: []

tags:
  - name: Article Import
    description: Create, update, fetch, list, delete, and publish article-based content.

# ---------------------------------------------------------------------------
# Paths
# ---------------------------------------------------------------------------
paths:
  /organizations/{organization_id}/publications/{publication_id}/stories:
    parameters:
      - $ref: "#/components/parameters/organizationId"
      - $ref: "#/components/parameters/publicationId"

    # -------------------------------------------------------------------------
    get:
      operationId: listStories
      summary: List articles for a publication
      tags: [Article Import]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: per
          in: query
          schema:
            type: integer
            default: 50
        - name: from
          in: query
          description: Return articles created on or after this timestamp (ISO 8601).
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          description: Return articles created on or before this timestamp (ISO 8601).
          schema:
            type: string
            format: date-time
        - name: external_id
          in: query
          description: |
            Filter by external_id. Accepts a single value or a comma-separated list;
            each element is matched via LIKE. Limited to the first 20 values.
          schema:
            type: string
        - name: search
          in: query
          description: Search across title, content, apple_product_identifier, and google_product_identifier.
          schema:
            type: string
      responses:
        "200":
          description: Paginated list of articles.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoryIndex"
        "401":
          description: Missing or invalid API token.

    # -------------------------------------------------------------------------
    post:
      operationId: createStory
      summary: Create an article
      tags: [Article Import]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                story:
                  $ref: "#/components/schemas/StoryInput"
              required: [story]
            example:
              story:
                name: "Example Article"
                title: "Example Article"
                preview: "Short article summary."
                content: "# Example Article\n\nArticle body in Markdown."
                external_id: "article-001"
                released_at: "2026-07-10T12:00:00Z"
                cents: 199
                currency: "EUR"
                language: "de"
                subscription_required: false
                purchase_required: false
          multipart/form-data:
            schema:
              type: object
              properties:
                story:
                  $ref: "#/components/schemas/StoryInput"
              required: [story]
      responses:
        "201":
          description: Article created successfully.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoryEnvelope"
        "401":
          description: Missing or invalid API token.
        "422":
          description: Validation failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrors"

  /organizations/{organization_id}/publications/{publication_id}/stories/{id}:
    parameters:
      - $ref: "#/components/parameters/organizationId"
      - $ref: "#/components/parameters/publicationId"
      - $ref: "#/components/parameters/storyId"

    # -------------------------------------------------------------------------
    get:
      operationId: showStory
      summary: Fetch a single article
      tags: [Article Import]
      responses:
        "200":
          description: Article found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoryEnvelope"
        "401":
          description: Missing or invalid API token.
        "404":
          description: Article not found or belongs to a different publication.

    # -------------------------------------------------------------------------
    put:
      operationId: updateStory
      summary: Replace article attributes
      tags: [Article Import]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                story:
                  $ref: "#/components/schemas/StoryInput"
              required: [story]
            example:
              story:
                name: "Updated Example Article"
                title: "Updated Example Article"
                preview: "Updated article summary."
                content: "# Updated Example Article\n\nUpdated Markdown body."
                external_id: "article-001"
                released_at: "2026-07-10T12:00:00Z"
                cents: 199
                currency: "EUR"
                language: "de"
                subscription_required: false
                purchase_required: true
          multipart/form-data:
            schema:
              type: object
              properties:
                story:
                  $ref: "#/components/schemas/StoryInput"
              required: [story]
      responses:
        "200":
          description: Article updated successfully.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoryEnvelope"
        "401":
          description: Missing or invalid API token.
        "404":
          description: Article not found.
        "422":
          description: Validation failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrors"

    patch:
      operationId: patchStory
      summary: Partially update article attributes
      tags: [Article Import]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                story:
                  $ref: "#/components/schemas/StoryInput"
              required: [story]
            example:
              story:
                title: "Updated Example Article"
                preview: "Updated article summary."
                purchase_required: true
      responses:
        "200":
          description: Article updated successfully.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoryEnvelope"
        "401":
          description: Missing or invalid API token.
        "404":
          description: Article not found.
        "422":
          description: Validation failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrors"

    # -------------------------------------------------------------------------
    delete:
      operationId: destroyStory
      summary: Delete an article
      description: |
        Deletes the article from normal API responses. Deleted articles are no
        longer returned by list or show requests.
      tags: [Article Import]
      responses:
        "204":
          description: Article deleted.
        "401":
          description: Missing or invalid API token.
        "404":
          description: Article not found.

  /organizations/{organization_id}/publications/{publication_id}/stories/{id}/publish:
    parameters:
      - $ref: "#/components/parameters/organizationId"
      - $ref: "#/components/parameters/publicationId"
      - $ref: "#/components/parameters/storyId"

    # -------------------------------------------------------------------------
    post:
      operationId: publishStory
      summary: Publish an article
      description: |
        Publishes the article for the configured publication. The article must
        satisfy all required fields and publishing prerequisites.
      tags: [Article Import]
      responses:
        "200":
          description: Article published successfully.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoryEnvelope"
        "401":
          description: Missing or invalid API token.
        "404":
          description: Article not found.
        "422":
          description: |
            Publish failed. Possible `base` messages include:
            - `"Article cannot be published"`
            - `"Unable to publish article"`
            Validation errors are returned as field-keyed objects when the story is invalid.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/BaseError"
                  - $ref: "#/components/schemas/ValidationErrors"
