diff --git a/apps/drs_spot67_stac_example.py b/examples/drs_spot67_stac_example.py
similarity index 87%
rename from apps/drs_spot67_stac_example.py
rename to examples/drs_spot67_stac_example.py
index 4f724739cd5538c4b76a53480775abcf16a8dd80..b54f3f46fc493786189149f7f8b3e6f73672b524 100755
--- a/apps/drs_spot67_stac_example.py
+++ b/examples/drs_spot67_stac_example.py
@@ -6,7 +6,7 @@ def main():
 
     bbox = scenes.BoundingBox(xmin=2, ymin=45.23, xmax=2.1, ymax=45.26)
     provider = scenes.stac.DinamisSpot67Provider()
-    scs = provider.search(bbox_wgs84=bbox)
+    scs = provider.scenes_search(bbox_wgs84=bbox)
     for i, sc in enumerate(scs):
         print(sc)
         pyotb.HaralickTextureExtraction({"in": sc.get_xs(), "out": f"/data/tex_{i}.img"})
diff --git a/apps/mpc_s2_stac_example.py b/examples/mpc_s2_stac_example.py
similarity index 64%
rename from apps/mpc_s2_stac_example.py
rename to examples/mpc_s2_stac_example.py
index 51434c1946c742b41f77a40f3af912741056eb01..96601edd1258015af4f42c4bcfa651a3a63af403 100755
--- a/apps/mpc_s2_stac_example.py
+++ b/examples/mpc_s2_stac_example.py
@@ -6,9 +6,10 @@ def main():
 
     bbox = scenes.BoundingBox(xmin=2, ymin=45.23, xmax=2.1, ymax=45.26)
     provider = scenes.stac.MPCProvider()
-    scs = provider.search(bbox_wgs84=bbox)
-    for i, sc in enumerate(scs):
-        print(sc)
+    results = provider.stac_search(bbox_wgs84=bbox)
+    for i, result in enumerate(results):
+        print(result)
+        sc = provider.stac_item_to_scene(result)
         dyn = pyotb.DynamicConvert(sc.get_10m_bands()[0:256, 0:256, :])
         dyn.write(f"/tmp/{i}.png")
 
diff --git a/scenes/stac.py b/scenes/stac.py
index e63a92a1e3fc4f75dd005c2d597ae076278f7ad8..b1a60245b5ffe4684e36dd437931bf26222cc1c4 100644
--- a/scenes/stac.py
+++ b/scenes/stac.py
@@ -23,6 +23,7 @@ import tempfile
 import datetime
 import pystac
 from pystac_client import Client
+from scenes.core import Scene
 from scenes.spatial import BoundingBox
 from scenes.dates import any2datetime
 from scenes.spot import Spot67DRSScene
@@ -31,16 +32,20 @@ from scenes.auth import OAuth2KeepAlive
 import requests
 from tqdm.autonotebook import tqdm
 import threading
+from abc import abstractmethod
+
 
 class ProviderBase:
-    def __init__(self, url):
+    def __init__(self, url, default_collections):
         """
 
         Args:
             url: STAC endpoint
+            default_collections: default collections
         """
         assert url
         self.url = url
+        self.default_collections = default_collections
         self.vsicurl_media_types = [pystac.MediaType.GEOPACKAGE,
                                     pystac.MediaType.GEOJSON,
                                     pystac.MediaType.COG,
@@ -48,15 +53,16 @@ class ProviderBase:
                                     pystac.MediaType.TIFF,
                                     pystac.MediaType.JPEG2000]
 
-    def stac_search(self, collections: list[str], bbox_wgs84: BoundingBox, date_min: datetime.datetime | str = None,
-                    date_max: datetime.datetime | str = None, filt: dict = None, query: dict = None):
+    def stac_search(self, bbox_wgs84: BoundingBox, collections: list[str] = None,
+                    date_min: datetime.datetime | str = None, date_max: datetime.datetime | str = None,
+                    filt: dict = None, query: dict = None):
         """
         Search an item in a STAC catalog.
         see https://pystac-client.readthedocs.io/en/latest/api.html#pystac_client.Client.search
 
         Args:
-            collections: names of the collections to search
             bbox_wgs84: The bounding box in WGS84 (BoundingBox instance)
+            collections: names of the collections to search
             date_min: date min (datetime.datetime or str)
             date_max: date max (datetime.datetime or str)
             filt: JSON of query parameters as per the STAC API filter extension
@@ -67,10 +73,28 @@ class ProviderBase:
         """
         dt = [any2datetime(date) for date in [date_min, date_max] if date] if date_min or date_max else None
         api = Client.open(self.url)
-        results = api.search(max_items=None, bbox=bbox_wgs84.to_list(), datetime=dt, collections=collections,
-                             filter=filt, query=query)
+        results = api.search(max_items=None, bbox=bbox_wgs84.to_list(), datetime=dt, filter=filt, query=query,
+                             collections=self.default_collections if not collections else collections)
         return results.items()
 
+    def scenes_search(self, *args, as_generator=False, **kwargs) -> list[Scene]:
+        """
+        Perform a STAC search then converts the resulting items into `Scene` objects
+
+        Args:
+            *args: same args as stac_search()
+            as_generator: return scenes as generator, or not
+            **kwargs: same kwargs as stac_search()
+
+        Returns: a list of `Scenes`
+
+        """
+        items = self.stac_search(*args, **kwargs)
+        gen = (self.stac_item_to_scene(item) for item in tqdm(items))
+        if as_generator:
+            return gen
+        return list(gen)
+
     def get_asset_path(self, asset: pystac.asset) -> str:
         """
         Return the URI suited for GDAL if the asset is some geospatial data.
@@ -89,10 +113,24 @@ class ProviderBase:
                 return f"/vsicurl/{url}"
         return url
 
+    @abstractmethod
+    def stac_item_to_scene(self, item: pystac.item) -> Scene:
+        """
+        Convert a STAC item into a `Scene`
+
+        Args:
+            item: STAC item
+
+        Returns: scene
+
+        """
+        raise NotImplementedError("")
+
 
 class DinamisSpot67Provider(ProviderBase):
-    def __init__(self, url="https://stacapi.147.100.200.143.nip.io", auth=None):
-        super().__init__(url=url)
+    def __init__(self, auth=None):
+        super().__init__(url="https://stacapi.147.100.200.143.nip.io",
+                         default_collections=["spot-6-7-drs"])
         self.temp_dir = tempfile.TemporaryDirectory()
         self.headers_file = os.path.join(self.temp_dir.name, 'headers.txt')
         self.__auth_headers = None
@@ -111,7 +149,6 @@ class DinamisSpot67Provider(ProviderBase):
         with self.__auth_headers_lock:
             self.__auth_headers = {"Authorization": f"Bearer {access_token}"}
 
-
     def get_auth_headers(self):
         with self.__auth_headers_lock:
             return self.__auth_headers
@@ -132,7 +169,6 @@ class DinamisSpot67Provider(ProviderBase):
         return url
 
     def update_headers(self, token):
-        print("update headers")
         self.set_auth_headers(token=token)
         tmp_headers_file = f"{self.headers_file}.tmp"
         old_headers_file = f"{self.headers_file}.old"
@@ -143,38 +179,24 @@ class DinamisSpot67Provider(ProviderBase):
             os.rename(self.headers_file, old_headers_file)
         os.rename(tmp_headers_file, self.headers_file)
 
-    def search(self, bbox_wgs84: BoundingBox, date_min: datetime.datetime | str = None,
-               date_max: datetime.datetime | str = None, collections: list[str] = None,
-               as_generator=False) -> list[Spot67DRSScene]:
+    def stac_item_to_scene(self, item: pystac.item) -> Scene:
         """
-        Search and instantiate S2 products from Microsoft
+        Convert a STAC item into a `Spot67DRSScene`
 
         Args:
-            bbox_wgs84: The bounding box in WGS84 (BoundingBox instance)
-            date_min: date min (datetime.datetime or str)
-            date_max: date max (datetime.datetime or str)
-            collections: list of collections
-            as_generator: return the scenes as generator
+            item: STAC item
 
-        Returns:
-            `Spot67DRSScene` instances in a list
+        Returns: scene
 
         """
-        if not collections:
-            collections = ["spot-6-7-drs"]
-        items = self.stac_search(collections=collections, bbox_wgs84=bbox_wgs84, date_min=date_min, date_max=date_max)
-        gen = (Spot67DRSScene(
-            assets_paths={key: self.get_asset_path(asset) for key, asset in item.assets.items()},
-            assets_headers=self.get_auth_headers()
-        ) for item in tqdm(items))
-        if as_generator:
-            return gen
-        return list(gen)
+        return Spot67DRSScene(assets_paths={key: self.get_asset_path(asset) for key, asset in item.assets.items()},
+                              assets_headers=self.get_auth_headers())
 
 
 class MPCProvider(ProviderBase):
-    def __init__(self, url="https://planetarycomputer.microsoft.com/api/stac/v1"):
-        super().__init__(url=url)
+    def __init__(self):
+        super().__init__(url="https://planetarycomputer.microsoft.com/api/stac/v1",
+                         default_collections="sentinel-2-l2a")
 
     def get_asset_path(self, asset: pystac.asset) -> str:
         """
@@ -198,29 +220,14 @@ class MPCProvider(ProviderBase):
             return f"/vsicurl/{new_url}"
         return url
 
-    def search(self, bbox_wgs84: BoundingBox, date_min: datetime.datetime | str = None,
-               date_max: datetime.datetime | str = None, collections: list[str] = None,
-               as_generator=True) -> list[Sentinel2MPCScene]:
+    def stac_item_to_scene(self, item: pystac.item) -> Scene:
         """
-        Search and instantiate Spot-6/7 DRS products from Dinamis
+        Convert a STAC item into a `Sentinel2MPCScene`
 
         Args:
-            bbox_wgs84: The bounding box in WGS84 (BoundingBox instance)
-            date_min: date min (datetime.datetime or str)
-            date_max: date max (datetime.datetime or str)
-            collections: list of collections
-            as_generator: return the scenes as generator
+            item: STAC item
 
-        Returns:
-            `Sentinel2MPCScene` instances in a list
+        Returns: scene
 
         """
-        if not collections:
-            collections = ["sentinel-2-l2a"]
-        items = self.stac_search(collections=collections, bbox_wgs84=bbox_wgs84, date_min=date_min, date_max=date_max)
-        gen = (Sentinel2MPCScene(
-            assets_paths={key: self.get_asset_path(asset) for key, asset in item.assets.items()},
-        ) for item in tqdm(items))
-        if as_generator:
-            return gen
-        return list(gen)
+        return Sentinel2MPCScene(assets_paths={key: self.get_asset_path(asset) for key, asset in item.assets.items()})