diff --git a/app/services/ci/pipeline_trigger_service.rb b/app/services/ci/pipeline_trigger_service.rb
index 7340e90c5351b4d1e55ba780c627c0ae1418cd02..476f46a72c6e896de774bb1778bda2e507735ad5 100644
--- a/app/services/ci/pipeline_trigger_service.rb
+++ b/app/services/ci/pipeline_trigger_service.rb
@@ -31,7 +31,7 @@ def create_pipeline_from_trigger(trigger)
 
       response = Ci::CreatePipelineService
         .new(project, trigger.owner, ref: params[:ref], variables_attributes: variables)
-        .execute(:trigger, ignore_skip_ci: true) do |pipeline|
+        .execute(:trigger, ignore_skip_ci: true, inputs: inputs) do |pipeline|
           pipeline.trigger = trigger
           pipeline.trigger_requests.build(trigger: trigger, project_id: project.id)
         end
@@ -62,7 +62,7 @@ def create_pipeline_from_job(job)
 
       response = Ci::CreatePipelineService
         .new(project, job.user, ref: params[:ref], variables_attributes: variables)
-        .execute(:pipeline, ignore_skip_ci: true) do |pipeline|
+        .execute(:pipeline, ignore_skip_ci: true, inputs: inputs) do |pipeline|
           source = job.sourced_pipelines.build(
             source_pipeline: job.pipeline,
             source_project: job.project,
@@ -82,6 +82,10 @@ def job_from_token
       end
     end
 
+    def inputs
+      params[:inputs]
+    end
+
     def variables
       param_variables + [payload_variable]
     end
diff --git a/doc/api/pipeline_triggers.md b/doc/api/pipeline_triggers.md
index 699a415f26b5d8cd4ccf047b2c94eda46da951c7..cf12a9d9b2b610e9e351790f21424db638b3a87d 100644
--- a/doc/api/pipeline_triggers.md
+++ b/doc/api/pipeline_triggers.md
@@ -156,6 +156,13 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
 
 ## Trigger a pipeline with a token
 
+{{< history >}}
+
+- `inputs` attribute [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/519958) in GitLab 17.10
+[with a flag](../administration/feature_flags.md) named `ci_inputs_for_pipelines`. Disabled by default.
+
+{{< /history >}}
+
 Trigger a pipeline by using a [pipeline trigger token](../ci/triggers/_index.md#create-a-pipeline-trigger-token)
 or a [CI/CD job token](../ci/jobs/ci_job_token.md) for authentication.
 
@@ -177,11 +184,26 @@ Supported attributes:
 | `ref`       | string         | Yes      | The branch or tag to run the pipeline on. |
 | `token`     | string         | Yes      | The trigger token or CI/CD job token. |
 | `variables` | hash           | No       | A map of key-valued strings containing the pipeline variables. For example: `{ VAR1: "value1", VAR2: "value2" }`. |
+| `inputs`    | hash           | No       | A map of inputs, as key-value pairs, to use when creating the pipeline. Required feature flag: `ci_inputs_for_pipelines` |
+
+Example request with [variables](../ci/variables/_index.md):
+
+```shell
+curl --request POST \
+  --form "variables[VAR1]=value1" \
+  --form "variables[VAR2]=value2" \
+  "https://gitlab.example.com/api/v4/projects/123/trigger/pipeline?token=2cb1840fb9dfc9fb0b7b1609cd29cb&ref=main"
+```
+
+Example request with [inputs](../ci/yaml/inputs.md):
 
-Example request:
+_Required [feature flag](feature_flags.md): `ci_inputs_for_pipelines`_
 
 ```shell
-curl --request POST --form "variables[VAR1]=value1" --form "variables[VAR2]=value2" "https://gitlab.example.com/api/v4/projects/123/trigger/pipeline?token=2cb1840fb9dfc9fb0b7b1609cd29cb&ref=main"
+curl --request POST \
+  --header "Content-Type: application/json" \
+  --data '{"inputs": {"environment": "environment", "scan_security": false, "level": 3}}' \
+  "https://gitlab.example.com/api/v4/projects/123/trigger/pipeline?token=2cb1840fb9dfc9fb0b7b1609cd29cb&ref=main"
 ```
 
 Example response:
diff --git a/lib/api/ci/triggers.rb b/lib/api/ci/triggers.rb
index f9afa754f6342e63de50c23dcbbe76c7ba2c9357..2b106e73c2eefb49354d1431d95754e5eb3578df 100644
--- a/lib/api/ci/triggers.rb
+++ b/lib/api/ci/triggers.rb
@@ -31,6 +31,7 @@ class Triggers < ::API::Base
             documentation: { example: '6d056f63e50fe6f8c5f8f4aa10edb7' }
           optional :variables, type: Hash, desc: 'The list of variables to be injected into build',
             documentation: { example: { VAR1: "value1", VAR2: "value2" } }
+          optional :inputs, type: Hash, desc: 'The list of inputs to be used to create the pipeline.'
         end
         post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do
           Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20758')
diff --git a/lib/ci/pipeline_creation/inputs.rb b/lib/ci/pipeline_creation/inputs.rb
new file mode 100644
index 0000000000000000000000000000000000000000..af1234de8ba8c9ca33d9a740a0ff956e72a22019
--- /dev/null
+++ b/lib/ci/pipeline_creation/inputs.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Ci
+  module PipelineCreation
+    module Inputs
+      def self.parse_params(params)
+        return params unless params.is_a?(Hash)
+
+        params.to_hash.transform_values do |value| # `to_hash` to avoid `ActiveSupport::HashWithIndifferentAccess`
+          next value unless value.is_a?(String)
+
+          begin
+            Gitlab::Json.parse(value) # convert to number, boolean, array
+          rescue JSON::ParserError
+            value # we treat the value as-is as it's likely a string like 'blue-green'.
+          end
+        end.deep_symbolize_keys # `deep_symbolize_keys` because Interpolator requires
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/config/content.rb b/lib/gitlab/ci/pipeline/chain/config/content.rb
index cdd9a0825d35bc0315a0573c109d63c71aed986f..f7824fb9a89b9e1b8271ecc0db651870878771c6 100644
--- a/lib/gitlab/ci/pipeline/chain/config/content.rb
+++ b/lib/gitlab/ci/pipeline/chain/config/content.rb
@@ -35,7 +35,7 @@ def pipeline_config
                   triggered_for_branch: @pipeline.branch?,
                   ref: @pipeline.ref,
                   pipeline_policy_context: @command.pipeline_policy_context,
-                  inputs: @command.inputs
+                  inputs: ::Ci::PipelineCreation::Inputs.parse_params(@command.inputs)
                 )
               end
             end
diff --git a/lib/gitlab/ci/project_config/source.rb b/lib/gitlab/ci/project_config/source.rb
index d355e879db34ae78e38265104c029bca99d13ba4..219980df32731790753b0946588278ad0b939628 100644
--- a/lib/gitlab/ci/project_config/source.rb
+++ b/lib/gitlab/ci/project_config/source.rb
@@ -61,7 +61,9 @@ def ci_yaml_include(config)
         end
 
         def include_inputs
-          { 'inputs' => inputs }.compact_blank
+          { inputs: inputs }
+            .compact_blank
+            .deep_stringify_keys # to avoid symbols in the YAML
         end
       end
     end
diff --git a/spec/lib/ci/pipeline_creation/inputs_spec.rb b/spec/lib/ci/pipeline_creation/inputs_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..703f105acae93aea245bd27ee4063fe0c0c560ea
--- /dev/null
+++ b/spec/lib/ci/pipeline_creation/inputs_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'oj'
+require_relative Rails.root.join('lib/ci/pipeline_creation/inputs.rb')
+
+RSpec.describe Ci::PipelineCreation::Inputs, feature_category: :pipeline_composition do
+  describe '.parse_params' do
+    let(:params) do
+      {
+        'string_param' => 'regular-string',
+        json_array: '[1, 2, 3]',
+        'json_object' => '{"key": "value"}',
+        'json_boolean' => 'true',
+        'json_number' => '42',
+        'nested' => { 'param' => 'value' }
+      }
+    end
+
+    subject(:parse_params) { described_class.parse_params(params) }
+
+    it 'transforms values' do
+      expect(parse_params).to include(
+        string_param: 'regular-string',
+        json_array: [1, 2, 3],
+        json_object: { key: 'value' },
+        json_boolean: true,
+        json_number: 42,
+        nested: { param: 'value' }
+      )
+    end
+
+    context 'when params are not a hash' do
+      let(:params) { 'not a hash' }
+
+      it 'returns the params as-is' do
+        expect(parse_params).to eq('not a hash')
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
index 09e8ca4561255abad794691a3a6707e6cb8bb2eb..be432859afbb8e46048a864950240d852d6ddef2 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
@@ -150,14 +150,26 @@
       end
 
       context 'when passing inputs' do
-        let(:inputs) { { 'foo' => 'bar' } }
+        let(:inputs) do
+          {
+            'string' => 'bar',
+            boolean: true,
+            'array' => [{ 'foo' => 'bar' }],
+            number: 1
+          }
+        end
+
         let(:config_content_result) do
           <<~CICONFIG
             ---
             include:
             - local: ".gitlab-ci.yml"
               inputs:
-                foo: bar
+                string: bar
+                boolean: true
+                array:
+                - foo: bar
+                number: 1
           CICONFIG
         end
 
@@ -168,6 +180,7 @@
           expect(pipeline.pipeline_config.content).to eq(config_content_result)
           expect(command.config_content).to eq(config_content_result)
           expect(command.pipeline_config.internal_include_prepended?).to eq(true)
+          expect(command.pipeline_config.inputs_for_pipeline_creation).to eq({})
         end
       end
     end
@@ -217,7 +230,15 @@
       end
 
       context 'when passing inputs' do
-        let(:inputs) { { 'foo' => 'bar' } }
+        let(:content) do
+          <<~EOY
+            ---
+            stages:
+              - $[[ inputs.stage ]]
+          EOY
+        end
+
+        let(:inputs) { { stage: 'bar' } }
 
         it 'uses the parameter content with inputs' do
           subject.perform!
@@ -226,6 +247,7 @@
           expect(pipeline.pipeline_config.content).to eq(content)
           expect(command.config_content).to eq(content)
           expect(command.pipeline_config.internal_include_prepended?).to eq(false)
+          expect(command.pipeline_config.inputs_for_pipeline_creation).to eq(inputs)
         end
       end
     end
diff --git a/spec/requests/api/ci/triggers_spec.rb b/spec/requests/api/ci/triggers_spec.rb
index 615d3b15c890665fa3ecb08c8fcc9eb5fe75d33c..330e271d3a16d0ca104d5f3f42a15bc0f50e7a15 100644
--- a/spec/requests/api/ci/triggers_spec.rb
+++ b/spec/requests/api/ci/triggers_spec.rb
@@ -181,6 +181,84 @@
         expect(response).to have_gitlab_http_status(:forbidden)
       end
     end
+
+    context 'when using inputs' do
+      let(:inputs) do
+        {
+          deploy_strategy: 'blue-green',
+          job_stage: 'deploy',
+          test_script: ['echo "test"'],
+          parallel_jobs: 3,
+          allow_failure: true,
+          test_rules: [
+            { if: '$CI_PIPELINE_SOURCE == "web"' }
+          ]
+        }
+      end
+
+      before do
+        stub_ci_pipeline_yaml_file(
+          File.read(Rails.root.join('spec/lib/gitlab/ci/config/yaml/fixtures/complex-included-ci.yml'))
+        )
+      end
+
+      shared_examples 'sending request using inputs' do
+        shared_examples 'creating a succesful pipeline' do
+          it 'creates a pipeline using inputs' do
+            expect { post_request }.to change { Ci::Pipeline.count }.by(1)
+
+            expect(response).to have_gitlab_http_status(:created)
+
+            pipeline = Ci::Pipeline.find(json_response['id'])
+
+            expect(pipeline.builds.map { |b| "#{b.name} #{b.allow_failure}" }).to contain_exactly(
+              'my-job-build 1/3 false', 'my-job-build 2/3 false', 'my-job-build 3/3 false',
+              'my-job-test true', 'my-job-deploy false'
+            )
+          end
+        end
+
+        context 'when passing parameters as JSON' do
+          let(:headers) do
+            { 'Content-Type' => 'application/json' }
+          end
+
+          subject(:post_request) do
+            post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{token}"),
+              headers: headers,
+              params: { ref: 'refs/heads/other-branch', inputs: inputs }.to_json
+          end
+
+          it_behaves_like 'creating a succesful pipeline'
+        end
+
+        context 'when passing parameters as form data' do
+          let(:headers) do
+            { 'Content-Type' => 'application/x-www-form-urlencoded' }
+          end
+
+          subject(:post_request) do
+            post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{token}"),
+              headers: headers,
+              params: { ref: 'refs/heads/other-branch', inputs: inputs.transform_values(&:to_json) }
+          end
+
+          it_behaves_like 'creating a succesful pipeline'
+        end
+      end
+
+      context 'when triggering a pipeline from a trigger token' do
+        let!(:token) { trigger_token }
+
+        it_behaves_like 'sending request using inputs'
+      end
+
+      context 'when triggered from another running job' do
+        let!(:token) { create(:ci_build, :running, project: project, user: user).token }
+
+        it_behaves_like 'sending request using inputs'
+      end
+    end
   end
 
   describe 'GET /projects/:id/triggers' do
diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb
index 68f0ca5efeeeddd810b1200e998ab1c8bf03969a..d459a9968aa3f2e3d0560b6436cb4dc0744a2c6e 100644
--- a/spec/services/ci/pipeline_trigger_service_spec.rb
+++ b/spec/services/ci/pipeline_trigger_service_spec.rb
@@ -45,6 +45,33 @@
       end
     end
 
+    shared_examples 'accepting inputs' do
+      let(:inputs) do
+        {
+          deploy_strategy: 'blue-green',
+          job_stage: 'deploy',
+          test_script: ['echo "test"'],
+          test_rules: [
+            { if: '$CI_PIPELINE_SOURCE == "web"' }
+          ]
+        }
+      end
+
+      before do
+        stub_ci_pipeline_yaml_file(
+          File.read(Rails.root.join('spec/lib/gitlab/ci/config/yaml/fixtures/complex-included-ci.yml'))
+        )
+      end
+
+      it 'triggers a pipeline using inputs' do
+        expect { result }.to change { Ci::Pipeline.count }.by(1)
+
+        expect(result.payload[:pipeline].builds.map(&:name)).to contain_exactly(
+          'my-job-build 1/2', 'my-job-build 2/2', 'my-job-test', 'my-job-deploy'
+        )
+      end
+    end
+
     context 'with a trigger token' do
       let(:trigger) { create(:ci_trigger, project: project, owner: user) }
 
@@ -150,6 +177,12 @@
           expect(result).to be_nil
         end
       end
+
+      context 'when using inputs' do
+        let(:params) { { token: trigger.token, ref: 'master', inputs: inputs } }
+
+        it_behaves_like 'accepting inputs'
+      end
     end
 
     context 'with a pipeline job token' do
@@ -253,6 +286,12 @@
           expect(result).to be_nil
         end
       end
+
+      context 'when using inputs' do
+        let(:params) { { token: job.token, ref: 'master', inputs: inputs } }
+
+        it_behaves_like 'accepting inputs'
+      end
     end
   end
 end