> ## Documentation Index
> Fetch the complete documentation index at: https://docs.autosana.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# App Build Upload API

> Upload mobile builds or web app URLs programmatically via the API

These endpoints allow you to upload mobile app builds or web app URLs programmatically. For most use cases, we recommend using our [GitHub Action](/ci-cd-integration) instead.

All requests require an API key in the `X-API-Key` header. See [API Reference](/api-reference) for authentication details.

***

<Tabs>
  <Tab title="Mobile (iOS/Android)">
    ## Endpoints Overview

    | Endpoint                          | Platform     | Description                                |
    | --------------------------------- | ------------ | ------------------------------------------ |
    | [Start Upload](#start-upload)     | iOS, Android | Get a presigned URL to upload a build file |
    | [Confirm Upload](#confirm-upload) | iOS, Android | Finalize a mobile build upload             |

    ## Start Upload

    Initiate an app build upload and get a presigned URL for uploading your build file.

    <Note>
      **POST** `/api/ci/start-upload` — Returns `200 OK`
    </Note>

    ### Request Body

    <ParamField body="bundle_id" type="string" required>
      Your app's bundle identifier (e.g., `com.company.app`)
    </ParamField>

    <ParamField body="platform" type="string" required>
      Target platform: `ios` or `android`
    </ParamField>

    <ParamField body="filename" type="string" required>
      Name of the build file (e.g., `app-release.apk` or `MyApp.zip`)
    </ParamField>

    <ParamField body="environment" type="string">
      Environment name to associate this app with (e.g., `staging`, `production`). Apps with the same bundle ID but different environments are treated as separate apps. Must match an existing environment in your organization. If omitted, the app is assigned to your default environment.
    </ParamField>

    ### Example Request

    ```bash theme={null}
    curl -X POST https://backend.autosana.ai/api/ci/start-upload \
      -H "X-API-Key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "bundle_id": "com.company.app",
        "platform": "android",
        "filename": "app-release.apk"
      }'
    ```

    ### Response Fields

    <ResponseField name="upload_url" type="string">
      Presigned URL for uploading your build file via PUT request. Valid for 1 hour.
    </ResponseField>

    <ResponseField name="file_path" type="string">
      Storage path of the uploaded file. Pass this to the confirm-upload endpoint.
    </ResponseField>

    ### Example Response

    ```json theme={null}
    {
      "upload_url": "https://storage.supabase.co/...",
      "file_path": "app-uuid/android/app-release-20250115T103000.apk"
    }
    ```

    After receiving the response, upload your build file to the `upload_url` using a PUT request:

    ```bash theme={null}
    curl -X PUT "UPLOAD_URL_FROM_RESPONSE" \
      -H "Content-Type: application/octet-stream" \
      --data-binary @./path/to/your/app.apk
    ```

    ***

    ## Confirm Upload

    After uploading your build file, call this endpoint to finalize the upload and trigger any configured automations.

    <Note>
      **POST** `/api/ci/confirm-upload` — Returns `200 OK`
    </Note>

    ### Request Body

    <ParamField body="bundle_id" type="string" required>
      Your app's bundle identifier (must match the one used in start-upload)
    </ParamField>

    <ParamField body="platform" type="string" required>
      Target platform: `ios` or `android`
    </ParamField>

    <ParamField body="uploaded_file_path" type="string" required>
      The `file_path` returned from the start-upload response
    </ParamField>

    <ParamField body="name" type="string">
      Display name for your app (e.g., "My Android App"). If the app already exists and the name differs, it will be updated.
    </ParamField>

    <ParamField body="environment" type="string">
      Environment name (must match the value used in start-upload). Used to look up the correct app when multiple apps share the same bundle ID.
    </ParamField>

    <ParamField body="commit_sha" type="string">
      Git commit SHA for tracking which commit this build came from.
    </ParamField>

    <ParamField body="branch_name" type="string">
      Git branch name. Displayed in the Autosana UI for build identification.
    </ParamField>

    <ParamField body="repo_full_name" type="string">
      Repository name in `org/repo` format (e.g., `myorg/myrepo`). Required for [GitHub Bot](/github-integration) integration — links this build to your repository so the bot can find it when processing PRs.
    </ParamField>

    <ParamField body="variables" type="string | object">
      Key-value variables to attach to this build. Available in flow instructions via `${env:KEY}`. Accepts a string (`"KEY1=VALUE1,KEY2=VALUE2"`) or a JSON object (`{"KEY1": "VALUE1"}`). See [Build Variables](/variables#build-variables).
    </ParamField>

    ### Example Request

    ```bash theme={null}
    curl -X POST https://backend.autosana.ai/api/ci/confirm-upload \
      -H "X-API-Key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "bundle_id": "com.company.app",
        "platform": "android",
        "uploaded_file_path": "app-uuid/android/app-release-20250115T103000.apk",
        "name": "My Android App",
        "environment": "staging",
        "commit_sha": "abc123def456",
        "branch_name": "feature/new-login",
        "repo_full_name": "myorg/myrepo",
        "variables": "PR_NUMBER=42,BRANCH=feature/new-login"
      }'
    ```

    ### Response Fields

    <ResponseField name="status" type="string">
      Result status: `success` or `error`
    </ResponseField>

    <ResponseField name="message" type="string">
      Human-readable description of the result
    </ResponseField>

    <ResponseField name="triggered_flows" type="integer">
      Number of automations triggered by this upload (based on your [Automations](/automations) configuration).
    </ResponseField>

    ### Example Response

    ```json theme={null}
    {
      "status": "success",
      "message": "App build uploaded successfully",
      "triggered_flows": 3
    }
    ```

    ***

    ## Upload Workflow

    Here's the complete workflow for uploading a build via the API:

    <Steps>
      <Step title="Start Upload">
        Call `/api/ci/start-upload` with your app details to get a presigned upload URL.
      </Step>

      <Step title="Upload File">
        PUT your build file (`.apk` for Android, `.zip` for iOS) to the presigned URL.
      </Step>

      <Step title="Confirm Upload">
        Call `/api/ci/confirm-upload` to finalize the upload and trigger automations.
      </Step>
    </Steps>

    ### Build Requirements

    <CardGroup cols={2}>
      <Card title="iOS Builds" icon="apple">
        Upload a `.zip` file containing a simulator-compatible `.app` bundle. See [App Build Guide](/app-build-guide) for details.
      </Card>

      <Card title="Android Builds" icon="android">
        Upload a universal `.apk` file (not AAB). The APK must be compatible with `x86_64` emulators.
      </Card>
    </CardGroup>

    ***

    ## Example: Upload from EAS Build Hooks

    If you use Expo EAS, you can upload to Autosana directly from an [EAS build hook](https://docs.expo.dev/build-reference/npm-hooks/) instead of using the GitHub Action. This avoids keeping a GitHub Actions runner idle while waiting for the EAS build to finish, saving CI minutes.

    <Accordion title="EAS build hook script and setup">
      Create an `eas-build-on-success.sh` file in your project root (next to `package.json`). EAS automatically runs this script when a build completes successfully.

      <Warning>
        Replace `com.example.myapp` with your bundle ID, `MyApp.app` with your `.app` bundle name, and `"My App"` with your app's display name.
      </Warning>

      ```bash eas-build-on-success.sh theme={null}
      #!/usr/bin/env bash
      set -euo pipefail

      upload_to_autosana() {
        if [[ -z "${AUTOSANA_API_KEY:-}" ]]; then
          echo "AUTOSANA_API_KEY is not set, skipping upload."
          return 0
        fi

        local platform="$EAS_BUILD_PLATFORM"
        local commit_sha="${EAS_BUILD_GIT_COMMIT_HASH:-unknown}"
        local branch_name="${EAS_BUILD_GIT_COMMIT_BRANCH:-}"
        local bundle_id="com.example.myapp"
        local app_name="My App"
        local repo_full_name="myorg/myrepo" # TODO: Replace with your org/repo

        if [[ "$platform" == "ios" ]]; then
          local app_path
          app_path=$(find . -name "MyApp.app" -type d 2>/dev/null | head -1)
          if [[ -z "$app_path" ]]; then
            echo "Error: Could not find .app bundle"
            return 1
          fi

          local filename="app-simulator.zip"
          local artifact="/tmp/${filename}"
          pushd "$(dirname "$app_path")"
          zip -r "$artifact" "$(basename "$app_path")"
          popd
        elif [[ "$platform" == "android" ]]; then
          local artifact
          artifact=$(find . -name "*.apk" -type f 2>/dev/null | head -1)
          if [[ -z "$artifact" ]]; then
            echo "Error: Could not find .apk file"
            return 1
          fi
          local filename=$(basename "$artifact")
        else
          echo "Unsupported platform: $platform"
          return 1
        fi

        local start_response
        start_response=$(curl -sf -X POST https://backend.autosana.ai/api/ci/start-upload \
          -H "X-API-Key: $AUTOSANA_API_KEY" \
          -H "Content-Type: application/json" \
          -d "{
            \"bundle_id\": \"$bundle_id\",
            \"platform\": \"$platform\",
            \"filename\": \"$filename\"
          }")

        local upload_url file_path
        upload_url=$(echo "$start_response" | jq -r '.upload_url')
        file_path=$(echo "$start_response" | jq -r '.file_path')

        if [[ -z "$upload_url" || "$upload_url" == "null" ]]; then
          echo "Error: Failed to get presigned URL"
          echo "Response: $start_response"
          return 1
        fi

        if ! curl -sf -X PUT "$upload_url" \
          -H "Content-Type: application/octet-stream" \
          --data-binary "@$artifact"; then
          echo "Error: Failed to upload file to presigned URL"
          return 1
        fi

        if ! curl -sf -X POST https://backend.autosana.ai/api/ci/confirm-upload \
          -H "X-API-Key: $AUTOSANA_API_KEY" \
          -H "Content-Type: application/json" \
          -d "{
            \"bundle_id\": \"$bundle_id\",
            \"platform\": \"$platform\",
            \"uploaded_file_path\": \"$file_path\",
            \"name\": \"$app_name\",
            \"commit_sha\": \"$commit_sha\",
            \"branch_name\": \"$branch_name\",
            \"repo_full_name\": \"$repo_full_name\"
          }"; then
          echo "Error: Failed to confirm upload"
          return 1
        fi

        echo "Uploaded to Autosana successfully"
      }

      upload_to_autosana || echo "Warning: Autosana upload failed, continuing..."
      ```

      Make the script executable and commit it to your repo:

      ```bash theme={null}
      chmod +x eas-build-on-success.sh
      ```

      Then add your API key as an EAS secret:

      ```bash theme={null}
      eas secret:create --name AUTOSANA_API_KEY --value your-api-key-here --scope project
      ```

      When you trigger a build (`eas build --platform ios --profile preview-simulator --non-interactive`), EAS runs the hook automatically after the build succeeds — no `--wait` flag or GitHub runner required.
    </Accordion>
  </Tab>

  <Tab title="Web">
    ## Upload Web Build

    Register a web app URL for testing. This endpoint creates or updates the app and creates a new build record.

    <Note>
      **POST** `/api/ci/upload-web-build` — Returns `200 OK`
    </Note>

    ### Request Body

    <ParamField body="app_id" type="string" required>
      Unique identifier for your web app. Must be lowercase alphanumeric with hyphens only (e.g., `my-web-app`). This identifier is used to track your web app across deployments.
    </ParamField>

    <ParamField body="url" type="string" required>
      The URL to test. Must start with `http://` or `https://`. This is typically your preview deployment URL.
    </ParamField>

    <ParamField body="name" type="string">
      Display name for your web app (e.g., "My Web App"). If the app already exists and the name differs, it will be updated.
    </ParamField>

    <ParamField body="environment" type="string">
      Environment name to associate this app with (e.g., `staging`, `production`). Apps with the same app ID but different environments are treated as separate apps. Must match an existing environment in your organization. If omitted, the app is assigned to your default environment.
    </ParamField>

    <ParamField body="commit_sha" type="string">
      Git commit SHA for tracking which commit this build came from.
    </ParamField>

    <ParamField body="branch_name" type="string">
      Git branch name. Displayed in the Autosana UI for build identification.
    </ParamField>

    <ParamField body="repo_full_name" type="string">
      Repository name in `org/repo` format. Required for GitHub Check integration.
    </ParamField>

    <ParamField body="variables" type="string | object">
      Key-value variables to attach to this build. Available in flow instructions via `${env:KEY}`. Accepts a string (`"KEY1=VALUE1,KEY2=VALUE2"`) or a JSON object (`{"KEY1": "VALUE1"}`). See [Build Variables](/variables#build-variables).
    </ParamField>

    ### Example Request

    ```bash theme={null}
    curl -X POST https://backend.autosana.ai/api/ci/upload-web-build \
      -H "X-API-Key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "app_id": "my-web-app",
        "url": "https://my-app-preview.vercel.app",
        "name": "My Web App",
        "commit_sha": "abc123def456",
        "branch_name": "feature/new-login",
        "repo_full_name": "myorg/myrepo",
        "variables": {"PR_NUMBER": "42", "DEPLOY_URL": "https://pr-42.preview.app"}
      }'
    ```

    ### Response Fields

    <ResponseField name="status" type="string">
      Result status: `success` or `error`
    </ResponseField>

    <ResponseField name="message" type="string">
      Human-readable description of the result
    </ResponseField>

    <ResponseField name="app_id" type="string">
      The internal app ID (UUID) for the registered web app
    </ResponseField>

    <ResponseField name="build_id" type="string">
      The internal build ID (UUID) for this registration
    </ResponseField>

    <ResponseField name="triggered_flows" type="integer">
      Number of automations triggered (if no GitHub metadata provided)
    </ResponseField>

    <ResponseField name="github_check_runs" type="string">
      Status of GitHub check runs: `pending` (if GitHub metadata provided)
    </ResponseField>

    ### Example Response

    ```json theme={null}
    {
      "status": "success",
      "message": "Web URL registered successfully",
      "app_id": "550e8400-e29b-41d4-a716-446655440000",
      "build_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
      "triggered_flows": 2
    }
    ```

    ### app\_id Format Requirements

    The `app_id` parameter must follow these rules:

    * Lowercase letters and numbers only
    * Hyphens allowed between words
    * No spaces, underscores, or special characters
    * Cannot start or end with a hyphen
    * Maximum 64 characters

    **Valid examples:** `my-web-app`, `staging`, `preview-app-123`, `frontend`

    **Invalid examples:** `My-Web-App`, `my_web_app`, `my web app`, `-my-app`
  </Tab>
</Tabs>
