openapi: 3.1.0
info:
  title: Wove External API
  version: 1.0.0
  description: |
    # Wove External API Documentation

    The Wove External API allows you to programmatically access document processing, shipment management, and validation capabilities.

    ## Features

    - **OAuth 2.0 Authentication**: Secure client credentials flow
    - **Rate Limiting**: Configurable per-client rate limits
    - **Document Processing**: Upload, validate, and merge shipping documents
    - **Shipment Management**: Create and manage shipment records with validation and merge operations
    - **Webhooks**: Get notified of document processing events (extraction, validation, merge)
    - **Comprehensive Error Handling**: Detailed error responses with troubleshooting information

    ## Getting Started

    1. **Create OAuth Client**: Contact your account manager to create OAuth credentials
    2. **Get Access Token**: Use client credentials flow to obtain bearer token
    3. **Make API Calls**: Include bearer token in Authorization header
    4. **Handle Rate Limits**: Monitor rate limit headers in responses

    ## Authentication

    All API endpoints require OAuth 2.0 authentication using the client credentials flow.

    ### Getting an Access Token

    ```bash
    curl -X POST https://api.wove.com/api/v1/external/auth/token \
      -H "Content-Type: application/json" \
      -d '{
        "grant_type": "client_credentials",
        "client_id": "your_client_id",
        "client_secret": "your_client_secret"
      }'
    ```

    ### Using the Access Token

    Include the access token in the Authorization header:

    ```bash
    curl -H "Authorization: Bearer your_access_token" \
      https://api.wove.com/api/v1/external/shipments
    ```

    ## Rate Limiting

    All endpoints are subject to rate limiting based on your OAuth client configuration:

    - **Per-minute limit**: Default 60 requests/minute
    - **Daily limit**: Default 10,000 requests/day

    Rate limit information is included in response headers:
    - `X-RateLimit-Limit-Minute`: Your per-minute limit
    - `X-RateLimit-Remaining-Minute`: Remaining requests this minute
    - `X-RateLimit-Reset-Minute`: When the minute window resets
    - `X-RateLimit-Limit-Day`: Your daily limit
    - `X-RateLimit-Remaining-Day`: Remaining requests today
    - `X-RateLimit-Reset-Day`: When the daily window resets

    ## Webhook Security

    All webhook payloads are signed using HMAC-SHA256 for verification:

    ```javascript
    // Verify webhook signature
    const crypto = require('crypto');
    const signature = request.headers['x-wove-signature'];
    const payload = JSON.stringify(request.body);
    const expectedSignature = crypto
      .createHmac('sha256', your_webhook_secret)
      .update(payload)
      .digest('hex');

    const isValid = crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expectedSignature)
    );
    ```

    Headers included with every webhook:
    - `X-Wove-Signature`: HMAC-SHA256 signature of the payload
    - `X-Wove-Event`: Event type (e.g., "extraction.completed")
    - `X-Wove-Timestamp`: ISO timestamp when the webhook was sent

    ## Error Handling

    All errors follow a consistent format:

    ```json
    {
      "success": false,
      "error": {
        "code": "VALIDATION_ERROR",
        "message": "One or more documents not found or access denied",
        "details": {
          "field": "documentIds",
          "value": ["invalid_id"]
        }
      }
    }
    ```

    ### Error Codes

    - `VALIDATION_ERROR` - Invalid request parameters or data validation failure
    - `AUTHENTICATION_ERROR` - Invalid or expired credentials
    - `AUTHORIZATION_ERROR` - Insufficient permissions for the requested operation
    - `NOT_FOUND` - Requested resource not found
    - `RATE_LIMIT_ERROR` - Too many requests, rate limit exceeded
    - `INTERNAL_ERROR` - Internal server error

    See the common error responses in the components section for detailed examples.

  contact:
    name: Wove API Support
    email: api-support@wove.com
    url: https://docs.wove.com
  license:
    name: Proprietary
    url: https://wove.com/terms

servers:
  - url: https://api.wove.com
    description: Production server
  - url: https://staging-api.wove.com
    description: Staging server
  - url: http://localhost:4000
    description: Development server

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: OAuth 2.0 Bearer token obtained from /auth/token endpoint

  schemas:
    SuccessResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          description: Response data (varies by endpoint)
        message:
          type: string
          description: Optional success message
      required: [success, data]

    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          properties:
            code:
              type: string
              example: VALIDATION_ERROR
            message:
              type: string
              example: Invalid input parameters
            details:
              type: object
              description: Additional error context
      required: [success, error]

    PaginatedResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          type: array
          items:
            type: object
            description: Array of items (varies by endpoint)
        message:
          type: string
          description: Optional success message
        pagination:
          type: object
          properties:
            page:
              type: integer
              example: 1
            limit:
              type: integer
              example: 20
            total:
              type: integer
              example: 150
            totalPages:
              type: integer
              example: 8
            hasNext:
              type: boolean
              example: true
            hasPrev:
              type: boolean
              example: false
          required: [page, limit, total, totalPages, hasNext, hasPrev]
      required: [success, data, pagination]

    TokenRequest:
      type: object
      properties:
        grant_type:
          type: string
          enum: [client_credentials]
          example: client_credentials
        client_id:
          type: string
          example: wove_prod_abc123...
        client_secret:
          type: string
          example: sk_...
        scope:
          type: string
          description: Space-separated list of scopes
          example: shipments:read documents:read validation:create
      required: [grant_type, client_id, client_secret]

    TokenResponse:
      type: object
      properties:
        access_token:
          type: string
          example: eyJhbGciOiJIUzI1NiIs...
        token_type:
          type: string
          example: Bearer
        expires_in:
          type: integer
          example: 3600
          description: Token lifetime in seconds
        scope:
          type: string
          example: shipments:read documents:read validation:create

    PortLocation:
      type: object
      properties:
        portCode:
          type: string
          example: USLAX
        portName:
          type: string
          example: Los Angeles
        countryCode:
          type: string
          example: US
      required: [portCode]

    AirportLocation:
      type: object
      properties:
        airportCode:
          type: string
          example: LAX
        airportName:
          type: string
          example: Los Angeles International Airport
        countryCode:
          type: string
          example: US
      required: [airportCode]

    AddressLocation:
      type: object
      properties:
        address1:
          type: string
          example: 123 Main St
        address2:
          type: string
          example: Suite 100
        city:
          type: string
          example: New York
        province:
          type: string
          example: NY
        postalCode:
          type: string
          example: '10001'
        country:
          type: string
          example: US

    Location:
      oneOf:
        - type: object
          properties:
            type:
              type: string
              enum: [Port]
            portCode:
              type: string
              example: USNYC
            portName:
              type: string
              example: New York/Newark
            countryCode:
              type: string
              example: US
          required: [type, portCode]
        - type: object
          properties:
            type:
              type: string
              enum: [Airport]
            airportCode:
              type: string
              example: JFK
            airportName:
              type: string
              example: John F. Kennedy International Airport
            countryCode:
              type: string
              example: US
          required: [type, airportCode]
        - type: object
          properties:
            type:
              type: string
              enum: [Door]
            address1:
              type: string
              example: 123 Main St
            address2:
              type: string
              example: Suite 100
            city:
              type: string
              example: New York
            province:
              type: string
              example: NY
            postalCode:
              type: string
              example: '10001'
            country:
              type: string
              example: US
          required: [type, address1, city, country]

    ReferenceNumber:
      type: object
      properties:
        id:
          type: string
          example: '1014810279825727493'
        type:
          type: string
          example: booking
          description: Type of reference number (booking, invoice, po, etc.)
        displayNumber:
          type: string
          example: BOOK-2024-001
        cleanedNumber:
          type: string
          example: BOOK2024001
          description: Cleaned version without special characters
      required: [id, type, displayNumber]

    ShipmentSummary:
      type: object
      properties:
        id:
          type: string
          example: '1014810279825727490'
        status:
          type: string
          enum: [draft, pending, booked, in_transit, delivered, cancelled]
          example: draft
          description: Current status of the shipment
        name:
          type: string
          example: External API Test Shipment
        shipmentType:
          type: string
          enum: [FCL, LCL, AIR, LTL, FTL, RAIL]
          example: LCL
        commodity:
          type: string
          example: Electronics
        portOfLoading:
          $ref: '#/components/schemas/PortLocation'
        portOfDischarge:
          $ref: '#/components/schemas/PortLocation'
        placeOfReceiptType:
          type: string
          enum: [Port, Airport, Door]
        placeOfReceiptPort:
          $ref: '#/components/schemas/PortLocation'
        placeOfReceiptAirport:
          $ref: '#/components/schemas/AirportLocation'
        placeOfReceiptAddress:
          $ref: '#/components/schemas/AddressLocation'
        placeOfDeliveryType:
          type: string
          enum: [Port, Airport, Door]
        placeOfDeliveryPort:
          $ref: '#/components/schemas/PortLocation'
        placeOfDeliveryAirport:
          $ref: '#/components/schemas/AirportLocation'
        placeOfDeliveryAddress:
          $ref: '#/components/schemas/AddressLocation'
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        documentCount:
          type: integer
          example: 5
          description: Number of documents attached to this shipment

    ShipmentCarrier:
      type: object
      properties:
        code:
          type: string
          example: MAEU
        name:
          type: string
          example: Maersk Line
      required: [code, name]

    # Backwards compatibility alias
    Carrier:
      $ref: '#/components/schemas/ShipmentCarrier'

    ShipmentDetails:
      allOf:
        - $ref: '#/components/schemas/ShipmentSummary'
        - type: object
          properties:
            carrier:
              $ref: '#/components/schemas/Carrier'
            vessel:
              type: string
              example: MSC OSCAR
            voyage:
              type: string
              example: 123W
            incoterm:
              type: string
              example: FOB
            serviceType:
              type: string
              example: CY-CY
            invoiceDate:
              type: string
              format: date-time
            dueDate:
              type: string
              format: date-time
            issueDate:
              type: string
              format: date-time
            shipmentDate:
              type: string
              format: date-time
            expectedDeliveryDate:
              type: string
              format: date-time
            readyDate:
              type: string
              format: date-time
            dateOfSignature:
              type: string
              format: date-time
            referenceNumbers:
              type: array
              items:
                $ref: '#/components/schemas/ReferenceNumber'
            # Party fields - Consignee
            consigneeName:
              $ref: '#/components/schemas/Organization'
            consigneeAddress:
              $ref: '#/components/schemas/Address'
            consigneeContactName:
              type: string
              nullable: true
            consigneeContactPhone:
              type: string
              nullable: true
            consigneeContactEmail:
              type: string
              nullable: true
            # Party fields - Consignor
            consignorName:
              $ref: '#/components/schemas/Organization'
            consignorAddress:
              $ref: '#/components/schemas/Address'
            consignorContactName:
              type: string
              nullable: true
            consignorContactPhone:
              type: string
              nullable: true
            consignorContactEmail:
              type: string
              nullable: true
            # Party fields - Notify Party
            notifyPartyName:
              $ref: '#/components/schemas/Organization'
            notifyPartyAddress:
              $ref: '#/components/schemas/Address'
            notifyPartyContactName:
              type: string
              nullable: true
            notifyPartyContactPhone:
              type: string
              nullable: true
            notifyPartyContactEmail:
              type: string
              nullable: true
            # Party fields - Destination Agent
            destinationAgentName:
              $ref: '#/components/schemas/Organization'
            destinationAgentAddress:
              $ref: '#/components/schemas/Address'
            destinationAgentContactName:
              type: string
              nullable: true
            destinationAgentContactPhone:
              type: string
              nullable: true
            destinationAgentContactEmail:
              type: string
              nullable: true
            # Weight and volume fields
            totalWeight:
              type: number
              nullable: true
            weightUnit:
              type: string
              nullable: true
            totalVolume:
              type: number
              nullable: true
            volumeUnit:
              type: string
              nullable: true
            totalPieces:
              type: number
              nullable: true
            # Aggregate package fields
            totalPacks:
              type: number
              nullable: true
            totalPacksPackageType:
              type: string
              nullable: true
            # Total value fields
            totalValue:
              type: number
              nullable: true
            currency:
              type: string
              nullable: true

    ShipmentItem:
      type: object
      properties:
        id:
          type: string
          example: '1014810279825727491'
        lineNumber:
          type: integer
          example: 1
        sku:
          type: string
          example: SKU-12345
        partNumber:
          type: string
          example: PN-67890
        description:
          type: string
          example: Electronic Component
        quantity:
          type: number
          example: 100
        unitOfMeasure:
          type: string
          example: PCS
        unitPrice:
          type: number
          example: 25.50
        amount:
          type: number
          example: 2550.00
        currency:
          type: string
          example: USD
        countryOfOrigin:
          type: string
          example: CN
        hsCode:
          type: string
          example: '8541.10.00'
        weight:
          type: number
          example: 150.5
        weightUnit:
          type: string
          example: KG
        volume:
          type: number
          example: 2.5
        volumeUnit:
          type: string
          example: CBM
        packageNumber:
          type: string
          example: PKG-001
        dimensions:
          type: object
          properties:
            length:
              type: number
              example: 100
            width:
              type: number
              example: 50
            height:
              type: number
              example: 50
            unit:
              type: string
              example: CM

    ShipmentContainer:
      type: object
      properties:
        id:
          type: string
          example: '1014810279825727492'
        number:
          type: string
          example: MSKU1234567
        size:
          type: string
          example: '40'
        type:
          type: string
          example: HC
        sizeType:
          type: string
          example: 40HC
          description: Combined size and type (e.g., 20GP, 40HC)
        sealNumber:
          type: string
          example: SEAL123456
        quantity:
          type: integer
          example: 1
        packages:
          type: array
          items:
            $ref: '#/components/schemas/ShipmentPackage'
          description: Packages contained in this container

    # Backwards compatibility alias
    Container:
      $ref: '#/components/schemas/ShipmentContainer'

    ShipmentPackage:
      type: object
      properties:
        id:
          type: string
          example: '1014810279825727493'
        containerId:
          type: string
          nullable: true
          description: ID of the container this package belongs to (null for loose packages)
        packageNumber:
          type: string
          example: PKG001
        packageType:
          type: string
          example: CARTON
        description:
          type: string
          nullable: true
        packageCount:
          type: integer
          example: 1
        lineNumber:
          type: integer
          nullable: true
        grossWeight:
          type: number
          nullable: true
        netWeight:
          type: number
          nullable: true
        volume:
          type: number
          nullable: true
        weightUnit:
          type: string
          nullable: true
        volumeUnit:
          type: string
          nullable: true
        items:
          type: array
          items:
            $ref: '#/components/schemas/ShipmentItem'
          description: Items contained in this package

    ShipmentEvent:
      type: object
      properties:
        id:
          type: string
          example: '1014810279825727495'
        eventType:
          type: string
          example: status_change
        eventDate:
          type: string
          format: date-time
        eventLocation:
          type: string
          example: USLAX
        eventDescription:
          type: string
          example: Status changed from draft to booked
        createdAt:
          type: string
          format: date-time

    CreateShipmentRequest:
      type: object
      properties:
        name:
          type: string
          example: My Shipment
        shipmentType:
          type: string
          enum: [FCL, LCL, AIR, LTL, FTL, RAIL]
          example: FCL
        commodity:
          type: string
          example: Electronics
        carrier:
          type: string
          example: MSC
        vessel:
          type: string
          example: MSC OSCAR
        voyage:
          type: string
          example: 123E
        incoterm:
          type: string
          example: FOB
        serviceType:
          type: string
          example: CY/CY
        portOfLoading:
          type: string
          example: CNSHA
        portOfDischarge:
          type: string
          example: USLAX
        placeOfReceipt:
          $ref: '#/components/schemas/Location'
        placeOfDelivery:
          $ref: '#/components/schemas/Location'
        referenceNumbers:
          type: array
          items:
            $ref: '#/components/schemas/ReferenceNumber'
        customFields:
          type: object
          additionalProperties: true
      required: [name]

    UpdateShipmentRequest:
      allOf:
        - $ref: '#/components/schemas/CreateShipmentRequest'
        - type: object
          description: All fields from CreateShipmentRequest are optional for updates

    Document:
      type: object
      properties:
        id:
          type: string
          example: '1013737064058920960'
        entityId:
          type: string
          example: '1014810279825727490'
        entityType:
          type: string
          example: shipment
        type:
          type: string
          example: CommercialInvoice
        name:
          type: string
          example: commercial_invoice.pdf
        size:
          type: integer
          example: 1024000
        checksum:
          type: string
          example: 'a1b2c3d4e5f6...'
        isSelected:
          type: boolean
          example: true
        extractedData:
          type: object
          description: Extracted data from the document
          additionalProperties: true
        extractionStatus:
          type: string
          enum: [pending, processing, completed, failed]
          example: completed
        extractionError:
          type: string
          example: null
        extractedAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    ValidationRequest:
      type: object
      properties:
        documentIds:
          type: array
          items:
            type: string
          minItems: 1
          maxItems: 20
          example: ['1013737064058920960', '1013737064063115265']
        ruleSetId:
          type: string
          description: Optional validation rule set ID
        async:
          type: boolean
          description: Process validation asynchronously
          default: false
        webhookUrl:
          type: string
          format: uri
          description: Webhook URL for async processing notifications
      required: [documentIds]

    ValidationJobResponse:
      type: object
      properties:
        jobId:
          type: string
          example: '1014851234567890123'
        status:
          type: string
          enum: [pending, processing, completed, failed]
          example: pending
        message:
          type: string
          example: Validation job created. Use the job ID to check status.

    ValidationResult:
      type: object
      properties:
        jobId:
          type: string
        status:
          type: string
          enum: [pending, processing, completed, failed]
        startedAt:
          type: string
          format: date-time
        completedAt:
          type: string
          format: date-time
        result:
          type: object
          properties:
            status:
              type: string
              enum: [valid, warnings, errors]
            issues:
              type: array
              items:
                type: object
                properties:
                  severity:
                    type: string
                    enum: [error, warning, info]
                  field:
                    type: string
                  message:
                    type: string
                  documents:
                    type: object
                    additionalProperties: true

    MergeJobResponse:
      type: object
      properties:
        jobId:
          type: string
          example: '1014851234567890123'
        status:
          type: string
          enum: [pending, processing, completed, failed]
          example: pending
        documentIds:
          type: array
          items:
            type: string
        message:
          type: string
          example: Merge job created. Use the job ID to check status.

    MergeResult:
      type: object
      properties:
        jobId:
          type: string
        status:
          type: string
          enum: [pending, processing, completed, failed]
        startedAt:
          type: string
          format: date-time
        completedAt:
          type: string
          format: date-time
        result:
          type: object
          properties:
            mergedData:
              type: object
              additionalProperties: true
            conflicts:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                  values:
                    type: object
                    additionalProperties: true
                  resolution:
                    type: string

    Webhook:
      type: object
      properties:
        id:
          type: string
          example: '1014560123456789012'
        url:
          type: string
          format: uri
          example: https://your-app.com/webhook
        events:
          type: array
          items:
            type: string
            enum:
              - extraction.completed
              - extraction.failed
              - validation.completed
              - validation.failed
              - merge.completed
              - merge.failed
              - document.uploaded
              - document.deleted
              - shipment.created
              - shipment.updated
              - source.processing.completed
              - source.processing.failed
              - webhook.test
        description:
          type: string
        isActive:
          type: boolean
        createdAt:
          type: string
          format: date-time
        lastTriggeredAt:
          type: string
          format: date-time

    CreateWebhookRequest:
      type: object
      properties:
        url:
          type: string
          format: uri
          description: HTTPS URL to receive webhook notifications
        events:
          type: array
          items:
            type: string
            enum:
              - extraction.completed
              - extraction.failed
              - validation.completed
              - validation.failed
              - merge.completed
              - merge.failed
              - document.uploaded
              - document.deleted
              - shipment.created
              - shipment.updated
              - source.processing.completed
              - source.processing.failed
              - webhook.test
          minItems: 1
        description:
          type: string
      required: [url, events]

    WebhookPayload:
      type: object
      description: Standard wrapper for all webhook payloads
      properties:
        event:
          type: string
          description: The event type that triggered this webhook
          example: extraction.completed
        timestamp:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the webhook was sent
        data:
          type: object
          description: Event-specific data payload
      required: [event, timestamp, data]

    WebhookDocument:
      type: object
      description: Document object included in webhook payloads
      properties:
        id:
          type: string
          example: '1014560123456789012'
        type:
          type: string
          example: CommercialInvoice
        name:
          type: string
          example: invoice_2024_001.pdf
        size:
          type: integer
          example: 245678
        checksum:
          type: string
          example: '1234567890123456'
        isSelected:
          type: boolean
        extractedData:
          type: object
          nullable: true
          description: The extracted JSON data (null if not extracted)
        extractionStatus:
          type: string
          enum: [pending, processing, completed, failed]
        extractionError:
          type: string
          nullable: true
        extractedAt:
          type: string
          format: date-time
          nullable: true
        mimeType:
          type: string
          example: application/pdf
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    ExtractionCompletedPayload:
      type: object
      properties:
        document:
          $ref: '#/components/schemas/WebhookDocument'
        entityType:
          type: string
          description: Type of entity the document belongs to
          example: shipments
        entityId:
          type: string
          description: ID of the entity the document belongs to
          example: '1014560123456789012'

    ExtractionFailedPayload:
      type: object
      properties:
        document:
          $ref: '#/components/schemas/WebhookDocument'
        entityType:
          type: string
          example: shipments
        entityId:
          type: string
        error:
          type: string
          description: Error message describing what went wrong

    ValidationCompletedPayload:
      type: object
      properties:
        jobId:
          type: string
        entityId:
          type: string
          description: ID of the entity being validated
        entityType:
          type: string
          description: Type of entity being validated
          example: shipments
        documents:
          type: array
          items:
            $ref: '#/components/schemas/WebhookDocument'
        validationResult:
          type: object
          description: Validation results including any warnings/errors
        itemsValidated:
          type: integer
        issuesFound:
          type: integer
        completedAt:
          type: string
          format: date-time
        type:
          type: string
          example: cross-validation

    ValidationFailedPayload:
      type: object
      properties:
        jobId:
          type: string
        entityId:
          type: string
        entityType:
          type: string
        documents:
          type: array
          items:
            $ref: '#/components/schemas/WebhookDocument'
        error:
          type: string
        failedAt:
          type: string
          format: date-time
        type:
          type: string
          example: cross-validation

    MergeCompletedPayload:
      type: object
      properties:
        jobId:
          type: string
        entityId:
          type: string
        entityType:
          type: string
        documents:
          type: array
          items:
            $ref: '#/components/schemas/WebhookDocument'
        mergeResult:
          type: object
          description: Merged document data
        completedAt:
          type: string
          format: date-time

    MergeFailedPayload:
      type: object
      properties:
        jobId:
          type: string
        entityId:
          type: string
        entityType:
          type: string
        documents:
          type: array
          items:
            $ref: '#/components/schemas/WebhookDocument'
        error:
          type: string
        failedAt:
          type: string
          format: date-time

    DocumentUploadedPayload:
      type: object
      properties:
        documentId:
          type: string
        fileName:
          type: string
        entityId:
          type: string
          description: ID of the parent entity
        entityType:
          type: string
          description: Type of parent entity (e.g., "shipments", "rfq_shipments")
        shipmentName:
          type: string
          description: Name of the associated shipment if applicable
        uploadedAt:
          type: string
          format: date-time

    DocumentDeletedPayload:
      type: object
      properties:
        documentId:
          type: string
        fileName:
          type: string
        entityId:
          type: string
          description: ID of the parent entity
        entityType:
          type: string
          description: Type of parent entity (e.g., "shipments", "rfq_shipments")
        shipmentName:
          type: string
          description: Name of the associated shipment if applicable
        deletedAt:
          type: string
          format: date-time

    ShipmentCreatedPayload:
      type: object
      properties:
        shipmentId:
          type: string
        shipmentName:
          type: string
        shipmentType:
          type: string
        status:
          type: string
        commodity:
          type: string
          nullable: true
        portOfLoading:
          type: string
          nullable: true
        portOfDischarge:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time

    ShipmentUpdatedPayload:
      type: object
      properties:
        shipmentId:
          type: string
        shipmentName:
          type: string
        updates:
          type: object
          description: Object containing the fields that were updated
        previousData:
          type: object
          description: Previous values of updated fields (optional)
          nullable: true
        updatedAt:
          type: string
          format: date-time

    WebhookTestPayload:
      type: object
      properties:
        message:
          type: string
          example: This is a test webhook from Wove API
        customerId:
          type: string
        testId:
          type: string
          example: test_1704067200000

    DocumentHistoryEntry:
      type: object
      properties:
        type:
          type: string
          example: edit
        source:
          type: string
          example: manual
        date:
          type: string
          format: date-time
        changes:
          type: object
          additionalProperties: true

    ValidationIssue:
      type: object
      properties:
        field:
          type: string
          example: portOfLoading
        message:
          type: string
          example: Port of loading is required for ocean shipments
        severity:
          type: string
          example: error
        code:
          type: string
          example: REQUIRED_FIELD

    ValidationItem:
      type: object
      properties:
        id:
          type: string
          example: '1014810279825727496'
        crossValidationResultId:
          type: string
        shipmentId:
          type: string
        fieldName:
          type: string
          example: invoiceNumber
        status:
          type: string
          example: error
        reason:
          type: string
          example: Invoice numbers do not match across documents
        suggestion:
          type: string
          example: Use invoice number from the commercial invoice
        files:
          type: array
          items:
            type: string
        documentIds:
          type: array
          items:
            type: string
        actionStatus:
          type: string
          enum: [PENDING, ACCEPTED, IGNORED, OVERRIDDEN]
        actionTakenById:
          type: string
        actionTakenAt:
          type: string
          format: date-time
        actionReason:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    CrossValidationJob:
      type: object
      properties:
        id:
          type: string
        type:
          type: string
          enum: [cross-validation]
        status:
          type: string
          enum: [pending, processing, completed, failed]
        progress:
          type: number
          description: Progress percentage (0-100)
        documentIds:
          type: array
          items:
            type: string
        result:
          type: object
          nullable: true
          properties:
            validatedFields:
              type: array
              items:
                type: string
            issues:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                  message:
                    type: string
                  severity:
                    type: string
                    enum: [critical, warning]
                  suggestion:
                    type: string
                    nullable: true
                  files:
                    type: array
                    items:
                      type: string
                  documents:
                    type: array
                    items:
                      type: string
            validationResult:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  field:
                    type: string
                  files:
                    type: array
                    items:
                      type: string
                  reason:
                    type: string
                  status:
                    type: string
                  suggestion:
                    type: string
                    nullable: true
        createdAt:
          type: string
          format: date-time
        startedAt:
          type: string
          format: date-time
          nullable: true
        completedAt:
          type: string
          format: date-time
          nullable: true

    MergeJob:
      type: object
      properties:
        id:
          type: string
        type:
          type: string
          enum: [merge]
        status:
          type: string
          enum: [pending, processing, completed, failed]
        progress:
          type: number
          description: Progress percentage (0-100)
        documentIds:
          type: array
          items:
            type: string
        result:
          nullable: true
        createdAt:
          type: string
          format: date-time
        startedAt:
          type: string
          format: date-time
          nullable: true
        completedAt:
          type: string
          format: date-time
          nullable: true

    UpdateValidationItemRequest:
      type: object
      properties:
        actionStatus:
          type: string
          enum: [ACCEPTED, IGNORED, OVERRIDDEN]
        actionReason:
          type: string
      required: [actionStatus]

    ValidateDocumentsRequest:
      type: object
      properties:
        documentIds:
          type: array
          items:
            type: string
          minItems: 1
      required: [documentIds]

    MergeDocumentsRequest:
      type: object
      properties:
        documentIds:
          type: array
          items:
            type: string
          minItems: 2
      required: [documentIds]

    UpdateShipmentDetailsRequest:
      type: object
      description: |
        Request body for updating shipment-level details only.
        Containers, packages, and items are now managed through separate CRUD endpoints.
      properties:
        shipment:
          type: object
          properties:
            carrier:
              type: string
            vessel:
              type: string
            voyage:
              type: string
            incoterm:
              type: string
            serviceType:
              type: string
            invoiceDate:
              type: string
              example: '2024-03-15'
            dueDate:
              type: string
              example: '2024-04-15'
            issueDate:
              type: string
              example: '2024-03-10'
            shipmentDate:
              type: string
              example: '2024-03-20'
            expectedDeliveryDate:
              type: string
              example: '2024-04-05'
            readyDate:
              type: string
              example: '2024-03-18'
            dateOfSignature:
              type: string
              example: '2024-03-10'
            portOfLoading:
              type: string
            portOfDischarge:
              type: string
            placeOfReceiptType:
              type: string
              enum: [Port, Airport, Door]
            placeOfReceiptPort:
              $ref: '#/components/schemas/PortLocation'
            placeOfReceiptAirport:
              $ref: '#/components/schemas/AirportLocation'
            placeOfReceiptAddress:
              $ref: '#/components/schemas/AddressLocation'
            placeOfDeliveryType:
              type: string
              enum: [Port, Airport, Door]
            placeOfDeliveryPort:
              $ref: '#/components/schemas/PortLocation'
            placeOfDeliveryAirport:
              $ref: '#/components/schemas/AirportLocation'
            placeOfDeliveryAddress:
              $ref: '#/components/schemas/AddressLocation'
            referenceNumbers:
              type: array
              items:
                $ref: '#/components/schemas/ReferenceNumber'
            consigneeName:
              type: string
              description: Consignee/receiver company name
            consigneeAddress:
              type: object
              description: Consignee address (JSON object)
            consigneeContactName:
              type: string
              description: Consignee contact person name
            consigneeContactPhone:
              type: string
              description: Consignee contact phone number
            consigneeContactEmail:
              type: string
              format: email
              description: Consignee contact email address
            consignorName:
              type: string
              description: Consignor company name
            consignorAddress:
              type: object
              description: Consignor address (JSON object)
            consignorContactName:
              type: string
              description: Consignor contact person name
            consignorContactPhone:
              type: string
              description: Consignor contact phone number
            consignorContactEmail:
              type: string
              format: email
              description: Consignor contact email address
            notifyPartyName:
              type: string
              description: Notify party company name
            notifyPartyAddress:
              type: object
              description: Notify party address (JSON object)
            notifyPartyContactName:
              type: string
              description: Notify party contact person name
            notifyPartyContactPhone:
              type: string
              description: Notify party contact phone number
            notifyPartyContactEmail:
              type: string
              format: email
              description: Notify party contact email address
            destinationAgentName:
              type: string
              description: Destination agent company name
            destinationAgentAddress:
              type: object
              description: Destination agent address (JSON object)
            destinationAgentContactName:
              type: string
              description: Destination agent contact person name
            destinationAgentContactPhone:
              type: string
              description: Destination agent contact phone number
            destinationAgentContactEmail:
              type: string
              format: email
              description: Destination agent contact email address
            totalWeight:
              type: number
              nullable: true
              description: Total weight of all packages
              example: 1500.5
            weightUnit:
              type: string
              nullable: true
              description: Unit for weight measurements
              example: KG
            totalVolume:
              type: number
              nullable: true
              description: Total volume of all packages
              example: 25.5
            volumeUnit:
              type: string
              nullable: true
              description: Unit for volume measurements
              example: CBM
            totalPieces:
              type: integer
              nullable: true
              description: Total number of pieces/items
              example: 500
            totalPacks:
              type: integer
              nullable: true
              description: Total number of top-level shipping units (e.g., 7 pallets containing 200 cartons)
              example: 7
            totalPacksPackageType:
              type: string
              nullable: true
              description: Type of shipping units for totalPacks (e.g., Pallets, Containers)
              example: Pallets
            totalValue:
              type: number
              nullable: true
              description: Total declared value of the shipment
              example: 25000.00
            currency:
              type: string
              nullable: true
              description: Currency code for the total value (ISO 4217)
              example: USD

    CreateContainerRequest:
      type: object
      required: [number, sizeType]
      properties:
        number:
          type: string
          description: Container number/identifier
          example: MSKU1234567
        sizeType:
          type: string
          description: Container size and type (e.g., 20GP, 40HC)
          example: 20GP
        sealNumber:
          type: string
          description: Container seal number
        quantity:
          type: integer
          description: Number of containers
          default: 1
          minimum: 1

    UpdateContainerRequest:
      type: object
      properties:
        number:
          type: string
          description: Container number/identifier
        sizeType:
          type: string
          description: Container size and type
        sealNumber:
          type: string
          description: Container seal number
        quantity:
          type: integer
          description: Number of containers
          minimum: 1

    CreatePackageRequest:
      type: object
      properties:
        containerId:
          type: string
          nullable: true
          description: Container ID (null for loose packages)
        packageNumber:
          type: string
          description: Package number/identifier
        packageType:
          type: string
          description: Type of package (e.g., CARTON, PALLET)
        description:
          type: string
          description: Package description
        packageCount:
          type: integer
          description: Number of packages
          minimum: 1
        lineNumber:
          type: integer
          description: Line number for ordering
        grossWeight:
          type: number
          description: Gross weight of package
        netWeight:
          type: number
          description: Net weight of package
        volume:
          type: number
          description: Volume of package
        weightUnit:
          type: string
          description: Unit for weight measurements
        volumeUnit:
          type: string
          description: Unit for volume measurements

    UpdatePackageRequest:
      type: object
      properties:
        packageNumber:
          type: string
        packageType:
          type: string
        description:
          type: string
        packageCount:
          type: integer
          minimum: 1
        lineNumber:
          type: integer
        grossWeight:
          type: number
        netWeight:
          type: number
        volume:
          type: number
        weightUnit:
          type: string
        volumeUnit:
          type: string

    CreateItemRequest:
      type: object
      properties:
        packageId:
          type: string
          nullable: true
          description: Package ID this item belongs to
        containerId:
          type: string
          nullable: true
          description: Container ID (optional)
        lineNumber:
          type: integer
          description: Line number for ordering
        sku:
          type: string
          description: Stock Keeping Unit
        partNumber:
          type: string
          description: Part number
        description:
          type: string
          description: Item description
        quantity:
          type: integer
          description: Quantity of items
          minimum: 1
        unitOfMeasure:
          type: string
          description: Unit of measure for quantity
        unitPrice:
          type: number
          description: Price per unit
        amount:
          type: number
          description: Total amount
        currency:
          type: string
          description: Currency code
        countryOfOrigin:
          type: string
          description: Country of origin code
        hsCode:
          type: string
          description: Harmonized System code
        weight:
          type: number
          description: Weight per item
        weightUnit:
          type: string
          description: Unit for weight measurement

    UpdateItemRequest:
      type: object
      properties:
        lineNumber:
          type: integer
        sku:
          type: string
        partNumber:
          type: string
        description:
          type: string
        quantity:
          type: integer
          minimum: 1
        unitOfMeasure:
          type: string
        unitPrice:
          type: number
        amount:
          type: number
        currency:
          type: string
        countryOfOrigin:
          type: string
        hsCode:
          type: string
        weight:
          type: number
        weightUnit:
          type: string

    # Sources Schemas
    CreateSourceUploadRequest:
      type: object
      properties:
        filename:
          type: string
          description: The filename of the rate sheet to upload
          example: carrier_rates_2024.xlsx
        contentType:
          type: string
          description: MIME type of the file
          example: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
        sourceType:
          type: string
          enum: [contract, spot, tariff]
          description: Type of rate sheet
        carrierId:
          type: string
          description: Optional carrier ID to associate with this source
        effectiveDate:
          type: string
          format: date
          description: Date when rates become effective
        expiryDate:
          type: string
          format: date
          description: Date when rates expire
        description:
          type: string
          description: Optional description of the source
      required: [filename]

    CreateSourceUploadResponse:
      type: object
      properties:
        sourceId:
          type: string
          description: Unique identifier for the created source
        uploadUrl:
          type: string
          description: Presigned URL for uploading the file (expires in 15 minutes)
        expiresAt:
          type: string
          format: date-time
          description: When the upload URL expires
      required: [sourceId, uploadUrl, expiresAt]

    ProcessSourceResponse:
      type: object
      properties:
        sourceId:
          type: string
          description: The source ID
        status:
          type: string
          enum: [pending, processing]
          description: Current processing status
        message:
          type: string
          description: Status message
      required: [sourceId, status]

    ExternalSourceSummary:
      type: object
      properties:
        id:
          type: string
          description: Source ID
        filename:
          type: string
          description: Original filename
        status:
          type: string
          enum: [awaiting_upload, pending, processing, complete, failed]
          description: Processing status
        sourceType:
          type: string
          enum: [contract, spot, tariff]
        carrierId:
          type: string
        carrierName:
          type: string
        rateCount:
          type: integer
          description: Number of rates extracted from this source
        effectiveDate:
          type: string
          format: date
        expiryDate:
          type: string
          format: date
        createdAt:
          type: string
          format: date-time
        processedAt:
          type: string
          format: date-time

    ExternalSourceDetail:
      type: object
      properties:
        id:
          type: string
        filename:
          type: string
        status:
          type: string
          enum: [awaiting_upload, pending, processing, complete, failed]
        sourceType:
          type: string
          enum: [contract, spot, tariff]
        carrierId:
          type: string
        carrierName:
          type: string
        rateCount:
          type: integer
        effectiveDate:
          type: string
          format: date
        expiryDate:
          type: string
          format: date
        description:
          type: string
        createdAt:
          type: string
          format: date-time
        processedAt:
          type: string
          format: date-time
        error:
          type: string
          nullable: true
          description: Error message if processing failed

    # Query Bank Schemas
    ExternalQueryBankSource:
      type: object
      properties:
        id:
          type: string
          description: Query Bank entry ID
        sourceId:
          type: string
          description: The linked source ID
        filename:
          type: string
          description: Source filename
        carrierId:
          type: string
        carrierName:
          type: string
        sourceType:
          type: string
        rateCount:
          type: integer
        effectiveDate:
          type: string
          format: date
        expiryDate:
          type: string
          format: date
        linkedAt:
          type: string
          format: date-time
          description: When the source was added to Query Bank

    AddSourceToQueryBankRequest:
      type: object
      properties:
        sourceId:
          type: string
          description: The source ID to add to Query Bank
      required: [sourceId]

    AddSourceToQueryBankResponse:
      type: object
      properties:
        sourceId:
          type: string
        linkedAt:
          type: string
          format: date-time
      required: [sourceId, linkedAt]

    RemoveSourceFromQueryBankResponse:
      type: object
      properties:
        sourceId:
          type: string
        unlinkedAt:
          type: string
          format: date-time
      required: [sourceId, unlinkedAt]

    # Rates Query Schemas
    QueryBankRateQueryRequest:
      type: object
      properties:
        origins:
          type: array
          items:
            type: string
          description: Inland origin codes or region codes (USWC, USEC, etc.) for door moves
          example: ['USWC', 'USCHI']
        destinations:
          type: array
          items:
            type: string
          description: Inland destination codes or region codes for door moves
          example: ['CNSHA', 'CNNBO']
        loadingPorts:
          type: array
          items:
            type: string
          description: Port of loading codes (UN/LOCODE format)
          example: ['CNSHA', 'CNNBO']
        dischargePorts:
          type: array
          items:
            type: string
          description: Port of discharge codes (UN/LOCODE format)
          example: ['USLAX', 'USLGB']
        effectiveDate:
          type: string
          format: date
          description: Filter rates effective on or after this date
        expiryDate:
          type: string
          format: date
          description: Filter rates that haven't expired by this date
        containerType:
          type: string
          enum: [dry, reefer, nor]
          description: Container type filter
        containerSizes:
          type: array
          items:
            type: string
            enum: [20ft, 40ft, 40ft_hc, 45ft]
          description: Container size filter
        carrierCodes:
          type: array
          items:
            type: string
          description: Filter by specific carrier SCAC codes
          example: ['MAEU', 'MSCU', 'COSU']
        commodityType:
          type: string
          description: Filter by commodity type
        limit:
          type: integer
          default: 100
          maximum: 500
          description: Maximum number of rates to return
        offset:
          type: integer
          default: 0
          description: Number of rates to skip for pagination

    QueryBankRateQueryResponse:
      type: object
      properties:
        rates:
          type: array
          items:
            $ref: '#/components/schemas/CombinedRate'
        metadata:
          type: object
          properties:
            totalRates:
              type: integer
              description: Total number of matching rates
            sourcesSearched:
              type: integer
              description: Number of sources searched
            queryTimeMs:
              type: integer
              description: Query execution time in milliseconds

    CombinedRate:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier for the rate (hash for deduplication)
        placeOfReceipt:
          description: Origin inland point (for pre-carriage)
          allOf:
            - $ref: '#/components/schemas/LocationInfo'
        portOfLoading:
          description: Port of loading
          allOf:
            - $ref: '#/components/schemas/LocationInfo'
        portOfDischarge:
          description: Port of discharge
          allOf:
            - $ref: '#/components/schemas/LocationInfo'
        placeOfDelivery:
          description: Destination inland point (for on-carriage)
          allOf:
            - $ref: '#/components/schemas/LocationInfo'
        viaPorts:
          description: Transshipment ports in order
          type: array
          items:
            $ref: '#/components/schemas/LocationInfo'
        carrierCode:
          type: string
          description: Carrier SCAC code
        carrierName:
          type: string
          description: Carrier display name
        effectiveDate:
          type: string
          format: date
        expiryDate:
          type: string
          format: date
        commodityType:
          type: string
        transitTime:
          type: integer
          description: Transit time in days
        legs:
          description: Rate breakdown by leg (pre-carriage, main, on-carriage)
          type: array
          items:
            $ref: '#/components/schemas/RateLeg'
        containerRates:
          description: Container-specific pricing
          type: array
          items:
            $ref: '#/components/schemas/CombinedContainerRate'
        surcharges:
          description: Applicable surcharges
          type: array
          items:
            $ref: '#/components/schemas/RateSurcharge'
        sourceId:
          type: string
          description: Source ID where this rate originated
        sourceName:
          type: string
          description: Source filename
      required: [id, portOfLoading, portOfDischarge, carrierCode, containerRates, sourceId, sourceName]

    LocationInfo:
      type: object
      properties:
        code:
          type: string
          description: Location code (UN/LOCODE for ports)
          example: 'CNSHA'
        name:
          type: string
          description: Location display name
          example: 'Shanghai'
        type:
          type: string
          enum: [port, city, address, region]
          description: Type of location
      required: [code, type]

    RateLeg:
      type: object
      properties:
        type:
          type: string
          enum: [pre_carriage, main, on_carriage]
          description: Leg type
        origin:
          $ref: '#/components/schemas/LocationInfo'
        destination:
          $ref: '#/components/schemas/LocationInfo'
        mode:
          type: string
          enum: [truck, rail, barge, ocean]
          description: Transport mode for this leg
        rates:
          description: Container rates for this specific leg
          type: array
          items:
            $ref: '#/components/schemas/LegContainerRate'
      required: [type, origin, destination]

    LegContainerRate:
      type: object
      properties:
        containerSize:
          type: string
          enum: [20ft, 40ft, 40ft_hc, 45ft]
        containerType:
          type: string
          enum: [dry, reefer, nor, general]
        amount:
          type: number
          description: Rate amount
        currency:
          type: string
          description: Currency code (ISO 4217)
          example: 'USD'
      required: [containerSize, containerType, amount, currency]

    CombinedContainerRate:
      type: object
      properties:
        containerType:
          type: string
          enum: [dry, reefer, nor]
        containerSize:
          type: string
          enum: [20ft, 40ft, 40ft_hc, 45ft]
        amount:
          type: number
          description: Total combined rate amount
        currency:
          type: string
          example: 'USD'
        breakdown:
          type: object
          properties:
            preCarriage:
              type: number
              description: Pre-carriage portion
            main:
              type: number
              description: Ocean freight portion
            onCarriage:
              type: number
              description: On-carriage portion
          description: Per-leg breakdown of the total amount
      required: [containerType, containerSize, amount, currency]

    RateSurcharge:
      type: object
      properties:
        code:
          type: string
          description: Surcharge code (e.g., BAF, CAF, THC)
          example: 'BAF'
        name:
          type: string
          description: Surcharge display name
          example: 'Bunker Adjustment Factor'
        applicability:
          type: string
          enum: [inclusive, subject_to, not_subject_to, not_applicable]
          description: Whether this surcharge is included in base rate or additional
        basis:
          type: string
          description: Charging basis (e.g., per_container, per_bl, percentage)
          example: 'per_container'
        amount:
          type: number
          description: Default surcharge amount (when not container-specific)
        currency:
          type: string
          example: 'USD'
        containerRates:
          description: Container-specific surcharge amounts
          type: array
          items:
            $ref: '#/components/schemas/SurchargeContainerRate'
        percentage:
          type: number
          description: Percentage-based surcharge (if applicable)
        applicablePorts:
          type: array
          items:
            type: string
          description: Specific ports this surcharge applies to
        effectiveDate:
          type: string
          format: date
        expiryDate:
          type: string
          format: date
      required: [code, name, applicability]

    SurchargeContainerRate:
      type: object
      properties:
        containerSize:
          type: string
          enum: [20ft, 40ft, 40ft_hc, 45ft]
        containerType:
          type: string
          enum: [dry, reefer, nor, general, all]
        amount:
          type: number
        currency:
          type: string
          example: 'USD'
      required: [containerSize, amount, currency]

    Organization:
      type: object
      description: Organization/company information
      properties:
        name:
          type: string
          description: Organization name
        code:
          type: string
          description: Organization code or identifier
          nullable: true
      required: [name]

    Address:
      type: object
      description: Physical address
      properties:
        street1:
          type: string
          description: Street address line 1
          nullable: true
        street2:
          type: string
          description: Street address line 2
          nullable: true
        city:
          type: string
          description: City name
          nullable: true
        state:
          type: string
          description: State or province
          nullable: true
        postalCode:
          type: string
          description: Postal or ZIP code
          nullable: true
        country:
          type: string
          description: Country code (2-letter ISO)
          nullable: true

  responses:
    UnauthorizedError:
      description: Authentication required or invalid credentials
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            invalid_token:
              summary: Invalid or expired token
              value:
                success: false
                error:
                  code: AUTHENTICATION_ERROR
                  message: Invalid or expired access token
            missing_auth:
              summary: Missing authentication
              value:
                success: false
                error:
                  code: AUTHENTICATION_ERROR
                  message: Authentication required

    ForbiddenError:
      description: Insufficient permissions
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            insufficient_scope:
              summary: Missing required scope
              value:
                success: false
                error:
                  code: AUTHORIZATION_ERROR
                  message: 'Insufficient scope: documents:read required'
            access_denied:
              summary: Access denied to resource
              value:
                success: false
                error:
                  code: AUTHORIZATION_ERROR
                  message: Access denied to this resource

    NotFoundError:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: NOT_FOUND
              message: Resource not found

    BadRequestError:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            validation_error:
              summary: Validation error
              value:
                success: false
                error:
                  code: VALIDATION_ERROR
                  message: Invalid input parameters
                  details:
                    field: shipmentType
                    error: Must be one of FCL, LCL, or Air
            missing_required:
              summary: Missing required field
              value:
                success: false
                error:
                  code: VALIDATION_ERROR
                  message: Missing required field
                  details:
                    field: name
                    error: |
                      Field is required
    RateLimitError:
      description: Too many requests
      headers:
        X-RateLimit-Limit-Minute:
          schema:
            type: integer
          description: Requests per minute limit
        X-RateLimit-Remaining-Minute:
          schema:
            type: integer
          description: Remaining requests this minute
        X-RateLimit-Reset-Minute:
          schema:
            type: integer
          description: Unix timestamp when minute limit resets
        X-RateLimit-Limit-Day:
          schema:
            type: integer
          description: Requests per day limit
        X-RateLimit-Remaining-Day:
          schema:
            type: integer
          description: Remaining requests today
        X-RateLimit-Reset-Day:
          schema:
            type: integer
          description: Unix timestamp when daily limit resets
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: RATE_LIMIT_ERROR
              message: Too many requests. Please retry after some time.
              details:
                retryAfter: 60
                limit: 60
                remaining: 0

    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: INTERNAL_ERROR
              message: An internal error occurred. Please try again later.

    TmsOrganization:
      type: object
      properties:
        id:
          type: string
          example: '123456789'
        customerId:
          type: string
        source:
          type: string
          enum: [cw, gf, lw, ns]
        code:
          type: string
          example: 'ORG001'
        name:
          type: string
          example: 'Acme Shipping Corp'
        types:
          type: array
          items:
            type: string
          nullable: true
          example: ['consignee', 'shipper']
        carrierCode:
          type: string
          nullable: true
        address1:
          type: string
          nullable: true
        address2:
          type: string
          nullable: true
        city:
          type: string
          nullable: true
        state:
          type: string
          nullable: true
        postalCode:
          type: string
          nullable: true
        country:
          type: string
          nullable: true
        phoneNumber:
          type: string
          nullable: true
        mobileNumber:
          type: string
          nullable: true
        faxNumber:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
        website:
          type: string
          nullable: true
        metadata:
          type: object
          nullable: true
        isActive:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required: [id, customerId, source, code, name, isActive, createdAt, updatedAt]

    CreateTmsOrganizationRequest:
      type: object
      properties:
        source:
          type: string
          enum: [cw, gf, lw, ns]
        code:
          type: string
          example: 'ORG001'
        name:
          type: string
          example: 'Acme Shipping Corp'
        types:
          type: array
          items:
            type: string
        carrierCode:
          type: string
        address1:
          type: string
        address2:
          type: string
        city:
          type: string
        state:
          type: string
        postalCode:
          type: string
        country:
          type: string
        phoneNumber:
          type: string
        mobileNumber:
          type: string
        faxNumber:
          type: string
        email:
          type: string
        website:
          type: string
        isActive:
          type: boolean
          default: true
        metadata:
          type: object
      required: [source, code, name]

    UpdateTmsOrganizationRequest:
      type: object
      properties:
        code:
          type: string
        name:
          type: string
        types:
          type: array
          items:
            type: string
          nullable: true
        carrierCode:
          type: string
          nullable: true
        address1:
          type: string
          nullable: true
        address2:
          type: string
          nullable: true
        city:
          type: string
          nullable: true
        state:
          type: string
          nullable: true
        postalCode:
          type: string
          nullable: true
        country:
          type: string
          nullable: true
        phoneNumber:
          type: string
          nullable: true
        mobileNumber:
          type: string
          nullable: true
        faxNumber:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
        website:
          type: string
          nullable: true
        isActive:
          type: boolean
        metadata:
          type: object
          nullable: true

    TmsOrganizationImportResponse:
      type: object
      properties:
        jobId:
          type: string
          description: ID of the async import job
        message:
          type: string
      required: [jobId, message]

    TmsOrganizationImportStatus:
      type: object
      properties:
        jobId:
          type: string
        status:
          type: string
          enum: [pending, processing, completed, failed]
        totalRows:
          type: integer
        processedRows:
          type: integer
        successfulRows:
          type: integer
        failedRows:
          type: integer
        percentComplete:
          type: integer
      required: [jobId, status, totalRows, processedRows, successfulRows, failedRows, percentComplete]

security:
  - bearerAuth: []

tags:
  - name: Authentication
    description: OAuth 2.0 authentication endpoints
  - name: Testing
    description: Test endpoints for API validation
  - name: Shipments
    description: Shipment management operations
  - name: Documents
    description: Document management within shipments
  - name: Webhooks
    description: Webhook management for async notifications
  - name: Sources
    description: Rate sheet source management - upload, process, and query status
  - name: Query Bank
    description: Query Bank management - add/remove sources from your rate query pool
  - name: Rates
    description: Query freight rates from your Query Bank
  - name: Tariffs
    description: Duty and tariff rate lookup by HS code with customer-specific overrides
  - name: TMS Organizations
    description: TMS organization management - CRUD and bulk JSONL import

paths:
  /api/v1/external/auth/token:
    post:
      tags: [Authentication]
      summary: Get OAuth access token
      description: |
        Obtain an access token using OAuth 2.0 client credentials flow.
        This token is required for all other API endpoints.

        **Rate Limiting**: 100 requests per minute per IP address.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenRequest'
            example:
              grant_type: client_credentials
              client_id: wove_prod_abc123def456
              client_secret: sk_your_secret_key
              scope: shipments:read documents:read validation:create
      responses:
        '200':
          description: Access token generated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
              example:
                access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                token_type: Bearer
                expires_in: 3600
                scope: shipments:read documents:read validation:create
        '400':
          description: Invalid request parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Invalid client credentials
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/auth/revoke:
    post:
      tags: [Authentication]
      summary: Revoke OAuth token
      description: |
        Revoke an OAuth access token to immediately invalidate it.
        This is useful when a token is compromised or no longer needed.
      security:
        - bearerAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                token:
                  type: string
                  description: |
                    The token to revoke (optional, defaults to current token)
                token_type_hint:
                  type: string
                  enum: [access_token, refresh_token]
                  description: Hint about the token type
      responses:
        '200':
          description: Token revoked successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
              example:
                success: true
                data:
                  revoked: true
                message: Token revoked successfully
        '401':
          description: Invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/test/ping:
    get:
      tags: [Testing]
      summary: Test API connectivity
      description: |
        Simple ping endpoint to test API connectivity and authentication.
        Returns basic information about your authenticated session.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Ping successful
          headers:
            X-RateLimit-Limit-Minute:
              schema:
                type: integer
              description: Requests per minute limit
            X-RateLimit-Remaining-Minute:
              schema:
                type: integer
              description: Remaining requests this minute
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
              example:
                success: true
                data:
                  message: pong
                  customerId: '1'
                  clientId: '1014844138697089024'
                  timestamp: '2025-07-12T00:30:05.083Z'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/test/rate-limit-status:
    get:
      tags: [Testing]
      summary: Get rate limit status
      description: |
        Check your current rate limit usage and remaining quota.
        Useful for monitoring your API usage and planning requests.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Rate limit status retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      limits:
                        type: object
                        properties:
                          perMinute:
                            type: integer
                            example: 60
                          perDay:
                            type: integer
                            example: 10000
                      usage:
                        type: object
                        properties:
                          minute:
                            type: integer
                            example: 5
                          day:
                            type: integer
                            example: 150
                      remaining:
                        type: object
                        properties:
                          minute:
                            type: integer
                            example: 55
                          day:
                            type: integer
                            example: 9850
                      resetTimes:
                        type: object
                        properties:
                          minute:
                            type: string
                            format: date-time
                            example: '2025-07-12T00:32:00.000Z'
                          day:
                            type: string
                            format: date-time
                            example: '2025-07-13T00:00:00.000Z'

  /api/v1/external/test/protected:
    get:
      tags: [Testing]
      summary: Test protected endpoint
      description: |
        Test endpoint that requires authentication and specific scopes.
        Use this to verify that your OAuth token has the correct permissions.
      security:
        - bearerAuth: [shipments:read]
      responses:
        '200':
          description: Access granted
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                        example: Access granted to protected endpoint
                      customerId:
                        type: string
                        example: '1'
                      scopes:
                        type: array
                        items:
                          type: string
                        example: [shipments:read, documents:read]
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Insufficient permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/test/api-usage-status:
    get:
      tags: [Testing]
      summary: Get API usage statistics
      description: |
        Get detailed API usage statistics for the current OAuth client.
        Includes daily, monthly, and total usage metrics.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: API usage statistics retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      dailyUsage:
                        type: integer
                        example: 150
                        description: Number of API calls made today
                      monthlyUsage:
                        type: integer
                        example: 4523
                        description: Number of API calls made this month
                      totalUsage:
                        type: integer
                        example: 15234
                        description: Total number of API calls made
                      lastRequest:
                        type: string
                        format: date-time
                        example: '2025-07-12T00:30:05.083Z'
                        description: Timestamp of the last API request
                      averageResponseTime:
                        type: number
                        example: 125.5
                        description: Average response time in milliseconds
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/shipments:
    get:
      tags: [Shipments]
      summary: List shipments
      description: |
        Retrieve a paginated list of shipments for your organization.
        Results are ordered by creation date (newest first).
      security:
        - bearerAuth: []
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Page number for pagination
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: Number of items per page
        - name: status
          in: query
          schema:
            type: string
            enum: [draft, booked, pending, in_transit, delivered, cancelled]
          description: Filter by shipment status
        - name: shipmentType
          in: query
          schema:
            type: string
            enum: [FCL, LCL, Air]
          description: Filter by shipment type
        - name: search
          in: query
          schema:
            type: string
          description: Search shipments by name or ID
      responses:
        '200':
          description: Shipments retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/ShipmentSummary'
                      pagination:
                        type: object
                        properties:
                          page:
                            type: integer
                            example: 1
                          limit:
                            type: integer
                            example: 20
                          total:
                            type: integer
                            example: 150
                          totalPages:
                            type: integer
                            example: 8
                          hasNext:
                            type: boolean
                            example: true
                          hasPrev:
                            type: boolean
                            example: false

    post:
      tags: [Shipments]
      summary: Create shipment
      description: Create a new shipment in draft status.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateShipmentRequest'
      responses:
        '201':
          description: Shipment created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/ShipmentDetails'
        '400':
          description: Invalid request parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/shipments/{shipmentId}:
    get:
      tags: [Shipments]
      summary: Get shipment details
      description: Retrieve detailed information about a specific shipment.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      responses:
        '200':
          description: Shipment details retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/ShipmentDetails'
        '404':
          description: Shipment not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

    put:
      tags: [Shipments]
      summary: Update shipment
      description: Update an existing shipment's details.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateShipmentRequest'
      responses:
        '200':
          description: |
            Shipment updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/ShipmentDetails'
        '404':
          description: Shipment not found

    delete:
      tags: [Shipments]
      summary: Delete shipment
      description: Delete a shipment and all associated data.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      responses:
        '204':
          description: Shipment deleted successfully
        '404':
          description: Shipment not found

  /api/v1/external/shipments/{shipmentId}/details:
    get:
      tags: [Shipments]
      summary: Get detailed shipment information
      description: Retrieve comprehensive shipment details including items and containers.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      responses:
        '200':
          description: Detailed shipment information retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      shipment:
                        $ref: '#/components/schemas/ShipmentDetails'
                      items:
                        type: array
                        items:
                          $ref: '#/components/schemas/ShipmentItem'
                      containers:
                        type: array
                        items:
                          $ref: '#/components/schemas/Container'
        '404':
          description: Shipment not found

    put:
      tags: [Shipments]
      summary: Update shipment information
      description: |
        Update shipment-level details only. 
        To update containers, packages, or items, use the respective CRUD endpoints:
        - Containers: PUT /shipments/{shipmentId}/containers/{containerId}
        - Packages: PUT /shipments/{shipmentId}/packages/{packageId}
        - Items: PUT /shipments/{shipmentId}/items/{itemId}
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateShipmentDetailsRequest'
      responses:
        '200':
          description: Shipment details updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/ShipmentDetails'
                  message:
                    type: string
        '400':
          description: Invalid request parameters
        '404':
          description: Shipment not found

  /api/v1/external/shipments/{shipmentId}/events:
    get:
      tags: [Shipments]
      summary: Get shipment events
      description: Retrieve chronological events and milestones for a shipment.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      responses:
        '200':
          description: Shipment events retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      events:
                        type: array
                        items:
                          $ref: '#/components/schemas/ShipmentEvent'
        '404':
          description: Shipment not found

  /api/v1/external/shipments/{shipmentId}/documents:
    get:
      tags: [Documents]
      summary: List shipment documents
      description: Retrieve all documents associated with a specific shipment.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: includeDeleted
          in: query
          schema:
            type: boolean
          description: Include deleted documents in the response
        - name: documentTypes
          in: query
          schema:
            type: string
          description: Filter by document types (comma-separated)
        - name: status
          in: query
          schema:
            type: string
            enum: [uploaded, processing, completed, failed]
          description: Filter by document status
      responses:
        '200':
          description: Documents retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Document'

    post:
      tags: [Documents]
      summary: Upload document
      description: Upload a single document to a shipment.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: File to upload
                documentType:
                  type: string
                  description: Document type
                  example: CommercialInvoice
              required: [file]
      responses:
        '201':
          description: Document uploaded successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Document'
                  message:
                    type: string
                    example: Document uploaded successfully and extraction queued

  /api/v1/external/shipments/{shipmentId}/documents/{documentId}:
    get:
      tags: [Documents]
      summary: Get document details
      description: Retrieve detailed information about a specific document.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Unique document identifier
      responses:
        '200':
          description: Document details retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    allOf:
                      - $ref: '#/components/schemas/Document'
                      - type: object
                        properties:
                          validationItems:
                            type: array
                            items:
                              type: object
                            description: Associated validation items
        '404':
          description: Document not found

    put:
      tags: [Documents]
      summary: Update document
      description: Update document metadata or type.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Unique document identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                type:
                  type: string
                  description: Document type
                name:
                  type: string
                  description: Document name
                isSelected:
                  type: boolean
                  description: Whether document is selected
      responses:
        '200':
          description: Document updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Document'

    delete:
      tags: [Documents]
      summary: Delete document
      description: Delete a document from the system.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Unique document identifier
      responses:
        '204':
          description: Document deleted successfully
        '404':
          description: Document not found

  /api/v1/external/shipments/{shipmentId}/documents/{documentId}/download:
    get:
      tags: [Documents]
      summary: Download document
      description: Download the original document file.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Unique document identifier
      responses:
        '200':
          description: Document file
          content:
            application/pdf:
              schema:
                type: string
                format: binary
            application/octet-stream:
              schema:
                type: string
                format: binary
        '404':
          description: Document not found

  /api/v1/external/shipments/{shipmentId}/documents/{documentId}/extraction:
    get:
      tags: [Documents]
      summary: Get extraction status
      description: Get the extraction status and results for a document.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Unique document identifier
      responses:
        '200':
          description: Extraction status retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      status:
                        type: string
                        enum: [pending, processing, completed, failed]
                      extractedData:
                        type: object
                      confidence:
                        type: number
                      extractedAt:
                        type: string
                        format: date-time
                      extractionMethod:
                        type: string

  /api/v1/external/shipments/{shipmentId}/documents/{documentId}/extract:
    post:
      tags: [Documents]
      summary: Start extraction
      description: Start or restart extraction for a document.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Unique document identifier
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                reprocess:
                  type: boolean
                  description: Force reprocessing even if already extracted
                fields:
                  type: array
                  items:
                    type: string
                  description: Specific fields to extract
      responses:
        '200':
          description: Extraction started successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      jobId:
                        type: string
                      message:
                        type: string

  /api/v1/external/shipments/{shipmentId}/documents/{documentId}/extracted-data:
    put:
      tags: [Documents]
      summary: Update extracted data
      description: Manually update the extracted data for a document.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Unique document identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                data:
                  type: object
                  description: The extracted data
                changes:
                  type: object
                  description: Description of changes made
              required: [data, changes]
      responses:
        '200':
          description: Extracted data updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                  message:
                    type: string

  /api/v1/external/shipments/{shipmentId}/documents/{documentId}/history:
    get:
      tags: [Documents]
      summary: Get document history
      description: Get the history of changes for a document.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Unique document identifier
      responses:
        '200':
          description: Document history retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/DocumentHistoryEntry'

  /api/v1/external/shipments/{shipmentId}/documents/validate:
    post:
      tags: [Documents]
      summary: Validate documents
      description: Validate specific documents within a shipment.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                documentIds:
                  type: array
                  items:
                    type: string
                  minItems: 1
              required: [documentIds]
      responses:
        '200':
          description: Validation completed
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      status:
                        type: string
                        enum: [valid, errors]
                      issues:
                        type: array
                        items:
                          type: object
                      validatedAt:
                        type: string
                        format: date-time
                      documentIds:
                        type: array
                        items:
                          type: string

  /api/v1/external/shipments/{shipmentId}/documents/cross-validate/{jobId}:
    get:
      tags: [Documents]
      summary: Get cross-validation job status
      description: Check the status and results of a cross-validation job.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: jobId
          in: path
          required: true
          schema:
            type: string
          description: Unique job identifier
      responses:
        '200':
          description: Cross-validation status retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      job:
                        type: object
                        properties:
                          id:
                            type: string
                          status:
                            type: string
                            enum: [pending, processing, completed, failed]
                          progress:
                            type: number
                          result:
                            type: object
                            nullable: true
                            properties:
                              validatedFields:
                                type: array
                                items:
                                  type: string
                              issues:
                                type: array
                                items:
                                  type: object
                                  properties:
                                    field:
                                      type: string
                                    message:
                                      type: string
                                    severity:
                                      type: string
                                      enum: [critical, warning]
                                    suggestion:
                                      type: string
                                      nullable: true
                                    files:
                                      type: array
                                      items:
                                        type: string
                                    documents:
                                      type: array
                                      items:
                                        type: string
                          error:
                            type: string
                            nullable: true
                          createdAt:
                            type: string
                            format: date-time
                          startedAt:
                            type: string
                            format: date-time
                            nullable: true
                          completedAt:
                            type: string
                            format: date-time
                            nullable: true
        '404':
          description: Job not found

  /api/v1/external/shipments/{shipmentId}/documents/merge/{jobId}:
    get:
      tags: [Documents]
      summary: Get merge job status
      description: Check the status and results of a document merge operation.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: jobId
          in: path
          required: true
          schema:
            type: string
          description: Unique job identifier
      responses:
        '200':
          description: Merge status retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      job:
                        type: object
                        properties:
                          id:
                            type: string
                          status:
                            type: string
                            enum: [pending, processing, completed, failed]
                          progress:
                            type: number
                          result:
                            nullable: true
                          error:
                            type: string
                            nullable: true
                          createdAt:
                            type: string
                            format: date-time
                          startedAt:
                            type: string
                            format: date-time
                            nullable: true
                          completedAt:
                            type: string
                            format: date-time
                            nullable: true
        '404':
          description: Job not found

  /api/v1/external/shipments/{shipmentId}/validation-items:
    get:
      tags: [Shipments]
      summary: Get validation items
      description: Retrieve all validation items for a shipment with optional filtering.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: status
          in: query
          schema:
            type: string
          description: Filter by validation item status
        - name: actionStatus
          in: query
          schema:
            type: string
            enum: [PENDING, ACCEPTED, IGNORED, OVERRIDDEN]
          description: Filter by validation item action status
      responses:
        '200':
          description: Validation items retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ValidationItem'
        '404':
          description: Shipment not found

  /api/v1/external/shipments/{shipmentId}/validation-items/{itemId}:
    put:
      tags: [Shipments]
      summary: Update validation item
      description: Update the action status and details of a validation item.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: itemId
          in: path
          required: true
          schema:
            type: string
          description: Unique validation item identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateValidationItemRequest'
      responses:
        '200':
          description: Validation item updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                    example: Validation item updated successfully
        '400':
          description: Invalid request
        '404':
          description: Validation item or shipment not found

  /api/v1/external/shipments/{shipmentId}/validation-items/bulk-update:
    post:
      tags: [Shipments]
      summary: Bulk update validation items
      description: Update multiple validation items at once with the same action.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                itemIds:
                  type: array
                  items:
                    type: string
                  minItems: 1
                  maxItems: 100
                  description: Array of validation item IDs to update
                actionStatus:
                  type: string
                  enum: [ACCEPTED, IGNORED, OVERRIDDEN]
                  description: Action to apply to all items
                actionReason:
                  type: string
                  description: Optional reason explaining the action
              required: [itemIds, actionStatus]
      responses:
        '200':
          description: Validation items updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    nullable: true
                  message:
                    type: string
                    example: 5 validation items updated successfully
        '400':
          description: Invalid request
        '404':
          description: Shipment not found

  /api/v1/external/shipments/{shipmentId}/jobs:
    get:
      tags: [Shipments]
      summary: Get shipment jobs
      description: Retrieve all background jobs (extraction, validation, merge) for a shipment.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      responses:
        '200':
          description: Jobs retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      jobs:
                        type: object
                        properties:
                          crossValidation:
                            type: array
                            items:
                              $ref: '#/components/schemas/CrossValidationJob'
                          merge:
                            type: array
                            items:
                              $ref: '#/components/schemas/MergeJob'
        '404':
          description: Shipment not found

  /api/v1/external/shipments/{shipmentId}/documents/cross-validate:
    post:
      tags: [Documents]
      summary: Cross-validate documents
      description: |
        Create a cross-validation job to check for inconsistencies between documents in a shipment.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ValidateDocumentsRequest'
      responses:
        '200':
          description: Cross-validation job created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      jobId:
                        type: string
                      message:
                        type: string
        '400':
          description: Invalid request
        '404':
          description: Shipment not found

  /api/v1/external/shipments/{shipmentId}/documents/merge:
    post:
      tags: [Documents]
      summary: Merge shipment documents
      description: |
        Create a job to merge data from multiple documents within a shipment.
        Intelligently combines data from related documents (e.g., commercial invoice and packing list).
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MergeDocumentsRequest'
      responses:
        '200':
          description: Merge job created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/MergeJobResponse'
        '400':
          description: Invalid request or documents not found
        '404':
          description: Shipment not found

  /api/v1/external/shipments/{shipmentId}/documents/analyze-types:
    post:
      tags: [Documents]
      summary: Analyze document types
      description: |
        Analyze a document to detect multiple document types within it.
        For PDFs, this will identify different document types and their page boundaries.
        For other file types, it will identify the document type based on content.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: The document file to analyze
              required: [file]
      responses:
        '200':
          description: Document analysis completed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      types:
                        type: array
                        description: Array of detected document types and their boundaries
                        items:
                          type: object
                          properties:
                            documentType:
                              type: string
                              description: Type of document detected
                              enum:
                                [
                                  CommercialInvoice,
                                  PackingList,
                                  BillOfLading,
                                  AirWaybill,
                                  CertificateOfOrigin,
                                  DangerousGoodsDeclaration,
                                  Other,
                                ]
                            startPage:
                              type: integer
                              nullable: true
                              description: Starting page number (1-indexed) for PDFs
                            endPage:
                              type: integer
                              nullable: true
                              description: Ending page number (1-indexed) for PDFs
                            confidence:
                              type: string
                              enum: [high, medium, low]
                              description: Confidence level of the detection
                      totalPages:
                        type: integer
                        description: Total number of pages in the document (for PDFs)
                      requiresSplit:
                        type: boolean
                        description: Whether the document contains multiple types requiring split
        '400':
          description: Invalid file or request
        '404':
          description: Shipment not found
        '413':
          description: File too large

  /api/v1/external/shipments/{shipmentId}/documents/upload-multi:
    post:
      tags: [Documents]
      summary: Upload multi-document file
      description: |
        Upload a file containing multiple documents.
        For PDFs with multiple document types, this will create separate document records for each type.
        The original file is stored once, with virtual splits created for each document type.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: The document file to upload
                types:
                  type: string
                  description: JSON string containing array of document types with page ranges
                  example: '[{"documentType":"BillOfLading","startPage":1,"endPage":2},{"documentType":"CommercialInvoice","startPage":3,"endPage":4}]'
              required: [file, types]
      responses:
        '200':
          description: Documents uploaded successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      documents:
                        type: array
                        description: Array of created document records
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              description: Document ID
                            type:
                              type: string
                              description: Document type
                            name:
                              type: string
                              description: Generated document name
                            startPage:
                              type: integer
                              nullable: true
                              description: Start page for split documents
                            endPage:
                              type: integer
                              nullable: true
                              description: End page for split documents
                            isSplit:
                              type: boolean
                              description: Whether this is a split document
        '400':
          description: Invalid file or types specification
        '404':
          description: Shipment not found
        '413':
          description: File too large

  # Container CRUD endpoints
  /api/v1/external/shipments/{shipmentId}/containers:
    get:
      tags: [Shipments]
      summary: List containers
      description: Retrieve all containers for a specific shipment.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      responses:
        '200':
          description: Containers retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      containers:
                        type: array
                        items:
                          $ref: '#/components/schemas/ShipmentContainer'
        '404':
          description: Shipment not found

    post:
      tags: [Shipments]
      summary: Create container
      description: Add a new container to a shipment.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateContainerRequest'
      responses:
        '201':
          description: Container created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/ShipmentContainer'
                  message:
                    type: string
        '400':
          description: Invalid request data
        '404':
          description: Shipment not found

  /api/v1/external/shipments/{shipmentId}/containers/{containerId}:
    put:
      tags: [Shipments]
      summary: Update container
      description: Update an existing container's details.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: containerId
          in: path
          required: true
          schema:
            type: string
          description: Unique container identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateContainerRequest'
      responses:
        '200':
          description: Container updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/ShipmentContainer'
                  message:
                    type: string
        '400':
          description: Invalid request data
        '404':
          description: Container or shipment not found

    delete:
      tags: [Shipments]
      summary: Delete container
      description: Remove a container from a shipment. Associated packages will become loose packages.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: containerId
          in: path
          required: true
          schema:
            type: string
          description: Unique container identifier
      responses:
        '200':
          description: Container deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      success:
                        type: boolean
                  message:
                    type: string
        '404':
          description: Container or shipment not found

  # Package CRUD endpoints
  /api/v1/external/shipments/{shipmentId}/packages:
    get:
      tags: [Shipments]
      summary: List packages
      description: Retrieve all packages for a specific shipment, optionally filtered by container.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: containerId
          in: query
          required: false
          schema:
            type: string
            nullable: true
          description: Filter packages by container ID (use 'null' for loose packages)
      responses:
        '200':
          description: Packages retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      packages:
                        type: array
                        items:
                          $ref: '#/components/schemas/ShipmentPackage'
        '404':
          description: Shipment not found

    post:
      tags: [Shipments]
      summary: Create package
      description: Add a new package to a shipment (either in a container or as a loose package).
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePackageRequest'
      responses:
        '201':
          description: Package created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/ShipmentPackage'
                  message:
                    type: string
        '400':
          description: Invalid request data
        '404':
          description: Shipment not found

  /api/v1/external/shipments/{shipmentId}/packages/{packageId}:
    put:
      tags: [Shipments]
      summary: Update package
      description: Update an existing package's details.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: packageId
          in: path
          required: true
          schema:
            type: string
          description: Unique package identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdatePackageRequest'
      responses:
        '200':
          description: Package updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/ShipmentPackage'
                  message:
                    type: string
        '400':
          description: Invalid request data
        '404':
          description: Package or shipment not found

    delete:
      tags: [Shipments]
      summary: Delete package
      description: Remove a package from a shipment. Associated items will be deleted.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: packageId
          in: path
          required: true
          schema:
            type: string
          description: Unique package identifier
      responses:
        '200':
          description: Package deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      success:
                        type: boolean
                  message:
                    type: string
        '404':
          description: Package or shipment not found

  /api/v1/external/shipments/{shipmentId}/packages/{packageId}/move:
    post:
      tags: [Shipments]
      summary: Move package
      description: Move a package to a different container or make it a loose package.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: packageId
          in: path
          required: true
          schema:
            type: string
          description: Unique package identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                containerId:
                  type: string
                  nullable: true
                  description: Target container ID (null to make it a loose package)
              required: [containerId]
      responses:
        '200':
          description: Package moved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
        '400':
          description: Invalid request data
        '404':
          description: Package, container, or shipment not found

  # Item CRUD endpoints
  /api/v1/external/shipments/{shipmentId}/items:
    get:
      tags: [Shipments]
      summary: List items
      description: Retrieve all items for a specific shipment, optionally filtered by package.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: packageId
          in: query
          required: false
          schema:
            type: string
          description: Filter items by package ID
      responses:
        '200':
          description: Items retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      items:
                        type: array
                        items:
                          $ref: '#/components/schemas/ShipmentItem'
        '404':
          description: Shipment not found

    post:
      tags: [Shipments]
      summary: Create item
      description: Add a new item to a package.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateItemRequest'
      responses:
        '201':
          description: Item created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/ShipmentItem'
                  message:
                    type: string
        '400':
          description: Invalid request data
        '404':
          description: Shipment or package not found

  /api/v1/external/shipments/{shipmentId}/items/{itemId}:
    put:
      tags: [Shipments]
      summary: Update item
      description: Update an existing item's details.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: itemId
          in: path
          required: true
          schema:
            type: string
          description: Unique item identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateItemRequest'
      responses:
        '200':
          description: Item updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/ShipmentItem'
                  message:
                    type: string
        '400':
          description: Invalid request data
        '404':
          description: Item or shipment not found

    delete:
      tags: [Shipments]
      summary: Delete item
      description: Remove an item from a package.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: itemId
          in: path
          required: true
          schema:
            type: string
          description: Unique item identifier
      responses:
        '200':
          description: Item deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      success:
                        type: boolean
                  message:
                    type: string
        '404':
          description: Item or shipment not found

  /api/v1/external/shipments/{shipmentId}/items/{itemId}/move:
    post:
      tags: [Shipments]
      summary: Move item
      description: Move an item to a different package.
      security:
        - bearerAuth: []
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: Unique shipment identifier
        - name: itemId
          in: path
          required: true
          schema:
            type: string
          description: Unique item identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                packageId:
                  type: string
                  nullable: true
                  description: Target package ID
                containerId:
                  type: string
                  nullable: true
                  description: Target container ID (optional)
              required: [packageId]
      responses:
        '200':
          description: Item moved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
        '400':
          description: Invalid request data
        '404':
          description: Item, package, or shipment not found

  /api/v1/external/entities/{entityType}/{entityId}/documents:
    get:
      tags: [Documents]
      summary: List entity documents
      description: |
        Retrieve all documents associated with a specific entity.
        Supports any entity type (shipment, rfq_shipment, etc.).
      security:
        - bearerAuth: []
      parameters:
        - name: entityType
          in: path
          required: true
          schema:
            type: string
            enum: [shipment, rfq_shipment]
          description: Type of entity
        - name: entityId
          in: path
          required: true
          schema:
            type: string
          description: Entity ID
        - name: includeDeleted
          in: query
          schema:
            type: boolean
          description: Include deleted documents
        - name: documentTypes
          in: query
          schema:
            type: string
          description: Comma-separated list of document types to filter
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, processing, completed, failed]
          description: Filter by document status
      responses:
        '200':
          description: Documents retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Document'
        '404':
          description: Entity not found
    post:
      tags: [Documents]
      summary: Upload document to entity
      description: |
        Upload a single document and attach it to an entity.
        Document type can be auto-detected or explicitly specified.
      security:
        - bearerAuth: []
      parameters:
        - name: entityType
          in: path
          required: true
          schema:
            type: string
            enum: [shipment, rfq_shipment]
          description: Type of entity
        - name: entityId
          in: path
          required: true
          schema:
            type: string
          description: Entity ID
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: Document file to upload
                documentType:
                  type: string
                  description: Document type (optional, will be auto-detected if not provided)
      responses:
        '201':
          description: Document uploaded successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Document'
                  message:
                    type: string
        '400':
          description: Invalid file or parameters
        '404':
          description: Entity not found

  /api/v1/external/entities/{entityType}/{entityId}/documents/multi:
    post:
      tags: [Documents]
      summary: Upload multi-document PDF
      description: |
        Upload a single PDF file containing multiple documents.
        The system will automatically split and classify each document.
      security:
        - bearerAuth: []
      parameters:
        - name: entityType
          in: path
          required: true
          schema:
            type: string
            enum: [shipment, rfq_shipment]
          description: Type of entity
        - name: entityId
          in: path
          required: true
          schema:
            type: string
          description: Entity ID
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: Multi-document PDF file
                types:
                  type: array
                  items:
                    type: string
                  description: Expected document types (helps with classification)
      responses:
        '201':
          description: Documents uploaded and split successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      jobId:
                        type: string
                        description: Job ID for tracking processing status
                      documents:
                        type: array
                        items:
                          $ref: '#/components/schemas/Document'
                  message:
                    type: string
        '400':
          description: Invalid file or parameters
        '404':
          description: Entity not found

  /api/v1/external/entities/{entityType}/{entityId}/documents/{documentId}:
    get:
      tags: [Documents]
      summary: Get document details
      description: Retrieve detailed information about a specific document.
      security:
        - bearerAuth: []
      parameters:
        - name: entityType
          in: path
          required: true
          schema:
            type: string
            enum: [shipment, rfq_shipment]
          description: Type of entity
        - name: entityId
          in: path
          required: true
          schema:
            type: string
          description: Entity ID
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Document ID
      responses:
        '200':
          description: Document retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Document'
        '404':
          description: Document or entity not found
    put:
      tags: [Documents]
      summary: Update document metadata
      description: Update document type or other metadata.
      security:
        - bearerAuth: []
      parameters:
        - name: entityType
          in: path
          required: true
          schema:
            type: string
            enum: [shipment, rfq_shipment]
          description: Type of entity
        - name: entityId
          in: path
          required: true
          schema:
            type: string
          description: Entity ID
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Document ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                documentType:
                  type: string
                  description: New document type
      responses:
        '200':
          description: Document updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Document'
                  message:
                    type: string
        '404':
          description: Document or entity not found
    delete:
      tags: [Documents]
      summary: Delete document
      description: Delete a document from an entity.
      security:
        - bearerAuth: []
      parameters:
        - name: entityType
          in: path
          required: true
          schema:
            type: string
            enum: [shipment, rfq_shipment]
          description: Type of entity
        - name: entityId
          in: path
          required: true
          schema:
            type: string
          description: Entity ID
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Document ID
      responses:
        '204':
          description: Document deleted successfully
        '404':
          description: Document or entity not found

  /api/v1/external/entities/{entityType}/{entityId}/documents/{documentId}/download:
    get:
      tags: [Documents]
      summary: Download document
      description: Download the original document file.
      security:
        - bearerAuth: []
      parameters:
        - name: entityType
          in: path
          required: true
          schema:
            type: string
            enum: [shipment, rfq_shipment]
          description: Type of entity
        - name: entityId
          in: path
          required: true
          schema:
            type: string
          description: Entity ID
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Document ID
      responses:
        '200':
          description: Document file
          content:
            application/pdf:
              schema:
                type: string
                format: binary
            application/octet-stream:
              schema:
                type: string
                format: binary
        '404':
          description: Document or entity not found

  /api/v1/external/entities/{entityType}/{entityId}/documents/{documentId}/preview:
    get:
      tags: [Documents]
      summary: Get document preview URL
      description: Get a presigned URL for previewing the document.
      security:
        - bearerAuth: []
      parameters:
        - name: entityType
          in: path
          required: true
          schema:
            type: string
            enum: [shipment, rfq_shipment]
          description: Type of entity
        - name: entityId
          in: path
          required: true
          schema:
            type: string
          description: Entity ID
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Document ID
        - name: purpose
          in: query
          schema:
            type: string
          description: Purpose of the preview (for tracking)
      responses:
        '200':
          description: Preview URL generated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      url:
                        type: string
                        description: Presigned URL for document preview
                      expiresAt:
                        type: string
                        format: date-time
                        description: URL expiration time
        '404':
          description: Document or entity not found

  /api/v1/external/entities/{entityType}/{entityId}/documents/{documentId}/history:
    get:
      tags: [Documents]
      summary: Get document history
      description: Retrieve the processing history of a document.
      security:
        - bearerAuth: []
      parameters:
        - name: entityType
          in: path
          required: true
          schema:
            type: string
            enum: [shipment, rfq_shipment]
          description: Type of entity
        - name: entityId
          in: path
          required: true
          schema:
            type: string
          description: Entity ID
        - name: documentId
          in: path
          required: true
          schema:
            type: string
          description: Document ID
      responses:
        '200':
          description: Document history retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/DocumentHistoryEntry'
        '404':
          description: Document or entity not found

  /api/v1/external/webhooks:
    get:
      tags: [Webhooks]
      summary: List webhooks
      description: |
        Retrieve all configured webhooks for your organization.

        ## Webhook Events

        The following events can trigger webhooks:
        - `extraction.completed` - Document extraction successful
        - `extraction.failed` - Document extraction failed
        - `validation.completed` - Cross-validation completed
        - `validation.failed` - Cross-validation failed
        - `merge.completed` - Document merge completed
        - `merge.failed` - Document merge failed
        - `document.uploaded` - New document uploaded
        - `document.deleted` - Document deleted
        - `shipment.created` - Shipment created
        - `shipment.updated` - Shipment updated
        - `source.processing.completed` - Rate sheet source processing completed
        - `source.processing.failed` - Rate sheet source processing failed

        ## Payload Format

        All webhook payloads follow this structure:
        ```json
        {
          "event": "extraction.completed",
          "timestamp": "2024-01-15T10:30:00.000Z",
          "data": { /* event-specific data */ }
        }
        ```

        See the schemas section for detailed payload structures for each event type.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Webhooks retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Webhook'

    post:
      tags: [Webhooks]
      summary: Create webhook
      description: |
        Create a new webhook endpoint for receiving notifications.

        ## Example Payloads

        ### extraction.completed
        ```json
        {
          "event": "extraction.completed",
          "timestamp": "2024-01-15T10:30:00.000Z",
          "data": {
            "document": { /* WebhookDocument object */ },
            "entityType": "shipments",
            "entityId": "1014560123456789012"
          }
        }
        ```

        ### validation.completed
        ```json
        {
          "event": "validation.completed",
          "timestamp": "2024-01-15T10:30:00.000Z",
          "data": {
            "jobId": "1014560123456789012",
            "entityId": "1014560123456789012",
            "entityType": "shipments",
            "documents": [ /* array of WebhookDocument objects */ ],
            "validationResult": { /* validation results */ },
            "itemsValidated": 15,
            "issuesFound": 2,
            "completedAt": "2024-01-15T10:30:00.000Z",
            "type": "cross-validation"
          }
        }
        ```
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookRequest'
            examples:
              allEvents:
                summary: Subscribe to all document events
                value:
                  url: https://your-app.com/webhooks
                  events:
                    - extraction.completed
                    - extraction.failed
                    - validation.completed
                    - validation.failed
                    - merge.completed
                    - merge.failed
                  description: Production webhook for document processing
              extractionOnly:
                summary: Subscribe to extraction events only
                value:
                  url: https://your-app.com/webhooks/extraction
                  events:
                    - extraction.completed
                    - extraction.failed
                  description: Extraction notifications
      responses:
        '201':
          description: Webhook created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    allOf:
                      - $ref: '#/components/schemas/Webhook'
                      - type: object
                        properties:
                          secret:
                            type: string
                            description: Webhook secret for signature verification
                            example: whsec_1234567890abcdef
        '400':
          description: Invalid request parameters

  /api/v1/external/webhooks/{webhookId}:
    get:
      tags: [Webhooks]
      summary: Get webhook details
      description: Retrieve details of a specific webhook.
      security:
        - bearerAuth: []
      parameters:
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
          description: Unique webhook identifier
      responses:
        '200':
          description: Webhook details retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Webhook'
        '404':
          description: Webhook not found

    put:
      tags: [Webhooks]
      summary: Update webhook
      description: Update webhook configuration.
      security:
        - bearerAuth: []
      parameters:
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
          description: Unique webhook identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
                active:
                  type: boolean
                description:
                  type: string
                headers:
                  type: object
                  additionalProperties:
                    type: string
      responses:
        '200':
          description: Webhook updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Webhook'
        '404':
          description: Webhook not found

    delete:
      tags: [Webhooks]
      summary: Delete webhook
      description: Delete a webhook endpoint.
      security:
        - bearerAuth: []
      parameters:
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
          description: Unique webhook identifier
      responses:
        '200':
          description: Webhook deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '404':
          description: Webhook not found

  /api/v1/external/webhooks/{webhookId}/test:
    post:
      tags: [Webhooks]
      summary: Test webhook
      description: Send a test event to the webhook endpoint.
      security:
        - bearerAuth: []
      parameters:
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
          description: Unique webhook identifier
      responses:
        '200':
          description: Test completed
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      status:
                        type: string
                        enum: [success, failed]
                      statusCode:
                        type: integer
                      duration:
                        type: integer
                        description: Response time in milliseconds
                      response:
                        type: string
                        description: Response body from webhook endpoint
        '404':
          description: Webhook not found

  /api/v1/external/webhooks/test:
    post:
      tags: [Webhooks]
      summary: Test webhook URL
      description: Test a webhook URL before creating it.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                  description: HTTPS URL to test
                event:
                  type: string
                  description: Event type to simulate
                  default: webhook.test
              required: [url]
      responses:
        '200':
          description: Test completed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                      payload:
                        type: object
                        description: The test payload that was sent
        '400':
          description: Invalid URL or test failed

  /api/v1/external/webhooks/{webhookId}/deliveries:
    get:
      tags: [Webhooks]
      summary: Get webhook deliveries
      description: Retrieve delivery history for a webhook.
      security:
        - bearerAuth: []
      parameters:
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
          description: Unique webhook identifier
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
          description: Number of deliveries to return
      responses:
        '200':
          description: Delivery history retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        url:
                          type: string
                        status:
                          type: string
                          enum: [success, failed, pending]
                        attempt:
                          type: integer
                        responseStatus:
                          type: integer
                        error:
                          type: string
                          description: Error message if failed
                        createdAt:
                          type: string
                          format: date-time
                        completedAt:
                          type: string
                          format: date-time
        '404':
          description: Webhook not found

  /api/v1/external/documents/guess-type:
    post:
      tags: [Documents]
      summary: Guess document type
      description: |
        Intelligently guess the type of a document based on its filename and optionally its content.
        Uses AI-powered detection to analyze filename patterns and file content for accurate type identification.

        **File size limit**: 50MB when uploading file content

        **Supported file types for content analysis**:
        - Images (JPEG, PNG, etc.)
        - PDFs
        - Spreadsheets (Excel, CSV)
        - Word documents (DOCX)
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                fileName:
                  type: string
                  description: The name of the file to analyze
                  example: Packing_List_ABC.xlsx
                file:
                  type: string
                  format: binary
                  description: Optional file content for more accurate type detection
              required: [fileName]
      responses:
        '200':
          description: Document type guessed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      documentType:
                        type: string
                        description: The detected document type
                        enum:
                          - CommercialInvoice
                          - PackingList
                          - BillOfLading
                          - AirWaybill
                          - CertificateOfOrigin
                          - DangerousGoodsDeclaration
                          - InsuranceCertificate
                          - SafetyDataSheet
                          - BookingConfirmation
                          - APInvoice
                          - ARInvoice
                          - PaymentConfirmation
                          - ISFFiling
                          - Other
                        example: CommercialInvoice
                      confidence:
                        type: string
                        description: Confidence level of the detection
                        enum: [high, low]
                        example: high
                      fileName:
                        type: string
                        description: The analyzed filename
                        example: Commercial_Invoice_123.pdf
                      fileAnalyzed:
                        type: boolean
                        description: Whether file content was analyzed (in addition to filename)
                        example: false
              example:
                success: true
                data:
                  documentType: CommercialInvoice
                  confidence: high
                  fileName: Commercial_Invoice_123.pdf
                  fileAnalyzed: false
        '400':
          description: Bad request - missing required parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error:
                  code: BAD_REQUEST
                  message: fileName is required
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Insufficient permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error:
                  code: FORBIDDEN
                  message: 'Insufficient scope: documents:read required'

  # Sources Endpoints
  /api/v1/external/sources/upload:
    post:
      tags: [Sources]
      summary: Get presigned URL for source upload
      description: |
        Creates a new source record and returns a presigned URL for uploading a rate sheet file.

        **Workflow**:
        1. Call this endpoint with file metadata
        2. Upload the file directly to the returned presigned URL using PUT
        3. Call POST /sources/{sourceId}/process to start processing

        **Supported file types**: Excel (.xlsx, .xls), CSV, PDF

        **File size limit**: 50MB
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSourceUploadRequest'
            example:
              filename: carrier_rates_2024.xlsx
              contentType: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
              sourceType: contract
              carrierId: '123456789'
              effectiveDate: '2024-01-01'
              expiryDate: '2024-12-31'
              description: Q1 2024 contract rates from carrier
      responses:
        '201':
          description: Source created successfully with upload URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/CreateSourceUploadResponse'
              example:
                success: true
                data:
                  sourceId: '1234567890123456789'
                  uploadUrl: 'https://s3.amazonaws.com/bucket/path?signature=...'
                  expiresAt: '2024-01-15T12:30:00.000Z'
        '400':
          description: Bad request - invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Insufficient permissions - requires sources:write scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/sources/{sourceId}/process:
    post:
      tags: [Sources]
      summary: Start processing an uploaded source
      description: |
        Initiates processing of a source file after it has been uploaded to the presigned URL.

        The source will be queued for processing and its status will transition through:
        - `pending` → `processing` → `complete` or `failed`

        Use GET /sources/{sourceId} to monitor processing status.
        Configure webhooks to receive notifications when processing completes.
      security:
        - bearerAuth: []
      parameters:
        - name: sourceId
          in: path
          required: true
          schema:
            type: string
          description: The source ID returned from the upload endpoint
          example: '1234567890123456789'
      responses:
        '200':
          description: Processing started successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/ProcessSourceResponse'
              example:
                success: true
                data:
                  sourceId: '1234567890123456789'
                  status: pending
                  message: 'Source queued for processing'
        '400':
          description: Source not ready for processing (not uploaded yet)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Source not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/sources:
    get:
      tags: [Sources]
      summary: List all sources
      description: |
        Returns a paginated list of all rate sheet sources for your organization.

        Sources can be filtered by status and sorted by various fields.
      security:
        - bearerAuth: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [awaiting_upload, pending, processing, complete, failed]
          description: Filter by processing status
        - name: carrierId
          in: query
          schema:
            type: string
          description: Filter by carrier ID
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 100
          description: Number of results to return
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
          description: Number of results to skip
      responses:
        '200':
          description: Sources retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      sources:
                        type: array
                        items:
                          $ref: '#/components/schemas/ExternalSourceSummary'
                      total:
                        type: integer
                        description: Total number of sources matching the filter
              example:
                success: true
                data:
                  sources:
                    - id: '1234567890123456789'
                      filename: carrier_rates_2024.xlsx
                      status: complete
                      sourceType: contract
                      carrierId: '123456789'
                      carrierName: 'Maersk'
                      rateCount: 1250
                      effectiveDate: '2024-01-01'
                      expiryDate: '2024-12-31'
                      createdAt: '2024-01-15T10:00:00.000Z'
                      processedAt: '2024-01-15T10:05:00.000Z'
                  total: 1
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/sources/{sourceId}:
    get:
      tags: [Sources]
      summary: Get source details
      description: |
        Returns detailed information about a specific source, including processing status and any errors.
      security:
        - bearerAuth: []
      parameters:
        - name: sourceId
          in: path
          required: true
          schema:
            type: string
          description: The source ID
          example: '1234567890123456789'
      responses:
        '200':
          description: Source details retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/ExternalSourceDetail'
              example:
                success: true
                data:
                  id: '1234567890123456789'
                  filename: carrier_rates_2024.xlsx
                  status: complete
                  sourceType: contract
                  carrierId: '123456789'
                  carrierName: 'Maersk'
                  rateCount: 1250
                  effectiveDate: '2024-01-01'
                  expiryDate: '2024-12-31'
                  description: 'Q1 2024 contract rates'
                  createdAt: '2024-01-15T10:00:00.000Z'
                  processedAt: '2024-01-15T10:05:00.000Z'
                  error: null
        '404':
          description: Source not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  # Query Bank Endpoints
  /api/v1/external/query-bank/sources:
    get:
      tags: [Query Bank]
      summary: List Query Bank sources
      description: |
        Returns all sources currently linked to your Query Bank.

        The Query Bank is a collection of rate sources used when querying for rates.
        Only sources with status "complete" can be added to the Query Bank.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Query Bank sources retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      sources:
                        type: array
                        items:
                          $ref: '#/components/schemas/ExternalQueryBankSource'
              example:
                success: true
                data:
                  sources:
                    - id: '9876543210987654321'
                      sourceId: '1234567890123456789'
                      filename: carrier_rates_2024.xlsx
                      carrierId: '123456789'
                      carrierName: 'Maersk'
                      effectiveDate: '2024-01-01'
                      expiryDate: '2024-12-31'
                      linkedAt: '2024-01-15T11:00:00.000Z'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

    post:
      tags: [Query Bank]
      summary: Add source to Query Bank
      description: |
        Links a processed source to your Query Bank, making its rates available for querying.

        **Requirements**:
        - Source must exist and belong to your organization
        - Source must have status "complete"
        - Source cannot already be in the Query Bank
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddSourceToQueryBankRequest'
            example:
              sourceId: '1234567890123456789'
      responses:
        '201':
          description: Source added to Query Bank successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/AddSourceToQueryBankResponse'
              example:
                success: true
                data:
                  sourceId: '1234567890123456789'
                  linkedAt: '2024-01-15T11:00:00.000Z'
        '400':
          description: Source not eligible (not complete, already in Query Bank)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Source not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/query-bank/sources/{sourceId}:
    delete:
      tags: [Query Bank]
      summary: Remove source from Query Bank
      description: |
        Unlinks a source from your Query Bank. The source will no longer be included in rate queries.

        This does not delete the source itself - it only removes it from the Query Bank.
      security:
        - bearerAuth: []
      parameters:
        - name: sourceId
          in: path
          required: true
          schema:
            type: string
          description: The source ID to remove from Query Bank
          example: '1234567890123456789'
      responses:
        '200':
          description: Source removed from Query Bank successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/RemoveSourceFromQueryBankResponse'
              example:
                success: true
                data:
                  sourceId: '1234567890123456789'
                  unlinkedAt: '2024-01-15T12:00:00.000Z'
        '404':
          description: Source not found in Query Bank
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  # Rates Query Endpoint
  /api/v1/external/rates/query:
    post:
      tags: [Rates]
      summary: Query freight rates
      description: |
        Query freight rates from your Query Bank with flexible location and container filtering.

        **Location Filtering**:
        - Use `origins` and `destinations` for door-to-door moves (inland points)
        - Use `loadingPorts` and `dischargePorts` for port-to-port moves
        - Combine both for door-to-port or port-to-door queries
        - Region codes like USWC, USEC are supported for broader searches

        **Rate Structure**:
        Each rate includes:
        - Route information with all legs (pre-carriage, main, on-carriage)
        - Container rates with per-leg breakdown
        - Applicable surcharges with container-specific amounts

        **Pagination**:
        Results are paginated with a default limit of 100 rates per request.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QueryBankRateQueryRequest'
            examples:
              port_to_port:
                summary: Port-to-port query
                value:
                  loadingPorts: ['CNSHA', 'CNNBO']
                  dischargePorts: ['USLAX', 'USLGB']
                  containerType: dry
                  containerSizes: ['40ft', '40ft_hc']
                  effectiveDate: '2024-01-15'
                  limit: 50
              door_to_door:
                summary: Door-to-door with regions
                value:
                  origins: ['USWC']
                  destinations: ['CNSHA', 'CNNBO']
                  containerType: reefer
                  carrierCodes: ['MAEU', 'MSCU']
              simple:
                summary: Simple query
                value:
                  loadingPorts: ['CNSHA']
                  dischargePorts: ['USLAX']
      responses:
        '200':
          description: Rates retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/QueryBankRateQueryResponse'
              example:
                success: true
                data:
                  rates:
                    - id: 'rate_abc123'
                      portOfLoading:
                        code: 'CNSHA'
                        name: 'Shanghai'
                        type: 'port'
                      portOfDischarge:
                        code: 'USLAX'
                        name: 'Los Angeles'
                        type: 'port'
                      carrierCode: 'MAEU'
                      carrierName: 'Maersk'
                      effectiveDate: '2024-01-01'
                      expiryDate: '2024-03-31'
                      transitTime: 14
                      legs:
                        - type: 'main'
                          origin:
                            code: 'CNSHA'
                            name: 'Shanghai'
                            type: 'port'
                          destination:
                            code: 'USLAX'
                            name: 'Los Angeles'
                            type: 'port'
                          mode: 'ocean'
                      containerRates:
                        - containerType: 'dry'
                          containerSize: '40ft'
                          amount: 2500
                          currency: 'USD'
                        - containerType: 'dry'
                          containerSize: '40ft_hc'
                          amount: 2700
                          currency: 'USD'
                      surcharges:
                        - code: 'BAF'
                          name: 'Bunker Adjustment Factor'
                          applicability: 'subject_to'
                          basis: 'per_container'
                          containerRates:
                            - containerSize: '40ft'
                              amount: 150
                              currency: 'USD'
                            - containerSize: '40ft_hc'
                              amount: 150
                              currency: 'USD'
                      sourceId: '1234567890123456789'
                      sourceName: 'carrier_rates_2024.xlsx'
                  metadata:
                    totalRates: 1
                    sourcesSearched: 5
                    queryTimeMs: 45
        '400':
          description: Bad request - invalid query parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Insufficient permissions - requires rates:read scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  # Tariffs Endpoints
  /api/v1/external/tariffs/lookup:
    get:
      tags: [Tariffs]
      summary: Look up duty rates by HS code
      description: |
        Look up import duty rates for a specific HS/HTS code.

        **Features**:
        - Supports US, EU, Canada, and Australia tariff schedules
        - Returns MFN rates, preferential rates, and additional duties (Section 301, 232, etc.)
        - Calculates landed cost including customs fees
        - Supports FTA program selection
        - Customer-specific tariff overrides are automatically applied

        **HS Code Format**:
        - US: 10-digit HTS code (e.g., "6209205000" or "6209.20.50.00")
        - Other countries: 4-12 digit HS code

        **Additional Duties**:
        For products subject to Section 232 tariffs (steel/aluminum derivatives),
        you may need to provide component percentages via `componentValues`.
      security:
        - bearerAuth: []
      parameters:
        - name: hsCode
          in: query
          required: true
          schema:
            type: string
          description: HS/HTS code to look up (e.g., "6209205000" or "6209.20.50.00")
          example: '8471609050'
        - name: originCountry
          in: query
          required: true
          schema:
            type: string
            pattern: '^[A-Z]{2}$'
          description: Origin country code (2-letter ISO)
          example: 'CN'
        - name: destinationCountry
          in: query
          required: true
          schema:
            type: string
            pattern: '^[A-Z]{2}$'
          description: Destination country code (2-letter ISO)
          example: 'US'
        - name: customsValue
          in: query
          required: false
          schema:
            type: number
          description: Customs value for duty calculation
          example: 10000
        - name: currency
          in: query
          required: false
          schema:
            type: string
            default: 'USD'
          description: Currency code for calculations
          example: 'USD'
        - name: entryDate
          in: query
          required: false
          schema:
            type: string
            format: date
          description: Entry date for historical rate lookup (ISO date)
          example: '2024-01-15'
        - name: dateOfLoading
          in: query
          required: false
          schema:
            type: string
            format: date
          description: Date of loading for historical rate lookup (used if entryDate not provided)
        - name: transportMode
          in: query
          required: false
          schema:
            type: string
            enum: [ocean, air, land, rail]
            default: ocean
          description: Transport mode for customs fee calculations
        - name: componentValues
          in: query
          required: false
          schema:
            type: string
          description: |
            JSON object of component values for Section 232 calculations (e.g., '{"aluminum": 30}' for 30% aluminum content)
          example: '{"aluminum": 30}'
        - name: exclusionCodes
          in: query
          required: false
          schema:
            type: string
          description: JSON array of Chapter 99 exclusion codes to apply (e.g., '["9903.01.21"]')
          example: '["9903.01.21"]'
        - name: includeFtaOptions
          in: query
          required: false
          schema:
            type: string
            enum: ['true', 'false']
          description: Whether to include FTA options in the response
        - name: applyFtaProgram
          in: query
          required: false
          schema:
            type: string
          description: FTA program code to apply (e.g., 'US_AU_FTA')
          example: 'US_AU_FTA'
        - name: weightKg
          in: query
          required: false
          schema:
            type: number
          description: Weight in kilograms for per-kg fee calculations
      responses:
        '200':
          description: Tariff lookup successful
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      tariffLine:
                        type: object
                        properties:
                          id:
                            type: string
                          code:
                            type: string
                            example: '8471.60.90.50'
                          description:
                            type: string
                            example: 'Other data processing equipment'
                          fullDescription:
                            type: string
                          hs6:
                            type: string
                            example: '847160'
                          uomPrimary:
                            type: string
                            example: 'NO'
                      applicableRate:
                        type: object
                        properties:
                          regime:
                            type: string
                            example: 'MFN'
                          programCode:
                            type: string
                            example: 'US_MFN'
                          rateType:
                            type: string
                            example: 'AD_VALOREM'
                          adValoremRate:
                            type: number
                            example: 0
                          formulaDescription:
                            type: string
                            example: 'Free'
                      baseRate:
                        type: object
                        description: Base MFN/preferential rate without additional duties
                      mfnRate:
                        type: number
                        description: MFN rate percentage
                        example: 0
                      additionalDuties:
                        type: array
                        description: Additional duties (Section 301, 232, etc.)
                        items:
                          type: object
                          properties:
                            programCode:
                              type: string
                            programName:
                              type: string
                            rate:
                              type: number
                            rateType:
                              type: string
                      calculation:
                        type: object
                        description: Duty calculation breakdown (when customsValue provided)
                        properties:
                          dutyAmount:
                            type: number
                          baseDutyAmount:
                            type: number
                          additionalDutyAmount:
                            type: number
                          currency:
                            type: string
                          effectiveRate:
                            type: number
                      ftaOptions:
                        type: array
                        description: Available FTA programs
                        items:
                          type: object
                          properties:
                            program:
                              type: string
                            programName:
                              type: string
                            rate:
                              type: number
                      customsFees:
                        type: object
                        description: Customs fees breakdown
                        properties:
                          totalFees:
                            type: number
                          fees:
                            type: array
                            items:
                              type: object
                      landedCost:
                        type: object
                        description: Total landed cost breakdown
                        properties:
                          customsValue:
                            type: number
                          totalDuties:
                            type: number
                          totalFees:
                            type: number
                          grandTotal:
                            type: number
              example:
                success: true
                data:
                  tariffLine:
                    id: '12345'
                    code: '8471.60.90.50'
                    description: 'Other data processing equipment'
                    hs6: '847160'
                    uomPrimary: 'NO'
                  applicableRate:
                    regime: 'MFN'
                    programCode: 'US_MFN'
                    rateType: 'AD_VALOREM'
                    adValoremRate: 0
                    formulaDescription: 'Free'
                  mfnRate: 0
                  additionalDuties: []
        '400':
          description: Bad request - invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missing_hs_code:
                  summary: Missing HS code
                  value:
                    success: false
                    error:
                      code: VALIDATION_ERROR
                      message: Query parameter "hsCode" is required
                invalid_hs_code:
                  summary: Invalid HS code format
                  value:
                    success: false
                    error:
                      code: VALIDATION_ERROR
                      message: Invalid HS code format. Expected 4-12 digit code.
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Insufficient permissions - requires tariffs:read scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Tariff line not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error:
                  code: NOT_FOUND
                  message: No tariff line found for HS code 1234567890

  /api/v1/external/tariffs/search:
    get:
      tags: [Tariffs]
      summary: Search tariffs by product description
      description: |
        Search for tariff lines using semantic search on product descriptions.

        This endpoint uses AI-powered semantic search to find relevant HS codes
        based on natural language product descriptions. Results are ranked by
        relevance to the search query.

        **Note**: This endpoint excludes Chapter 99 codes (9903.xx.xx) as they
        are administrative tariff codes, not product classifications.
      security:
        - bearerAuth: []
      parameters:
        - name: q
          in: query
          required: true
          schema:
            type: string
          description: Product description to search for
          example: 'laptop computer'
        - name: country
          in: query
          required: true
          schema:
            type: string
            pattern: '^[A-Z]{2}$'
          description: Destination country code (2-letter ISO)
          example: 'US'
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 10
          description: Maximum number of results (max 50)
        - name: entryDate
          in: query
          required: false
          schema:
            type: string
            format: date
          description: Entry date for historical rate lookup (ISO date)
        - name: dateOfLoading
          in: query
          required: false
          schema:
            type: string
            format: date
          description: Date of loading for historical rate lookup
      responses:
        '200':
          description: Search results retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      results:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                            code:
                              type: string
                              example: '8471.30.01.00'
                            description:
                              type: string
                              example: 'Portable automatic data processing machines'
                            fullDescription:
                              type: string
                            similarity:
                              type: number
                              description: Relevance score (0-1)
                              example: 0.89
              example:
                success: true
                data:
                  results:
                    - id: '12345'
                      code: '8471.30.01.00'
                      description: 'Portable automatic data processing machines, weighing not more than 10 kg'
                      similarity: 0.92
                    - id: '12346'
                      code: '8471.41.01.00'
                      description: 'Other automatic data processing machines comprising in the same housing at least a CPU and an input and output unit'
                      similarity: 0.85
        '400':
          description: Bad request - invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Insufficient permissions - requires tariffs:read scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/tariffs/batch-lookup:
    post:
      tags: [Tariffs]
      summary: Batch look up duty rates for multiple HS codes, origins, destinations, or entry dates
      description: |
        Look up import duty rates for a batch of items in a single request.

        Each item is a self-contained query carrying its own `hsCode`, `originCountry`,
        `destinationCountry`, and (optionally) `entryDate`, so callers can mix and match
        any combination of dimensions in a single request.

        **Behaviour**:
        - Items are processed independently; a failure on one item does not abort the batch.
        - Results are returned in request order and matched to each item by the caller-provided `id`.
        - Each item counts toward OAuth rate limits as one request.

        **Limits**:
        - Maximum 100 items per request.
        - Item `id` values must be unique within a request.

        For per-item parameter semantics (HS code format, country codes, FTA programs,
        component values, exclusion codes, etc.) see `POST /api/v1/external/tariffs/lookup`.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [items]
              properties:
                items:
                  type: array
                  minItems: 1
                  maxItems: 100
                  items:
                    type: object
                    required: [id, hsCode, originCountry, destinationCountry]
                    properties:
                      id:
                        type: string
                        description: Caller-provided identifier echoed back in the response. Must be unique within the request.
                        example: 'q1'
                      hsCode:
                        type: string
                        description: HS/HTS code (e.g., "6209205000" or "6209.20.50.00")
                        example: '8471609050'
                      originCountry:
                        type: string
                        pattern: '^[A-Za-z]{2}$'
                        description: Origin country code (2-letter ISO)
                        example: 'CN'
                      destinationCountry:
                        type: string
                        pattern: '^[A-Za-z]{2}$'
                        description: Destination country code (2-letter ISO)
                        example: 'US'
                      customsValue:
                        type: number
                        example: 10000
                      currency:
                        type: string
                        default: 'USD'
                        example: 'USD'
                      entryDate:
                        type: string
                        format: date
                        description: Entry date for historical rate lookup (ISO date)
                        example: '2024-01-15'
                      dateOfLoading:
                        type: string
                        format: date
                      transportMode:
                        type: string
                        enum: [ocean, air, land, rail]
                        default: ocean
                      componentValues:
                        type: object
                        additionalProperties:
                          oneOf:
                            - type: number
                            - type: boolean
                        description: 'Component values for Section 232 calculations (e.g., {"aluminum": 30} for 30% aluminum content)'
                        example:
                          aluminum: 30
                      exclusionCodes:
                        type: array
                        items:
                          type: string
                        description: Chapter 99 exclusion codes to apply
                        example: ['9903.01.21']
                      includeFtaOptions:
                        type: boolean
                      applyFtaProgram:
                        type: string
                        example: 'US_AU_FTA'
                      weightKg:
                        type: number
            examples:
              mixed_origins:
                summary: Same HS code across multiple origins
                value:
                  items:
                    - id: 'cn'
                      hsCode: '8471609050'
                      originCountry: 'CN'
                      destinationCountry: 'US'
                      customsValue: 10000
                    - id: 'mx'
                      hsCode: '8471609050'
                      originCountry: 'MX'
                      destinationCountry: 'US'
                      customsValue: 10000
                    - id: 'de'
                      hsCode: '8471609050'
                      originCountry: 'DE'
                      destinationCountry: 'US'
                      customsValue: 10000
              mixed_dates:
                summary: Same HS code/origin across historical entry dates
                value:
                  items:
                    - id: '2024'
                      hsCode: '6209205000'
                      originCountry: 'CN'
                      destinationCountry: 'US'
                      entryDate: '2024-06-01'
                    - id: '2025'
                      hsCode: '6209205000'
                      originCountry: 'CN'
                      destinationCountry: 'US'
                      entryDate: '2025-06-01'
              mixed_codes:
                summary: Multiple HS codes from one origin
                value:
                  items:
                    - id: 'a'
                      hsCode: '6209205000'
                      originCountry: 'CN'
                      destinationCountry: 'US'
                    - id: 'b'
                      hsCode: '8471609050'
                      originCountry: 'CN'
                      destinationCountry: 'US'
      responses:
        '200':
          description: Batch lookup processed. Per-item status is reported via the `success` flag on each result.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      results:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              description: Caller-provided id from the matching request item
                            success:
                              type: boolean
                            data:
                              type: object
                              description: 'Tariff lookup result (same shape as `GET /api/v1/external/tariffs/lookup`). Present when success is true.'
                            error:
                              type: string
                              description: 'Error message. Present when success is false.'
                            status:
                              type: integer
                              description: 'HTTP-style status code for the per-item failure (e.g., 400, 404, 500). Present when success is false.'
              example:
                success: true
                data:
                  results:
                    - id: 'cn'
                      success: true
                      data:
                        tariffLine:
                          code: '8471.60.90.50'
                          description: 'Other data processing equipment'
                        applicableRate:
                          regime: 'MFN'
                          adValoremRate: 0
                        additionalDuties: []
                    - id: 'mx'
                      success: false
                      error: 'No tariff line found for HS code 8471609050'
                      status: 404
        '400':
          description: Bad request - request body invalid, items array empty, too many items, or duplicate ids
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                empty_items:
                  summary: Empty items array
                  value:
                    success: false
                    error:
                      code: VALIDATION_ERROR
                      message: '"items" array must not be empty'
                too_many_items:
                  summary: Over 100 items
                  value:
                    success: false
                    error:
                      code: VALIDATION_ERROR
                      message: Maximum 100 items allowed per batch
                duplicate_id:
                  summary: Duplicate item id
                  value:
                    success: false
                    error:
                      code: VALIDATION_ERROR
                      message: Duplicate item id "q1" in request
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Insufficient permissions - requires tariffs:read scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/tms/organizations:
    get:
      tags: [TMS Organizations]
      summary: List TMS organizations
      description: Retrieve a paginated list of TMS organizations for your customer.
      security:
        - bearerAuth: []
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
          description: Page number
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          description: Items per page
        - name: source
          in: query
          schema:
            type: string
            enum: [cw, gf, lw, ns]
          description: Filter by TMS source
        - name: type
          in: query
          schema:
            type: string
          description: Filter by organization type (e.g. consignee, shipper, carrier)
        - name: active
          in: query
          schema:
            type: boolean
          description: Filter by active status
        - name: search
          in: query
          schema:
            type: string
          description: Search by name, code, city, state, country, or email
      responses:
        '200':
          description: Organizations retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/TmsOrganization'
                  pagination:
                    $ref: '#/components/schemas/PaginatedResponse/properties/pagination'
        '401':
          $ref: '#/components/schemas/UnauthorizedError'
        '403':
          description: Insufficient permissions - requires tms-organizations:read scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags: [TMS Organizations]
      summary: Create a TMS organization
      description: Create a new TMS organization. The combination of (source, code) must be unique.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTmsOrganizationRequest'
      responses:
        '201':
          description: Organization created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/TmsOrganization'
        '400':
          description: Invalid request or duplicate organization code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/schemas/UnauthorizedError'
        '403':
          description: Insufficient permissions - requires tms-organizations:write scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/tms/organizations/{organizationId}:
    get:
      tags: [TMS Organizations]
      summary: Get a TMS organization
      description: Retrieve a specific TMS organization by ID.
      security:
        - bearerAuth: []
      parameters:
        - name: organizationId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Organization retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/TmsOrganization'
        '404':
          $ref: '#/components/schemas/NotFoundError'
        '401':
          $ref: '#/components/schemas/UnauthorizedError'
        '403':
          description: Insufficient permissions - requires tms-organizations:read scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    put:
      tags: [TMS Organizations]
      summary: Update a TMS organization
      description: Update an existing TMS organization. Only provided fields are updated.
      security:
        - bearerAuth: []
      parameters:
        - name: organizationId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTmsOrganizationRequest'
      responses:
        '200':
          description: Organization updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/TmsOrganization'
        '400':
          description: Invalid request or duplicate organization code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          $ref: '#/components/schemas/NotFoundError'
        '401':
          $ref: '#/components/schemas/UnauthorizedError'
        '403':
          description: Insufficient permissions - requires tms-organizations:write scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags: [TMS Organizations]
      summary: Delete a TMS organization
      description: Delete a TMS organization by ID.
      security:
        - bearerAuth: []
      parameters:
        - name: organizationId
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Organization deleted successfully
        '404':
          $ref: '#/components/schemas/NotFoundError'
        '401':
          $ref: '#/components/schemas/UnauthorizedError'
        '403':
          description: Insufficient permissions - requires tms-organizations:delete scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/tms/organizations/import:
    post:
      tags: [TMS Organizations]
      summary: Import organizations from JSONL file
      description: |
        Upload a JSONL file to bulk import TMS organizations. Each line must be a JSON object
        with at least `code` and `name` fields. Processing is asynchronous — returns a job ID
        that can be polled for status. A `bulk_upload.completed` or `bulk_upload.failed` webhook
        is fired when the job finishes, including row-level stats (totalRows, successfulRows, failedRows).
      security:
        - bearerAuth: []
      parameters:
        - name: source
          in: query
          required: true
          schema:
            type: string
            enum: [cw, gf, lw, ns]
          description: TMS source for the imported organizations
        - name: mode
          in: query
          schema:
            type: string
            enum: [replace, merge, skip]
            default: merge
          description: |
            Import mode: `merge` (upsert), `skip` (skip existing), `replace` (delete all then insert)
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: JSONL file (max 10MB)
              required: [file]
      responses:
        '200':
          description: Import job queued successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/TmsOrganizationImportResponse'
        '400':
          description: Invalid source, mode, or file
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/schemas/UnauthorizedError'
        '403':
          description: Insufficient permissions - requires tms-organizations:write scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/external/tms/organizations/import/{jobId}:
    get:
      tags: [TMS Organizations]
      summary: Get import job status
      description: Check the status and progress of an organization import job.
      security:
        - bearerAuth: []
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Import job status retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/TmsOrganizationImportStatus'
        '404':
          $ref: '#/components/schemas/NotFoundError'
        '401':
          $ref: '#/components/schemas/UnauthorizedError'
        '403':
          description: Insufficient permissions - requires tms-organizations:read scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
