Repository: CS-SI/eodag
Pull request #1433 (2024-12-09 08:38:25)

refactor(ecmwf): simplify ECMWFSearch configuration
This PR simplifies the configuration of `ECMWFSearch` products. 

- metadata mapping defined in the plugin are extended to cover all the common metadata mapping across `ECMWFSearch` providers
- remove the need of `"ecmwf:"` prefix in products configuration
- update config to always get datetime parameters from `datetime` filter in addition to ecmwf `day`, `month`, `year`
- change the date interval in case of missing end to be `start/start` instead of `start/start + 1d`. This is done to align the behavior of EODAG with the way ECMWF expects.

github-actions[bot]: ## Test Results
    3 files   -   1      3 suites   - 1   1h 2m 7s ⏱️ + 55m 59s
  581 tests  -   1    568 ✅  -  11   3 💤 ± 0  10 ❌ +10 
1 738 runs   - 590  1 658 ✅  - 572  52 💤  - 46  28 ❌ +28 

For more details on these failures, see [this check](https://github.com/CS-SI/eodag/runs/34116816055).

Results for commit 9db17da3. ± Comparison against base commit d709297e.

<details>
  <summary>This pull request <b>removes</b> 1 test.</summary>

```
tests.units.test_search_plugins.TestSearchPluginQueryStringSearch ‑ test_plugins_search_ecmwf_search_wekeo_discover_queryables
```
</details>

[test-results]:data:application/gzip;base64,H4sIAMaxVmcC/03MTQ7CIBCG4as0rF04IGXwMoYONCH2x1BYNd5dSAt1x/cMeXc2+slt7NmJW8e25GMbNgUT/bqUqbjKkm+xXCVCXa8tERXq8aK3/5yNA0bjpwxwb+JCWEOmIiEtJQpK4LlqFHp50RGVvMIZ5e3Hf5PWefYxD6btAMoa8SDiwMEOZLVEItcjClMefNDoLPv+AHyl46wLAQAA

github-actions[bot]: ![badge](https://raw.githubusercontent.com/CS-SI/eodag/_xml_coverage_reports/data/ecmwf-simplify-config/./badge.svg)


## Code Coverage (Ubuntu)

<details>


```
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                      8       0  100.00%
cli.py                                         323      64  80.19%   62-63, 87, 674-735, 837-888, 892
config.py                                      428      27  93.69%   83-85, 94, 102, 106-108, 179, 190, 690-692, 805-808, 851-852, 861-862, 967, 1030-1035, 1037
crunch.py                                        5       5  0.00%    20-24
api/__init__.py                                  0       0  100.00%
api/core.py                                    778      72  90.75%   363, 653, 697-700, 738, 782, 816, 861-866, 892, 983, 1051, 1189, 1274-1286, 1322, 1324, 1352, 1356-1367, 1380-1386, 1469-1472, 1505-1525, 1577, 1594-1598, 1610-1613, 1949, 1973-1979, 2230, 2234-2238, 2247-2249, 2293-2294, 2323-2324
api/search_result.py                            58       4  93.10%   92, 101, 108, 122
api/product/__init__.py                          6       0  100.00%
api/product/_assets.py                          48       5  89.58%   75, 147, 155, 158-162
api/product/_product.py                        187      20  89.30%   70-72, 231-232, 307, 336, 393, 407-410, 423, 447-450, 493-499
api/product/metadata_mapping.py                695      77  88.92%   131-133, 230, 262-263, 309-310, 320-332, 334, 345, 351-363, 370, 410-411, 448, 469-472, 495, 503-504, 590-591, 615-616, 622-625, 640-641, 790, 836, 989, 998-1002, 1019-1024, 1151, 1165-1185, 1205, 1210, 1339, 1353, 1378, 1424, 1476, 1499-1500, 1516-1520, 1536, 1544
api/product/drivers/__init__.py                  6       0  100.00%
api/product/drivers/base.py                      6       1  83.33%   38
plugins/__init__.py                              0       0  100.00%
plugins/base.py                                 21       2  90.48%   48, 55
plugins/manager.py                             172      15  91.28%   116-121, 171, 209, 231, 235, 259, 281, 399-402, 414-415
plugins/apis/__init__.py                         0       0  100.00%
plugins/apis/base.py                             4       0  100.00%
plugins/apis/ecmwf.py                           97      33  65.98%   166-168, 196-198, 212-259, 297-298
plugins/apis/usgs.py                           180      31  82.78%   155, 257, 291, 326-328, 333, 359-360, 365, 395-402, 413-418, 440-446, 448-454, 477
plugins/authentication/__init__.py               6       1  83.33%   31
plugins/authentication/aws_auth.py              19       0  100.00%
plugins/authentication/base.py                  17       2  88.24%   43, 56
plugins/authentication/generic.py               14       2  85.71%   51, 61
plugins/authentication/header.py                19       0  100.00%
plugins/authentication/keycloak.py              46       7  84.78%   151-154, 175-180
plugins/authentication/oauth.py                 13       7  46.15%   42-44, 48-51
plugins/authentication/openid_connect.py       206      27  86.89%   80-81, 93-111, 149, 155-183, 191, 323-326, 352
plugins/authentication/qsauth.py                34       1  97.06%   91
plugins/authentication/sas_auth.py              47       1  97.87%   76
plugins/authentication/token.py                 92      16  82.61%   114, 143, 145, 176-189, 245-249
plugins/authentication/token_exchange.py        36      14  61.11%   75, 92-120
plugins/crunch/__init__.py                       0       0  100.00%
plugins/crunch/base.py                          10       1  90.00%   43
plugins/crunch/filter_date.py                   59      14  76.27%   52-57, 69, 78, 87, 90, 102-104, 113-115, 122
plugins/crunch/filter_latest_intersect.py       47       8  82.98%   52-53, 69, 78-81, 83, 90-93
plugins/crunch/filter_latest_tpl_name.py        31       1  96.77%   83
plugins/crunch/filter_overlap.py                66      18  72.73%   28-30, 66-69, 76-79, 85, 93, 104-120
plugins/crunch/filter_property.py               30       7  76.67%   55-60, 63-64, 80-84
plugins/download/__init__.py                     0       0  100.00%
plugins/download/aws.py                        491     163  66.80%   282, 295, 362-365, 379-383, 425-427, 431, 463-464, 470-474, 503, 535, 539, 546, 576-584, 588, 620-628, 639-641, 672-746, 764-824, 835-840, 852-865, 890, 905-907, 910, 920-928, 936-949, 959-990, 997-1009, 1047, 1073, 1118-1120, 1340
plugins/download/base.py                       258      36  86.05%   136, 164, 300-301, 355-359, 365-366, 408, 411-425, 437, 441, 505-509, 539-540, 549-556, 629, 651, 673, 681
plugins/download/creodias_s3.py                 17       9  47.06%   53-67
plugins/download/http.py                       541     105  80.59%   237, 333-336, 339, 346-351, 369-384, 401, 413, 461, 468-474, 492, 506, 520, 528-530, 546-551, 562, 580, 623-626, 648, 688, 733, 747-753, 782-846, 864, 894-903, 925-926, 955-960, 966, 969, 986, 1003-1004, 1034-1035, 1042, 1104, 1119, 1178-1179, 1185, 1195, 1231, 1267, 1287, 1322-1324
plugins/download/s3rest.py                     116      24  79.31%   118, 154, 161, 196, 223-230, 233-235, 239, 250-256, 264-265, 268-272, 295, 316-319
plugins/search/__init__.py                      22       0  100.00%
plugins/search/base.py                         145      11  92.41%   100, 104, 128-134, 274, 294, 427
plugins/search/build_search_result.py          322     142  55.90%   227-283, 416-433, 457, 505, 507, 542, 549-645, 663-764, 781-842, 861-879, 919, 946-947, 953, 964, 1018-1025, 1051
plugins/search/cop_marine.py                   240      47  80.42%   56, 64-66, 76-77, 82, 87-88, 104, 106, 109, 175-176, 218, 224, 228, 232, 245, 256-257, 265, 293, 297, 312, 316, 320, 324, 328-332, 338-341, 344-358, 375, 424-428, 433, 445
plugins/search/creodias_s3.py                   55       3  94.55%   59, 77, 111
plugins/search/csw.py                          105      81  22.86%   98-99, 103-104, 112-160, 166-179, 187-219, 237-278
plugins/search/data_request_search.py          202      69  65.84%   188-191, 207, 218, 222-223, 234, 239, 244, 251, 264-267, 321-322, 326, 336-342, 347, 373-376, 384-395, 412, 414, 421-424, 426-427, 445-449, 482, 492, 503, 516, 522-537, 542
plugins/search/qssearch.py                     732      89  87.84%   450, 508, 522, 526-532, 560-564, 677-689, 734-737, 808-809, 857, 876, 883, 895, 952, 973, 976-977, 986-987, 996-997, 1006-1007, 1034, 1105-1110, 1114-1123, 1157, 1179, 1256, 1346, 1431-1432, 1442, 1519-1523, 1585, 1588, 1594-1595, 1616, 1644-1656, 1663, 1695-1697, 1707-1713, 1743, 1766, 1771-1772, 1787, 1803, 1888-1891, 1896-1899, 1908, 1938-1942, 1948
plugins/search/static_stac_search.py            75      13  82.67%   98-124, 140, 153
rest/__init__.py                                 4       2  50.00%   21-22
rest/cache.py                                   33       7  78.79%   35-37, 53-55, 59, 68
rest/config.py                                  25       0  100.00%
rest/constants.py                                6       0  100.00%
rest/core.py                                   257      63  75.49%   258, 266, 284-301, 316-354, 448, 485-524, 707, 714-762
rest/errors.py                                  69       5  92.75%   106, 116, 127, 143-144
rest/server.py                                 192      24  87.50%   94, 117-119, 293-298, 326, 522-524, 541-546, 575, 577, 581-582, 586-587
rest/stac.py                                   319      63  80.25%   307, 329, 381-384, 411-438, 469-471, 494, 526-527, 609-649, 671-687, 779-783, 790, 844-845, 906, 996-998
rest/types/__init__.py                           0       0  100.00%
rest/types/collections_search.py                13      13  0.00%    18-44
rest/types/eodag_search.py                     180       6  96.67%   225-229, 282, 285, 353, 385
rest/types/queryables.py                        57       5  91.23%   93-98, 162
rest/types/stac_search.py                      125       7  94.40%   138, 184, 199-201, 209, 213
rest/utils/__init__.py                          94      12  87.23%   111-112, 131-133, 185, 195-209
rest/utils/cql_evaluate.py                      48       5  89.58%   69, 76, 90, 97, 105
rest/utils/rfc3339.py                           23       3  86.96%   48, 60, 62
types/__init__.py                              128      46  64.06%   66, 79-83, 94-106, 134-136, 143-148, 187-196, 208, 230, 240-256, 261, 263, 285, 290, 298, 308
types/bbox.py                                   43      19  55.81%   46-61, 72-74, 85-87, 99-101, 113-115, 123
types/download_args.py                           9       0  100.00%
types/queryables.py                            108       6  94.44%   60-65
types/search_args.py                            70      18  74.29%   60-64, 71-88, 103
types/whoosh.py                                 15       0  100.00%
utils/__init__.py                              520      47  90.96%   85, 194-195, 204-231, 234, 249, 329-333, 406-410, 429-431, 444-458, 537, 552, 592-593, 957-960, 968-969, 1007-1008, 1055-1056, 1190
utils/exceptions.py                             46       0  100.00%
utils/import_system.py                          28      19  32.14%   64-78, 89-99
utils/logging.py                                28       1  96.43%   41
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/repr.py                                   38       8  78.95%   51, 53, 57, 98, 122-129
utils/requests.py                               55      29  47.27%   51-52, 64, 85-96, 107-124, 128
utils/rest.py                                   36       1  97.22%   55
utils/stac_reader.py                           111      45  59.46%   56-57, 63-85, 95-97, 101, 137, 153-156, 203-212, 222-252
TOTAL                                         9859    1752  82.23%
```

</details>


### Diff against develop

```
Filename                                 Stmts    Miss  Cover
-------------------------------------  -------  ------  -------
api/core.py                                 -1       0  -0.01%
api/product/metadata_mapping.py             -5      +1  -0.22%
plugins/apis/ecmwf.py                        0     +23  -23.71%
plugins/download/base.py                     0     -13  +5.04%
plugins/search/base.py                      -1       0  -0.06%
plugins/search/build_search_result.py      -19     +93  -29.73%
types/__init__.py                            0      +4  -3.13%
types/queryables.py                          0      +5  -4.63%
TOTAL                                      -26    +113  -1.19%
```


Results for commit: 9db17da34cc2121dbcd958cce6883a8cce2b98ed


_Minimum allowed coverage is `70%`_


:recycle: This comment has been updated with latest results


<!-- Sticky Pull Request Commentartifacts/unit-test-results-python3.13-ubuntu-latest/coverage.xml -->
github-actions[bot]: ![badge](https://raw.githubusercontent.com/CS-SI/eodag/_xml_coverage_reports_win/data/ecmwf-simplify-config/./badge.svg)


## Code Coverage (Windows)

<details>


```
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                      8       0  100.00%
cli.py                                         323      64  80.19%   62-63, 87, 674-735, 837-888, 892
config.py                                      428      28  93.46%   83-85, 94, 102, 106-108, 179, 190, 690-692, 805-808, 851-852, 861-862, 967, 998, 1030-1035, 1037
crunch.py                                        5       5  0.00%    20-24
api/__init__.py                                  0       0  100.00%
api/core.py                                    778      72  90.75%   363, 653, 697-700, 738, 782, 816, 861-866, 892, 983, 1051, 1189, 1274-1286, 1322, 1324, 1352, 1356-1367, 1380-1386, 1469-1472, 1505-1525, 1577, 1594-1598, 1610-1613, 1949, 1973-1979, 2230, 2234-2238, 2247-2249, 2293-2294, 2323-2324
api/search_result.py                            58       4  93.10%   92, 101, 108, 122
api/product/__init__.py                          6       0  100.00%
api/product/_assets.py                          48       5  89.58%   75, 147, 155, 158-162
api/product/_product.py                        187      20  89.30%   70-72, 231-232, 307, 336, 393, 407-410, 423, 447-450, 493-499
api/product/metadata_mapping.py                695      85  87.77%   131-133, 230, 262-263, 309-310, 320-332, 334, 345, 351-363, 370, 377, 410-411, 448, 469-472, 495, 503-504, 590-591, 615-616, 622-625, 640-641, 790, 836, 989, 998-1002, 1019-1024, 1151, 1165-1185, 1205, 1210, 1339, 1353, 1378, 1424, 1476, 1492-1504, 1516-1520, 1536, 1544
api/product/drivers/__init__.py                  6       0  100.00%
api/product/drivers/base.py                      6       1  83.33%   38
plugins/__init__.py                              0       0  100.00%
plugins/base.py                                 21       2  90.48%   48, 55
plugins/manager.py                             172      15  91.28%   116-121, 171, 209, 231, 235, 259, 281, 399-402, 414-415
plugins/apis/__init__.py                         0       0  100.00%
plugins/apis/base.py                             4       0  100.00%
plugins/apis/ecmwf.py                           97      35  63.92%   124, 132, 166-168, 196-198, 212-259, 297-298
plugins/apis/usgs.py                           180      31  82.78%   155, 257, 291, 326-328, 333, 359-360, 365, 395-402, 413-418, 440-446, 448-454, 477
plugins/authentication/__init__.py               6       1  83.33%   31
plugins/authentication/aws_auth.py              19       0  100.00%
plugins/authentication/base.py                  17       2  88.24%   43, 56
plugins/authentication/generic.py               14       2  85.71%   51, 61
plugins/authentication/header.py                19       0  100.00%
plugins/authentication/keycloak.py              46       7  84.78%   151-154, 175-180
plugins/authentication/oauth.py                 13       7  46.15%   42-44, 48-51
plugins/authentication/openid_connect.py       206      27  86.89%   80-81, 93-111, 149, 155-183, 191, 323-326, 352
plugins/authentication/qsauth.py                34       1  97.06%   91
plugins/authentication/sas_auth.py              47       1  97.87%   76
plugins/authentication/token.py                 92      16  82.61%   114, 143, 145, 176-189, 245-249
plugins/authentication/token_exchange.py        36      14  61.11%   75, 92-120
plugins/crunch/__init__.py                       0       0  100.00%
plugins/crunch/base.py                          10       1  90.00%   43
plugins/crunch/filter_date.py                   59      14  76.27%   52-57, 69, 78, 87, 90, 102-104, 113-115, 122
plugins/crunch/filter_latest_intersect.py       47      33  29.79%   49-54, 67-112
plugins/crunch/filter_latest_tpl_name.py        31       1  96.77%   83
plugins/crunch/filter_overlap.py                66      18  72.73%   28-30, 66-69, 76-79, 85, 93, 104-120
plugins/crunch/filter_property.py               30       7  76.67%   55-60, 63-64, 80-84
plugins/download/__init__.py                     0       0  100.00%
plugins/download/aws.py                        491     163  66.80%   282, 295, 362-365, 379-383, 425-427, 431, 463-464, 470-474, 503, 535, 539, 546, 576-584, 588, 620-628, 639-641, 672-746, 764-824, 835-840, 852-865, 890, 905-907, 910, 920-928, 936-949, 959-990, 997-1009, 1047, 1073, 1118-1120, 1340
plugins/download/base.py                       258      40  84.50%   136, 164, 231-233, 300-301, 355-359, 365-366, 408, 411-425, 437, 441, 505-509, 539-540, 549-556, 582-586, 629, 651, 673, 681
plugins/download/creodias_s3.py                 17       9  47.06%   53-67
plugins/download/http.py                       541     106  80.41%   237, 333-336, 339, 346-351, 369-384, 401, 413, 461, 468-474, 492, 506, 520, 528-530, 546-551, 562, 580, 623-626, 648, 688, 733, 747-753, 782-846, 864, 894-903, 925-926, 955-960, 966, 969, 986, 1003-1004, 1017, 1034-1035, 1042, 1104, 1119, 1178-1179, 1185, 1195, 1231, 1267, 1287, 1322-1324
plugins/download/s3rest.py                     116      24  79.31%   118, 154, 161, 196, 223-230, 233-235, 239, 250-256, 264-265, 268-272, 295, 316-319
plugins/search/__init__.py                      22       0  100.00%
plugins/search/base.py                         145      11  92.41%   100, 104, 128-134, 274, 294, 427
plugins/search/build_search_result.py          322     142  55.90%   227-283, 416-433, 457, 505, 507, 542, 549-645, 663-764, 781-842, 861-879, 919, 946-947, 953, 964, 1018-1025, 1051
plugins/search/cop_marine.py                   240      47  80.42%   56, 64-66, 76-77, 82, 87-88, 104, 106, 109, 175-176, 218, 224, 228, 232, 245, 256-257, 265, 293, 297, 312, 316, 320, 324, 328-332, 338-341, 344-358, 375, 424-428, 433, 445
plugins/search/creodias_s3.py                   55       3  94.55%   59, 77, 111
plugins/search/csw.py                          105      81  22.86%   98-99, 103-104, 112-160, 166-179, 187-219, 237-278
plugins/search/data_request_search.py          202      69  65.84%   188-191, 207, 218, 222-223, 234, 239, 244, 251, 264-267, 321-322, 326, 336-342, 347, 373-376, 384-395, 412, 414, 421-424, 426-427, 445-449, 482, 492, 503, 516, 522-537, 542
plugins/search/qssearch.py                     732     125  82.92%   450, 508, 522, 526-532, 560-564, 677-689, 734-737, 808-809, 857, 876, 883, 895, 952, 973, 976-977, 986-987, 996-997, 1006-1007, 1034, 1105-1110, 1114-1123, 1157, 1179, 1256, 1346, 1431-1432, 1442, 1519-1523, 1585, 1588, 1594-1595, 1616, 1644-1656, 1663, 1695-1697, 1707-1713, 1743, 1766, 1771-1772, 1787, 1803, 1871-1981
plugins/search/static_stac_search.py            75      13  82.67%   98-124, 140, 153
rest/__init__.py                                 4       2  50.00%   21-22
rest/cache.py                                   33      22  33.33%   35-37, 44-70
rest/config.py                                  25       1  96.00%   35
rest/constants.py                                6       0  100.00%
rest/core.py                                   257     150  41.63%   157, 159, 161, 164-165, 179-189, 198-199, 205, 208, 249-303, 316-354, 385-422, 437-453, 469-478, 485-524, 541, 583-668, 707, 714-762
rest/errors.py                                  69      49  28.99%   60, 65-100, 105-108, 115-118, 126-147, 155-160, 175-181
rest/server.py                                 192     192  0.00%    18-600
rest/stac.py                                   319      68  78.68%   241, 307, 329, 381-384, 411-438, 469-471, 494, 526-527, 609-649, 671-687, 714, 779-783, 790, 844-845, 851, 906, 944, 977, 996-998
rest/types/__init__.py                           0       0  100.00%
rest/types/collections_search.py                13      13  0.00%    18-44
rest/types/eodag_search.py                     180      20  88.89%   225-229, 262-264, 282, 285, 291, 295, 353, 371-386
rest/types/queryables.py                        57      13  77.19%   50-51, 58-59, 66-67, 93-98, 107-108, 162
rest/types/stac_search.py                      125      11  91.20%   136-138, 184, 199-201, 209, 213, 261, 264
rest/utils/__init__.py                          94      30  68.09%   82-88, 108, 111-112, 131-133, 146, 153, 178-186, 193-214
rest/utils/cql_evaluate.py                      48       5  89.58%   69, 76, 90, 97, 105
rest/utils/rfc3339.py                           23       5  78.26%   43-44, 48, 60, 62
types/__init__.py                              128      50  60.94%   66, 70, 79-83, 94-106, 134-136, 143-148, 187-196, 199-200, 203, 208, 230, 240-256, 261, 263, 285, 290, 298, 308
types/bbox.py                                   43      19  55.81%   46-61, 72-74, 85-87, 99-101, 113-115, 123
types/download_args.py                           9       0  100.00%
types/queryables.py                            108       6  94.44%   60-65
types/search_args.py                            70      18  74.29%   60-64, 71-88, 103
types/whoosh.py                                 15       0  100.00%
utils/__init__.py                              520      47  90.96%   85, 194-195, 204-231, 234, 249, 329-333, 406-410, 429-431, 444-458, 537, 552, 592-593, 957-960, 968-969, 1007-1008, 1055-1056, 1190
utils/exceptions.py                             46       0  100.00%
utils/import_system.py                          28      19  32.14%   64-78, 89-99
utils/logging.py                                28       1  96.43%   41
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/repr.py                                   38       8  78.95%   51, 53, 57, 98, 122-129
utils/requests.py                               55      29  47.27%   51-52, 64, 85-96, 107-124, 128
utils/rest.py                                   36       1  97.22%   55
utils/stac_reader.py                           111      45  59.46%   56-57, 63-85, 95-97, 101, 137, 153-156, 203-212, 222-252
TOTAL                                         9859    2199  77.70%
```

</details>


### Diff against develop

```
Filename                                 Stmts    Miss  Cover
-------------------------------------  -------  ------  -------
api/core.py                                 -1       0  -0.01%
api/product/metadata_mapping.py             -5      +8  -1.23%
plugins/base.py                              0      -1  +4.77%
plugins/apis/ecmwf.py                        0     +25  -25.77%
plugins/download/base.py                     0     -11  +4.27%
plugins/search/base.py                      -1       0  -0.06%
plugins/search/build_search_result.py      -19     +92  -29.44%
types/__init__.py                            0      +6  -4.68%
types/queryables.py                          0      +5  -4.63%
TOTAL                                      -26    +124  -1.31%
```


Results for commit: 9db17da34cc2121dbcd958cce6883a8cce2b98ed


_Minimum allowed coverage is `70%`_


:recycle: This comment has been updated with latest results


<!-- Sticky Pull Request Commentartifacts/unit-test-results-python3.13-windows-latest/coverage.xml -->
alambare: > I tried to do a download but I git an error when I clicked on the downloadLink.
> 
> ` 2025-02-07 18:30:45,072 uvicorn.error [ERROR ] Exception in ASGI application
> 
> * Exception Group Traceback (most recent call last):
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/_utils.py", line 76, in collapse_excgroups
>   |     yield
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/middleware/base.py", line 178, in **call**
>   |     async with anyio.create_task_group() as task_group:
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 767, in **aexit**
>   |     raise BaseExceptionGroup(
>   | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
>   +-+---------------- 1 ----------------
>   | Traceback (most recent call last):
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py", line 409, in run_asgi
>   |     result = await app(  # type: ignore[func-returns-value]
>   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in **call**
>   |     return await self.app(scope, receive, send)
>   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/fastapi/applications.py", line 1054, in **call**
>   |     await super().**call**(scope, receive, send)
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/applications.py", line 112, in **call**
>   |     await self.middleware_stack(scope, receive, send)
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/middleware/errors.py", line 187, in **call**
>   |     raise exc
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/middleware/errors.py", line 165, in **call**
>   |     await self.app(scope, receive, _send)
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/middleware/base.py", line 177, in **call**
>   |     with recv_stream, send_stream, collapse_excgroups():
>   |   File "/usr/lib/python3.11/contextlib.py", line 155, in **exit**
>   |     self.gen.throw(typ, value, traceback)
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/_utils.py", line 82, in collapse_excgroups
>   |     raise exc
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/middleware/base.py", line 179, in **call**
>   |     response = await self.dispatch_func(request, call_next)
>   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/eodag/rest/server.py", line 211, in forward_middleware
>   |     response = await call_next(request)
>   |                ^^^^^^^^^^^^^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/middleware/base.py", line 154, in call_next
>   |     raise app_exc
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/middleware/base.py", line 141, in coro
>   |     await self.app(scope, receive_or_disconnect, send_no_error)
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/middleware/cors.py", line 85, in **call**
>   |     await self.app(scope, receive, send)
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 62, in **call**
>   |     await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
>   |     raise exc
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
>   |     await app(scope, receive, sender)
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/routing.py", line 715, in **call**
>   |     await self.middleware_stack(scope, receive, send)
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/routing.py", line 735, in app
>   |     await route.handle(scope, receive, send)
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/routing.py", line 288, in handle
>   |     await self.app(scope, receive, send)
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/routing.py", line 76, in app
>   |     await wrap_app_handling_exceptions(app, request)(scope, receive, send)
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
>   |     raise exc
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
>   |     await app(scope, receive, sender)
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/routing.py", line 73, in app
>   |     response = await f(request)
>   |                ^^^^^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/fastapi/routing.py", line 301, in app
>   |     raw_response = await run_endpoint_function(
>   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/fastapi/routing.py", line 214, in run_endpoint_function
>   |     return await run_in_threadpool(dependant.call, **values)
>   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/starlette/concurrency.py", line 37, in run_in_threadpool
>   |     return await anyio.to_thread.run_sync(func)
>   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/anyio/to_thread.py", line 56, in run_sync
>   |     return await get_async_backend().run_sync_in_worker_thread(
>   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2461, in run_sync_in_worker_thread
>   |     return await future
>   |            ^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 962, in run
>   |     result = context.run(func, *args)
>   |              ^^^^^^^^^^^^^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/eodag/rest/server.py", line 266, in stac_collections_item_download
>   |     return download_stac_item(
>   |            ^^^^^^^^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/eodag/rest/core.py", line 268, in download_stac_item
>   |     download_stream = product.downloader._stream_download_dict(
>   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/eodag/plugins/download/http.py", line 797, in _stream_download_dict
>   |     chunk_iterator = self._stream_download(
>   |                      ^^^^^^^^^^^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/eodag/plugins/download/http.py", line 965, in _stream_download
>   |     self.stream = s.request(
>   |                   ^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/requests/sessions.py", line 575, in request
>   |     prep = self.prepare_request(req)
>   |            ^^^^^^^^^^^^^^^^^^^^^^^^^
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/requests/sessions.py", line 484, in prepare_request
>   |     p.prepare(
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/requests/models.py", line 367, in prepare
>   |     self.prepare_url(url, params)
>   |   File "/home/julia/eodag/venv/lib/python3.11/site-packages/requests/models.py", line 438, in prepare_url
>   |     raise MissingSchema(
>   | requests.exceptions.MissingSchema: Invalid URL '%7B%22year%22%3A%20%5B%221940%22%5D%2C%20%22month%22%3A%20%5B%2201%22%5D%2C%20%22day%22%3A%20%5B%2201%22%5D%2C%20%22time%22%3A%20%5B%2209%3A00%22%5D%2C%20%22product_type%22%3A%20%22ensemble_mean%22%2C%20%22variable%22%3A%20%2210m_u_component_of_wind%22%2C%20%22download_format%22%3A%20%22unarchived%22%2C%20%22data_format%22%3A%20%22grib%22%7D': No scheme supplied. Perhaps you meant https://%7B%22year%22%3A%20%5B%221940%22%5D%2C%20%22month%22%3A%20%5B%2201%22%5D%2C%20%22day%22%3A%20%5B%2201%22%5D%2C%20%22time%22%3A%20%5B%2209%3A00%22%5D%2C%20%22product_type%22%3A%20%22ensemble_mean%22%2C%20%22variable%22%3A%20%2210m_u_component_of_wind%22%2C%20%22download_format%22%3A%20%22unarchived%22%2C%20%22data_format%22%3A%20%22grib%22%7D?
>   +---
>   `

fixed

Patch:
diff --git a/docs/getting_started_guide/register.rst b/docs/getting_started_guide/register.rst
index 0a42645c..70bc5405 100644
--- a/docs/getting_started_guide/register.rst
+++ b/docs/getting_started_guide/register.rst
@@ -46,12 +46,12 @@ You need credentials for both EOS (search) and AWS (download):
 ^^^^^^^^^^^
 Go to the `ECMWF homepage <https://www.ecmwf.int/>`__ and create an account by clicking on *Log in* and then *Register*.
 
-Then log in and go to your user profile on `Atmosphere Data Store <https://ads-beta.atmosphere.copernicus.eu/>`__ and
+Then log in and go to your user profile on `Atmosphere Data Store <https://ads.atmosphere.copernicus.eu/>`__ and
 use your *Personal Access Token* as *apikey* in eodag credentials.
 
 To download data you have to accept the `Licence to use Copernicus Products`. To accept the licence:
 
-* Go to `Datasets <https://ads-beta.atmosphere.copernicus.eu/datasets>`__ while being logged in.
+* Go to `Datasets <https://ads.atmosphere.copernicus.eu/datasets>`__ while being logged in.
 * Open the details of a dataset and go to the download tab.
 * Scroll down and accept the licence in the section `Terms of use`.
 * You can check which licences you have accepted in your user profile.
@@ -59,7 +59,7 @@ To download data you have to accept the `Licence to use Copernicus Products`. To
 ``cop_cds``
 ^^^^^^^^^^^
 Go to the `ECMWF homepage <https://www.ecmwf.int/>`__ and create an account by clicking on *Log in* and then *Register*.
-Then log in and go to your user profile on `Climate Data Store <https://cds-beta.climate.copernicus.eu/>`__ and use your
+Then log in and go to your user profile on `Climate Data Store <https://cds.climate.copernicus.eu/>`__ and use your
 *Personal Access Token* as *apikey* in eodag credentials.
 
 To download data, you also have to accept certain terms depending on the dataset. Some datasets have a specific licence
@@ -69,7 +69,7 @@ also have to accept the `Additional licence to use non European contributions`.
 
 To accept a licence:
 
-* Search for the dataset you want to download `here <https://cds-beta.climate.copernicus.eu/datasets>`__ while being
+* Search for the dataset you want to download `here <https://cds.climate.copernicus.eu/datasets>`__ while being
   logged in.
 * Open the dataset details and go to the download tab.
 * Scroll down and accept the licence in the section `Terms of use`.
diff --git a/docs/notebooks/tutos/tuto_cds.ipynb b/docs/notebooks/tutos/tuto_cds.ipynb
index 672b6e2d..91bcae22 100644
--- a/docs/notebooks/tutos/tuto_cds.ipynb
+++ b/docs/notebooks/tutos/tuto_cds.ipynb
@@ -383,8 +383,8 @@
     "            credentials:\n",
     "                apikey: myapikey\n",
     "```\n",
-    "- you can check your request status from https://ads-beta.atmosphere.copernicus.eu/requests?tab=all\n",
-    "- See [support](https://ads-beta.atmosphere.copernicus.eu/help) for any problem related to the provider"
+    "- you can check your request status from https://ads.atmosphere.copernicus.eu/requests?tab=all\n",
+    "- See [support](https://ads.atmosphere.copernicus.eu/help) for any problem related to the provider"
    ]
   },
   {
diff --git a/eodag/api/product/metadata_mapping.py b/eodag/api/product/metadata_mapping.py
index 1f343773..438429ac 100644
--- a/eodag/api/product/metadata_mapping.py
+++ b/eodag/api/product/metadata_mapping.py
@@ -367,8 +367,8 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
             )
 
         @staticmethod
-        def convert_to_geojson(string: str) -> str:
-            return geojson.dumps(string)
+        def convert_to_geojson(value: Any) -> str:
+            return geojson.dumps(value)
 
         @staticmethod
         def convert_from_ewkt(ewkt_string: str) -> Union[BaseGeometry, str]:
@@ -496,9 +496,16 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
             return NOT_AVAILABLE
 
         @staticmethod
-        def convert_replace_str(string: str, args: str) -> str:
+        def convert_replace_str(value: Any, args: str) -> str:
+            if isinstance(value, dict):
+                value = MetadataFormatter.convert_to_geojson(value)
+            elif not isinstance(value, str):
+                raise TypeError(
+                    f"convert_replace_str expects a string or a dict (apply to_geojson). Got {type(value)}"
+                )
+
             old, new = ast.literal_eval(args)
-            return re.sub(old, new, string)
+            return re.sub(old, new, value)
 
         @staticmethod
         def convert_recursive_sub_str(
diff --git a/eodag/plugins/apis/ecmwf.py b/eodag/plugins/apis/ecmwf.py
index c6e5c0c2..6ca0b572 100644
--- a/eodag/plugins/apis/ecmwf.py
+++ b/eodag/plugins/apis/ecmwf.py
@@ -30,11 +30,7 @@ from pydantic.fields import FieldInfo
 from eodag.plugins.apis.base import Api
 from eodag.plugins.search import PreparedSearch
 from eodag.plugins.search.base import Search
-from eodag.plugins.search.build_search_result import (
-    ECMWF_KEYWORDS,
-    ECMWFSearch,
-    keywords_to_mdt,
-)
+from eodag.plugins.search.build_search_result import ECMWFSearch, ecmwf_mtd
 from eodag.utils import (
     DEFAULT_DOWNLOAD_TIMEOUT,
     DEFAULT_DOWNLOAD_WAIT,
@@ -94,7 +90,7 @@ class EcmwfApi(Api, ECMWFSearch):
     def __init__(self, provider: str, config: PluginConfig) -> None:
         # init self.config.metadata_mapping using Search Base plugin
         config.metadata_mapping = {
-            **keywords_to_mdt(ECMWF_KEYWORDS, "ecmwf"),
+            **ecmwf_mtd(),
             **config.metadata_mapping,
         }
         Search.__init__(self, provider, config)
diff --git a/eodag/plugins/search/base.py b/eodag/plugins/search/base.py
index 104b0f85..0bd5cdd6 100644
--- a/eodag/plugins/search/base.py
+++ b/eodag/plugins/search/base.py
@@ -150,7 +150,7 @@ class Search(PluginTopic):
         )
 
     def get_product_type_def_params(
-        self, product_type: str, **kwargs: Any
+        self, product_type: str, format_variables: Optional[dict[str, Any]] = None
     ) -> dict[str, Any]:
         """Get the provider product type definition parameters and specific settings
 
@@ -171,7 +171,8 @@ class Search(PluginTopic):
             return {
                 k: v
                 for k, v in format_dict_items(
-                    self.config.products[GENERIC_PRODUCT_TYPE], **kwargs
+                    self.config.products[GENERIC_PRODUCT_TYPE],
+                    **(format_variables or {}),
                 ).items()
                 if v
             }
diff --git a/eodag/plugins/search/build_search_result.py b/eodag/plugins/search/build_search_result.py
index 74264184..dbc09c6f 100644
--- a/eodag/plugins/search/build_search_result.py
+++ b/eodag/plugins/search/build_search_result.py
@@ -23,14 +23,14 @@ import logging
 import re
 from collections import OrderedDict
 from datetime import datetime, timedelta
-from typing import TYPE_CHECKING, Annotated, Any, Optional, Union, cast
+from typing import TYPE_CHECKING, Annotated, Any, Optional, Union
 from urllib.parse import quote_plus, unquote_plus
 
 import geojson
 import orjson
 from dateutil.parser import isoparse
 from dateutil.tz import tzutc
-from jsonpath_ng import Child, Fields, Root
+from dateutil.utils import today
 from pydantic import Field
 from pydantic.fields import FieldInfo
 from requests.auth import AuthBase
@@ -40,10 +40,8 @@ from typing_extensions import get_args
 from eodag.api.product import EOProduct
 from eodag.api.product.metadata_mapping import (
     NOT_AVAILABLE,
-    NOT_MAPPED,
+    OFFLINE_STATUS,
     format_metadata,
-    format_query_params,
-    mtd_cfg_as_conversion_and_querypath,
     properties_from_json,
 )
 from eodag.api.search_result import RawSearchResult
@@ -52,6 +50,7 @@ from eodag.plugins.search.qssearch import PostJsonSearch, QueryStringSearch
 from eodag.types import json_field_definition_to_python
 from eodag.types.queryables import Queryables, QueryablesDict
 from eodag.utils import (
+    DEFAULT_MISSION_START_DATE,
     DEFAULT_SEARCH_TIMEOUT,
     deepcopy,
     dict_items_recursive_sort,
@@ -66,9 +65,11 @@ if TYPE_CHECKING:
 
 logger = logging.getLogger("eodag.search.build_search_result")
 
+ECMWF_PREFIX = "ecmwf:"
+
 # keywords from ECMWF keyword database + "dataset" (not part of database but exists)
 # database: https://confluence.ecmwf.int/display/UDOC/Keywords+in+MARS+and+Dissemination+requests
-ECMWF_KEYWORDS = [
+ECMWF_KEYWORDS = {
     "dataset",
     "accuracy",
     "activity",
@@ -132,10 +133,10 @@ ECMWF_KEYWORDS = [
     "truncation",
     "type",
     "use",
-]
+}
 
 # additional keywords from copernicus services
-COP_DS_KEYWORDS = [
+COP_DS_KEYWORDS = {
     "aerosol_type",
     "altitude",
     "product_type",
@@ -190,55 +191,28 @@ COP_DS_KEYWORDS = [
     "variable_type",
     "version",
     "year",
-]
+}
+
+ALLOWED_KEYWORDS = ECMWF_KEYWORDS | COP_DS_KEYWORDS
+
+END = "completionTimeFromAscendingNode"
+
+START = "startTimeFromAscendingNode"
 
 
-def keywords_to_mdt(
-    keywords: list[str], prefix: Optional[str] = None
-) -> dict[str, Any]:
+def ecmwf_mtd() -> dict[str, Any]:
     """
-    Make metadata mapping dict from a list of keywords
+    Make metadata mapping dict from a list of defined ECMWF Keywords
 
-    prefix:keyword:
-        - keyword
-        - $."prefix:keyword"
+    We automatically add the #to_geojson convert to prevent modification of entries by eval() in the metadata mapping.
 
-    >>> keywords_to_mdt(["month", "year"])
-    {'month': ['month', '$."month"'], 'year': ['year', '$."year"']}
-    >>> keywords_to_mdt(["month", "year"], "ecmwf")
-    {'ecmwf:month': ['month', '$."ecmwf:month"'], 'ecmwf:year': ['year', '$."ecmwf:year"']}
+    keyword:
+        - keyword
+        - $."keyword"#to_geojson
 
-    :param keywords: List of keywords to be converted
-    :param prefix: prefix to be added to the parameter in the mapping
     :return: metadata mapping dict
     """
-    mdt: dict[str, Any] = {}
-    for keyword in keywords:
-        key = f"{prefix}:{keyword}" if prefix else keyword
-        mdt[key] = [keyword, f'$."{key}"']
-    return mdt
-
-
-def strip_quotes(value: Any) -> Any:
-    """Strip superfluous quotes from elements (added by mapping converter to_geojson).
-
-    >>> strip_quotes("'abc'")
-    'abc'
-    >>> strip_quotes(["'abc'", '"def'])
-    ['abc', 'def']
-    >>> strip_quotes({"'abc'": 'def"'})
-    {'abc': 'def'}
-
-    :param value: value from which quotes should be removed (should be either str or list)
-    :return: value without quotes
-    :raises: NotImplementedError
-    """
-    if isinstance(value, (list, tuple)):
-        return [strip_quotes(v) for v in value]
-    elif isinstance(value, dict):
-        return {strip_quotes(k): strip_quotes(v) for k, v in value.items()}
-    else:
-        return str(value).strip("'\"")
+    return {k: [k, f'{{$."{k}"#to_geojson}}'] for k in ALLOWED_KEYWORDS}
 
 
 def _update_properties_from_element(
@@ -309,7 +283,7 @@ def _update_properties_from_element(
 
 def ecmwf_format(v: str) -> str:
     """Add ECMWF prefix to value v if v is a ECMWF keyword."""
-    return "ecmwf:" + v if v in ECMWF_KEYWORDS + COP_DS_KEYWORDS else v
+    return ECMWF_PREFIX + v if v in ALLOWED_KEYWORDS else v
 
 
 class ECMWFSearch(PostJsonSearch):
@@ -342,54 +316,24 @@ class ECMWFSearch(PostJsonSearch):
 
     def __init__(self, provider: str, config: PluginConfig) -> None:
         config.metadata_mapping = {
-            **keywords_to_mdt(ECMWF_KEYWORDS + COP_DS_KEYWORDS, "ecmwf"),
+            **ecmwf_mtd(),
+            **{
+                "id": "$.id",
+                "title": "$.id",
+                "storageStatus": OFFLINE_STATUS,
+                "downloadLink": "$.null",
+                "defaultGeometry": "POLYGON((180 -90, 180 90, -180 90, -180 -90, 180 -90))",
+            },
             **config.metadata_mapping,
         }
 
         super().__init__(provider, config)
 
+        # ECMWF providers do not feature any api_endpoint or next_page_query_obj.
+        # Searched is faked by EODAG.
         self.config.__dict__.setdefault("api_endpoint", "")
-
-        # needed by QueryStringSearch.build_query_string / format_free_text_search
-        self.config.__dict__.setdefault("free_text_search_operations", {})
-        # needed for compatibility
         self.config.pagination.setdefault("next_page_query_obj", "{{}}")
 
-        # parse jsonpath on init: product type specific metadata-mapping
-        for product_type in self.config.products.keys():
-            if "metadata_mapping" in self.config.products[product_type].keys():
-                self.config.products[product_type][
-                    "metadata_mapping"
-                ] = mtd_cfg_as_conversion_and_querypath(
-                    self.config.products[product_type]["metadata_mapping"]
-                )
-                # Complete and ready to use product type specific metadata-mapping
-                product_type_metadata_mapping = deepcopy(self.config.metadata_mapping)
-
-                # update config using provider product type definition metadata_mapping
-                # from another product
-                other_product_for_mapping = cast(
-                    str,
-                    self.config.products[product_type].get(
-                        "metadata_mapping_from_product", ""
-                    ),
-                )
-                if other_product_for_mapping:
-                    other_product_type_def_params = self.get_product_type_def_params(
-                        other_product_for_mapping,
-                    )
-                    product_type_metadata_mapping.update(
-                        other_product_type_def_params.get("metadata_mapping", {})
-                    )
-                # from current product
-                product_type_metadata_mapping.update(
-                    self.config.products[product_type]["metadata_mapping"]
-                )
-
-                self.config.products[product_type][
-                    "metadata_mapping"
-                ] = product_type_metadata_mapping
-
     def do_search(self, *args: Any, **kwargs: Any) -> list[dict[str, Any]]:
         """Should perform the actual search request.
 
@@ -414,7 +358,7 @@ class ECMWFSearch(PostJsonSearch):
         product_type = prep.product_type
         if not product_type:
             product_type = kwargs.get("productType", None)
-        self._preprocess_search_params(kwargs, product_type)
+        kwargs = self._preprocess_search_params(kwargs, product_type)
         result, num_items = super().query(prep, **kwargs)
         if prep.count and not num_items:
             num_items = 1
@@ -426,34 +370,31 @@ class ECMWFSearch(PostJsonSearch):
         super().clear()
 
     def build_query_string(
-        self, product_type: str, **kwargs: Any
+        self, product_type: str, query_dict: dict[str, Any]
     ) -> tuple[dict[str, Any], str]:
         """Build The query string using the search parameters
 
         :param product_type: product type id
-        :param kwargs: keyword arguments to be used in the query string
+        :param query_dict: keyword arguments to be used in the query string
         :return: formatted query params and encode query string
         """
-        # parse kwargs as properties as they might be needed to build the query
-        parsed_properties = properties_from_json(
-            kwargs,
-            self.config.metadata_mapping,
-        )
-        available_properties = {
-            # We strip values of superfluous quotes (added by mapping converter to_geojson).
-            k: strip_quotes(v)
-            for k, v in parsed_properties.items()
-            if v not in [NOT_AVAILABLE, NOT_MAPPED]
-        }
+        query_dict["_date"] = f"{query_dict.get(START)}/{query_dict.get(END)}"
+
+        # Reorder kwargs to make sure year/month/day/time if set overwrite default datetime.
+        priority_keys = [
+            START,
+            END,
+        ]
+        ordered_kwargs = {k: query_dict[k] for k in priority_keys if k in query_dict}
+        ordered_kwargs.update(query_dict)
 
-        # build and return the query
         return super().build_query_string(
-            product_type=product_type, **available_properties
+            product_type=product_type, query_dict=ordered_kwargs
         )
 
     def _preprocess_search_params(
         self, params: dict[str, Any], product_type: Optional[str]
-    ) -> None:
+    ) -> dict[str, Any]:
         """Preprocess search parameters before making a request to the CDS API.
 
         This method is responsible for checking and updating the provided search parameters
@@ -469,28 +410,20 @@ class ECMWFSearch(PostJsonSearch):
             # if available, update search params using datacube query-string
             _dc_qp = geojson.loads(unquote_plus(unquote_plus(_dc_qs)))
             if "/to/" in _dc_qp.get("date", ""):
-                (
-                    params["startTimeFromAscendingNode"],
-                    params["completionTimeFromAscendingNode"],
-                ) = _dc_qp["date"].split("/to/")
+                params[START], params[END] = _dc_qp["date"].split("/to/")
             elif "/" in _dc_qp.get("date", ""):
-                (
-                    params["startTimeFromAscendingNode"],
-                    params["completionTimeFromAscendingNode"],
-                ) = _dc_qp["date"].split("/")
+                (params[START], params[END],) = _dc_qp[
+                    "date"
+                ].split("/")
             elif _dc_qp.get("date", None):
-                params["startTimeFromAscendingNode"] = params[
-                    "completionTimeFromAscendingNode"
-                ] = _dc_qp["date"]
+                params[START] = params[END] = _dc_qp["date"]
 
             if "/" in _dc_qp.get("area", ""):
                 params["geometry"] = _dc_qp["area"].split("/")
 
-        non_none_params = {k: v for k, v in params.items() if v}
-
-        # productType
-        dataset = params.get("ecmwf:dataset", None)
-        params["productType"] = non_none_params.get("productType", dataset)
+        params = {
+            k.removeprefix(ECMWF_PREFIX): v for k, v in params.items() if v is not None
+        }
 
         # dates
         # check if default dates have to be added
@@ -498,25 +431,23 @@ class ECMWFSearch(PostJsonSearch):
             self._check_date_params(params, product_type)
 
         # adapt end date if it is midnight
-        if "completionTimeFromAscendingNode" in params:
+        if END in params:
             end_date_excluded = getattr(self.config, "end_date_excluded", True)
             is_datetime = True
             try:
-                end_date = datetime.strptime(
-                    params["completionTimeFromAscendingNode"], "%Y-%m-%dT%H:%M:%SZ"
-                )
+                end_date = datetime.strptime(params[END], "%Y-%m-%dT%H:%M:%SZ")
                 end_date = end_date.replace(tzinfo=tzutc())
             except ValueError:
                 try:
                     end_date = datetime.strptime(
-                        params["completionTimeFromAscendingNode"],
+                        params[END],
                         "%Y-%m-%dT%H:%M:%S.%fZ",
                     )
                     end_date = end_date.replace(tzinfo=tzutc())
                 except ValueError:
-                    end_date = isoparse(params["completionTimeFromAscendingNode"])
+                    end_date = isoparse(params[END])
                     is_datetime = False
-            start_date = isoparse(params["startTimeFromAscendingNode"])
+            start_date = isoparse(params[START])
             if (
                 not end_date_excluded
                 and is_datetime
@@ -525,12 +456,73 @@ class ECMWFSearch(PostJsonSearch):
                 == end_date.replace(hour=0, minute=0, second=0, microsecond=0)
             ):
                 end_date += timedelta(days=-1)
-                params["completionTimeFromAscendingNode"] = end_date.isoformat()
+                params[END] = end_date.isoformat()
 
         # geometry
         if "geometry" in params:
             params["geometry"] = get_geometry_from_various(geometry=params["geometry"])
 
+        return params
+
+    def _check_date_params(
+        self, keywords: dict[str, Any], product_type: Optional[str]
+    ) -> None:
+        """checks if start and end date are present in the keywords and adds them if not"""
+
+        if START and END in keywords:
+            return
+
+        product_type_conf = getattr(self.config, "metadata_mapping", {})
+        if (
+            product_type
+            and product_type in self.config.products
+            and "metadata_mapping" in self.config.products[product_type]
+        ):
+            product_type_conf = self.config.products[product_type]["metadata_mapping"]
+
+        # start time given, end time missing
+        if START in keywords:
+            keywords[END] = (
+                keywords[START]
+                if END in product_type_conf
+                else self.get_product_type_cfg_value(
+                    "missionEndDate", today().isoformat()
+                )
+            )
+            return
+
+        if END in product_type_conf:
+            mapping = product_type_conf[START]
+            if not isinstance(mapping, list):
+                mapping = product_type_conf[END]
+            if isinstance(mapping, list):
+                # get time parameters (date, year, month, ...) from metadata mapping
+                input_mapping = mapping[0].replace("{{", "").replace("}}", "")
+                time_params = [
+                    values.split(":")[0].strip() for values in input_mapping.split(",")
+                ]
+                time_params = [
+                    tp.replace('"', "").replace("'", "") for tp in time_params
+                ]
+                # if startTime is not given but other time params (e.g. year/month/(day)) are given,
+                # no default date is required
+                in_keywords = True
+                for tp in time_params:
+                    if tp not in keywords:
+                        in_keywords = False
+                        break
+                if not in_keywords:
+                    keywords[START] = self.get_product_type_cfg_value(
+                        "missionStartDate", DEFAULT_MISSION_START_DATE
+                    )
+                    keywords[END] = (
+                        keywords[START]
+                        if END in product_type_conf
+                        else self.get_product_type_cfg_value(
+                            "missionEndDate", today().isoformat()
+                        )
+                    )
+
     def _get_product_type_queryables(
         self, product_type: Optional[str], alias: Optional[str], filters: dict[str, Any]
     ) -> QueryablesDict:
@@ -555,62 +547,60 @@ class ECMWFSearch(PostJsonSearch):
         :returns: fetched queryable parameters dict
         """
         product_type = kwargs.pop("productType")
-        product_type_config = self.config.products.get(product_type, {})
-        provider_product_type = (
-            product_type_config.get("ecmwf:dataset", None)
-            or product_type_config["productType"]
-        )
-        if "start" in kwargs:
-            kwargs["startTimeFromAscendingNode"] = kwargs.pop("start")
-        if "end" in kwargs:
-            kwargs["completionTimeFromAscendingNode"] = kwargs.pop("end")
+
+        pt_config = self.get_product_type_def_params(product_type)
+
+        default_values = deepcopy(pt_config)
+        default_values.pop("metadata_mapping", None)
+        filters = {**default_values, **kwargs}
+
+        if "start" in filters:
+            filters[START] = filters.pop("start")
+        if "end" in filters:
+            filters[END] = filters.pop("end")
 
         # extract default datetime
-        processed_kwargs = deepcopy(kwargs)
-        self._preprocess_search_params(processed_kwargs, product_type)
+        processed_filters = self._preprocess_search_params(
+            deepcopy(filters), product_type
+        )
 
         constraints_url = format_metadata(
             getattr(self.config, "discover_queryables", {}).get("constraints_url", ""),
-            **kwargs,
+            **filters,
         )
         constraints: list[dict[str, Any]] = self._fetch_data(constraints_url)
 
         form_url = format_metadata(
             getattr(self.config, "discover_queryables", {}).get("form_url", ""),
-            **kwargs,
+            **filters,
         )
         form: list[dict[str, Any]] = self._fetch_data(form_url)
 
-        formated_kwargs = self.format_as_provider_keyword(
-            product_type, processed_kwargs
+        formated_filters = self.format_as_provider_keyword(
+            product_type, processed_filters
         )
         # we re-apply kwargs input to consider override of year, month, day and time.
-        for key in kwargs:
-            if key.startswith("ecmwf:"):
-                formated_kwargs[key.replace("ecmwf:", "")] = kwargs[key]
-            elif key in (
-                "startTimeFromAscendingNode",
-                "completionTimeFromAscendingNode",
+        for k, v in {**default_values, **kwargs}.items():
+            key = k.removeprefix(ECMWF_PREFIX)
+
+            if key not in ALLOWED_KEYWORDS | {
+                START,
+                END,
                 "geom",
                 "geometry",
-            ):
-                formated_kwargs[key] = kwargs[key]
-            else:
+            }:
                 raise ValidationError(
                     f"{key} is not a queryable parameter for {self.provider}"
                 )
 
-        # we use non empty kwargs as default to integrate user inputs
+            formated_filters[key] = v
+
+        # we use non empty filters as default to integrate user inputs
         # it is needed because pydantic json schema does not represent "value"
         # but only "default"
         non_empty_formated: dict[str, Any] = {
             k: v
-            for k, v in formated_kwargs.items()
-            if v and (not isinstance(v, list) or all(v))
-        }
-        non_empty_kwargs: dict[str, Any] = {
-            k: v
-            for k, v in processed_kwargs.items()
+            for k, v in formated_filters.items()
             if v and (not isinstance(v, list) or all(v))
         }
 
@@ -636,7 +626,7 @@ class ECMWFSearch(PostJsonSearch):
             if not values_url:
                 return self.queryables_from_metadata_mapping(product_type)
             if "{" in values_url:
-                values_url = values_url.format(productType=provider_product_type)
+                values_url = values_url.format(**filters)
             data = self._fetch_data(values_url)
             available_values = data["constraints"]
             required_keywords = data.get("required", [])
@@ -644,18 +634,18 @@ class ECMWFSearch(PostJsonSearch):
         # To check if all keywords are queryable parameters, we check if they are in the
         # available values or the product type config (available values calculated from the
         # constraints might not include all queryables)
-        for keyword in kwargs:
+        for keyword in filters:
             if (
                 keyword
                 not in available_values.keys()
-                | product_type_config.keys()
+                | pt_config.keys()
                 | {
-                    "startTimeFromAscendingNode",
-                    "completionTimeFromAscendingNode",
+                    START,
+                    END,
                     "geom",
                 }
                 and keyword not in [f["name"] for f in form]
-                and keyword.replace("ecmwf:", "")
+                and keyword.removeprefix(ECMWF_PREFIX)
                 not in set(list(available_values.keys()) + [f["name"] for f in form])
             ):
                 raise ValidationError(f"{keyword} is not a queryable parameter")
@@ -669,24 +659,24 @@ class ECMWFSearch(PostJsonSearch):
             )
         else:
             queryables = self.queryables_by_values(
-                available_values, list(required_keywords), non_empty_kwargs
+                available_values, list(required_keywords), non_empty_formated
             )
 
         # ecmwf:date is replaced by start and end.
         # start and end filters are supported whenever combinations of "year", "month", "day" filters exist
         if (
-            queryables.pop("ecmwf:date", None)
-            or "ecmwf:year" in queryables
-            or "ecmwf:hyear" in queryables
+            queryables.pop(f"{ECMWF_PREFIX}date", None)
+            or f"{ECMWF_PREFIX}year" in queryables
+            or f"{ECMWF_PREFIX}hyear" in queryables
         ):
             queryables.update(
                 {
                     "start": Queryables.get_with_default(
-                        "start", non_empty_kwargs.get("startTimeFromAscendingNode")
+                        "start", processed_filters.get(START)
                     ),
                     "end": Queryables.get_with_default(
                         "end",
-                        non_empty_kwargs.get("completionTimeFromAscendingNode"),
+                        processed_filters.get(END),
                     ),
                 }
             )
@@ -747,14 +737,15 @@ class ECMWFSearch(PostJsonSearch):
                 raise ValidationError(
                     f"Parameter value as object is not supported: {keyword}={values}"
                 )
-            filter_v = values if isinstance(values, (list, tuple)) else [values]
 
             # We convert every single value to a list of string
+            filter_v = values if isinstance(values, (list, tuple)) else [values]
+
             # We strip values of superfluous quotes (added by mapping converter to_geojson).
             # ECMWF accept values with /to/. We need to split it to an array
             # ECMWF accept values in format val1/val2. We need to split it to an array
             sep = re.compile(r"/to/|/")
-            filter_v = [i for v in filter_v for i in sep.split(strip_quotes(v))]
+            filter_v = [i for v in filter_v for i in sep.split(str(v))]
 
             # special handling for time 0000 converted to 0 by pre-formating with metadata_mapping
             if keyword.split(":")[-1] == "time":
@@ -796,12 +787,12 @@ class ECMWFSearch(PostJsonSearch):
                     {value for c in constraints for value in c.get(keyword, [])}
                 )
                 # restore ecmwf: prefix before raising error
-                keyword = f"ecmwf:{keyword}"
+                keyword = ECMWF_PREFIX + keyword
 
                 all_keywords_str = ""
                 if len(parsed_keywords) > 1:
                     keywords = [
-                        f"ecmwf:{k}={pk}"
+                        f"{ECMWF_PREFIX + k}={pk}"
                         for k in parsed_keywords
                         if (pk := input_keywords.get(k))
                     ]
@@ -880,10 +871,6 @@ class ECMWFSearch(PostJsonSearch):
             if name == "area" and isinstance(default, dict):
                 default = list(default.values())
 
-            if default:
-                # We strip values of superfluous quotes (addded by mapping converter to_geojson).
-                default = strip_quotes(default)
-
             # sometimes form returns default as array instead of string
             if default and prop.get("type") == "string" and isinstance(default, list):
                 default = ",".join(default)
@@ -929,13 +916,11 @@ class ECMWFSearch(PostJsonSearch):
             # Needed to map constraints like "xxxx" to eodag parameter "ecmwf:xxxx"
             key = ecmwf_format(name)
 
-            default = defaults.get(key)
-
             queryables[key] = Annotated[
                 get_args(
                     json_field_definition_to_python(
                         {"type": "string", "title": name, "enum": values},
-                        default_value=strip_quotes(default) if default else None,
+                        default_value=defaults.get(name),
                         required=bool(key in required),
                     )
                 )
@@ -952,16 +937,26 @@ class ECMWFSearch(PostJsonSearch):
         :param properties: dict of properties to be formatted
         :return: dict of formatted properties
         """
-        parsed_properties = properties_from_json(
-            properties,
-            self.config.metadata_mapping,
+        properties["productType"] = product_type
+
+        # provider product type specific conf
+        product_type_def_params = self.get_product_type_def_params(
+            product_type, format_variables=properties
         )
-        available_properties = {
-            k: v
-            for k, v in parsed_properties.items()
-            if v not in [NOT_AVAILABLE, NOT_MAPPED]
-        }
-        return format_query_params(product_type, self.config, available_properties)
+
+        # Add to the query, the queryable parameters set in the provider product type definition
+        properties.update(
+            {
+                k: v
+                for k, v in product_type_def_params.items()
+                if k not in properties.keys()
+                and k in self.config.metadata_mapping.keys()
+                and isinstance(self.config.metadata_mapping[k], list)
+            }
+        )
+        qp, _ = self.build_query_string(product_type, properties)
+
+        return qp
 
     def _fetch_data(self, url: str) -> Any:
         """
@@ -1049,67 +1044,37 @@ class ECMWFSearch(PostJsonSearch):
             discovery_config=getattr(self.config, "discover_metadata", {}),
         )
 
-        if not product_type:
-            product_type = parsed_properties.get("productType", None)
+        properties = {
+            # use product_type_config as default properties
+            **getattr(self.config, "product_type_config", {}),
+            **{ecmwf_format(k): v for k, v in parsed_properties.items()},
+        }
+
+        def slugify(date_str: str) -> str:
+            return date_str.split("T")[0].replace("-", "")
 
         # build product id
-        id_prefix = (product_type or self.provider).upper()
-        if (
-            "startTimeFromAscendingNode" in parsed_properties
-            and parsed_properties["startTimeFromAscendingNode"] != "Not Available"
-            and "completionTimeFromAscendingNode" in parsed_properties
-            and parsed_properties["completionTimeFromAscendingNode"] != "Not Available"
-        ):
-            product_id = "%s_%s_%s_%s" % (
-                id_prefix,
-                parsed_properties["startTimeFromAscendingNode"]
-                .split("T")[0]
-                .replace("-", ""),
-                parsed_properties["completionTimeFromAscendingNode"]
-                .split("T")[0]
-                .replace("-", ""),
-                query_hash,
-            )
-        elif (
-            "startTimeFromAscendingNode" in parsed_properties
-            and parsed_properties["startTimeFromAscendingNode"] != "Not Available"
-        ):
-            product_id = "%s_%s_%s" % (
-                id_prefix,
-                parsed_properties["startTimeFromAscendingNode"]
-                .split("T")[0]
-                .replace("-", ""),
-                query_hash,
-            )
-        else:
-            product_id = f"{id_prefix}_{query_hash}"
-
-        parsed_properties["id"] = parsed_properties["title"] = product_id
-
-        # update downloadLink and orderLink
-        parsed_properties["_dc_qs"] = quote_plus(qs)
-        if parsed_properties["downloadLink"] != "Not Available":
-            parsed_properties["downloadLink"] += f"?{qs}"
-
-        # parse metadata needing downloadLink
-        dl_path = Fields("downloadLink")
-        dl_path_from_root = Child(Root(), dl_path)
-        for param, mapping in self.config.metadata_mapping.items():
-            if dl_path in mapping or dl_path_from_root in mapping:
-                parsed_properties.update(
-                    properties_from_json(parsed_properties, {param: mapping})
-                )
+        product_id = (product_type or kwargs.get("dataset") or self.provider).upper()
 
-        # use product_type_config as default properties
-        parsed_properties = dict(
-            getattr(self.config, "product_type_config", {}),
-            **parsed_properties,
-        )
+        start = properties.get(START, NOT_AVAILABLE)
+        end = properties.get(END, NOT_AVAILABLE)
+
+        if start != NOT_AVAILABLE:
+            product_id += f"_{slugify(start)}"
+            if end != NOT_AVAILABLE:
+                product_id += f"_{slugify(end)}"
+
+        product_id += f"_{query_hash}"
+
+        properties["id"] = properties["title"] = product_id
+
+        # used by server mode to generate downloadlink href
+        properties["_dc_qs"] = quote_plus(qs)
 
         product = EOProduct(
             provider=self.provider,
             productType=product_type,
-            properties=parsed_properties,
+            properties=properties,
         )
 
         return [
@@ -1186,17 +1151,15 @@ class MeteoblueSearch(ECMWFSearch):
         return [response.json()]
 
     def build_query_string(
-        self, product_type: str, **kwargs: Any
+        self, product_type: str, query_dict: dict[str, Any]
     ) -> tuple[dict[str, Any], str]:
         """Build The query string using the search parameters
 
         :param product_type: product type id
-        :param kwargs: keyword arguments to be used in the query string
+        :param query_dict: keyword arguments to be used in the query string
         :return: formatted query params and encode query string
         """
-        return QueryStringSearch.build_query_string(
-            self, product_type=product_type, **kwargs
-        )
+        return QueryStringSearch.build_query_string(self, product_type, query_dict)
 
 
 class WekeoECMWFSearch(ECMWFSearch):
@@ -1260,25 +1223,3 @@ class WekeoECMWFSearch(ECMWFSearch):
         :return: list containing the results from the provider in json format
         """
         return QueryStringSearch.do_search(self, *args, **kwargs)
-
-    def build_query_string(
-        self, product_type: str, **kwargs: Any
-    ) -> tuple[dict[str, Any], str]:
-        """Build The query string using the search parameters
-
-        :param product_type: product type id
-        :param kwargs: keyword arguments to be used in the query string
-        :return: formatted query params and encode query string
-        """
-        # Reorder kwargs to make sure year/month/day/time if set overwrite default datetime.
-        # strip_quotes to remove duplicated quotes like "'1_1'" produced by convertors like to_geojson.
-        priority_keys = [
-            "startTimeFromAscendingNode",
-            "completionTimeFromAscendingNode",
-        ]
-        ordered_kwargs = {k: kwargs[k] for k in priority_keys if k in kwargs}
-        ordered_kwargs.update({k: strip_quotes(v) for k, v in kwargs.items()})
-
-        return QueryStringSearch.build_query_string(
-            self, product_type=product_type, **ordered_kwargs
-        )
diff --git a/eodag/plugins/search/data_request_search.py b/eodag/plugins/search/data_request_search.py
index 3dab1924..bf3952a9 100644
--- a/eodag/plugins/search/data_request_search.py
+++ b/eodag/plugins/search/data_request_search.py
@@ -187,7 +187,7 @@ class DataRequestSearch(Search):
                 )
                 if other_product_for_mapping:
                     other_product_type_def_params = self.get_product_type_def_params(
-                        other_product_for_mapping,  # **kwargs
+                        other_product_for_mapping,
                     )
                     product_type_metadata_mapping.update(
                         other_product_type_def_params.get("metadata_mapping", {})
@@ -253,7 +253,7 @@ class DataRequestSearch(Search):
 
         # provider product type specific conf
         self.product_type_def_params = self.get_product_type_def_params(
-            product_type, **kwargs
+            product_type, format_variables=kwargs
         )
 
         # update config using provider product type definition metadata_mapping
@@ -263,7 +263,7 @@ class DataRequestSearch(Search):
         )
         if other_product_for_mapping:
             other_product_type_def_params = self.get_product_type_def_params(
-                other_product_for_mapping, **kwargs
+                other_product_for_mapping, format_variables=kwargs
             )
             self.config.metadata_mapping.update(
                 other_product_type_def_params.get("metadata_mapping", {})
diff --git a/eodag/plugins/search/qssearch.py b/eodag/plugins/search/qssearch.py
index 060f3853..151ce0b3 100644
--- a/eodag/plugins/search/qssearch.py
+++ b/eodag/plugins/search/qssearch.py
@@ -20,7 +20,6 @@ from __future__ import annotations
 import logging
 import re
 from copy import copy as copy_copy
-from datetime import datetime, timedelta
 from typing import (
     TYPE_CHECKING,
     Annotated,
@@ -48,7 +47,6 @@ import geojson
 import orjson
 import requests
 import yaml
-from dateutil.utils import today
 from jsonpath_ng import JSONPath
 from lxml import etree
 from pydantic import create_model
@@ -73,7 +71,6 @@ from eodag.plugins.search.base import Search
 from eodag.types import json_field_definition_to_python, model_fields_to_annotated
 from eodag.types.search_args import SortByList
 from eodag.utils import (
-    DEFAULT_MISSION_START_DATE,
     DEFAULT_SEARCH_TIMEOUT,
     GENERIC_PRODUCT_TYPE,
     HTTP_REQ_TIMEOUT,
@@ -533,7 +530,7 @@ class QueryStringSearch(Search):
 
             prep.info_message = "Fetching product types: {}".format(prep.url)
             prep.exception_message = (
-                "Skipping error while fetching product types for " "{} {} instance:"
+                "Skipping error while fetching product types for {} {} instance:"
             ).format(self.provider, self.__class__.__name__)
 
             # Query using appropriate method
@@ -567,7 +564,7 @@ class QueryStringSearch(Search):
                         result = result[0]
 
                     def conf_update_from_product_type_result(
-                        product_type_result: dict[str, Any]
+                        product_type_result: dict[str, Any],
                     ) -> None:
                         """Update ``conf_update_dict`` using given product type json response"""
                         # providers_config extraction
@@ -751,7 +748,7 @@ class QueryStringSearch(Search):
 
         # provider product type specific conf
         prep.product_type_def_params = (
-            self.get_product_type_def_params(product_type, **kwargs)
+            self.get_product_type_def_params(product_type, format_variables=kwargs)
             if product_type is not None
             else {}
         )
@@ -775,7 +772,7 @@ class QueryStringSearch(Search):
                 }
             )
 
-        qp, qs = self.build_query_string(product_type, **keywords)
+        qp, qs = self.build_query_string(product_type, keywords)
 
         prep.query_params = qp
         prep.query_string = qs
@@ -809,15 +806,15 @@ class QueryStringSearch(Search):
             self.config.metadata_mapping.update(metadata_mapping)
 
     def build_query_string(
-        self, product_type: str, **kwargs: Any
+        self, product_type: str, query_dict: dict[str, Any]
     ) -> tuple[dict[str, Any], str]:
         """Build The query string using the search parameters"""
         logger.debug("Building the query string that will be used for search")
-        query_params = format_query_params(product_type, self.config, kwargs)
+        query_params = format_query_params(product_type, self.config, query_dict)
 
         # Build the final query string, in one go without quoting it
         # (some providers do not operate well with urlencoded and quoted query strings)
-        def quote_via(x: Any, *_args, **_kwargs) -> str:
+        def quote_via(x: Any, *_args: Any, **_kwargs: Any) -> str:
             return x
 
         return (
@@ -1434,82 +1431,6 @@ class PostJsonSearch(QueryStringSearch):
 
     """
 
-    def _get_default_end_date_from_start_date(
-        self, start_datetime: str, product_type_conf: dict[str, Any]
-    ) -> str:
-        try:
-            start_date = datetime.fromisoformat(start_datetime)
-        except ValueError:
-            start_date = datetime.strptime(start_datetime, "%Y-%m-%dT%H:%M:%SZ")
-        if "completionTimeFromAscendingNode" in product_type_conf:
-            mapping = product_type_conf["completionTimeFromAscendingNode"]
-            # if date is mapped to year/month/(day), use end_date = start_date  else start_date + 1 day
-            # (default dates are only needed for ecmwf products where selected timespans should not be too large)
-            if isinstance(mapping, list) and "year" in mapping[0]:
-                end_date = start_date
-            else:
-                end_date = start_date + timedelta(days=1)
-            return end_date.isoformat()
-        return self.get_product_type_cfg_value("missionEndDate", today().isoformat())
-
-    def _check_date_params(
-        self, keywords: dict[str, Any], product_type: Optional[str]
-    ) -> None:
-        """checks if start and end date are present in the keywords and adds them if not"""
-        if (
-            "startTimeFromAscendingNode"
-            and "completionTimeFromAscendingNode" in keywords
-        ):
-            return
-
-        product_type_conf = getattr(self.config, "metadata_mapping", {})
-        if (
-            product_type
-            and product_type in self.config.products
-            and "metadata_mapping" in self.config.products[product_type]
-        ):
-            product_type_conf = self.config.products[product_type]["metadata_mapping"]
-        # start time given, end time missing
-        if "startTimeFromAscendingNode" in keywords:
-            keywords[
-                "completionTimeFromAscendingNode"
-            ] = self._get_default_end_date_from_start_date(
-                keywords["startTimeFromAscendingNode"], product_type_conf
-            )
-            return
-
-        if "completionTimeFromAscendingNode" in product_type_conf:
-            mapping = product_type_conf["startTimeFromAscendingNode"]
-            if not isinstance(mapping, list):
-                mapping = product_type_conf["completionTimeFromAscendingNode"]
-            if isinstance(mapping, list):
-                # get time parameters (date, year, month, ...) from metadata mapping
-                input_mapping = mapping[0].replace("{{", "").replace("}}", "")
-                time_params = [
-                    values.split(":")[0].strip() for values in input_mapping.split(",")
-                ]
-                time_params = [
-                    tp.replace('"', "").replace("'", "") for tp in time_params
-                ]
-                # if startTime is not given but other time params (e.g. year/month/(day)) are given,
-                # no default date is required
-                in_keywords = True
-                for tp in time_params:
-                    if tp not in keywords:
-                        in_keywords = False
-                        break
-                if not in_keywords:
-                    keywords[
-                        "startTimeFromAscendingNode"
-                    ] = self.get_product_type_cfg_value(
-                        "missionStartDate", DEFAULT_MISSION_START_DATE
-                    )
-                    keywords[
-                        "completionTimeFromAscendingNode"
-                    ] = self._get_default_end_date_from_start_date(
-                        keywords["startTimeFromAscendingNode"], product_type_conf
-                    )
-
     def query(
         self,
         prep: PreparedSearch = PreparedSearch(),
@@ -1532,7 +1453,7 @@ class PostJsonSearch(QueryStringSearch):
 
             # provider product type specific conf
             prep.product_type_def_params = self.get_product_type_def_params(
-                product_type, **kwargs
+                product_type, format_variables=kwargs
             )
         else:
             keywords = {
@@ -1546,7 +1467,7 @@ class PostJsonSearch(QueryStringSearch):
 
             # provider product type specific conf
             prep.product_type_def_params = self.get_product_type_def_params(
-                product_type, **kwargs
+                product_type, format_variables=kwargs
             )
 
             # Add to the query, the queryable parameters set in the provider product type definition
@@ -1559,10 +1480,8 @@ class PostJsonSearch(QueryStringSearch):
                     and isinstance(self.config.metadata_mapping[k], list)
                 }
             )
-            if getattr(self.config, "dates_required", False):
-                self._check_date_params(keywords, product_type)
 
-            qp, _ = self.build_query_string(product_type, **keywords)
+            qp, _ = self.build_query_string(product_type, keywords)
 
         for query_param, query_value in qp.items():
             if (
@@ -1845,24 +1764,24 @@ class StacSearch(PostJsonSearch):
         self.config.results_entry = results_entry
 
     def build_query_string(
-        self, product_type: str, **kwargs: Any
+        self, product_type: str, query_dict: dict[str, Any]
     ) -> tuple[dict[str, Any], str]:
         """Build The query string using the search parameters"""
         logger.debug("Building the query string that will be used for search")
 
         # handle opened time intervals
         if any(
-            k in kwargs
-            for k in ("startTimeFromAscendingNode", "completionTimeFromAscendingNode")
+            q in query_dict
+            for q in ("startTimeFromAscendingNode", "completionTimeFromAscendingNode")
         ):
-            kwargs.setdefault("startTimeFromAscendingNode", "..")
-            kwargs.setdefault("completionTimeFromAscendingNode", "..")
+            query_dict.setdefault("startTimeFromAscendingNode", "..")
+            query_dict.setdefault("completionTimeFromAscendingNode", "..")
 
-        query_params = format_query_params(product_type, self.config, kwargs)
+        query_params = format_query_params(product_type, self.config, query_dict)
 
         # Build the final query string, in one go without quoting it
         # (some providers do not operate well with urlencoded and quoted query strings)
-        def quote_via(x: Any, *_args, **_kwargs) -> str:
+        def quote_via(x: Any, *_args: Any, **_kwargs: Any) -> str:
             return x
 
         return (
@@ -2001,7 +1920,7 @@ class PostJsonSearchWithStacQueryables(StacSearch, PostJsonSearch):
         PostJsonSearch.__init__(self, provider, config)
 
     def build_query_string(
-        self, product_type: str, **kwargs: Any
+        self, product_type: str, query_dict: dict[str, Any]
     ) -> tuple[dict[str, Any], str]:
         """Build The query string using the search parameters"""
-        return PostJsonSearch.build_query_string(self, product_type, **kwargs)
+        return PostJsonSearch.build_query_string(self, product_type, query_dict)
diff --git a/eodag/plugins/search/static_stac_search.py b/eodag/plugins/search/static_stac_search.py
index 8da514c5..c4328c9a 100644
--- a/eodag/plugins/search/static_stac_search.py
+++ b/eodag/plugins/search/static_stac_search.py
@@ -142,7 +142,7 @@ class StaticStacSearch(StacSearch):
         product_type = kwargs.get("productType", prep.product_type)
         # provider product type specific conf
         self.product_type_def_params = (
-            self.get_product_type_def_params(product_type, **kwargs)
+            self.get_product_type_def_params(product_type, format_variables=kwargs)
             if product_type is not None
             else {}
         )
diff --git a/eodag/resources/product_types.yml b/eodag/resources/product_types.yml
index f23db8a3..5ddaf440 100644
--- a/eodag/resources/product_types.yml
+++ b/eodag/resources/product_types.yml
@@ -1773,6 +1773,179 @@ MTG_FCI_HRFI:
   title: FCI Level 1c High Resolution Image Data - MTG - 0 degree
   missionStartDate: "2024-09-24T00:00:00Z"
 
+MTG_FCI_ASR_BUFR:
+  abstract: |
+    The All-Sky Radiance (ASR) product is a segmented product that provides
+    FCI Level 1C data statistics within processing segments referred to as
+    Field-of-Regard (FoR). The statistics are computed on the L1C radiances
+    (for all FCI channels), brightness temperatures (for the eight IR
+    channels) and reflectances (for the eight visible and near-infrared
+    channels) and include the mean value, standard deviation, minimum and
+    maximum values within the FoR. The ASR product is available in BUFR
+    and netCDF format, every 10 minutes, at a spatial resolution of 16x16
+    pixels (IR) and 32x32 pixels (VIS).
+  instrument: FCI
+  platform: MTG
+  platformSerialIdentifier: MTG
+  processingLevel: L2
+  keywords: MTG,L2,FCI,ASR,Radiance,BUFR
+  sensorType: Imager
+  license: other
+  title: All Sky Radiance (BUFR) - MTG - 0 degree
+  missionStartDate: "2025-01-22T00:00:00Z"
+
+MTG_FCI_ASR_NETCDF:
+  abstract: |
+    The All-Sky Radiance (ASR) product is a segmented product that provides
+    FCI Level 1C data statistics within processing segments referred to as
+    Field-of-Regard (FoR). The statistics are computed on the L1C radiances
+    (for all FCI channels), brightness temperatures (for the eight IR
+    channels) and reflectances (for the eight visible and near-infrared
+    channels) and include the mean value, standard deviation, minimum and
+    maximum values within the FoR. The ASR product is available in BUFR
+    and netCDF format, every 10 minutes, at a spatial resolution of 16x16
+    pixels (IR) and 32x32 pixels (VIS).
+  instrument: FCI
+  platform: MTG
+  platformSerialIdentifier: MTG
+  processingLevel: L2
+  keywords: MTG,L2,FCI,ASR,Radiance,netCDF
+  sensorType: Imager
+  license: other
+  title: All Sky Radiance (netCDF) - MTG - 0 degree
+  missionStartDate: "2025-01-22T00:00:00Z"
+
+MTG_FCI_AMV_BUFR:
+  abstract: |
+    The Atmospheric Motion Vector (AMV) product is realised by tracking
+    clouds or water vapour features in consecutive FCI satellite images
+    based on feature tracking between each pair of consecutive repeat
+    cycles, leading to two intermediate AMV products for an image triplet.
+    The final product is then derived from these two intermediate products,
+    and includes information on wind speed, direction, height, and quality.
+    AMVs are extracted from the FCI VIS 0.8, IR 3.8 (night only), IR 10.5,
+    WV 6.3 and WV 7.3 channels. The AMV product is available in BUFR and
+    netCDF format, every 30 minutes.
+  instrument: FCI
+  platform: MTG
+  platformSerialIdentifier: MTG
+  processingLevel: L2
+  keywords: MTG,L2,FCI,AMV,Clouds,BUFR
+  sensorType: Imager
+  license: other
+  title: Atmospheric Motion Vectors (BUFR) - MTG - 0 degree
+  missionStartDate: "2025-01-22T00:00:00Z"
+
+MTG_FCI_AMV_NETCDF:
+  abstract: |
+    The Atmospheric Motion Vector (AMV) product is realised by tracking
+    clouds or water vapour features in consecutive FCI satellite images
+    based on feature tracking between each pair of consecutive repeat
+    cycles, leading to two intermediate AMV products for an image triplet.
+    The final product is then derived from these two intermediate products,
+    and includes information on wind speed, direction, height, and quality.
+    AMVs are extracted from the FCI VIS 0.8, IR 3.8 (night only), IR 10.5,
+    WV 6.3 and WV 7.3 channels. The AMV product is available in BUFR and
+    netCDF format, every 30 minutes.
+  instrument: FCI
+  platform: MTG
+  platformSerialIdentifier: MTG
+  processingLevel: L2
+  keywords: MTG,L2,FCI,AMV,Clouds,netCDF
+  sensorType: Imager
+  license: other
+  title: Atmospheric Motion Vectors (netCDF) - MTG - 0 degree
+  missionStartDate: "2025-01-22T00:00:00Z"
+
+MTG_FCI_CLM:
+  abstract: |
+    The central aim of the cloud mask (CLM) product is to identify cloudy and
+    cloud free FCI Level 1c pixels with high confidence. The product also
+    provides information on the presence of snow/sea ice, volcanic ash and
+    dust. This information is crucial both for spatiotemporal analyses of the
+    cloud coverage and for the subsequent retrieval of other meteorological
+    products that are only valid for cloudy (e.g. cloud properties) or clear
+    pixels (e.g. clear sky reflectance maps or global instability indices).
+    The algorithm is based on multispectral threshold techniques applied to
+    each pixel of the image. CLM is available in netCDF and GRIB format,
+    every 10 minutes, at a spatial resolution of 2 km at nadir.
+  instrument: FCI
+  platform: MTG
+  platformSerialIdentifier: MTG
+  processingLevel: L2
+  keywords: MTG,L2,FCI,CLM,Clouds
+  sensorType: Imager
+  license: other
+  title: Cloud Mask - MTG - 0 degree
+  missionStartDate: "2025-01-22T00:00:00Z"
+
+MTG_FCI_GII:
+  abstract: |
+    The Global Instability Index (GII) product provides information about
+    instability of the atmosphere and thus can identify regions of convective
+    potential. GII is a segmented product that uses an optimal estimation
+    scheme to fit clear-sky vertical profiles of temperature and humidity,
+    constrained by NWP forecast products, to FCI observations in the seven
+    channels WV6.3, WV7.3, IR8.7, IR9.7, IR10.5, IR12.3, and IR13.3. The
+    retrieved profiles are then used to compute atmospheric instability
+    indices: Lifted Index, K Index, Layer Precipitable Water, Total
+    Precipitable Water. The GII product is available in netCDF format, every
+    10 minutes, in 3x3 pixels (IR channels), leading to a spatial resolution
+    of 6 km at nadir.
+  instrument: FCI
+  platform: MTG
+  platformSerialIdentifier: MTG
+  processingLevel: L2
+  keywords: MTG,L2,FCI,GII,atmosphere
+  sensorType: Imager
+  license: other
+  title: Global Instability Indices - MTG - 0 degree
+  missionStartDate: "2025-01-22T00:00:00Z"
+
+MTG_FCI_OCA:
+  abstract: |
+    The Optimal Cloud Analysis (OCA) product uses an optimal estimation
+    retrieval scheme to retrieve cloud properties (phase, height and
+    microphysical properties) from visible, near-infrared and thermal
+    infrared FCI channels. The optimal estimation framework aims to ensure
+    that measurements and any prior information may be given appropriate
+    weight in the solution depending on error characteristics whether
+    instrumental or from modelling sources. The product can also contain
+    information on dust and volcanic ash clouds if these are flagged in the
+    corresponding Cloud Analysis Product. The OCA product is available in
+    netCDF format, every 10 minutes, at 2km spatial resolution at nadir.
+  instrument: FCI
+  platform: MTG
+  platformSerialIdentifier: MTG
+  processingLevel: L2
+  keywords: MTG,L2,FCI,OCA,Clouds
+  sensorType: Imager
+  license: other
+  title: Optimal Cloud Analysis - MTG - 0 degree
+  missionStartDate: "2025-01-22T00:00:00Z"
+
+MTG_FCI_OLR:
+  abstract: |
+    The Outgoing Longwave Radiation (OLR) product is important for Earth
+    radiation budget studies as well as for weather and climate model
+    validation purposes, since variations in OLR reflect the response of the
+    Earth-atmosphere system to solar diurnal forcing. The product is based on
+    a statistical relationship linking the radiance measured in each FCI
+    infrared channel to the top-of-atmosphere outgoing longwave flux
+    integrated over the full infrared spectrum. The computation is done for
+    each pixel considering the cloud cover characteristics (clear sky,
+    semi-transparent and opaque cloud cover). The OLR product is available in
+      netCDF format, every 10 minutes, at 2 km spatial resolution at nadir.
+  instrument: FCI
+  platform: MTG
+  platformSerialIdentifier: MTG
+  processingLevel: L2
+  keywords: MTG,L2,FCI,OLR,Radiation,LW
+  sensorType: Imager
+  license: other
+  title: Outgoing LW radiation at TOA - MTG - 0 degree
+  missionStartDate: "2025-01-22T00:00:00Z"
+
 # MARK: S3 SYNERGY
 # Synergy data products are generally combinations of OLCI and SLSTR instruments
 S3_SY_SYN:
@@ -4078,7 +4251,7 @@ GLOFAS_SEASONAL_REFORECAST:
   sensorType: ATMOSPHERIC
   license: other
   title: Seasonal reforecasts of river discharge and related data from the Global Flood Awareness System
-  missionStartDate: "1981-01-01T00:00:00Z"
+  missionStartDate: "1981-01-27T00:00:00Z"
 
 EFAS_FORECAST:
   abstract: |
@@ -4149,7 +4322,7 @@ EFAS_HISTORICAL:
   sensorType: ATMOSPHERIC
   license: other
   title: River discharge and related historical data from the European Flood Awareness System
-  missionStartDate: "1991-01-01T00:00:00Z"
+  missionStartDate: "1991-01-01T06:00:00Z"
 
 EFAS_REFORECAST:
   abstract: |
diff --git a/eodag/resources/providers.yml b/eodag/resources/providers.yml
index 3d7f8b5f..1ffeac28 100644
--- a/eodag/resources/providers.yml
+++ b/eodag/resources/providers.yml
@@ -1862,13 +1862,11 @@
     type: EcmwfApi
     auth_endpoint: https://api.ecmwf.int/v1
     metadata_mapping:
-      productType: '$.productType'
-      title: '$.id'
+      productType: $.productType
       startTimeFromAscendingNode: '{$.startTimeFromAscendingNode#to_iso_date}'
       completionTimeFromAscendingNode:
         - 'date={startTimeFromAscendingNode#to_iso_date}/to/{completionTimeFromAscendingNode#to_iso_date(-1,)}'
         - '{$.completionTimeFromAscendingNode#to_iso_date}'
-      id: '$.id'
       # The geographic extent of the product
       geometry:
         - 'area={geometry#to_nwse_bounds_str(/)}'
@@ -1876,22 +1874,23 @@
       defaultGeometry: 'POLYGON((180 -90, 180 90, -180 90, -180 -90, 180 -90))'
       # storageStatus set to ONLINE for consistency between providers
       storageStatus: '{$.null#replace_str("Not Available","ONLINE")}'
-      downloadLink: 'https://apps.ecmwf.int/datasets/data/{dataset}'
+      qs: $.qs
+      downloadLink: 'https://apps.ecmwf.int/datasets/data/{dataset}?{qs#to_geojson}'
   products:
     # See Archive Catalog in https://apps.ecmwf.int/archive-catalogue/
     # See available Public Datasets in https://apps.ecmwf.int/datasets/
     TIGGE_CF_SFC:
-      "ecmwf:class": ti
-      "ecmwf:dataset": tigge
-      "ecmwf:expver": prod
-      "ecmwf:type": cf
-      "ecmwf:levtype": sfc
-      "ecmwf:origin": ecmwf
-      "ecmwf:grid": 0.5/0.5
-      "ecmwf:param": 59/134/136/146/147/151/165/166/167/168/172/176/177/179/189/235/228002/228039/228139/228141/228144/228164/228228
-      "ecmwf:step": 0
-      "ecmwf:time": 00:00
-      "ecmwf:target": output
+      class: ti
+      dataset: tigge
+      expver: prod
+      type: cf
+      levtype: sfc
+      origin: ecmwf
+      grid: 0.5/0.5
+      param: 59/134/136/146/147/151/165/166/167/168/172/176/177/179/189/235/228002/228039/228139/228141/228144/228164/228228
+      step: 0
+      time: 00:00
+      target: output
     GENERIC_PRODUCT_TYPE:
       dataset: '{productType}'
 ---
@@ -1951,123 +1950,117 @@
     discover_queryables:
       fetch_url: null
       product_type_fetch_url: null
-      constraints_url: "https://ads.atmosphere.copernicus.eu/api/catalogue/v1/collections/{ecmwf:dataset}/constraints.json"
-      form_url: https://ads.atmosphere.copernicus.eu/api/catalogue/v1/collections/{ecmwf:dataset}/form.json
+      constraints_url: "https://ads.atmosphere.copernicus.eu/api/catalogue/v1/collections/{dataset}/constraints.json"
+      form_url: https://ads.atmosphere.copernicus.eu/api/catalogue/v1/collections/{dataset}/form.json
     metadata_mapping:
-      productType: '$.productType'
-      title: '$.id'
+      productType: $.productType
       startTimeFromAscendingNode: '{$.startTimeFromAscendingNode#to_iso_date}'
       completionTimeFromAscendingNode:
         - 'date={startTimeFromAscendingNode#to_iso_date}/{completionTimeFromAscendingNode#to_iso_date}'
         - '{$.completionTimeFromAscendingNode#to_iso_date}'
-      _date: '{startTimeFromAscendingNode}/{completionTimeFromAscendingNode}'
-      id: '$.id'
       # The geographic extent of the product
       geometry:
         - '{{"area": {geometry#to_nwse_bounds}}}'
-        - '$.geometry'
-      defaultGeometry: 'POLYGON((180 -90, 180 90, -180 90, -180 -90, 180 -90))'
-      storageStatus: OFFLINE
-      downloadLink: '$.null'
+        - $.geometry
       qs: $.qs
-      orderLink: 'https://ads.atmosphere.copernicus.eu/api/retrieve/v1/processes/{ecmwf:dataset}/execution?{{"inputs": {qs#to_geojson}}}'
+      orderLink: 'https://ads.atmosphere.copernicus.eu/api/retrieve/v1/processes/{dataset}/execution?{{"inputs": {qs#to_geojson}}}'
   products:
     # See available Public Datasets in https://ads.atmosphere.copernicus.eu/cdsapp#!/search?type=dataset
     CAMS_GAC_FORECAST:
-      "ecmwf:dataset": cams-global-atmospheric-composition-forecasts
-      "ecmwf:type": forecast
-      "ecmwf:data_format": grib
-      "ecmwf:variable": 10m_u_component_of_wind
-      "ecmwf:time": '00:00'
-      "ecmwf:leadtime_hour": '0'
+      dataset: cams-global-atmospheric-composition-forecasts
+      type: forecast
+      data_format: grib
+      variable: 10m_u_component_of_wind
+      time: 00:00
+      leadtime_hour: 0
     CAMS_GFE_GFAS:
-      "ecmwf:dataset": cams-global-fire-emissions-gfas
-      "ecmwf:data_format": grib
-      "ecmwf:variable": wildfire_combustion_rate
+      dataset: cams-global-fire-emissions-gfas
+      data_format: grib
+      variable: wildfire_combustion_rate
     CAMS_EU_AIR_QUALITY_FORECAST:
-      "ecmwf:dataset": cams-europe-air-quality-forecasts
-      "ecmwf:model": ensemble
-      "ecmwf:data_format": grib
-      "ecmwf:variable": ammonia
-      "ecmwf:type": forecast
-      "ecmwf:time": '00:00'
-      "ecmwf:level": '0'
-      "ecmwf:leadtime_hour": '0'
+      dataset: cams-europe-air-quality-forecasts
+      model: ensemble
+      data_format: grib
+      variable: ammonia
+      type: forecast
+      time: 00:00
+      level: 0
+      leadtime_hour: 0
     CAMS_EU_AIR_QUALITY_RE:
-      "ecmwf:dataset": cams-europe-air-quality-reanalyses
-      "ecmwf:type": validated_reanalysis
-      "ecmwf:data_format": zip
-      "ecmwf:variable": nitrogen_dioxide
-      "ecmwf:model": ensemble
-      "ecmwf:level": '0'
+      dataset: cams-europe-air-quality-reanalyses
+      type: validated_reanalysis
+      data_format: zip
+      variable: nitrogen_dioxide
+      model: ensemble
+      level: 0
       metadata_mapping:
         <<: *month_year
     CAMS_GRF:
-      "ecmwf:dataset": cams-global-radiative-forcings
-      "ecmwf:data_format": zip
-      "ecmwf:variable": radiative_forcing_of_carbon_dioxide
-      "ecmwf:forcing_type": instantaneous
-      "ecmwf:band": long_wave
-      "ecmwf:sky_type": all_sky
-      "ecmwf:level": surface
-      "ecmwf:version": '2'
+      dataset: cams-global-radiative-forcings
+      data_format: zip
+      variable: radiative_forcing_of_carbon_dioxide
+      forcing_type: instantaneous
+      band: long_wave
+      sky_type: all_sky
+      level: surface
+      version: 2
       metadata_mapping:
         <<: *month_year
     CAMS_GRF_AUX:
-      "ecmwf:dataset": cams-global-radiative-forcing-auxilliary-variables
-      "ecmwf:band": short_wave
-      "ecmwf:sky_type": clear_sky
-      "ecmwf:version": '1.5'
-      "ecmwf:data_format": zip
-      "ecmwf:variable": aerosol_radiation_effect
-      "ecmwf:aerosol_type": marine
-      "ecmwf:level": surface
+      dataset: cams-global-radiative-forcing-auxilliary-variables
+      band: short_wave
+      sky_type: clear_sky
+      version: 1.5
+      data_format: zip
+      variable: aerosol_radiation_effect
+      aerosol_type: marine
+      level: surface
       metadata_mapping:
         <<: *month_year
     CAMS_SOLAR_RADIATION:
-      "ecmwf:dataset": cams-solar-radiation-timeseries
-      "ecmwf:sky_type": clear
-      "ecmwf:time_step": 1minute
-      "ecmwf:time_reference": true_solar_time
-      "ecmwf:location":
-        "latitude": 0
-        "longitude": 0
-      "ecmwf:altitude": -999
-      "ecmwf:format": csv
-      metadata_mapping:
-        "ecmwf:latitude":
-          - '{{"location": {{"latitude": {"ecmwf:latitude"}, "longitude": {"ecmwf:longitude"}}}}}'
-          - '$."ecmwf:latitude"'
-        "ecmwf:longitude":
-          - '{{"location": {{"latitude": {"ecmwf:latitude"}, "longitude": {"ecmwf:longitude"}}}}}'
-          - '$."ecmwf:longitude"'
+      dataset: cams-solar-radiation-timeseries
+      sky_type: clear
+      time_step: 1minute
+      time_reference: true_solar_time
+      location:
+        latitude: 0
+        longitude: 0
+      altitude: -999
+      format: csv
+      metadata_mapping:
+        latitude:
+          - '{{"location": {{"latitude": {"latitude"}, "longitude": {"longitude"}}}}}'
+          - $."latitude"
+        longitude:
+          - '{{"location": {{"latitude": {"latitude"}, "longitude": {"longitude"}}}}}'
+          - $."longitude"
     CAMS_GREENHOUSE_EGG4_MONTHLY:
-      "ecmwf:dataset": cams-global-ghg-reanalysis-egg4-monthly
-      "ecmwf:data_format": grib
-      "ecmwf:variable": snow_albedo
-      "ecmwf:product_type": monthly_mean
+      dataset: cams-global-ghg-reanalysis-egg4-monthly
+      data_format: grib
+      variable: snow_albedo
+      product_type: monthly_mean
       metadata_mapping:
         <<: *month_year
     CAMS_GREENHOUSE_EGG4:
-      "ecmwf:dataset": cams-global-ghg-reanalysis-egg4
-      "ecmwf:data_format": grib
-      "ecmwf:variable": snow_albedo
-      "ecmwf:step": '0'
+      dataset: cams-global-ghg-reanalysis-egg4
+      data_format: grib
+      variable: snow_albedo
+      step: 0
     CAMS_GREENHOUSE_INVERSION:
-      "ecmwf:dataset": cams-global-greenhouse-gas-inversion
-      "ecmwf:version": latest
-      "ecmwf:variable": carbon_dioxide
-      "ecmwf:quantity": mean_column
-      "ecmwf:input_observations": surface
-      "ecmwf:time_aggregation": instantaneous
+      dataset: cams-global-greenhouse-gas-inversion
+      version: latest
+      variable: carbon_dioxide
+      quantity: mean_column
+      input_observations: surface
+      time_aggregation: instantaneous
       metadata_mapping:
         <<: *month_year
     CAMS_GLOBAL_EMISSIONS:
-      "ecmwf:dataset": cams-global-emission-inventories
-      "ecmwf:version": latest
-      "ecmwf:data_format": zip
-      "ecmwf:variable": acids
-      "ecmwf:source": anthropogenic
+      dataset: cams-global-emission-inventories
+      version: latest
+      data_format: zip
+      variable: acids
+      source: anthropogenic
       metadata_mapping:
         completionTimeFromAscendingNode:
           - |
@@ -2076,19 +2069,19 @@
             }}
           - '{$.completionTimeFromAscendingNode#to_iso_date}'
     CAMS_EAC4:
-      "ecmwf:dataset": cams-global-reanalysis-eac4
-      "ecmwf:data_format": grib
-      "ecmwf:variable": '2m_dewpoint_temperature'
-      "ecmwf:time": '00:00'
+      dataset: cams-global-reanalysis-eac4
+      data_format: grib
+      variable: 2m_dewpoint_temperature
+      time: 00:00
     CAMS_EAC4_MONTHLY:
-      "ecmwf:dataset": cams-global-reanalysis-eac4-monthly
-      "ecmwf:data_format": grib
-      "ecmwf:variable": 2m_dewpoint_temperature
-      "ecmwf:product_type": monthly_mean
+      dataset: cams-global-reanalysis-eac4-monthly
+      data_format: grib
+      variable: 2m_dewpoint_temperature
+      product_type: monthly_mean
       metadata_mapping:
         <<: *month_year
     GENERIC_PRODUCT_TYPE:
-      "ecmwf:dataset": '{productType}'
+      dataset: '{productType}'
 ---
 !provider # MARK: cop_cds
   name: cop_cds
@@ -2098,6 +2091,15 @@
     - host
   url: https://cds.climate.copernicus.eu
   # anchors to avoid duplications
+  anchor_day_month_year: &nday_month_year
+    completionTimeFromAscendingNode:
+      - |
+        {{
+          "year": {_date#interval_to_datetime_dict}["year"],
+          "month": {_date#interval_to_datetime_dict}["month"],
+          "nominal_day": {_date#interval_to_datetime_dict}["day"]
+        }}
+      - '{$.completionTimeFromAscendingNode#to_iso_date}'
   anchor_day_month_year: &day_month_year
     completionTimeFromAscendingNode:
       - |
@@ -2107,7 +2109,7 @@
           "day": {_date#interval_to_datetime_dict}["day"]
         }}
       - '{$.completionTimeFromAscendingNode#to_iso_date}'
-  anchor_day_month_year_time: &day_month_year_time
+  anchor_time_day_month_year: &time_day_month_year
     completionTimeFromAscendingNode:
       - |
         {{
@@ -2125,7 +2127,7 @@
         "month": {_date#interval_to_datetime_dict}["month"]
       }}
     - '{$.completionTimeFromAscendingNode#to_iso_date}'
-  anchor_month_year_time: &month_year_time
+  anchor_time_month_year: &time_month_year
     completionTimeFromAscendingNode:
       - |
         {{
@@ -2176,106 +2178,95 @@
     discover_queryables:
       fetch_url: null
       product_type_fetch_url: null
-      constraints_url: https://cds.climate.copernicus.eu/api/catalogue/v1/collections/{ecmwf:dataset}/constraints.json
-      form_url: https://cds.climate.copernicus.eu/api/catalogue/v1/collections/{ecmwf:dataset}/form.json
+      constraints_url: https://cds.climate.copernicus.eu/api/catalogue/v1/collections/{dataset}/constraints.json
+      form_url: https://cds.climate.copernicus.eu/api/catalogue/v1/collections/{dataset}/form.json
     metadata_mapping:
       productType: $.productType
-      title: $.id
       startTimeFromAscendingNode: '{$.startTimeFromAscendingNode#to_iso_utc_datetime}'
       completionTimeFromAscendingNode:
         - date={startTimeFromAscendingNode#to_iso_date}/{completionTimeFromAscendingNode#to_iso_date}
         - '{$.completionTimeFromAscendingNode#to_iso_utc_datetime}'
-      _date: '{startTimeFromAscendingNode#to_iso_date}/{completionTimeFromAscendingNode#to_iso_date}'
-      id: $.id
       # The geographic extent of the product
       geometry:
         - '{{"area": {geometry#to_nwse_bounds}}}'
         - $.geometry
-      defaultGeometry: POLYGON((180 -90, 180 90, -180 90, -180 -90, 180 -90))
-      storageStatus: OFFLINE
-      downloadLink: $.null
       qs: $.qs
-      orderLink: 'https://cds.climate.copernicus.eu/api/retrieve/v1/processes/{ecmwf:dataset}/execution?{{"inputs": {qs#to_geojson}}}'
-      # Copernicus CDS specific parameters
-      # to_geojson is used to handle versions like 1_0 that would be otherwise evaluated to 10 by the metadata mapping formating
-      "ecmwf:version":
-        - version
-        - '{$."ecmwf:version"#to_geojson}'
+      orderLink: 'https://cds.climate.copernicus.eu/api/retrieve/v1/processes/{dataset}/execution?{{"inputs": {qs#to_geojson}}}'
   products:
     # See available Public Datasets in https://cds.climate.copernicus.eu/cdsapp#!/search?type=dataset
     AG_ERA5:
-      "ecmwf:dataset": sis-agrometeorological-indicators
-      "ecmwf:version": '"1_1"'
-      "ecmwf:variable": cloud_cover
-      "ecmwf:statistic": 24_hour_mean
+      dataset: sis-agrometeorological-indicators
+      version: '1_1'
+      variable: cloud_cover
+      statistic: 24_hour_mean
       metadata_mapping:
         <<: *day_month_year
     ERA5_SL:
-      "ecmwf:dataset": reanalysis-era5-single-levels
-      "ecmwf:product_type": reanalysis
-      "ecmwf:variable": 10m_u_component_of_wind
-      "ecmwf:data_format": grib
-      "ecmwf:download_format": zip
+      dataset: reanalysis-era5-single-levels
+      product_type: reanalysis
+      variable: 10m_u_component_of_wind
+      data_format: grib
+      download_format: zip
       metadata_mapping:
-        <<: *day_month_year_time
+        <<: *time_day_month_year
     ERA5_PL:
-      "ecmwf:dataset": reanalysis-era5-pressure-levels
-      "ecmwf:product_type": reanalysis
-      "ecmwf:variable": geopotential
-      "ecmwf:data_format": grib
-      "ecmwf:download_format": zip
+      dataset: reanalysis-era5-pressure-levels
+      product_type: reanalysis
+      variable: geopotential
+      data_format: grib
+      download_format: zip
       metadata_mapping:
-        <<: *day_month_year_time
+        <<: *time_day_month_year
     ERA5_PL_MONTHLY:
-      "ecmwf:dataset": reanalysis-era5-pressure-levels-monthly-means
-      "ecmwf:product_type": monthly_averaged_reanalysis_by_hour_of_day
-      "ecmwf:variable": divergence
-      "ecmwf:pressure_level": '1'
-      "ecmwf:data_format": grib
-      "ecmwf:download_format": zip
-      metadata_mapping:
-        <<: *month_year_time
+      dataset: reanalysis-era5-pressure-levels-monthly-means
+      product_type: monthly_averaged_reanalysis_by_hour_of_day
+      variable: divergence
+      pressure_level: 1
+      data_format: grib
+      download_format: zip
+      metadata_mapping:
+        <<: *time_month_year
     ERA5_LAND:
-      "ecmwf:dataset": reanalysis-era5-land
-      "ecmwf:variable": 2m_dewpoint_temperature
-      "ecmwf:data_format": grib
-      "ecmwf:download_format": zip
+      dataset: reanalysis-era5-land
+      variable: 2m_dewpoint_temperature
+      data_format: grib
+      download_format: zip
       metadata_mapping:
-        <<: *day_month_year_time
+        <<: *time_day_month_year
     ERA5_LAND_MONTHLY:
-      "ecmwf:dataset": reanalysis-era5-land-monthly-means
-      "ecmwf:product_type": monthly_averaged_reanalysis_by_hour_of_day
-      "ecmwf:variable": 2m_dewpoint_temperature
-      "ecmwf:data_format": grib
-      "ecmwf:download_format": zip
+      dataset: reanalysis-era5-land-monthly-means
+      product_type: monthly_averaged_reanalysis_by_hour_of_day
+      variable: 2m_dewpoint_temperature
+      data_format: grib
+      download_format: zip
       metadata_mapping:
-        <<: *month_year_time
+        <<: *time_month_year
     ERA5_SL_MONTHLY:
-      "ecmwf:dataset": reanalysis-era5-single-levels-monthly-means
-      "ecmwf:product_type": monthly_averaged_reanalysis_by_hour_of_day
-      "ecmwf:data_format": grib
-      "ecmwf:download_format": zip
+      dataset: reanalysis-era5-single-levels-monthly-means
+      product_type: monthly_averaged_reanalysis_by_hour_of_day
+      data_format: grib
+      download_format: zip
       metadata_mapping:
-        <<: *month_year_time
+        <<: *time_month_year
     UERRA_EUROPE_SL:
-      "ecmwf:dataset": reanalysis-uerra-europe-single-levels
-      "ecmwf:origin": mescan_surfex
-      "ecmwf:variable": 10m_wind_direction
-      "ecmwf:time": 00:00
-      "ecmwf:data_format": grib
+      dataset: reanalysis-uerra-europe-single-levels
+      origin: mescan_surfex
+      variable: 10m_wind_direction
+      time: 00:00
+      data_format: grib
       metadata_mapping:
         <<: *day_month_year
     GLACIERS_DIST_RANDOLPH:
-      "ecmwf:dataset": insitu-glaciers-extent
-      "ecmwf:variable": glacier_area
-      "ecmwf:product_type": gridded
-      "ecmwf:version": rgi_6_0
-      "ecmwf:data_format": zip
+      dataset: insitu-glaciers-extent
+      variable: glacier_area
+      product_type: gridded
+      version: rgi_6_0
+      data_format: zip
     GRIDDED_GLACIERS_MASS_CHANGE:
-      "ecmwf:dataset": derived-gridded-glacier-mass-change
-      "ecmwf:variable": glacier_mass_change
-      "ecmwf:product_version": wgms_fog_2023_09
-      "ecmwf:data_format": zip
+      dataset: derived-gridded-glacier-mass-change
+      variable: glacier_mass_change
+      product_version: wgms_fog_2023_09
+      data_format: zip
       metadata_mapping:
         completionTimeFromAscendingNode:
           - |
@@ -2284,147 +2275,145 @@
             }}
           - '{$.completionTimeFromAscendingNode#get_hydrological_year}'
     SATELLITE_CARBON_DIOXIDE:
-      "ecmwf:dataset": satellite-carbon-dioxide
-      "ecmwf:processing_level": level_2
-      "ecmwf:variable": xco2
-      "ecmwf:sensor_and_algorithm": merged_emma
-      "ecmwf:version": '"4_5"'
+      dataset: satellite-carbon-dioxide
+      processing_level: level_2
+      variable: xco2
+      sensor_and_algorithm: merged_emma
+      version: '4_5'
       metadata_mapping:
         <<: *day_month_year
     SATELLITE_FIRE_BURNED_AREA:
-      "ecmwf:dataset": satellite-fire-burned-area
-      "ecmwf:origin": esa_cci
-      "ecmwf:sensor": modis
-      "ecmwf:variable": pixel_variables
-      "ecmwf:version": '5_1_1cds'
-      "ecmwf:region": "europe"
-      metadata_mapping:
-        <<: *day_month_year
+      dataset: satellite-fire-burned-area
+      origin: esa_cci
+      sensor: modis
+      variable: pixel_variables
+      version: '5_1_1cds'
+      region: europe
+      metadata_mapping:
+        <<: *nday_month_year
     SATELLITE_METHANE:
-      "ecmwf:dataset": satellite-methane
-      "ecmwf:processing_level": level_2
-      "ecmwf:variable": xch4
-      "ecmwf:sensor_and_algorithm": merged_emma
-      "ecmwf:version": '"4_5"'
+      dataset: satellite-methane
+      processing_level: level_2
+      variable: xch4
+      sensor_and_algorithm: merged_emma
+      version: '4_5'
       metadata_mapping:
         <<: *day_month_year
     SATELLITE_SEA_ICE_EDGE_TYPE:
-      "ecmwf:dataset": satellite-sea-ice-edge-type
-      "ecmwf:cdr_type": cdr
-      "ecmwf:region": northern_hemisphere
-      "ecmwf:variable":
+      dataset: satellite-sea-ice-edge-type
+      cdr_type: cdr
+      region: northern_hemisphere
+      variable:
         - sea_ice_edge
         - sea_ice_type
-      "ecmwf:version": '"3_0"'
+      version: '3_0'
       metadata_mapping:
         <<: *day_month_year
     SATELLITE_SEA_ICE_THICKNESS:
-      "ecmwf:dataset": satellite-sea-ice-thickness
-      "ecmwf:cdr_type": cdr
-      "ecmwf:satellite": envisat
-      "ecmwf:variable": all
-      "ecmwf:version": '"3_0"'
+      dataset: satellite-sea-ice-thickness
+      cdr_type: cdr
+      satellite: envisat
+      variable: all
+      version: '3_0'
       metadata_mapping:
         <<: *month_year
     SATELLITE_SEA_ICE_CONCENTRATION:
-      "ecmwf:dataset": satellite-sea-ice-concentration
-      "ecmwf:cdr_type": cdr
-      "ecmwf:origin": eumetsat_osi_saf
-      "ecmwf:region":
+      dataset: satellite-sea-ice-concentration
+      cdr_type: cdr
+      origin: eumetsat_osi_saf
+      region:
         - northern_hemisphere
         - southern_hemisphere
-      "ecmwf:sensor": ssmis
-      "ecmwf:temporal_aggregation": daily
-      "ecmwf:variable": all
-      "ecmwf:version": v3
+      sensor: ssmis
+      temporal_aggregation: daily
+      variable: all
+      version: v3
       metadata_mapping:
         <<: *day_month_year
     SATELLITE_SEA_LEVEL_GLOBAL:
-      "ecmwf:dataset": satellite-sea-level-global
-      "ecmwf:variable":
+      dataset: satellite-sea-level-global
+      variable:
         - daily
-      "ecmwf:version": vdt2021
+      version: vdt2021
       metadata_mapping:
         <<: *day_month_year
     SEASONAL_POSTPROCESSED_PL:
-      "ecmwf:dataset": seasonal-postprocessed-pressure-levels
-      "ecmwf:data_format": grib
-      "ecmwf:originating_centre": ecmwf
-      "ecmwf:system": '5'
-      "ecmwf:variable": geopotential_anomaly
-      "ecmwf:pressure_level": '10'
-      "ecmwf:product_type": ensemble_mean
-      "ecmwf:leadtime_month": '1'
+      dataset: seasonal-postprocessed-pressure-levels
+      data_format: grib
+      originating_centre: ecmwf
+      system: 5
+      variable: geopotential_anomaly
+      pressure_level: 10
+      product_type: ensemble_mean
+      leadtime_month: 1
       metadata_mapping:
         <<: *month_year
     SEASONAL_POSTPROCESSED_SL:
-      "ecmwf:dataset": seasonal-postprocessed-single-levels
-      "ecmwf:data_format": grib
-      "ecmwf:originating_centre": ecmwf
-      "ecmwf:system": '5'
-      "ecmwf:variable": 2m_dewpoint_temperature_anomaly
-      "ecmwf:product_type": ensemble_mean
-      "ecmwf:leadtime_month": '1'
+      dataset: seasonal-postprocessed-single-levels
+      data_format: grib
+      originating_centre: ecmwf
+      system: 5
+      variable: 2m_dewpoint_temperature_anomaly
+      product_type: ensemble_mean
+      leadtime_month: 1
       metadata_mapping:
         <<: *month_year
     SEASONAL_ORIGINAL_SL:
-      "ecmwf:dataset": seasonal-original-single-levels
-      "ecmwf:data_format": grib
-      "ecmwf:originating_centre": ecmwf
-      "ecmwf:system": '5'
-      "ecmwf:variable": 10m_u_component_of_wind
-      "ecmwf:leadtime_hour": '6'
-      "ecmwf:day": '01'
+      dataset: seasonal-original-single-levels
+      data_format: grib
+      originating_centre: ecmwf
+      system: 5
+      variable: 10m_u_component_of_wind
+      leadtime_hour: 6
       metadata_mapping:
-        <<: *month_year
+        <<: *day_month_year
     SEASONAL_ORIGINAL_PL:
-      "ecmwf:dataset": seasonal-original-pressure-levels
-      "ecmwf:data_format": grib
-      "ecmwf:originating_centre": ecmwf
-      "ecmwf:system": '5'
-      "ecmwf:variable": geopotential
-      "ecmwf:pressure_level": '10'
-      "ecmwf:leadtime_hour": '12'
-      "ecmwf:day": '01'
+      dataset: seasonal-original-pressure-levels
+      data_format: grib
+      originating_centre: ecmwf
+      system: 5
+      variable: geopotential
+      pressure_level: 10
+      leadtime_hour: 12
       metadata_mapping:
-        <<: *month_year
+        <<: *day_month_year
     SEASONAL_MONTHLY_PL:
-      "ecmwf:dataset": seasonal-monthly-pressure-levels
-      "ecmwf:data_format": grib
-      "ecmwf:originating_centre": ecmwf
-      "ecmwf:system": '5'
-      "ecmwf:variable": geopotential
-      "ecmwf:pressure_level": '10'
-      "ecmwf:product_type": monthly_mean
-      "ecmwf:leadtime_month": '1'
+      dataset: seasonal-monthly-pressure-levels
+      data_format: grib
+      originating_centre: ecmwf
+      system: 5
+      variable: geopotential
+      pressure_level: 10
+      product_type: monthly_mean
+      leadtime_month: 1
       metadata_mapping:
         <<: *month_year
     SEASONAL_MONTHLY_SL:
-      "ecmwf:dataset": seasonal-monthly-single-levels
-      "ecmwf:data_format": grib
-      "ecmwf:originating_centre": ecmwf
-      "ecmwf:system": '5'
-      "ecmwf:variable": 10m_u_component_of_wind
-      "ecmwf:product_type": monthly_mean
-      "ecmwf:leadtime_month": '1'
+      dataset: seasonal-monthly-single-levels
+      data_format: grib
+      originating_centre: ecmwf
+      system: 5
+      variable: 10m_u_component_of_wind
+      product_type: monthly_mean
+      leadtime_month: 1
       metadata_mapping:
         <<: *month_year
     SIS_HYDRO_MET_PROJ:
-      "ecmwf:dataset": sis-hydrology-meteorology-derived-projections
-      "ecmwf:data_format": zip
-      "ecmwf:product_type": climate_impact_indicators
-      "ecmwf:variable": 2m_air_temperature
-      "ecmwf:variable_type": absolute_change_from_reference_period
-      "ecmwf:processing_type": original
-      "ecmwf:time_aggregation": monthly_mean
-      "ecmwf:horizontal_resolution": 5_km
-      "ecmwf:experiment": degree_scenario
-      "ecmwf:rcm": cclm4_8_17
-      "ecmwf:gcm": ec_earth
-      "ecmwf:ensemble_member": r12i1p1
-      "ecmwf:period": 1_5_c
+      dataset: sis-hydrology-meteorology-derived-projections
+      data_format: zip
+      product_type: climate_impact_indicators
+      variable: 2m_air_temperature
+      variable_type: absolute_change_from_reference_period
+      processing_type: original
+      time_aggregation: monthly_mean
+      horizontal_resolution: 5_km
+      experiment: degree_scenario
+      rcm: cclm4_8_17
+      gcm: ec_earth
+      ensemble_member: r12i1p1
+      period: 1_5_c
     GENERIC_PRODUCT_TYPE:
-      "ecmwf:dataset": '{productType}'
+      dataset: '{productType}'
 ---
 !provider # MARK: sara
   name: sara
@@ -2738,7 +2727,8 @@
       defaultGeometry: 'POLYGON((180 -90, 180 90, -180 90, -180 -90, 180 -90))'
       productType: '$.queries[0].domain'
       storageStatus: '{$.requiresJobQueue#get_group_name((?P<ONLINE>False)|(?P<OFFLINE>True))}'
-      downloadLink: 'https://my.meteoblue.com/dataset/query'
+      qs: $.qs
+      downloadLink: https://my.meteoblue.com/dataset/query?{qs#to_geojson}
       # Meteoblue specific parameters
       datapoints: '$.datapoints'
       requiresJobQueue: '$.requiresJobQueue'
@@ -2755,7 +2745,8 @@
       timeIntervalsAlignment:
         - '{{"timeIntervalsAlignment": {timeIntervalsAlignment#to_geojson} }}'
         - '$.timeIntervalsAlignment'
-      orderLink: '{$.downloadLink#replace_str(r"^(.*)(\")(queries\")(.)",r"\1\2runOnJobQueue\2\4 true, \2\3\4")}'
+      orderLink: https://my.meteoblue.com/dataset/query?{qs#replace_str(r"^(.*)(\")(queries\")(.)",r"\1\2runOnJobQueue\2\4 true, \2\3\4")}
+      # orderLink: '{$.downloadLink#replace_str(r"^(.*)(\")(queries\")(.)",r"\1\2runOnJobQueue\2\4 true, \2\3\4")}'
   products:
     NEMSGLOBAL_TCDC:
       productType: NEMSGLOBAL
@@ -3835,9 +3826,9 @@
     startTimeFromAscendingNode:
       - |
         {{
-          "year": {startTimeFromAscendingNode#to_datetime_dict(list)}["year"],
-          "month": {startTimeFromAscendingNode#to_datetime_dict(list)}["month"],
-          "day": {startTimeFromAscendingNode#to_datetime_dict(list)}["day"],
+          "year": {_date#interval_to_datetime_dict}["year"],
+          "month": {_date#interval_to_datetime_dict}["month"],
+          "day": {_date#interval_to_datetime_dict}["day"],
           "time": {startTimeFromAscendingNode#get_ecmwf_time}
         }}
       - $.properties.startdate
@@ -3846,18 +3837,18 @@
     startTimeFromAscendingNode:
       - |
         {{
-          "year": {startTimeFromAscendingNode#to_datetime_dict(list)}["year"],
-          "month": {startTimeFromAscendingNode#to_datetime_dict(list)}["month"],
-          "day": {startTimeFromAscendingNode#to_datetime_dict(list)}["day"]
+          "year": {_date#interval_to_datetime_dict}["year"],
+          "month": {_date#interval_to_datetime_dict}["month"],
+          "day": {_date#interval_to_datetime_dict}["day"]
         }}
       - $.properties.startdate
     completionTimeFromAscendingNode: $.properties.enddate
-  anchor_year_month: &month_year
+  anchor_month_year: &month_year
     startTimeFromAscendingNode:
       - |
         {{
-          "year": {startTimeFromAscendingNode#to_datetime_dict(list)}["year"],
-          "month": {startTimeFromAscendingNode#to_datetime_dict(list)}["month"]
+          "year": {_date#interval_to_datetime_dict}["year"],
+          "month": {_date#interval_to_datetime_dict}["month"]
         }}
       - $.properties.startdate
     completionTimeFromAscendingNode: $.properties.enddate
@@ -3875,16 +3866,11 @@
       max_items_per_page: 200
     discover_product_types:
       fetch_url: null
-    available_values_url: 'https://gateway.prod.wekeo2.eu/hda-broker/api/v1/dataaccess/queryable/{productType}'
+    available_values_url: 'https://gateway.prod.wekeo2.eu/hda-broker/api/v1/dataaccess/queryable/{dataset}'
     metadata_mapping:
-      productType:
-        - '{{"dataset_id": "{productType}"}}'
-        - $.productType
       geometry:
         - '{{"bbox": {geometry#to_bounds}}}'
         - $.geometry
-      defaultGeometry: POLYGON((180 -90, 180 90, -180 90, -180 -90, 180 -90))
-      id: $.id
       startTimeFromAscendingNode:
         - '{{"startdate": "{startTimeFromAscendingNode#to_iso_utc_datetime}"}}'
         - $.properties.startdate
@@ -3892,25 +3878,26 @@
         - '{{"enddate": "{completionTimeFromAscendingNode#to_iso_utc_datetime}"}}'
         - $.properties.enddate
       downloadLink: $.properties.location
-      title: $.id
-      storageStatus: OFFLINE
-      orderLink: 'https://gateway.prod.wekeo2.eu/hda-broker/api/v1/dataaccess/download?{{"location": "{downloadLink}","product_id":"{id}", "dataset_id": "{productType}"}}'
+      dataset:
+        - dataset_id
+        - $.dataset
+      orderLink: 'https://gateway.prod.wekeo2.eu/hda-broker/api/v1/dataaccess/download?{{"location": "{downloadLink}","product_id":"{id}", "dataset_id": "{dataset}"}}'
   products:
     SATELLITE_CARBON_DIOXIDE:
-      productType: EO:ECMWF:DAT:SATELLITE_CARBON_DIOXIDE
-      ecmwf:processing_level: level_2
-      ecmwf:version: '4_5'
-      ecmwf:variable: xco2
-      ecmwf:sensor_and_algorithm: merged_emma
+      dataset: EO:ECMWF:DAT:SATELLITE_CARBON_DIOXIDE
+      processing_level: level_2
+      version: '4_5'
+      variable: xco2
+      sensor_and_algorithm: merged_emma
       metadata_mapping:
         <<: *day_month_year
     SATELLITE_FIRE_BURNED_AREA:
-      productType: EO:ECMWF:DAT:SATELLITE_FIRE_BURNED_AREA
-      ecmwf:origin: esa_cci
-      ecmwf:sensor: modis
-      ecmwf:variable: pixel_variables
-      ecmwf:version: 5_1_1cds
-      ecmwf:region: europe
+      dataset: EO:ECMWF:DAT:SATELLITE_FIRE_BURNED_AREA
+      origin: esa_cci
+      sensor: modis
+      variable: pixel_variables
+      version: 5_1_1cds
+      region: europe
       metadata_mapping:
         startTimeFromAscendingNode:
           - |
@@ -3922,133 +3909,133 @@
           - $.properties.startdate
         completionTimeFromAscendingNode: $.properties.enddate
     SATELLITE_METHANE:
-      productType: EO:ECMWF:DAT:SATELLITE_METHANE
-      ecmwf:processing_level: level_2
-      ecmwf:version: '4_0'
-      ecmwf:variable: xch4
-      ecmwf:sensor_and_algorithm: sciamachy_wfmd
+      dataset: EO:ECMWF:DAT:SATELLITE_METHANE
+      processing_level: level_2
+      version: '4_0'
+      variable: xch4
+      sensor_and_algorithm: sciamachy_wfmd
       metadata_mapping:
         <<: *day_month_year
     SATELLITE_SEA_ICE_EDGE_TYPE:
-      productType: EO:ECMWF:DAT:SATELLITE_SEA_ICE_EDGE_TYPE
-      ecmwf:variable: sea_ice_type
-      ecmwf:region: northern_hemisphere
-      ecmwf:cdr_type: cdr
-      ecmwf:version: '3_0'
+      dataset: EO:ECMWF:DAT:SATELLITE_SEA_ICE_EDGE_TYPE
+      variable: sea_ice_type
+      region: northern_hemisphere
+      cdr_type: cdr
+      version: '3_0'
       metadata_mapping:
         <<: *day_month_year
     SATELLITE_SEA_ICE_THICKNESS:
-      productType: EO:ECMWF:DAT:SATELLITE_SEA_ICE_THICKNESS
-      ecmwf:satellite: envisat
-      ecmwf:cdr_type: cdr
-      ecmwf:variable: all
-      ecmwf:version: '3_0'
+      dataset: EO:ECMWF:DAT:SATELLITE_SEA_ICE_THICKNESS
+      satellite: envisat
+      cdr_type: cdr
+      variable: all
+      version: '3_0'
       metadata_mapping:
         <<: *month_year
     SATELLITE_SEA_ICE_CONCENTRATION:
-      productType: EO:ECMWF:DAT:SATELLITE_SEA_ICE_CONCENTRATION
-      ecmwf:cdr_type: cdr
-      ecmwf:origin: eumetsat_osi_saf
-      ecmwf:region:
+      dataset: EO:ECMWF:DAT:SATELLITE_SEA_ICE_CONCENTRATION
+      cdr_type: cdr
+      origin: eumetsat_osi_saf
+      region:
         - northern_hemisphere
         - southern_hemisphere
-      ecmwf:sensor: ssmis
-      ecmwf:temporal_aggregation: daily
-      ecmwf:variable: all
-      ecmwf:version: v3
+      sensor: ssmis
+      temporal_aggregation: daily
+      variable: all
+      version: v3
       metadata_mapping:
         <<: *day_month_year
     SATELLITE_SEA_LEVEL_GLOBAL:
-      productType: EO:ECMWF:DAT:SATELLITE_SEA_LEVEL_GLOBAL
-      ecmwf:variable: daily
-      ecmwf:version: vdt2021
+      dataset: EO:ECMWF:DAT:SATELLITE_SEA_LEVEL_GLOBAL
+      variable: daily
+      version: vdt2021
       metadata_mapping:
         <<: *day_month_year
     SEASONAL_ORIGINAL_SL:
-      productType: EO:ECMWF:DAT:SEASONAL_ORIGINAL_SINGLE_LEVELS
-      ecmwf:variable: land_sea_mask
-      ecmwf:leadtime_hour: 0
-      ecmwf:originating_centre: ecmwf
-      ecmwf:system: 5
-      ecmwf:data_format: grib
+      dataset: EO:ECMWF:DAT:SEASONAL_ORIGINAL_SINGLE_LEVELS
+      variable: land_sea_mask
+      leadtime_hour: 0
+      originating_centre: ecmwf
+      system: 5
+      data_format: grib
       metadata_mapping:
         <<: *day_month_year
     SEASONAL_ORIGINAL_PL:
-      productType: EO:ECMWF:DAT:SEASONAL_ORIGINAL_PRESSURE_LEVELS
-      ecmwf:variable: geopotential
-      ecmwf:pressure_level: 10
-      ecmwf:leadtime_hour: 12
-      ecmwf:originating_centre: ecmwf
-      ecmwf:system: 5
-      ecmwf:data_format: grib
+      dataset: EO:ECMWF:DAT:SEASONAL_ORIGINAL_PRESSURE_LEVELS
+      variable: geopotential
+      pressure_level: 10
+      leadtime_hour: 12
+      originating_centre: ecmwf
+      system: 5
+      data_format: grib
       metadata_mapping:
         <<: *day_month_year
     SEASONAL_POSTPROCESSED_SL:
-      productType: EO:ECMWF:DAT:SEASONAL_POSTPROCESSED_SINGLE_LEVELS
-      ecmwf:product_type: ensemble_mean
-      ecmwf:originating_centre: ecmwf
-      ecmwf:variable: 2m_dewpoint_temperature_anomaly
-      ecmwf:leadtime_month: 1
-      ecmwf:origin: ecmwf
-      ecmwf:system: 5
-      ecmwf:data_format: grib
+      dataset: EO:ECMWF:DAT:SEASONAL_POSTPROCESSED_SINGLE_LEVELS
+      product_type: ensemble_mean
+      originating_centre: ecmwf
+      variable: 2m_dewpoint_temperature_anomaly
+      leadtime_month: 1
+      origin: ecmwf
+      system: 5
+      data_format: grib
       metadata_mapping:
         <<: *month_year
     SEASONAL_POSTPROCESSED_PL:
-      productType: EO:ECMWF:DAT:SEASONAL_POSTPROCESSED_PRESSURE_LEVELS
-      ecmwf:product_type: ensemble_mean
-      ecmwf:variable: geopotential_anomaly
-      ecmwf:pressure_level: 10
-      ecmwf:leadtime_month: 1
-      ecmwf:originating_centre: ecmwf
-      ecmwf:system: 5
-      ecmwf:data_format: grib
+      dataset: EO:ECMWF:DAT:SEASONAL_POSTPROCESSED_PRESSURE_LEVELS
+      product_type: ensemble_mean
+      variable: geopotential_anomaly
+      pressure_level: 10
+      leadtime_month: 1
+      originating_centre: ecmwf
+      system: 5
+      data_format: grib
       metadata_mapping:
         <<: *month_year
     SEASONAL_MONTHLY_SL:
-      productType: EO:ECMWF:DAT:SEASONAL_MONTHLY_SINGLE_LEVELS
-      ecmwf:variable: 10m_u_component_of_wind
-      ecmwf:product_type: monthly_mean
-      ecmwf:leadtime_month: 1
-      ecmwf:originating_centre: ecmwf
-      ecmwf:system: 5
-      ecmwf:data_format: grib
+      dataset: EO:ECMWF:DAT:SEASONAL_MONTHLY_SINGLE_LEVELS
+      variable: 10m_u_component_of_wind
+      product_type: monthly_mean
+      leadtime_month: 1
+      originating_centre: ecmwf
+      system: 5
+      data_format: grib
       metadata_mapping:
         <<: *month_year
     SEASONAL_MONTHLY_PL:
-      productType: EO:ECMWF:DAT:SEASONAL_MONTHLY_PRESSURE_LEVELS
-      ecmwf:variable: geopotential
-      ecmwf:product_type: monthly_mean
-      ecmwf:leadtime_month: 1
-      ecmwf:pressure_level: 10
-      ecmwf:originating_centre: ecmwf
-      ecmwf:system: 5
-      ecmwf:data_format: grib
+      dataset: EO:ECMWF:DAT:SEASONAL_MONTHLY_PRESSURE_LEVELS
+      variable: geopotential
+      product_type: monthly_mean
+      leadtime_month: 1
+      pressure_level: 10
+      originating_centre: ecmwf
+      system: 5
+      data_format: grib
       metadata_mapping:
         <<: *month_year
     GLACIERS_DIST_RANDOLPH:
-      productType: EO:ECMWF:DAT:INSITU_GLACIERS_EXTENT
-      ecmwf:variable: glacier_area
-      ecmwf:product_type: gridded
-      ecmwf:data_format: zip
-      ecmwf:version: rgi_6_0
+      dataset: EO:ECMWF:DAT:INSITU_GLACIERS_EXTENT
+      variable: glacier_area
+      product_type: gridded
+      data_format: zip
+      version: rgi_6_0
       metadata_mapping:
         <<: *day_month_year
     FIRE_HISTORICAL:
-      productType: EO:ECMWF:DAT:CEMS_FIRE_HISTORICAL_V1
-      ecmwf:product_type: reanalysis
-      ecmwf:variable: fire_weather_index
-      ecmwf:system_version: '4_1'
-      ecmwf:data_format: grib
-      ecmwf:grid: original_grid
-      ecmwf:dataset_type: consolidated_dataset
+      dataset: EO:ECMWF:DAT:CEMS_FIRE_HISTORICAL_V1
+      product_type: reanalysis
+      variable: fire_weather_index
+      system_version: '4_1'
+      data_format: grib
+      grid: original_grid
+      dataset_type: consolidated_dataset
       metadata_mapping:
         <<: *day_month_year
     GRIDDED_GLACIERS_MASS_CHANGE:
-      productType: EO:ECMWF:DAT:DERIVED_GRIDDED_GLACIER_MASS_CHANGE
-      ecmwf:variable: glacier_mass_change
-      ecmwf:data_format: zip
-      ecmwf:product_version: wgms_fog_2022_09
+      dataset: EO:ECMWF:DAT:DERIVED_GRIDDED_GLACIER_MASS_CHANGE
+      variable: glacier_mass_change
+      data_format: zip
+      product_version: wgms_fog_2022_09
       metadata_mapping:
         startTimeFromAscendingNode:
           - |
@@ -4058,44 +4045,44 @@
           - $.properties.startdate
         completionTimeFromAscendingNode: $.properties.enddate
     UERRA_EUROPE_SL:
-      productType: EO:ECMWF:DAT:REANALYSIS_UERRA_EUROPE_SINGLE_LEVELS
-      ecmwf:variable: total_cloud_cover
-      ecmwf:origin: uerra_harmonie
-      ecmwf:data_format: grib # netcdf format may fail
+      dataset: EO:ECMWF:DAT:REANALYSIS_UERRA_EUROPE_SINGLE_LEVELS
+      variable: total_cloud_cover
+      origin: uerra_harmonie
+      data_format: grib # netcdf format may fail
       metadata_mapping:
         <<: *time_day_month_year
     AG_ERA5:
-      productType: EO:ECMWF:DAT:SIS_AGROMETEOROLOGICAL_INDICATORS
-      ecmwf:variable: cloud_cover
-      ecmwf:version: '1_1'
-      ecmwf:time: '06_00'
-      ecmwf:data_format: zip
-      ecmwf:statistic: 24_hour_mean
+      dataset: EO:ECMWF:DAT:SIS_AGROMETEOROLOGICAL_INDICATORS
+      variable: cloud_cover
+      version: '1_1'
+      time: '06_00'
+      data_format: zip
+      statistic: 24_hour_mean
       metadata_mapping:
         <<: *day_month_year
     ERA5_SL:
-      productType: EO:ECMWF:DAT:REANALYSIS_ERA5_SINGLE_LEVELS
-      ecmwf:product_type: ensemble_mean
-      ecmwf:variable: 10m_u_component_of_wind
-      ecmwf:download_format: unarchived
-      ecmwf:data_format: grib # netcdf format may fail
+      dataset: EO:ECMWF:DAT:REANALYSIS_ERA5_SINGLE_LEVELS
+      product_type: ensemble_mean
+      variable: 10m_u_component_of_wind
+      download_format: unarchived
+      data_format: grib # netcdf format may fail
       metadata_mapping:
         <<: *time_day_month_year
     ERA5_PL:
-      productType: EO:ECMWF:DAT:REANALYSIS_ERA5_PRESSURE_LEVELS
-      ecmwf:product_type: ensemble_mean
-      ecmwf:variable: temperature
-      ecmwf:pressure_level: 1
-      ecmwf:data_format: grib
-      ecmwf:download_format: unarchived
+      dataset: EO:ECMWF:DAT:REANALYSIS_ERA5_PRESSURE_LEVELS
+      product_type: ensemble_mean
+      variable: temperature
+      pressure_level: 1
+      data_format: grib
+      download_format: unarchived
       metadata_mapping:
         <<: *time_day_month_year
     ERA5_SL_MONTHLY:
-      productType: EO:ECMWF:DAT:REANALYSIS_ERA5_SINGLE_LEVELS_MONTHLY_MEANS
-      ecmwf:product_type: monthly_averaged_ensemble_members
-      ecmwf:variable: 10m_u_component_of_wind
-      ecmwf:data_format: grib
-      ecmwf:download_format: unarchived
+      dataset: EO:ECMWF:DAT:REANALYSIS_ERA5_SINGLE_LEVELS_MONTHLY_MEANS
+      product_type: monthly_averaged_ensemble_members
+      variable: 10m_u_component_of_wind
+      data_format: grib
+      download_format: unarchived
       metadata_mapping:
         startTimeFromAscendingNode:
           - |
@@ -4107,12 +4094,12 @@
           - $.properties.startdate
         completionTimeFromAscendingNode: $.properties.enddate
     ERA5_PL_MONTHLY:
-      productType: EO:ECMWF:DAT:REANALYSIS_ERA5_PRESSURE_LEVELS_MONTHLY_MEANS
-      ecmwf:product_type: monthly_averaged_ensemble_members
-      ecmwf:variable: divergence
-      ecmwf:pressure_level: 1
-      ecmwf:data_format: grib
-      ecmwf:download_format: unarchived
+      dataset: EO:ECMWF:DAT:REANALYSIS_ERA5_PRESSURE_LEVELS_MONTHLY_MEANS
+      product_type: monthly_averaged_ensemble_members
+      variable: divergence
+      pressure_level: 1
+      data_format: grib
+      download_format: unarchived
       metadata_mapping:
         startTimeFromAscendingNode:
           - |
@@ -4124,18 +4111,18 @@
           - $.properties.startdate
         completionTimeFromAscendingNode: $.properties.enddate
     ERA5_LAND:
-      productType: EO:ECMWF:DAT:REANALYSIS_ERA5_LAND
-      ecmwf:variable: 2m_dewpoint_temperature
-      ecmwf:data_format: grib
-      ecmwf:download_format: unarchived
+      dataset: EO:ECMWF:DAT:REANALYSIS_ERA5_LAND
+      variable: 2m_dewpoint_temperature
+      data_format: grib
+      download_format: unarchived
       metadata_mapping:
         <<: *time_day_month_year
     ERA5_LAND_MONTHLY:
-      productType: EO:ECMWF:DAT:REANALYSIS_ERA5_LAND_MONTHLY_MEANS
-      ecmwf:product_type: monthly_averaged_reanalysis
-      ecmwf:variable: 2m_dewpoint_temperature
-      ecmwf:data_format: grib
-      ecmwf:download_format: unarchived
+      dataset: EO:ECMWF:DAT:REANALYSIS_ERA5_LAND_MONTHLY_MEANS
+      product_type: monthly_averaged_reanalysis
+      variable: 2m_dewpoint_temperature
+      data_format: grib
+      download_format: unarchived
       metadata_mapping:
         startTimeFromAscendingNode:
           - |
@@ -4147,16 +4134,16 @@
           - $.properties.startdate
         completionTimeFromAscendingNode: $.properties.enddate
     CAMS_EAC4:
-      productType: EO:ECMWF:DAT:CAMS_GLOBAL_REANALYSIS_EAC4
-      ecmwf:data_format: grib
-      ecmwf:variable: 2m_dewpoint_temperature
-      ecmwf:time: 00:00
+      dataset: EO:ECMWF:DAT:CAMS_GLOBAL_REANALYSIS_EAC4
+      data_format: grib
+      variable: 2m_dewpoint_temperature
+      time: 00:00
     CAMS_GLOBAL_EMISSIONS:
-      productType: EO:ECMWF:DAT:CAMS_GLOBAL_EMISSION_INVENTORIES
-      ecmwf:version: latest
-      ecmwf:data_format: zip
-      ecmwf:variable: acids
-      ecmwf:source: anthropogenic
+      dataset: EO:ECMWF:DAT:CAMS_GLOBAL_EMISSION_INVENTORIES
+      version: latest
+      data_format: zip
+      variable: acids
+      source: anthropogenic
       metadata_mapping:
         startTimeFromAscendingNode:
           - |
@@ -4166,95 +4153,95 @@
           - $.properties.startdate
         completionTimeFromAscendingNode: $.properties.enddate
     CAMS_EAC4_MONTHLY:
-      productType: EO:ECMWF:DAT:CAMS_GLOBAL_REANALYSIS_EAC4_MONTHLY
-      ecmwf:data_format: grib
-      ecmwf:variable: 2m_dewpoint_temperature
-      ecmwf:product_type: monthly_mean
+      dataset: EO:ECMWF:DAT:CAMS_GLOBAL_REANALYSIS_EAC4_MONTHLY
+      data_format: grib
+      variable: 2m_dewpoint_temperature
+      product_type: monthly_mean
       metadata_mapping:
         <<: *month_year
     CAMS_GREENHOUSE_INVERSION:
-      productType: EO:ECMWF:DAT:CAMS_GLOBAL_GREENHOUSE_GAS_INVERSION
-      ecmwf:version: latest
-      ecmwf:variable: carbon_dioxide
-      ecmwf:quantity: mean_column
-      ecmwf:input_observations: surface
-      ecmwf:time_aggregation: instantaneous
+      dataset: EO:ECMWF:DAT:CAMS_GLOBAL_GREENHOUSE_GAS_INVERSION
+      version: latest
+      variable: carbon_dioxide
+      quantity: mean_column
+      input_observations: surface
+      time_aggregation: instantaneous
       metadata_mapping:
         <<: *month_year
     CAMS_EU_AIR_QUALITY_RE:
-      productType: EO:ECMWF:DAT:CAMS_EUROPE_AIR_QUALITY_REANALYSES
-      ecmwf:type: validated_reanalysis
-      ecmwf:data_format: zip
-      ecmwf:variable: nitrogen_dioxide
-      ecmwf:model: ensemble
-      ecmwf:level: 0
+      dataset: EO:ECMWF:DAT:CAMS_EUROPE_AIR_QUALITY_REANALYSES
+      type: validated_reanalysis
+      data_format: zip
+      variable: nitrogen_dioxide
+      model: ensemble
+      level: 0
       metadata_mapping:
         <<: *month_year
     CAMS_GRF:
-      productType: EO:ECMWF:DAT:CAMS_GLOBAL_RADIATIVE_FORCINGS
-      ecmwf:data_format: zip
-      ecmwf:variable: radiative_forcing_of_carbon_dioxide
-      ecmwf:forcing_type: instantaneous
-      ecmwf:band: long_wave
-      ecmwf:sky_type: all_sky
-      ecmwf:level: surface
-      ecmwf:version: 2
+      dataset: EO:ECMWF:DAT:CAMS_GLOBAL_RADIATIVE_FORCINGS
+      data_format: zip
+      variable: radiative_forcing_of_carbon_dioxide
+      forcing_type: instantaneous
+      band: long_wave
+      sky_type: all_sky
+      level: surface
+      version: 2
       metadata_mapping:
         <<: *month_year
     CAMS_GRF_AUX:
-      productType: EO:ECMWF:DAT:CAMS_GLOBAL_RADIATIVE_FORCING_AUXILLIARY_VARIABLES
-      ecmwf:band: short_wave
-      ecmwf:sky_type: clear_sky
-      ecmwf:version: 1.5
-      ecmwf:data_format: zip
-      ecmwf:variable: aerosol_radiation_effect
-      ecmwf:aerosol_type: marine
-      ecmwf:level: surface
+      dataset: EO:ECMWF:DAT:CAMS_GLOBAL_RADIATIVE_FORCING_AUXILLIARY_VARIABLES
+      band: short_wave
+      sky_type: clear_sky
+      version: 1.5
+      data_format: zip
+      variable: aerosol_radiation_effect
+      aerosol_type: marine
+      level: surface
       metadata_mapping:
         <<: *month_year
     CAMS_GREENHOUSE_EGG4:
-      productType: EO:ECMWF:DAT:CAMS_GLOBAL_GHG_REANALYSIS_EGG4
-      ecmwf:data_format: grib
-      ecmwf:variable: snow_albedo
-      ecmwf:step: 0
+      dataset: EO:ECMWF:DAT:CAMS_GLOBAL_GHG_REANALYSIS_EGG4
+      data_format: grib
+      variable: snow_albedo
+      step: 0
     CAMS_GREENHOUSE_EGG4_MONTHLY:
-      productType: EO:ECMWF:DAT:CAMS_GLOBAL_GHG_REANALYSIS_EGG4_MONTHLY
-      ecmwf:data_format: grib
-      ecmwf:variable: snow_albedo
-      ecmwf:product_type: monthly_mean_by_hour_of_day
-      ecmwf:step: 3
-      ecmwf:time: 00:00
+      dataset: EO:ECMWF:DAT:CAMS_GLOBAL_GHG_REANALYSIS_EGG4_MONTHLY
+      data_format: grib
+      variable: snow_albedo
+      product_type: monthly_mean_by_hour_of_day
+      step: 3
+      time: 00:00
       metadata_mapping:
         <<: *month_year
     CAMS_EU_AIR_QUALITY_FORECAST:
-      productType: EO:ECMWF:DAT:CAMS_EUROPE_AIR_QUALITY_FORECASTS
-      ecmwf:model: ensemble
-      ecmwf:data_format: grib
-      ecmwf:variable: alder_pollen
-      ecmwf:type: forecast
-      ecmwf:time: 00:00
-      ecmwf:level: 0
-      ecmwf:leadtime_hour: 0
+      dataset: EO:ECMWF:DAT:CAMS_EUROPE_AIR_QUALITY_FORECASTS
+      model: ensemble
+      data_format: grib
+      variable: alder_pollen
+      type: forecast
+      time: 00:00
+      level: 0
+      leadtime_hour: 0
     CAMS_GAC_FORECAST:
-      productType: EO:ECMWF:DAT:CAMS_GLOBAL_ATMOSPHERIC_COMPOSITION_FORECASTS
-      ecmwf:type: forecast
-      ecmwf:data_format: grib
-      ecmwf:variable: 10m_u_component_of_wind
-      ecmwf:time: 00:00
-      ecmwf:leadtime_hour: 0
+      dataset: EO:ECMWF:DAT:CAMS_GLOBAL_ATMOSPHERIC_COMPOSITION_FORECASTS
+      type: forecast
+      data_format: grib
+      variable: 10m_u_component_of_wind
+      time: 00:00
+      leadtime_hour: 0
     CAMS_GFE_GFAS:
-      productType: EO:ECMWF:DAT:CAMS_GLOBAL_FIRE_EMISSIONS_GFAS
-      ecmwf:data_format: grib
-      ecmwf:variable: wildfire_combustion_rate
-      ecmwf:time: 00:00
-      ecmwf:leadtime_hour: 0
+      dataset: EO:ECMWF:DAT:CAMS_GLOBAL_FIRE_EMISSIONS_GFAS
+      data_format: grib
+      variable: wildfire_combustion_rate
+      time: 00:00
+      leadtime_hour: 0
     CAMS_SOLAR_RADIATION:
-      productType: EO:ECMWF:DAT:CAMS_SOLAR_RADIATION_TIMESERIES
-      ecmwf:sky_type: clear
-      ecmwf:time_step: 1minute
-      ecmwf:time_reference: true_solar_time
-      ecmwf:altitude: -999
-      ecmwf:format: csv
+      dataset: EO:ECMWF:DAT:CAMS_SOLAR_RADIATION_TIMESERIES
+      sky_type: clear
+      time_step: 1minute
+      time_reference: true_solar_time
+      altitude: -999
+      format: csv
       geometry: POLYGON((180 -90, 180 90, -180 90, -180 -90, 180 -90))
       metadata_mapping:
         geometry:
@@ -4787,44 +4774,40 @@
     discover_queryables:
       fetch_url: null
       product_type_fetch_url: null
-      constraints_url: "https://s3.central.data.destination-earth.eu/swift/v1/dedt-lumi-constraints/{ecmwf:dataset}.json"
+      constraints_url: "https://s3.central.data.destination-earth.eu/swift/v1/dedt-lumi-constraints/{dataset}.json"
     metadata_mapping:
-      productType: destination-earth
-      storageStatus: OFFLINE
       startTimeFromAscendingNode: '{$.startTimeFromAscendingNode#to_iso_date}'
       completionTimeFromAscendingNode:
         - '{{"date": "{startTimeFromAscendingNode#to_non_separated_date}/to/{completionTimeFromAscendingNode#to_non_separated_date}"}}'
         - '{$.completionTimeFromAscendingNode#to_iso_date}'
-      geometry: POLYGON((180 -90, 180 90, -180 90, -180 -90, 180 -90))
       qs: $.qs
       orderLink: 'https://polytope.lumi.apps.dte.destination-earth.eu/api/v1/requests/destination-earth?{{"verb": "retrieve", "request": {qs#to_geojson} }}'
-      downloadLink: $.null
   products:
     DT_EXTREMES:
-      "ecmwf:dataset": extremes-dt
-      "ecmwf:class": d1
-      "ecmwf:expver": "0001"
-      "ecmwf:stream": oper
-      "ecmwf:type": fc
-      "ecmwf:levtype": sfc
-      "ecmwf:step": "0"
-      "ecmwf:param": "31"
-      "ecmwf:time": "0000"
+      dataset: extremes-dt
+      class: d1
+      expver: "0001"
+      stream: oper
+      type: fc
+      levtype: sfc
+      step: 0
+      param: 31
+      time: "0000"
     DT_CLIMATE_ADAPTATION:
-      "ecmwf:dataset": climate-dt
-      "ecmwf:class": d1
-      "ecmwf:generation": 1
-      "ecmwf:expver": "0001"
-      "ecmwf:stream": clte
-      "ecmwf:type": fc
-      "ecmwf:activity": ScenarioMIP
-      "ecmwf:experiment": SSP3-7.0
-      "ecmwf:realization": 1
-      "ecmwf:model": IFS-NEMO
-      "ecmwf:param": 134/165/166
-      "ecmwf:resolution": high
-      "ecmwf:levtype": sfc
-      "ecmwf:time": "0000"
+      dataset: climate-dt
+      class: d1
+      generation: 1
+      expver: "0001"
+      stream: clte
+      type: fc
+      activity: ScenarioMIP
+      experiment: SSP3-7.0
+      realization: 1
+      model: IFS-NEMO
+      param: 134/165/166
+      resolution: high
+      levtype: sfc
+      time: "0000"
   download: !plugin
     type: HTTPDownload
     ssl_verify: true
@@ -5827,6 +5810,22 @@
       parentIdentifier: EO:EUM:DAT:0662
     MTG_FCI_HRFI:
       parentIdentifier: EO:EUM:DAT:0665
+    MTG_FCI_ASR_BUFR:
+      parentIdentifier: EO:EUM:DAT:0799
+    MTG_FCI_ASR_NETCDF:
+      parentIdentifier: EO:EUM:DAT:0677
+    MTG_FCI_AMV_BUFR:
+      parentIdentifier: EO:EUM:DAT:0998
+    MTG_FCI_AMV_NETCDF:
+      parentIdentifier: EO:EUM:DAT:0676
+    MTG_FCI_CLM:
+      parentIdentifier: EO:EUM:DAT:0678
+    MTG_FCI_GII:
+      parentIdentifier: EO:EUM:DAT:0683
+    MTG_FCI_OCA:
+      parentIdentifier: EO:EUM:DAT:0684
+    MTG_FCI_OLR:
+      parentIdentifier: EO:EUM:DAT:0685
     GENERIC_PRODUCT_TYPE:
       productType: '{productType}'
       parentIdentifier: '{parentIdentifier}'
@@ -6293,6 +6292,16 @@
   roles:
     - host
   url: https://ewds.climate.copernicus.eu
+  anchor_time_day_month_year: &time_day_month_year
+    completionTimeFromAscendingNode:
+      - |
+        {{
+          "year": {_date#interval_to_datetime_dict}["year"],
+          "month": {_date#interval_to_datetime_dict}["month"],
+          "day": {_date#interval_to_datetime_dict}["day"],
+          "time": {startTimeFromAscendingNode#get_ecmwf_time}
+        }}
+      - '{$.completionTimeFromAscendingNode#to_iso_date}'
   anchor_day_month_year: &day_month_year
     completionTimeFromAscendingNode:
       - |
@@ -6310,6 +6319,16 @@
           "month": {_date#interval_to_datetime_dict}["month"]
         }}
       - '{$.completionTimeFromAscendingNode#to_iso_date}'
+  anchor_time_hday_hmonth_hyear: &time_hday_hmonth_hyear
+    completionTimeFromAscendingNode:
+      - |
+        {{
+          "hyear": {_date#interval_to_datetime_dict}["year"],
+          "hmonth": {_date#interval_to_datetime_dict}["month"],
+          "hday": {_date#interval_to_datetime_dict}["day"],
+          "time": {startTimeFromAscendingNode#get_ecmwf_time}
+        }}
+      - '{$.completionTimeFromAscendingNode#to_iso_date}'
   anchor_hday_hmonth_hyear: &hday_hmonth_hyear
     completionTimeFromAscendingNode:
       - |
@@ -6370,156 +6389,140 @@
     discover_queryables:
       fetch_url: null
       product_type_fetch_url: null
-      constraints_url: https://ewds.climate.copernicus.eu/api/catalogue/v1/collections/{ecmwf:dataset}/constraints.json
-      form_url: https://ewds.climate.copernicus.eu/api/catalogue/v1/collections/{ecmwf:dataset}/form.json
+      constraints_url: https://ewds.climate.copernicus.eu/api/catalogue/v1/collections/{dataset}/constraints.json
+      form_url: https://ewds.climate.copernicus.eu/api/catalogue/v1/collections/{dataset}/form.json
     metadata_mapping:
-      productType: $.productType
-      title: $.id
       startTimeFromAscendingNode: '{$.startTimeFromAscendingNode#to_iso_utc_datetime}'
       completionTimeFromAscendingNode:
         - date={startTimeFromAscendingNode#to_iso_date}/{completionTimeFromAscendingNode#to_iso_date}
         - '{$.completionTimeFromAscendingNode#to_iso_utc_datetime}'
-      _date: '{startTimeFromAscendingNode#to_iso_date}/{completionTimeFromAscendingNode#to_iso_date}'
-      id: $.id
       # The geographic extent of the product
       geometry:
         - '{{"area": {geometry#to_nwse_bounds}}}'
         - $.geometry
-      defaultGeometry: POLYGON((180 -90, 180 90, -180 90, -180 -90, 180 -90))
-      storageStatus: '{$.null#replace_str("Not Available","OFFLINE")}'
-      downloadLink: $.null
       qs: $.qs
-      orderLink: 'https://ewds.climate.copernicus.eu/api/retrieve/v1/processes/{ecmwf:dataset}/execution?{{"inputs": {qs#to_geojson}}}'
-      "ecmwf:system_version":
-        - system_version
-        - '{$."ecmwf:system_version"#to_geojson}'
+      orderLink: 'https://ewds.climate.copernicus.eu/api/retrieve/v1/processes/{dataset}/execution?{{"inputs": {qs#to_geojson}}}'
   products:
     EFAS_HISTORICAL:
-      "ecmwf:dataset": efas-historical
-      "ecmwf:data_format": grib
-      "ecmwf:download_format": zip
-      "ecmwf:model_levels": surface_level
-      "ecmwf:variable": snow_depth_water_equivalent
-      "ecmwf:system_version": version_4_0
-      "ecmwf:time": 06:00
-      metadata_mapping:
-        <<: *hday_hmonth_hyear
+      dataset: efas-historical
+      data_format: grib
+      download_format: zip
+      model_levels: surface_level
+      variable: snow_depth_water_equivalent
+      system_version: version_4_0
+      metadata_mapping:
+        <<: *time_hday_hmonth_hyear
     EFAS_FORECAST:
-      "ecmwf:dataset": efas-forecast
-      "ecmwf:data_format": grib
-      "ecmwf:download_format": zip
-      "ecmwf:system_version": operational
-      "ecmwf:originating_centre": ecmwf
-      "ecmwf:product_type": control_forecast
-      "ecmwf:variable":
+      dataset: efas-forecast
+      data_format: grib
+      download_format: zip
+      system_version: operational
+      originating_centre: ecmwf
+      product_type: control_forecast
+      variable:
         - river_discharge_in_the_last_6_hours
         - river_discharge_in_the_last_24_hours
-      "ecmwf:model_levels": surface_level
-      "ecmwf:time": 00:00
-      "ecmwf:leadtime_hour": '24'
+      model_levels: surface_level
+      leadtime_hour: 24
       metadata_mapping:
-        <<: *day_month_year
+        <<: *time_day_month_year
     EFAS_SEASONAL:
-      "ecmwf:dataset": efas-seasonal
-      "ecmwf:system_version": operational
-      "ecmwf:data_format": grib
-      "ecmwf:download_format": zip
-      "ecmwf:variable": river_discharge_in_the_last_24_hours
-      "ecmwf:model_levels": surface_level
-      "ecmwf:leadtime_hour": '24'
+      dataset: efas-seasonal
+      system_version: operational
+      data_format: grib
+      download_format: zip
+      variable: river_discharge_in_the_last_24_hours
+      model_levels: surface_level
+      leadtime_hour: 24
       metadata_mapping:
         <<: *month_year
     EFAS_REFORECAST:
-      "ecmwf:dataset": efas-reforecast
-      "ecmwf:system_version": version_4_0
-      "ecmwf:data_format": grib
-      "ecmwf:download_format": zip
-      "ecmwf:product_type": control_forecast
-      "ecmwf:variable": river_discharge_in_the_last_6_hours
-      "ecmwf:model_levels": surface_level
-      "ecmwf:leadtime_hour": '0'
-      "ecmwf:hday": '03'
+      dataset: efas-reforecast
+      system_version: version_4_0
+      data_format: grib
+      download_format: zip
+      product_type: control_forecast
+      variable: river_discharge_in_the_last_6_hours
+      model_levels: surface_level
+      leadtime_hour: 0
       metadata_mapping:
-        <<: *hmonth_hyear
+        <<: *hday_hmonth_hyear
     EFAS_SEASONAL_REFORECAST:
-      "ecmwf:dataset": efas-seasonal-reforecast
-      "ecmwf:system_version": version_5_0
-      "ecmwf:data_format": grib
-      "ecmwf:download_format": zip
-      "ecmwf:variable": river_discharge_in_the_last_24_hours
-      "ecmwf:model_levels": surface_level
-      "ecmwf:leadtime_hour": '24'
+      dataset: efas-seasonal-reforecast
+      system_version: version_5_0
+      data_format: grib
+      download_format: zip
+      variable: river_discharge_in_the_last_24_hours
+      model_levels: surface_level
+      leadtime_hour: 24
       metadata_mapping:
         <<: *hmonth_hyear
     GLOFAS_HISTORICAL:
-      "ecmwf:dataset": cems-glofas-historical
-      "ecmwf:system_version": version_4_0
-      "ecmwf:variable": river_discharge_in_the_last_24_hours
-      "ecmwf:data_format": grib2
-      "ecmwf:download_format": zip
-      "ecmwf:hydrological_model": lisflood
-      "ecmwf:product_type": consolidated
+      dataset: cems-glofas-historical
+      system_version: version_4_0
+      variable: river_discharge_in_the_last_24_hours
+      data_format: grib2
+      download_format: zip
+      hydrological_model: lisflood
+      product_type: consolidated
       metadata_mapping:
         <<: *hday_hmonth_hyear
     GLOFAS_FORECAST:
-      "ecmwf:dataset": cems-glofas-forecast
-      "ecmwf:system_version": operational
-      "ecmwf:variable": river_discharge_in_the_last_24_hours
-      "ecmwf:data_format": grib2
-      "ecmwf:hydrological_model": htessel_lisflood
-      "ecmwf:product_type": control_forecast
-      "ecmwf:leadtime_hour": '24'
-      "ecmwf:download_format": zip
+      dataset: cems-glofas-forecast
+      system_version: operational
+      variable: river_discharge_in_the_last_24_hours
+      data_format: grib2
+      hydrological_model: htessel_lisflood
+      product_type: control_forecast
+      leadtime_hour: 24
+      download_format: zip
       metadata_mapping:
         <<: *day_month_year
     GLOFAS_SEASONAL:
-      "ecmwf:dataset": cems-glofas-seasonal
-      "ecmwf:variable": river_discharge_in_the_last_24_hours
-      "ecmwf:data_format": grib2
-      "ecmwf:download_format": zip
-      "ecmwf:system_version": operational
-      "ecmwf:hydrological_model": htessel_lisflood
-      "ecmwf:leadtime_hour": '24'
+      dataset: cems-glofas-seasonal
+      variable: river_discharge_in_the_last_24_hours
+      data_format: grib2
+      download_format: zip
+      system_version: operational
+      hydrological_model: htessel_lisflood
+      leadtime_hour: 24
       metadata_mapping:
         <<: *month_year
     GLOFAS_SEASONAL_REFORECAST:
-      "ecmwf:dataset": cems-glofas-seasonal-reforecast
-      "ecmwf:data_format": grib2
-      "ecmwf:download_format": zip
-      "ecmwf:variable": river_discharge_in_the_last_24_hours
-      "ecmwf:system_version": version_4_0
-      "ecmwf:hydrological_model": lisflood
-      "ecmwf:leadtime_hour": '24'
-      "ecmwf:hday": '27'
+      dataset: cems-glofas-seasonal-reforecast
+      data_format: grib2
+      download_format: zip
+      variable: river_discharge_in_the_last_24_hours
+      system_version: version_4_0
+      hydrological_model: lisflood
+      leadtime_hour: 24
       metadata_mapping:
         <<: *hmonth_hyear
     GLOFAS_REFORECAST:
-      "ecmwf:dataset": cems-glofas-reforecast
-      "ecmwf:variable": river_discharge_in_the_last_24_hours
-      "ecmwf:data_format": grib2
-      "ecmwf:download_format": zip
-      "ecmwf:system_version": version_3_1
-      "ecmwf:hydrological_model": lisflood
-      "ecmwf:product_type": control_reforecast
-      "ecmwf:leadtime_hour": '24'
-      "ecmwf:hday": '03'
+      dataset: cems-glofas-reforecast
+      variable: river_discharge_in_the_last_24_hours
+      data_format: grib2
+      download_format: zip
+      system_version: version_3_1
+      hydrological_model: lisflood
+      product_type: control_reforecast
+      leadtime_hour: 24
       metadata_mapping:
-        <<: *hmonth_hyear
+        <<: *hday_hmonth_hyear
     FIRE_HISTORICAL:
-      "ecmwf:dataset": cems-fire-historical-v1
-      "ecmwf:grid": original_grid
-      "ecmwf:dataset_type": consolidated_dataset
-      "ecmwf:product_type": reanalysis
-      "ecmwf:variable": build_up_index
-      "ecmwf:system_version": '4_1'
-      "ecmwf:data_format": grib
+      dataset: cems-fire-historical-v1
+      grid: original_grid
+      dataset_type: consolidated_dataset
+      product_type: reanalysis
+      variable: build_up_index
+      system_version: '4_1'
+      data_format: grib
       metadata_mapping:
         <<: *day_month_year
     FIRE_SEASONAL:
-      "ecmwf:dataset": cems-fire-seasonal
-      "ecmwf:day": '01'
-      "ecmwf:leadtime_hour": '12'
-      "ecmwf:variable": build_up_index
-      "ecmwf:release_version": '5'
+      dataset: cems-fire-seasonal
+      leadtime_hour: 12
+      variable: build_up_index
+      release_version: 5
       metadata_mapping:
-        <<: *month_year
+        <<: *day_month_year
diff --git a/eodag/rest/stac.py b/eodag/rest/stac.py
index ad91b24e..01e1843b 100644
--- a/eodag/rest/stac.py
+++ b/eodag/rest/stac.py
@@ -97,6 +97,7 @@ IGNORED_ITEM_PROPERTIES = [
     "qs",
     "defaultGeometry",
     "_date",
+    "productType",
 ]
 
 
diff --git a/eodag/rest/types/queryables.py b/eodag/rest/types/queryables.py
index 1f06dce8..2246ebe5 100644
--- a/eodag/rest/types/queryables.py
+++ b/eodag/rest/types/queryables.py
@@ -25,8 +25,9 @@ from pydantic import (
     Field,
     SerializationInfo,
     SerializerFunctionWrapHandler,
-    computed_field,
+    field_validator,
     model_serializer,
+    model_validator,
 )
 
 from eodag.rest.types.eodag_search import EODAGSearch
@@ -35,6 +36,7 @@ from eodag.types import python_field_definition_to_json
 
 if TYPE_CHECKING:
     from pydantic.fields import FieldInfo
+    from typing_extensions import Self
 
 
 class QueryablesGetParams(BaseModel):
@@ -42,6 +44,8 @@ class QueryablesGetParams(BaseModel):
 
     collection: Optional[str] = Field(default=None, serialization_alias="productType")
     datetime: Optional[str] = Field(default=None)
+    start_datetime: Optional[str] = Field(default=None)
+    end_datetime: Optional[str] = Field(default=None)
 
     model_config = ConfigDict(extra="allow", frozen=True)
 
@@ -50,21 +54,29 @@ class QueryablesGetParams(BaseModel):
         dumped: dict[str, Any] = handler(self)
         return {EODAGSearch.to_eodag(k): v for k, v in dumped.items()}
 
-    # use [prop-decorator] mypy error code when mypy==1.12 is released
-    @computed_field  # type: ignore[misc]
-    @property
-    def start_datetime(self) -> Optional[str]:
-        """Extract start_datetime property from datetime"""
-        start = str_to_interval(self.datetime)[0]
-        return start.strftime("%Y-%m-%dT%H:%M:%SZ") if start else None
-
-    # use [prop-decorator] mypy error code when mypy==1.12 is released
-    @computed_field  # type: ignore[misc]
-    @property
-    def end_datetime(self) -> Optional[str]:
-        """Extract end_datetime property from datetime"""
-        end = str_to_interval(self.datetime)[1]
-        return end.strftime("%Y-%m-%dT%H:%M:%SZ") if end else None
+    @field_validator("datetime", "start_datetime", "end_datetime", mode="before")
+    def validate_datetime(cls, value: Any) -> Optional[str]:
+        """datetime, start_datetime and end_datetime must be a string"""
+        if isinstance(value, list):
+            return value[0]
+
+        return value
+
+    @model_validator(mode="after")
+    def compute_datetimes(self: Self) -> Self:
+        """Start datetime must be a string"""
+        if not self.datetime:
+            return self
+
+        start, end = str_to_interval(self.datetime)
+
+        if not self.start_datetime and start:
+            self.start_datetime = start.strftime("%Y-%m-%dT%H:%M:%SZ")
+
+        if not self.end_datetime and end:
+            self.end_datetime = end.strftime("%Y-%m-%dT%H:%M:%SZ")
+
+        return self
 
 
 class StacQueryableProperty(BaseModel):
diff --git a/eodag/utils/__init__.py b/eodag/utils/__init__.py
index b4c47295..b875bb55 100644
--- a/eodag/utils/__init__.py
+++ b/eodag/utils/__init__.py
@@ -1048,7 +1048,7 @@ def format_string(key: str, str_to_format: Any, **format_variables: Any) -> Any:
         # defaultdict usage will return "" for missing keys in format_args
         try:
             result = str_to_format.format_map(defaultdict(str, **format_variables))
-        except TypeError as e:
+        except (ValueError, TypeError) as e:
             raise MisconfiguredError(
                 f"Unable to format str={str_to_format} using {str(format_variables)}: {str(e)}"
             )


Test patch:
diff --git a/tests/units/test_core.py b/tests/units/test_core.py
index 12ef1e6d..3c0a68a8 100644
--- a/tests/units/test_core.py
+++ b/tests/units/test_core.py
@@ -247,6 +247,14 @@ class TestCore(TestCoreBase):
         "MTG_LI_LEF": ["eumetsat_ds"],
         "MTG_FCI_FDHSI": ["eumetsat_ds"],
         "MTG_FCI_HRFI": ["eumetsat_ds"],
+        "MTG_FCI_ASR_BUFR": ["eumetsat_ds"],
+        "MTG_FCI_ASR_NETCDF": ["eumetsat_ds"],
+        "MTG_FCI_AMV_BUFR": ["eumetsat_ds"],
+        "MTG_FCI_AMV_NETCDF": ["eumetsat_ds"],
+        "MTG_FCI_CLM": ["eumetsat_ds"],
+        "MTG_FCI_GII": ["eumetsat_ds"],
+        "MTG_FCI_OCA": ["eumetsat_ds"],
+        "MTG_FCI_OLR": ["eumetsat_ds"],
         "MODIS_MCD43A4": ["aws_eos", "planetary_computer"],
         "MO_GLOBAL_ANALYSISFORECAST_PHY_001_024": ["cop_marine", "dedl"],
         "MO_GLOBAL_ANALYSISFORECAST_BGC_001_028": ["cop_marine", "dedl"],
@@ -1542,26 +1550,26 @@ class TestCore(TestCoreBase):
         self.dag.list_queryables(provider="cop_cds", productType="ERA5_SL")
         defaults = {
             "productType": "ERA5_SL",
-            "ecmwf:product_type": "reanalysis",
-            "ecmwf:dataset": "reanalysis-era5-single-levels",
-            "ecmwf:data_format": "grib",
-            "ecmwf:download_format": "zip",
-            "ecmwf:variable": "10m_u_component_of_wind",
+            "product_type": "reanalysis",
+            "dataset": "reanalysis-era5-single-levels",
+            "data_format": "grib",
+            "download_format": "zip",
+            "variable": "10m_u_component_of_wind",
         }
         mock_discover_queryables.assert_called_once_with(plugin, **defaults)
         mock_discover_queryables.reset_mock()
         # default values + additional param
         res = self.dag.list_queryables(
-            provider="cop_cds", **{"productType": "ERA5_SL", "ecmwf:month": "02"}
+            provider="cop_cds", **{"productType": "ERA5_SL", "month": "02"}
         )
         params = {
             "productType": "ERA5_SL",
-            "ecmwf:product_type": "reanalysis",
-            "ecmwf:dataset": "reanalysis-era5-single-levels",
-            "ecmwf:data_format": "grib",
-            "ecmwf:download_format": "zip",
-            "ecmwf:variable": "10m_u_component_of_wind",
-            "ecmwf:month": "02",
+            "product_type": "reanalysis",
+            "dataset": "reanalysis-era5-single-levels",
+            "data_format": "grib",
+            "download_format": "zip",
+            "variable": "10m_u_component_of_wind",
+            "month": "02",
         }
         mock_discover_queryables.assert_called_once_with(plugin, **params)
         self.assertFalse(res.additional_properties)
@@ -1569,15 +1577,15 @@ class TestCore(TestCoreBase):
 
         # unset default values
         self.dag.list_queryables(
-            provider="cop_cds", **{"productType": "ERA5_SL", "ecmwf:data_format": ""}
+            provider="cop_cds", **{"productType": "ERA5_SL", "data_format": ""}
         )
         defaults = {
             "productType": "ERA5_SL",
-            "ecmwf:product_type": "reanalysis",
-            "ecmwf:dataset": "reanalysis-era5-single-levels",
-            "ecmwf:variable": "10m_u_component_of_wind",
-            "ecmwf:data_format": "",
-            "ecmwf:download_format": "zip",
+            "product_type": "reanalysis",
+            "dataset": "reanalysis-era5-single-levels",
+            "variable": "10m_u_component_of_wind",
+            "data_format": "",
+            "download_format": "zip",
         }
         mock_discover_queryables.assert_called_once_with(plugin, **defaults)
 
diff --git a/tests/units/test_metadata_mapping.py b/tests/units/test_metadata_mapping.py
index d86430b1..990f6f53 100644
--- a/tests/units/test_metadata_mapping.py
+++ b/tests/units/test_metadata_mapping.py
@@ -244,11 +244,27 @@ class TestMetadataFormatter(unittest.TestCase):
 
     def test_convert_replace_str(self):
         to_format = r"{fieldname#replace_str(r'(.*) is (.*)',r'\1 was \2...')}"
+
+        # Test with a string
         self.assertEqual(
             format_metadata(to_format, fieldname="this is foo"),
             "this was foo...",
         )
 
+        # Test with a dictionary
+        self.assertEqual(
+            format_metadata(to_format, fieldname={"key": "this is foo"}),
+            '{"key": "this was foo"}...',
+        )
+
+        # Test with a list (should fail)
+        with self.assertRaises(TypeError):
+            format_metadata(to_format, fieldname=["this is foo"])
+
+        # Test with an integer (should fail)
+        with self.assertRaises(TypeError):
+            format_metadata(to_format, fieldname=123)
+
     def test_convert_recursive_sub_str(self):
         to_format = r"{fieldname#recursive_sub_str(r'(.*) is (.*)',r'\1 was \2...')}"
         self.assertEqual(
@@ -506,14 +522,20 @@ class TestMetadataFormatter(unittest.TestCase):
         self.assertEqual(
             format_metadata(to_format, text="20231019-20231020"),
             str(
-                {"startDate": "2023-10-19T00:00:00Z", "endDate": "2023-10-20T00:00:00Z"}
+                {
+                    "startDate": "2023-10-19T00:00:00Z",
+                    "endDate": "2023-10-20T00:00:00Z",
+                }
             ),
         )
         to_format = "{text#get_dates_from_string(_)}"
         self.assertEqual(
             format_metadata(to_format, text="20231019_20231020"),
             str(
-                {"startDate": "2023-10-19T00:00:00Z", "endDate": "2023-10-20T00:00:00Z"}
+                {
+                    "startDate": "2023-10-19T00:00:00Z",
+                    "endDate": "2023-10-20T00:00:00Z",
+                }
             ),
         )
 
@@ -558,7 +580,10 @@ class TestMetadataFormatter(unittest.TestCase):
                 to_format, product_id="mfwamglocep_2021121102_R20211212_12H.nc"
             ),
             str(
-                {"min_date": "2021-12-11T02:00:00Z", "max_date": "2021-12-12T02:00:00Z"}
+                {
+                    "min_date": "2021-12-11T02:00:00Z",
+                    "max_date": "2021-12-12T02:00:00Z",
+                }
             ),
         )
         self.assertEqual(
@@ -567,7 +592,10 @@ class TestMetadataFormatter(unittest.TestCase):
                 product_id="glo12_rg_1d-m_20220601-20220601_3D-uovo_hcst_R20220615.nc",
             ),
             str(
-                {"min_date": "2022-06-01T00:00:00Z", "max_date": "2022-06-02T00:00:00Z"}
+                {
+                    "min_date": "2022-06-01T00:00:00Z",
+                    "max_date": "2022-06-02T00:00:00Z",
+                }
             ),
         )
 
diff --git a/tests/units/test_search_plugins.py b/tests/units/test_search_plugins.py
index 9918d6e9..b6fe8108 100644
--- a/tests/units/test_search_plugins.py
+++ b/tests/units/test_search_plugins.py
@@ -1047,7 +1047,7 @@ class TestSearchPluginPostJsonSearch(BaseSearchPluginTest):
             "https://gateway.prod.wekeo2.eu/hda-broker/api/v1/dataaccess/search",
             json={
                 "startdate": "2003-01-01T00:00:00.000Z",
-                "enddate": "2003-01-02T00:00:00.000Z",
+                "enddate": "2003-01-01T00:00:00.000Z",
                 "dataset_id": "EO:ECMWF:DAT:CAMS_GLOBAL_REANALYSIS_EAC4",
                 "data_format": "grib",
                 "variable": "2m_dewpoint_temperature",
@@ -2256,7 +2256,7 @@ class TestSearchPluginECMWFSearch(unittest.TestCase):
         )
         exp_end_date = datetime.strptime(
             DEFAULT_MISSION_START_DATE, "%Y-%m-%dT%H:%M:%SZ"
-        ) + timedelta(days=1)
+        )
         self.assertIn(
             eoproduct.properties["completionTimeFromAscendingNode"],
             exp_end_date.strftime("%Y-%m-%d"),
@@ -2276,7 +2276,7 @@ class TestSearchPluginECMWFSearch(unittest.TestCase):
             eoproduct.properties["startTimeFromAscendingNode"], "1985-10-26"
         )
         self.assertEqual(
-            eoproduct.properties["completionTimeFromAscendingNode"], "1985-10-27"
+            eoproduct.properties["completionTimeFromAscendingNode"], "1985-10-26"
         )
 
     def test_plugins_search_ecmwfsearch_without_producttype(self):
@@ -2410,7 +2410,7 @@ class TestSearchPluginECMWFSearch(unittest.TestCase):
         # ones of the constraints file to an empty value to check if its associated queryable has no default value
         eodag_formatted_data_format = "ecmwf:data_format"
         provider_data_format = eodag_formatted_data_format.replace("ecmwf:", "")
-        self.assertIn(eodag_formatted_data_format, default_values)
+        self.assertIn(provider_data_format, default_values)
         self.assertIn(provider_data_format, [param["name"] for param in form])
         data_format_in_form = [
             param for param in form if param["name"] == provider_data_format