# Step uploads API

A step upload is a pipeline configuration uploaded by [`buildkite-agent pipeline upload`](/docs/agent/cli/reference/pipeline) while a build is running, as part of a [dynamic pipeline](/docs/pipelines/configure/dynamic-pipelines). 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](/docs/apis/graphql-api#graphql-ids) 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](#get-a-step-upload) to fetch the definition for a specific upload.

```bash
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](/docs/apis/rest-api/builds#build-number-vs-build-id).

```json
{
  "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](/docs/api#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.

```bash
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](/docs/apis/rest-api/builds#build-number-vs-build-id).

```json
{
  "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 |


