Repository: apache/airflow (language: python)

Issue:
AIP-84 Migrate public endpoints from api_connexion to the new Rest API
### Body

https://github.com/apache/airflow/issues/42366 is taking care of migrating private endpoints.

This issue focuses on migrating all the public ones. Ideally they shouldn't change too much and be feature rich compare to airflow 2.x.

An exemple PR can be found here https://github.com/apache/airflow/pull/42196


### Tasks (total ~ 81 endpoints) 
####  Backfill (6 endpoints) @dstandish 
- `list_backfills` https://github.com/apache/airflow/pull/43496 @dstandish 
- `get_backfill` https://github.com/apache/airflow/pull/43496 @dstandish 
- `pause_backfill` https://github.com/apache/airflow/pull/43496 @dstandish 
- `unpause_backfill` https://github.com/apache/airflow/pull/43496 @dstandish 
- `cancel_backfill` https://github.com/apache/airflow/pull/43496 @dstandish 
- `create_backfill` https://github.com/apache/airflow/pull/43496 @dstandish 

#### Config (2 endpoints) @rawwar 
- https://github.com/apache/airflow/issues/42745 @jason810496 

#### Connection @bugraoz93  (6 endpoints)
- `api_connexion/endpoints/connection_endpoint.py` `delete_connection` https://github.com/apache/airflow/issues/42559 @bugraoz93 
- `api_connexion/endpoints/connection_endpoint.py` `get_connection` https://github.com/apache/airflow/issues/42590 @rawwar  
- `api_connexion/endpoints/connection_endpoint.py` `get_connections`  https://github.com/apache/airflow/issues/42591 @bugraoz93 
- `api_connexion/endpoints/connection_endpoint.py` `patch_connection` https://github.com/apache/airflow/issues/42592 @bugraoz93 
- `api_connexion/endpoints/connection_endpoint.py` `post_connection` https://github.com/apache/airflow/issues/42593 @bugraoz93 
- `api_connexion/endpoints/connection_endpoint.py` `test_connection` https://github.com/apache/airflow/issues/42594 @bugraoz93 

####  DAG (5 endpoints)
- `api_connexion/endpoints/dag_endpoint.py` `get_dag_details`  https://github.com/apache/airflow/issues/42453 @omkar-foss
- `api_connexion/endpoints/dag_endpoint.py` `patch_dag` https://github.com/apache/airflow/issues/42468 @pierrejeambrun 
- `api_connexion/endpoints/dag_endpoint.py` `patch_dags` https://github.com/apache/airflow/issues/42544 @pierrejeambrun 
- `api_connexion/endpoints/dag_endpoint.py` `get_dag` https://github.com/apache/airflow/issues/42652 @omkar-foss 
- `api_connexion/endpoints/dag_endpoint.py` `delete_dag` https://github.com/apache/airflow/issues/42650 @omkar-foss

#### Dag Parsing (1 endpoint)
- https://github.com/apache/airflow/pull/44416/ @prabhusneha 

#### Dag Runs @rawwar (9 endpoints)
- All dag run endpoints`api_connexion/endpoints/dag_run_endpoint.py` https://github.com/apache/airflow/issues/42701 @rawwar 

#### Dag Source (1 endpoint) 
- https://github.com/apache/airflow/issues/42876 @omkar-foss 

#### Dag Stats (1 endpoint)
- https://github.com/apache/airflow/issues/42877 @omkar-foss 

#### Dag Warnings (1 endpoint) @rawwar 
- https://github.com/apache/airflow/issues/42748 @rawwar 

#### Dataset / Assets (10 endpoints) @amoghrajesh
- `get_assets` https://github.com/apache/airflow/pull/43783 @amoghrajesh
- `get_asset` https://github.com/apache/airflow/pull/43825 @amoghrajesh 
- `get_upstream_asset_events` https://github.com/apache/airflow/pull/43874 @amoghrajesh
- `get_asset_events` https://github.com/apache/airflow/pull/43881 @vatsrahul1001
- `create_asset_event` https://github.com/apache/airflow/pull/43984 @vatsrahul1001 
- `get_asset_queued_events` https://github.com/apache/airflow/pull/44139 @vatsrahul1001 
- `delete_asset_queued_events` https://github.com/apache/airflow/pull/44138 @vatsrahul1001 
- `get_dag_asset_queued_events` https://github.com/apache/airflow/pull/44124 @amoghrajesh
- `delete_dag_asset_queued_events` https://github.com/apache/airflow/pull/44129 @amoghrajesh
- `delete_dag_asset_queued_event` https://github.com/apache/airflow/pull/44130 @amoghrajesh
- `get_dag_asset_queued_event` https://github.com/apache/airflow/pull/44128 @amoghrajesh

#### Event Logs (2 endpoints)

- https://github.com/apache/airflow/issues/43326 @jason810496 
- https://github.com/apache/airflow/issues/43327 @jason810496 

#### Extra Link (1 endpoint)
- https://github.com/apache/airflow/pull/44277 @prabhusneha

#### Monitor (2 endpoints) 
- https://github.com/apache/airflow/issues/42937 @bbovenzi
- https://github.com/apache/airflow/issues/42879 @omkar-foss 

#### Import Error (2 endpoints) @jason810496
- https://github.com/apache/airflow/issues/43594 @jason810496
https://github.com/apache/airflow/issues/43595 @jason810496

#### Log (1 endpoint)
- https://github.com/apache/airflow/pull/44238 @utkarsharma2 

#### Plugin (1 endpoint)
- https://github.com/apache/airflow/pull/43125 @pierrejeambrun 

#### Pool (5 endpoints) @pierrejeambrun
- https://github.com/apache/airflow/pull/43165 @pierrejeambrun 
- https://github.com/apache/airflow/pull/43221 @pierrejeambrun
- https://github.com/apache/airflow/pull/43223 @pierrejeambrun
- https://github.com/apache/airflow/pull/43266 @pierrejeambrun
- https://github.com/apache/airflow/pull/43317 @pierrejeambrun

#### Provider (1 endpoint)
 - https://github.com/apache/airflow/pull/43159 @pierrejeambrun

#### Task (2 endpoints) @omkar-foss 
- https://github.com/apache/airflow/issues/42874 @omkar-foss 
- https://github.com/apache/airflow/issues/42875 @omkar-foss 

#### Task Instance (15 endpoints) @pierrejeambrun
- https://github.com/apache/airflow/pull/43485 @pierrejeambrun 
- https://github.com/apache/airflow/pull/43548 @pierrejeambrun 
- https://github.com/apache/airflow/pull/43642 @pierrejeambrun 
- https://github.com/apache/airflow/issues/43748 @pierrejeambrun 
- https://github.com/apache/airflow/issues/43749 @pierrejeambrun
- https://github.com/apache/airflow/issues/43750 @pierrejeambrun
- https://github.com/apache/airflow/issues/43756 @pierrejeambrun
- https://github.com/apache/airflow/issues/43751 @omkar-foss 
- https://github.com/apache/airflow/issues/43753 @omkar-foss 
- https://github.com/apache/airflow/issues/43754 @omkar-foss 
- https://github.com/apache/airflow/issues/43752 @omkar-foss
-  https://github.com/apache/airflow/pull/43675 @kandharvishnu 
- https://github.com/apache/airflow/pull/44206 @kandharvishnu
- https://github.com/apache/airflow/pull/44301 @kandharvishnu  
- https://github.com/apache/airflow/pull/44303 @kandharvishnu


#### Variables (5 endpoints)
- https://github.com/apache/airflow/pull/42798 @pierrejeambrun 
- https://github.com/apache/airflow/pull/42834 @pierrejeambrun 
- https://github.com/apache/airflow/pull/42929 @pierrejeambrun 
- https://github.com/apache/airflow/pull/42948 @pierrejeambrun 
- https://github.com/apache/airflow/pull/43083 @pierrejeambrun 

#### XCom (2 endpoints)
- https://github.com/apache/airflow/issues/42978 @michaeljs-c 
- https://github.com/apache/airflow/issues/42980 @michaeljs-c 


### Committer

- [X] I acknowledge that I am a maintainer/committer of the Apache Airflow project.


Discussion / hints:
Trimming down `views.py` is very tempting 😄

May I pick this up? If yes, please assign this issue to me. Thanks!
Hello @omkar-foss,

`views.py` are mostly 'rendered' front end that will get replaced by the new UI react, in `views.py` you also find `private/UI` REST route, most of them are with the `/object/` prefix, such as `def grid_data`. If you want to work on those UI dedicated endpoints you can that would be part of https://github.com/apache/airflow/issues/42366.

This issue focuses on the public endpoints, those are located under the `api_connexion` folder.

In anycase, just mention the endpoint you are working on, so people do not do the same ones, and I will update the description to track that :). I think we should do one at the time.
Hi @pierrejeambrun, thank you for explaining it to me, much appreciate it.

I'll start migrating the public endpoints, beginning with migration of the DAG Details API (`get_dag_details`) in `api_connexion/endpoints/dag_endpoint.py`.

Since this is a meta issue (missed that previously!), I'll create the issue/PR corresponding to the endpoint(s) that I'll be working on and link to this issue (like usual).

P.S: `views.py` will have to wait :)
Thanks for creating the ticket @pierrejeambrun! Let me take connection endpoints
Great, thanks @bugraoz93 
Thanks for assigning! Let me save you from regular updates :) I created issues for all the endpoints in connections. 
Great, thanks @bugraoz93. I just updated the meta issue with those new additions. Let me know if you plan to work on all of them so I can assign you, or just some of them so I can label the others with `good first issue` and let other contributors grab them :)
I am planning to work on all of them @pierrejeambrun. if you can assign them to me, that would be great! Thanks :) 
Hey folks! After https://github.com/apache/airflow/pull/42631 (open for review), I can pick up these two next in order:

1. `get_dag` - https://github.com/apache/airflow/issues/42652
2. `delete_dag` - https://github.com/apache/airflow/issues/42650

With these 2 done, I suppose we should have the `dags.py` migration complete. When you find some time, please update this meta and assign these to me, thank you! 😃
@omkar-foss Assigned. Thanks for picking these issues up!
I am working on the following 
1. get_connection - #42674 
2. get_connections - #42677 
Hey folks, just a heads-up on Pydantic aliases - try not to use `AliasGenerator` as it breaks Airflow lowest direct dependency resolution tests with a `TypeError` because lower versions of Pydantic wouldn't be having [this patch](https://github.com/pydantic/pydantic/pull/8806). The TypeError looks like `alias_generator ... must return str, not ...` (for more info see [this issue](https://github.com/pydantic/pydantic/issues/8768)).

Can use either `AliasChoices` or `AliasPath` as those work fine. I faced this issue in https://github.com/apache/airflow/pull/42631 and spent a while on it, so just thought should let you all know!
> Hey folks, just a heads-up on Pydantic aliases - try not to use AliasGenerator as it breaks Airflow lowest direct 

We can also update the minimum version of Pydantic to the version that is fixed. This is the feature of the "lowest direct" tests that they detect such minimum requirements are not met. So maybe you can figure out which is the minimum version and we set it @omkar-foss ?
>This is the feature of the "lowest direct" tests that they detect such minimum requirements are not met.

It's a very cool feature! Helping to find issues we normally wouldn't find during regular unit or integration tests.

>We can also update the minimum version of Pydantic to the version that is fixed.
>So maybe you can figure out which is the minimum version and we set it

That would be great, the minimum Pydantic version that is fixed is v2.6.4, [this commit](https://github.com/pydantic/pydantic/commit/6214a4bc472bfc4cd72232ab5d407dc640dc64aa#diff-648afe3d986261d8f2015b2b131b0e4a448d4dc6946cfde1a7a836876cee255eR9) for quick reference.
> That would be great, the minimum Pydantic version that is fixed is v2.6.4, [this commit](https://github.com/pydantic/pydantic/commit/6214a4bc472bfc4cd72232ab5d407dc640dc64aa#diff-648afe3d986261d8f2015b2b131b0e4a448d4dc6946cfde1a7a836876cee255eR9) for quick reference.

Added min version for Pydantic in https://github.com/apache/airflow/pull/42694
@rawwar 

> I am working on the following

> get_connection - https://github.com/apache/airflow/pull/42674
> get_connections - AIP-84 Migrate the public endpoint Get Connections to FastAPI  #42677`

Thanks for your contribution and help on AIP-84, as mentioned above, I think @bugraoz93 is already working on connections endpoints, or is planning to do so. Feel free to choose any other endpoints that looks cool to you, under `api_connexions/endpoints`. Besides `dag_endpoint` and `connection_endpoint` eveything is up for a grab, just let me know and I will assign you :)
Assigning myself to variables
I've created new issues for sections Dag Source (1 endpoint), Dag Stats (1 endpoint), Task (2 endpoints) and Version (1 endpoint). List as below:

1. https://github.com/apache/airflow/issues/42876
2. https://github.com/apache/airflow/issues/42877
3. https://github.com/apache/airflow/issues/42874
4. https://github.com/apache/airflow/issues/42875
5. https://github.com/apache/airflow/issues/42879

Please assign these to me, I'll pick these up one by one in the next few days, right after https://github.com/apache/airflow/issues/42650. Cheers.
@omkar-foss assigned and meta issue updated, thanks :)
Hi @pierrejeambrun I've added issues for XCom, please assign to me 🙂 thanks
> Hi @pierrejeambrun I've added issues for XCom, please assign to me 🙂 thanks

Done, and meta task is updated.

Thanks for taking those ones @michaeljs-c!

Hi @pierrejeambrun, I can take on the migrations for **Event Logs**:
1. Get Event Log
2. Get Event Logs

However, none of the current issue form templates fit a task issue (the `Task and Meta` template tags the issue with `kind:meta` instead of `kind:feature`).
Could you please create the issue and assign it to me? Thanks!

Hi @pierrejeambrun, I can handle the migrations for **Import Error**

1. `get_import_error`
2. `get_import_errors`

I’m still encountering the issue where I can’t create the correct issue type.
Could you please create it and assign it to me? Thanks!

I've unassigned myself for the config endpoint issue. If no one takes it, I'll pick it up once I finish DagRun and DagWarning endpoints. 
Issue: https://github.com/apache/airflow/issues/42745 <- If anyone wants to take this, please let me know and I can assign it to you.
@pierrejeambrun I am working on `get taskinstance try` endpoint 
Hi @pierrejeambrun, could you please assign these TI-related endpoints to me in above issue description:

**GET:**
- https://github.com/apache/airflow/issues/43748
- https://github.com/apache/airflow/issues/43749
- https://github.com/apache/airflow/issues/43750
- https://github.com/apache/airflow/issues/43756

**POST:**
- https://github.com/apache/airflow/issues/43751

**PUT:**
- https://github.com/apache/airflow/issues/43753
- https://github.com/apache/airflow/issues/43754
- https://github.com/apache/airflow/issues/43755
- https://github.com/apache/airflow/issues/43752
Hey omkar, as you can see in I'm already assigned to the `TaskInstances` endpoint.

Thanks for creating the issue, we can split the work on that matter, i'll assign some of them to you maybe all the `PUT/POST`  as I am already working on GET.
You're assigned, issue updated, let me know if that shounds good for you :).
> You're assigned, issue updated, let me know if that shounds good for you :).

Yes sure, that works. Thank you :)

Fix patch:
diff --git a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
index 3542430176bb9..2d26571b1e374 100644
--- a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
+++ b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
@@ -768,7 +768,7 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
-  /public/backfills/:
+  /public/backfills:
     get:
       tags:
       - Backfill
@@ -1219,7 +1219,7 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
-  /public/connections/:
+  /public/connections:
     get:
       tags:
       - Connection
@@ -1736,7 +1736,7 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
-  /public/dagStats/:
+  /public/dagStats:
     get:
       tags:
       - DagStats
@@ -2018,7 +2018,7 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
-  /public/dags/:
+  /public/dags:
     get:
       tags:
       - DAG
@@ -2577,7 +2577,7 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
-  /public/eventLogs/:
+  /public/eventLogs:
     get:
       tags:
       - Event Log
@@ -2772,7 +2772,7 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
-  /public/importErrors/:
+  /public/importErrors:
     get:
       tags:
       - Import Error
@@ -2828,7 +2828,7 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
-  /public/plugins/:
+  /public/plugins:
     get:
       tags:
       - Plugin
@@ -3033,7 +3033,7 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
-  /public/pools/:
+  /public/pools:
     get:
       tags:
       - Pool
@@ -3095,7 +3095,6 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
-  /public/pools:
     post:
       tags:
       - Pool
@@ -3103,11 +3102,11 @@ paths:
       description: Create a Pool.
       operationId: post_pool
       requestBody:
+        required: true
         content:
           application/json:
             schema:
               $ref: '#/components/schemas/PoolPostBody'
-        required: true
       responses:
         '201':
           description: Successful Response
@@ -3116,24 +3115,24 @@ paths:
               schema:
                 $ref: '#/components/schemas/PoolResponse'
         '401':
-          description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPExceptionResponse'
+          description: Unauthorized
         '403':
-          description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPExceptionResponse'
+          description: Forbidden
         '422':
           description: Validation Error
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
-  /public/providers/:
+  /public/providers:
     get:
       tags:
       - Provider
@@ -4030,7 +4029,7 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
-  /public/dags/{dag_id}/tasks/:
+  /public/dags/{dag_id}/tasks:
     get:
       tags:
       - Task
@@ -4295,7 +4294,7 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
-  /public/variables/:
+  /public/variables:
     get:
       tags:
       - Variable
@@ -4495,7 +4494,7 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HealthInfoSchema'
-  /public/version/:
+  /public/version:
     get:
       tags:
       - Version
diff --git a/airflow/api_fastapi/core_api/routes/public/backfills.py b/airflow/api_fastapi/core_api/routes/public/backfills.py
index 41e9314e9f95d..0e7f47cbb8fd7 100644
--- a/airflow/api_fastapi/core_api/routes/public/backfills.py
+++ b/airflow/api_fastapi/core_api/routes/public/backfills.py
@@ -47,7 +47,7 @@
 
 
 @backfills_router.get(
-    path="/",
+    path="",
 )
 def list_backfills(
     dag_id: str,
@@ -176,7 +176,7 @@ def cancel_backfill(backfill_id, session: Annotated[Session, Depends(get_session
 
 
 @backfills_router.post(
-    path="/",
+    path="",
     responses=create_openapi_http_exception_doc(
         [
             status.HTTP_404_NOT_FOUND,
diff --git a/airflow/api_fastapi/core_api/routes/public/connections.py b/airflow/api_fastapi/core_api/routes/public/connections.py
index 473d3edc4623b..cf1a069b9dc77 100644
--- a/airflow/api_fastapi/core_api/routes/public/connections.py
+++ b/airflow/api_fastapi/core_api/routes/public/connections.py
@@ -82,7 +82,7 @@ def get_connection(
 
 
 @connections_router.get(
-    "/",
+    "",
     responses=create_openapi_http_exception_doc([status.HTTP_404_NOT_FOUND]),
 )
 def get_connections(
@@ -119,7 +119,7 @@ def get_connections(
 
 
 @connections_router.post(
-    "/",
+    "",
     status_code=status.HTTP_201_CREATED,
     responses=create_openapi_http_exception_doc([status.HTTP_409_CONFLICT]),
 )
diff --git a/airflow/api_fastapi/core_api/routes/public/dag_stats.py b/airflow/api_fastapi/core_api/routes/public/dag_stats.py
index 574760f17c571..49c5e7ec48f94 100644
--- a/airflow/api_fastapi/core_api/routes/public/dag_stats.py
+++ b/airflow/api_fastapi/core_api/routes/public/dag_stats.py
@@ -41,7 +41,7 @@
 
 
 @dag_stats_router.get(
-    "/",
+    "",
     responses=create_openapi_http_exception_doc(
         [
             status.HTTP_400_BAD_REQUEST,
diff --git a/airflow/api_fastapi/core_api/routes/public/dags.py b/airflow/api_fastapi/core_api/routes/public/dags.py
index 8ab1ae3798d66..6a86534be46c5 100644
--- a/airflow/api_fastapi/core_api/routes/public/dags.py
+++ b/airflow/api_fastapi/core_api/routes/public/dags.py
@@ -58,7 +58,7 @@
 dags_router = AirflowRouter(tags=["DAG"], prefix="/dags")
 
 
-@dags_router.get("/")
+@dags_router.get("")
 def get_dags(
     limit: QueryLimit,
     offset: QueryOffset,
@@ -223,7 +223,7 @@ def patch_dag(
 
 
 @dags_router.patch(
-    "/",
+    "",
     responses=create_openapi_http_exception_doc(
         [
             status.HTTP_400_BAD_REQUEST,
diff --git a/airflow/api_fastapi/core_api/routes/public/event_logs.py b/airflow/api_fastapi/core_api/routes/public/event_logs.py
index 1a0e7fad2f63f..ecc7ba7fcba30 100644
--- a/airflow/api_fastapi/core_api/routes/public/event_logs.py
+++ b/airflow/api_fastapi/core_api/routes/public/event_logs.py
@@ -61,7 +61,7 @@ def get_event_log(
 
 
 @event_logs_router.get(
-    "/",
+    "",
 )
 def get_event_logs(
     limit: QueryLimit,
diff --git a/airflow/api_fastapi/core_api/routes/public/import_error.py b/airflow/api_fastapi/core_api/routes/public/import_error.py
index 5312904fc7b35..5d84abe622c31 100644
--- a/airflow/api_fastapi/core_api/routes/public/import_error.py
+++ b/airflow/api_fastapi/core_api/routes/public/import_error.py
@@ -65,7 +65,7 @@ def get_import_error(
 
 
 @import_error_router.get(
-    "/",
+    "",
 )
 def get_import_errors(
     limit: QueryLimit,
diff --git a/airflow/api_fastapi/core_api/routes/public/plugins.py b/airflow/api_fastapi/core_api/routes/public/plugins.py
index d0028b7025082..717d073636c19 100644
--- a/airflow/api_fastapi/core_api/routes/public/plugins.py
+++ b/airflow/api_fastapi/core_api/routes/public/plugins.py
@@ -25,7 +25,7 @@
 plugins_router = AirflowRouter(tags=["Plugin"], prefix="/plugins")
 
 
-@plugins_router.get("/")
+@plugins_router.get("")
 def get_plugins(
     limit: QueryLimit,
     offset: QueryOffset,
diff --git a/airflow/api_fastapi/core_api/routes/public/pools.py b/airflow/api_fastapi/core_api/routes/public/pools.py
index 547ec5265851c..168fbe57400a7 100644
--- a/airflow/api_fastapi/core_api/routes/public/pools.py
+++ b/airflow/api_fastapi/core_api/routes/public/pools.py
@@ -81,7 +81,7 @@ def get_pool(
 
 
 @pools_router.get(
-    "/",
+    "",
     responses=create_openapi_http_exception_doc([status.HTTP_404_NOT_FOUND]),
 )
 def get_pools(
diff --git a/airflow/api_fastapi/core_api/routes/public/providers.py b/airflow/api_fastapi/core_api/routes/public/providers.py
index 45f3b9a7a1f08..28b4682dc3e55 100644
--- a/airflow/api_fastapi/core_api/routes/public/providers.py
+++ b/airflow/api_fastapi/core_api/routes/public/providers.py
@@ -39,7 +39,7 @@ def _provider_mapper(provider: ProviderInfo) -> ProviderResponse:
     )
 
 
-@providers_router.get("/")
+@providers_router.get("")
 def get_providers(
     limit: QueryLimit,
     offset: QueryOffset,
diff --git a/airflow/api_fastapi/core_api/routes/public/tasks.py b/airflow/api_fastapi/core_api/routes/public/tasks.py
index a8a366cf6df00..be1fdc7324d8f 100644
--- a/airflow/api_fastapi/core_api/routes/public/tasks.py
+++ b/airflow/api_fastapi/core_api/routes/public/tasks.py
@@ -31,7 +31,7 @@
 
 
 @tasks_router.get(
-    "/",
+    "",
     responses=create_openapi_http_exception_doc(
         [
             status.HTTP_400_BAD_REQUEST,
diff --git a/airflow/api_fastapi/core_api/routes/public/variables.py b/airflow/api_fastapi/core_api/routes/public/variables.py
index 9818e70db4419..593f7b5cad1a5 100644
--- a/airflow/api_fastapi/core_api/routes/public/variables.py
+++ b/airflow/api_fastapi/core_api/routes/public/variables.py
@@ -72,7 +72,7 @@ def get_variable(
 
 
 @variables_router.get(
-    "/",
+    "",
 )
 def get_variables(
     limit: QueryLimit,
@@ -144,7 +144,7 @@ def patch_variable(
 
 
 @variables_router.post(
-    "/",
+    "",
     status_code=status.HTTP_201_CREATED,
 )
 def post_variable(
diff --git a/airflow/api_fastapi/core_api/routes/public/version.py b/airflow/api_fastapi/core_api/routes/public/version.py
index a53fdb2029a6f..0e784fbfd8694 100644
--- a/airflow/api_fastapi/core_api/routes/public/version.py
+++ b/airflow/api_fastapi/core_api/routes/public/version.py
@@ -25,7 +25,7 @@
 version_router = AirflowRouter(tags=["Version"], prefix="/version")
 
 
-@version_router.get("/")
+@version_router.get("")
 def get_version() -> VersionInfo:
     """Get version information."""
     airflow_version = airflow.__version__
diff --git a/airflow/ui/openapi-gen/requests/services.gen.ts b/airflow/ui/openapi-gen/requests/services.gen.ts
index 09eb2432d77ad..467c2961420e5 100644
--- a/airflow/ui/openapi-gen/requests/services.gen.ts
+++ b/airflow/ui/openapi-gen/requests/services.gen.ts
@@ -568,7 +568,7 @@ export class BackfillService {
   ): CancelablePromise<ListBackfillsResponse> {
     return __request(OpenAPI, {
       method: "GET",
-      url: "/public/backfills/",
+      url: "/public/backfills",
       query: {
         dag_id: data.dagId,
         limit: data.limit,
@@ -595,7 +595,7 @@ export class BackfillService {
   ): CancelablePromise<CreateBackfillResponse> {
     return __request(OpenAPI, {
       method: "POST",
-      url: "/public/backfills/",
+      url: "/public/backfills",
       body: data.requestBody,
       mediaType: "application/json",
       errors: {
@@ -814,7 +814,7 @@ export class ConnectionService {
   ): CancelablePromise<GetConnectionsResponse> {
     return __request(OpenAPI, {
       method: "GET",
-      url: "/public/connections/",
+      url: "/public/connections",
       query: {
         limit: data.limit,
         offset: data.offset,
@@ -842,7 +842,7 @@ export class ConnectionService {
   ): CancelablePromise<PostConnectionResponse> {
     return __request(OpenAPI, {
       method: "POST",
-      url: "/public/connections/",
+      url: "/public/connections",
       body: data.requestBody,
       mediaType: "application/json",
       errors: {
@@ -1087,7 +1087,7 @@ export class DagStatsService {
   ): CancelablePromise<GetDagStatsResponse> {
     return __request(OpenAPI, {
       method: "GET",
-      url: "/public/dagStats/",
+      url: "/public/dagStats",
       query: {
         dag_ids: data.dagIds,
       },
@@ -1224,7 +1224,7 @@ export class DagService {
   ): CancelablePromise<GetDagsResponse> {
     return __request(OpenAPI, {
       method: "GET",
-      url: "/public/dags/",
+      url: "/public/dags",
       query: {
         limit: data.limit,
         offset: data.offset,
@@ -1267,7 +1267,7 @@ export class DagService {
   ): CancelablePromise<PatchDagsResponse> {
     return __request(OpenAPI, {
       method: "PATCH",
-      url: "/public/dags/",
+      url: "/public/dags",
       query: {
         update_mask: data.updateMask,
         limit: data.limit,
@@ -1488,7 +1488,7 @@ export class EventLogService {
   ): CancelablePromise<GetEventLogsResponse> {
     return __request(OpenAPI, {
       method: "GET",
-      url: "/public/eventLogs/",
+      url: "/public/eventLogs",
       query: {
         dag_id: data.dagId,
         task_id: data.taskId,
@@ -1556,7 +1556,7 @@ export class ImportErrorService {
   ): CancelablePromise<GetImportErrorsResponse> {
     return __request(OpenAPI, {
       method: "GET",
-      url: "/public/importErrors/",
+      url: "/public/importErrors",
       query: {
         limit: data.limit,
         offset: data.offset,
@@ -1585,7 +1585,7 @@ export class PluginService {
   ): CancelablePromise<GetPluginsResponse> {
     return __request(OpenAPI, {
       method: "GET",
-      url: "/public/plugins/",
+      url: "/public/plugins",
       query: {
         limit: data.limit,
         offset: data.offset,
@@ -1700,7 +1700,7 @@ export class PoolService {
   ): CancelablePromise<GetPoolsResponse> {
     return __request(OpenAPI, {
       method: "GET",
-      url: "/public/pools/",
+      url: "/public/pools",
       query: {
         limit: data.limit,
         offset: data.offset,
@@ -1755,7 +1755,7 @@ export class ProviderService {
   ): CancelablePromise<GetProvidersResponse> {
     return __request(OpenAPI, {
       method: "GET",
-      url: "/public/providers/",
+      url: "/public/providers",
       query: {
         limit: data.limit,
         offset: data.offset,
@@ -2147,7 +2147,7 @@ export class TaskService {
   ): CancelablePromise<GetTasksResponse> {
     return __request(OpenAPI, {
       method: "GET",
-      url: "/public/dags/{dag_id}/tasks/",
+      url: "/public/dags/{dag_id}/tasks",
       path: {
         dag_id: data.dagId,
       },
@@ -2294,7 +2294,7 @@ export class VariableService {
   ): CancelablePromise<GetVariablesResponse> {
     return __request(OpenAPI, {
       method: "GET",
-      url: "/public/variables/",
+      url: "/public/variables",
       query: {
         limit: data.limit,
         offset: data.offset,
@@ -2321,7 +2321,7 @@ export class VariableService {
   ): CancelablePromise<PostVariableResponse> {
     return __request(OpenAPI, {
       method: "POST",
-      url: "/public/variables/",
+      url: "/public/variables",
       body: data.requestBody,
       mediaType: "application/json",
       errors: {
@@ -2400,7 +2400,7 @@ export class VersionService {
   public static getVersion(): CancelablePromise<GetVersionResponse> {
     return __request(OpenAPI, {
       method: "GET",
-      url: "/public/version/",
+      url: "/public/version",
     });
   }
 }
diff --git a/airflow/ui/openapi-gen/requests/types.gen.ts b/airflow/ui/openapi-gen/requests/types.gen.ts
index 71a94d534d326..21ad2839bc80b 100644
--- a/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -1992,7 +1992,7 @@ export type $OpenApiTs = {
       };
     };
   };
-  "/public/backfills/": {
+  "/public/backfills": {
     get: {
       req: ListBackfillsData;
       res: {
@@ -2245,7 +2245,7 @@ export type $OpenApiTs = {
       };
     };
   };
-  "/public/connections/": {
+  "/public/connections": {
     get: {
       req: GetConnectionsData;
       res: {
@@ -2494,7 +2494,7 @@ export type $OpenApiTs = {
       };
     };
   };
-  "/public/dagStats/": {
+  "/public/dagStats": {
     get: {
       req: GetDagStatsData;
       res: {
@@ -2610,7 +2610,7 @@ export type $OpenApiTs = {
       };
     };
   };
-  "/public/dags/": {
+  "/public/dags": {
     get: {
       req: GetDagsData;
       res: {
@@ -2832,7 +2832,7 @@ export type $OpenApiTs = {
       };
     };
   };
-  "/public/eventLogs/": {
+  "/public/eventLogs": {
     get: {
       req: GetEventLogsData;
       res: {
@@ -2882,7 +2882,7 @@ export type $OpenApiTs = {
       };
     };
   };
-  "/public/importErrors/": {
+  "/public/importErrors": {
     get: {
       req: GetImportErrorsData;
       res: {
@@ -2905,7 +2905,7 @@ export type $OpenApiTs = {
       };
     };
   };
-  "/public/plugins/": {
+  "/public/plugins": {
     get: {
       req: GetPluginsData;
       res: {
@@ -3013,7 +3013,7 @@ export type $OpenApiTs = {
       };
     };
   };
-  "/public/pools/": {
+  "/public/pools": {
     get: {
       req: GetPoolsData;
       res: {
@@ -3039,8 +3039,6 @@ export type $OpenApiTs = {
         422: HTTPValidationError;
       };
     };
-  };
-  "/public/pools": {
     post: {
       req: PostPoolData;
       res: {
@@ -3063,7 +3061,7 @@ export type $OpenApiTs = {
       };
     };
   };
-  "/public/providers/": {
+  "/public/providers": {
     get: {
       req: GetProvidersData;
       res: {
@@ -3329,7 +3327,7 @@ export type $OpenApiTs = {
       };
     };
   };
-  "/public/dags/{dag_id}/tasks/": {
+  "/public/dags/{dag_id}/tasks": {
     get: {
       req: GetTasksData;
       res: {
@@ -3472,7 +3470,7 @@ export type $OpenApiTs = {
       };
     };
   };
-  "/public/variables/": {
+  "/public/variables": {
     get: {
       req: GetVariablesData;
       res: {
@@ -3557,7 +3555,7 @@ export type $OpenApiTs = {
       };
     };
   };
-  "/public/version/": {
+  "/public/version": {
     get: {
       res: {
         /**


Test patch:
diff --git a/tests/api_fastapi/core_api/routes/test_routes.py b/tests/api_fastapi/core_api/routes/test_routes.py
index 6258a5f30ca75..1930815a0f08b 100644
--- a/tests/api_fastapi/core_api/routes/test_routes.py
+++ b/tests/api_fastapi/core_api/routes/test_routes.py
@@ -20,7 +20,7 @@
 
 # Set of paths that are allowed to be accessible without authentication
 NO_AUTH_PATHS = {
-    "/public/version/",
+    "/public/version",
     "/public/monitor/health",
 }