Step uploads API

A step upload is a pipeline configuration uploaded by buildkite-agent pipeline upload while a build is running, as part of a dynamic pipeline. This API provides read-only access to a build's step uploads, including the definition that was uploaded.

Step uploads are only available for builds within their maximum lifetime (up to 30 days after creation). Requesting step uploads for an older build returns a 410 Gone response.

Step upload data model

uuid UUID of the step upload
graphql_id GraphQL ID of the step upload
state State of the step upload. One of pending, processing, applied, rejected, or failed
source Source of the step upload. Currently always job
source_job_id ID of the job that performed the upload
replace_existing_steps Whether the upload replaced the rest of the pipeline's steps instead of appending to them. Corresponds to the --replace flag
created_jobs_count Number of jobs created from this upload. null while the upload hasn't been applied yet, as distinct from 0 for an applied upload that created no jobs
rejection_type Type of rejection for a rejected upload. One of custom_error, validation_error, build_not_running_error, or job_creation_error. Otherwise null
message Human-readable message describing the outcome of rejected and failed uploads, otherwise null. The message never includes the uploaded configuration or internal failure details
url Canonical API URL of the step upload
created_at When the step upload was received
processed_at When the step upload finished processing, or null if it hasn't finished yet

List a build's step uploads

Returns a paginated list of step uploads for a build, newest first. The uploaded definitions aren't included. Use Get a step upload to fetch the definition for a specific upload.

curl -H "Authorization: Bearer $TOKEN" \
  -X GET "https://api.buildkite.com/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}/step-uploads"

Note that this URL requires using a build number (for example, 3), not a build ID (for example, 01908131-7d9f-495e-a17b-80ed31276810).

Learn more about the difference between these concepts in Build number vs build ID.

{
  "items": [
    {
      "uuid": "0198f2f4-1c33-4e0a-9d5e-3a4a5b6c7d8e",
      "graphql_id": "QnVpbGRTdGVwVXBsb2FkLS0tMDE5OGYyZjQtMWMzMy00ZTBhLTlkNWUtM2E0YTViNmM3ZDhl",
      "state": "applied",
      "source": "job",
      "source_job_id": "0198f2f3-64a2-4a8e-8b78-0d9157a0e35f",
      "replace_existing_steps": false,
      "created_jobs_count": 1,
      "rejection_type": null,
      "message": null,
      "url": "https://api.buildkite.com/v2/organizations/my-great-org/pipelines/my-pipeline/builds/42/step-uploads/0198f2f4-1c33-4e0a-9d5e-3a4a5b6c7d8e",
      "created_at": "2026-08-11T10:15:32.000Z",
      "processed_at": "2026-08-11T10:15:33.000Z"
    }
  ],
  "links": {
    "self": "https://api.buildkite.com/v2/organizations/my-great-org/pipelines/my-pipeline/builds/42/step-uploads?per_page=30",
    "next": "https://api.buildkite.com/v2/organizations/my-great-org/pipelines/my-pipeline/builds/42/step-uploads?after=...&per_page=30"
  }
}

This endpoint uses cursor-based pagination. The response body is a JSON object with an items array and a links object. Use the next URL from links to fetch the next page, following it verbatim rather than constructing your own cursor values.

Optional query string parameters:

filter[source_job_id] Returns only step uploads made by the given job.

Example: filter[source_job_id]=0198f2f3-64a2-4a8e-8b78-0d9157a0e35f

per_page How many results to return per page.

Default: 30

Maximum: 100

after Return results after this cursor value. Mutually exclusive with before.
before Return results before this cursor value. Mutually exclusive with after.

Required scope: read_builds

Success response: 200 OK

Error responses:

400 Bad Request Invalid per_page or cursor value, or both after and before supplied
422 Unprocessable Entity Unsupported filter key, or an invalid filter[source_job_id] value
410 Gone The build is outside its maximum lifetime

Get a step upload

Returns a single step upload, including its uploaded definition rendered as YAML.

curl -H "Authorization: Bearer $TOKEN" \
  -X GET "https://api.buildkite.com/v2/organizations/{org.slug}/pipelines/{pipeline.slug}/builds/{build.number}/step-uploads/{uuid}"

Note that this URL requires using a build number (for example, 3), not a build ID (for example, 01908131-7d9f-495e-a17b-80ed31276810).

Learn more about the difference between these concepts in Build number vs build ID.

{
  "uuid": "0198f2f4-1c33-4e0a-9d5e-3a4a5b6c7d8e",
  "graphql_id": "QnVpbGRTdGVwVXBsb2FkLS0tMDE5OGYyZjQtMWMzMy00ZTBhLTlkNWUtM2E0YTViNmM3ZDhl",
  "state": "applied",
  "source": "job",
  "source_job_id": "0198f2f3-64a2-4a8e-8b78-0d9157a0e35f",
  "replace_existing_steps": false,
  "created_jobs_count": 1,
  "rejection_type": null,
  "message": null,
  "url": "https://api.buildkite.com/v2/organizations/my-great-org/pipelines/my-pipeline/builds/42/step-uploads/0198f2f4-1c33-4e0a-9d5e-3a4a5b6c7d8e",
  "created_at": "2026-08-11T10:15:32.000Z",
  "processed_at": "2026-08-11T10:15:33.000Z",
  "definition_bytes": 61,
  "definition_yaml": "---\nsteps:\n- command: echo hello\n  key: dynamic-step\n",
  "definition_yaml_omitted": false
}

The uploaded document isn't retained as raw text, so definition_yaml is re-rendered from the stored definition and may not preserve the original file's formatting, comments, or anchors. definition_bytes reports the size of the serialized JSON definition, not the rendered YAML.

Definitions whose serialized JSON is larger than 2 MB aren't rendered as YAML. For these, definition_yaml is null and definition_yaml_omitted is true, while definition_bytes still reports the serialized JSON size.

Required scope: read_builds

Success response: 200 OK

Error responses:

404 Not Found No step upload matches the given UUID for this build
410 Gone The build is outside its maximum lifetime