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: |
|---|---|
per_page |
How many results to return per page.
Default: Maximum: |
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 |