Repository: airbytehq/airbyte
Pull request #58628: feat(source-linkedin-ads): Convert LinkedIn Ads to manifest-only
Author: login: brianjlai
Labels: name: connectors/source/linkedin-ads
Created: 2025-04-24T07:57:02Z | Merged: 2025-04-25T20:06:32Z

Closes https://github.com/airbytehq/airbyte-internal-issues/issues/12567

## What

Converts `source-linkedin-ads` to manifest-only format using source declarative manifest.

## How

Generally follows the same pattern as some of our other migrations. A few interesting things to note:

- Like other connectors, we had a dependency on `pendulum` by a few custom components. These were replaced by their equivalent `ab_datetime` implementation
  - `.to_datetime().isoformat()` renders the string representation in 3339 (w/ T delimiter)
  - `.to_datetime().strftime("%Y-%m-%d %H:%M:%S")` to emit timestamp w/ ` ` (space) delimiter
  - `.to_datetime().strftime("%Y-%m-%d")` to emit yyyy-mm-dd for analytics streams
- This connector also has some mock server tests so we need to do a similar workaround to allow for loading the custom `components.py` so that it is available for testing. 

## Review guide

1. `components.py`
2. `test_source.py`
3. `test_streams.py`

The rest is mostly moving files around, adding to the manifest, or deleting them entirely

## User Impact

should be none

## Can this PR be safely reverted and rolled back?

- [x] YES 💚
- [ ] NO ❌



Diff:
diff --git a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/manifest_only_connectors.py b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/manifest_only_connectors.py
index 7d78c970b97a..d618e8e9875f 100644
--- a/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/manifest_only_connectors.py
+++ b/airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/manifest_only_connectors.py
@@ -64,7 +64,7 @@ class ManifestOnlyConnectorUnitTests(PytestStep):
 
     title = "Manifest-only unit tests"
     test_directory_name = "unit_tests"
-    common_test_dependencies = ["pytest", "requests-mock"]
+    common_test_dependencies = ["freezegun", "pytest", "pytest-mock", "requests-mock"]
 
     async def install_testing_environment(
         self,
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/.coveragerc b/airbyte-integrations/connectors/source-linkedin-ads/.coveragerc
deleted file mode 100644
index 6b0b0af5e2ce..000000000000
--- a/airbyte-integrations/connectors/source-linkedin-ads/.coveragerc
+++ /dev/null
@@ -1,3 +0,0 @@
-[run]
-omit =
-    source_linkedin_ads/run.py
\ No newline at end of file
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py b/airbyte-integrations/connectors/source-linkedin-ads/components.py
similarity index 62%
rename from airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py
rename to airbyte-integrations/connectors/source-linkedin-ads/components.py
index 46c78930b012..a0eba74abe6d 100644
--- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/utils.py
+++ b/airbyte-integrations/connectors/source-linkedin-ads/components.py
@@ -1,11 +1,29 @@
 #
-# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
+# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
 #
 
-import json
-from typing import Any, Dict, Iterable, List, Mapping
 
-import pendulum as pdm
+import json
+from dataclasses import dataclass
+from typing import Any, Dict, Iterable, List, Mapping, MutableMapping, Optional, Union
+from urllib.parse import urlencode
+
+import requests
+from requests.exceptions import InvalidURL
+
+from airbyte_cdk.models import FailureType
+from airbyte_cdk.sources.declarative.extractors.record_extractor import RecordExtractor
+from airbyte_cdk.sources.declarative.requesters import HttpRequester
+from airbyte_cdk.sources.declarative.requesters.error_handlers import DefaultErrorHandler
+from airbyte_cdk.sources.declarative.requesters.request_options.interpolated_request_options_provider import (
+    InterpolatedRequestOptionsProvider,
+    RequestInput,
+)
+from airbyte_cdk.sources.streams.http import HttpClient
+from airbyte_cdk.sources.streams.http.error_handlers import ErrorResolution, ResponseAction
+from airbyte_cdk.sources.streams.http.exceptions import DefaultBackoffException, RequestBodyException, UserDefinedBackoffException
+from airbyte_cdk.sources.streams.http.http import BODY_REQUEST_METHODS
+from airbyte_cdk.utils.datetime_helpers import AirbyteDateTime, ab_datetime_parse
 
 
 # replace `pivot` with `_pivot`, to allow redshift normalization,
@@ -15,28 +33,133 @@
 DESTINATION_RESERVED_KEYWORDS: list = ["pivot"]
 
 
-def update_specific_key(target_dict, target_key, target_value, condition_func=None, excluded_keys=None):
-    if excluded_keys is None:
-        excluded_keys = []
-
-    for key, value in target_dict.items():
-        # Skip any keys that are in excluded_keys
-        if key in excluded_keys:
-            continue
-        # Apply the condition function if provided, and only update if the condition is true
-        if key == target_key and (condition_func is None or condition_func(target_dict)):
-            target_dict[key] = target_value
-        elif isinstance(value, dict):
-            # Recursively update nested dictionaries
-            target_dict[key] = update_specific_key(value, target_key, target_value, condition_func, excluded_keys)
-        elif isinstance(value, list):
-            # Recursively update lists
-            target_dict[key] = [
-                update_specific_key(item, target_key, target_value, condition_func, excluded_keys) if isinstance(item, dict) else item
-                for item in value
-            ]
+class SafeHttpClient(HttpClient):
+    """
+    A custom HTTP client that safely validates query parameters, ensuring that the symbols ():,% are preserved
+    during UTF-8 encoding.
+    """
+
+    def _create_prepared_request(
+        self,
+        http_method: str,
+        url: str,
+        dedupe_query_params: bool = False,
+        headers: Optional[Mapping[str, str]] = None,
+        params: Optional[Mapping[str, str]] = None,
+        json: Optional[Mapping[str, Any]] = None,
+        data: Optional[Union[str, Mapping[str, Any]]] = None,
+    ) -> requests.PreparedRequest:
+        """
+        Prepares an HTTP request with optional deduplication of query parameters and safe encoding.
+        """
+        if dedupe_query_params:
+            query_params = self._dedupe_query_params(url, params)
+        else:
+            query_params = params or {}
+        query_params = urlencode(query_params, safe="():,%")
+        args = {"method": http_method, "url": url, "headers": headers, "params": query_params}
+        if http_method.upper() in BODY_REQUEST_METHODS:
+            if json and data:
+                raise RequestBodyException(
+                    "At the same time only one of the 'request_body_data' and 'request_body_json' functions can return data"
+                )
+            elif json:
+                args["json"] = json
+            elif data:
+                args["data"] = data
+        prepared_request: requests.PreparedRequest = self._session.prepare_request(requests.Request(**args))
+
+        return prepared_request
+
+
+@dataclass
+class SafeEncodeHttpRequester(HttpRequester):
+    """
+    A custom HTTP requester that ensures safe encoding of query parameters, preserving the symbols ():,% during UTF-8 encoding.
+    """
+
+    request_body_json: Optional[RequestInput] = None
+    request_headers: Optional[RequestInput] = None
+    request_parameters: Optional[RequestInput] = None
+    request_body_data: Optional[RequestInput] = None
+    query_properties_key: Optional[str] = None
+
+    def __post_init__(self, parameters: Mapping[str, Any]) -> None:
+        """
+        Initializes the request options provider with the provided parameters and any
+        configured request components like headers, parameters, or bodies.
+        """
+        self.request_options_provider = InterpolatedRequestOptionsProvider(
+            request_body_data=self.request_body_data,
+            request_body_json=self.request_body_json,
+            request_headers=self.request_headers,
+            request_parameters=self.request_parameters,
+            query_properties_key=self.query_properties_key,
+            config=self.config,
+            parameters=parameters or {},
+        )
+        super().__post_init__(parameters)
+
+        if self.error_handler is not None and hasattr(self.error_handler, "backoff_strategies"):
+            backoff_strategies = self.error_handler.backoff_strategies
+        else:
+            backoff_strategies = None
+
+        self._http_client = SafeHttpClient(
+            name=self.name,
+            logger=self.logger,
+            error_handler=self.error_handler,
+            authenticator=self._authenticator,
+            use_cache=self.use_cache,
+            backoff_strategy=backoff_strategies,
+            disable_retries=self.disable_retries,
+            message_repository=self.message_repository,
+        )
+
+
+@dataclass
+class LinkedInAdsRecordExtractor(RecordExtractor):
+    """
+    Extracts and transforms LinkedIn Ads records, ensuring that 'lastModified' and 'created'
+    date-time fields are formatted to RFC3339.
+    """
+
+    def _date_time_to_rfc3339(self, record: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
+        """
+        Converts 'lastModified' and 'created' fields in the record to RFC3339 format.
+        """
+        for item in ["lastModified", "created"]:
+            if record.get(item) is not None:
+                record[item] = ab_datetime_parse(record[item]).to_datetime().isoformat()
+        return record
+
+    def extract_records(self, response: requests.Response) -> Iterable[Mapping[str, Any]]:
+        """
+        Extracts and transforms records from an HTTP response.
+        """
+        for record in transform_data(response.json().get("elements")):
+            yield self._date_time_to_rfc3339(record)
+
 
-    return target_dict
+@dataclass
+class LinkedInAdsErrorHandler(DefaultErrorHandler):
+    """
+    An error handler for LinkedIn Ads that interprets responses, providing custom error resolutions
+    for specific exceptions like `InvalidURL`.
+    This is a temporary workaround untill we update this in the CDK. https://github.com/airbytehq/airbyte-internal-issues/issues/11320
+    """
+
+    def interpret_response(self, response_or_exception: Optional[Union[requests.Response, Exception]]) -> ErrorResolution:
+        """
+        Interprets responses and exceptions, providing custom error resolutions for specific exceptions.
+        """
+        if isinstance(response_or_exception, InvalidURL):
+            return ErrorResolution(
+                response_action=ResponseAction.RETRY,
+                failure_type=FailureType.transient_error,
+                error_message="source-linkedin-ads has faced a temporary DNS resolution issue. Retrying...",
+            )
+        return super().interpret_response(response_or_exception)
 
 
 def transform_change_audit_stamps(
@@ -61,7 +184,7 @@ def transform_change_audit_stamps(
     for prop in props:
         # Update dict with flatten key:value
         for field in fields:
-            record[prop] = pdm.from_timestamp(target_dict.get(prop).get(field) / 1000).to_datetime_string()
+            record[prop] = ab_datetime_parse(target_dict.get(prop).get(field) // 1000).to_datetime().strftime("%Y-%m-%d %H:%M:%S")
     record.pop(dict_key)
 
     return record
@@ -86,7 +209,7 @@ def date_str_from_date_range(record: Dict, prefix: str) -> str:
     year = record.get(f"{prefix}.year")
     month = record.get(f"{prefix}.month")
     day = record.get(f"{prefix}.day")
-    return pdm.date(year, month, day).to_date_string()
+    return AirbyteDateTime(year, month, day).to_datetime().strftime("%Y-%m-%d")
 
 
 def transform_date_range(
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/main.py b/airbyte-integrations/connectors/source-linkedin-ads/main.py
deleted file mode 100644
index bf3f38b6d9fc..000000000000
--- a/airbyte-integrations/connectors/source-linkedin-ads/main.py
+++ /dev/null
@@ -1,9 +0,0 @@
-#
-# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
-#
-
-from source_linkedin_ads.run import run
-
-
-if __name__ == "__main__":
-    run()
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/manifest.yaml b/airbyte-integrations/connectors/source-linkedin-ads/manifest.yaml
similarity index 98%
rename from airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/manifest.yaml
rename to airbyte-integrations/connectors/source-linkedin-ads/manifest.yaml
index 8fa9fee3c5a1..f95b500300c9 100644
--- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/manifest.yaml
+++ b/airbyte-integrations/connectors/source-linkedin-ads/manifest.yaml
@@ -26,7 +26,7 @@ definitions:
             Linkedin-Version: "202502"
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -34,7 +34,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
           record_filter:
             condition: "{{ record['id'] in config['account_ids'] if config.get('account_ids') else True }}"
         paginator:
@@ -76,7 +76,7 @@ definitions:
             Linkedin-Version: "202502"
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -84,7 +84,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
         paginator:
           type: DefaultPaginator
           page_token_option:
@@ -129,7 +129,7 @@ definitions:
         type: SimpleRetriever
         requester:
           type: CustomRequester
-          class_name: "source_linkedin_ads.components.SafeEncodeHttpRequester"
+          class_name: source_declarative_manifest.components.SafeEncodeHttpRequester
           url_base: https://api.linkedin.com/rest/
           authenticator: "#/definitions/authenticator"
           path: adAccounts/{{stream_slice.get('account_id')}}/adCampaigns
@@ -142,7 +142,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -150,7 +150,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
         paginator:
           type: DefaultPaginator
           page_token_option:
@@ -201,7 +201,7 @@ definitions:
         type: SimpleRetriever
         requester:
           type: CustomRequester
-          class_name: "source_linkedin_ads.components.SafeEncodeHttpRequester"
+          class_name: source_declarative_manifest.components.SafeEncodeHttpRequester
           url_base: https://api.linkedin.com/rest/
           authenticator: "#/definitions/authenticator"
           path: adAccounts/{{stream_slice.get('account_id')}}/adCampaignGroups
@@ -214,7 +214,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -222,7 +222,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
         paginator:
           type: DefaultPaginator
           page_token_option:
@@ -304,7 +304,7 @@ definitions:
                       - 500
                       - 503
               - type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -312,7 +312,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
         paginator:
           type: DefaultPaginator
           page_token_option:
@@ -373,7 +373,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -436,7 +436,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -487,7 +487,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -532,7 +532,7 @@ definitions:
           name: ad_campaign_analytics
         requester:
           type: CustomRequester
-          class_name: "source_linkedin_ads.components.SafeEncodeHttpRequester"
+          class_name: source_declarative_manifest.components.SafeEncodeHttpRequester
           url_base: https://api.linkedin.com/rest/
           authenticator: "#/definitions/authenticator"
           path: adAnalytics
@@ -549,7 +549,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -557,7 +557,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
           schema_normalization: Default
         paginator:
           type: NoPagination
@@ -611,7 +611,7 @@ definitions:
           name: ad_creative_analytics
         requester:
           type: CustomRequester
-          class_name: "source_linkedin_ads.components.SafeEncodeHttpRequester"
+          class_name: source_declarative_manifest.components.SafeEncodeHttpRequester
           url_base: https://api.linkedin.com/rest/
           authenticator: "#/definitions/authenticator"
           path: adAnalytics
@@ -628,7 +628,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -636,7 +636,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
           schema_normalization: Default
         paginator:
           type: NoPagination
@@ -690,7 +690,7 @@ definitions:
           name: ad_impression_device_analytics
         requester:
           type: CustomRequester
-          class_name: "source_linkedin_ads.components.SafeEncodeHttpRequester"
+          class_name: source_declarative_manifest.components.SafeEncodeHttpRequester
           url_base: https://api.linkedin.com/rest/
           authenticator: "#/definitions/authenticator"
           path: adAnalytics
@@ -707,7 +707,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -715,7 +715,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
           schema_normalization: Default
         paginator:
           type: NoPagination
@@ -769,7 +769,7 @@ definitions:
           name: ad_member_company_size_analytics
         requester:
           type: CustomRequester
-          class_name: "source_linkedin_ads.components.SafeEncodeHttpRequester"
+          class_name: source_declarative_manifest.components.SafeEncodeHttpRequester
           url_base: https://api.linkedin.com/rest/
           authenticator: "#/definitions/authenticator"
           path: adAnalytics
@@ -786,7 +786,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -794,7 +794,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
           schema_normalization: Default
         paginator:
           type: NoPagination
@@ -848,7 +848,7 @@ definitions:
           name: ad_member_country_analytics
         requester:
           type: CustomRequester
-          class_name: "source_linkedin_ads.components.SafeEncodeHttpRequester"
+          class_name: source_declarative_manifest.components.SafeEncodeHttpRequester
           url_base: https://api.linkedin.com/rest/
           authenticator: "#/definitions/authenticator"
           path: adAnalytics
@@ -865,7 +865,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -873,7 +873,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
           schema_normalization: Default
         paginator:
           type: NoPagination
@@ -927,7 +927,7 @@ definitions:
           name: ad_member_job_function_analytics
         requester:
           type: CustomRequester
-          class_name: "source_linkedin_ads.components.SafeEncodeHttpRequester"
+          class_name: source_declarative_manifest.components.SafeEncodeHttpRequester
           url_base: https://api.linkedin.com/rest/
           authenticator: "#/definitions/authenticator"
           path: adAnalytics
@@ -944,7 +944,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -952,7 +952,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
           schema_normalization: Default
         paginator:
           type: NoPagination
@@ -1006,7 +1006,7 @@ definitions:
           name: ad_member_job_title_analytics
         requester:
           type: CustomRequester
-          class_name: "source_linkedin_ads.components.SafeEncodeHttpRequester"
+          class_name: source_declarative_manifest.components.SafeEncodeHttpRequester
           url_base: https://api.linkedin.com/rest/
           authenticator: "#/definitions/authenticator"
           path: adAnalytics
@@ -1023,7 +1023,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -1031,7 +1031,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
           schema_normalization: Default
         paginator:
           type: NoPagination
@@ -1085,7 +1085,7 @@ definitions:
           name: ad_member_industry_analytics
         requester:
           type: CustomRequester
-          class_name: "source_linkedin_ads.components.SafeEncodeHttpRequester"
+          class_name: source_declarative_manifest.components.SafeEncodeHttpRequester
           url_base: https://api.linkedin.com/rest/
           authenticator: "#/definitions/authenticator"
           path: adAnalytics
@@ -1102,7 +1102,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -1110,7 +1110,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
           schema_normalization: Default
         paginator:
           type: NoPagination
@@ -1164,7 +1164,7 @@ definitions:
           name: ad_member_seniority_analytics
         requester:
           type: CustomRequester
-          class_name: "source_linkedin_ads.components.SafeEncodeHttpRequester"
+          class_name: source_declarative_manifest.components.SafeEncodeHttpRequester
           url_base: https://api.linkedin.com/rest/
           authenticator: "#/definitions/authenticator"
           path: adAnalytics
@@ -1181,7 +1181,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -1189,7 +1189,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
           schema_normalization: Default
         paginator:
           type: NoPagination
@@ -1243,7 +1243,7 @@ definitions:
           name: ad_member_region_analytics
         requester:
           type: CustomRequester
-          class_name: "source_linkedin_ads.components.SafeEncodeHttpRequester"
+          class_name: source_declarative_manifest.components.SafeEncodeHttpRequester
           url_base: https://api.linkedin.com/rest/
           authenticator: "#/definitions/authenticator"
           path: adAnalytics
@@ -1260,7 +1260,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -1268,7 +1268,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
           schema_normalization: Default
         paginator:
           type: NoPagination
@@ -1322,7 +1322,7 @@ definitions:
           name: ad_member_company_analytics
         requester:
           type: CustomRequester
-          class_name: "source_linkedin_ads.components.SafeEncodeHttpRequester"
+          class_name: source_declarative_manifest.components.SafeEncodeHttpRequester
           url_base: https://api.linkedin.com/rest/
           authenticator: "#/definitions/authenticator"
           path: adAnalytics
@@ -1339,7 +1339,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -1347,7 +1347,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
           schema_normalization: Default
         paginator:
           type: NoPagination
@@ -1402,7 +1402,7 @@ definitions:
           name: custom_analytics_report
         requester:
           type: CustomRequester
-          class_name: "source_linkedin_ads.components.SafeEncodeHttpRequester"
+          class_name: source_declarative_manifest.components.SafeEncodeHttpRequester
           url_base: https://api.linkedin.com/rest/
           authenticator: "#/definitions/authenticator"
           path: adAnalytics
@@ -1419,7 +1419,7 @@ definitions:
             X-Restli-Protocol-Version: 2.0.0
           error_handlers:
             type: CustomErrorHandler
-            class_name: "source_linkedin_ads.components.LinkedInAdsErrorHandler"
+            class_name: source_declarative_manifest.components.LinkedInAdsErrorHandler
             backoff_strategies:
               - type: ExponentialBackoffStrategy
                 factor: 5
@@ -1427,7 +1427,7 @@ definitions:
           type: RecordSelector
           extractor:
             type: CustomRecordExtractor
-            class_name: source_linkedin_ads.components.LinkedInAdsRecordExtractor
+            class_name: source_declarative_manifest.components.LinkedInAdsRecordExtractor
           schema_normalization: Default
         paginator:
           type: NoPagination
@@ -1660,6 +1660,21 @@ dynamic_streams:
               "value",
             ]
           value: "{{components_values['pivot_by']}}"
+
+# LinkedIn Ads does not publish their standard API rate limits.
+# https://learn.microsoft.com/en-us/linkedin/shared/api-guide/concepts/rate-limits
+# I looked into our test account to establish some baselines, but it seems like each endpoint
+# has its own individually tracked rate limit that resets daily. There are also tiers, but
+# there is not a good way of ascertaining what value are for each. There are 19 streams so
+# as a standard default will be set to 19 so each stream can at least run in parallel. This
+# could require some tuning.
+#
+# It is likely that certain customers may have a greater limit provisioned so allow for a max of 50.
+concurrency_level:
+  type: ConcurrencyLevel
+  default_concurrency: "19"
+  max_concurrency: 50
+
 spec:
   type: Spec
   connection_specification:
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/metadata.yaml b/airbyte-integrations/connectors/source-linkedin-ads/metadata.yaml
index 68bd73fa5b0b..e5724fb5f6af 100644
--- a/airbyte-integrations/connectors/source-linkedin-ads/metadata.yaml
+++ b/airbyte-integrations/connectors/source-linkedin-ads/metadata.yaml
@@ -7,11 +7,11 @@ data:
       - linkedin.com
       - api.linkedin.com
   connectorBuildOptions:
-    baseImage: docker.io/airbyte/python-connector-base:4.0.0@sha256:d9894b6895923b379f3006fa251147806919c62b7d9021b5cd125bb67d7bbe22
+    baseImage: docker.io/airbyte/source-declarative-manifest:6.45.10@sha256:76b1aff1fe90c41dc7a4fa8902c5cbc1a5f3a659140af4a0fd610c1a215166aa
   connectorSubtype: api
   connectorType: source
   definitionId: 137ece28-5434-455c-8f34-69dc3782f451
-  dockerImageTag: 5.4.1
+  dockerImageTag: 5.5.0-rc.1
   dockerRepository: airbyte/source-linkedin-ads
   documentationUrl: https://docs.airbyte.com/integrations/sources/linkedin-ads
   githubIssueLabel: source-linkedin-ads
@@ -21,7 +21,7 @@ data:
   name: LinkedIn Ads
   remoteRegistries:
     pypi:
-      enabled: true
+      enabled: false
       packageName: airbyte-source-linkedin-ads
   registryOverrides:
     cloud:
@@ -31,7 +31,7 @@ data:
   releaseStage: generally_available
   releases:
     rolloutConfiguration:
-      enableProgressiveRollout: false
+      enableProgressiveRollout: true
     breakingChanges:
       1.0.0:
         message: This upgrade brings changes in primary key to *-analytics streams.
@@ -104,8 +104,8 @@ data:
       - creatives
   supportLevel: certified
   tags:
-    - language:python
     - cdk:low-code
+    - language:manifest-only
   connectorTestSuitesOptions:
     - suite: liveTests
       testConnections:
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/pyproject.toml b/airbyte-integrations/connectors/source-linkedin-ads/pyproject.toml
deleted file mode 100644
index 1e2b75b428bd..000000000000
--- a/airbyte-integrations/connectors/source-linkedin-ads/pyproject.toml
+++ /dev/null
@@ -1,39 +0,0 @@
-[build-system]
-requires = [ "poetry-core>=1.0.0",]
-build-backend = "poetry.core.masonry.api"
-
-[tool.poetry]
-version = "5.4.1"
-name = "source-linkedin-ads"
-description = "Source implementation for Linkedin Ads."
-authors = [ "Airbyte <contact@airbyte.io>",]
-license = "MIT"
-readme = "README.md"
-documentation = "https://docs.airbyte.com/integrations/sources/linkedin-ads"
-homepage = "https://airbyte.com"
-repository = "https://github.com/airbytehq/airbyte"
-[[tool.poetry.packages]]
-include = "source_linkedin_ads"
-
-[tool.poetry.dependencies]
-python = "^3.10,<3.12"
-airbyte-cdk = "^6"
-coverage = "^7.5.3"
-pendulum = "<3.0.0"
-
-[tool.poetry.scripts]
-source-linkedin-ads = "source_linkedin_ads.run:run"
-
-[tool.poetry.group.dev.dependencies]
-pytest = "^8.0.0"
-freezegun = "==1.2.2"
-pytest-mock = "^3.6.1"
-requests-mock = "^1.11.0"
-
-
-[tool.poe]
-include = [
-    # Shared tasks definition file(s) can be imported here.
-    # Run `poe` or `poe --help` to see the list of available tasks.
-    "${POE_GIT_DIR}/poe-tasks/poetry-connector-tasks.toml",
-]
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/__init__.py b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/__init__.py
deleted file mode 100644
index 041b230d536a..000000000000
--- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/__init__.py
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# MIT License
-#
-# Copyright (c) 2020 Airbyte
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
-
-
-from .source import SourceLinkedinAds
-
-__all__ = ["SourceLinkedinAds"]
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/components.py b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/components.py
deleted file mode 100644
index d63f0282c549..000000000000
--- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/components.py
+++ /dev/null
@@ -1,156 +0,0 @@
-#
-# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
-#
-
-
-from dataclasses import dataclass
-from typing import Any, List, Mapping, MutableMapping, Optional, Union
-from urllib.parse import urlencode
-
-import pendulum
-import requests
-from requests.exceptions import InvalidURL
-
-from airbyte_cdk.models import FailureType
-from airbyte_cdk.sources.declarative.extractors.record_extractor import RecordExtractor
-from airbyte_cdk.sources.declarative.requesters import HttpRequester
-from airbyte_cdk.sources.declarative.requesters.error_handlers import DefaultErrorHandler
-from airbyte_cdk.sources.declarative.requesters.request_options.interpolated_request_options_provider import (
-    InterpolatedRequestOptionsProvider,
-    RequestInput,
-)
-from airbyte_cdk.sources.streams.http import HttpClient
-from airbyte_cdk.sources.streams.http.error_handlers import ErrorResolution, ResponseAction
-from airbyte_cdk.sources.streams.http.exceptions import DefaultBackoffException, RequestBodyException, UserDefinedBackoffException
-from airbyte_cdk.sources.streams.http.http import BODY_REQUEST_METHODS
-
-from .utils import transform_data
-
-
-class SafeHttpClient(HttpClient):
-    """
-    A custom HTTP client that safely validates query parameters, ensuring that the symbols ():,% are preserved
-    during UTF-8 encoding.
-    """
-
-    def _create_prepared_request(
-        self,
-        http_method: str,
-        url: str,
-        dedupe_query_params: bool = False,
-        headers: Optional[Mapping[str, str]] = None,
-        params: Optional[Mapping[str, str]] = None,
-        json: Optional[Mapping[str, Any]] = None,
-        data: Optional[Union[str, Mapping[str, Any]]] = None,
-    ) -> requests.PreparedRequest:
-        """
-        Prepares an HTTP request with optional deduplication of query parameters and safe encoding.
-        """
-        if dedupe_query_params:
-            query_params = self._dedupe_query_params(url, params)
-        else:
-            query_params = params or {}
-        query_params = urlencode(query_params, safe="():,%")
-        args = {"method": http_method, "url": url, "headers": headers, "params": query_params}
-        if http_method.upper() in BODY_REQUEST_METHODS:
-            if json and data:
-                raise RequestBodyException(
-                    "At the same time only one of the 'request_body_data' and 'request_body_json' functions can return data"
-                )
-            elif json:
-                args["json"] = json
-            elif data:
-                args["data"] = data
-        prepared_request: requests.PreparedRequest = self._session.prepare_request(requests.Request(**args))
-
-        return prepared_request
-
-
-@dataclass
-class SafeEncodeHttpRequester(HttpRequester):
-    """
-    A custom HTTP requester that ensures safe encoding of query parameters, preserving the symbols ():,% during UTF-8 encoding.
-    """
-
-    request_body_json: Optional[RequestInput] = None
-    request_headers: Optional[RequestInput] = None
-    request_parameters: Optional[RequestInput] = None
-    request_body_data: Optional[RequestInput] = None
-    query_properties_key: Optional[str] = None
-
-    def __post_init__(self, parameters: Mapping[str, Any]) -> None:
-        """
-        Initializes the request options provider with the provided parameters and any
-        configured request components like headers, parameters, or bodies.
-        """
-        self.request_options_provider = InterpolatedRequestOptionsProvider(
-            request_body_data=self.request_body_data,
-            request_body_json=self.request_body_json,
-            request_headers=self.request_headers,
-            request_parameters=self.request_parameters,
-            query_properties_key=self.query_properties_key,
-            config=self.config,
-            parameters=parameters or {},
-        )
-        super().__post_init__(parameters)
-
-        if self.error_handler is not None and hasattr(self.error_handler, "backoff_strategies"):
-            backoff_strategies = self.error_handler.backoff_strategies
-        else:
-            backoff_strategies = None
-
-        self._http_client = SafeHttpClient(
-            name=self.name,
-            logger=self.logger,
-            error_handler=self.error_handler,
-            authenticator=self._authenticator,
-            use_cache=self.use_cache,
-            backoff_strategy=backoff_strategies,
-            disable_retries=self.disable_retries,
-            message_repository=self.message_repository,
-        )
-
-
-@dataclass
-class LinkedInAdsRecordExtractor(RecordExtractor):
-    """
-    Extracts and transforms LinkedIn Ads records, ensuring that 'lastModified' and 'created'
-    date-time fields are formatted to RFC3339.
-    """
-
-    def _date_time_to_rfc3339(self, record: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
-        """
-        Converts 'lastModified' and 'created' fields in the record to RFC3339 format.
-        """
-        for item in ["lastModified", "created"]:
-            if record.get(item) is not None:
-                record[item] = pendulum.parse(record[item]).to_rfc3339_string()
-        return record
-
-    def extract_records(self, response: requests.Response) -> List[Mapping[str, Any]]:
-        """
-        Extracts and transforms records from an HTTP response.
-        """
-        for record in transform_data(response.json().get("elements")):
-            yield self._date_time_to_rfc3339(record)
-
-
-@dataclass
-class LinkedInAdsErrorHandler(DefaultErrorHandler):
-    """
-    An error handler for LinkedIn Ads that interprets responses, providing custom error resolutions
-    for specific exceptions like `InvalidURL`.
-    This is a temporary workaround untill we update this in the CDK. https://github.com/airbytehq/airbyte-internal-issues/issues/11320
-    """
-
-    def interpret_response(self, response_or_exception: Optional[Union[requests.Response, Exception]]) -> ErrorResolution:
-        """
-        Interprets responses and exceptions, providing custom error resolutions for specific exceptions.
-        """
-        if isinstance(response_or_exception, InvalidURL):
-            return ErrorResolution(
-                response_action=ResponseAction.RETRY,
-                failure_type=FailureType.transient_error,
-                error_message="source-linkedin-ads has faced a temporary DNS resolution issue. Retrying...",
-            )
-        return super().interpret_response(response_or_exception)
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/config_migrations.py b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/config_migrations.py
deleted file mode 100644
index 5bfbe9788132..000000000000
--- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/config_migrations.py
+++ /dev/null
@@ -1,78 +0,0 @@
-#
-# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
-#
-
-
-import logging
-from abc import ABC
-from typing import Any, List, Mapping
-
-from airbyte_cdk import AirbyteEntrypoint, Source, create_connector_config_control_message
-from airbyte_cdk.config_observation import emit_configuration_as_airbyte_control_message
-
-
-logger = logging.getLogger("airbyte_logger")
-
-
-class MigrateCredentials(ABC):
-    """
-    This class stands for migrating the config inside object `credentials`
-    """
-
-    @classmethod
-    def should_migrate(cls, config: Mapping[str, Any]) -> bool:
-        return "credentials" not in config
-
-    @classmethod
-    def migrate_config(cls, config: Mapping[str, Any]) -> Mapping[str, Any]:
-        config["credentials"] = {
-            "auth_method": "access_token",
-            "access_token": config.pop("access_token"),
-        }
-        return config
-
-    @classmethod
-    def modify_and_save(cls, config_path: str, source: Source, config: Mapping[str, Any]) -> Mapping[str, Any]:
-        """
-        Modifies the configuration and then saves it back to the source.
-
-        Args:
-        - config_path (str): The path where the configuration is stored.
-        - source (Source): The data source.
-        - config (Mapping[str, Any]): The current configuration.
-
-        Returns:
-        - Mapping[str, Any]: The updated configuration.
-        """
-        migrated_config = cls.migrate_config(config)
-        source.write_config(migrated_config, config_path)
-        return migrated_config
-
-    @classmethod
-    def emit_control_message(cls, migrated_config: Mapping[str, Any]) -> None:
-        """
-        Emits the control messages related to configuration migration.
-
-        Args:
-        - migrated_config (Mapping[str, Any]): The migrated configuration.
-        """
-        print(create_connector_config_control_message(migrated_config))
-
-    @classmethod
-    def migrate(cls, args: List[str], source: Source) -> None:
-        """
-        Orchestrates the configuration migration process.
-
-        It first checks if the `--config` argument is provided, and if so,
-        determines whether migration is needed, and then performs the migration
-        if required.
-
-        Args:
-        - args (List[str]): List of command-line arguments.
-        - source (Source): The data source.
-        """
-        config_path = AirbyteEntrypoint(source).extract_config(args)
-        if config_path:
-            config = source.read_config(config_path)
-            if cls.should_migrate(config):
-                emit_configuration_as_airbyte_control_message(cls.modify_and_save(config_path, source, config))
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/run.py b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/run.py
deleted file mode 100644
index 2290bbf7f73c..000000000000
--- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/run.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#
-# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
-#
-
-
-import sys
-import traceback
-from datetime import datetime
-from typing import List
-
-from orjson import orjson
-
-from airbyte_cdk.entrypoint import AirbyteEntrypoint, launch
-from airbyte_cdk.models import AirbyteErrorTraceMessage, AirbyteMessage, AirbyteMessageSerializer, AirbyteTraceMessage, TraceType, Type
-from source_linkedin_ads import SourceLinkedinAds
-from source_linkedin_ads.config_migrations import MigrateCredentials
-
-
-def _get_source(args: List[str]):
-    catalog_path = AirbyteEntrypoint.extract_catalog(args)
-    config_path = AirbyteEntrypoint.extract_config(args)
-    state_path = AirbyteEntrypoint.extract_state(args)
-    try:
-        return SourceLinkedinAds(
-            SourceLinkedinAds.read_catalog(catalog_path) if catalog_path else None,
-            SourceLinkedinAds.read_config(config_path) if config_path else None,
-            SourceLinkedinAds.read_state(state_path) if state_path else None,
-        )
-    except Exception as error:
-        print(
-            orjson.dumps(
-                AirbyteMessageSerializer.dump(
-                    AirbyteMessage(
-                        type=Type.TRACE,
-                        trace=AirbyteTraceMessage(
-                            type=TraceType.ERROR,
-                            emitted_at=int(datetime.now().timestamp() * 1000),
-                            error=AirbyteErrorTraceMessage(
-                                message=f"Error starting the sync. This could be due to an invalid configuration or catalog. Please contact Support for assistance. Error: {error}",
-                                stack_trace=traceback.format_exc(),
-                            ),
-                        ),
-                    )
-                )
-            ).decode()
-        )
-        return None
-
-
-def run():
-    _args = sys.argv[1:]
-    source = _get_source(_args)
-    MigrateCredentials.migrate(_args, source)
-    if source:
-        launch(source, _args)
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/source.py b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/source.py
deleted file mode 100644
index 8a6bcb568d40..000000000000
--- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/source.py
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
-#
-
-from typing import Any, Mapping, Optional
-
-from airbyte_cdk.models import ConfiguredAirbyteCatalog
-from airbyte_cdk.sources.declarative.yaml_declarative_source import YamlDeclarativeSource
-from airbyte_cdk.sources.source import TState
-
-
-"""
-This file provides the necessary constructs to interpret a provided declarative YAML configuration file into
-source connector.
-WARNING: Do not modify this file.
-"""
-
-
-# Declarative Source
-class SourceLinkedinAds(YamlDeclarativeSource):
-    def __init__(self, catalog: Optional[ConfiguredAirbyteCatalog], config: Optional[Mapping[str, Any]], state: TState, **kwargs):
-        super().__init__(catalog=catalog, config=config, state=state, **{"path_to_yaml": "manifest.yaml"})
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/spec.yaml b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/spec.yaml
deleted file mode 100644
index 296f8f2b36aa..000000000000
--- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/spec.yaml
+++ /dev/null
@@ -1,161 +0,0 @@
-connectionSpecification:
-  $schema: http://json-schema.org/draft-07/schema#
-  title: "Linkedin Ads Spec"
-  type: object
-  required:
-    - start_date
-  additionalProperties: true
-  properties:
-    credentials:
-      title: Authentication
-      type: object
-      oneOf:
-        - type: object
-          title: OAuth2.0
-          required:
-            - client_id
-            - client_secret
-            - refresh_token
-          properties:
-            auth_method:
-              type: string
-              const: oAuth2.0
-            client_id:
-              type: string
-              title: Client ID
-              description: "The client ID of your developer application. Refer to our <a href='https://docs.airbyte.com/integrations/sources/linkedin-ads#setup-guide'>documentation</a> for more information."
-              airbyte_secret: true
-            client_secret:
-              type: string
-              title: Client Secret
-              description: "The client secret of your developer application. Refer to our <a href='https://docs.airbyte.com/integrations/sources/linkedin-ads#setup-guide'>documentation</a> for more information."
-              airbyte_secret: true
-            refresh_token:
-              type: string
-              title: Refresh Token
-              description: "The key to refresh the expired access token. Refer to our <a href='https://docs.airbyte.com/integrations/sources/linkedin-ads#setup-guide'>documentation</a> for more information."
-              airbyte_secret: true
-        - title: Access Token
-          type: object
-          required:
-            - access_token
-          properties:
-            auth_method:
-              type: string
-              const: access_token
-            access_token:
-              type: string
-              title: Access Token
-              description: "The access token generated for your developer application. Refer to our <a href='https://docs.airbyte.com/integrations/sources/linkedin-ads#setup-guide'>documentation</a> for more information."
-              airbyte_secret: true
-    start_date:
-      type: string
-      title: Start Date
-      pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
-      pattern_descriptor: YYYY-MM-DD
-      description: "UTC date in the format YYYY-MM-DD. Any data before this date will not be replicated."
-      examples:
-        - "2021-05-17"
-      format: date
-    lookback_window:
-      type: integer
-      title: Lookback Window
-      default: 0
-      minimum: 0
-      description: How far into the past to look for records. (in days)
-    account_ids:
-      title: Account IDs
-      type: array
-      description: 'Specify the account IDs to pull data from, separated by a space. Leave this field empty if you want to pull the data from all accounts accessible by the authenticated user. See the <a href="https://www.linkedin.com/help/linkedin/answer/a424270/find-linkedin-ads-account-details?lang=en">LinkedIn docs</a> to locate these IDs.'
-      items:
-        type: integer
-      examples:
-        - 123456789
-      default: []
-    ad_analytics_reports:
-      title: Custom Ad Analytics Reports
-      type: array
-      items:
-        type: object
-        title: Ad Analytics Report Configuration
-        description: "Config for custom ad Analytics Report"
-        required:
-          - name
-          - pivot_by
-          - time_granularity
-        properties:
-          name:
-            title: Report Name
-            description: "The name for the custom report."
-            type: string
-          pivot_by:
-            title: Pivot Category
-            description: "Choose a category to pivot your analytics report around. This selection will organize your data based on the chosen attribute, allowing you to analyze trends and performance from different perspectives."
-            type: string
-            enum:
-              - COMPANY
-              - ACCOUNT
-              - SHARE
-              - CAMPAIGN
-              - CREATIVE
-              - CAMPAIGN_GROUP
-              - CONVERSION
-              - CONVERSATION_NODE
-              - CONVERSATION_NODE_OPTION_INDEX
-              - SERVING_LOCATION
-              - CARD_INDEX
-              - MEMBER_COMPANY_SIZE
-              - MEMBER_INDUSTRY
-              - MEMBER_SENIORITY
-              - MEMBER_JOB_TITLE
-              - MEMBER_JOB_FUNCTION
-              - MEMBER_COUNTRY_V2
-              - MEMBER_REGION_V2
-              - MEMBER_COMPANY
-              - PLACEMENT_NAME
-              - IMPRESSION_DEVICE_TYPE
-          time_granularity:
-            title: Time Granularity
-            description: "Choose how to group the data in your report by time. The options are:<br>- 'ALL': A single result summarizing the entire time range.<br>- 'DAILY': Group results by each day.<br>- 'MONTHLY': Group results by each month.<br>- 'YEARLY': Group results by each year.<br>Selecting a time grouping helps you analyze trends and patterns over different time periods."
-            type: string
-            enum:
-              - ALL
-              - DAILY
-              - MONTHLY
-              - YEARLY
-      default: []
-advanced_auth:
-  auth_flow_type: oauth2.0
-  predicate_key:
-    - credentials
-    - auth_method
-  predicate_value: oAuth2.0
-  oauth_config_specification:
-    complete_oauth_output_specification:
-      type: object
-      properties:
-        refresh_token:
-          type: string
-          path_in_connector_config:
-            - credentials
-            - refresh_token
-    complete_oauth_server_input_specification:
-      type: object
-      properties:
-        client_id:
-          type: string
-        client_secret:
-          type: string
-    complete_oauth_server_output_specification:
-      type: object
-      properties:
-        client_id:
-          type: string
-          path_in_connector_config:
-            - credentials
-            - client_id
-        client_secret:
-          type: string
-          path_in_connector_config:
-            - credentials
-            - client_secret
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/conftest.py b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/conftest.py
index 3f6a566f4c4d..fc51a4b7f159 100644
--- a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/conftest.py
+++ b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/conftest.py
@@ -4,21 +4,37 @@
 
 import json
 import os
+import sys
+from pathlib import Path
 from typing import Any, Mapping
 
-from source_linkedin_ads.source import SourceLinkedinAds
-
+from airbyte_cdk.sources.declarative.yaml_declarative_source import YamlDeclarativeSource
 from airbyte_cdk.test.catalog_builder import CatalogBuilder
 from airbyte_cdk.test.state_builder import StateBuilder
 
 
 os.environ["REQUEST_CACHE_PATH"] = "REQUEST_CACHE_PATH"
 
+pytest_plugins = ["airbyte_cdk.test.utils.manifest_only_fixtures"]
+
+
+def _get_manifest_path() -> Path:
+    source_declarative_manifest_path = Path("/airbyte/integration_code/source_declarative_manifest")
+    if source_declarative_manifest_path.exists():
+        return source_declarative_manifest_path
+    return Path(__file__).parent.parent
+
+
+_SOURCE_FOLDER_PATH = _get_manifest_path()
+_YAML_FILE_PATH = _SOURCE_FOLDER_PATH / "manifest.yaml"
+
+sys.path.append(str(_SOURCE_FOLDER_PATH))  # to allow loading custom components
+
 
-def get_source(config) -> SourceLinkedinAds:
+def get_source(config) -> YamlDeclarativeSource:
     catalog = CatalogBuilder().build()
     state = StateBuilder().build()
-    return SourceLinkedinAds(catalog, config, state)
+    return YamlDeclarativeSource(path_to_yaml=str(_YAML_FILE_PATH), catalog=catalog, config=config, state=state)
 
 
 def find_stream(stream_name, config):
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/poetry.lock b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/poetry.lock
similarity index 92%
rename from airbyte-integrations/connectors/source-linkedin-ads/poetry.lock
rename to airbyte-integrations/connectors/source-linkedin-ads/unit_tests/poetry.lock
index c9dd8eb64bb1..68417d020c66 100644
--- a/airbyte-integrations/connectors/source-linkedin-ads/poetry.lock
+++ b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/poetry.lock
@@ -2,17 +2,17 @@
 
 [[package]]
 name = "airbyte-cdk"
-version = "6.45.7"
+version = "6.45.10"
 description = "A framework for writing Airbyte Connectors."
 optional = false
 python-versions = "<3.13,>=3.10"
 files = [
-    {file = "airbyte_cdk-6.45.7-py3-none-any.whl", hash = "sha256:78efa1190203ade46ed7beca3fabdff6ed1947cae28e4ae2834dfbc6651e31da"},
-    {file = "airbyte_cdk-6.45.7.tar.gz", hash = "sha256:94c6d05c4a52493cd71b07353ac771f87d64f6b76e419d83371a6e3124220663"},
+    {file = "airbyte_cdk-6.45.10-py3-none-any.whl", hash = "sha256:245bcbb859497ed4c70e7c33c135152b01d6437c7444dfbd536a7d053e569c70"},
+    {file = "airbyte_cdk-6.45.10.tar.gz", hash = "sha256:0d2d939bd6283ac286bb485158236d897619406d749acd9a78429cbe485990ee"},
 ]
 
 [package.dependencies]
-airbyte-protocol-models-dataclasses = ">=0.14,<0.15"
+airbyte-protocol-models-dataclasses = ">=0.15,<0.16"
 anyascii = ">=0.3.2,<0.4.0"
 backoff = "*"
 boltons = ">=25.0.0,<26.0.0"
@@ -55,13 +55,13 @@ vector-db-based = ["cohere (==4.21)", "langchain (==0.1.16)", "openai[embeddings
 
 [[package]]
 name = "airbyte-protocol-models-dataclasses"
-version = "0.14.5"
+version = "0.15.0"
 description = "Declares the Airbyte Protocol using Python Dataclasses. Dataclasses in Python have less performance overhead compared to Pydantic models, making them a more efficient choice for scenarios where speed and memory usage are critical"
 optional = false
 python-versions = ">=3.9"
 files = [
-    {file = "airbyte_protocol_models_dataclasses-0.14.5-py3-none-any.whl", hash = "sha256:ff346861821ccb959909a41abec2768785c40f65c62ab027b97d4a3900894d52"},
-    {file = "airbyte_protocol_models_dataclasses-0.14.5.tar.gz", hash = "sha256:ce493580d178e5c18dcbf641ca6dcc6d2354d2545a19251974a5c6991dc837bd"},
+    {file = "airbyte_protocol_models_dataclasses-0.15.0-py3-none-any.whl", hash = "sha256:0fe8d7c2863c348b350efcf5f1af5872dc9071060408285e4708d97a9be5e2fb"},
+    {file = "airbyte_protocol_models_dataclasses-0.15.0.tar.gz", hash = "sha256:a5bad4ee7ae0a04f1436967b7afd3306d28e1cd2e5acedf0cce588f0c80ed001"},
 ]
 
 [[package]]
@@ -424,81 +424,6 @@ files = [
     {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
 ]
 
-[[package]]
-name = "coverage"
-version = "7.8.0"
-description = "Code coverage measurement for Python"
-optional = false
-python-versions = ">=3.9"
-files = [
-    {file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"},
-    {file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"},
-    {file = "coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3"},
-    {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676"},
-    {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d"},
-    {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a"},
-    {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c"},
-    {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f"},
-    {file = "coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f"},
-    {file = "coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23"},
-    {file = "coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27"},
-    {file = "coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea"},
-    {file = "coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7"},
-    {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040"},
-    {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543"},
-    {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2"},
-    {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318"},
-    {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9"},
-    {file = "coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c"},
-    {file = "coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78"},
-    {file = "coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc"},
-    {file = "coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6"},
-    {file = "coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d"},
-    {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05"},
-    {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a"},
-    {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6"},
-    {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47"},
-    {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe"},
-    {file = "coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545"},
-    {file = "coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b"},
-    {file = "coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd"},
-    {file = "coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00"},
-    {file = "coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64"},
-    {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067"},
-    {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008"},
-    {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733"},
-    {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323"},
-    {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3"},
-    {file = "coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d"},
-    {file = "coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487"},
-    {file = "coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25"},
-    {file = "coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42"},
-    {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502"},
-    {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1"},
-    {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4"},
-    {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73"},
-    {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a"},
-    {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883"},
-    {file = "coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada"},
-    {file = "coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257"},
-    {file = "coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f"},
-    {file = "coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a"},
-    {file = "coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82"},
-    {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814"},
-    {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c"},
-    {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd"},
-    {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4"},
-    {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899"},
-    {file = "coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f"},
-    {file = "coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3"},
-    {file = "coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd"},
-    {file = "coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7"},
-    {file = "coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501"},
-]
-
-[package.extras]
-toml = ["tomli"]
-
 [[package]]
 name = "cryptography"
 version = "44.0.2"
@@ -597,13 +522,13 @@ test = ["pytest (>=6)"]
 
 [[package]]
 name = "freezegun"
-version = "1.2.2"
+version = "1.5.1"
 description = "Let your Python tests travel through time"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 files = [
-    {file = "freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"},
-    {file = "freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446"},
+    {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"},
+    {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"},
 ]
 
 [package.dependencies]
@@ -834,7 +759,10 @@ files = [
 [package.dependencies]
 httpx = ">=0.23.0,<1"
 orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""}
-pydantic = {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}
+pydantic = [
+    {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""},
+    {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""},
+]
 requests = ">=2,<3"
 requests-toolbelt = ">=1.0.0,<2.0.0"
 
@@ -1111,6 +1039,7 @@ files = [
 numpy = [
     {version = ">=1.22.4", markers = "python_version < \"3.11\""},
     {version = ">=1.23.2", markers = "python_version == \"3.11\""},
+    {version = ">=1.26.0", markers = "python_version >= \"3.12\""},
 ]
 python-dateutil = ">=2.8.2"
 pytz = ">=2020.1"
@@ -1141,40 +1070,6 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d
 test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
 xml = ["lxml (>=4.9.2)"]
 
-[[package]]
-name = "pendulum"
-version = "2.1.2"
-description = "Python datetimes made easy"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-files = [
-    {file = "pendulum-2.1.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b6c352f4bd32dff1ea7066bd31ad0f71f8d8100b9ff709fb343f3b86cee43efe"},
-    {file = "pendulum-2.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:318f72f62e8e23cd6660dbafe1e346950281a9aed144b5c596b2ddabc1d19739"},
-    {file = "pendulum-2.1.2-cp35-cp35m-macosx_10_15_x86_64.whl", hash = "sha256:0731f0c661a3cb779d398803655494893c9f581f6488048b3fb629c2342b5394"},
-    {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3481fad1dc3f6f6738bd575a951d3c15d4b4ce7c82dce37cf8ac1483fde6e8b0"},
-    {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9702069c694306297ed362ce7e3c1ef8404ac8ede39f9b28b7c1a7ad8c3959e3"},
-    {file = "pendulum-2.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:fb53ffa0085002ddd43b6ca61a7b34f2d4d7c3ed66f931fe599e1a531b42af9b"},
-    {file = "pendulum-2.1.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:c501749fdd3d6f9e726086bf0cd4437281ed47e7bca132ddb522f86a1645d360"},
-    {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c807a578a532eeb226150d5006f156632df2cc8c5693d778324b43ff8c515dd0"},
-    {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2d1619a721df661e506eff8db8614016f0720ac171fe80dda1333ee44e684087"},
-    {file = "pendulum-2.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f888f2d2909a414680a29ae74d0592758f2b9fcdee3549887779cd4055e975db"},
-    {file = "pendulum-2.1.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e95d329384717c7bf627bf27e204bc3b15c8238fa8d9d9781d93712776c14002"},
-    {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4c9c689747f39d0d02a9f94fcee737b34a5773803a64a5fdb046ee9cac7442c5"},
-    {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1245cd0075a3c6d889f581f6325dd8404aca5884dea7223a5566c38aab94642b"},
-    {file = "pendulum-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:db0a40d8bcd27b4fb46676e8eb3c732c67a5a5e6bfab8927028224fbced0b40b"},
-    {file = "pendulum-2.1.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f5e236e7730cab1644e1b87aca3d2ff3e375a608542e90fe25685dae46310116"},
-    {file = "pendulum-2.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:de42ea3e2943171a9e95141f2eecf972480636e8e484ccffaf1e833929e9e052"},
-    {file = "pendulum-2.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7c5ec650cb4bec4c63a89a0242cc8c3cebcec92fcfe937c417ba18277d8560be"},
-    {file = "pendulum-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:33fb61601083f3eb1d15edeb45274f73c63b3c44a8524703dc143f4212bf3269"},
-    {file = "pendulum-2.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:29c40a6f2942376185728c9a0347d7c0f07905638c83007e1d262781f1e6953a"},
-    {file = "pendulum-2.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:94b1fc947bfe38579b28e1cccb36f7e28a15e841f30384b5ad6c5e31055c85d7"},
-    {file = "pendulum-2.1.2.tar.gz", hash = "sha256:b06a0ca1bfe41c990bbf0c029f0b6501a7f2ec4e38bfec730712015e8860f207"},
-]
-
-[package.dependencies]
-python-dateutil = ">=2.6,<3.0"
-pytzdata = ">=2020.1"
-
 [[package]]
 name = "platformdirs"
 version = "4.3.7"
@@ -1530,17 +1425,6 @@ files = [
     {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"},
 ]
 
-[[package]]
-name = "pytzdata"
-version = "2020.1"
-description = "The Olson timezone database for Python."
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-files = [
-    {file = "pytzdata-2020.1-py2.py3-none-any.whl", hash = "sha256:e1e14750bcf95016381e4d472bad004eef710f2d6417240904070b3d6654485f"},
-    {file = "pytzdata-2020.1.tar.gz", hash = "sha256:3efa13b335a00a8de1d345ae41ec78dd11c9f8807f522d39850f2dd828681540"},
-]
-
 [[package]]
 name = "pyyaml"
 version = "6.0.2"
@@ -2227,5 +2111,5 @@ files = [
 
 [metadata]
 lock-version = "2.0"
-python-versions = "^3.10,<3.12"
-content-hash = "53f3063b83360baf4b91ee16a18afa12cfe62d43b6b67f220348eebd9ae4b429"
+python-versions = "^3.10,<3.13"
+content-hash = "803c9ec1d40d56098baf7404c77ea58720f48274cc42a27d89bd4368a0db9da5"
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/pyproject.toml b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/pyproject.toml
new file mode 100644
index 000000000000..fc76cf0a8126
--- /dev/null
+++ b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/pyproject.toml
@@ -0,0 +1,19 @@
+[build-system]
+requires = ["poetry-core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
+[tool.poetry]
+name = "source-linkedin-ads-tests"
+version = "0.0.0"
+description = "Unit tests for source-linkedin-ads"
+authors = ["Airbyte <contact@airbyte.io>"]
+[tool.poetry.dependencies]
+python = "^3.10,<3.13"
+airbyte-cdk = "^6"
+pytest = "^8"
+freezegun = "^1.4.0"
+pytest-mock = "^3.6.1"
+requests-mock = "^1.12.1"
+[tool.pytest.ini_options]
+filterwarnings = [
+    "ignore:This class is experimental*"
+]
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/samples/test_data_for_tranform.py
similarity index 100%
rename from airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/samples/test_data_for_tranform.py
rename to airbyte-integrations/connectors/source-linkedin-ads/unit_tests/samples/test_data_for_tranform.py
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_components.py b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_components.py
index 74392913cce1..767231963b53 100644
--- a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_components.py
+++ b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_components.py
@@ -3,17 +3,11 @@
 #
 
 import logging
-from datetime import datetime, timedelta
 from unittest.mock import MagicMock
 
 import pytest
-from requests import Response, Session
+from requests import Response
 from requests.models import PreparedRequest
-from source_linkedin_ads.components import (
-    LinkedInAdsRecordExtractor,
-    SafeEncodeHttpRequester,
-    SafeHttpClient,
-)
 
 
 logger = logging.getLogger("airbyte")
@@ -47,7 +41,9 @@ def mock_retriever_params():
     return {"requester": MagicMock(), "record_selector": MagicMock(), "config": MagicMock(), "parameters": MagicMock()}
 
 
-def test_safe_http_client_create_prepared_request():
+def test_safe_http_client_create_prepared_request(components_module):
+    SafeHttpClient = components_module.SafeHttpClient
+
     client = SafeHttpClient(name="test_client", logger=logger)
 
     http_method = "GET"
@@ -64,7 +60,10 @@ def test_safe_http_client_create_prepared_request():
     assert "key1=value1&key2=value2" in prepared_request.url
 
 
-def test_safe_encode_http_requester_post_init():
+def test_safe_encode_http_requester_post_init(components_module):
+    SafeHttpClient = components_module.SafeHttpClient
+    SafeEncodeHttpRequester = components_module.SafeEncodeHttpRequester
+
     parameters = {"param1": "value1"}
     requester = SafeEncodeHttpRequester(
         name="test_requester",
@@ -79,11 +78,31 @@ def test_safe_encode_http_requester_post_init():
     assert isinstance(requester._http_client, SafeHttpClient)
 
 
-def test_linkedin_ads_record_extractor_extract_records(mock_response):
+def test_linkedin_ads_record_extractor_extract_records(components_module, mock_response):
+    LinkedInAdsRecordExtractor = components_module.LinkedInAdsRecordExtractor
+
+    expected_records = [
+        {"lastModified": "2024-09-01T00:00:00+00:00", "created": "2024-08-01T00:00:00+00:00", "data": "value1"},
+        {"lastModified": "2024-09-02T00:00:00+00:00", "created": "2024-08-02T00:00:00+00:00", "data": "value2"},
+    ]
+
     extractor = LinkedInAdsRecordExtractor()
     records = list(extractor.extract_records(response=mock_response))
 
     assert len(records) == 2
-    for record in records:
-        assert "lastModified" in record
-        assert "created" in record
+    for i, record in enumerate(records):
+        assert record["lastModified"] == expected_records[i]["lastModified"]
+        assert record["created"] == expected_records[i]["created"]
+
+
+def test_date_str_from_date_range(components_module):
+    transform_date_range = components_module.transform_date_range
+
+    expected_start_date = "2021-08-13"
+    expected_end_date = "2021-08-30"
+
+    record = {"dateRange": {"start": {"month": 8, "day": 13, "year": 2021}, "end": {"month": 8, "day": 30, "year": 2021}}}
+    transformed_record = transform_date_range(record)
+
+    assert transformed_record["start_date"] == expected_start_date
+    assert transformed_record["end_date"] == expected_end_date
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_config_migration.py b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_config_migration.py
deleted file mode 100644
index e16bcbf48d7b..000000000000
--- a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_config_migration.py
+++ /dev/null
@@ -1,18 +0,0 @@
-#
-# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
-#
-
-import os
-
-from conftest import get_source, load_config
-from source_linkedin_ads.config_migrations import MigrateCredentials
-
-
-def test_credentials_config_migration():
-    config_path = f"{os.path.dirname(__file__)}/test_configs/test_config_without_credentials.json"
-    initial_config = load_config(config_path)
-    migration_instance = MigrateCredentials
-    source = get_source(initial_config)
-    migration_instance.migrate(["check", "--config", config_path], source)
-    test_migrated_config = load_config(config_path)
-    assert test_migrated_config["credentials"]["access_token"] == initial_config["access_token"]
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_source.py b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_source.py
index f5aba949b88d..9cdbbcbf507e 100644
--- a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_source.py
+++ b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_source.py
@@ -8,17 +8,17 @@
 import pytest
 import requests
 from conftest import find_stream, get_source, load_json_file
-from source_linkedin_ads.source import SourceLinkedinAds
 
 from airbyte_cdk.models import SyncMode
 from airbyte_cdk.sources.declarative.manifest_declarative_source import ManifestDeclarativeSource
 from airbyte_cdk.sources.streams import Stream
 from airbyte_cdk.sources.streams.http.exceptions import DefaultBackoffException
-from airbyte_cdk.sources.streams.http.requests_native_auth import Oauth2Authenticator, TokenAuthenticator
+from airbyte_cdk.sources.streams.http.requests_native_auth import TokenAuthenticator
 
 
 logger = logging.getLogger("airbyte")
 
+
 LINKEDIN_VERSION_API = 202404
 
 TEST_OAUTH_CONFIG: dict = {
@@ -60,10 +60,6 @@
 
 
 class TestAllStreams:
-    @pytest.fixture
-    def linkedin_source(self) -> SourceLinkedinAds:
-        return get_source(TEST_CONFIG)
-
     @staticmethod
     def _mock_initialize_cache_for_parent_streams(stream_configs: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
         parent_streams = set()
@@ -179,7 +175,7 @@ def test_path(self, stream_name, expected):
             (200, True, None),
         ),
     )
-    def test_check_connection(self, requests_mock, linkedin_source, status_code, is_connection_successful, error_msg, mocker):
+    def test_check_connection(self, requests_mock, status_code, is_connection_successful, error_msg, mocker):
         mocker.patch.object(
             ManifestDeclarativeSource, "_initialize_cache_for_parent_streams", side_effect=self._mock_initialize_cache_for_parent_streams
         )
@@ -191,7 +187,7 @@ def test_check_connection(self, requests_mock, linkedin_source, status_code, is_
             status_code=status_code,
             json=json,
         )
-        success, error = linkedin_source.check_connection(logger=logger, config=TEST_CONFIG)
+        success, error = get_source(config=TEST_CONFIG).check_connection(logger=logger, config=TEST_CONFIG)
         assert success is is_connection_successful
         assert error == error_msg
 
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/test_transform_data.py b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_transform_data.py
similarity index 78%
rename from airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/test_transform_data.py
rename to airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_transform_data.py
index 17df3d545d4e..02ce06da1adc 100644
--- a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/test_transform_data.py
+++ b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_transform_data.py
@@ -3,12 +3,12 @@
 #
 
 from samples.test_data_for_tranform import input_test_data, output_test_data
-from source_linkedin_ads.utils import transform_data
 
 
-def test_transform_data():
+def test_transform_data(components_module):
     """
     As far as we transform the data within the generator object,
     we use list() to have the actual output for the test assertion.
     """
+    transform_data = components_module.transform_data
     assert list(transform_data(input_test_data)) == output_test_data
diff --git a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/test_update_specific_key.py b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/test_update_specific_key.py
deleted file mode 100644
index c62676cfc0ff..000000000000
--- a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/utils_tests/test_update_specific_key.py
+++ /dev/null
@@ -1,82 +0,0 @@
-#
-# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
-#
-
-
-import pytest
-from source_linkedin_ads.utils import update_specific_key
-
-
-@pytest.mark.parametrize(
-    "target_dict, target_key, target_value, condition_func, excluded_keys, expected_output",
-    [
-        (
-            {"key1": "value1", "key2": "value2"},
-            "key1",
-            "new_value1",
-            None,
-            None,
-            {"key1": "new_value1", "key2": "value2"},
-        ),
-        (
-            {"key1": "value1", "key2": "value2"},
-            "key1",
-            "new_value1",
-            None,
-            ["key1"],
-            {"key1": "value1", "key2": "value2"},
-        ),
-        (
-            {"key1": "value1", "key2": "value2"},
-            "key1",
-            "new_value1",
-            lambda x: x["key2"] == "value2",
-            None,
-            {"key1": "new_value1", "key2": "value2"},
-        ),
-        (
-            {"outer_key": {"key1": "value1", "key2": "value2"}},
-            "key1",
-            "new_value1",
-            None,
-            None,
-            {"outer_key": {"key1": "new_value1", "key2": "value2"}},
-        ),
-        (
-            {"key_list": [{"key1": "value1"}, {"key1": "value2"}]},
-            "key1",
-            "new_value1",
-            None,
-            None,
-            {"key_list": [{"key1": "new_value1"}, {"key1": "new_value1"}]},
-        ),
-        (
-            {"outer_key": {"key1": "value1", "key2": "value2"}},
-            "key1",
-            "new_value1",
-            None,
-            ["key1"],
-            {"outer_key": {"key1": "value1", "key2": "value2"}},
-        ),
-        (
-            {"key_list": [{"key1": "value1"}, "string", {"key1": "value2"}]},
-            "key1",
-            "new_value1",
-            None,
-            None,
-            {"key_list": [{"key1": "new_value1"}, "string", {"key1": "new_value1"}]},
-        ),
-    ],
-    ids=[
-        "simple_dictionary_update",
-        "exclude_key_from_update",
-        "condition_function_update",
-        "nested_dictionary_update",
-        "list_of_dictionaries_update",
-        "excluded_key_in_nested_dict",
-        "nested_list_with_mixed_types",
-    ],
-)
-def test_update_specific_key(target_dict, target_key, target_value, condition_func, excluded_keys, expected_output):
-    result = update_specific_key(target_dict, target_key, target_value, condition_func, excluded_keys)
-    assert result == expected_output
diff --git a/docs/integrations/sources/linkedin-ads.md b/docs/integrations/sources/linkedin-ads.md
index 9ee25dbdc7b9..ebcba8731cc3 100644
--- a/docs/integrations/sources/linkedin-ads.md
+++ b/docs/integrations/sources/linkedin-ads.md
@@ -200,90 +200,91 @@ No workaround has been identified to manage this issue as of 2025, February.
 <details>
   <summary>Expand to review</summary>
 
-| Version | Date       | Pull Request                                             | Subject                                                                                                                                                                |
-|:--------|:-----------|:---------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| 5.4.1 | 2025-04-23 | [58134](https://github.com/airbytehq/airbyte/pull/58134) | Fix to properly retrieve `approximateMemberReach` for `adAnalytics` streams following `v5.3.3`. |
-| 5.4.0 | 2025-04-22 | [58593](https://github.com/airbytehq/airbyte/pull/58593) | Promoting release candidate 5.4.0-rc.1 to a main version. |
-| 5.4.0-rc.1 | 2025-04-18 | [58114](https://github.com/airbytehq/airbyte/pull/58114)     | Removes custom retrievers and cursors from analytics streams so that they can take up concurrency.                                                                     |
-| 5.3.3   | 2025-03-12 | [55724](https://github.com/airbytehq/airbyte/pull/55724) | Update outdated schema `approximateUniqueImpressions` to new `approximateMemberReach` for `adAnalytics` streams.                                                       |
-| 5.3.2   | 2025-03-08 | [55447](https://github.com/airbytehq/airbyte/pull/55447) | Update dependencies                                                                                                                                                    |
-| 5.3.1   | 2025-03-05 | [55211](https://github.com/airbytehq/airbyte/pull/55211) | Update dependencies                                                                                                                                                    |
-| 5.3.0   | 2025-03-02 | [55171](https://github.com/airbytehq/airbyte/pull/55171) | Migrate API to v202502                                                                                                                                                 |
-| 5.2.3   | 2025-03-01 | [54813](https://github.com/airbytehq/airbyte/pull/54813) | Update dependencies                                                                                                                                                    |
-| 5.2.2   | 2025-02-22 | [53308](https://github.com/airbytehq/airbyte/pull/53308) | Update dependencies                                                                                                                                                    |
-| 5.2.1   | 2025-02-10 | [53611](https://github.com/airbytehq/airbyte/pull/53611) | Add schema precisions for `Lead forms` and `Lead form responses` streams                                                                                               |
-| 5.2.0   | 2025-02-04 | [52047](https://github.com/airbytehq/airbyte/pull/52047) | Add `Lead forms` and `Lead form responses` new streams - See Limits & considerations                                                                                   |
-| 5.1.6   | 2025-02-02 | [49458](https://github.com/airbytehq/airbyte/pull/49458) | Update Linkedin Ads API version to 202410 for creatives stream                                                                                                         |
-| 5.1.5   | 2025-02-01 | [52791](https://github.com/airbytehq/airbyte/pull/52791) | Update dependencies                                                                                                                                                    |
-| 5.1.4   | 2025-01-30 | [52604](https://github.com/airbytehq/airbyte/pull/52604) | Fix state error                                                                                                                                                        |
-| 5.1.3   | 2025-01-22 | [52604](https://github.com/airbytehq/airbyte/pull/52604) | Update CDK to production ^6                                                                                                                                            |
-| 5.1.2   | 2025-01-25 | [52253](https://github.com/airbytehq/airbyte/pull/52253) | Update dependencies                                                                                                                                                    |
-| 5.1.1   | 2025-01-15 | [47092](https://github.com/airbytehq/airbyte/pull/47092) | Starting with this version, the Docker image is now rootless. Please note that this and future versions will not be compatible with Airbyte versions earlier than 0.64 |
-| 5.1.0   | 2025-01-14 | [48863](https://github.com/airbytehq/airbyte/pull/48863) | Custom streams moved to manifest implementation & URL Error Handling                                                                                                   |
-| 5.0.0   | 2024-11-26 | [48451](https://github.com/airbytehq/airbyte/pull/48451) | Update primary keys for streams ad_campaign_analytics, Custom Ad Analytics Reports and account_users                                                                   |
-| 4.1.4   | 2024-10-12 | [46862](https://github.com/airbytehq/airbyte/pull/46862) | Update dependencies                                                                                                                                                    |
-| 4.1.3   | 2024-10-05 | [46433](https://github.com/airbytehq/airbyte/pull/46433) | Update dependencies                                                                                                                                                    |
-| 4.1.2   | 2024-09-28 | [46171](https://github.com/airbytehq/airbyte/pull/46171) | Update dependencies                                                                                                                                                    |
-| 4.1.1   | 2024-09-21 | [45774](https://github.com/airbytehq/airbyte/pull/45774) | Update dependencies                                                                                                                                                    |
-| 4.1.0   | 2024-09-20 | [44370](https://github.com/airbytehq/airbyte/pull/44370) | Migrate to low-code                                                                                                                                                    |
-| 4.0.6   | 2024-09-14 | [45532](https://github.com/airbytehq/airbyte/pull/45532) | Update dependencies                                                                                                                                                    |
-| 4.0.5   | 2024-09-07 | [45209](https://github.com/airbytehq/airbyte/pull/45209) | Update dependencies                                                                                                                                                    |
-| 4.0.4   | 2024-08-31 | [44950](https://github.com/airbytehq/airbyte/pull/44950) | Update dependencies                                                                                                                                                    |
-| 4.0.3   | 2024-08-24 | [44682](https://github.com/airbytehq/airbyte/pull/44682) | Update dependencies                                                                                                                                                    |
-| 4.0.2   | 2024-08-17 | [44220](https://github.com/airbytehq/airbyte/pull/44220) | Update dependencies                                                                                                                                                    |
-| 4.0.1   | 2024-08-10 | [43629](https://github.com/airbytehq/airbyte/pull/43629) | Update dependencies                                                                                                                                                    |
-| 4.0.0   | 2024-08-07 | [43359](https://github.com/airbytehq/airbyte/pull/43359) | Revert low code migration                                                                                                                                              |
-| 3.0.1   | 2024-08-03 | [43087](https://github.com/airbytehq/airbyte/pull/43087) | Update dependencies                                                                                                                                                    |
-| 3.0.0   | 2024-06-18 | [38314](https://github.com/airbytehq/airbyte/pull/38314) | Migrate to low-code                                                                                                                                                    |
-| 2.1.12  | 2024-07-27 | [42728](https://github.com/airbytehq/airbyte/pull/42728) | Update dependencies                                                                                                                                                    |
-| 2.1.11  | 2024-07-20 | [42291](https://github.com/airbytehq/airbyte/pull/42291) | Update dependencies                                                                                                                                                    |
-| 2.1.10  | 2024-07-13 | [41710](https://github.com/airbytehq/airbyte/pull/41710) | Update dependencies                                                                                                                                                    |
-| 2.1.9   | 2024-07-10 | [41517](https://github.com/airbytehq/airbyte/pull/41517) | Update dependencies                                                                                                                                                    |
-| 2.1.8   | 2024-07-09 | [41315](https://github.com/airbytehq/airbyte/pull/41315) | Update dependencies                                                                                                                                                    |
-| 2.1.7   | 2024-07-06 | [40868](https://github.com/airbytehq/airbyte/pull/40868) | Update dependencies                                                                                                                                                    |
-| 2.1.6   | 2024-06-25 | [40331](https://github.com/airbytehq/airbyte/pull/40331) | Update dependencies                                                                                                                                                    |
-| 2.1.5   | 2024-06-22 | [39998](https://github.com/airbytehq/airbyte/pull/39998) | Update dependencies                                                                                                                                                    |
-| 2.1.4   | 2024-06-16 | [39442](https://github.com/airbytehq/airbyte/pull/39442) | Fix README commands, change spec from json to yaml, fix schema states to object                                                                                        |
-| 2.1.3   | 2024-06-06 | [39240](https://github.com/airbytehq/airbyte/pull/39240) | [autopull] Upgrade base image to v1.2.2                                                                                                                                |
-| 2.1.2   | 2024-05-07 | [36648](https://github.com/airbytehq/airbyte/pull/36648) | Schema descriptions                                                                                                                                                    |
-| 2.1.1   | 2024-05-07 | [38013](https://github.com/airbytehq/airbyte/pull/38013) | Fix an issue where the `Accounts` stream did not correctly handle provided account IDs                                                                                 |
-| 2.1.0   | 2024-04-30 | [37573](https://github.com/airbytehq/airbyte/pull/37573) | Update API version to `202404`; add cursor-based pagination                                                                                                            |
-| 2.0.0   | 2024-04-24 | [37531](https://github.com/airbytehq/airbyte/pull/37531) | Change primary key for Analytics Streams                                                                                                                               |
-| 1.0.1   | 2024-03-28 | [34152](https://github.com/airbytehq/airbyte/pull/34152) | Proceed pagination if return less than expected                                                                                                                        |
-| 1.0.0   | 2024-04-10 | [36927](https://github.com/airbytehq/airbyte/pull/36927) | Update primary key for Analytics Streams                                                                                                                               |
-| 0.8.0   | 2024-03-19 | [36267](https://github.com/airbytehq/airbyte/pull/36267) | Pin airbyte-cdk version to `^0`                                                                                                                                        |
-| 0.7.0   | 2024-02-20 | [35465](https://github.com/airbytehq/airbyte/pull/35465) | Per-error reporting and continue sync on stream failures                                                                                                               |
-| 0.6.8   | 2024-02-09 | [35086](https://github.com/airbytehq/airbyte/pull/35086) | Manage dependencies with Poetry                                                                                                                                        |
-| 0.6.7   | 2024-01-11 | [34152](https://github.com/airbytehq/airbyte/pull/34152) | Prepare for airbyte-lib                                                                                                                                                |
-| 0.6.6   | 2024-01-15 | [34222](https://github.com/airbytehq/airbyte/pull/34222) | Use stream slices for Analytics streams                                                                                                                                |
-| 0.6.5   | 2023-12-15 | [33530](https://github.com/airbytehq/airbyte/pull/33530) | Fix typo in `Pivot Category` list                                                                                                                                      |
-| 0.6.4   | 2023-10-19 | [31599](https://github.com/airbytehq/airbyte/pull/31599) | Base image migration: remove Dockerfile and use the python-connector-base image                                                                                        |
-| 0.6.3   | 2023-10-13 | [31396](https://github.com/airbytehq/airbyte/pull/31396) | Fix pagination for reporting                                                                                                                                           |
-| 0.6.2   | 2023-08-23 | [31221](https://github.com/airbytehq/airbyte/pull/31221) | Increase max time between messages to 24 hours                                                                                                                         |
-| 0.6.1   | 2023-08-23 | [29600](https://github.com/airbytehq/airbyte/pull/29600) | Update field descriptions                                                                                                                                              |
-| 0.6.0   | 2023-08-22 | [29721](https://github.com/airbytehq/airbyte/pull/29721) | Add `Conversions` stream                                                                                                                                               |
-| 0.5.0   | 2023-08-14 | [29175](https://github.com/airbytehq/airbyte/pull/29175) | Add Custom report Constructor                                                                                                                                          |
-| 0.4.0   | 2023-08-08 | [29175](https://github.com/airbytehq/airbyte/pull/29175) | Add analytics streams                                                                                                                                                  |
-| 0.3.1   | 2023-08-08 | [29189](https://github.com/airbytehq/airbyte/pull/29189) | Fix empty accounts field                                                                                                                                               |
-| 0.3.0   | 2023-08-07 | [29045](https://github.com/airbytehq/airbyte/pull/29045) | Add new fields to schemas; convert datetime fields to `rfc3339`                                                                                                        |
-| 0.2.1   | 2023-05-30 | [26780](https://github.com/airbytehq/airbyte/pull/26780) | Reduce records limit for Creatives Stream                                                                                                                              |
-| 0.2.0   | 2023-05-23 | [26372](https://github.com/airbytehq/airbyte/pull/26372) | Migrate to LinkedIn API version: May 2023                                                                                                                              |
-| 0.1.16  | 2023-05-24 | [26512](https://github.com/airbytehq/airbyte/pull/26512) | Removed authSpecification from spec.json in favour of advancedAuth                                                                                                     |
-| 0.1.15  | 2023-02-13 | [22940](https://github.com/airbytehq/airbyte/pull/22940) | Specified date formatting in specification                                                                                                                             |
-| 0.1.14  | 2023-02-03 | [22361](https://github.com/airbytehq/airbyte/pull/22361) | Turn on default HttpAvailabilityStrategy                                                                                                                               |
-| 0.1.13  | 2023-01-27 | [22013](https://github.com/airbytehq/airbyte/pull/22013) | For adDirectSponsoredContents stream skip accounts which are part of organization                                                                                      |
-| 0.1.12  | 2022-10-18 | [18111](https://github.com/airbytehq/airbyte/pull/18111) | For adDirectSponsoredContents stream skip accounts which are part of organization                                                                                      |
-| 0.1.11  | 2022-10-07 | [17724](https://github.com/airbytehq/airbyte/pull/17724) | Retry 429/5xx errors when refreshing access token                                                                                                                      |
-| 0.1.10  | 2022-09-28 | [17326](https://github.com/airbytehq/airbyte/pull/17326) | Migrate to per-stream states.                                                                                                                                          |
-| 0.1.9   | 2022-07-21 | [14924](https://github.com/airbytehq/airbyte/pull/14924) | Remove `additionalProperties` field from schemas                                                                                                                       |
-| 0.1.8   | 2022-06-07 | [13495](https://github.com/airbytehq/airbyte/pull/13495) | Fixed `base-normalization` issue on `Destination Redshift` caused by wrong casting of `pivot` column                                                                   |
-| 0.1.7   | 2022-05-04 | [12482](https://github.com/airbytehq/airbyte/pull/12482) | Update input configuration copy                                                                                                                                        |
-| 0.1.6   | 2022-04-04 | [11690](https://github.com/airbytehq/airbyte/pull/11690) | Small documentation corrections                                                                                                                                        |
-| 0.1.5   | 2021-12-21 | [8984](https://github.com/airbytehq/airbyte/pull/8984)   | Update connector fields title/description                                                                                                                              |
-| 0.1.4   | 2021-12-02 | [8382](https://github.com/airbytehq/airbyte/pull/8382)   | Modify log message in rate-limit cases                                                                                                                                 |
-| 0.1.3   | 2021-11-11 | [7839](https://github.com/airbytehq/airbyte/pull/7839)   | Added OAuth support                                                                                                                                                    |
-| 0.1.2   | 2021-11-08 | [7499](https://github.com/airbytehq/airbyte/pull/7499)   | Remove base-python dependencies                                                                                                                                        |
-| 0.1.1   | 2021-10-02 | [6610](https://github.com/airbytehq/airbyte/pull/6610)   | Fix for `Campaigns/targetingCriteria` transformation, coerced `Creatives/variables/values` to string by default                                                        |
-| 0.1.0   | 2021-09-05 | [5285](https://github.com/airbytehq/airbyte/pull/5285)   | Initial release of Native LinkedIn Ads connector for Airbyte                                                                                                           |
+| Version    | Date       | Pull Request                                             | Subject                                                                                                                                                                |
+|:-----------|:-----------|:---------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| 5.5.0-rc.1 | 2025-04-25 | [58628](https://github.com/airbytehq/airbyte/pull/58628)     | Convert to manifest-only format                                                                                                                                        |
+| 5.4.1      | 2025-04-23 | [58134](https://github.com/airbytehq/airbyte/pull/58134) | Fix to properly retrieve `approximateMemberReach` for `adAnalytics` streams following `v5.3.3`.                                                                        |
+| 5.4.0      | 2025-04-22 | [58593](https://github.com/airbytehq/airbyte/pull/58593) | Promoting release candidate 5.4.0-rc.1 to a main version.                                                                                                              |
+| 5.4.0-rc.1 | 2025-04-18 | [58114](https://github.com/airbytehq/airbyte/pull/58114) | Removes custom retrievers and cursors from analytics streams so that they can take up concurrency.                                                                     |
+| 5.3.3      | 2025-03-12 | [55724](https://github.com/airbytehq/airbyte/pull/55724) | Update outdated schema `approximateUniqueImpressions` to new `approximateMemberReach` for `adAnalytics` streams.                                                       |
+| 5.3.2      | 2025-03-08 | [55447](https://github.com/airbytehq/airbyte/pull/55447) | Update dependencies                                                                                                                                                    |
+| 5.3.1      | 2025-03-05 | [55211](https://github.com/airbytehq/airbyte/pull/55211) | Update dependencies                                                                                                                                                    |
+| 5.3.0      | 2025-03-02 | [55171](https://github.com/airbytehq/airbyte/pull/55171) | Migrate API to v202502                                                                                                                                                 |
+| 5.2.3      | 2025-03-01 | [54813](https://github.com/airbytehq/airbyte/pull/54813) | Update dependencies                                                                                                                                                    |
+| 5.2.2      | 2025-02-22 | [53308](https://github.com/airbytehq/airbyte/pull/53308) | Update dependencies                                                                                                                                                    |
+| 5.2.1      | 2025-02-10 | [53611](https://github.com/airbytehq/airbyte/pull/53611) | Add schema precisions for `Lead forms` and `Lead form responses` streams                                                                                               |
+| 5.2.0      | 2025-02-04 | [52047](https://github.com/airbytehq/airbyte/pull/52047) | Add `Lead forms` and `Lead form responses` new streams - See Limits & considerations                                                                                   |
+| 5.1.6      | 2025-02-02 | [49458](https://github.com/airbytehq/airbyte/pull/49458) | Update Linkedin Ads API version to 202410 for creatives stream                                                                                                         |
+| 5.1.5      | 2025-02-01 | [52791](https://github.com/airbytehq/airbyte/pull/52791) | Update dependencies                                                                                                                                                    |
+| 5.1.4      | 2025-01-30 | [52604](https://github.com/airbytehq/airbyte/pull/52604) | Fix state error                                                                                                                                                        |
+| 5.1.3      | 2025-01-22 | [52604](https://github.com/airbytehq/airbyte/pull/52604) | Update CDK to production ^6                                                                                                                                            |
+| 5.1.2      | 2025-01-25 | [52253](https://github.com/airbytehq/airbyte/pull/52253) | Update dependencies                                                                                                                                                    |
+| 5.1.1      | 2025-01-15 | [47092](https://github.com/airbytehq/airbyte/pull/47092) | Starting with this version, the Docker image is now rootless. Please note that this and future versions will not be compatible with Airbyte versions earlier than 0.64 |
+| 5.1.0      | 2025-01-14 | [48863](https://github.com/airbytehq/airbyte/pull/48863) | Custom streams moved to manifest implementation & URL Error Handling                                                                                                   |
+| 5.0.0      | 2024-11-26 | [48451](https://github.com/airbytehq/airbyte/pull/48451) | Update primary keys for streams ad_campaign_analytics, Custom Ad Analytics Reports and account_users                                                                   |
+| 4.1.4      | 2024-10-12 | [46862](https://github.com/airbytehq/airbyte/pull/46862) | Update dependencies                                                                                                                                                    |
+| 4.1.3      | 2024-10-05 | [46433](https://github.com/airbytehq/airbyte/pull/46433) | Update dependencies                                                                                                                                                    |
+| 4.1.2      | 2024-09-28 | [46171](https://github.com/airbytehq/airbyte/pull/46171) | Update dependencies                                                                                                                                                    |
+| 4.1.1      | 2024-09-21 | [45774](https://github.com/airbytehq/airbyte/pull/45774) | Update dependencies                                                                                                                                                    |
+| 4.1.0      | 2024-09-20 | [44370](https://github.com/airbytehq/airbyte/pull/44370) | Migrate to low-code                                                                                                                                                    |
+| 4.0.6      | 2024-09-14 | [45532](https://github.com/airbytehq/airbyte/pull/45532) | Update dependencies                                                                                                                                                    |
+| 4.0.5      | 2024-09-07 | [45209](https://github.com/airbytehq/airbyte/pull/45209) | Update dependencies                                                                                                                                                    |
+| 4.0.4      | 2024-08-31 | [44950](https://github.com/airbytehq/airbyte/pull/44950) | Update dependencies                                                                                                                                                    |
+| 4.0.3      | 2024-08-24 | [44682](https://github.com/airbytehq/airbyte/pull/44682) | Update dependencies                                                                                                                                                    |
+| 4.0.2      | 2024-08-17 | [44220](https://github.com/airbytehq/airbyte/pull/44220) | Update dependencies                                                                                                                                                    |
+| 4.0.1      | 2024-08-10 | [43629](https://github.com/airbytehq/airbyte/pull/43629) | Update dependencies                                                                                                                                                    |
+| 4.0.0      | 2024-08-07 | [43359](https://github.com/airbytehq/airbyte/pull/43359) | Revert low code migration                                                                                                                                              |
+| 3.0.1      | 2024-08-03 | [43087](https://github.com/airbytehq/airbyte/pull/43087) | Update dependencies                                                                                                                                                    |
+| 3.0.0      | 2024-06-18 | [38314](https://github.com/airbytehq/airbyte/pull/38314) | Migrate to low-code                                                                                                                                                    |
+| 2.1.12     | 2024-07-27 | [42728](https://github.com/airbytehq/airbyte/pull/42728) | Update dependencies                                                                                                                                                    |
+| 2.1.11     | 2024-07-20 | [42291](https://github.com/airbytehq/airbyte/pull/42291) | Update dependencies                                                                                                                                                    |
+| 2.1.10     | 2024-07-13 | [41710](https://github.com/airbytehq/airbyte/pull/41710) | Update dependencies                                                                                                                                                    |
+| 2.1.9      | 2024-07-10 | [41517](https://github.com/airbytehq/airbyte/pull/41517) | Update dependencies                                                                                                                                                    |
+| 2.1.8      | 2024-07-09 | [41315](https://github.com/airbytehq/airbyte/pull/41315) | Update dependencies                                                                                                                                                    |
+| 2.1.7      | 2024-07-06 | [40868](https://github.com/airbytehq/airbyte/pull/40868) | Update dependencies                                                                                                                                                    |
+| 2.1.6      | 2024-06-25 | [40331](https://github.com/airbytehq/airbyte/pull/40331) | Update dependencies                                                                                                                                                    |
+| 2.1.5      | 2024-06-22 | [39998](https://github.com/airbytehq/airbyte/pull/39998) | Update dependencies                                                                                                                                                    |
+| 2.1.4      | 2024-06-16 | [39442](https://github.com/airbytehq/airbyte/pull/39442) | Fix README commands, change spec from json to yaml, fix schema states to object                                                                                        |
+| 2.1.3      | 2024-06-06 | [39240](https://github.com/airbytehq/airbyte/pull/39240) | [autopull] Upgrade base image to v1.2.2                                                                                                                                |
+| 2.1.2      | 2024-05-07 | [36648](https://github.com/airbytehq/airbyte/pull/36648) | Schema descriptions                                                                                                                                                    |
+| 2.1.1      | 2024-05-07 | [38013](https://github.com/airbytehq/airbyte/pull/38013) | Fix an issue where the `Accounts` stream did not correctly handle provided account IDs                                                                                 |
+| 2.1.0      | 2024-04-30 | [37573](https://github.com/airbytehq/airbyte/pull/37573) | Update API version to `202404`; add cursor-based pagination                                                                                                            |
+| 2.0.0      | 2024-04-24 | [37531](https://github.com/airbytehq/airbyte/pull/37531) | Change primary key for Analytics Streams                                                                                                                               |
+| 1.0.1      | 2024-03-28 | [34152](https://github.com/airbytehq/airbyte/pull/34152) | Proceed pagination if return less than expected                                                                                                                        |
+| 1.0.0      | 2024-04-10 | [36927](https://github.com/airbytehq/airbyte/pull/36927) | Update primary key for Analytics Streams                                                                                                                               |
+| 0.8.0      | 2024-03-19 | [36267](https://github.com/airbytehq/airbyte/pull/36267) | Pin airbyte-cdk version to `^0`                                                                                                                                        |
+| 0.7.0      | 2024-02-20 | [35465](https://github.com/airbytehq/airbyte/pull/35465) | Per-error reporting and continue sync on stream failures                                                                                                               |
+| 0.6.8      | 2024-02-09 | [35086](https://github.com/airbytehq/airbyte/pull/35086) | Manage dependencies with Poetry                                                                                                                                        |
+| 0.6.7      | 2024-01-11 | [34152](https://github.com/airbytehq/airbyte/pull/34152) | Prepare for airbyte-lib                                                                                                                                                |
+| 0.6.6      | 2024-01-15 | [34222](https://github.com/airbytehq/airbyte/pull/34222) | Use stream slices for Analytics streams                                                                                                                                |
+| 0.6.5      | 2023-12-15 | [33530](https://github.com/airbytehq/airbyte/pull/33530) | Fix typo in `Pivot Category` list                                                                                                                                      |
+| 0.6.4      | 2023-10-19 | [31599](https://github.com/airbytehq/airbyte/pull/31599) | Base image migration: remove Dockerfile and use the python-connector-base image                                                                                        |
+| 0.6.3      | 2023-10-13 | [31396](https://github.com/airbytehq/airbyte/pull/31396) | Fix pagination for reporting                                                                                                                                           |
+| 0.6.2      | 2023-08-23 | [31221](https://github.com/airbytehq/airbyte/pull/31221) | Increase max time between messages to 24 hours                                                                                                                         |
+| 0.6.1      | 2023-08-23 | [29600](https://github.com/airbytehq/airbyte/pull/29600) | Update field descriptions                                                                                                                                              |
+| 0.6.0      | 2023-08-22 | [29721](https://github.com/airbytehq/airbyte/pull/29721) | Add `Conversions` stream                                                                                                                                               |
+| 0.5.0      | 2023-08-14 | [29175](https://github.com/airbytehq/airbyte/pull/29175) | Add Custom report Constructor                                                                                                                                          |
+| 0.4.0      | 2023-08-08 | [29175](https://github.com/airbytehq/airbyte/pull/29175) | Add analytics streams                                                                                                                                                  |
+| 0.3.1      | 2023-08-08 | [29189](https://github.com/airbytehq/airbyte/pull/29189) | Fix empty accounts field                                                                                                                                               |
+| 0.3.0      | 2023-08-07 | [29045](https://github.com/airbytehq/airbyte/pull/29045) | Add new fields to schemas; convert datetime fields to `rfc3339`                                                                                                        |
+| 0.2.1      | 2023-05-30 | [26780](https://github.com/airbytehq/airbyte/pull/26780) | Reduce records limit for Creatives Stream                                                                                                                              |
+| 0.2.0      | 2023-05-23 | [26372](https://github.com/airbytehq/airbyte/pull/26372) | Migrate to LinkedIn API version: May 2023                                                                                                                              |
+| 0.1.16     | 2023-05-24 | [26512](https://github.com/airbytehq/airbyte/pull/26512) | Removed authSpecification from spec.json in favour of advancedAuth                                                                                                     |
+| 0.1.15     | 2023-02-13 | [22940](https://github.com/airbytehq/airbyte/pull/22940) | Specified date formatting in specification                                                                                                                             |
+| 0.1.14     | 2023-02-03 | [22361](https://github.com/airbytehq/airbyte/pull/22361) | Turn on default HttpAvailabilityStrategy                                                                                                                               |
+| 0.1.13     | 2023-01-27 | [22013](https://github.com/airbytehq/airbyte/pull/22013) | For adDirectSponsoredContents stream skip accounts which are part of organization                                                                                      |
+| 0.1.12     | 2022-10-18 | [18111](https://github.com/airbytehq/airbyte/pull/18111) | For adDirectSponsoredContents stream skip accounts which are part of organization                                                                                      |
+| 0.1.11     | 2022-10-07 | [17724](https://github.com/airbytehq/airbyte/pull/17724) | Retry 429/5xx errors when refreshing access token                                                                                                                      |
+| 0.1.10     | 2022-09-28 | [17326](https://github.com/airbytehq/airbyte/pull/17326) | Migrate to per-stream states.                                                                                                                                          |
+| 0.1.9      | 2022-07-21 | [14924](https://github.com/airbytehq/airbyte/pull/14924) | Remove `additionalProperties` field from schemas                                                                                                                       |
+| 0.1.8      | 2022-06-07 | [13495](https://github.com/airbytehq/airbyte/pull/13495) | Fixed `base-normalization` issue on `Destination Redshift` caused by wrong casting of `pivot` column                                                                   |
+| 0.1.7      | 2022-05-04 | [12482](https://github.com/airbytehq/airbyte/pull/12482) | Update input configuration copy                                                                                                                                        |
+| 0.1.6      | 2022-04-04 | [11690](https://github.com/airbytehq/airbyte/pull/11690) | Small documentation corrections                                                                                                                                        |
+| 0.1.5      | 2021-12-21 | [8984](https://github.com/airbytehq/airbyte/pull/8984)   | Update connector fields title/description                                                                                                                              |
+| 0.1.4      | 2021-12-02 | [8382](https://github.com/airbytehq/airbyte/pull/8382)   | Modify log message in rate-limit cases                                                                                                                                 |
+| 0.1.3      | 2021-11-11 | [7839](https://github.com/airbytehq/airbyte/pull/7839)   | Added OAuth support                                                                                                                                                    |
+| 0.1.2      | 2021-11-08 | [7499](https://github.com/airbytehq/airbyte/pull/7499)   | Remove base-python dependencies                                                                                                                                        |
+| 0.1.1      | 2021-10-02 | [6610](https://github.com/airbytehq/airbyte/pull/6610)   | Fix for `Campaigns/targetingCriteria` transformation, coerced `Creatives/variables/values` to string by default                                                        |
+| 0.1.0      | 2021-09-05 | [5285](https://github.com/airbytehq/airbyte/pull/5285)   | Initial release of Native LinkedIn Ads connector for Airbyte                                                                                                           |
 
 </details>


Reviews:
user: login: brianjlai
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T04:21:04Z
user: login: brianjlai
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T04:21:38Z
user: login: brianjlai
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T07:00:12Z
user: login: maxi297
body: LGTM! More connectors to manifest only is always a win
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T12:51:08Z
user: login: brianjlai
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T19:43:22Z

Review comments:
diff_hunk: @@ -64,7 +64,7 @@ class ManifestOnlyConnectorUnitTests(PytestStep):
 
     title = "Manifest-only unit tests"
     test_directory_name = "unit_tests"
-    common_test_dependencies = ["pytest", "requests-mock"]
+    common_test_dependencies = ["freezegun", "pytest", "pytest-mock", "requests-mock"]
path: airbyte-ci/connectors/pipelines/pipelines/airbyte_ci/connectors/test/steps/manifest_only_connectors.py
user: login: brianjlai
body: After talking with max a bit, he informed me that `pyproject.toml` in the test directory doesn't actually do anything for CI tests... These seem like reasonable generic libraries that many of our unit tests uses
created_at: 2025-04-25T04:21:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -15,28 +33,133 @@
 DESTINATION_RESERVED_KEYWORDS: list = ["pivot"]
 
 
-def update_specific_key(target_dict, target_key, target_value, condition_func=None, excluded_keys=None):
path: airbyte-integrations/connectors/source-linkedin-ads/components.py
user: login: brianjlai
body: no longer used any flows after deprecating some of our other components
created_at: 2025-04-25T04:21:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1660,6 +1660,21 @@ dynamic_streams:
               "value",
             ]
           value: "{{components_values['pivot_by']}}"
+
+# LinkedIn Ads does not publish their standard API rate limits.
+# https://learn.microsoft.com/en-us/linkedin/shared/api-guide/concepts/rate-limits
+# I looked into our test account to establish some baselines, but it seems like each endpoint
+# has its own individually tracked rate limit that resets daily. There are also tiers, but
+# there is not a good way of ascertaining what value are for each. There are 19 streams so
+# as a standard default will be set to 19 so each stream can at least run in parallel. This
+# could require some tuning.
+#
+# It is likely that certain customers may have a greater limit provisioned so allow for a max of 50.
+concurrency_level:
+  type: ConcurrencyLevel
+  default_concurrency: "19"
path: airbyte-integrations/connectors/source-linkedin-ads/manifest.yaml
user: login: brianjlai
body: I decided to leave this as non-configurable for user at this moment given the above. We can explore making this user-configurable later, but its easier to open this up later while still getting a nice performance bump
created_at: 2025-04-25T07:00:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,21 +4,37 @@
 
 import json
 import os
+import sys
+from pathlib import Path
 from typing import Any, Mapping
 
-from source_linkedin_ads.source import SourceLinkedinAds
-
+from airbyte_cdk.sources.declarative.yaml_declarative_source import YamlDeclarativeSource
 from airbyte_cdk.test.catalog_builder import CatalogBuilder
 from airbyte_cdk.test.state_builder import StateBuilder
 
 
 os.environ["REQUEST_CACHE_PATH"] = "REQUEST_CACHE_PATH"
 
+pytest_plugins = ["airbyte_cdk.test.utils.manifest_only_fixtures"]
+
+
+def _get_manifest_path() -> Path:
+    source_declarative_manifest_path = Path("/airbyte/integration_code/source_declarative_manifest")
+    if source_declarative_manifest_path.exists():
+        return source_declarative_manifest_path
+    return Path(__file__).parent.parent
+
+
+_SOURCE_FOLDER_PATH = _get_manifest_path()
+_YAML_FILE_PATH = _SOURCE_FOLDER_PATH / "manifest.yaml"
+
+sys.path.append(str(_SOURCE_FOLDER_PATH))  # to allow loading custom components
+
 
-def get_source(config) -> SourceLinkedinAds:
+def get_source(config) -> YamlDeclarativeSource:
     catalog = CatalogBuilder().build()
     state = StateBuilder().build()
-    return SourceLinkedinAds(catalog, config, state)
+    return YamlDeclarativeSource(path_to_yaml=str(_YAML_FILE_PATH), catalog=catalog, config=config, state=state)
path: airbyte-integrations/connectors/source-linkedin-ads/unit_tests/conftest.py
user: login: maxi297
body: Just curious if following @aaronsteers change on FAST if there is a better way to do this. This was done as a hacky but easy to implement solution when adding tests to source-intercom if I'm not mistaken but it feels that maybe FAST offer better capabilities
created_at: 2025-04-25T12:48:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,7 +41,9 @@ def mock_retriever_params():
     return {"requester": MagicMock(), "record_selector": MagicMock(), "config": MagicMock(), "parameters": MagicMock()}
 
 
-def test_safe_http_client_create_prepared_request():
+def test_safe_http_client_create_prepared_request(components_module):
path: airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_components.py
user: login: maxi297
body: For my personal knowledge, is `pytest_plugins = ["airbyte_cdk.test.utils.manifest_only_fixtures"]` in `conftest.py` that provides the ability to reference `components_module` like that? Does it work with unittests or is this a pytest fixture? 
created_at: 2025-04-25T12:50:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,7 +41,9 @@ def mock_retriever_params():
     return {"requester": MagicMock(), "record_selector": MagicMock(), "config": MagicMock(), "parameters": MagicMock()}
 
 
-def test_safe_http_client_create_prepared_request():
+def test_safe_http_client_create_prepared_request(components_module):
path: airbyte-integrations/connectors/source-linkedin-ads/unit_tests/test_components.py
user: login: brianjlai
body: That was my understanding. If I comment out `pytest_plugins = ["airbyte_cdk.test.utils.manifest_only_fixtures"]`, then I will get an error `fixture 'components_module' not found`. 

So I'm pretty sure that the line is what supplied the components_module fixture so we can get the component and unit test it when it's not available otherwise

created_at: 2025-04-25T19:43:22Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-04-24T07:57:07Z
body: [vc]: #VWKOXvaK8eFzNRE5GgddNzh5vAokkGQtCfU/7rRC8b0=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJhaXJieXRlLWRvY3MiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiYWlyYnl0ZS1kb2NzLWdpdC1icmlhbi1saW5rZWRpbmFkc3RvbWFuaWZlc3Rvbmx5LWFpcmJ5dGUtZ3Jvd3RoLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2FpcmJ5dGUtZ3Jvd3RoL2FpcmJ5dGUtZG9jcy81UTQ5OFhqYlJEdlRaYmNRRm93b0ZkY2hEd1lZIiwicHJldmlld1VybCI6ImFpcmJ5dGUtZG9jcy1naXQtYnJpYW4tbGlua2VkaW5hZHN0b21hbmlmZXN0b25seS1haXJieXRlLWdyb3d0aC52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwicm9vdERpcmVjdG9yeSI6ImRvY3VzYXVydXMifV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **airbyte-docs** | ✅ Ready ([Inspect](https://vercel.com/airbyte-growth/airbyte-docs/5Q498XjbRDvTZbcQFowoFdchDwYY)) | [Visit Preview](https://airbyte-docs-git-brian-linkedinadstomanifestonly-airbyte-growth.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/airbyte-docs-git-brian-linkedinadstomanifestonly-airbyte-growth.vercel.app?via=pr-comment-feedback-link) | Apr 25, 2025 5:45am |


author_association: NONE
user: login: brianjlai
created_at: 2025-04-25T05:42:36Z
body: Regression test results https://github.com/airbytehq/airbyte/actions/runs/14657105105:

- The number of records matched exactly. It says it might have some content mismatches but I chalk that up to drift between runs. This is a manifest only migration with almost no changes to the rest of the manifest
- The one part that did change was the pendulum migration, but I did a manual check of records from before and after and the datetime formatted in both control and target matched
- The catalogs do not match, but this is because in a more recent CDK version, `"is_file_based": false` was introduced which is now present in the generated catalog

<img width="1651" alt="linkedin_ads_run" src="https://github.com/user-attachments/assets/56353b74-f761-4d32-b31c-add43cb77e18" />


author_association: CONTRIBUTOR
user: login: brianjlai
created_at: 2025-04-25T05:43:17Z
body: /approve-regression-tests see above comment for validation of results
> [Check job output.][1]

[1]: https://github.com/airbytehq/airbyte/actions/runs/14657780276
> ✅ Approving regression tests
author_association: CONTRIBUTOR