REST API: Add token expiration params to POST /user/runners API
What does this MR do and why?
Adds two new optional parameters -- token_expires_at and token_rotation_deadline -- to the POST /user/runners REST API endpoint, along with the shared service-layer validation. This is an EE-only feature.
Organizations implementing automated "Bring Your Own Runner" (BYOR) services need per-token expiration control at creation time. The existing API only supports instance-wide automatic rotation policies but doesn't allow explicit token expiration when creating runners programmatically.
New parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
token_expires_at |
DateTime |
No | Expiration time for the runner authentication token (ISO 8601). Must be between 5 minutes and 15 days in the future, and cannot exceed instance/group/project-level limits. Only applies to the initial token -- rotated tokens use computed expiry from settings. |
token_rotation_deadline |
DateTime |
No | Deadline after which token rotation is rejected. Requires token_expires_at and must be ≤ token_expires_at. Setting both to the same value disables rotation entirely. |
Key changes
-
lib/api/user_runners.rb-- includes newRunnerHelpersand addstoken_expires_at/token_rotation_deadlinetoattributes_for_keys -
lib/api/helpers/runner_helpers.rb(new) -- CE stub with empty:create_runner_params_eeparam block -
ee/lib/ee/api/helpers/runner_helpers.rb(new) -- EE override defining the two new Grape parameters with a conditional requirement (token_rotation_deadlinerequirestoken_expires_at) -
ee/app/services/ee/ci/runners/create_runner_service.rb-- addsnormalize_token_expiration_params(convertsDateTime→Time, stores inparams[:explicit_token_expires_at]andparams[:token_rotation_deadline]),convert_to_time, andvalidate_token_expiration_params/validate_token_rotation_deadlinewith validation for min/max expiry, rotation deadline constraints, and a guard requiringtoken_expires_atwhentoken_rotation_deadlineis specified -
ee/spec/services/ci/runners/create_runner_service_spec.rb-- updates the "withouttoken_expires_at" test to expect a validation error (was previously silently accepted) -
doc/api/users.md-- documents the new parameters
References
- Part of #573604 (closed) (Phase 1: API Layer + Phase 2: Service Layer + Phase 5: Documentation)
Screenshots or screen recordings
Not applicable -- backend API change only.
How to set up and validate locally
- Create a runner with token expiration:
curl --request POST --header "PRIVATE-TOKEN: $GITLAB_GDK_TOKEN" \ --data "runner_type=project_type&project_id=<id>&token_expires_at=2026-03-15T00:00:00Z" \ "http://gdk.test:3000/api/v4/user/runners" - Verify
token_expires_atis set on the created runner:Ci::Runner.last.token_expires_at # => 2026-03-15 00:00:00 UTC - Verify validation -- token too soon (< 5 minutes):
curl --request POST --header "PRIVATE-TOKEN: $GITLAB_GDK_TOKEN" \ --data "runner_type=project_type&project_id=<id>&token_expires_at=2026-03-05T17:45:00Z" \ "http://gdk.test:3000/api/v4/user/runners" # => 400 Bad Request with "token_expires_at must be at least 5 minutes in the future" - Verify validation --
token_rotation_deadlinewithouttoken_expires_at:curl --request POST --header "PRIVATE-TOKEN: $GITLAB_GDK_TOKEN" \ --data "runner_type=project_type&project_id=<id>&token_rotation_deadline=2026-03-10T00:00:00Z" \ "http://gdk.test:3000/api/v4/user/runners" # => 400 Bad Request with "token_expires_at is missing" - Run specs:
bundle exec rspec ee/spec/requests/api/user_runners_spec.rb \ spec/requests/api/user_runners_spec.rb \ ee/spec/services/ci/runners/create_runner_service_spec.rb \ spec/services/ci/runners/create_runner_service_spec.rb
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Edited by Pedro Pombeiro