openapi: 3.1.0
info:
  title: SCPI Radar Basic Facts API
  version: v1
  description: |
    API factuelle versionnée pour les SCPI suivies par SCPI Radar.
    Les réponses sont construites par liste blanche : elles n'exposent aucun avis,
    condition d'achat, prix cible, analyse éditoriale, note de gérant ou charte interne.
servers:
  - url: https://api.scpiradar.fr
    description: Production
  - url: https://api.staging-scpiradar.fr
    description: Staging
security:
  - bearerAuth: []
paths:
  /api/v1/scpi:
    get:
      operationId: listBasicFacts
      summary: Liste les SCPI Basic Facts suivies
      description: |
        Recherche insensible aux accents dans l'identifiant, le nom de la SCPI,
        l'identifiant du gérant et le nom du gérant. L'univers retourné est
        limité aux SCPI présentes dans le référentiel public SCPI Radar.
      parameters:
        - $ref: '#/components/parameters/Search'
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
      responses:
        '200':
          description: Liste paginée de SCPI factuelles.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FundListResponse'
        '400':
          $ref: '#/components/responses/InvalidPagination'
        '401':
          $ref: '#/components/responses/InvalidApiKey'
        '403':
          $ref: '#/components/responses/SubscriptionRequired'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
  /api/v1/scpi/{id}:
    get:
      operationId: getBasicFacts
      summary: Retourne la fiche factuelle d'une SCPI
      parameters:
        - name: id
          in: path
          required: true
          description: Identifiant stable de la SCPI.
          schema:
            type: string
            pattern: '^[a-z0-9]+(?:-[a-z0-9]+)*$'
          example: pierval-sante
      responses:
        '200':
          description: Fiche factuelle et son historique disponible.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FundResponse'
        '401':
          $ref: '#/components/responses/InvalidApiKey'
        '403':
          $ref: '#/components/responses/SubscriptionRequired'
        '404':
          $ref: '#/components/responses/FundNotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: 'Clé API créée dans le portail client, transmise avec `Authorization: Bearer <clé>`.'
  parameters:
    Search:
      name: search
      in: query
      required: false
      description: Terme de recherche par nom, gérant ou identifiant.
      schema:
        type: string
      example: santé
    Page:
      name: page
      in: query
      required: false
      description: 'Numéro de page, entier positif. Par défaut : 1.'
      schema:
        type: integer
        minimum: 1
        maximum: 1000000
        default: 1
    PerPage:
      name: per_page
      in: query
      required: false
      description: 'Nombre de résultats par page, entier positif. Par défaut : 20 ; maximum : 100.'
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
  headers:
    RateLimitLimit:
      description: Limite quotidienne de requêtes pour le compte authentifié.
      schema:
        type: integer
    RateLimitRemaining:
      description: Requêtes quotidiennes restantes pour le compte authentifié.
      schema:
        type: integer
  responses:
    InvalidPagination:
      description: Paramètre `page` ou `per_page` invalide, ou page hors résultat.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            invalidPagination:
              value:
                error:
                  code: invalid_pagination
                  message: page and per_page must be positive integers.
            pageOutOfRange:
              value:
                error:
                  code: page_out_of_range
                  message: page exceeds the available result pages.
    InvalidApiKey:
      description: Clé API absente ou invalide.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            invalidApiKey:
              value:
                error:
                  code: invalid_api_key
                  message: Clé API invalide.
    SubscriptionRequired:
      description: Essai ou abonnement inactif.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            subscriptionRequired:
              value:
                error:
                  code: subscription_required
                  message: Abonnement inactif.
    FundNotFound:
      description: Identifiant absent de l'univers Basic Facts.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            notFound:
              value:
                error:
                  code: not_found
                  message: SCPI not found in the Basic Facts coverage.
    RateLimitExceeded:
      description: Quota dépassé.
      headers:
        Retry-After:
          description: Délai indicatif avant une nouvelle tentative, en secondes.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            rateLimit:
              value:
                error:
                  code: rate_limit
                  message: Quota dépassé.
  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
            message:
              type: string
    FundListResponse:
      type: object
      required: [version, generatedAt, coverage, pagination, data]
      properties:
        version:
          type: string
          const: v1
        generatedAt:
          type: string
          format: date-time
          description: Date de génération de cette réponse, distincte de la date des données.
        coverage:
          $ref: '#/components/schemas/Coverage'
        pagination:
          $ref: '#/components/schemas/Pagination'
        data:
          type: array
          items:
            $ref: '#/components/schemas/FundListItem'
    FundResponse:
      type: object
      required: [version, generatedAt, data]
      properties:
        version:
          type: string
          const: v1
        generatedAt:
          type: string
          format: date-time
        data:
          $ref: '#/components/schemas/Fund'
    Coverage:
      type: object
      required: [trackedFunds, description]
      properties:
        trackedFunds:
          type: integer
          minimum: 0
        description:
          type: string
    Pagination:
      type: object
      required: [page, perPage, total, totalPages]
      properties:
        page:
          type: integer
        perPage:
          type: integer
        total:
          type: integer
        totalPages:
          type: integer
    FundListItem:
      type: object
      required: [id, name, manager, strategy, geography, dataAsOf, reviewedAt]
      properties:
        id:
          type: string
        name:
          type: string
        manager:
          $ref: '#/components/schemas/Manager'
        strategy:
          type: string
        geography:
          type: string
        dataAsOf:
          type: string
          format: date
        reviewedAt:
          type: string
          format: date
    Fund:
      allOf:
        - $ref: '#/components/schemas/FundListItem'
        - type: object
          required: [website, creationYear, prices, metrics, history]
          properties:
            website:
              type: string
              format: uri
            creationYear:
              $ref: '#/components/schemas/YearValue'
            prices:
              type: object
              required: [subscription, realization, reconstitution]
              properties:
                subscription:
                  $ref: '#/components/schemas/MoneyValue'
                sponsor:
                  description: Prix sponsor annoncé lorsque le fonds comporte une offre de lancement, sous réserve des conditions et de disponibilité.
                  $ref: '#/components/schemas/MoneyValue'
                realization:
                  $ref: '#/components/schemas/MoneyValue'
                reconstitution:
                  $ref: '#/components/schemas/MoneyValue'
            metrics:
              type: object
              required: [occupancy, debt]
              properties:
                occupancy:
                  $ref: '#/components/schemas/PercentValue'
                debt:
                  $ref: '#/components/schemas/PercentValue'
            history:
              type: array
              description: Historique disponible ; les lacunes de valeur restent à null.
              items:
                $ref: '#/components/schemas/HistoryPoint'
    Manager:
      type: [object, 'null']
      required: [id, name, website, reviewedAt, sources]
      properties:
        id:
          type: string
        name:
          type: string
        website:
          type: string
          format: uri
        reviewedAt:
          type: string
          format: date
        sources:
          type: array
          items:
            $ref: '#/components/schemas/ManagerSource'
    ManagerSource:
      type: object
      required: [label, url]
      properties:
        label:
          type: string
        url:
          type: string
          format: uri
    Source:
      type: [object, 'null']
      required: [id, label, url, period]
      properties:
        id:
          type: string
        label:
          type: string
        url:
          type: string
          format: uri
        period:
          type: string
          format: date
    Value:
      type: object
      required: [value, unit, dataAsOf, source, basis]
      properties:
        value:
          nullable: true
        unit:
          type: string
        dataAsOf:
          type: string
          format: date
        source:
          $ref: '#/components/schemas/Source'
        basis:
          type: [string, 'null']
      description: Une valeur inconnue est null ; elle n'est jamais remplacée par zéro.
    YearValue:
      allOf:
        - $ref: '#/components/schemas/Value'
        - type: object
          properties:
            value:
              type: [integer, 'null']
            unit:
              const: year
    MoneyValue:
      allOf:
        - $ref: '#/components/schemas/Value'
        - type: object
          properties:
            value:
              type: [number, 'null']
            unit:
              const: EUR per share
            basis:
              enum:
                [subscription price, realization value, reconstitution value]
    PercentValue:
      allOf:
        - $ref: '#/components/schemas/Value'
        - type: object
          properties:
            value:
              type: [number, 'null']
            unit:
              const: percent
    HistoryPoint:
      type: object
      required: [date, source, prices]
      properties:
        date:
          type: string
          format: date
        source:
          $ref: '#/components/schemas/Source'
        prices:
          type: object
          required: [subscription, realization, reconstitution]
          properties:
            subscription:
              $ref: '#/components/schemas/MoneyValue'
            realization:
              $ref: '#/components/schemas/MoneyValue'
            reconstitution:
              $ref: '#/components/schemas/MoneyValue'
