openapi: 3.1.0
info:
  title: Pratik Gümrük API
  version: 0.1.0
  description: |
    AI-assisted customs operations platform — case-driven workflows for import/export.
    All `/api/v1/*` endpoints except `/auth/*`, `/healthz`, `/readyz` require Bearer JWT.
servers:
  - url: http://localhost:8080/api/v1
    description: Local development
security:
  - bearerAuth: []
tags:
  - name: Auth
  - name: Profile
  - name: Cases
  - name: Customers
  - name: Documents
  - name: GTIP
  - name: Tax
  - name: Declarations
  - name: Risk
  - name: Brokers
  - name: Regulations
  - name: BTB
  - name: TradeMeasures
  - name: Notifications
  - name: Stats
  - name: Health

paths:
  /healthz:
    servers:
      - url: http://localhost:8080
    get:
      tags: [Health]
      security: []
      summary: Liveness
      responses:
        '200': { $ref: '#/components/responses/Status' }

  /readyz:
    servers:
      - url: http://localhost:8080
    get:
      tags: [Health]
      security: []
      summary: Readiness (DB ping)
      responses:
        '200': { $ref: '#/components/responses/Status' }
        '503': { $ref: '#/components/responses/Error' }

  /auth/register:
    post:
      tags: [Auth]
      security: []
      summary: Register a new organization + admin user
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/RegisterRequest' }
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AuthResponse' }
        '400': { $ref: '#/components/responses/Error' }
        '409': { $ref: '#/components/responses/Error' }

  /auth/login:
    post:
      tags: [Auth]
      security: []
      summary: Email + password login
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/LoginRequest' }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AuthResponse' }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }

  /me:
    get:
      tags: [Profile]
      summary: Current user
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/User' }
        '401': { $ref: '#/components/responses/Error' }
    patch:
      tags: [Profile]
      summary: Update current user (full_name, email)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/UpdateMeRequest' }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/User' }
        '400': { $ref: '#/components/responses/Error' }
        '409': { $ref: '#/components/responses/Error' }

  /me/password:
    post:
      tags: [Profile]
      summary: Change password
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ChangePasswordRequest' }
      responses:
        '204': { description: Password changed }
        '401': { $ref: '#/components/responses/Error' }
        '400': { $ref: '#/components/responses/Error' }

  /stats/summary:
    get:
      tags: [Stats]
      summary: Dashboard summary (case counts, unread, last 30d tax, risk dist.)
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/StatsSummary' }

  /cases/:
    get:
      tags: [Cases]
      summary: List cases (org-scoped)
      parameters:
        - { name: status, in: query, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer, default: 50 } }
        - { name: offset, in: query, schema: { type: integer, default: 0 } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/Case' } }
                  count: { type: integer }
    post:
      tags: [Cases]
      summary: Create case (code auto-generated if blank)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateCaseRequest' }
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Case' }
        '400': { $ref: '#/components/responses/Error' }
        '409': { $ref: '#/components/responses/Error' }

  /cases/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [Cases]
      summary: Get case
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/Case' } } }
        '404': { $ref: '#/components/responses/Error' }
    patch:
      tags: [Cases]
      summary: Update case fields
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/UpdateCaseRequest' }
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/Case' } } }
        '400': { $ref: '#/components/responses/Error' }
        '404': { $ref: '#/components/responses/Error' }
    delete:
      tags: [Cases]
      summary: Delete case
      responses:
        '204': { description: Deleted }
        '404': { $ref: '#/components/responses/Error' }

  /customers/:
    get:
      tags: [Customers]
      summary: List customers
      parameters:
        - { name: limit, in: query, schema: { type: integer } }
        - { name: offset, in: query, schema: { type: integer } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/Customer' } }
                  count: { type: integer }
    post:
      tags: [Customers]
      summary: Create customer
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CustomerInput' }
      responses:
        '201':
          description: Created
          content: { application/json: { schema: { $ref: '#/components/schemas/Customer' } } }

  /customers/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [Customers]
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/Customer' } } }
        '404': { $ref: '#/components/responses/Error' }
    patch:
      tags: [Customers]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CustomerInput' }
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/Customer' } } }
    delete:
      tags: [Customers]
      responses:
        '204': { description: Deleted }

  /cases/{caseID}/documents/:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [Documents]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/Document' } }
                  count: { type: integer }
    post:
      tags: [Documents]
      summary: Upload document (multipart)
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file, kind]
              properties:
                file: { type: string, format: binary }
                kind: { type: string, enum: [invoice, packing_list, bl, certificate, other] }
      responses:
        '201':
          description: Uploaded
          content: { application/json: { schema: { $ref: '#/components/schemas/Document' } } }

  /cases/{caseID}/documents/{id}:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
      - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    delete:
      tags: [Documents]
      responses:
        '204': { description: Deleted }

  /cases/{caseID}/documents/{id}/download:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
      - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [Documents]
      summary: Download file bytes
      responses:
        '200':
          description: OK
          content:
            application/octet-stream:
              schema: { type: string, format: binary }

  /cases/{caseID}/gtip/:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [GTIP]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/GtipResult' } }
                  count: { type: integer }
    post:
      tags: [GTIP]
      summary: Create GTIP result (manual entry or post-AI confirmed)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GtipInput' }
      responses:
        '201':
          description: Created
          content: { application/json: { schema: { $ref: '#/components/schemas/GtipResult' } } }

  /cases/{caseID}/gtip/suggest:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [GTIP]
      summary: AI-suggested GTIP candidates (does not persist)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GtipSuggestInput' }
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/GtipSuggestResponse' } } }
        '503':
          description: AI disabled (OPENAI_API_KEY not set)

  /cases/{caseID}/gtip/suggest-image:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [GTIP]
      summary: AI GTIP suggestion from product photo (vision)
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [image]
              properties:
                image: { type: string, format: binary }
                notes: { type: string }
                origin: { type: string }
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/GtipSuggestResponse' } } }
        '415': { description: Only image/* supported }
        '503': { description: AI disabled }

  /cases/{caseID}/gtip/{id}:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
      - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [GTIP]
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/GtipResult' } } }
        '404': { description: Not found }
    patch:
      tags: [GTIP]
      summary: Partial update of GTIP result
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GtipUpdateInput' }
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/GtipResult' } } }
        '404': { description: Not found }
    delete:
      tags: [GTIP]
      responses:
        '204': { description: Deleted }
        '404': { description: Not found }

  /cases/{caseID}/documents/{id}/extract:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
      - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [Documents]
      summary: AI-extract structured invoice data from uploaded document
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/DocumentExtractResponse' } } }
        '413': { description: File too large }
        '415': { description: Unsupported media type }
        '503': { description: AI disabled }

  /regulations/reindex:
    post:
      tags: [Regulations]
      summary: Backfill embeddings for regulations missing one (batch of 50)
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  processed: { type: integer }
                  remaining: { type: integer }
                  errors: { type: array, items: { type: string } }
        '503': { description: AI disabled }

  /regulations/ask:
    post:
      tags: [Regulations]
      summary: AI Q&A grounded on regulation DB (with citations)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/RegulationAskInput' }
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/RegulationAskResponse' } } }
        '503': { description: AI disabled }

  /btb/:
    get:
      tags: [BTB]
      parameters:
        - { name: q,    in: query, schema: { type: string } }
        - { name: gtip, in: query, schema: { type: string } }
        - { name: limit,  in: query, schema: { type: integer } }
        - { name: offset, in: query, schema: { type: integer } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/BTB' } }
                  count: { type: integer }
    post:
      tags: [BTB]
      requestBody:
        required: true
        content: { application/json: { schema: { $ref: '#/components/schemas/BTBInput' } } }
      responses:
        '201':
          description: Created
          content: { application/json: { schema: { $ref: '#/components/schemas/BTB' } } }
        '409': { description: reference_no already exists }

  /btb/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [BTB]
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/BTB' } } }
        '404': { description: Not found }
    delete:
      tags: [BTB]
      responses:
        '204': { description: Deleted }

  /trade-measures/:
    get:
      tags: [TradeMeasures]
      parameters:
        - { name: kind,   in: query, schema: { type: string } }
        - { name: gtip,   in: query, schema: { type: string } }
        - { name: origin, in: query, schema: { type: string } }
        - { name: limit,  in: query, schema: { type: integer } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/TradeMeasure' } }
                  count: { type: integer }
    post:
      tags: [TradeMeasures]
      requestBody:
        required: true
        content: { application/json: { schema: { $ref: '#/components/schemas/TradeMeasureInput' } } }
      responses:
        '201':
          description: Created
          content: { application/json: { schema: { $ref: '#/components/schemas/TradeMeasure' } } }

  /trade-measures/ingest:
    post:
      tags: [TradeMeasures]
      summary: AI-assisted extraction of measures from regulation text or URL
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:  { type: string }
                text: { type: string }
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/MeasureIngestResponse' } } }
        '503': { description: AI disabled }

  /trade-measures/lookup:
    get:
      tags: [TradeMeasures]
      summary: Find measures applicable to a given GTIP + origin at current date
      parameters:
        - { name: gtip,   in: query, required: true, schema: { type: string } }
        - { name: origin, in: query, schema: { type: string } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  gtip:   { type: string }
                  origin: { type: string }
                  items:  { type: array, items: { $ref: '#/components/schemas/TradeMeasure' } }
                  count:  { type: integer }

  /trade-measures/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [TradeMeasures]
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/TradeMeasure' } } }
        '404': { description: Not found }
    delete:
      tags: [TradeMeasures]
      responses:
        '204': { description: Deleted }

  /cases/{caseID}/declarations/{id}/submit:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
      - { name: id,     in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [Declarations]
      summary: Build BİLGE-ready XML and record a submission (prep layer — no wire transmit)
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  submission: { $ref: '#/components/schemas/DeclarationSubmission' }
                  disclaimer: { type: string }
        '422': { description: Declaration incomplete }

  /cases/{caseID}/declarations/{id}/submissions:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
      - { name: id,     in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [Declarations]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/DeclarationSubmission' } }
                  count: { type: integer }

  /cases/{caseID}/declarations/{id}/submissions/{subID}/transmit:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
      - { name: id,     in: path, required: true, schema: { type: string, format: uuid } }
      - { name: subID,  in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [Declarations]
      summary: Transmit a prepared submission to live BİLGE via mTLS SOAP
      responses:
        '200':
          description: OK (response from BİLGE included; status updated to submitted or rejected)
          content:
            application/json:
              schema:
                type: object
                properties:
                  submission: { $ref: '#/components/schemas/DeclarationSubmission' }
                  bilge:
                    type: object
                    properties:
                      http_status:   { type: integer }
                      response_body: { type: string }
                      reference_no:  { type: string }
                      fault:         { type: string }
                  endpoint:   { type: string }
                  disclaimer: { type: string }
        '503': { description: BİLGE client not configured }

  /cases/{caseID}/declarations/{id}/submissions/{subID}:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
      - { name: id,     in: path, required: true, schema: { type: string, format: uuid } }
      - { name: subID,  in: path, required: true, schema: { type: string, format: uuid } }
    patch:
      tags: [Declarations]
      summary: Update submission status (prepared→submitted→accepted/rejected)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [status]
              properties:
                status: { type: string, enum: [prepared, submitted, accepted, rejected] }
                reference_no:  { type: string }
                response_body: { type: string }
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/DeclarationSubmission' } } }

  /cases/{caseID}/tax/:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [Tax]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/TaxCalculation' } }
                  count: { type: integer }
    post:
      tags: [Tax]
      summary: Save tax calculation
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/TaxCreateInput' }
      responses:
        '201':
          description: Created
          content: { application/json: { schema: { $ref: '#/components/schemas/TaxCalculation' } } }

  /cases/{caseID}/tax/preview:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [Tax]
      summary: Preview computation (no DB write)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [inputs]
              properties:
                inputs: { $ref: '#/components/schemas/TaxInputs' }
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/TaxBreakdown' } } }

  /cases/{caseID}/declarations/:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [Declarations]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/Declaration' } }
                  count: { type: integer }
    post:
      tags: [Declarations]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [payload]
              properties:
                payload: { type: object, additionalProperties: true }
      responses:
        '201':
          description: Created
          content: { application/json: { schema: { $ref: '#/components/schemas/Declaration' } } }

  /cases/{caseID}/declarations/{id}:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
      - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    patch:
      tags: [Declarations]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                payload: { type: object, additionalProperties: true }
                status: { type: string }
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/Declaration' } } }
    delete:
      tags: [Declarations]
      responses:
        '204': { description: Deleted }

  /cases/{caseID}/declarations/{id}/validate:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
      - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [Declarations]
      summary: Validate 8 BİLGE fields
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      valid: { const: true }
                      declaration: { $ref: '#/components/schemas/Declaration' }
                  - type: object
                    properties:
                      valid: { const: false }
                      missing_fields: { type: array, items: { type: string } }

  /cases/{caseID}/risk/:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [Risk]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/RiskAssessment' } }
                  count: { type: integer }
    post:
      tags: [Risk]
      summary: Assess and persist
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/RiskInputs' }
      responses:
        '201':
          description: Created
          content: { application/json: { schema: { $ref: '#/components/schemas/RiskAssessment' } } }

  /cases/{caseID}/risk/preview:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [Risk]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/RiskInputs' }
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/RiskOutput' } } }

  /brokers/:
    get:
      tags: [Brokers]
      parameters:
        - { name: city, in: query, schema: { type: string } }
        - { name: sector, in: query, schema: { type: string } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/Broker' } }
                  count: { type: integer }
    post:
      tags: [Brokers]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/BrokerInput' }
      responses:
        '201':
          description: Created
          content: { application/json: { schema: { $ref: '#/components/schemas/Broker' } } }

  /brokers/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    delete:
      tags: [Brokers]
      responses:
        '204': { description: Deleted }

  /cases/{caseID}/brokers/:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [Brokers]
      summary: List brokers shared on this case
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/CaseShare' } }
                  count: { type: integer }
    post:
      tags: [Brokers]
      summary: Share case with broker
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [broker_id]
              properties:
                broker_id: { type: string, format: uuid }
                role: { type: string, default: viewer }
      responses:
        '201':
          description: Shared
          content: { application/json: { schema: { $ref: '#/components/schemas/CaseShare' } } }

  /cases/{caseID}/brokers/{brokerID}:
    parameters:
      - { name: caseID, in: path, required: true, schema: { type: string, format: uuid } }
      - { name: brokerID, in: path, required: true, schema: { type: string, format: uuid } }
    delete:
      tags: [Brokers]
      responses:
        '204': { description: Unshared }

  /regulations/:
    get:
      tags: [Regulations]
      parameters:
        - { name: q, in: query, schema: { type: string } }
        - { name: kind, in: query, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/Regulation' } }
                  count: { type: integer }
    post:
      tags: [Regulations]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/RegulationInput' }
      responses:
        '201':
          description: Created
          content: { application/json: { schema: { $ref: '#/components/schemas/Regulation' } } }

  /regulations/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      tags: [Regulations]
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/Regulation' } } }
        '404': { $ref: '#/components/responses/Error' }

  /notifications/:
    get:
      tags: [Notifications]
      parameters:
        - { name: unread, in: query, schema: { type: boolean } }
        - { name: type, in: query, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/Notification' } }
                  count: { type: integer }
    post:
      tags: [Notifications]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [type, title]
              properties:
                type: { type: string }
                title: { type: string }
                body: { type: string }
                file_case_id: { type: string, format: uuid }
      responses:
        '201':
          description: Created
          content: { application/json: { schema: { $ref: '#/components/schemas/Notification' } } }

  /notifications/unread-count:
    get:
      tags: [Notifications]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties: { unread: { type: integer } }

  /notifications/{id}/read:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    post:
      tags: [Notifications]
      responses:
        '204': { description: Marked }

  /notifications/read-all:
    post:
      tags: [Notifications]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties: { marked: { type: integer } }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  responses:
    Status:
      description: OK
      content:
        application/json:
          schema: { type: object, properties: { status: { type: string } } }
    Error:
      description: Error envelope
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorEnvelope' }

  schemas:
    ErrorEnvelope:
      type: object
      description: "Error envelope; either legacy string or structured object."
      properties:
        error:
          oneOf:
            - type: string
            - type: object
              properties:
                code: { type: string }
                message: { type: string }
                request_id: { type: string }

    User:
      type: object
      properties:
        id: { type: string, format: uuid }
        organization_id: { type: string, format: uuid }
        email: { type: string, format: email }
        full_name: { type: string }
        role: { type: string }
        is_active: { type: boolean }
        created_at: { type: string, format: date-time }

    AuthResponse:
      type: object
      required: [token, expires_at, user]
      properties:
        token: { type: string }
        expires_at: { type: string, format: date-time }
        user:
          type: object
          properties:
            id: { type: string, format: uuid }
            organization_id: { type: string, format: uuid }
            email: { type: string, format: email }
            full_name: { type: string }
            role: { type: string }

    RegisterRequest:
      type: object
      required: [organization_name, email, password, full_name]
      properties:
        organization_name: { type: string }
        email: { type: string, format: email }
        password: { type: string, minLength: 8 }
        full_name: { type: string }

    LoginRequest:
      type: object
      required: [email, password]
      properties:
        email: { type: string, format: email }
        password: { type: string }

    UpdateMeRequest:
      type: object
      properties:
        full_name: { type: string }
        email: { type: string, format: email }

    ChangePasswordRequest:
      type: object
      required: [current_password, new_password]
      properties:
        current_password: { type: string }
        new_password: { type: string, minLength: 8 }

    StatsSummary:
      type: object
      properties:
        case_counts: { type: object, additionalProperties: { type: integer } }
        total_cases: { type: integer }
        unread_notifications: { type: integer }
        tax_total_last_30_days: { type: number }
        risk_level_counts: { type: object, additionalProperties: { type: integer } }

    Case:
      type: object
      properties:
        id: { type: string, format: uuid }
        organization_id: { type: string, format: uuid }
        customer_id: { type: string, format: uuid, nullable: true }
        owner_user_id: { type: string, format: uuid, nullable: true }
        code: { type: string }
        title: { type: string }
        status:
          type: string
          enum: [draft, in_progress, awaiting_docs, review, closed, archived]
        operation_type:
          type: string
          enum: [import, export, transit]
        description: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    CreateCaseRequest:
      type: object
      required: [title]
      properties:
        code: { type: string }
        title: { type: string }
        operation_type: { type: string, enum: [import, export, transit] }
        description: { type: string }
        customer_id: { type: string, format: uuid }

    UpdateCaseRequest:
      type: object
      properties:
        title: { type: string }
        status: { type: string }
        operation_type: { type: string }
        description: { type: string }
        customer_id: { type: string, format: uuid }

    Customer:
      type: object
      properties:
        id: { type: string, format: uuid }
        organization_id: { type: string, format: uuid }
        name: { type: string }
        tax_number: { type: string, nullable: true }
        email: { type: string, nullable: true }
        phone: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    CustomerInput:
      type: object
      required: [name]
      properties:
        name: { type: string }
        tax_number: { type: string }
        email: { type: string, format: email }
        phone: { type: string }

    Document:
      type: object
      properties:
        id: { type: string, format: uuid }
        file_case_id: { type: string, format: uuid }
        uploaded_by: { type: string, format: uuid }
        kind: { type: string }
        file_name: { type: string }
        storage_key: { type: string }
        mime_type: { type: string }
        size_bytes: { type: integer }
        created_at: { type: string, format: date-time }

    GtipAlternative:
      type: object
      properties:
        code: { type: string }
        description: { type: string }
        confidence: { type: number, minimum: 0, maximum: 1 }

    GtipInput:
      type: object
      required: [product_text, suggested_code]
      properties:
        product_text: { type: string }
        suggested_code: { type: string }
        alternatives: { type: array, items: { $ref: '#/components/schemas/GtipAlternative' } }
        confidence: { type: number, minimum: 0, maximum: 1 }
        rationale: { type: string }

    GtipResult:
      type: object
      properties:
        id: { type: string, format: uuid }
        file_case_id: { type: string, format: uuid }
        product_text: { type: string }
        suggested_code: { type: string }
        alternatives: { type: array, items: { $ref: '#/components/schemas/GtipAlternative' } }
        confidence: { type: number }
        rationale: { type: string }
        created_at: { type: string, format: date-time }

    GtipUpdateInput:
      type: object
      description: All fields optional — send only changed ones
      properties:
        product_text: { type: string }
        suggested_code: { type: string }
        alternatives: { type: array, items: { $ref: '#/components/schemas/GtipAlternative' } }
        confidence: { type: number, minimum: 0, maximum: 1 }
        rationale: { type: string }

    GtipSuggestInput:
      type: object
      required: [product_text]
      properties:
        product_text: { type: string }
        origin: { type: string }
        material: { type: string }
        usage: { type: string }
        brand_model: { type: string }
        notes: { type: string }

    GtipSuggestCandidate:
      type: object
      properties:
        code: { type: string }
        description: { type: string }
        confidence: { type: number, minimum: 0, maximum: 1 }
        reason: { type: string }

    GtipSuggestResponse:
      type: object
      properties:
        product_text: { type: string }
        primary: { $ref: '#/components/schemas/GtipSuggestCandidate' }
        alternates: { type: array, items: { $ref: '#/components/schemas/GtipSuggestCandidate' } }
        disclaimer: { type: string }
        model: { type: string }

    DocumentExtractResponse:
      type: object
      properties:
        document_id: { type: string, format: uuid }
        file_name: { type: string }
        extracted:
          type: object
          additionalProperties: true
        disclaimer: { type: string }
        model: { type: string }

    RegulationAskInput:
      type: object
      required: [question]
      properties:
        question: { type: string }
        kind: { type: string }

    RegulationCitation:
      type: object
      properties:
        id: { type: string, format: uuid }
        title: { type: string }
        kind: { type: string }
        code: { type: string }

    RegulationAskResponse:
      type: object
      properties:
        question: { type: string }
        answer: { type: string }
        citations: { type: array, items: { $ref: '#/components/schemas/RegulationCitation' } }
        disclaimer: { type: string }
        model: { type: string }

    BTB:
      type: object
      properties:
        id: { type: string, format: uuid }
        organization_id: { type: string, format: uuid }
        reference_no: { type: string }
        applicant: { type: string }
        gtip_code: { type: string }
        product_description: { type: string }
        decision_date: { type: string, format: date-time }
        valid_until:   { type: string, format: date-time }
        country_of_decision: { type: string }
        regulation_id: { type: string, format: uuid }
        body: { type: string }
        source_url: { type: string }
        tags: { type: array, items: { type: string } }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    BTBInput:
      type: object
      required: [reference_no, gtip_code, product_description]
      properties:
        reference_no: { type: string }
        applicant: { type: string }
        gtip_code: { type: string }
        product_description: { type: string }
        decision_date: { type: string, format: date-time }
        valid_until:   { type: string, format: date-time }
        country_of_decision: { type: string }
        regulation_id: { type: string, format: uuid }
        body: { type: string }
        source_url: { type: string }
        tags: { type: array, items: { type: string } }

    TradeMeasure:
      type: object
      properties:
        id: { type: string, format: uuid }
        kind: { type: string, enum: [anti_damping, countervailing, safeguard, additional_tax, other] }
        gtip_prefix: { type: string }
        origin_country: { type: string }
        rate_percent: { type: number }
        fixed_amount: { type: number }
        currency: { type: string }
        unit: { type: string }
        description: { type: string }
        regulation_id: { type: string, format: uuid }
        effective_from: { type: string, format: date-time }
        effective_to:   { type: string, format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    TradeMeasureInput:
      type: object
      required: [kind, gtip_prefix, description]
      properties:
        kind: { type: string, enum: [anti_damping, countervailing, safeguard, additional_tax, other] }
        gtip_prefix: { type: string }
        origin_country: { type: string }
        rate_percent: { type: number }
        fixed_amount: { type: number }
        currency: { type: string }
        unit: { type: string }
        description: { type: string }
        regulation_id: { type: string, format: uuid }
        effective_from: { type: string, format: date-time }
        effective_to:   { type: string, format: date-time }

    MeasureIngestCandidate:
      type: object
      properties:
        kind: { type: string, enum: [anti_damping, countervailing, safeguard, additional_tax, other] }
        gtip_prefix: { type: string }
        origin_country: { type: string }
        rate_percent: { type: number }
        fixed_amount: { type: number }
        currency: { type: string }
        unit: { type: string }
        description: { type: string }
        effective_from: { type: string }
        effective_to: { type: string }
        confidence: { type: number }

    MeasureIngestResponse:
      type: object
      properties:
        source: { type: string }
        candidates: { type: array, items: { $ref: '#/components/schemas/MeasureIngestCandidate' } }
        notes: { type: string }
        disclaimer: { type: string }
        model: { type: string }

    DeclarationSubmission:
      type: object
      properties:
        id: { type: string, format: uuid }
        declaration_id: { type: string, format: uuid }
        file_case_id: { type: string, format: uuid }
        status: { type: string, enum: [prepared, submitted, accepted, rejected] }
        xml_payload: { type: string }
        reference_no: { type: string }
        response_body: { type: string }
        submitted_at: { type: string, format: date-time }
        created_by: { type: string, format: uuid }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    TaxInputs:
      type: object
      required: [fob]
      properties:
        fob: { type: number }
        freight: { type: number }
        insurance: { type: number }
        gv_rate: { type: number }
        igv_rate: { type: number }
        tev_rate: { type: number }
        otv_rate: { type: number }
        kdv_rate: { type: number }

    TaxLine:
      type: object
      properties:
        name: { type: string }
        base: { type: number }
        rate: { type: number }
        amount: { type: number }

    TaxBreakdown:
      type: object
      properties:
        cif: { type: number }
        lines: { type: array, items: { $ref: '#/components/schemas/TaxLine' } }
        total_tax: { type: number }
        total_cost: { type: number }

    TaxCreateInput:
      type: object
      required: [gtip_code, origin_country, inputs]
      properties:
        gtip_code: { type: string }
        origin_country: { type: string }
        currency: { type: string, default: USD }
        inputs: { $ref: '#/components/schemas/TaxInputs' }

    TaxCalculation:
      type: object
      properties:
        id: { type: string, format: uuid }
        file_case_id: { type: string, format: uuid }
        gtip_code: { type: string }
        origin_country: { type: string }
        currency: { type: string }
        inputs: { $ref: '#/components/schemas/TaxInputs' }
        breakdown: { $ref: '#/components/schemas/TaxBreakdown' }
        total: { type: number }
        created_at: { type: string, format: date-time }

    Declaration:
      type: object
      properties:
        id: { type: string, format: uuid }
        file_case_id: { type: string, format: uuid }
        status: { type: string, enum: [draft, validated, submitted, rejected] }
        payload: { type: object, additionalProperties: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    RiskInputs:
      type: object
      properties:
        gtip_code: { type: string }
        origin_country: { type: string }
        cif_value: { type: number }
        penalty_history: { type: integer }
        flags: { type: array, items: { type: string } }

    RiskFinding:
      type: object
      properties:
        rule: { type: string }
        severity: { type: string }
        message: { type: string }
        points: { type: integer }

    RiskOutput:
      type: object
      properties:
        score: { type: integer, minimum: 0, maximum: 100 }
        level: { type: string, enum: [low, medium, high, critical] }
        findings: { type: array, items: { $ref: '#/components/schemas/RiskFinding' }, nullable: true }

    RiskAssessment:
      allOf:
        - $ref: '#/components/schemas/RiskOutput'
        - type: object
          properties:
            id: { type: string, format: uuid }
            file_case_id: { type: string, format: uuid }
            created_at: { type: string, format: date-time }

    Broker:
      type: object
      properties:
        id: { type: string, format: uuid }
        organization_id: { type: string, format: uuid }
        full_name: { type: string }
        license_no: { type: string }
        email: { type: string }
        phone: { type: string }
        city: { type: string }
        sectors: { type: array, items: { type: string } }
        is_active: { type: boolean }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    BrokerInput:
      type: object
      required: [full_name]
      properties:
        full_name: { type: string }
        license_no: { type: string }
        email: { type: string }
        phone: { type: string }
        city: { type: string }
        sectors: { type: array, items: { type: string } }

    CaseShare:
      type: object
      properties:
        file_case_id: { type: string, format: uuid }
        broker_id: { type: string, format: uuid }
        role: { type: string }
        shared_at: { type: string, format: date-time }

    Regulation:
      type: object
      properties:
        id: { type: string, format: uuid }
        kind: { type: string, enum: [kanun, tebliğ, genelge, yönetmelik, btb, ab] }
        code: { type: string }
        title: { type: string }
        summary: { type: string }
        body: { type: string }
        source_url: { type: string }
        effective_at: { type: string, format: date-time }
        tags: { type: array, items: { type: string } }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    RegulationInput:
      type: object
      required: [kind, title]
      properties:
        kind: { type: string }
        title: { type: string }
        code: { type: string }
        summary: { type: string }
        body: { type: string }
        source_url: { type: string }
        tags: { type: array, items: { type: string } }

    Notification:
      type: object
      properties:
        id: { type: string, format: uuid }
        user_id: { type: string, format: uuid }
        file_case_id: { type: string, format: uuid }
        type: { type: string }
        title: { type: string }
        body: { type: string }
        read_at: { type: string, format: date-time, nullable: true }
        created_at: { type: string, format: date-time }
