openapi: 3.0.3
info:
  title: Reflecta Fördermittelkompass Partner API
  version: 1.0.0
  description: |
    The Partner API allows authorized partners to access:
    - Grant programs and grant providers
    - AI-powered matching of organizations to grant programs
    - Reference data (regions, grant areas, eligible entities)
    - Usage statistics and quotas
    
    ## Authentication
    
    All endpoints require a Bearer token in the Authorization header:
    ```
    Authorization: Bearer rfk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    ```
    
    ## Rate Limiting
    
    - 100 requests per minute
    - Quotas based on the partner tier
    - Response headers contain rate limit information
    
    ## Error Handling
    
    All error responses follow this format:
    ```json
    {
      "error": {
        "code": "error_code",
        "message": "Human-readable error message",
        "details": {}
      }
    }
    ```
    
servers:
  - url: https://foerdermittelkompass.reflecta.org/api/v1
    description: Production server
  - url: http://localhost:5001/api/v1
    description: Local development server

tags:
  - name: Grants
    description: Retrieve and search grant programs
  - name: Grant Providers
    description: Retrieve grant providers
  - name: Matching
    description: AI-powered matching of organizations to grant programs
  - name: Reference Data
    description: Reference data (regions, grant areas, eligible entities)
  - name: Usage
    description: Usage statistics and quotas

security:
  - BearerAuth: []

paths:
  /grants:
    get:
      tags:
        - Grants
      summary: List grant programs
      description: |
        Returns a list of grant programs, filtered by the partner's allowed regions and grant areas.
        
        Supports full-text search, filtering, and pagination.
      operationId: listGrants
      parameters:
        - name: region_ids[]
          in: query
          description: Filter by region IDs (multiple allowed)
          schema:
            type: array
            items:
              type: string
              format: uuid
        - name: grant_area_ids[]
          in: query
          description: Filter by grant area IDs (multiple allowed)
          schema:
            type: array
            items:
              type: string
              format: uuid
        - name: grant_provider_id
          in: query
          description: Filter by grant provider ID
          schema:
            type: string
            format: uuid
        - name: grant_type
          in: query
          description: Filter by grant type
          schema:
            type: string
            enum: [participation, surety, loan, guarantee, subsidy, sponsorship, other, award]
        - name: q
          in: query
          description: Full-text search in title and description
          schema:
            type: string
        - name: page
          in: query
          description: Page number
          schema:
            type: integer
            default: 1
            minimum: 1
        - name: per_page
          in: query
          description: Results per page
          schema:
            type: integer
            default: 25
            minimum: 1
            maximum: 100
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  grants:
                    type: array
                    items:
                      $ref: '#/components/schemas/GrantSummary'
                  meta:
                    $ref: '#/components/schemas/PaginationMeta'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'

  /grants/{id}:
    get:
      tags:
        - Grants
      summary: Get a single grant program
      description: Returns detailed information about a grant program
      operationId: getGrant
      parameters:
        - name: id
          in: path
          required: true
          description: UUID or slug of the grant program
          schema:
            type: string
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  grant:
                    $ref: '#/components/schemas/GrantDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /grant_providers:
    get:
      tags:
        - Grant Providers
      summary: List grant providers
      description: Returns a list of grant providers
      operationId: listGrantProviders
      parameters:
        - name: grant_provider_type
          in: query
          description: Filter by grant provider type
          schema:
            type: string
            enum: [
              federal_ministry,
              state_ministry,
              municipality,
              public_agency,
              european_union,
              federal_funding_bank,
              private_foundation,
              public_foundation,
              political_foundation,
              industry_chamber,
              research_institution,
              university,
              non_profit_organization,
              church_organization,
              federal_employment_agency,
              european_funding_bank
            ]
        - name: region_id
          in: query
          description: Filter by region
          schema:
            type: string
            format: uuid
        - name: q
          in: query
          description: Full-text search
          schema:
            type: string
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          schema:
            type: integer
            default: 25
            maximum: 100
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  grant_providers:
                    type: array
                    items:
                      $ref: '#/components/schemas/GrantProviderSummary'
                  meta:
                    $ref: '#/components/schemas/PaginationMeta'

  /grant_providers/{id}:
    get:
      tags:
        - Grant Providers
      summary: Get a single grant provider
      operationId: getGrantProvider
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  grant_provider:
                    $ref: '#/components/schemas/GrantProviderDetail'

  /matching:
    post:
      tags:
        - Matching
      summary: Start AI matching
      description: |
        Starts an asynchronous matching process for an organization.
        
        The matching runs in the background. The response contains a `matching_request_id`
        that can be used to retrieve the status.
      operationId: createMatching
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MatchingRequest'
            example:
              organization_id: "partner-org-123"
              organization_profile:
                name: "Example Association"
                description: "We promote education and culture for young people in Munich"
                regions: ["bavaria"]
                grant_areas: ["culture", "education"]
                eligible_entities: ["registered_association"]
                is_non_profit: true
                mission: "Education and culture for all"
                target_groups: ["youth"]
                keywords: ["music", "theater", "workshops"]
                annual_budget: "100000"
                number_of_employees: "5"
                locale: "en"
              project:
                title: "Music workshops for young people"
                description: "12-month program with weekly workshops"
                budget: "50000"
                duration: "12 months"
      responses:
        '202':
          description: Matching has been started
          content:
            application/json:
              schema:
                type: object
                properties:
                  matching_request_id:
                    type: string
                    format: uuid
                  status:
                    type: string
                    enum: [pending]
                  organization_id:
                    type: string
                  status_url:
                    type: string
                  results_url:
                    type: string
                  message:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'

  /matching/{id}:
    get:
      tags:
        - Matching
      summary: Get matching status
      description: Returns the current status and results of a matching request
      operationId: getMatchingStatus
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MatchingResponse'

  /matching/{id}/results:
    get:
      tags:
        - Matching
      summary: Get matching results
      description: Returns only the matching results (for completed requests)
      operationId: getMatchingResults
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  matching_request_id:
                    type: string
                    format: uuid
                  organization_id:
                    type: string
                  results_count:
                    type: integer
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/MatchResult'
                  quota:
                    $ref: '#/components/schemas/QuotaInfo'

  /regions:
    get:
      tags:
        - Reference Data
      summary: List regions
      description: |
        Returns a hierarchical list of regions (continent → country → state).
        
        Use `flat=true` to get a flat list instead.
      operationId: listRegions
      parameters:
        - name: type
          in: query
          schema:
            type: string
            enum: [continent, country, state]
        - name: country_id
          in: query
          schema:
            type: string
            format: uuid
        - name: flat
          in: query
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  regions:
                    type: array
                    items:
                      $ref: '#/components/schemas/Region'
                  meta:
                    type: object

  /regions/{id}:
    get:
      tags:
        - Reference Data
      summary: Get a single region
      operationId: getRegion
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  region:
                    $ref: '#/components/schemas/RegionDetail'

  /grant_areas:
    get:
      tags:
        - Reference Data
      summary: List grant areas
      description: Returns a hierarchical list of grant areas
      operationId: listGrantAreas
      parameters:
        - name: flat
          in: query
          description: If "true", returns a flat list instead of hierarchical
          schema:
            type: boolean
            default: false
        - name: include_parent
          in: query
          description: When flat=true, whether to include parent areas
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  grant_areas:
                    type: array
                    items:
                      $ref: '#/components/schemas/GrantArea'

  /grant_areas/{id}:
    get:
      tags:
        - Reference Data
      summary: Get a single grant area
      operationId: getGrantArea
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  grant_area:
                    $ref: '#/components/schemas/GrantAreaDetail'

  /eligible_entities:
    get:
      tags:
        - Reference Data
      summary: List eligible entities
      description: Returns a list of all available eligible entity types
      operationId: listEligibleEntities
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  eligible_entities:
                    type: array
                    items:
                      $ref: '#/components/schemas/EligibleEntity'

  /eligible_entities/{id}:
    get:
      tags:
        - Reference Data
      summary: Get a single eligible entity
      operationId: getEligibleEntity
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  eligible_entity:
                    $ref: '#/components/schemas/EligibleEntityDetail'

  /usage/stats:
    get:
      tags:
        - Usage
      summary: Usage statistics
      description: Returns aggregated usage statistics
      operationId: getUsageStats
      parameters:
        - name: period
          in: query
          schema:
            type: string
            enum: [day, week, month, quarter, year]
            default: quarter
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UsageStats'

  /usage/quota:
    get:
      tags:
        - Usage
      summary: Quota status
      description: Returns the current quota status
      operationId: getUsageQuota
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QuotaStatus'

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        API token in the format: `rfk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
        
        Tokens can be managed in the API portal at `/api/portal/tokens`.

  schemas:
    GrantSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        title:
          type: string
        short_description:
          type: string
        grant_type:
          type: string
          enum: [participation, surety, loan, guarantee, subsidy, sponsorship, other, award]
        grant_provider:
          type: object
          nullable: true
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            slug:
              type: string
        end_date:
          type: string
          format: date
          nullable: true
        ongoing_grant:
          type: boolean
        funding_amount:
          type: string
          nullable: true
        max_funding_amount:
          type: number
          nullable: true
        regions:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
        grant_areas:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
        updated_at:
          type: string
          format: date-time

    GrantDetail:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        title:
          type: string
        short_description:
          type: string
        description:
          type: string
        grant_type:
          type: string
          enum: [participation, surety, loan, guarantee, subsidy, sponsorship, other, award]
        grant_provider:
          type: object
          nullable: true
        contact_point:
          type: object
          nullable: true
        start_date:
          type: string
          format: date
          nullable: true
        end_date:
          type: string
          format: date
          nullable: true
        end_date_description:
          type: string
          nullable: true
        ongoing_grant:
          type: boolean
        earliest_project_start:
          type: string
          format: date
          nullable: true
        project_duration:
          type: string
          nullable: true
        funding_amount:
          type: string
          nullable: true
        max_funding_amount:
          type: number
          nullable: true
        funding_rate:
          type: string
          nullable: true
        funding_goal:
          type: string
          nullable: true
        eligibility_requirements:
          type: string
          nullable: true
        target_group_description:
          type: string
          nullable: true
        non_profit_only:
          type: boolean
        company_size:
          type: string
          nullable: true
        eligible_entities:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
        eligible_expenditures:
          type: string
          nullable: true
        non_eligible_expenditures:
          type: string
          nullable: true
        eligible_expenditure_categories:
          type: array
          items:
            type: string
        evaluation_criteria:
          type: string
          nullable: true
        documents_needed:
          type: string
          nullable: true
        application_level:
          type: string
          nullable: true
        application_links:
          type: array
          items:
            type: string
        guideline_links:
          type: array
          items:
            type: string
        reporting_and_claims_links:
          type: array
          items:
            type: string
        regions:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
              region_type:
                type: string
        grant_areas:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
              parent_id:
                type: string
                format: uuid
                nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    GrantProviderSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        name:
          type: string
        grant_provider_type:
          type: string
          enum: [
            federal_ministry,
            state_ministry,
            municipality,
            public_agency,
            european_union,
            federal_funding_bank,
            private_foundation,
            public_foundation,
            political_foundation,
            industry_chamber,
            research_institution,
            university,
            non_profit_organization,
            church_organization,
            federal_employment_agency,
            european_funding_bank
          ]
        website:
          type: string
          nullable: true
        city:
          type: string
          nullable: true
        grants_count:
          type: integer

    GrantProviderDetail:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        name:
          type: string
        grant_provider_type:
          type: string
          enum: [
            federal_ministry,
            state_ministry,
            municipality,
            public_agency,
            european_union,
            federal_funding_bank,
            private_foundation,
            public_foundation,
            political_foundation,
            industry_chamber,
            research_institution,
            university,
            non_profit_organization,
            church_organization,
            federal_employment_agency,
            european_funding_bank
          ]
        description:
          type: string
          nullable: true
        mission_statement:
          type: string
          nullable: true
        target_group_description:
          type: string
          nullable: true
        website:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
        phone:
          type: string
          nullable: true
        fax:
          type: string
          nullable: true
        city:
          type: string
          nullable: true
        address:
          type: object
          nullable: true
          properties:
            street_and_number:
              type: string
            address_line_2:
              type: string
            postal_code:
              type: string
            city:
              type: string
            state:
              type: string
            country:
              type: string
            complete_address:
              type: string
        geo_location:
          type: object
          nullable: true
          properties:
            latitude:
              type: number
            longitude:
              type: number
            federal_state:
              type: string
            federal_state_code:
              type: string
            county:
              type: string
        social_media:
          type: object
          nullable: true
          properties:
            linkedin:
              type: string
            facebook:
              type: string
            instagram:
              type: string
            youtube:
              type: string
        grants_count:
          type: integer
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    MatchingRequest:
      type: object
      required:
        - organization_id
        - organization_profile
      properties:
        organization_id:
          type: string
          description: External organization ID of the partner
        organization_profile:
          type: object
          required:
            - name
            - description
          properties:
            name:
              type: string
            description:
              type: string
            regions:
              type: array
              items:
                type: string
              description: Names, slugs, or UUIDs
            grant_areas:
              type: array
              items:
                type: string
              description: Names, slugs, or UUIDs
            eligible_entities:
              type: array
              items:
                type: string
              description: Names, slugs, or UUIDs
            is_non_profit:
              type: boolean
            mission:
              type: string
            target_groups:
              type: array
              items:
                type: string
            keywords:
              type: array
              items:
                type: string
            annual_budget:
              type: string
            number_of_employees:
              type: string
            locale:
              type: string
              enum: [de, en]
              default: de
        project:
          type: object
          properties:
            title:
              type: string
            description:
              type: string
            budget:
              type: string
            duration:
              type: string

    MatchingResponse:
      type: object
      properties:
        matching_request_id:
          type: string
          format: uuid
        status:
          type: string
          enum: [pending, processing, completed, failed]
        organization_id:
          type: string
        created_at:
          type: string
          format: date-time
        started_at:
          type: string
          format: date-time
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
        processing_time_ms:
          type: integer
          nullable: true
        results_count:
          type: integer
        error_message:
          type: string
          nullable: true
        results:
          type: array
          items:
            $ref: '#/components/schemas/MatchResult'
        results_url:
          type: string

    MatchResult:
      type: object
      properties:
        id:
          type: string
          format: uuid
        rank:
          type: integer
          nullable: true
        grant_id:
          type: string
          format: uuid
        title:
          type: string
        relevance_score:
          type: number
          format: float
          minimum: 0
          maximum: 100
        match_category:
          type: string
          enum: [not_suitable, conditionally_suitable, partially_suitable, very_suitable]
        match_reason:
          type: string
          nullable: true

    Region:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        name_de:
          type: string
        name_en:
          type: string
        region_type:
          type: string
          enum: [continent, country, state]
        countries:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
              name_de:
                type: string
              name_en:
                type: string
              region_type:
                type: string
              states:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      format: uuid
                    name:
                      type: string
                    name_de:
                      type: string
                    name_en:
                      type: string
                    region_type:
                      type: string

    RegionDetail:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        name_de:
          type: string
        name_en:
          type: string
        region_type:
          type: string
          enum: [continent, country, state]
        parent:
          type: object
          nullable: true
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            name_de:
              type: string
            name_en:
              type: string
            region_type:
              type: string
        subregions:
          type: array
          nullable: true
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
              name_de:
                type: string
              name_en:
                type: string
              region_type:
                type: string
              parent_id:
                type: string
                format: uuid
                nullable: true
              parent_name:
                type: string
                nullable: true

    GrantArea:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        name_de:
          type: string
        name_en:
          type: string
        subareas:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
              name_de:
                type: string
              name_en:
                type: string

    GrantAreaDetail:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        name_de:
          type: string
        name_en:
          type: string
        is_parent:
          type: boolean
        parent:
          type: object
          nullable: true
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            name_de:
              type: string
            name_en:
              type: string
        subareas:
          type: array
          nullable: true
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
              name_de:
                type: string
              name_en:
                type: string
              parent_id:
                type: string
                format: uuid
              parent_name:
                type: string
              is_parent:
                type: boolean

    EligibleEntity:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        name_de:
          type: string
        name_en:
          type: string
        description:
          type: string
          nullable: true
        description_de:
          type: string
          nullable: true
        description_en:
          type: string
          nullable: true

    EligibleEntityDetail:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        name_de:
          type: string
        name_en:
          type: string
        name_plural:
          type: string
        name_plural_de:
          type: string
        name_plural_en:
          type: string
        description:
          type: string
          nullable: true
        description_de:
          type: string
          nullable: true
        description_en:
          type: string
          nullable: true
        grants_count:
          type: integer

    UsageStats:
      type: object
      properties:
        stats:
          type: object
          properties:
            total_requests:
              type: integer
            successful_requests:
              type: integer
            failed_requests:
              type: integer
            matching_requests:
              type: integer
            grants_requests:
              type: integer
            active_organizations:
              type: integer
            average_response_time_ms:
              type: integer
            by_endpoint:
              type: object
              additionalProperties:
                type: integer
            by_day:
              type: array
              items:
                type: object
                properties:
                  date:
                    type: string
                    format: date
                  requests:
                    type: integer
                  matching_requests:
                    type: integer
        period:
          type: string
        partner:
          type: object
          properties:
            name:
              type: string
            tier:
              type: string

    QuotaStatus:
      type: object
      properties:
        quota:
          type: object
          properties:
            quarter:
              type: string
            tier:
              type: string
            active_organizations:
              type: object
              properties:
                current:
                  type: integer
                limit:
                  type: integer
                percentage:
                  type: number
            matching_requests:
              type: object
              properties:
                current:
                  type: integer
                limit:
                  type: integer
                percentage:
                  type: number
            status:
              type: string
              enum: [ok, warning, exceeded]
            warning:
              type: boolean
            exceeded:
              type: boolean

    QuotaInfo:
      type: object
      properties:
        matching_requests_used:
          type: integer
        matching_requests_limit:
          type: integer
        usage_percentage:
          type: number

    PaginationMeta:
      type: object
      properties:
        current_page:
          type: integer
        per_page:
          type: integer
        total_pages:
          type: integer
        total_count:
          type: integer

    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
            message:
              type: string
            details:
              type: object

  responses:
    Unauthorized:
      description: Unauthorized - Missing or invalid token
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: unauthorized
              message: Missing or invalid authentication token

    Forbidden:
      description: Forbidden - Insufficient permissions
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: insufficient_scope
              message: This endpoint requires the 'grants:read' scope

    NotFound:
      description: Not Found - Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: not_found
              message: Resource not found

    BadRequest:
      description: Bad Request - Invalid request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: invalid_parameter
              message: organization_id is required

    RateLimitExceeded:
      description: Rate Limit Exceeded - Too many requests
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: rate_limit_exceeded
              message: Too many requests. Please try again later.
