openapi: 3.1.0
info:
  title: Adaptria Public API
  version: 1.0.0
  description: |
    # Introduction

    The Adaptria Public API is a secure, programmatic interface that allows your organization to integrate Adaptria's services directly into your existing business systems and workflows.

    Instead of manually performing actions through a web portal, your systems can automatically create, manage, and track tasks through API calls.

    ---

    ### Rate Limiting

    The Adaptria Public API implements rate limiting to ensure fair usage and protect system resources. We use a **two-tier rate limiting system**:

    - **IP-Based Limit**: 100 requests per minute per IP address (applies to all requests before authentication)
    - **Credential-Based Limits**:
      - **Read operations** (GET): 60 requests per minute per API credential
      - **Write operations** (POST/PATCH/PUT/DELETE): 30 requests per minute per API credential
      - **Token generation**: 10 requests per 15 minutes per IP address

    When rate limits are exceeded, you'll receive a `429 Too Many Requests` response with `RateLimit-Remaining`, `RateLimit-Reset`, and `Retry-After` headers. Implement exponential backoff and monitor these headers to handle rate limits gracefully in your integration.

    ---

    **Ready to start your journey?** → Authenticate to begin integrating with the Adaptria Public API. See the [Authentication](#tag/%F0%9F%94%90%20Authentication) section to obtain your access token.
  contact:
    name: Adaptria API Support
    email: api-support@adaptria.com
    url: https://docs.adaptria.com/api
  license:
    name: Proprietary
servers:
  - url: https://staging.api.locate.studiographene.co.uk
    description: Staging Environment
tags:
  - name: 🚀 Getting Started
    description: |
      ## Getting Started

      Follow these steps to begin using the Adaptria Public API.

      ### Step 1: Obtain API Credentials

      Before using the Public API, you need to generate API credentials. There are two ways to do this:

      #### Option A: Client User Self-Service

      1. Log in to the [Adaptria Portal](https://test.locate.studiographene.co.uk) as a **Client User**
      2. Navigate to **Settings → API Credentials**
      3. Click **Generate New Credentials**
      4. Securely store your `clientId` and `clientSecret`

      #### Option B: Admin Creates for Client User

      Contact your Adaptria administrator to generate API credentials for your account. Administrators can create credentials through the Admin → Client User Management section in the Adaptria Portal.

      ### Step 2: Generate Access Token

      Exchange your credentials for an access token:

      ```bash
      curl -X POST https://api.adaptria.com/api/credentials/token \
        -H "Content-Type: application/json" \
        -d '{
          "clientId": "your_client_id",
          "clientSecret": "your_client_secret"
        }'
      ```

      ### Step 3: Make API Requests

      Include the access token in all subsequent requests:

      ```bash
      curl -X GET https://api.adaptria.com/api/public/v1/rfqs \
        -H "Authorisation: Bearer your_access_token"
      ```

      ### Common Workflows

      #### Creating an RFQ (Request for Quote)

      ```
      ┌─────────────────────────────────────────────────────────────┐
      │  1. Get Available Services                                  │
      │     GET /api/public/v1/client/services                      │
      ├─────────────────────────────────────────────────────────────┤
      │  2. Get Available Languages                                 │
      │     GET /api/public/v1/client/languages                     │
      ├─────────────────────────────────────────────────────────────┤
      │  3. Create RFQ (Draft)                                      │
      │     POST /api/public/v1/rfqs                                │
      ├─────────────────────────────────────────────────────────────┤
      │  4. Update RFQ (Add services, languages)                    │
      │     PATCH /api/public/v1/rfqs/:id                           │
      ├─────────────────────────────────────────────────────────────┤
      │  5. Submit RFQ for Quote                                    │
      │     POST /api/public/v1/rfqs/:id/submit                     │
      └─────────────────────────────────────────────────────────────┘
      ```

      ### Rate Limits

      | Endpoint Type | Rate Limit |
      |---------------|------------|
      | Token Generation | 10 requests/minute |
      | Read Operations | 100 requests/minute |
      | Write Operations | 30 requests/minute |
  - name: 🔐 Authentication
    description: |
      ## Token-Based Authentication

      The Public API uses JWT (JSON Web Token) authentication. Start here to obtain an access token before calling other endpoints.

      ### Authentication Flow

      ```
      Your App          Adaptria API
         │                    │
         │  POST /api/credentials/token
         │  {clientId, clientSecret}
         │ ──────────────────▶│
         │                    │
         │  {accessToken, expiresIn}
         │ ◀──────────────────│
         │                    │
         │  GET /api/public/v1/rfqs  │
         │  Authorisation: Bearer <token>
         │ ──────────────────▶│
         │                    │
         │  {data: [...]}     │
         │ ◀──────────────────│
      ```

      ### Using Authentication

      All Public API endpoints require authentication using a Bearer token.

      **Authorisation Header Required**

      Include the token in the `Authorisation` header (note the British English spelling with 's'):

      | Header | Value |
      |--------|-------|
      | `Authorisation` | `Bearer <your_access_token>` |

      **Important**: Use `Authorisation` (with 's'), not `Authorisation` (with 'z').

      **Token Lifetime**: 1 hour (3600 seconds)

      **Auto-Refresh**: Implement automatic token refresh in your integration to maintain uninterrupted access.
  - name: 🏢 Companies
    description: |
      ## Companies

      Companies represent the client organizations that your API credentials have access to.

      ### When to Use This

      Use this endpoint to:
      - See which companies you can create RFQs for
      - Get company IDs needed for RFQ creation
      - Verify your company associations

      ### Using Company IDs

      When creating an RFQ, you must specify the `companyId` field. This identifies which company the RFQ belongs to. You can only create RFQs for companies that your API credentials have access to.

      Get available companies from the endpoints below.
  - name: 📤 File Upload
    description: |
      ## File Upload

      Upload files to attach to your RFQ services. Files must be uploaded **before** creating your RFQ.

      ### Complete File Upload Workflow

      ```
      Step 1: Request Presigned URL
      POST /api/files/upload/presigned-url
      {
        "filename": "document.pdf",
        "sizeBytes": 1024000,
        "mimeType": "application/pdf",
        "entityType": "rfq"
      }
      → Response: { fileId: "67eb9e88-...", presignedUrl: "...", ... }

      Step 2: Upload File to Presigned URL
      PUT <presignedUrl>
      Content-Type: <uploadInstructions.contentType>
      [File binary data]
      → Response: 200 OK

      Step 3: Use fileId in RFQ
      POST /api/public/v1/rfqs
      {
        "services": [{
          "serviceId": "...",
          "type": "NON_LINGUISTICS",
          "targetMarketIds": ["..."],
          "assetFileIds": ["67eb9e88-7962-41a1-9695-f2051a968728"]
        }]
      }
      ```

      ### When to Upload Files

      Upload files **before** creating your RFQ if you want to attach them to services. The `fileId` you receive can be used in the `assetFileIds` array when creating RFQs.

      ### File Requirements

      - **Max Size**: 100MB (104,857,600 bytes)
      - **Allowed Types**: PDF, Word, Excel, PowerPoint, Images, Videos, Audio, ZIP
      - **Entity Type**: Use `"rfq"` for RFQ-related files

      ### Using File IDs in RFQs

      Once you have a `fileId` from the upload endpoint, include it in your service's `assetFileIds` array:

      ```json
      {
        "serviceId": "05b68c28-5659-4c57-809b-a55bae97f967",
        "type": "NON_LINGUISTICS",
        "targetMarketIds": ["976fba00-3e0d-40fe-b766-6017cb44c3a7"],
        "assetFileIds": ["67eb9e88-7962-41a1-9695-f2051a968728"]
      }
      ```

      **Note**: The `fileId` from the upload endpoint goes directly into the `assetFileIds` array. The system automatically transforms it to the internal `assets` format.

      Get file upload endpoint details below.
  - name: 📋 Services
    description: |
      ## Services

      Services define the types of work that can be requested in an RFQ.

      ### Service Types

      | Type | Description | Examples |
      |------|-------------|----------|
      | **LINGUISTICS** | Language-based services | Translation, Transcription, Localization |
      | **NON_LINGUISTICS** | Supporting services | Desktop Publishing (DTP), QA, Engineering, Video Adaptation |

      ### Using Services in RFQs

      When creating an RFQ, you'll need to include at least one service. The service type determines what additional fields are required:
      - **LINGUISTICS** services require source and target languages
      - **NON_LINGUISTICS** services require target markets

      Get available services from the endpoints below.
  - name: 🌍 Languages
    description: |
      ## Languages

      Languages are used for LINGUISTICS services (translation, localization).

      ### Language Categories

      Languages are organized into:
      - **Suggested Languages**: Based on your company's previous projects and preferences
      - **Other Languages**: All other supported languages

      ### Using Languages in RFQs

      When creating an RFQ with LINGUISTICS services, you'll need:
      - **sourceLanguageId**: The language of your source content
      - **targetLanguageIds**: Array of languages to translate into

      ### Language Codes

      We use ISO 639-1 (2-character) codes where available, falling back to ISO 639-3 (3-character) for languages without a 2-character code.

      Get available languages from the endpoints below.
  - name: 🎯 Target Markets
    description: |
      ## Target Markets

      Target markets represent geographical regions or countries for localization projects.

      ### What are Target Markets?

      Target markets help Adaptria:
      - Assign appropriate regional specialists
      - Apply region-specific style guides
      - Consider cultural nuances in translation

      ### Using Target Markets in RFQs

      When creating an RFQ with NON_LINGUISTICS services (video adaptation, creative services), you'll need to specify target market IDs.

      Get available target markets from the endpoints below.
  - name: 📄 RFQs
    description: |
      ## Request for Quote (RFQ) Management

      RFQs are the starting point for all translation and localization projects.

      ### RFQ Lifecycle

      ```
      PENDING ──▶ CHANGED_TO_QUOTE ──▶ CHANGED_TO_PROJECT
                    │
                    └──▶ Project begins!
      ```

      ### Status Descriptions

      | Status | Description | Can Edit? |
      |--------|-------------|-----------|
      | PENDING | Submitted, awaiting quote | ❌ No |
      | CHANGED_TO_QUOTE | Quote provided | ❌ No |
      | CHANGED_TO_PROJECT | Accepted, project started | ❌ No |
security:
  - bearerAuth: []
externalDocs:
  description: Full API Documentation
  url: https://docs.adaptria.com/api
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |-
        ## Authorisation Header Required

        JWT access token obtained from `POST /api/credentials/token`

        **How to authenticate**:
        1. Generate API credentials from the Adaptria Portal
        2. Exchange credentials for a token: `POST /api/credentials/token`
        3. Include the token in all requests using the `Authorisation` header: `Authorisation: Bearer <token>`

        **Important**: Use `Authorisation` (British English spelling with 's'), not `Authorisation` (American spelling with 'z').

        **Token Expiration**: 1 hour (3600 seconds)
  schemas:
    SuccessResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
          description: Indicates the request was successful
          example: true
        message:
          type: string
          description: Optional success message
          example: Operation completed successfully
      required:
        - success
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - false
          description: Indicates the request failed
          example: false
        error:
          type: object
          properties:
            message:
              type: string
              description: Human-readable error message
              example: The requested resource was not found
            code:
              type: string
              description: Machine-readable error code for programmatic handling
              example: NOT_FOUND
            details:
              type: object
              additionalProperties: {}
              description: Additional error details for debugging
          required:
            - message
            - code
      required:
        - success
        - error
    Pagination:
      type: object
      properties:
        page:
          type: integer
          exclusiveMinimum: 0
          description: Current page number (1-indexed)
          example: 1
        limit:
          type: integer
          exclusiveMinimum: 0
          maximum: 100
          description: Number of items per page (max 100)
          example: 20
        total:
          type: integer
          minimum: 0
          description: Total number of items across all pages
          example: 150
        totalPages:
          type: integer
          minimum: 0
          description: Total number of pages
          example: 8
        hasNextPage:
          type: boolean
          description: Whether there are more pages after the current page
          example: true
        hasPrevPage:
          type: boolean
          description: Whether there are pages before the current page
          example: false
      required:
        - page
        - limit
        - total
        - totalPages
        - hasNextPage
        - hasPrevPage
    GenerateTokenRequest:
      type: object
      properties:
        clientId:
          type: string
          minLength: 1
          description: |-
            Your unique client identifier provided when API credentials were generated.

            This ID uniquely identifies your application and is used together with the client secret to authenticate API requests.

            **Format**: `adaptria_<timestamp>_<random>`
          example: adaptria_1733836800000_abc123def456
        clientSecret:
          type: string
          minLength: 1
          description: |-
            Your confidential client secret provided when API credentials were generated.

            ⚠️ **Security Warning**: Keep this secret secure! Never expose it in client-side code, public repositories, or logs.

            **Best Practices**:
            - Store in environment variables or secure vaults
            - Rotate periodically
            - Never commit to version control
          example: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
      required:
        - clientId
        - clientSecret
    GenerateTokenResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          properties:
            accessToken:
              type: string
              description: |-
                JWT access token for authenticating Public API requests.

                **Usage**: Include this token in the `Authorisation` header of all API requests:
                ```
                Authorisation: Bearer <accessToken>
                ```

                **Token Contents**: The token contains encoded information about your identity and permissions.
              example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVkZW50aWFsSWQiOiIxMjM0NTY3OC0xMjM0LTEyMzQtMTIzNC0xMjM0NTY3ODkwYWIiLCJjbGllbnRJZCI6ImxvY2FyaWFfMTczMzgzNjgwMDAwMF9hYmMxMjNkZWY0NTYiLCJ1c2VySWQiOiI5ODc2NTQzMi05ODc2LTk4NzYtOTg3Ni05ODc2NTQzMjEwZmUiLCJlbWFpbCI6ImFwaUBleGFtcGxlLmNvbSIsInJvbGVDb2RlIjoiY2xpZW50IiwiY29tcGFueUlkIjoiYWJjZGVmMTItYWJjZC1hYmNkLWFiY2QtYWJjZGVmMTIzNDU2IiwiaWF0IjoxNzMzODQwMDAwLCJleHAiOjE3MzM4NDM2MDB9.signature
            tokenType:
              type: string
              enum:
                - Bearer
              description: Token type (always "Bearer" for OAuth 2.0 compliance)
              example: Bearer
            expiresIn:
              type: integer
              exclusiveMinimum: 0
              description: |-
                Token expiration time in seconds from generation.

                **Default**: 3600 seconds (1 hour)

                **Important**: Request a new token before expiration to maintain uninterrupted access.
              example: 3600
            expiresAt:
              type: string
              format: date-time
              description: Exact UTC timestamp when the token will expire (ISO 8601 format)
              example: '2025-12-11T11:00:00.000Z'
          required:
            - accessToken
            - tokenType
            - expiresIn
            - expiresAt
        message:
          type: string
      required:
        - success
        - data
    InvalidCredentialsError:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - false
        error:
          type: object
          properties:
            message:
              type: string
              description: Human-readable error description
              example: Invalid client credentials
            code:
              type: string
              enum:
                - INVALID_CREDENTIALS
                - CREDENTIAL_REVOKED
                - CREDENTIAL_EXPIRED
              description: |-
                Error code indicating the specific authentication failure:

                - `INVALID_CREDENTIALS`: The clientId or clientSecret is incorrect
                - `CREDENTIAL_REVOKED`: The API credential has been revoked
                - `CREDENTIAL_EXPIRED`: The API credential has expired
              example: INVALID_CREDENTIALS
          required:
            - message
            - code
      required:
        - success
        - error
    PublicService:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the service
          example: 34acfed6-ae10-46e3-8896-9afe78c2ecdc
        name:
          type: string
          description: Human-readable service name
          example: Translation
        type:
          type: string
          enum:
            - LINGUISTICS
            - NON_LINGUISTICS
          description: |-
            Service category type:
            - `LINGUISTICS`: Language-based services (translation, transcription, etc.)
            - `NON_LINGUISTICS`: Non-language services (desktop publishing, QA, etc.)
          example: LINGUISTICS
      required:
        - id
        - name
        - type
    GetServicesResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: array
          items:
            $ref: '#/components/schemas/PublicService'
          description: List of services available to your company
      required:
        - success
        - data
    PublicLanguage:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: '**Language ID - Use this in RFQ creation!** Unique identifier for the language.'
          example: 3ebf026a-578b-46cb-b4a5-de4b54bab575
        name:
          type: string
          description: English name of the language
          example: Danish
        iso639Code:
          type: string
          description: ISO 639 language code (can be 2 or 3 characters, or numeric)
          example: '322'
        isActive:
          type: boolean
          description: Whether the language is currently active
          example: true
        createdAt:
          type: string
          description: ISO timestamp when the language was created
          example: '2025-09-06T11:01:08.129Z'
        updatedAt:
          type: string
          description: ISO timestamp when the language was last updated
          example: '2025-09-06T11:01:08.129Z'
      required:
        - id
        - name
        - iso639Code
        - isActive
        - createdAt
        - updatedAt
    GetLanguagesResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          properties:
            suggestedLanguages:
              type: array
              items:
                $ref: '#/components/schemas/PublicLanguage'
              description: |-
                Languages commonly used by your company.

                These are prioritized based on your previous projects and company preferences.
            otherLanguages:
              type: array
              items:
                $ref: '#/components/schemas/PublicLanguage'
              description: All other available languages not in the suggested list
          required:
            - suggestedLanguages
            - otherLanguages
          description: Categorized language lists
      required:
        - success
        - data
    PublicTargetMarket:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the target market
          example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
        name:
          type: string
          description: Human-readable name of the target market/region
          example: Germany
        countryCode:
          type: string
          description: ISO 3166-1 alpha-2 country code
          example: DE
      required:
        - id
        - name
        - countryCode
    GetTargetMarketsResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: array
          items:
            $ref: '#/components/schemas/PublicTargetMarket'
          description: List of target markets available to your company
      required:
        - success
        - data
    PublicCompany:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the company
          example: b49b4440-1e59-4270-ba17-c5653fd71546
        name:
          type: string
          description: Company name
          example: Acme Corporation
        shortCode:
          type: string
          description: Short code identifier for the company
          example: ACME
        clientType:
          type: string
          enum:
            - direct_client
            - agency
            - subsidiary
          description: |-
            Type of client:
            - `direct_client`: Direct client company
            - `agency`: Translation agency
            - `subsidiary`: Subsidiary of a parent company
          example: direct_client
      required:
        - id
        - name
    GetCompaniesResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          properties:
            companies:
              type: array
              items:
                $ref: '#/components/schemas/PublicCompany'
              description: List of companies the user has access to
          required:
            - companies
          description: Company data
      required:
        - success
        - data
    LinguisticsService:
      type: object
      properties:
        serviceId:
          type: string
          format: uuid
          description: |-
            Service ID (serviceUUID) from GET /api/public/v1/client/services. Must be a service with type "LINGUISTICS".

            📖 **To get service IDs**: See the [Services](#tag/%F0%9F%93%8B%20Services) section → `GET /api/public/v1/client/services`
          example: 34acfed6-ae10-46e3-8896-9afe78c2ecdc
        type:
          type: string
          enum:
            - LINGUISTICS
          description: Service type - use LINGUISTICS for translation/localization
        sourceLanguageId:
          type: string
          format: uuid
          description: |-
            Source language ID from GET /api/public/v1/client/languages. Maps to combos[].sourceLanguage internally.

            📖 **To get language IDs**: See the [Languages](#tag/%F0%9F%8C%8D%20Languages) section → `GET /api/public/v1/client/languages`
          example: a1b2c3d4-0000-0000-0000-000000000001
        targetLanguageIds:
          type: array
          items:
            type: string
            format: uuid
            description: Universally Unique Identifier (UUID v4)
            example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
          minItems: 1
          description: |-
            Array of target language IDs from GET /api/public/v1/client/languages. Each maps to combos[].targetLanguages[].languageId.

            📖 **To get language IDs**: See the [Languages](#tag/%F0%9F%8C%8D%20Languages) section → `GET /api/public/v1/client/languages`
          example:
            - a1b2c3d4-0000-0000-0000-000000000002
            - a1b2c3d4-0000-0000-0000-000000000003
        assetFileIds:
          type: array
          items:
            type: string
            format: uuid
            description: Universally Unique Identifier (UUID v4)
            example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
          description: |-
            Optional array of pre-uploaded file IDs to attach to this service.

            📤 **To get file IDs**:
            1. Upload files using `POST /api/public/v1/files/upload/presigned-url` (see [File Upload](#tag/%F0%9F%93%A4%20File%20Upload) section)
            2. Get the `fileId` from the response
            3. Include it in this array

            **Important**: Files are uploaded per service. Each service can have its own set of files.

            **Example**: `["67eb9e88-7962-41a1-9695-f2051a968728", "another-file-id"]`

            The system automatically transforms these file IDs into the internal assets format (`assets` array with `type: "FILE"`, `assetType: "SOURCE"`).
      required:
        - serviceId
        - type
        - sourceLanguageId
        - targetLanguageIds
    NonLinguisticsService:
      type: object
      properties:
        serviceId:
          type: string
          format: uuid
          description: |-
            Service ID (serviceUUID) from GET /api/public/v1/client/services. Must be a service with type "NON_LINGUISTICS".

            📖 **To get service IDs**: See the [Services](#tag/%F0%9F%93%8B%20Services) section → `GET /api/public/v1/client/services`
          example: 0cf41489-6424-492a-8cd7-4f7eef02858e
        type:
          type: string
          enum:
            - NON_LINGUISTICS
          description: Service type - use NON_LINGUISTICS for video adaptation, creative services
        targetMarketIds:
          type: array
          items:
            type: string
            format: uuid
            description: Universally Unique Identifier (UUID v4)
            example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
          minItems: 1
          description: |-
            Array of target market IDs from GET /api/public/v1/client/target-markets. Each ID maps to targetMarkets[].targetMarketId internally.

            📖 **To get target market IDs**: See the [Target Markets](#tag/%F0%9F%8E%AF%20Target%20Markets) section → `GET /api/public/v1/client/target-markets`
          example:
            - 03b91040-5931-4c24-9310-7a33861edde2
            - 0f64c0dc-3e00-4b4f-86cc-9259a2b575d6
        assetFileIds:
          type: array
          items:
            type: string
            format: uuid
            description: Universally Unique Identifier (UUID v4)
            example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
          description: |-
            Optional array of pre-uploaded file IDs to attach to this service.

            📤 **To get file IDs**:
            1. Upload files using `POST /api/public/v1/files/upload/presigned-url` (see [File Upload](#tag/%F0%9F%93%A4%20File%20Upload) section)
            2. Get the `fileId` from the response
            3. Include it in this array

            **Important**: Files are uploaded per service. Each service can have its own set of files.

            **Example**: `["67eb9e88-7962-41a1-9695-f2051a968728", "another-file-id"]`

            The system automatically transforms these file IDs into the internal assets format (`assets` array with `type: "FILE"`, `assetType: "SOURCE"`).
      required:
        - serviceId
        - type
        - targetMarketIds
    RFQStatus:
      type: string
      enum:
        - PENDING
        - CHANGED_TO_QUOTE
        - CHANGED_TO_PROJECT
      description: |-
        RFQ lifecycle status:

        | Status | Description |
        |--------|-------------|
        | `PENDING` | Submitted - waiting for Adaptria to review and provide a quote |
        | `CHANGED_TO_QUOTE` | Adaptria has provided a quote based on this RFQ |
        | `CHANGED_TO_PROJECT` | Quote was accepted and converted to an active project |
      example: PENDING
    CreateRFQRequest:
      type: object
      properties:
        companyId:
          type: string
          format: uuid
          description: |-
            The company ID this RFQ belongs to.

            **Important**: You must have access to this company through your API credentials.

            📖 **To get company IDs**: See the [Companies](#tag/%F0%9F%8F%A2%20Companies) section → `GET /api/public/v1/client/companies`
          example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
        projectName:
          type: string
          minLength: 1
          maxLength: 255
          description: |-
            A clear, descriptive name for your project.

            **Best Practices**:
            - Be specific: "Q4 Marketing Campaign - German Website" vs "Translation Project"
            - Include key identifiers: campaign name, product, date
          example: Q4 2025 Marketing Campaign - Website Localization
        projectDescription:
          type: string
          minLength: 1
          maxLength: 2000
          description: |-
            Detailed description of what needs to be translated/localized.

            **Include**: Content type, volume estimate, specific requirements, file formats
          example: Translate our new product landing page from English to German, French, and Spanish. Content includes marketing copy (~2,000 words), product specifications, and customer testimonials.
        backgroundInfo:
          type: string
          maxLength: 1000
          description: Additional context about your company, brand, or project
          example: We're a B2B SaaS company expanding into European markets. We have a brand style guide and terminology glossary.
        targetAudience:
          type: string
          maxLength: 500
          description: Who will consume the translated content? Helps with tone and terminology choices.
          example: European B2B decision-makers, primarily CTOs and IT managers
        poNumber:
          type: string
          maxLength: 100
          description: Your internal Purchase Order number (must be unique per company)
          example: PO-2025-Q4-MKT-001
        referenceNumber:
          type: string
          maxLength: 100
          description: Your internal reference or ticket number
          example: CAMP-2025-Q4-EU
        projectDueDate:
          type:
            - string
            - 'null'
          format: date
          pattern: ^\d{4}-\d{2}-\d{2}$
          description: When do you need the final deliverables? (YYYY-MM-DD, can be null)
          example: '2025-12-31'
        deadlineForQuote:
          type: string
          format: date
          pattern: ^\d{4}-\d{2}-\d{2}$
          description: By when do you need the quote? (YYYY-MM-DD)
          example: '2025-12-15'
        services:
          type: array
          items:
            oneOf:
              - $ref: '#/components/schemas/LinguisticsService'
              - $ref: '#/components/schemas/NonLinguisticsService'
            discriminator:
              propertyName: type
              mapping:
                LINGUISTICS: '#/components/schemas/LinguisticsService'
                NON_LINGUISTICS: '#/components/schemas/NonLinguisticsService'
          description: |-
            **Services to include in this RFQ**

            Each service requires different fields based on its type:

            **LINGUISTICS** (translation/localization):
            - `serviceId`: Service ID - see [Services](#tag/%F0%9F%93%8B%20Services) section
            - `type`: "LINGUISTICS"
            - `sourceLanguageId`: Source language ID - see [Languages](#tag/%F0%9F%8C%8D%20Languages) section
            - `targetLanguageIds`: Array of target language IDs - see [Languages](#tag/%F0%9F%8C%8D%20Languages) section
            - `assetFileIds`: (optional) Pre-uploaded file IDs - see [File Upload](#tag/%F0%9F%93%A4%20File%20Upload) section

            **NON_LINGUISTICS** (video adaptation, creative):
            - `serviceId`: Service ID - see [Services](#tag/%F0%9F%93%8B%20Services) section
            - `type`: "NON_LINGUISTICS"
            - `targetMarketIds`: Array of target market IDs - see [Target Markets](#tag/%F0%9F%8E%AF%20Target%20Markets) section
            - `assetFileIds`: (optional) Pre-uploaded file IDs - see [File Upload](#tag/%F0%9F%93%A4%20File%20Upload) section
        autoSubmit:
          type: boolean
          default: true
          description: If true (default), the RFQ is automatically submitted with PENDING status.
          example: true
      required:
        - companyId
        - projectName
        - projectDescription
    CreateRFQResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          properties:
            id:
              type: string
              format: uuid
              description: Unique identifier for the created RFQ - use this for all subsequent operations
              example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
            rfqNumber:
              type:
                - string
                - 'null'
              description: Human-readable RFQ number (assigned after submission, null for drafts)
              example: RFQ-2025-001234
            projectName:
              type: string
            status:
              type: string
              enum:
                - PENDING
                - CHANGED_TO_QUOTE
                - CHANGED_TO_PROJECT
              description: RFQ status - starts at PENDING when submitted
              example: PENDING
            company:
              type: object
              properties:
                id:
                  type: string
                  format: uuid
                  description: Universally Unique Identifier (UUID v4)
                  example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
                name:
                  type: string
                  description: Company name
                  example: Acme Corporation
              required:
                - id
                - name
            servicesAdded:
              type: number
              description: Number of services configured in the RFQ
              example: 2
            createdAt:
              type: string
              format: date-time
            updatedAt:
              type: string
              format: date-time
          required:
            - id
            - rfqNumber
            - projectName
            - status
            - company
            - servicesAdded
            - createdAt
            - updatedAt
        message:
          type: string
      required:
        - success
        - data
    RFQListItem:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Universally Unique Identifier (UUID v4)
          example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
        rfqNumber:
          type:
            - string
            - 'null'
          description: Human-readable RFQ number (null for drafts)
          example: RFQ-2025-001234
        projectName:
          type: string
        status:
          type: string
          enum:
            - PENDING
            - CHANGED_TO_QUOTE
            - CHANGED_TO_PROJECT
          description: |-
            RFQ lifecycle status:

            | Status | Description |
            |--------|-------------|
            | `PENDING` | Submitted - waiting for Adaptria to review and provide a quote |
            | `CHANGED_TO_QUOTE` | Adaptria has provided a quote based on this RFQ |
            | `CHANGED_TO_PROJECT` | Quote was accepted and converted to an active project |
          example: PENDING
        companyId:
          type: string
          format: uuid
          description: Universally Unique Identifier (UUID v4)
          example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
        companyName:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        submittedAt:
          type: string
          format: date-time
      required:
        - id
        - rfqNumber
        - projectName
        - status
        - companyId
        - companyName
        - createdAt
        - updatedAt
    ListRFQsResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: array
          items:
            $ref: '#/components/schemas/RFQListItem'
        pagination:
          $ref: '#/components/schemas/Pagination'
      required:
        - success
        - data
        - pagination
    RFQDetails:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Universally Unique Identifier (UUID v4)
          example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
        rfqNumber:
          type:
            - string
            - 'null'
        projectName:
          type: string
        status:
          type: string
          enum:
            - PENDING
            - CHANGED_TO_QUOTE
            - CHANGED_TO_PROJECT
          description: |-
            RFQ lifecycle status:

            | Status | Description |
            |--------|-------------|
            | `PENDING` | Submitted - waiting for Adaptria to review and provide a quote |
            | `CHANGED_TO_QUOTE` | Adaptria has provided a quote based on this RFQ |
            | `CHANGED_TO_PROJECT` | Quote was accepted and converted to an active project |
          example: PENDING
        company:
          type: object
          properties:
            id:
              type: string
              format: uuid
              description: Universally Unique Identifier (UUID v4)
              example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
            name:
              type: string
          required:
            - id
            - name
        generalInformation:
          type: object
          properties:
            projectName:
              type: string
            projectDescription:
              type: string
            backgroundInfo:
              type: string
            targetAudience:
              type: string
            poNumber:
              type: string
            referenceNumber:
              type: string
          required:
            - projectName
        projectDueDate:
          type:
            - string
            - 'null'
        deadlineForQuote:
          type: string
        services:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
                description: Unique identifier for the service
                example: 34acfed6-ae10-46e3-8896-9afe78c2ecdc
              name:
                type: string
                description: Human-readable service name
                example: Translation
            required:
              - id
              - name
        languages:
          type: object
          properties:
            sourceLanguages:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                    description: '**Language ID - Use this in RFQ creation!** Unique identifier for the language.'
                    example: 3ebf026a-578b-46cb-b4a5-de4b54bab575
                  name:
                    type: string
                    description: English name of the language
                    example: Danish
                required:
                  - id
                  - name
            targetLanguages:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                    description: '**Language ID - Use this in RFQ creation!** Unique identifier for the language.'
                    example: 3ebf026a-578b-46cb-b4a5-de4b54bab575
                  name:
                    type: string
                    description: English name of the language
                    example: Danish
                required:
                  - id
                  - name
          required:
            - sourceLanguages
            - targetLanguages
        targetMarkets:
          type: array
          items:
            $ref: '#/components/schemas/PublicTargetMarket'
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        submittedAt:
          type: string
          format: date-time
      required:
        - id
        - rfqNumber
        - projectName
        - status
        - company
        - generalInformation
        - services
        - languages
        - targetMarkets
        - createdAt
        - updatedAt
    GeneratePresignedUrlRequest:
      type: object
      properties:
        filename:
          type: string
          minLength: 1
          maxLength: 255
          description: Original filename (max 255 characters)
          example: Screenshot 2026-01-21 at 2.22.54 PM.png
        sizeBytes:
          type: integer
          exclusiveMinimum: 0
          maximum: 104857600
          description: File size in bytes (max 100MB = 104857600 bytes)
          example: 424233
        mimeType:
          type: string
          enum:
            - application/pdf
            - application/msword
            - application/vnd.openxmlformats-officedocument.wordprocessingml.document
            - application/vnd.ms-excel
            - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
            - application/vnd.ms-powerpoint
            - application/vnd.openxmlformats-officedocument.presentationml.presentation
            - text/plain
            - text/csv
            - image/jpeg
            - image/png
            - image/gif
            - image/webp
            - video/mp4
            - video/quicktime
            - audio/mpeg
            - audio/wav
            - application/zip
            - application/x-zip-compressed
          description: File MIME type. Must be one of the allowed types.
          example: image/png
        entityType:
          type: string
          enum:
            - user
            - project
            - rfq
            - quote
            - job
            - profile
          description: Type of entity the file belongs to. Use "rfq" for RFQ-related files.
          example: rfq
        entityId:
          type: string
          format: uuid
          description: ID of the entity (optional). For RFQs, you can omit this or use the RFQ ID if you have it.
          example: 00000000-0000-0000-0000-000000000001
        isPublic:
          type: boolean
          default: false
          description: 'Whether the file should be publicly accessible. Default: false (private)'
          example: false
      required:
        - filename
        - sizeBytes
        - mimeType
        - entityType
    GeneratePresignedUrlResponse:
      type: object
      properties:
        fileId:
          type: string
          format: uuid
          description: '**File ID - Use this in your RFQ assets array!**'
          example: 67eb9e88-7962-41a1-9695-f2051a968728
        presignedUrl:
          type: string
          format: uri
          description: Presigned URL for uploading the file directly to cloud storage
          example: https://storage.googleapis.com/bucket/path?signature=...
        expiresAt:
          type: string
          description: ISO timestamp when the presigned URL expires (typically 15 minutes)
          example: '2026-01-21T14:37:54.000Z'
        uploadInstructions:
          type: object
          properties:
            method:
              type: string
              enum:
                - PUT
              description: HTTP method to use for upload
            contentType:
              type: string
              description: Content-Type header to use when uploading
            maxSizeBytes:
              type: number
              description: Maximum allowed file size in bytes
          required:
            - method
            - contentType
            - maxSizeBytes
          description: Instructions for uploading the file to the presigned URL
        downloadUrl:
          type: string
          format: uri
          description: Temporary download URL (for testing)
        downloadUrlExpiresAt:
          type: string
          description: When the download URL expires
      required:
        - fileId
        - presignedUrl
        - expiresAt
        - uploadInstructions
    DirectFileUploadResponse:
      type: object
      properties:
        fileId:
          type: string
          format: uuid
          description: '**File ID - Use this in your RFQ assets array!**'
          example: 67eb9e88-7962-41a1-9695-f2051a968728
      required:
        - fileId
    GetRFQDetailsResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          $ref: '#/components/schemas/RFQDetails'
      required:
        - success
        - data
  parameters: {}
paths:
  /api/credentials/token:
    post:
      summary: 🔐 Generate Access Token
      description: |-
        ## Authentication Entry Point

        This endpoint is your **first step** to accessing the Adaptria Public API. Exchange your API credentials for a time-limited access token.

        ### Prerequisites

        Before calling this endpoint, you need API credentials generated from the Adaptria Portal:

        **Option A: Client User Self-Service**
        1. Log in to the [Adaptria Portal](https://test.locate.studiographene.co.uk/) as a **Client User**
        2. Navigate to **Settings → API Credentials**
        3. Click **Generate New Credentials**
        4. You'll receive a `clientId` and `clientSecret`

        **Option B: Admin Creates for Client User**

        Contact your Adaptria administrator to generate API credentials for your account. Administrators can create credentials through the Admin → Client User Management section in the Adaptria Portal.

        ### Authentication Flow

        ```
        ┌─────────────────┐     POST /api/credentials/token     ┌─────────────────┐
        │   Your System   │ ─────────────────────────────────▶ │  Adaptria API  │
        │                 │    { clientId, clientSecret }       │                 │
        │                 │ ◀───────────────────────────────── │                 │
        └─────────────────┘     { accessToken, expiresIn }      └─────────────────┘
                │
                │  Use accessToken in Authorisation header
                ▼
        ┌─────────────────────────────────────────────────────────────────────────┐
        │  All other API calls: Authorisation: Bearer <accessToken>              │
        └─────────────────────────────────────────────────────────────────────────┘
        ```

        ### Token Lifecycle

        - **Validity**: 1 hour (3600 seconds)
        - **Renewal**: Request a new token before expiration
        - **Revocation**: If credentials are revoked, existing tokens become invalid immediately

        ### Security Best Practices

        - ⚠️ Never expose your `clientSecret` in client-side code
        - 🔄 Implement automatic token refresh before expiration
        - 🔒 Store credentials in secure environment variables
        - 📝 Monitor for `401 Unauthorised` responses and refresh tokens accordingly
      tags:
        - 🔐 Authentication
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GenerateTokenRequest'
            examples:
              default:
                summary: Generate Access Token
                description: Exchange API credentials for an access token
                value:
                  clientId: adaptria_1733836800000_abc123def456
                  clientSecret: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
      responses:
        '200':
          description: Token generated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenerateTokenResponse'
        '400':
          description: Missing required fields
        '401':
          description: Invalid or expired credentials
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvalidCredentialsError'
  /api/public/v1/client/services:
    get:
      summary: 📋 Get Available Services
      description: |-
        ## Get Services for RFQ Creation

        Retrieve the list of services available to your company for creating RFQs.

        ### When to Use This

        Call this endpoint **before creating an RFQ** to get the available service IDs. You'll need to include at least one service when creating or updating an RFQ.

        ### Service Types

        Services are categorized into two types:

        | Type | Description | Examples |
        |------|-------------|----------|
        | `LINGUISTICS` | Language-based services | Translation, Transcription, Localization |
        | `NON_LINGUISTICS` | Supporting services | Desktop Publishing (DTP), QA, Engineering |

        ### Response Structure

        Services are returned as a flat list. Use the `id` field when creating RFQs.
      tags:
        - 📋 Services
      security:
        - bearerAuth: []
      parameters:
        - name: search
          in: query
          required: false
          schema:
            type: string
          description: Filter services by name
        - name: companyId
          in: query
          required: true
          schema:
            type: string
            format: uuid
          description: Company ID to filter services. Required to scope results to a specific company.
      responses:
        '200':
          description: Services retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetServicesResponse'
        '401':
          description: Authentication required - provide a valid Bearer token
        '403':
          description: Access denied - your credentials do not have permission
  /api/public/v1/client/languages:
    get:
      summary: 🌍 Get Available Languages
      description: |-
        ## Get Languages for Translation Services

        Retrieve available languages for your company's translation and localization projects.

        ### Language Categories

        Languages are returned in two groups:

        1. **Suggested Languages**: Languages your company frequently uses, based on:
           - Previous project history
           - Company language preferences
           - Most common translation pairs

        2. **Other Languages**: All other supported languages

        ### Using Languages in RFQs

        When creating an RFQ, use the `id` field from the language objects:

        - `sourceLanguageId`: The `id` of the source language (e.g., `"3ebf026a-578b-46cb-b4a5-de4b54bab575"`)
        - `targetLanguageIds`: Array of `id` values for target languages (e.g., `["7da3f046-8d67-4cdc-beea-8e9afdaeaf30"]`)

        **Example**:
        ```json
        {
          "serviceId": "...",
          "type": "LINGUISTICS",
          "sourceLanguageId": "3ebf026a-578b-46cb-b4a5-de4b54bab575",  ← Use the 'id' field
          "targetLanguageIds": ["7da3f046-8d67-4cdc-beea-8e9afdaeaf30"]  ← Use the 'id' field
        }
        ```

        ### Response Fields

        Each language object includes:
        - `id`: **Use this in RFQ creation** - Unique language identifier (UUID)
        - `name`: Human-readable language name (e.g., "Danish", "English (UK)")
        - `iso639Code`: ISO 639 language code (can be numeric or alphanumeric)
        - `isActive`: Whether the language is currently active
        - `createdAt`: Timestamp when the language was created
        - `updatedAt`: Timestamp when the language was last updated
      tags:
        - 🌍 Languages
      security:
        - bearerAuth: []
      parameters:
        - name: search
          in: query
          required: false
          schema:
            type: string
          description: Filter languages by name or code
        - name: companyId
          in: query
          required: true
          schema:
            type: string
            format: uuid
          description: Company ID to filter languages. Required to scope results to a specific company.
      responses:
        '200':
          description: Languages retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetLanguagesResponse'
        '401':
          description: Authentication required
        '403':
          description: Access denied
  /api/public/v1/client/target-markets:
    get:
      summary: 🎯 Get Available Target Markets
      description: |-
        ## Get Target Markets for Localization

        Retrieve the list of target markets (regions/countries) available for your localization projects.

        ### What are Target Markets?

        Target markets represent the geographical regions or countries where your localized content will be used. This helps Adaptria:
        - Assign appropriate regional specialists
        - Apply region-specific style guides
        - Consider cultural nuances in translation

        ### Using Target Markets in RFQs

        When creating an RFQ, you can optionally specify target markets using their IDs:

        ```json
        {
          "targetMarkets": ["uuid-for-germany", "uuid-for-france"]
        }
        ```

        ### Common Target Markets

        | Country Code | Name |
        |--------------|------|
        | DE | Germany |
        | FR | France |
        | ES | Spain |
        | GB | United Kingdom |
        | US | United States |
        | JP | Japan |
        | CN | China |
      tags:
        - 🎯 Target Markets
      security:
        - bearerAuth: []
      parameters:
        - name: search
          in: query
          required: false
          schema:
            type: string
          description: Filter target markets by name or country code
        - name: companyId
          in: query
          required: true
          schema:
            type: string
            format: uuid
          description: Company ID to filter target markets. Required to scope results to a specific company.
      responses:
        '200':
          description: Target markets retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetTargetMarketsResponse'
        '401':
          description: Authentication required
        '403':
          description: Access denied
  /api/public/v1/client/companies:
    get:
      summary: 🏢 Get Your Companies
      description: |-
        ## Get Companies You Have Access To

        Retrieve the list of companies (clients) that your user account is associated with.

        ### When to Use This

        Use this endpoint to:
        - See which companies you can create RFQs for
        - Get company IDs needed for API requests
        - Verify your company associations

        ### Response Fields

        | Field | Description |
        |-------|-------------|
        | `id` | Unique identifier (UUID) for the company |
        | `name` | Full company name |
        | `shortCode` | Short identifier code (optional) |
        | `clientType` | Type: `direct_client`, `agency`, or `subsidiary` |

        ### Using Company IDs

        When creating RFQs, the company context is automatically determined from your API token. However, if you have access to multiple companies, you may need to specify which company the RFQ belongs to.
      tags:
        - 🏢 Companies
      security:
        - bearerAuth: []
      parameters:
        - name: search
          in: query
          required: false
          schema:
            type: string
          description: Filter companies by name or short code
      responses:
        '200':
          description: Companies retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetCompaniesResponse'
        '401':
          description: Authentication required
        '403':
          description: Access denied
  /api/public/v1/rfqs:
    post:
      summary: 📝 Create and Submit RFQ
      description: |-
        ## Create and Submit a Request for Quote (RFQ) - Unified API

        This **unified endpoint** creates an RFQ with services and automatically submits it in a **single API call**.

        ### What You Can Do in ONE Request

        - ✅ Create the RFQ with project details
        - ✅ Add **LINGUISTICS** services (translation) with source/target languages
        - ✅ Add **NON_LINGUISTICS** services (video adaptation) with target markets
        - ✅ Attach pre-uploaded files to services
        - ✅ Add notification recipients
        - ✅ Automatically submit for quoting

        ### Service Types

        **LINGUISTICS** (for translation/localization):
        ```json
        {
          "serviceId": "34acfed6-ae10-46e3-8896-9afe78c2ecdc",
          "type": "LINGUISTICS",
          "sourceLanguageId": "f5e4d3c2-b1a0-4321-8765-fedcba987654",
          "targetLanguageIds": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890", "b2c3d4e5-f6a7-8901-bcde-f12345678901"],
          "assetFileIds": ["67eb9e88-7962-41a1-9695-f2051a968728"]
        }
        ```

        **NON_LINGUISTICS** (for video adaptation, creative services like 3D animation):
        ```json
        {
          "serviceId": "0cf41489-6424-492a-8cd7-4f7eef02858e",
          "type": "NON_LINGUISTICS",
          "targetMarketIds": ["03b91040-5931-4c24-9310-7a33861edde2", "0f64c0dc-3e00-4b4f-86cc-9259a2b575d6"],
          "assetFileIds": ["67eb9e88-7962-41a1-9695-f2051a968728"]
        }
        ```

        **Note**: `assetFileIds` contains file IDs obtained from the [File Upload](#tag/%F0%9F%93%A4%20File%20Upload) endpoint. Upload files first, then use the returned `fileId` values here.

        ### Internal Transformation

        Your simplified input is automatically transformed to the internal format:

        | Public API Field | Internal Field |
        |------------------|----------------|
        | `serviceId` | `serviceUUID` |
        | `targetMarketIds[]` | `targetMarkets[].targetMarketId` |
        | `sourceLanguageId` | `combos[].sourceLanguage` |
        | `targetLanguageIds[]` | `combos[].targetLanguages[].languageId` |

        ### Recommended Workflow

        ```
        1. GET /api/public/v1/client/companies      → Get company ID (see [Companies](#tag/%F0%9F%8F%A2%20Companies) section)
        2. GET /api/public/v1/client/services       → Get available services (see [Services](#tag/%F0%9F%93%8B%20Services) section)
        3. GET /api/public/v1/client/languages      → Get language IDs (see [Languages](#tag/%F0%9F%8C%8D%20Languages) section)
        4. GET /api/public/v1/client/target-markets → Get target market IDs (see [Target Markets](#tag/%F0%9F%8E%AF%20Target%20Markets) section)
        5. (Optional) POST /api/files/upload/presigned-url → Upload files and get file IDs (see [File Upload](#tag/%F0%9F%93%A4%20File%20Upload) section)
        6. POST /api/public/v1/rfqs                 → Create & submit RFQ (this endpoint)
        7. GET /api/public/v1/rfqs/:id              → Check status
        ```

        ### Auto Submit Control

        By default (`autoSubmit: true`), the RFQ is submitted immediately with PENDING status.

        ### PO Number Uniqueness

        The `poNumber` field must be unique per company. Duplicate PO numbers return **409 Conflict**.
      tags:
        - 📄 RFQs
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateRFQRequest'
            examples:
              bothServiceTypes:
                summary: ⭐ Complete Example (LINGUISTICS + NON_LINGUISTICS)
                description: 'Full RFQ with both service types: LINGUISTICS uses sourceLanguageId/targetLanguageIds, NON_LINGUISTICS uses targetMarketIds'
                value:
                  companyId: 3fa85f64-5717-4562-b3fc-2c963f66afa6
                  projectName: Q4 2025 Full Campaign - Translation + Animation
                  projectDescription: Translate website content and create animated web banners for European markets.
                  backgroundInfo: We're a B2B SaaS company expanding into European markets.
                  targetAudience: European B2B decision-makers
                  poNumber: PO-2025-Q4-MKT-001
                  referenceNumber: CAMP-2025-Q4-EU
                  projectDueDate: '2025-12-31'
                  deadlineForQuote: '2025-12-15'
                  services:
                    - serviceId: 8398f952-99f3-468a-a44c-6620a48dd07c
                      type: LINGUISTICS
                      sourceLanguageId: 3ebf026a-578b-46cb-b4a5-de4b54bab575
                      targetLanguageIds:
                        - 7da3f046-8d67-4cdc-beea-8e9afdaeaf30
                      assetFileIds:
                        - 91cc2de4-293b-4c37-b257-5d39cd1133da
                    - serviceId: e71b7bf7-ed9c-4ff1-8dec-f136e31e1aa1
                      type: NON_LINGUISTICS
                      targetMarketIds:
                        - 0f64c0dc-3e00-4b4f-86cc-9259a2b575d6
                      assetFileIds:
                        - dc68867c-beb5-46e3-b291-47978c94783b
                  autoSubmit: true
              linguisticsOnly:
                summary: LINGUISTICS Only (Translation)
                description: RFQ with translation service only - requires sourceLanguageId and targetLanguageIds (language IDs)
                value:
                  companyId: 3fa85f64-5717-4562-b3fc-2c963f66afa6
                  projectName: Q4 2025 Marketing Campaign - Website Localization
                  projectDescription: Translate marketing content from English to German and French.
                  projectDueDate: '2025-12-31'
                  services:
                    - serviceId: 8398f952-99f3-468a-a44c-6620a48dd07c
                      type: LINGUISTICS
                      sourceLanguageId: 3ebf026a-578b-46cb-b4a5-de4b54bab575
                      targetLanguageIds:
                        - 7da3f046-8d67-4cdc-beea-8e9afdaeaf30
                        - a1b2c3d4-e5f6-7890-abcd-ef1234567890
                      assetFileIds:
                        - 67eb9e88-7962-41a1-9695-f2051a968728
                  autoSubmit: true
              nonLinguisticsOnly:
                summary: NON_LINGUISTICS Only (Video/Creative)
                description: RFQ with video/creative service only - requires targetMarketIds (market IDs, NOT language IDs)
                value:
                  companyId: 3fa85f64-5717-4562-b3fc-2c963f66afa6
                  projectName: Q4 2025 Video Campaign - Animated Web Banners
                  projectDescription: Create animated web banners for German and French markets.
                  projectDueDate: '2025-12-31'
                  services:
                    - serviceId: e71b7bf7-ed9c-4ff1-8dec-f136e31e1aa1
                      type: NON_LINGUISTICS
                      targetMarketIds:
                        - 0f64c0dc-3e00-4b4f-86cc-9259a2b575d6
                        - 03b91040-5931-4c24-9310-7a33861edde2
                      assetFileIds:
                        - dc68867c-beb5-46e3-b291-47978c94783b
                  autoSubmit: true
              basicRfq:
                summary: Basic RFQ (no services)
                description: Simple RFQ without pre-configured services - services can be discussed with Adaptria
                value:
                  companyId: 3fa85f64-5717-4562-b3fc-2c963f66afa6
                  projectName: Q4 2025 Marketing Campaign
                  projectDescription: Translation project - services to be discussed with Adaptria team.
                  projectDueDate: '2025-12-31'
                  autoSubmit: true
      responses:
        '201':
          description: RFQ created and submitted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateRFQResponse'
        '400':
          description: Validation error - check required fields and service configuration
        '401':
          description: Authentication required
        '403':
          description: Access denied to the specified company
        '409':
          description: Duplicate PO number - PO numbers must be unique per company
    get:
      summary: 📋 List RFQs
      description: |-
        ## List Your RFQs

        Retrieve a paginated list of RFQs accessible to your API credentials.

        ### Filtering Options

        | Parameter | Description |
        |-----------|-------------|
        | `status` | Filter by status (DRAFT, PENDING, etc.) |
        | `search` | Search by project name or RFQ number |
        | `page` | Page number (starts at 1) |
        | `limit` | Items per page (max 100) |

        ### Response

        Returns a summary view of each RFQ - use `GET /api/public/v1/rfqs/:id` for full details.
      tags:
        - 📄 RFQs
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            pattern: ^\d+$
            default: '1'
            description: Page number to retrieve (starts from 1)
            example: '1'
          required: false
          description: Page number to retrieve (starts from 1)
          name: page
          in: query
        - schema:
            type: string
            pattern: ^\d+$
            default: '20'
            description: Number of items per page (max 100)
            example: '20'
          required: false
          description: Number of items per page (max 100)
          name: limit
          in: query
        - schema:
            type: string
            description: |-
              Filter by RFQ status.

              Available values: `PENDING`, `CHANGED_TO_QUOTE`, `CHANGED_TO_PROJECT`
            example: DRAFT
          required: false
          description: |-
            Filter by RFQ status.

            Available values: `PENDING`, `CHANGED_TO_QUOTE`, `CHANGED_TO_PROJECT`
          name: status
          in: query
        - schema:
            type: string
            description: Search by project name or RFQ number
            example: Q4 Marketing
          required: false
          description: Search by project name or RFQ number
          name: search
          in: query
      responses:
        '200':
          description: RFQs retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListRFQsResponse'
        '401':
          description: Authentication required
  /api/public/v1/rfqs/{id}:
    get:
      summary: 🔍 Get RFQ Details
      description: |-
        ## Get Full RFQ Details

        Retrieve complete information about a specific RFQ, including:
        - General information (name, description, dates)
        - Selected services
        - Language configuration
        - Target markets
      tags:
        - 📄 RFQs
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: RFQ ID
            example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
          required: true
          description: RFQ ID
          name: id
          in: path
      responses:
        '200':
          description: RFQ details retrieved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetRFQDetailsResponse'
        '401':
          description: Authentication required
        '403':
          description: Access denied
        '404':
          description: RFQ not found
  /api/public/v1/files/upload/presigned-url:
    post:
      summary: 📤 Upload File and Get File ID
      description: |-
        ## Upload Files for RFQ Service Assets

        This endpoint allows you to upload files directly via `multipart/form-data` that will be attached to specific services in your RFQ.

        ### How It Works

        **Direct File Upload (Multipart Only)**
        - Use `multipart/form-data` content type
        - Select a file directly in Swagger UI or send as multipart form
        - The system automatically:
          1. Generates a presigned URL
          2. Uploads the file to GCP storage
          3. Saves file metadata
          4. Returns only the `fileId` (ready to use in RFQ services)
        - **Response**: `{ success: true, message: "File uploaded successfully", data: { fileId: "..." } }`
        - **All in one request!** No need to make a separate PUT request

        ### Complete File Upload Workflow

        **Important**: Files are uploaded **per service**. Each service in your RFQ can have its own set of files.

        ```
        Step 1: Upload File for a Specific Service
        POST /api/public/v1/files/upload/presigned-url
        Content-Type: multipart/form-data
        - file: [binary file data]

        → Response: { success: true, data: { fileId: "91cc2de4-293b-4c37-b257-5d39cd1133da" } }

        Step 2: Upload Another File for a Different Service (if needed)
        POST /api/public/v1/files/upload/presigned-url
        Content-Type: multipart/form-data
        - file: [another binary file data]

        → Response: { success: true, data: { fileId: "dc68867c-beb5-46e3-b291-47978c94783b" } }

        Step 3: Create or Update RFQ with fileIds in Service Objects
        POST /api/public/v1/rfqs
        {
          "companyId": "...",
          "projectName": "My Project",
          "services": [
            {
              "serviceId": "33cecd1e-6629-41d8-bc65-f1ad832a2bd2",
              "type": "NON_LINGUISTICS",
              "targetMarketIds": ["03b91040-5931-4c24-9310-7a33861edde2"],
              "assetFileIds": ["91cc2de4-293b-4c37-b257-5d39cd1133da"]  ← File for this service
            },
            {
              "serviceId": "7ff59ce3-d3e7-4898-ac35-63b38531c17d",
              "type": "NON_LINGUISTICS",
              "targetMarketIds": ["55247866-be7d-4dad-8cea-5e3a71a8e2a6"],
              "assetFileIds": ["dc68867c-beb5-46e3-b291-47978c94783b"]  ← Different file for this service
            }
          ]
        }

        OR Update an existing RFQ:
        PATCH /api/public/v1/rfqs/{rfqId}
        {
          "services": [
            {
              "id": "00000000-0000-0000-0000-000000000001",
              "type": "NON_LINGUISTICS",
              "serviceUUID": "33cecd1e-6629-41d8-bc65-f1ad832a2bd2",
              "targetMarkets": [{"targetMarketId": "03b91040-5931-4c24-9310-7a33861edde2"}],
              "assets": [
                {
                  "type": "FILE",
                  "assetType": "SOURCE",
                  "fileId": "91cc2de4-293b-4c37-b257-5d39cd1133da"
                }
              ]
            }
          ]
        }
        ```

        ### File Requirements

        - **Max Size**: 100MB (104,857,600 bytes)
        - **Allowed Types**: PDF, Word, Excel, PowerPoint, Images, Videos, Audio, ZIP

        ### Using File IDs in RFQ Services

        **For Creating RFQs**: Include `fileId`s in the `assetFileIds` array of each service object:

        ```json
        {
          "serviceId": "05b68c28-5659-4c57-809b-a55bae97f967",
          "type": "NON_LINGUISTICS",
          "targetMarketIds": ["976fba00-3e0d-40fe-b766-6017cb44c3a7"],
          "assetFileIds": ["67eb9e88-7962-41a1-9695-f2051a968728", "another-file-id-here"]
        }
        ```

        **For Updating RFQs**: Use the internal `assets` format with `fileId`:

        ```json
        {
          "assets": [
            {
              "type": "FILE",
              "assetType": "SOURCE",
              "fileId": "67eb9e88-7962-41a1-9695-f2051a968728"
            }
          ]
        }
        ```

        **Note**:
        - Each service can have multiple files (multiple `fileId`s in the `assetFileIds` array)
        - Files are associated with specific services, not the entire RFQ
        - The system automatically transforms `assetFileIds` to the internal `assets` format when creating RFQs
        - See the [RFQs](#tag/%F0%9F%93%84%20RFQs) section for complete RFQ creation examples.
      tags:
        - 📤 File Upload
      security:
        - bearerAuth: []
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: 'File to upload (binary). Max size: 100MB. Supported types: PDF, Word, Excel, PowerPoint, Images, Videos, Audio, ZIP'
            encoding:
              file:
                contentType: application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,text/plain,text/csv,image/jpeg,image/png,image/gif,image/webp,video/mp4,video/quicktime,audio/mpeg,audio/wav,application/zip,application/x-zip-compressed
      responses:
        '201':
          description: File uploaded successfully. The file is uploaded internally, metadata is saved automatically, and only fileId is returned (ready to use in RFQs).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DirectFileUploadResponse'
              example:
                success: true
                message: File uploaded successfully
                data:
                  fileId: 67eb9e88-7962-41a1-9695-f2051a968728
        '400':
          description: Invalid request (invalid file type, size too large, etc.)
        '401':
          description: Authentication required
        '403':
          description: Access denied
webhooks: {}
