openapi: 3.1.0
info:
  title: Tramya Public API v1
  version: 1.1.0
  summary: API read-only pour consulter votre espace Tramya depuis un service tiers.
  description: |
    L'API publique v1 expose l'état de votre compte Tramya (plan, projets,
    conversations, mémoires) en lecture seule. Elle est destinée à alimenter
    des dashboards internes, des outils de veille métier, ou l'application
    Tramya Mobile PWA.

    ## Authentification
    Toutes les routes attendent un token Bearer. Créez-en un dans la
    console admin Tramya (section « Clés API v1 »). Le secret n'est
    affiché qu'une seule fois. Rotation et révocation immédiates.

    ## Scopes
    - `billing:read` — statut de licence et plan
    - `cloud:read` — état de la connexion Cloud
    - `workspace:read` — statistiques globales
    - `projects:read` — projets, conversations, mémoires, recherche

    ## Rate limits
    - 60 requêtes/minute par token pour tous les endpoints
    - 30 requêtes/minute par token pour `/search` (plus coûteux)

    ## Confidentialité
    Aucun contenu de conversation ni de mémoire n'est renvoyé au-delà
    d'un snippet de 200 caractères. Les IDs Stripe et les chemins
    filesystem sont masqués. Cette API ne peut jamais servir à extraire
    l'intégralité de vos données — pour ça, utilisez l'export local.

servers:
  - url: https://app.tramya.com
    description: Production
  - url: http://127.0.0.1:4317
    description: Local (compagnon Tramya)

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: tra_pk_...
  schemas:
    BillingStatus:
      type: object
      properties:
        plan: { type: string, enum: [free, local, pro, cloud, team] }
        features: { type: array, items: { type: string } }
        expiresAt: { type: [string, "null"], format: date-time }
        issuedAt: { type: [string, "null"], format: date-time }
        enforcing: { type: boolean }
        customerId: { type: [string, "null"], description: "Masqué (4 premiers + 4 derniers chars)" }
        subscriptionId: { type: [string, "null"], description: "Masqué" }
        plans:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              name: { type: string }
              price: { type: number }
              currency: { type: string }
              interval: { type: [string, "null"] }
    CloudStatus:
      type: object
      properties:
        connected: { type: boolean }
        lastBackupAt: { type: [string, "null"], format: date-time }
        accountId: { type: [string, "null"], description: "Masqué" }
        accountPlan: { type: [string, "null"] }
        deviceId: { type: [string, "null"], description: "Masqué" }
        sync: { type: object }
        remote:
          type: [object, "null"]
          properties:
            connected: { type: boolean }
            lastPollAt: { type: [string, "null"], format: date-time }
            lastCommandAt: { type: [string, "null"], format: date-time }
    WorkspaceStatus:
      type: object
      properties:
        projects: { type: integer }
        conversations: { type: integer }
        messages: { type: integer }
        tokens: { type: integer }
        memories: { type: integer }
        checkpoints: { type: integer }
        unclassified: { type: integer }
        sources: { type: array, items: { type: object } }
    Project:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        description: { type: [string, "null"] }
        conversationCount: { type: integer }
        memoryCount: { type: integer }
    ConversationSummary:
      type: object
      properties:
        id: { type: string }
        projectId: { type: [string, "null"] }
        source: { type: string, description: "claude-code, codex, gemini, cursor, ..." }
        title: { type: string }
        messageCount: { type: integer }
        tokens: { type: integer }
        createdAt: { type: [string, "null"], format: date-time }
        updatedAt: { type: [string, "null"], format: date-time }
    MemorySummary:
      type: object
      properties:
        id: { type: string }
        projectId: { type: [string, "null"] }
        source: { type: [string, "null"] }
        title: { type: [string, "null"] }
        snippet: { type: string, maxLength: 200 }
        tags: { type: array, items: { type: string }, maxItems: 20 }
        createdAt: { type: [string, "null"], format: date-time }
        updatedAt: { type: [string, "null"], format: date-time }
    SearchHit:
      type: object
      properties:
        type: { type: string, enum: [memory, checkpoint, conversation-message] }
        conversationId: { type: [string, "null"] }
        projectId: { type: [string, "null"] }
        title: { type: [string, "null"] }
        snippet: { type: string, maxLength: 200 }
        score: { type: number }
    Error:
      type: object
      properties:
        error: { type: string }

security:
  - BearerAuth: []

paths:
  /api/v1/public/billing/status:
    get:
      summary: Statut de la licence et catalogue des plans.
      tags: [Billing]
      security:
        - BearerAuth: [billing:read]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BillingStatus' }
        '401': { description: Token manquant ou invalide, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        '403': { description: Scope insuffisant }

  /api/v1/public/cloud/status:
    get:
      summary: État de la connexion Cloud du compagnon.
      tags: [Cloud]
      security:
        - BearerAuth: [cloud:read]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CloudStatus' }
        '401': { description: Non authentifié }
        '403': { description: Scope insuffisant }

  /api/v1/public/workspace/status:
    get:
      summary: Statistiques globales de votre espace.
      tags: [Workspace]
      security:
        - BearerAuth: [workspace:read]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/WorkspaceStatus' }

  /api/v1/public/projects:
    get:
      summary: Liste des projets (sans chemin filesystem).
      tags: [Projects]
      security:
        - BearerAuth: [projects:read]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Project' }

  /api/v1/public/conversations:
    get:
      summary: Liste des conversations récentes (métadonnées seulement).
      tags: [Projects]
      security:
        - BearerAuth: [projects:read]
      parameters:
        - name: projectId
          in: query
          required: false
          schema: { type: string }
          description: Filtre optionnel par projet.
        - name: limit
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 100, default: 30 }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/ConversationSummary' }

  /api/v1/public/memories:
    get:
      summary: Liste des mémoires enregistrées (snippets 200 chars).
      tags: [Projects]
      security:
        - BearerAuth: [projects:read]
      parameters:
        - name: projectId
          in: query
          required: false
          schema: { type: string }
        - name: limit
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 100, default: 30 }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/MemorySummary' }

  /api/v1/public/search:
    get:
      summary: Recherche full-text (rate-limité 30/min par token).
      tags: [Projects]
      security:
        - BearerAuth: [projects:read]
      parameters:
        - name: q
          in: query
          required: true
          schema: { type: string, minLength: 2, maxLength: 200 }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/SearchHit' }
        '400': { description: Query trop courte }
        '429': { description: Rate limit dépassé (30/min par token) }
