openapi: 3.0.3
info:
  title: PrintMaster Server Integration API
  version: 0.1.0
  description: |
    Initial, verified subset of existing server HTTP endpoints; not a complete
    API inventory or a new anonymous service. Reviewed against PrintMaster
    commit 565f4c0762e467f083f54a9e0e7f6bc23ada56c3 (server/main.go).
    This contract version is independent of the program version. Existing UI
    endpoints have no separate stable public-API compatibility guarantee.
    Use your own HTTPS server. User sessions are not agent registration tokens.
  license:
    name: MIT
    url: https://github.com/Printmaster-Org/printmaster/blob/main/LICENSE
servers:
  - url: https://{host}
    description: Your HTTPS PrintMaster server, including port if needed
    variables:
      host:
        default: printmaster.example.com
security:
  - sessionBearer: []
  - sessionCookie: []
tags:
  - name: Authentication
  - name: Fleet
paths:
  /api/v1/auth/login:
    post:
      operationId: login
      tags: [Authentication]
      summary: Create a local user session
      description: Returns a session token and sets pm_session. Use SSO in the application when local credentials are unavailable. Successful sessions are created for 24 hours in this snapshot.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [username, password]
              properties:
                username: {type: string, minLength: 1}
                password: {type: string, minLength: 1, format: password, writeOnly: true}
      responses:
        '200':
          description: Session created
          headers:
            Set-Cookie:
              description: HttpOnly pm_session cookie; Secure when request is recognized as HTTPS; SameSite=Lax
              schema: {type: string}
          content:
            application/json:
              schema:
                type: object
                required: [success, token, expires_at]
                properties:
                  success: {type: boolean}
                  token: {type: string, description: Sensitive user session credential}
                  expires_at: {type: string, format: date-time}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '405': {$ref: '#/components/responses/MethodNotAllowed'}
        '500': {$ref: '#/components/responses/ServerError'}
  /api/v1/auth/me:
    get:
      operationId: currentUser
      tags: [Authentication]
      summary: Inspect current user session
      responses:
        '200':
          description: User and tenant context, without a password hash
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: {type: integer, format: int64}
                  username: {type: string}
                  role: {type: string, enum: [admin, operator, viewer]}
                  tenant_id: {type: string}
                  tenant_ids: {type: array, nullable: true, items: {type: string}}
                  created_at: {type: string, format: date-time}
                  session_token_hash: {type: string}
        '401': {$ref: '#/components/responses/Unauthorized'}
  /api/v1/auth/logout:
    post:
      operationId: logout
      tags: [Authentication]
      summary: Delete current session and expire cookie
      responses:
        '200':
          description: Session terminated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: {type: boolean}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '405': {$ref: '#/components/responses/MethodNotAllowed'}
  /api/v1/agents/list:
    get:
      operationId: listAgents
      tags: [Fleet]
      summary: List agents accessible to the user
      description: Requires agents.read authorization. Supplying limit enables pagination; omitting limit returns a legacy array. Invalid/nonpositive limit becomes 50, values over 200 clamp to 200; negative offset becomes zero. Agent token fields are blanked.
      parameters:
        - {$ref: '#/components/parameters/Limit'}
        - {$ref: '#/components/parameters/Offset'}
      responses:
        '200':
          description: Array without limit, paginated envelope with limit
          content:
            application/json:
              schema:
                oneOf:
                  - type: array
                    items: {$ref: '#/components/schemas/Agent'}
                  - allOf:
                      - {$ref: '#/components/schemas/Pagination'}
                      - type: object
                        required: [agents]
                        properties:
                          agents: {type: array, items: {$ref: '#/components/schemas/Agent'}}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '405': {$ref: '#/components/responses/MethodNotAllowed'}
        '500': {$ref: '#/components/responses/ServerError'}
  /api/v1/devices/list:
    get:
      operationId: listDevices
      tags: [Fleet]
      summary: List fleet devices with available latest metrics
      description: Requires devices.read authorization. Same pagination rules as agents/list. Schema intentionally describes only verified core fields; additional implementation fields may be returned. Tenant permissions are enforced by the deployed server, not this documentation site.
      parameters:
        - {$ref: '#/components/parameters/Limit'}
        - {$ref: '#/components/parameters/Offset'}
      responses:
        '200':
          description: Array without limit, paginated envelope with limit
          content:
            application/json:
              schema:
                oneOf:
                  - type: array
                    items: {$ref: '#/components/schemas/Device'}
                  - allOf:
                      - {$ref: '#/components/schemas/Pagination'}
                      - type: object
                        required: [devices]
                        properties:
                          devices: {type: array, items: {$ref: '#/components/schemas/Device'}}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '405': {$ref: '#/components/responses/MethodNotAllowed'}
        '500': {$ref: '#/components/responses/ServerError'}
components:
  securitySchemes:
    sessionBearer:
      type: http
      scheme: bearer
      description: User session token returned by login; not a JWT or an agent token. Exact Bearer prefix required.
    sessionCookie:
      type: apiKey
      in: cookie
      name: pm_session
      description: Alternative to bearer authentication, not an additional requirement.
  parameters:
    Limit:
      name: limit
      in: query
      description: Include to request paginated results. Use 1–200; invalid/nonpositive values become 50.
      schema: {type: integer, minimum: 1, maximum: 200}
    Offset:
      name: offset
      in: query
      description: Used only with limit. Invalid or negative values become zero.
      schema: {type: integer, minimum: 0, default: 0}
  schemas:
    Agent:
      type: object
      additionalProperties: true
      properties:
        id: {type: integer, format: int64}
        agent_id: {type: string}
        name: {type: string}
        hostname: {type: string}
        version: {type: string}
        tenant_id: {type: string}
        status: {type: string}
        last_seen: {type: string, format: date-time}
        connection_type: {type: string}
        site_ids: {type: array, items: {type: string}}
        token: {type: string, description: Always blank in list response}
    Device:
      type: object
      additionalProperties: true
      properties:
        serial: {type: string}
        agent_id: {type: string}
        manufacturer: {type: string}
        model: {type: string}
    Pagination:
      type: object
      required: [total_count, has_more, limit, offset]
      properties:
        total_count: {type: integer, format: int64}
        has_more: {type: boolean}
        limit: {type: integer}
        offset: {type: integer}
  responses:
    BadRequest:
      description: Invalid JSON or missing credentials
      content: {text/plain: {schema: {type: string}}}
    Unauthorized:
      description: Missing/invalid session or credentials
      content: {text/plain: {schema: {type: string}}}
    Forbidden:
      description: Insufficient role or tenant access
      content: {text/plain: {schema: {type: string}}}
    MethodNotAllowed:
      description: Unsupported HTTP method
      content: {text/plain: {schema: {type: string}}}
    ServerError:
      description: Storage or session creation failed
      content: {text/plain: {schema: {type: string}}}