diff --git a/doc/api/environments.md b/doc/api/environments.md
index d23351493019a7c3599b5adc3bfd4fa82c87a3d3..87f99bc9034e112ccfdd510284dd55317a5353b0 100644
--- a/doc/api/environments.md
+++ b/doc/api/environments.md
@@ -7,6 +7,8 @@ type: concepts, howto
 
 # Environments API **(FREE)**
 
+> Support for [GitLab CI/CD job token](../ci/jobs/ci_job_token.md) authentication [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/414549) in GitLab 16.2.
+
 ## List environments
 
 Get all environments for a given project.
diff --git a/doc/ci/jobs/ci_job_token.md b/doc/ci/jobs/ci_job_token.md
index a67cd2de4e8bf87461bf85a99147885060d5f4ba..c2fe3071b5285f42edb9f7832197f48192345146 100644
--- a/doc/ci/jobs/ci_job_token.md
+++ b/doc/ci/jobs/ci_job_token.md
@@ -25,6 +25,7 @@ You can use a GitLab CI/CD job token to authenticate with specific API endpoints
 - [Releases](../../api/releases/index.md) and [Release links](../../api/releases/links.md).
 - [Terraform plan](../../user/infrastructure/index.md).
 - [Deployments](../../api/deployments.md).
+- [Environments](../../api/environments.md).
 
 The token has the same permissions to access the API as the user that caused the
 job to run. A user can cause a job to run by taking action like pushing a commit,
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index bb261079d2a08fcdde6ea1d738534bff8f66fba8..b94391359ed75f98743fb5c4512f0ae24770d3ce 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -38,6 +38,7 @@ class Environments < ::API::Base
           desc: 'List all environments that match a specific state. Accepted values: `available`, `stopping`, or `stopped`. If no state value given, returns all environments'
         mutually_exclusive :name, :search, message: 'cannot be used together'
       end
+      route_setting :authentication, job_token_allowed: true
       get ':id/environments' do
         authorize! :read_environment, user_project
 
@@ -66,6 +67,7 @@ class Environments < ::API::Base
         optional :slug, absence: { message: "is automatically generated and cannot be changed" }, documentation: { hidden: true }
         optional :tier, type: String, values: Environment.tiers.keys, desc: 'The tier of the new environment. Allowed values are `production`, `staging`, `testing`, `development`, and `other`'
       end
+      route_setting :authentication, job_token_allowed: true
       post ':id/environments' do
         authorize! :create_environment, user_project
 
@@ -94,6 +96,7 @@ class Environments < ::API::Base
         optional :slug, absence: { message: "is automatically generated and cannot be changed" }, documentation: { hidden: true }
         optional :tier, type: String, values: Environment.tiers.keys, desc: 'The tier of the new environment. Allowed values are `production`, `staging`, `testing`, `development`, and `other`'
       end
+      route_setting :authentication, job_token_allowed: true
       put ':id/environments/:environment_id' do
         authorize! :update_environment, user_project
 
@@ -126,6 +129,7 @@ class Environments < ::API::Base
         optional :limit, type: Integer, desc: "Maximum number of environments to delete. Defaults to 100", default: 100, values: 1..1000
         optional :dry_run, type: Boolean, desc: "Defaults to true for safety reasons. It performs a dry run where no actual deletion will be performed. Set to false to actually delete the environment", default: true
       end
+      route_setting :authentication, job_token_allowed: true
       delete ":id/environments/review_apps" do
         authorize! :read_environment, user_project
 
@@ -156,6 +160,7 @@ class Environments < ::API::Base
       params do
         requires :environment_id, type: Integer, desc: 'The ID of the environment'
       end
+      route_setting :authentication, job_token_allowed: true
       delete ':id/environments/:environment_id' do
         authorize! :read_environment, user_project
 
@@ -178,6 +183,7 @@ class Environments < ::API::Base
         requires :environment_id, type: Integer, desc: 'The ID of the environment'
         optional :force, type: Boolean, default: false, desc: 'Force environment to stop without executing `on_stop` actions'
       end
+      route_setting :authentication, job_token_allowed: true
       post ':id/environments/:environment_id/stop' do
         authorize! :read_environment, user_project
 
@@ -202,6 +208,7 @@ class Environments < ::API::Base
                  type: DateTime,
                  desc: 'Stop all environments that were last modified or deployed to before this date.'
       end
+      route_setting :authentication, job_token_allowed: true
       post ':id/environments/stop_stale' do
         authorize! :stop_environment, user_project
 
@@ -229,6 +236,7 @@ class Environments < ::API::Base
       params do
         requires :environment_id, type: Integer, desc: 'The ID of the environment'
       end
+      route_setting :authentication, job_token_allowed: true
       get ':id/environments/:environment_id' do
         authorize! :read_environment, user_project
 
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index 9a435b3bce952f32c3c57f92cf87180bc5e24b67..498e030da0bec9f2b60b462847eff9a0d2b4faac 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -31,6 +31,14 @@
         expect(json_response.first).not_to have_key('last_deployment')
       end
 
+      it 'returns 200 HTTP status when using JOB-TOKEN auth' do
+        job = create(:ci_build, :running, project: project, user: user)
+
+        get api("/projects/#{project.id}/environments"), params: { job_token: job.token }
+
+        expect(response).to have_gitlab_http_status(:ok)
+      end
+
       context 'when filtering' do
         let_it_be(:stopped_environment) { create(:environment, :stopped, project: project) }
 
@@ -132,6 +140,14 @@
         expect(json_response['external']).to be nil
       end
 
+      it 'returns 200 HTTP status when using JOB-TOKEN auth' do
+        job = create(:ci_build, :running, project: project, user: user)
+
+        post api("/projects/#{project.id}/environments"), params: { name: "mepmep", job_token: job.token }
+
+        expect(response).to have_gitlab_http_status(:created)
+      end
+
       it 'requires name to be passed' do
         post api("/projects/#{project.id}/environments", user), params: { external_url: 'test.gitlab.com' }
 
@@ -173,6 +189,15 @@
         expect(response).to have_gitlab_http_status(:ok)
       end
 
+      it 'returns 200 HTTP status when using JOB-TOKEN auth' do
+        job = create(:ci_build, :running, project: project, user: user)
+
+        post api("/projects/#{project.id}/environments/stop_stale"),
+             params: { before: 1.week.ago.to_date.to_s, job_token: job.token }
+
+        expect(response).to have_gitlab_http_status(:ok)
+      end
+
       it 'returns a 400 for bad input date' do
         post api("/projects/#{project.id}/environments/stop_stale", user), params: { before: 1.day.ago.to_date.to_s }
 
@@ -229,6 +254,15 @@
       expect(json_response['tier']).to eq('production')
     end
 
+    it 'returns 200 HTTP status when using JOB-TOKEN auth' do
+      job = create(:ci_build, :running, project: project, user: user)
+
+      put api("/projects/#{project.id}/environments/#{environment.id}"),
+          params: { tier: 'production', job_token: job.token }
+
+      expect(response).to have_gitlab_http_status(:ok)
+    end
+
     it "won't allow slug to be changed" do
       slug = environment.slug
       api_url = api("/projects/#{project.id}/environments/#{environment.id}", user)
@@ -261,6 +295,17 @@
         expect(response).to have_gitlab_http_status(:no_content)
       end
 
+      it 'returns 204 HTTP status when using JOB-TOKEN auth' do
+        environment.stop
+
+        job = create(:ci_build, :running, project: project, user: user)
+
+        delete api("/projects/#{project.id}/environments/#{environment.id}"),
+               params: { job_token: job.token }
+
+        expect(response).to have_gitlab_http_status(:no_content)
+      end
+
       it 'returns a 404 for non existing id' do
         delete api("/projects/#{project.id}/environments/#{non_existing_record_id}", user)
 
@@ -291,17 +336,23 @@
       context 'with a stoppable environment' do
         before do
           environment.update!(state: :available)
-
-          post api("/projects/#{project.id}/environments/#{environment.id}/stop", user)
         end
 
         it 'returns a 200' do
+          post api("/projects/#{project.id}/environments/#{environment.id}/stop", user)
+
           expect(response).to have_gitlab_http_status(:ok)
           expect(response).to match_response_schema('public_api/v4/environment')
+          expect(environment.reload).to be_stopped
         end
 
-        it 'actually stops the environment' do
-          expect(environment.reload).to be_stopped
+        it 'returns 200 HTTP status when using JOB-TOKEN auth' do
+          job = create(:ci_build, :running, project: project, user: user)
+
+          post api("/projects/#{project.id}/environments/#{environment.id}/stop"),
+               params: { job_token: job.token }
+
+          expect(response).to have_gitlab_http_status(:ok)
         end
       end
 
@@ -333,6 +384,15 @@
         expect(response).to match_response_schema('public_api/v4/environment')
         expect(json_response['last_deployment']).to be_present
       end
+
+      it 'returns 200 HTTP status when using JOB-TOKEN auth' do
+        job = create(:ci_build, :running, project: project, user: user)
+
+        get api("/projects/#{project.id}/environments/#{environment.id}"),
+            params: { job_token: job.token }
+
+        expect(response).to have_gitlab_http_status(:ok)
+      end
     end
 
     context 'as non member' do