openapi: 3.0.3
info:
  title: DxPrint Public Licences API
  version: 1.0.0
  contact:
    name: Suport DxPrint
    email: suport@dxprint.ro
  description: |
    API pentru listarea licențelor și emiterea licențelor trial.
    Autentificarea folosește headerul `X-API-Key`.

    Clienții trebuie să trimită cel mult o cerere pe secundă. La depășirea
    limitei, serviciul poate răspunde cu `429 rate_limited` și `Retry-After: 1`.
servers:
  - url: https://www.dxprint.ro/api
    description: API public DxPrint
security:
  - APIKey: []
paths:
  /licences/all:
    get:
      operationId: listLicences
      summary: Listează licențele contului
      parameters:
        - name: device
          in: query
          required: false
          description: Serie completă; compararea este exactă și nu diferențiază literele mari de cele mici.
          schema:
            type: string
        - name: licence_type
          in: query
          required: false
          schema:
            type: string
            enum: [trial, annual, permanent]
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 100
        - name: cursor
          in: query
          required: false
          description: Cursor opac returnat în `next_cursor`.
          schema:
            type: string
      responses:
        "200":
          description: Pagina de licențe
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LicenceListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /licences/trial:
    post:
      operationId: createTrialLicence
      summary: Creează sau returnează o licență trial activă
      requestBody:
        required: true
        description: Corpul JSON poate avea maximum 4 KiB.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TrialLicenceRequest"
      responses:
        "200":
          description: Licența trial activă existentă
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrialLicenceResponse"
        "201":
          description: Licența trial a fost creată
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestID"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrialLicenceResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "409":
          $ref: "#/components/responses/TrialUnavailable"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

components:
  securitySchemes:
    APIKey:
      type: apiKey
      in: header
      name: X-API-Key

  headers:
    RequestID:
      description: Identificatorul cererii.
      schema:
        type: string

  responses:
    BadRequest:
      description: Cerere sau cursor invalid
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestID"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            invalidRequest:
              value:
                error:
                  code: invalid_request
                  message: Cererea nu este validă.
                request_id: 9b52a0f4
            invalidCursor:
              value:
                error:
                  code: invalid_cursor
                  message: Cursorul nu este valid.
                request_id: 9b52a0f4
    Unauthorized:
      description: Cheie API lipsă, malformată, necunoscută sau revocată
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestID"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: invalid_api_key
              message: Cheia API nu este validă.
            request_id: 9b52a0f4
    TrialUnavailable:
      description: Seria a mai avut o licență trial care nu poate fi reutilizată
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestID"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: trial_unavailable
              message: Licența trial nu este disponibilă pentru această serie.
            request_id: 9b52a0f4
    RateLimited:
      description: Limita de o cerere pe secundă a fost depășită
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestID"
        Retry-After:
          description: Numărul de secunde după care cererea poate fi repetată.
          schema:
            type: integer
            example: 1
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: rate_limited
              message: Limita de cereri a fost depășită.
            request_id: 9b52a0f4
    InternalError:
      description: Eroare internă
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestID"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            error:
              code: internal_error
              message: A apărut o eroare internă.
            request_id: 9b52a0f4

  schemas:
    Licence:
      type: object
      additionalProperties: false
      required:
        - id
        - created_at
        - expires_at
        - device_sn
        - licence
        - is_expired
        - is_trial
        - licence_type
        - source
        - client_name
        - notes
      properties:
        id:
          type: integer
          format: int64
        created_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
          nullable: true
        device_sn:
          type: string
        licence:
          type: string
        is_expired:
          type: boolean
        is_trial:
          type: boolean
        licence_type:
          type: string
          enum: [trial, annual, permanent]
        source:
          type: string
          description: Sursa din care a fost creată licența.
          enum: [web, api]
        client_name:
          type: string
        notes:
          type: string

    LicenceListResponse:
      type: object
      additionalProperties: false
      required: [licences, limit, has_more, next_cursor]
      properties:
        licences:
          type: array
          items:
            $ref: "#/components/schemas/Licence"
        limit:
          type: integer
          minimum: 1
          maximum: 100
        has_more:
          type: boolean
        next_cursor:
          type: string
          description: Cursor pentru pagina următoare. Șir gol când nu mai există o pagină.
      example:
        licences:
          - id: 42
            created_at: "2026-09-04T09:14:22Z"
            expires_at: "2027-09-04T09:14:22Z"
            device_sn: DB3444
            licence: "..."
            is_expired: false
            is_trial: false
            licence_type: annual
            source: web
            client_name: Client SRL
            notes: ""
        limit: 100
        has_more: true
        next_cursor: NDI

    TrialLicenceRequest:
      type: object
      additionalProperties: false
      required: [device_sn]
      properties:
        device_sn:
          type: string
          minLength: 5
          maxLength: 15
      example:
        device_sn: DB3444

    TrialLicenceResponse:
      type: object
      additionalProperties: false
      required: [device_sn, licence, expires_at]
      properties:
        device_sn:
          type: string
        licence:
          type: string
        expires_at:
          type: string
          format: date-time
      example:
        device_sn: DB3444
        licence: "..."
        expires_at: "2026-09-18T09:14:22Z"

    ErrorResponse:
      type: object
      additionalProperties: false
      required: [error, request_id]
      properties:
        error:
          type: object
          additionalProperties: false
          required: [code, message]
          properties:
            code:
              type: string
              enum:
                - invalid_request
                - invalid_cursor
                - invalid_api_key
                - trial_unavailable
                - rate_limited
                - internal_error
            message:
              type: string
        request_id:
          type: string
