openapi: 3.1.0
info:
  title: PlusAI REST API
  version: v0
  description: |
    Public REST API for the Plus AI platform. It exposes presentation generation,
    the AI agent, and file management under the `/r/v0` path prefix.

    ## Authentication

    Every request must include an API key as a bearer token:

    ```
    Authorization: Bearer $PLUSAI_API_KEY
    ```

    Requests without a valid token receive `401`. The organization must be on a
    plan that includes API access.
  contact:
    name: Plus
    url: https://plusdocs.com

servers:
  - url: https://api.plusdocs.com
    description: Production

security:
  - apiKey: []

tags:
  - name: Presentation Agent
    description: Create and drive AI presentation agent sessions.
  - name: Files
    description: Upload, list, and delete user files.
  - name: Presentations
    description: Generate presentations from a text prompt.
  - name: User
    description: Information about the authenticated user.

paths:
  /r/v0/agent/sessions:
    post:
      operationId: createAgentSession
      tags: [Presentation Agent]
      summary: Create and start an AI agent session
      description: |
        Creates a new agent session, posts the initial prompt, and starts the
        agent. Returns a polling URL to track progress.

        Optionally seeds the session with an existing PowerPoint file
        (`pptxFileId`) and/or file attachments (`attachments[].fileId`). File
        IDs must reference files owned by the caller's organization (e.g.
        uploaded via `PUT /r/v0/files/upload`).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAgentSessionRequest'
      responses:
        '201':
          description: Session created and started.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateAgentSessionResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: Insufficient AI agent credits for the billing cycle.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentRequiredError'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /r/v0/agent/sessions/{sessionId}:
    get:
      operationId: getAgentSession
      tags: [Presentation Agent]
      summary: Get the current state of an agent session
      description: |
        Returns the current status of an agent session. Poll this endpoint
        until `status` reaches a terminal value. When the agent finishes,
        `pdfUrl`, `pptxUrl`, and `thumbnailUrl` are populated with download URLs.
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The current session state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentSession'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Session not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /r/v0/files/upload:
    put:
      operationId: uploadFile
      tags: [Files]
      summary: Upload a file
      description: |
        Uploads a file as `multipart/form-data`. The upload is validated and
        accepted synchronously, then processed asynchronously.

        Constraints:
        - The request method must be `PUT`.
        - `Content-Type` must be `multipart/form-data`.
        - `Content-Length` is required (chunked transfer encoding is not
          supported).
        - Maximum size is 125 MB.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: The file to upload.
              required: [file]
      responses:
        '202':
          description: File received; processing will begin shortly.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadAccepted'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '405':
          description: Method not allowed (must use PUT).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadError'
        '411':
          description: Content-Length header is required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadError'
        '413':
          description: File exceeds the size limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadError'
        '415':
          description: Content-Type must be multipart/form-data.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadError'

  /r/v0/files:
    get:
      operationId: listFiles
      tags: [Files]
      summary: List files
      description: |
        Lists the authenticated user's files in the organization, ordered by
        creation time (newest first). Supports cursor-based pagination.
      parameters:
        - name: limit
          in: query
          required: false
          description: Page size (1–100). Defaults to 50.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: cursor
          in: query
          required: false
          description: The `nextCursor` returned by a previous page.
          schema:
            type: string
      responses:
        '200':
          description: A page of files.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /r/v0/files/{fileId}:
    delete:
      operationId: deleteFile
      tags: [Files]
      summary: Delete a file
      description: |
        Deletes a file the caller owns.
        Returns `204` on success.
      parameters:
        - name: fileId
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Deletion accepted; no content.
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: File not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /r/v0/presentation:
    post:
      operationId: createPresentation
      tags: [Presentations]
      summary: Generate a presentation from a prompt
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePresentationRequest'
      responses:
        '201':
          description: Presentation generation started.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatePresentationResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          description: Concurrent presentation processing limit exceeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /r/v0/presentation/{presentationId}:
    get:
      operationId: getPresentation
      tags: [Presentations]
      summary: Get a presentation
      parameters:
        - name: presentationId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The presentation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Presentation'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Presentation not found.

  /r/v0/presentations:
    get:
      operationId: listPresentations
      tags: [Presentations]
      summary: List presentations
      description: Returns up to 50 of the presentations created via this API.
      responses:
        '200':
          description: A list of presentations.
          content:
            application/json:
              schema:
                type: object
                properties:
                  presentations:
                    type: array
                    items:
                      $ref: '#/components/schemas/Presentation'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: API key issued for your Plus organization, sent as a bearer token.

  responses:
    BadRequest:
      description: The request was malformed or missing required fields.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    InternalServerError:
      description: An unexpected error occurred.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

  schemas:
    Error:
      type: object
      properties:
        message:
          type: string
      required: [message]

    UploadError:
      type: object
      description: Error envelope returned by the file-upload endpoint.
      properties:
        error:
          type: string
      required: [error]

    PaymentRequiredError:
      type: object
      properties:
        code:
          type: string
          example: INSUFFICIENT_AGENT_CREDITS
        message:
          type: string
      required: [code, message]

    CreateAgentSessionRequest:
      type: object
      properties:
        prompt:
          type: string
          description: The initial instruction for the agent.
        pptxFileId:
          type: string
          description: |
            ID of an existing PowerPoint file (uploaded via the files API) to
            seed the session with. Must reference a `.pptx`/PowerPoint file.
        language:
          type: string
          description: Target language. Defaults to `auto`.
          default: auto
        attachments:
          type: array
          description: Files to attach to the initial message.
          items:
            type: object
            properties:
              fileId:
                type: string
            required: [fileId]
        callbackUrl:
          type: string
          format: uri
          description: |
            HTTPS URL invoked when the session changes state. Must be a public
            HTTPS address (no localhost/private IPs).
        model:
          type: string
          description: The model to use.
          enum:
            - claude-opus-4-8
            - claude-opus-4-7
            - claude-opus-4-6
            - claude-sonnet-4-6
            - claude-haiku-4-5
            - gpt-5.4
            - gpt-5.4-nano
            - gpt-5.4-mini
            - gpt-5.5
      required: [prompt]

    CreateAgentSessionResponse:
      type: object
      properties:
        sessionId:
          type: string
        pollingUrl:
          type: string
          format: uri
          description: URL to poll for session state.
      required: [sessionId, pollingUrl]

    AgentSession:
      type: object
      properties:
        sessionId:
          type: string
        status:
          type: string
          enum:
            - READY
            - IDLE
            - RUNNING
            - PENDING_TOOL_CALLS
            - DONE
            - INTERRUPTED
            - FAILED
        pdfUrl:
          type: [string, 'null']
          format: uri
          description: Download URL for the generated PDF (populated when finished).
        pptxUrl:
          type: [string, 'null']
          format: uri
          description: Download URL for the generated PPTX (populated when finished).
        thumbnailUrl:
          type: [string, 'null']
          format: uri
          description: Download URL for a thumbnail image (populated when finished).
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required: [sessionId, status]

    UploadAccepted:
      type: object
      properties:
        status:
          type: string
          example: accepted
        uploadId:
          type: string
        message:
          type: string
      required: [status, uploadId, message]

    FileListResponse:
      type: object
      properties:
        files:
          type: array
          items:
            $ref: '#/components/schemas/File'
        nextCursor:
          type: [string, 'null']
          description: Cursor for the next page, or null if there are no more files.
      required: [files, nextCursor]

    File:
      type: object
      properties:
        id:
          type: string
        name:
          type: [string, 'null']
        contentType:
          type: [string, 'null']
        urn:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required: [id, urn]

    CreatePresentationRequest:
      type: object
      properties:
        prompt:
          type: string
        language:
          type: string
          default: en
        numberOfSlides:
          type: integer
        textHandling:
          type: string
          description: How existing text content should be handled.
        includeUserTips:
          type: boolean
          default: false
        templateId:
          type: string
      required: [prompt]

    CreatePresentationResponse:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
        pollingUrl:
          type: string
          format: uri
      required: [id, status, pollingUrl]

    Presentation:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
        language:
          type: [string, 'null']
        slides:
          type: [array, 'null']
          items:
            type: string
          description: Slide titles.
        url:
          type: [string, 'null']
          format: uri
          description: URL to the full generated PPTX.
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required: [id, status]

    User:
      type: object
      properties:
        email:
          type: string
        name:
          type: [string, 'null']
