openapi: 3.0.3
info:
  title: AI Act Radar API
  version: 0.1.0
  description: |
    Structured webhook feed and JSON API for EU AI Act updates.

    **Information only — not legal advice.** AI Act Radar is a data and
    information service. It does not constitute legal advice, regulatory
    advice, or compliance certification, and is not a substitute for
    qualified legal counsel.

    All endpoints are authenticated with a Bearer token issued at customer
    onboarding. Tier-based rate limits apply.
  contact:
    name: AI Act Radar
    email: hello@aiactradar.com
    url: https://aiactradar.com
  license:
    name: Proprietary
    url: https://aiactradar.com/terms

servers:
  - url: https://api.aiactradar.com
    description: Production
  - url: https://api-staging.aiactradar.com
    description: Staging

security:
  - bearerAuth: []

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key (aiactr_…)

  schemas:
    Severity:
      type: string
      enum: [info, low, medium, high, critical]

    EventType:
      type: string
      enum: [guidance, standard, consultation, enforcement, national, court, meta]

    Topic:
      type: string
      enum:
        - gpai
        - high-risk
        - prohibited
        - transparency
        - data-governance
        - human-oversight
        - robustness
        - cybersecurity
        - fundamental-rights
        - standards
        - enforcement
        - sandbox
        - national-de
        - national-fr
        - national-nl
        - national-other
        - meta

    Language:
      type: string
      enum: [en, de, both]

    Event:
      type: object
      required: [id, source_id, event_type, topics, severity, title_en, summary_en, url_official, published_at]
      properties:
        id: { type: string, example: "evt_2026_0427_001" }
        source_id: { type: string, example: "aioffice" }
        event_type: { $ref: '#/components/schemas/EventType' }
        topics:
          type: array
          items: { $ref: '#/components/schemas/Topic' }
        severity: { $ref: '#/components/schemas/Severity' }
        title_en: { type: string }
        title_de: { type: string }
        summary_en: { type: string }
        summary_de: { type: string }
        diff_summary_en: { type: string }
        url_official: { type: string, format: uri }
        ai_act_articles:
          type: array
          items: { type: string, example: "Art. 53(1)(d)" }
        published_at: { type: integer, format: int64, description: "Unix ms" }
        ingested_at: { type: integer, format: int64 }
        version: { type: integer, minimum: 1 }
        language_versions:
          type: array
          items: { type: string }

    Subscription:
      type: object
      required: [id, webhook_url, topics]
      properties:
        id: { type: string }
        webhook_url: { type: string, format: uri }
        topics:
          type: array
          items: { $ref: '#/components/schemas/Topic' }
        source_filter:
          type: array
          items: { type: string }
          nullable: true
        severity_min: { $ref: '#/components/schemas/Severity' }
        language: { $ref: '#/components/schemas/Language' }
        enabled: { type: boolean }
        created_at: { type: integer, format: int64 }
        last_delivery_at: { type: integer, format: int64, nullable: true }

    SubscriptionCreate:
      type: object
      required: [webhook_url, topics]
      properties:
        webhook_url: { type: string, format: uri }
        topics:
          type: array
          items: { $ref: '#/components/schemas/Topic' }
        source_filter:
          type: array
          items: { type: string }
        severity_min: { $ref: '#/components/schemas/Severity' }
        language: { $ref: '#/components/schemas/Language' }

    SubscriptionCreateResponse:
      allOf:
        - $ref: '#/components/schemas/Subscription'
        - type: object
          properties:
            webhook_secret:
              type: string
              description: HMAC signing secret. Shown ONCE — store immediately.

    Source:
      type: object
      properties:
        id: { type: string }
        name_en: { type: string }
        name_de: { type: string }
        base_url: { type: string }
        fetch_strategy: { type: string }
        last_success_at: { type: integer, nullable: true }
        consecutive_failures: { type: integer }

    Health:
      type: object
      properties:
        status: { type: string, example: ok }
        timestamp: { type: string, format: date-time }
        events_total: { type: integer }
        sources:
          type: array
          items: { $ref: '#/components/schemas/Source' }

    WebhookPayload:
      type: object
      description: |
        The body delivered to the customer's `webhook_url` for each matched
        event. Signed with `X-AIActRadar-Signature: sha256=<hex>` using the
        subscription secret.
      properties:
        id: { type: string }
        type: { $ref: '#/components/schemas/EventType' }
        source: { type: string }
        topics:
          type: array
          items: { $ref: '#/components/schemas/Topic' }
        severity: { $ref: '#/components/schemas/Severity' }
        title_en: { type: string }
        title_de: { type: string }
        summary_en: { type: string }
        summary_de: { type: string }
        diff_to_previous: { type: string }
        url_official: { type: string }
        published_at: { type: string, format: date-time }
        ingested_at: { type: string, format: date-time }
        ai_act_articles:
          type: array
          items: { type: string }
        delivery:
          type: object
          properties:
            id: { type: string }
            attempt: { type: integer }
            subscription_id: { type: string }
        disclaimer: { type: string, example: "Information only — not legal advice." }

    Error:
      type: object
      properties:
        error: { type: string }
        message: { type: string }
        details: { type: object }

paths:
  /v1/health:
    get:
      summary: Pipeline health
      description: Public endpoint — no auth required.
      security: []
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Health' }

  /v1/topics:
    get:
      summary: List available topics
      security: []
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  topics:
                    type: array
                    items: { $ref: '#/components/schemas/Topic' }

  /v1/sources:
    get:
      summary: List active data sources
      security: []
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  sources:
                    type: array
                    items: { $ref: '#/components/schemas/Source' }

  /v1/events:
    get:
      summary: Query events
      parameters:
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 200, default: 50 }
        - in: query
          name: since
          schema: { type: integer, description: "Unix ms" }
        - in: query
          name: source
          schema: { type: string }
        - in: query
          name: severity_min
          schema: { $ref: '#/components/schemas/Severity' }
        - in: query
          name: topics
          schema: { type: string, description: "Comma-separated topics" }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  events:
                    type: array
                    items: { $ref: '#/components/schemas/Event' }
                  count: { type: integer }
                  disclaimer: { type: string }
        '401': { $ref: '#/components/schemas/Error' }
        '402': { $ref: '#/components/schemas/Error' }

  /v1/events/{id}:
    get:
      summary: Get event by id
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  event: { $ref: '#/components/schemas/Event' }
                  disclaimer: { type: string }
        '404': { description: not found }

  /v1/subscriptions:
    get:
      summary: List own subscriptions
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: object
                properties:
                  subscriptions:
                    type: array
                    items: { $ref: '#/components/schemas/Subscription' }
    post:
      summary: Create webhook subscription
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/SubscriptionCreate' }
      responses:
        '201':
          description: created
          content:
            application/json:
              schema:
                type: object
                properties:
                  subscription: { $ref: '#/components/schemas/SubscriptionCreateResponse' }
                  note: { type: string }
                  disclaimer: { type: string }
        '400': { description: invalid_input }
        '402': { description: tier_limit_reached }

  /v1/subscriptions/{id}:
    delete:
      summary: Delete subscription (soft — disables it)
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '200': { description: ok }
        '404': { description: not_found }

  /v1/webhooks/stripe:
    post:
      summary: Stripe webhook endpoint (Stripe-only)
      description: |
        Used internally by Stripe to notify us of subscription events.
        Authentication is via Stripe-Signature header (HMAC SHA-256).
      security: []
      responses:
        '200': { description: received }
        '400': { description: invalid }
        '401': { description: bad signature }
