diff --git a/editoast/openapi.yaml b/editoast/openapi.yaml
index 32e31f7af38..173ff46f20d 100644
--- a/editoast/openapi.yaml
+++ b/editoast/openapi.yaml
@@ -3095,7 +3095,18 @@ paths:
         content:
           application/json:
             schema:
-              $ref: '#/components/schemas/WorkScheduleCreateForm'
+              type: object
+              description: This structure is used by the post endpoint to create a work schedule
+              required:
+              - work_schedule_group_name
+              - work_schedules
+              properties:
+                work_schedule_group_name:
+                  type: string
+                work_schedules:
+                  type: array
+                  items:
+                    $ref: '#/components/schemas/WorkScheduleItemForm'
         required: true
       responses:
         '201':
@@ -3103,7 +3114,151 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/WorkScheduleCreateResponse'
+                type: object
+                required:
+                - work_schedule_group_id
+                properties:
+                  work_schedule_group_id:
+                    type: integer
+                    format: int64
+  /work_schedules/group:
+    get:
+      tags:
+      - work_schedules
+      responses:
+        '201':
+          description: The existing work schedule group ids
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  type: integer
+                  format: int64
+    post:
+      tags:
+      - work_schedules
+      requestBody:
+        content:
+          application/json:
+            schema:
+              type: object
+              properties:
+                work_schedule_group_name:
+                  type: string
+                  nullable: true
+        required: true
+      responses:
+        '200':
+          description: The id of the created work schedule group
+          content:
+            application/json:
+              schema:
+                type: object
+                required:
+                - work_schedule_group_id
+                properties:
+                  work_schedule_group_id:
+                    type: integer
+                    format: int64
+  /work_schedules/group/{id}:
+    get:
+      tags:
+      - work_schedules
+      parameters:
+      - name: page
+        in: query
+        required: false
+        schema:
+          type: integer
+          format: int64
+          default: 1
+          minimum: 1
+      - name: page_size
+        in: query
+        required: false
+        schema:
+          type: integer
+          format: int64
+          default: 25
+          nullable: true
+          minimum: 1
+      - name: id
+        in: path
+        description: A work schedule group ID
+        required: true
+        schema:
+          type: integer
+          format: int64
+      - name: ordering
+        in: query
+        required: false
+        schema:
+          $ref: '#/components/schemas/Ordering'
+      responses:
+        '200':
+          description: The work schedules in the group
+          content:
+            application/json:
+              schema:
+                allOf:
+                - $ref: '#/components/schemas/PaginationStats'
+                - type: object
+                  required:
+                  - results
+                  properties:
+                    results:
+                      type: array
+                      items:
+                        $ref: '#/components/schemas/WorkSchedule'
+        '404':
+          description: Work schedule group not found
+    put:
+      tags:
+      - work_schedules
+      parameters:
+      - name: id
+        in: path
+        description: A work schedule group ID
+        required: true
+        schema:
+          type: integer
+          format: int64
+      requestBody:
+        content:
+          application/json:
+            schema:
+              type: array
+              items:
+                $ref: '#/components/schemas/WorkScheduleItemForm'
+        required: true
+      responses:
+        '200':
+          description: The work schedules have been created
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/WorkSchedule'
+        '404':
+          description: Work schedule group not found
+    delete:
+      tags:
+      - work_schedules
+      parameters:
+      - name: id
+        in: path
+        description: A work schedule group ID
+        required: true
+        schema:
+          type: integer
+          format: int64
+      responses:
+        '204':
+          description: The work schedule group has been deleted
+        '404':
+          description: The work schedule group does not exist
   /work_schedules/project_path:
     post:
       tags:
@@ -4375,6 +4530,7 @@ components:
       - $ref: '#/components/schemas/EditoastTrainScheduleErrorInfraNotFound'
       - $ref: '#/components/schemas/EditoastTrainScheduleErrorNotFound'
       - $ref: '#/components/schemas/EditoastWorkScheduleErrorNameAlreadyUsed'
+      - $ref: '#/components/schemas/EditoastWorkScheduleErrorWorkScheduleGroupNotFound'
       description: Generated error type for Editoast
       discriminator:
         propertyName: type
@@ -5793,6 +5949,30 @@ components:
           type: string
           enum:
           - editoast:work_schedule:NameAlreadyUsed
+    EditoastWorkScheduleErrorWorkScheduleGroupNotFound:
+      type: object
+      required:
+      - type
+      - status
+      - message
+      properties:
+        context:
+          type: object
+          required:
+          - id
+          properties:
+            id:
+              type: integer
+        message:
+          type: string
+        status:
+          type: integer
+          enum:
+          - 404
+        type:
+          type: string
+          enum:
+          - editoast:work_schedule:WorkScheduleGroupNotFound
     EffortCurve:
       type: object
       required:
@@ -10873,27 +11053,37 @@ components:
             type: string
             enum:
             - Detector
-    WorkScheduleCreateForm:
+    WorkSchedule:
       type: object
-      description: This structure is used by the post endpoint to create a work schedule
       required:
-      - work_schedule_group_name
-      - work_schedules
+      - id
+      - start_date_time
+      - end_date_time
+      - track_ranges
+      - obj_id
+      - work_schedule_type
+      - work_schedule_group_id
       properties:
-        work_schedule_group_name:
+        end_date_time:
+          type: string
+          format: date-time
+        id:
+          type: integer
+          format: int64
+        obj_id:
+          type: string
+        start_date_time:
           type: string
-        work_schedules:
+          format: date-time
+        track_ranges:
           type: array
           items:
-            $ref: '#/components/schemas/WorkScheduleItemForm'
-    WorkScheduleCreateResponse:
-      type: object
-      required:
-      - work_schedule_group_id
-      properties:
+            $ref: '#/components/schemas/TrackRange'
         work_schedule_group_id:
           type: integer
           format: int64
+        work_schedule_type:
+          $ref: '#/components/schemas/WorkScheduleType'
     WorkScheduleItemForm:
       type: object
       required:
@@ -10920,6 +11110,11 @@ components:
           enum:
           - CATENARY
           - TRACK
+    WorkScheduleType:
+      type: string
+      enum:
+      - CATENARY
+      - TRACK
     ZoneUpdate:
       type: object
       required:
diff --git a/editoast/src/models/work_schedules.rs b/editoast/src/models/work_schedules.rs
index 80da485fc51..a8aa31c4317 100644
--- a/editoast/src/models/work_schedules.rs
+++ b/editoast/src/models/work_schedules.rs
@@ -29,9 +29,9 @@ pub enum WorkScheduleType {
     Track,
 }
 
-#[derive(Debug, Default, Clone, Model)]
+#[derive(Debug, Default, Clone, Model, Serialize, Deserialize, ToSchema)]
 #[model(table = editoast_models::tables::work_schedule)]
-#[model(gen(batch_ops = c, list))]
+#[model(gen(batch_ops = cd, list))]
 pub struct WorkSchedule {
     pub id: i64,
     pub start_date_time: DateTime<Utc>,
diff --git a/editoast/src/views/operational_studies.rs b/editoast/src/views/operational_studies.rs
index 871be4e8e1a..3c287931483 100644
--- a/editoast/src/views/operational_studies.rs
+++ b/editoast/src/views/operational_studies.rs
@@ -1,4 +1,5 @@
 use crate::models::prelude::*;
+use crate::models::work_schedules::WorkSchedule;
 use crate::models::Project;
 use crate::models::Scenario;
 use crate::models::Study;
@@ -58,4 +59,12 @@ impl Ordering {
             Ordering::LastModifiedDesc => Scenario::LAST_MODIFICATION.desc(),
         }
     }
+
+    pub fn as_work_schedule_ordering(&self) -> SortSetting<WorkSchedule> {
+        match *self {
+            Ordering::NameAsc => WorkSchedule::OBJ_ID.asc(),
+            Ordering::NameDesc => WorkSchedule::OBJ_ID.desc(),
+            _ => WorkSchedule::OBJ_ID.asc(),
+        }
+    }
 }
diff --git a/editoast/src/views/work_schedules.rs b/editoast/src/views/work_schedules.rs
index 0f7f116daf2..6c555edf8ce 100644
--- a/editoast/src/views/work_schedules.rs
+++ b/editoast/src/views/work_schedules.rs
@@ -1,5 +1,23 @@
+use super::pagination::PaginatedList;
+use crate::core::pathfinding::TrackRange as CoreTrackRange;
+use crate::error::InternalError;
+use crate::error::Result;
+use crate::models::prelude::*;
+use crate::models::work_schedules::WorkSchedule;
+use crate::models::work_schedules::WorkScheduleGroup;
+use crate::models::work_schedules::WorkScheduleType;
+use crate::views::operational_studies::Ordering;
+use crate::views::pagination::PaginationQueryParam;
+use crate::views::pagination::PaginationStats;
+use crate::views::path::projection::Intersection;
+use crate::views::path::projection::PathProjection;
+use crate::views::AuthenticationExt;
+use crate::views::AuthorizationError;
 use axum::extract::Json;
+use axum::extract::Path;
+use axum::extract::Query;
 use axum::extract::State;
+use axum::response::IntoResponse;
 use axum::Extension;
 use chrono::DateTime;
 use chrono::Utc;
@@ -7,38 +25,43 @@ use derivative::Derivative;
 use editoast_authz::BuiltinRole;
 use editoast_derive::EditoastError;
 use editoast_models::DbConnectionPoolV2;
+use editoast_schemas::infra::Direction;
+use editoast_schemas::infra::TrackRange;
 use serde::de::Error as SerdeError;
 use serde::Deserialize;
 use serde::Serialize;
 use std::result::Result as StdResult;
 use thiserror::Error;
+use utoipa::IntoParams;
 use utoipa::ToSchema;
-
-use crate::core::pathfinding::TrackRange as CoreTrackRange;
-use crate::error::InternalError;
-use crate::error::Result;
-use crate::models::prelude::*;
-use crate::models::work_schedules::WorkSchedule;
-use crate::models::work_schedules::WorkScheduleGroup;
-use crate::models::work_schedules::WorkScheduleType;
-use crate::views::path::projection::Intersection;
-use crate::views::path::projection::PathProjection;
-use crate::views::AuthenticationExt;
-use crate::views::AuthorizationError;
-use crate::AppState;
-use editoast_schemas::infra::{Direction, TrackRange};
+use uuid::Uuid;
 
 crate::routes! {
     "/work_schedules" => {
         create,
         "/project_path" => project_path,
+        "/group" => {
+            create_group,
+            list_groups,
+            "/{id}" => {
+                delete_group,
+                get_group,
+                put_in_group,
+            },
+        },
     },
 }
 
 editoast_common::schemas! {
-    WorkScheduleCreateForm,
-    WorkScheduleCreateResponse,
+    WorkSchedule,
     WorkScheduleItemForm,
+    WorkScheduleType,
+}
+
+#[derive(IntoParams, Deserialize)]
+struct WorkScheduleGroupIdParam {
+    /// A work schedule group ID
+    id: i64,
 }
 
 #[derive(Debug, Error, EditoastError)]
@@ -47,6 +70,9 @@ enum WorkScheduleError {
     #[error("Name '{name}' already used")]
     #[editoast_error(status = 400)]
     NameAlreadyUsed { name: String },
+    #[error("Work schedule group '{id}' not found")]
+    #[editoast_error(status = 404)]
+    WorkScheduleGroupNotFound { id: i64 },
 }
 
 pub fn map_diesel_error(e: InternalError, name: impl AsRef<str>) -> InternalError {
@@ -136,49 +162,43 @@ struct WorkScheduleCreateResponse {
 #[utoipa::path(
     post, path = "",
     tag = "work_schedules",
-    request_body = WorkScheduleCreateForm,
+    request_body = inline(WorkScheduleCreateForm),
     responses(
-        (status = 201, body = WorkScheduleCreateResponse, description = "The id of the created work schedule group"),
+        (status = 201, body = inline(WorkScheduleCreateResponse), description = "The id of the created work schedule group"),
     )
 )]
 async fn create(
-    State(app_state): State<AppState>,
+    State(db_pool): State<DbConnectionPoolV2>,
     Extension(auth): AuthenticationExt,
     Json(WorkScheduleCreateForm {
         work_schedule_group_name,
         work_schedules,
     }): Json<WorkScheduleCreateForm>,
 ) -> Result<Json<WorkScheduleCreateResponse>> {
-    let authorized = auth
-        .check_roles([BuiltinRole::WorkScheduleWrite].into())
-        .await
-        .map_err(AuthorizationError::AuthError)?;
-    if !authorized {
-        return Err(AuthorizationError::Unauthorized.into());
-    }
+    // Create the group (using the method for the create group endpoint)
+    let work_schedule_group = create_group(
+        State(db_pool.clone()),
+        Extension(auth),
+        Json(WorkScheduleGroupCreateForm {
+            work_schedule_group_name: Some(work_schedule_group_name),
+        }),
+    )
+    .await?;
 
-    let db_pool = app_state.db_pool_v2.clone();
     let conn = &mut db_pool.get().await?;
 
-    // Create the work_schedule_group
-    let work_schedule_group = WorkScheduleGroup::changeset()
-        .name(work_schedule_group_name.clone())
-        .creation_date(Utc::now())
-        .create(conn)
-        .await;
-    let work_schedule_group =
-        work_schedule_group.map_err(|e| map_diesel_error(e, work_schedule_group_name))?;
-
     // Create work schedules
     let work_schedules_changesets = work_schedules
         .into_iter()
-        .map(|work_schedule| work_schedule.into_work_schedule_changeset(work_schedule_group.id))
+        .map(|work_schedule| {
+            work_schedule.into_work_schedule_changeset(work_schedule_group.work_schedule_group_id)
+        })
         .collect::<Vec<_>>();
     let _work_schedules: Vec<_> =
         WorkSchedule::create_batch(conn, work_schedules_changesets).await?;
 
     Ok(Json(WorkScheduleCreateResponse {
-        work_schedule_group_id: work_schedule_group.id,
+        work_schedule_group_id: work_schedule_group.work_schedule_group_id,
     }))
 }
 
@@ -275,6 +295,227 @@ async fn project_path(
     Ok(Json(projections))
 }
 
+#[derive(Serialize, Deserialize, ToSchema)]
+struct WorkScheduleGroupCreateForm {
+    work_schedule_group_name: Option<String>,
+}
+
+#[derive(Serialize, Deserialize, ToSchema)]
+struct WorkScheduleGroupCreateResponse {
+    work_schedule_group_id: i64,
+}
+
+#[utoipa::path(
+    post, path = "",
+    tag = "work_schedules",
+    request_body = inline(WorkScheduleGroupCreateForm),
+    responses(
+        (status = 200, body = inline(WorkScheduleGroupCreateResponse), description = "The id of the created work schedule group"),
+    )
+)]
+async fn create_group(
+    State(db_pool): State<DbConnectionPoolV2>,
+    Extension(auth): AuthenticationExt,
+    Json(WorkScheduleGroupCreateForm {
+        work_schedule_group_name,
+    }): Json<WorkScheduleGroupCreateForm>,
+) -> Result<Json<WorkScheduleGroupCreateResponse>> {
+    let authorized = auth
+        .check_roles([BuiltinRole::WorkScheduleWrite].into())
+        .await
+        .map_err(AuthorizationError::AuthError)?;
+    if !authorized {
+        return Err(AuthorizationError::Unauthorized.into());
+    }
+
+    let conn = &mut db_pool.get().await?;
+    let group_name = work_schedule_group_name.unwrap_or(Uuid::new_v4().to_string());
+
+    // Create the work_schedule_group
+    let work_schedule_group = WorkScheduleGroup::changeset()
+        .name(group_name.clone())
+        .creation_date(Utc::now())
+        .create(conn)
+        .await;
+    let work_schedule_group = work_schedule_group.map_err(|e| map_diesel_error(e, group_name))?;
+    Ok(Json(WorkScheduleGroupCreateResponse {
+        work_schedule_group_id: work_schedule_group.id,
+    }))
+}
+
+#[utoipa::path(
+    delete, path = "",
+    tag = "work_schedules",
+    params(WorkScheduleGroupIdParam),
+    responses(
+        (status = 204, description = "The work schedule group has been deleted"),
+        (status = 404, description = "The work schedule group does not exist"),
+    )
+)]
+async fn delete_group(
+    State(db_pool): State<DbConnectionPoolV2>,
+    Extension(auth): AuthenticationExt,
+    Path(WorkScheduleGroupIdParam { id: group_id }): Path<WorkScheduleGroupIdParam>,
+) -> Result<impl IntoResponse> {
+    let authorized = auth
+        .check_roles([BuiltinRole::WorkScheduleWrite].into())
+        .await
+        .map_err(AuthorizationError::AuthError)?;
+    if !authorized {
+        return Err(AuthorizationError::Unauthorized.into());
+    }
+
+    let conn = &mut db_pool.get().await?;
+    WorkScheduleGroup::delete_static_or_fail(conn, group_id, || {
+        WorkScheduleError::WorkScheduleGroupNotFound { id: group_id }
+    })
+    .await?;
+
+    Ok(axum::http::StatusCode::NO_CONTENT)
+}
+
+#[utoipa::path(
+    get, path = "",
+    tag = "work_schedules",
+    responses(
+        (status = 201, body = Vec<i64>, description = "The existing work schedule group ids"),
+    )
+)]
+async fn list_groups(
+    State(db_pool): State<DbConnectionPoolV2>,
+    Extension(auth): AuthenticationExt,
+) -> Result<Json<Vec<i64>>> {
+    let authorized = auth
+        .check_roles([BuiltinRole::WorkScheduleRead].into())
+        .await
+        .map_err(AuthorizationError::AuthError)?;
+    if !authorized {
+        return Err(AuthorizationError::Unauthorized.into());
+    }
+
+    let conn = &mut db_pool.get().await?;
+
+    let selection_setting = SelectionSettings::new();
+    let work_schedule_group_ids = WorkScheduleGroup::list(conn, selection_setting)
+        .await?
+        .iter()
+        .map(|group| group.id)
+        .collect::<Vec<i64>>();
+
+    Ok(Json(work_schedule_group_ids))
+}
+
+#[utoipa::path(
+    put, path = "",
+    tag = "work_schedules",
+    request_body = Vec<WorkScheduleItemForm>,
+    params(WorkScheduleGroupIdParam),
+    responses(
+        (status = 200, description = "The work schedules have been created", body = Vec<WorkSchedule>),
+        (status = 404, description = "Work schedule group not found"),
+    )
+)]
+async fn put_in_group(
+    State(db_pool): State<DbConnectionPoolV2>,
+    Extension(auth): AuthenticationExt,
+    Path(WorkScheduleGroupIdParam { id: group_id }): Path<WorkScheduleGroupIdParam>,
+    Json(work_schedules): Json<Vec<WorkScheduleItemForm>>,
+) -> Result<Json<Vec<WorkSchedule>>> {
+    let authorized = auth
+        .check_roles([BuiltinRole::WorkScheduleWrite].into())
+        .await
+        .map_err(AuthorizationError::AuthError)?;
+    if !authorized {
+        return Err(AuthorizationError::Unauthorized.into());
+    }
+
+    let conn = &mut db_pool.get().await?;
+
+    conn.transaction(|conn| {
+        Box::pin(async move {
+            // Check that the group exists
+            WorkScheduleGroup::retrieve_or_fail(&mut conn.clone(), group_id, || {
+                WorkScheduleError::WorkScheduleGroupNotFound { id: group_id }
+            })
+            .await?;
+
+            // Create work schedules
+            let work_schedules_changesets = work_schedules
+                .into_iter()
+                .map(|work_schedule| work_schedule.into_work_schedule_changeset(group_id))
+                .collect::<Vec<_>>();
+            let work_schedules =
+                WorkSchedule::create_batch(&mut conn.clone(), work_schedules_changesets).await?;
+
+            Ok(Json(work_schedules))
+        })
+    })
+    .await
+}
+
+#[derive(Serialize, ToSchema)]
+#[cfg_attr(test, derive(Deserialize))]
+struct GroupContentResponse {
+    #[schema(value_type = Vec<WorkSchedule>)]
+    results: Vec<WorkSchedule>,
+    #[serde(flatten)]
+    stats: PaginationStats,
+}
+
+#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
+#[into_params(parameter_in = Query)]
+pub struct WorkScheduleOrderingParam {
+    #[serde(default)]
+    pub ordering: Ordering,
+}
+
+#[utoipa::path(
+    get, path = "",
+    tag = "work_schedules",
+    params(PaginationQueryParam, WorkScheduleGroupIdParam, WorkScheduleOrderingParam),
+    responses(
+        (status = 200, description = "The work schedules in the group", body = inline(GroupContentResponse)),
+        (status = 404, description = "Work schedule group not found"),
+    )
+)]
+async fn get_group(
+    State(db_pool): State<DbConnectionPoolV2>,
+    Extension(auth): AuthenticationExt,
+    Path(WorkScheduleGroupIdParam { id: group_id }): Path<WorkScheduleGroupIdParam>,
+    Query(pagination_params): Query<PaginationQueryParam>,
+    Query(ordering_params): Query<WorkScheduleOrderingParam>,
+) -> Result<Json<GroupContentResponse>> {
+    let authorized = auth
+        .check_roles([BuiltinRole::WorkScheduleRead].into())
+        .await
+        .map_err(AuthorizationError::AuthError)?;
+    if !authorized {
+        return Err(AuthorizationError::Unauthorized.into());
+    }
+
+    let ordering = ordering_params.ordering;
+    let settings = pagination_params
+        .validate(100)?
+        .into_selection_settings()
+        .filter(move || WorkSchedule::WORK_SCHEDULE_GROUP_ID.eq(group_id))
+        .order_by(move || ordering.as_work_schedule_ordering());
+
+    let conn = &mut db_pool.get().await?;
+
+    // Check that the group exists
+    WorkScheduleGroup::retrieve_or_fail(conn, group_id, || {
+        WorkScheduleError::WorkScheduleGroupNotFound { id: group_id }
+    })
+    .await?;
+
+    let (work_schedules, stats) = WorkSchedule::list_paginated(conn, settings).await?;
+
+    Ok(Json(GroupContentResponse {
+        results: work_schedules,
+        stats,
+    }))
+}
+
 #[cfg(test)]
 pub mod tests {
     use axum::http::StatusCode;
@@ -510,4 +751,48 @@ pub mod tests {
 
         assert_eq!(work_schedule_project_response, expected);
     }
+
+    #[rstest]
+    async fn work_schedule_endpoints_workflow() {
+        let app = TestAppBuilder::default_app();
+
+        // Create a new group
+        let create_group_request = app.post("/work_schedules/group").json(&json!({}));
+        let group_creation_response = app
+            .fetch(create_group_request)
+            .assert_status(StatusCode::OK)
+            .json_into::<WorkScheduleGroupCreateResponse>();
+        let group_id = group_creation_response.work_schedule_group_id;
+        let work_schedule_url = format!("/work_schedules/group/{group_id}");
+
+        // Add a work schedule
+        let ref_obj_id = Uuid::new_v4().to_string();
+        let request = app.put(&work_schedule_url).json(&json!([{
+                "start_date_time": "2024-01-01T08:00:00Z",
+                "end_date_time": "2024-01-01T09:00:00Z",
+                "track_ranges": [],
+                "obj_id": ref_obj_id,
+                "work_schedule_type": "CATENARY"
+            }]
+        ));
+        app.fetch(request).assert_status(StatusCode::OK);
+
+        // Get the content of the group
+        let request = app.get(&work_schedule_url);
+        let response = app
+            .fetch(request)
+            .assert_status(StatusCode::OK)
+            .json_into::<GroupContentResponse>();
+        let work_schedules = response.results;
+        assert_eq!(1, work_schedules.len());
+        assert_eq!(ref_obj_id, work_schedules[0].obj_id);
+
+        // Delete it
+        let request = app.delete(&work_schedule_url);
+        app.fetch(request).assert_status(StatusCode::NO_CONTENT);
+
+        // Try to access it
+        let request = app.get(&work_schedule_url);
+        app.fetch(request).assert_status(StatusCode::NOT_FOUND);
+    }
 }
diff --git a/front/public/locales/en/errors.json b/front/public/locales/en/errors.json
index 37a96754166..f576f310332 100644
--- a/front/public/locales/en/errors.json
+++ b/front/public/locales/en/errors.json
@@ -220,7 +220,8 @@
       "InvalidUrl": "Invalid url '{{url}}'"
     },
     "work_schedule": {
-      "NameAlreadyUsed": "A group of work schedules with '{{name}}' already exists"
+      "NameAlreadyUsed": "A group of work schedules with '{{name}}' already exists",
+      "WorkScheduleGroupNotFound": "No such work schedule group with id '{{id}}'"
     },
     "temporary_speed_limit": {
       "NameAlreadyUsed": "A group of temporary speed limits with '{{name}}' already exists"
diff --git a/front/public/locales/fr/errors.json b/front/public/locales/fr/errors.json
index c0a3ccaaa58..18ab7faa678 100644
--- a/front/public/locales/fr/errors.json
+++ b/front/public/locales/fr/errors.json
@@ -217,7 +217,8 @@
       "InvalidUrl": "Url invalide '{{url}}'"
     },
     "work_schedule": {
-      "NameAlreadyUsed": "Un groupe de planches travaux avec le nom '{{name}}' existe déjà"
+      "NameAlreadyUsed": "Un groupe de planches travaux avec le nom '{{name}}' existe déjà",
+      "WorkScheduleGroupNotFound": "Le groupe de planche travaux avec l'id {{id}} n'existe pas"
     },
     "temporary_speed_limit": {
       "NameAlreadyUsed": "Un groupe de limites temporaires de vitesse avec le nom '{{name}} existe déjà"
diff --git a/front/src/common/api/generatedEditoastApi.ts b/front/src/common/api/generatedEditoastApi.ts
index ad017ddfc5c..30f89a0b3bf 100644
--- a/front/src/common/api/generatedEditoastApi.ts
+++ b/front/src/common/api/generatedEditoastApi.ts
@@ -911,13 +911,59 @@ const injectedRtkApi = api
         query: () => ({ url: `/version/core` }),
       }),
       postWorkSchedules: build.mutation<PostWorkSchedulesApiResponse, PostWorkSchedulesApiArg>({
+        query: (queryArg) => ({ url: `/work_schedules`, method: 'POST', body: queryArg.body }),
+        invalidatesTags: ['work_schedules'],
+      }),
+      getWorkSchedulesGroup: build.query<
+        GetWorkSchedulesGroupApiResponse,
+        GetWorkSchedulesGroupApiArg
+      >({
+        query: () => ({ url: `/work_schedules/group` }),
+        providesTags: ['work_schedules'],
+      }),
+      postWorkSchedulesGroup: build.mutation<
+        PostWorkSchedulesGroupApiResponse,
+        PostWorkSchedulesGroupApiArg
+      >({
         query: (queryArg) => ({
-          url: `/work_schedules`,
+          url: `/work_schedules/group`,
           method: 'POST',
-          body: queryArg.workScheduleCreateForm,
+          body: queryArg.body,
         }),
         invalidatesTags: ['work_schedules'],
       }),
+      getWorkSchedulesGroupById: build.query<
+        GetWorkSchedulesGroupByIdApiResponse,
+        GetWorkSchedulesGroupByIdApiArg
+      >({
+        query: (queryArg) => ({
+          url: `/work_schedules/group/${queryArg.id}`,
+          params: {
+            page: queryArg.page,
+            page_size: queryArg.pageSize,
+            ordering: queryArg.ordering,
+          },
+        }),
+        providesTags: ['work_schedules'],
+      }),
+      putWorkSchedulesGroupById: build.mutation<
+        PutWorkSchedulesGroupByIdApiResponse,
+        PutWorkSchedulesGroupByIdApiArg
+      >({
+        query: (queryArg) => ({
+          url: `/work_schedules/group/${queryArg.id}`,
+          method: 'PUT',
+          body: queryArg.body,
+        }),
+        invalidatesTags: ['work_schedules'],
+      }),
+      deleteWorkSchedulesGroupById: build.mutation<
+        DeleteWorkSchedulesGroupByIdApiResponse,
+        DeleteWorkSchedulesGroupByIdApiArg
+      >({
+        query: (queryArg) => ({ url: `/work_schedules/group/${queryArg.id}`, method: 'DELETE' }),
+        invalidatesTags: ['work_schedules'],
+      }),
       postWorkSchedulesProjectPath: build.query<
         PostWorkSchedulesProjectPathApiResponse,
         PostWorkSchedulesProjectPathApiArg
@@ -1686,9 +1732,49 @@ export type GetVersionApiArg = void;
 export type GetVersionCoreApiResponse = /** status 200 Return the core service version */ Version;
 export type GetVersionCoreApiArg = void;
 export type PostWorkSchedulesApiResponse =
-  /** status 201 The id of the created work schedule group */ WorkScheduleCreateResponse;
+  /** status 201 The id of the created work schedule group */ {
+    work_schedule_group_id: number;
+  };
 export type PostWorkSchedulesApiArg = {
-  workScheduleCreateForm: WorkScheduleCreateForm;
+  body: {
+    work_schedule_group_name: string;
+    work_schedules: WorkScheduleItemForm[];
+  };
+};
+export type GetWorkSchedulesGroupApiResponse =
+  /** status 201 The existing work schedule group ids */ number[];
+export type GetWorkSchedulesGroupApiArg = void;
+export type PostWorkSchedulesGroupApiResponse =
+  /** status 200 The id of the created work schedule group */ {
+    work_schedule_group_id: number;
+  };
+export type PostWorkSchedulesGroupApiArg = {
+  body: {
+    work_schedule_group_name?: string | null;
+  };
+};
+export type GetWorkSchedulesGroupByIdApiResponse =
+  /** status 200 The work schedules in the group */ PaginationStats & {
+    results: WorkSchedule[];
+  };
+export type GetWorkSchedulesGroupByIdApiArg = {
+  page?: number;
+  pageSize?: number | null;
+  /** A work schedule group ID */
+  id: number;
+  ordering?: Ordering;
+};
+export type PutWorkSchedulesGroupByIdApiResponse =
+  /** status 200 The work schedules have been created */ WorkSchedule[];
+export type PutWorkSchedulesGroupByIdApiArg = {
+  /** A work schedule group ID */
+  id: number;
+  body: WorkScheduleItemForm[];
+};
+export type DeleteWorkSchedulesGroupByIdApiResponse = unknown;
+export type DeleteWorkSchedulesGroupByIdApiArg = {
+  /** A work schedule group ID */
+  id: number;
 };
 export type PostWorkSchedulesProjectPathApiResponse =
   /** status 201 Returns a list of work schedules whose track ranges intersect the given path */ {
@@ -3389,9 +3475,6 @@ export type TrainScheduleForm = TrainScheduleBase & {
 export type Version = {
   git_describe: string | null;
 };
-export type WorkScheduleCreateResponse = {
-  work_schedule_group_id: number;
-};
 export type WorkScheduleItemForm = {
   end_date_time: string;
   obj_id: string;
@@ -3399,9 +3482,15 @@ export type WorkScheduleItemForm = {
   track_ranges: TrackRange[];
   work_schedule_type: 'CATENARY' | 'TRACK';
 };
-export type WorkScheduleCreateForm = {
-  work_schedule_group_name: string;
-  work_schedules: WorkScheduleItemForm[];
+export type WorkScheduleType = 'CATENARY' | 'TRACK';
+export type WorkSchedule = {
+  end_date_time: string;
+  id: number;
+  obj_id: string;
+  start_date_time: string;
+  track_ranges: TrackRange[];
+  work_schedule_group_id: number;
+  work_schedule_type: WorkScheduleType;
 };
 export type Intersection = {
   /** Distance of the end of the intersection relative to the beginning of the path */