> For the complete documentation index, see [llms.txt](https://docs.drizz.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.drizz.dev/automate-and-integrate/upload-a-build.md).

# Upload a build

`POST <DRIZZ_API_BASE_URL>/apk/upload` registers an app binary so test plans can install and run it. The call is optional — skip it when the version under test is already registered.

|               |                                                                                              |
| ------------- | -------------------------------------------------------------------------------------------- |
| **Platforms** | Android (`.apk`) · iOS (`.ipa`, real-device build)                                           |
| **Body**      | `multipart/form-data`, one field: `file`                                                     |
| **Max size**  | 500 MB                                                                                       |
| **Returns**   | Package name, version name, version code                                                     |
| **Watch out** | Uploading a version that already exists returns **409** — upload only when the build changes |

## Prerequisites

* Access token from [Authenticate](/automate-and-integrate/authenticate.md), in `$DRIZZ_API_KEY`
* API base URL, in `$DRIZZ_BASE_URL`
* A signed build under 500 MB — `.apk` (Android) or `.ipa` for a real device (iOS)
* `curl`, or an equivalent HTTP client

## Copy this

```bash
# Upload an Android build. $DRIZZ_API_KEY is the token from the auth call
curl -X POST "$DRIZZ_BASE_URL/apk/upload" \
  -H "x-api-key: $DRIZZ_API_KEY" \
  -F "file=@app-release.apk"
```

## Endpoint

```
POST <DRIZZ_API_BASE_URL>/apk/upload
```

The same endpoint accepts iOS binaries. Drizz validates the binary, then registers it with the version metadata extracted from it, making it available to test plans, to Memory, and to a later trigger call.

## Request headers

| Header         | Required          | Value                 |
| -------------- | ----------------- | --------------------- |
| `x-api-key`    | Yes               | Your access token     |
| `Content-Type` | Set by the client | `multipart/form-data` |

With `curl -F`, `Content-Type` is set automatically. Setting it by hand drops the multipart boundary and the upload fails.

## Request parameters

| Name   | Type   | Required | Max    | Description                                |
| ------ | ------ | -------- | ------ | ------------------------------------------ |
| `file` | binary | Yes      | 500 MB | The app binary. Example: `app-release.apk` |

One field, one file per request. There is no batch upload.

## Response

```json
{
  "message": "APK uploaded successfully",
  "details": {
    "package_name": "com.shopease.android",
    "version_name": "1.0.0",
    "version_code": 100
  }
}
```

| Field                  | Type   | Description                                    |
| ---------------------- | ------ | ---------------------------------------------- |
| `message`              | string | Upload confirmation                            |
| `details.package_name` | string | Package name or bundle ID read from the binary |
| `details.version_name` | string | Human-readable version                         |
| `details.version_code` | number | Numeric version code                           |

Drizz reads the package name and version from the binary — they are not sent in the request. Use the response values to build the `apks` map for [Trigger a run](/automate-and-integrate/trigger-a-run.md):

```json
{ "com.shopease.android": "1.0.0" }
```

## Limits

| Limit               | Value                                       |
| ------------------- | ------------------------------------------- |
| Maximum binary size | 500 MB                                      |
| Files per request   | 1                                           |
| Duplicate version   | HTTP 409                                    |
| Rate limit          | \~4 requests/second, burst \~20, per client |

To bring a binary under 500 MB, strip debug symbols, drop unused resources, or split by ABI and upload the split under test.

## Platform notes

{% tabs %}
{% tab title="Android" %}

* Upload a `.apk`.
* The package name and version code are read from the binary.
  {% endtab %}

{% tab title="iOS" %}

* Upload an `.ipa` built and signed for a **real device**.
* A simulator build uploads successfully and then fails when the test plan installs it.
  {% endtab %}
  {% endtabs %}

## Errors

| Code    | Meaning           | What to do                                                            |
| ------- | ----------------- | --------------------------------------------------------------------- |
| **200** | Success           | Read `details` for the registered version                             |
| **400** | Bad Request       | Validate the multipart body and the `file` field                      |
| **409** | Conflict          | This app version already exists — bump the version or skip the upload |
| **429** | Too Many Requests | Over the rate limit. Back off and retry                               |
| **500** | Server Error      | Contact `support@drizz.dev`                                           |
| **502** | Bad Gateway       | Retry, or check service availability                                  |

A binary over 500 MB is rejected. See [Errors & limits](/automate-and-integrate/errors-and-limits.md).

## When to upload

Upload when the app version changes. Trigger against the registered version on every other run. Re-uploading a registered version returns **409 Conflict** and transfers up to 500 MB with no effect.

## Common mistakes

| What you did                                                   | What happens                                      |
| -------------------------------------------------------------- | ------------------------------------------------- |
| Set `Content-Type: multipart/form-data` by hand with `curl -F` | The boundary is lost and the upload fails         |
| Uploaded a simulator build for an iOS plan                     | Uploads fine, then fails at install on the device |
| Uploaded the same version twice                                | HTTP 409 — bump the version                       |
| Uploaded a 700 MB debug build                                  | Rejected at the 500 MB cap                        |
| Uploaded on every commit                                       | Slow pipelines, and 409s on unchanged versions    |
| Deleted your local copy afterward                              | A build cannot be downloaded back out of Drizz    |

## Next

* [Trigger a run](/automate-and-integrate/trigger-a-run.md) — run a plan against this build
* [Managing apps](/your-account/managing-apps.md) — registered apps in the UI
* [Errors & limits](/automate-and-integrate/errors-and-limits.md) — what 409 and the rest mean

***

*Last updated: 6 August 2026*
