# Apps API Source: https://docs.autosana.ai/api-apps List apps and manage default Chrome extension dependencies Use the Apps API to discover app and build UUIDs, then configure the default Chrome extensions loaded with a web app. All requests require an API key in the `X-API-Key` header. See [API Reference](/api-reference) for authentication. ## List Apps **GET** `/api/v1/apps` — Returns `200 OK` Optional platform filter: `ios`, `android`, `web`, or `chrome-extension`. ```bash theme={null} curl "https://backend.autosana.ai/api/v1/apps?platform=chrome-extension" \ -H "X-API-Key: YOUR_API_KEY" ``` ```json theme={null} { "apps": [ { "id": "extension-app-uuid", "name": "MetaMask", "platform": "chrome-extension", "bundle_id": "nkbihfbeogaeaoehlefnkodbefgpgknn", "ios_keychain_access_group_remapping_enabled": false, "active_build": { "id": "extension-build-uuid" } } ], "count": 1 } ``` `active_build` is `null` when an app has no active build. `bundle_id` may also be `null`. `ios_keychain_access_group_remapping_enabled` is an app-level iOS preference for future `.ipa` uploads; it does not indicate whether the active build was instrumented. ## List App Dependencies List the Chrome extensions attached by default to a web app. **GET** `/api/v1/apps/{app_id}/dependencies` — Returns `200 OK` ```bash theme={null} curl "https://backend.autosana.ai/api/v1/apps/WEB_APP_UUID/dependencies" \ -H "X-API-Key: YOUR_API_KEY" ``` ```json theme={null} { "app_id": "web-app-uuid", "dependencies": [ { "app": { "id": "extension-app-uuid", "name": "MetaMask", "platform": "chrome-extension", "bundle_id": "nkbihfbeogaeaoehlefnkodbefgpgknn", "ios_keychain_access_group_remapping_enabled": false, "active_build": { "id": "extension-build-uuid" } } } ], "count": 1 } ``` ## Attach a Dependency Attach a Chrome extension app to a web app. Future runs inherit the extension's active build unless the run request supplies an explicit `dependencies` override. **POST** `/api/v1/apps/{app_id}/dependencies` — Returns `201 Created` ```bash theme={null} curl -X POST \ "https://backend.autosana.ai/api/v1/apps/WEB_APP_UUID/dependencies" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"dependency_app_id": "EXTENSION_APP_UUID"}' ``` ```json theme={null} { "app_id": "web-app-uuid", "dependency": { "app": { "id": "extension-app-uuid", "name": "MetaMask", "platform": "chrome-extension", "bundle_id": "nkbihfbeogaeaoehlefnkodbefgpgknn", "ios_keychain_access_group_remapping_enabled": false, "active_build": { "id": "extension-build-uuid" } } } } ``` The primary app must have platform `web`, and the dependency must have platform `chrome-extension`. Both apps must belong to the API key's organization. ## Detach a Dependency **DELETE** `/api/v1/apps/{app_id}/dependencies/{dependency_app_id}` — Returns `200 OK` ```bash theme={null} curl -X DELETE \ "https://backend.autosana.ai/api/v1/apps/WEB_APP_UUID/dependencies/EXTENSION_APP_UUID" \ -H "X-API-Key: YOUR_API_KEY" ``` ```json theme={null} { "app_id": "web-app-uuid", "dependency_app_id": "extension-app-uuid", "detached": true } ``` Detaching changes future runs only. Historical run results retain the exact extension build UUIDs they used. To override defaults for one run, including running with no extensions, see [Run Flows](/api-runs#run-flows). # App Build Upload API Source: https://docs.autosana.ai/api-ci 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. *** ## 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. **POST** `/api/ci/start-upload` — Returns `200 OK` ### Request Body Your app's bundle identifier (e.g., `com.company.app`) Target platform: `ios` or `android` Name of the build file (e.g., `app-release.apk` or `MyApp.zip`) 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. ### 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 Presigned URL for uploading your build file via PUT request. Valid for 1 hour. Storage path of the uploaded file. Pass this to the confirm-upload endpoint. ### 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. **POST** `/api/ci/confirm-upload` — Returns `200 OK` ### Request Body Your app's bundle identifier (must match the one used in start-upload) Target platform: `ios` or `android` The `file_path` returned from the start-upload response Display name for your app (e.g., "My Android App"). If the app already exists and the name differs, it will be updated. 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. Git commit SHA for tracking which commit this build came from. Git branch name. Displayed in the Autosana UI for build identification. 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. 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). iOS `.ipa` only. Persists the app preference and instruments the IPA so Team-ID-prefixed keychain access groups keep working after cloud device re-signing. When omitted, the app's saved preference is used. ### 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": "ios", "uploaded_file_path": "app-uuid/ios/MyApp-20250115T103000.ipa", "name": "My iOS App", "environment": "staging", "commit_sha": "abc123def456", "branch_name": "feature/new-login", "repo_full_name": "myorg/myrepo", "enable_ios_keychain_access_group_remapping": true, "variables": "PR_NUMBER=42,BRANCH=feature/new-login" }' ``` Android / non-IPA uploads omit `enable_ios_keychain_access_group_remapping`: ```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", "variables": "PR_NUMBER=42,BRANCH=feature/new-login" }' ``` ### Response Fields Result status: `success` or `error` Human-readable description of the result Number of automations triggered by this upload (based on your [Automations](/automations) configuration). ### 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: Call `/api/ci/start-upload` with your app details to get a presigned upload URL. PUT your build file (`.apk` for Android, `.zip` for iOS) to the presigned URL. Call `/api/ci/confirm-upload` to finalize the upload and trigger automations. ### Build Requirements Upload a `.zip` file containing a simulator-compatible `.app` bundle. See [App Build Guide](/app-build-guide) for details. Upload a universal `.apk` file (not AAB). The APK must be compatible with `x86_64` emulators. *** ## 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. 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. Replace `com.example.myapp` with your bundle ID, `MyApp.app` with your `.app` bundle name, and `"My App"` with your app's display name. ```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. ## Upload Web Build Register a web app URL for testing. This endpoint creates or updates the app and creates a new build record. **POST** `/api/ci/upload-web-build` — Returns `200 OK` ### Request Body 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. The URL to test. Must start with `http://` or `https://`. This is typically your preview deployment URL. Display name for your web app (e.g., "My Web App"). If the app already exists and the name differs, it will be updated. 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. Git commit SHA for tracking which commit this build came from. Git branch name. Displayed in the Autosana UI for build identification. Repository name in `org/repo` format. Required for GitHub Check integration. 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). ### 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 Result status: `success` or `error` Human-readable description of the result The internal app ID (UUID) for the registered web app The internal build ID (UUID) for this registration Number of automations triggered (if no GitHub metadata provided) Status of GitHub check runs: `pending` (if GitHub metadata provided) ### 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` ## Upload an Extension Zip Use the normal two-step upload endpoints with `"platform": "chrome-extension"`. The build must be a `.zip` of an unpacked Manifest V3 extension. Extension apps are organization-wide and do not take an `environment`. ```bash theme={null} # 1. Request an upload URL 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": "my-wallet-extension", "platform": "chrome-extension", "filename": "extension.zip", "name": "My Wallet Extension" }' # 2. PUT extension.zip to the returned upload_url # 3. Confirm the upload 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": "my-wallet-extension", "platform": "chrome-extension", "uploaded_file_path": "FILE_PATH_FROM_STEP_1", "name": "My Wallet Extension" }' ``` You can also snapshot a public Chrome Web Store extension without uploading a file: ```bash theme={null} curl -X POST https://backend.autosana.ai/api/ci/upload-extension-from-store \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "store_url": "https://chromewebstore.google.com/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn", "name": "MetaMask" }' ``` Limits: 500MB compressed, 2GB expanded, and 25,000 archive entries. Encrypted archives, symlinks, unsafe paths, and Manifest V2 are rejected. # Environment Variables API Source: https://docs.autosana.ai/api-env-vars List environments and manage environment variables programmatically These endpoints let you browse [environments](/environments) and manage their environment variables programmatically. Use them to bulk-import config from another secrets manager, keep credentials in sync with CI, or build dashboards on top of Autosana. All requests require an API key in the `X-API-Key` header. See [API Reference](/api-reference) for authentication and the standard `401` / `403` / `429` / `500` error shapes shared by every endpoint on this page. **Secret values are write-only through the API.** When `is_secret=true`, the value is stored encrypted in vault and is never returned by any read endpoint — list and get responses always render secret values as `"***"`. There is no API path to retrieve a decrypted secret. During test runs, secrets are never sent to the AI agent. The agent works with the `${env:KEY}` placeholder and the real value is injected only at the moment keystrokes are sent to your app, so run history records the placeholder, not the value. See [Secret Variables](/environments#secret-variables). Environments themselves (create / rename / delete) are managed from the [dashboard](https://autosana.ai/settings?tab=environments). The API surface here covers env vars and read access to the environments that contain them. *** ## List Environments Returns every environment in your organization with its env vars nested. Plaintext values come back in full; secret values are masked as `"***"`. **GET** `/api/v1/environments` — Returns `200 OK` ### Example Request ```bash theme={null} curl -X GET https://backend.autosana.ai/api/v1/environments \ -H "X-API-Key: YOUR_API_KEY" ``` ### Response Fields List of environments. Unique identifier (UUID). Environment name (e.g. "Staging", "Production"). Environment variables, alphabetically by `key`. Unique identifier (UUID). UUID of the parent environment. Variable name (referenced from flows as `${env:KEY}`). Plaintext value, or `"***"` placeholder when `is_secret=true`. **Never** the decrypted secret. Whether the value is stored encrypted in vault. Optional human-readable note. ISO 8601 timestamp. ISO 8601 timestamp. Total number of environments returned. #### Example Response ```json theme={null} { "environments": [ { "id": "770e8400-e29b-41d4-a716-446655440099", "name": "Staging", "env_vars": [ { "id": "880e8400-e29b-41d4-a716-446655440010", "environment_id": "770e8400-e29b-41d4-a716-446655440099", "key": "API_TOKEN", "value": "***", "is_secret": true, "description": null, "created_at": "2026-01-15T10:30:00Z", "updated_at": "2026-01-15T10:30:00Z" }, { "id": "880e8400-e29b-41d4-a716-446655440011", "environment_id": "770e8400-e29b-41d4-a716-446655440099", "key": "API_URL", "value": "https://staging.api.example.com", "is_secret": false, "description": "Staging API base URL", "created_at": "2026-01-15T10:31:00Z", "updated_at": "2026-01-15T10:31:00Z" } ] } ], "count": 1 } ``` *** ## Create Env Var **POST** `/api/v1/env-vars` — Returns `201 Created`. Returns `409 Conflict` if an env var with the same `key` already exists on this environment. ### Request Body UUID of the environment this variable belongs to. Variable name (1–255 characters). Referenced from flows and curl hooks as `${env:KEY}`. Variable value — must be a non-empty string. Empty values are rejected with `422` because they're indistinguishable from "unset" at consumption time. For secrets, this is written to vault and never returned by any read endpoint. Optional human-readable note. Defaults to `false`. When `true`, the value is stored encrypted in vault and is never readable back through the API. ### Example Request (plaintext) ```bash theme={null} curl -X POST https://backend.autosana.ai/api/v1/env-vars \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "environment_id": "770e8400-e29b-41d4-a716-446655440099", "key": "API_URL", "value": "https://staging.api.example.com", "description": "Staging API base URL" }' ``` ### Example Request (secret) ```bash theme={null} curl -X POST https://backend.autosana.ai/api/v1/env-vars \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "environment_id": "770e8400-e29b-41d4-a716-446655440099", "key": "API_TOKEN", "value": "abc123...super-secret", "is_secret": true }' ``` #### Example Response ```json theme={null} { "id": "880e8400-e29b-41d4-a716-446655440010", "environment_id": "770e8400-e29b-41d4-a716-446655440099", "key": "API_TOKEN", "value": "***", "is_secret": true, "description": null, "created_at": "2026-01-15T10:30:00Z", "updated_at": "2026-01-15T10:30:00Z" } ``` *** ## Get Env Var **GET** `/api/v1/env-vars/{env_var_id}` — Returns `200 OK` with the same shape as items in [List Environments](#list-environments)' `env_vars`, or `404` if the var does not exist. Secret values are returned as `"***"`. UUID of the env var. ```bash theme={null} curl -X GET https://backend.autosana.ai/api/v1/env-vars/ENV_VAR_UUID \ -H "X-API-Key: YOUR_API_KEY" ``` *** ## Update Env Var Update an env var. Only provided fields change; omitted fields are unchanged. **PATCH** `/api/v1/env-vars/{env_var_id}` — Returns `200 OK`. `400` if no recognized fields are provided. `404` if the var does not exist. `409` if renaming `key` collides with another env var in the same environment. `422` for invalid transitions (see below). **No-op PATCHes don't bump `updated_at`.** If the body resolves to no actual state change — e.g. `{"is_secret": true}` on a row that's already a secret — the endpoint returns the unchanged row without writing to the DB. Don't rely on PATCH to act as a heartbeat. UUID of the env var. New variable name (1–255 characters). **Note:** any flows or curl hooks that reference the old name as `${env:OLD_KEY}` must be updated in lockstep — there is no automatic rewrite. Collisions with another env var in the same environment return `409`. New value. Omit to keep the existing value unchanged. Required when changing `is_secret` in either direction (the API never re-uses the existing value across a secret/plaintext transition). `null` is treated as "no change", not "clear" — there is no way to clear a value without deleting the row. New description. Pass `""` or `null` to explicitly clear (this is the only field where `null` clears rather than meaning "no change"). Move the var between plaintext and vault-encrypted storage. Switching the flag in either direction requires `value`. ### Secret transition rules | Existing state | Request | Result | | -------------- | -------------------------------------- | --------------------------------------------------------------------- | | Plaintext | `value: "new"` | Plaintext value rewritten in place. | | Plaintext | `is_secret: true`, no `value` | **422** — promoting to secret requires a `value`. | | Plaintext | `is_secret: true`, `value: "secret-x"` | New vault row written; row repointed to it. | | Secret | `value: "rotated"` | New vault row written, old one cleaned up after the row is repointed. | | Secret | `is_secret: false`, no `value` | **422** — demoting to plaintext requires a `value`. | | Secret | `is_secret: false`, `value: "plain"` | Old vault row cleaned up, plaintext value written. | ### Example Request ```bash theme={null} # Rotate an existing secret curl -X PATCH https://backend.autosana.ai/api/v1/env-vars/ENV_VAR_UUID \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "value": "new-rotated-secret-value" }' # Promote a plaintext var to a secret curl -X PATCH https://backend.autosana.ai/api/v1/env-vars/ENV_VAR_UUID \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "is_secret": true, "value": "secret-only-known-at-rotation" }' ``` *** ## Delete Env Var Deletes the env var. If the var is a secret, its vault entry is cleaned up too. **DELETE** `/api/v1/env-vars/{env_var_id}` — Returns `204 No Content`, or `404` if the var does not exist. UUID of the env var. ```bash theme={null} curl -X DELETE https://backend.autosana.ai/api/v1/env-vars/ENV_VAR_UUID \ -H "X-API-Key: YOUR_API_KEY" ``` Deleting a variable will cause any flows or hooks that reference `${env:VARIABLE_NAME}` to fail. Update or remove references before deleting. # Hooks API Source: https://docs.autosana.ai/api-hooks Manage hooks (curl, scripts, app launch args) programmatically These endpoints let you create, read, update, and delete [hooks](/hooks) — the setup, teardown, and runtime scripts that prepare app state and bridge your backend with the Autosana agent. Requests authenticate with an API key in the `X-API-Key` header. The [Test Hook](#test-hook) endpoint also accepts a signed-in user's session token. See [API Reference](/api-reference) for authentication and the standard `401` / `403` / `429` / `500` error shapes shared by every endpoint on this page. This API covers hook CRUD and test execution. To attach a hook as a setup/teardown step on a flow or suite, use the [dashboard](https://autosana.ai/hooks) or the Autosana MCP. Runtime hooks need no attachment — reference them from flow instructions as `${hooks:Hook Name}`. *** ## List Hooks **GET** `/api/v1/hooks` — Returns `200 OK` ### Query Parameters Optional. Filter to setup/teardown hooks attached to this flow. Runtime hooks (embedded in flow instructions as `${hooks:NAME}`) are NOT returned. Mutually exclusive with `suite_id`. Optional. Filter to setup/teardown hooks attached to this suite. Mutually exclusive with `flow_id`. ### Example Request ```bash theme={null} curl -X GET https://backend.autosana.ai/api/v1/hooks \ -H "X-API-Key: YOUR_API_KEY" # Filter to a flow's setup/teardown hooks curl -X GET "https://backend.autosana.ai/api/v1/hooks?flow_id=FLOW_UUID" \ -H "X-API-Key: YOUR_API_KEY" # Filter to a suite's setup/teardown hooks curl -X GET "https://backend.autosana.ai/api/v1/hooks?suite_id=SUITE_UUID" \ -H "X-API-Key: YOUR_API_KEY" ``` ### Response Fields List of hook objects. Unique identifier (UUID). Hook name. Referenced from flow instructions as `${hooks:NAME}` for runtime hooks. Full hook script (curl command, Python source, etc.). One of `curl`, `python`, `javascript`, `typescript`, `bash`, `launch_args`. Optional human-readable note. Filename-derived identity (e.g. `seed-db`) used by code-managed tooling to name the exported hook file and to match on adopt-by-slug. ISO 8601 timestamp. ISO 8601 timestamp of the last modification. Useful for sync tooling that polls for drift. Present only when filtering by `flow_id` or `suite_id`: which attachment(s) this hook has on that flow/suite — `setup`, `teardown`, or both (parallel to `run_orders`). Present only when filtering by `flow_id` or `suite_id`: the execution order for each entry in `hook_types`, parallel by index. Total number of hooks returned. #### Example Response ```json theme={null} { "hooks": [ { "id": "660e8400-e29b-41d4-a716-446655440001", "name": "Create Test User", "script": "curl -X POST https://api.example.com/users -d '{...}'", "script_type": "curl", "description": "Provisions a fresh test user before each flow", "slug": "create-test-user", "created_at": "2026-01-15T10:31:00Z", "updated_at": "2026-01-15T10:31:00Z", "hook_types": ["setup"], "run_orders": [1] } ], "count": 1 } ``` *** ## Create Hook Create a new hook. Reference it in flow instructions as `${hooks:NAME}` for runtime use, or attach it as a setup/teardown hook from the dashboard. **POST** `/api/v1/hooks` — Returns `201 Created` ### Request Body Name of the hook (1–255 characters). Used as the `${hooks:NAME}` reference in flow instructions. The script content (curl command, Python source, etc.). One of `curl`, `python`, `javascript`, `typescript`, `bash`, `launch_args`. See [Hook Types](/hooks#hook-types) for what each one runs. Optional description of what the hook does. ### Example Request ```bash theme={null} curl -X POST https://backend.autosana.ai/api/v1/hooks \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Create Test User", "script": "curl -X POST https://api.example.com/users -H \"Content-Type: application/json\" -d \"{\\\"email\\\":\\\"test@example.com\\\"}\"", "script_type": "curl", "description": "Provisions a fresh test user" }' ``` #### Example Response ```json theme={null} { "id": "660e8400-e29b-41d4-a716-446655440001", "name": "Create Test User", "script": "curl -X POST https://api.example.com/users ...", "script_type": "curl", "description": "Provisions a fresh test user", "created_at": "2026-01-15T10:31:00Z" } ``` *** ## Get Hook **GET** `/api/v1/hooks/{hook_id}` — Returns `200 OK` with the same shape as items in [List Hooks](#list-hooks), or `404` if the hook does not exist. UUID of the hook. ```bash theme={null} curl -X GET https://backend.autosana.ai/api/v1/hooks/HOOK_UUID \ -H "X-API-Key: YOUR_API_KEY" ``` *** ## Update Hook Update a hook's `script`, `script_type`, and/or `description`. Omitted fields are left unchanged. **Hook names cannot be updated.** Runtime hooks reference hooks by name in flow instructions (`${hooks:NAME}`), so renaming would silently break those references. To rename safely: create a new hook, update every flow that references the old name to use the new one, then delete the old hook. There is no atomic rename. **PATCH** `/api/v1/hooks/{hook_id}` — Returns `200 OK`. `400` if no recognized fields are provided. `404` if the hook does not exist. UUID of the hook. New script content. New script type (`curl`, `python`, `javascript`, `typescript`, `bash`, `launch_args`). New description. Pass `""` or `null` to clear. ```bash theme={null} curl -X PATCH https://backend.autosana.ai/api/v1/hooks/HOOK_UUID \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "script": "curl -X POST https://api.example.com/v2/users" }' ``` *** ## Delete Hook Deletes the hook. Past run history (the rows that referenced this hook when they ran) is preserved. If the hook is currently attached to one or more flows or suites, the call returns `409 Conflict` with the names of the affected resources. Pass `?force=true` to detach and delete anyway — the setup/teardown attachments are deactivated as part of the delete. **DELETE** `/api/v1/hooks/{hook_id}` — Returns `204 No Content`. `409` if the hook is in use and `force=false`. `404` if the hook does not exist. UUID of the hook. Defaults to `false`. When `true`, deletes the hook even if it's attached to flows or suites — those attachments are deactivated. `force=true` detaches the hook from **every** flow and suite at once. There's no per-attachment opt-out, no dry-run, and no undo. Inspect the `409` conflict response (without `force`) first to see exactly which flows/suites will be affected. ```bash theme={null} # Refuses to delete if attached anywhere curl -X DELETE https://backend.autosana.ai/api/v1/hooks/HOOK_UUID \ -H "X-API-Key: YOUR_API_KEY" # Force delete + detach curl -X DELETE "https://backend.autosana.ai/api/v1/hooks/HOOK_UUID?force=true" \ -H "X-API-Key: YOUR_API_KEY" ``` #### Example 409 Response ```json theme={null} { "detail": "Hook 'Create Test User' is used in flow(s): Login Flow. Deleting will detach it from these flows/suites. Pass ?force=true to delete anyway." } ``` *** ## Test Hook Execute the hook in isolation against a chosen environment and return the result. Uses a shorter 60-second timeout for fast feedback. Authenticate with `X-API-Key` or `Authorization: Bearer `. Both the hook and environment must belong to the authenticated organization. If both credentials are supplied, the API key takes precedence. The dashboard sends your session token automatically, so testing a hook there does not require creating or pasting an API key. **POST** `/api/v1/hooks/{hook_id}/test` — Returns `200 OK`. `404` if the hook does not exist. ### Request Body UUID of the environment whose env vars to inject. Optional. Test a modified script without saving the changes — useful for CI to dry-run a proposed update before persisting it via `PATCH /api/v1/hooks/{id}`. ### Example Request ```bash theme={null} # Test the saved script curl -X POST https://backend.autosana.ai/api/v1/hooks/HOOK_UUID/test \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "environment_id": "ENV_UUID" }' # Test a proposed script change without saving curl -X POST https://backend.autosana.ai/api/v1/hooks/HOOK_UUID/test \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "environment_id": "ENV_UUID", "script_override": "curl -X POST https://api.example.com/v2/users -d \"{...}\"" }' ``` ### Response Fields Whether the hook executed successfully. Exit code (for scripts) or HTTP status (for `curl`). Captured stdout/stderr from the sandbox. Wall-clock duration in milliseconds. Category of failure when `success=false` (e.g. `Execution timed out`, `Missing environment variable`). # Labels API Source: https://docs.autosana.ai/api-labels Create, manage, and run tests by label programmatically Labels organize your flows and suites and let you run groups of tests by name (e.g. `smoke`, `checkout`, `regression`). These endpoints manage labels, attach them to flows and suites, and trigger runs by label — useful for CI pipelines that run a subset of tests per branch or PR. All requests require an API key in the `X-API-Key` header. See [API Reference](/api-reference) for authentication and the standard `401` / `403` / `429` / `500` error shapes shared by every endpoint on this page. Labels are also managed in the [dashboard](https://autosana.ai/settings?tab=labels) (Settings → Labels, and inline on any flow or suite). The dashboard uses your signed-in session; this API is for programmatic and CI use with an organization API key. *** ## List Labels Returns every label in your organization, ordered by name. **GET** `/api/v1/labels` — Returns `200 OK` ### Example Request ```bash theme={null} curl -X GET "https://backend.autosana.ai/api/v1/labels" \ -H "X-API-Key: YOUR_API_KEY" ``` ### Response Fields List of label objects. Unique identifier (UUID). Label name (unique per organization, case-insensitive). Hex color used to render the label (e.g. `"#4cb782"`). ISO 8601 timestamp. Number of active flows carrying this label. Number of active suites carrying this label. Total number of labels returned. #### Example Response ```json theme={null} { "labels": [ { "id": "aa0e8400-e29b-41d4-a716-446655440001", "name": "smoke", "color": "#4cb782", "created_at": "2026-01-15T10:30:00Z", "flow_count": 12, "suite_count": 2 }, { "id": "aa0e8400-e29b-41d4-a716-446655440002", "name": "checkout", "color": "#5e6ad2", "created_at": "2026-01-16T09:00:00Z", "flow_count": 4, "suite_count": 1 } ], "count": 2 } ``` *** ## Create Label **POST** `/api/v1/labels` — Returns `201 Created`. Returns `409 Conflict` if a label with the same `name` already exists (names are unique per organization, case-insensitive). ### Request Body Label name (1–255 characters). Must be unique within your organization. Optional hex color (e.g. `"#eb5757"`). Defaults to a stable color derived from the name. ### Example Request ```bash theme={null} curl -X POST https://backend.autosana.ai/api/v1/labels \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "smoke", "color": "#4cb782" }' ``` #### Example Response ```json theme={null} { "id": "aa0e8400-e29b-41d4-a716-446655440001", "name": "smoke", "color": "#4cb782", "created_at": "2026-01-15T10:30:00Z" } ``` *** ## Get Label **GET** `/api/v1/labels/{label_id}` — Returns `200 OK` with the same shape as items in [List Labels](#list-labels), or `404` if the label does not exist. UUID of the label. ```bash theme={null} curl -X GET https://backend.autosana.ai/api/v1/labels/LABEL_UUID \ -H "X-API-Key: YOUR_API_KEY" ``` *** ## Update Label Rename or recolor a label. Only provided fields change; omitted fields are unchanged. **PATCH** `/api/v1/labels/{label_id}` — Returns `200 OK`. `400` if no recognized fields are provided. `404` if the label does not exist. `409` if renaming collides with another label. UUID of the label. New name (1–255 characters). New hex color. Renaming a label affects [runs by label](#run-by-label) and the CI `labels` input, which match on name — update any workflows that reference the old name. ```bash theme={null} curl -X PATCH https://backend.autosana.ai/api/v1/labels/LABEL_UUID \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "smoke-tests", "color": "#26b5ce" }' ``` *** ## Delete Label Deletes the label and removes it from every flow and suite it's attached to. **DELETE** `/api/v1/labels/{label_id}` — Returns `204 No Content`, or `404` if the label does not exist. UUID of the label. ```bash theme={null} curl -X DELETE https://backend.autosana.ai/api/v1/labels/LABEL_UUID \ -H "X-API-Key: YOUR_API_KEY" ``` *** ## Attach & Detach Labels Attach or detach a single label to a flow or suite. There is no bulk endpoint — issue one call per (entity, label) pair. ### Attach to a flow **POST** `/api/v1/flows/{flow_id}/labels/{label_id}` — Returns `201 Created`. `404` if the flow or label does not exist. `409` if the label is already attached. ```bash theme={null} curl -X POST https://backend.autosana.ai/api/v1/flows/FLOW_UUID/labels/LABEL_UUID \ -H "X-API-Key: YOUR_API_KEY" ``` ### Detach from a flow **DELETE** `/api/v1/flows/{flow_id}/labels/{label_id}` — Returns `204 No Content`. `404` if the label is not attached to the flow. ```bash theme={null} curl -X DELETE https://backend.autosana.ai/api/v1/flows/FLOW_UUID/labels/LABEL_UUID \ -H "X-API-Key: YOUR_API_KEY" ``` ### Attach to / detach from a suite Same shape, with `/suites/{suite_id}` instead of `/flows/{flow_id}`: ```bash theme={null} # Attach curl -X POST https://backend.autosana.ai/api/v1/suites/SUITE_UUID/labels/LABEL_UUID \ -H "X-API-Key: YOUR_API_KEY" # Detach curl -X DELETE https://backend.autosana.ai/api/v1/suites/SUITE_UUID/labels/LABEL_UUID \ -H "X-API-Key: YOUR_API_KEY" ``` *** ## Run by Label Trigger a run of everything carrying one or more labels by passing `labels` (label **names**) to the [Runs API](/api-runs#run-flows). Autosana runs the union of the suites and flows that carry any of those labels; a flow already covered by a matched suite runs once. As with any run, provide `app_id` (web) or `bundle_id` + `platform` (mobile). A request whose labels match nothing returns `400`. **POST** `/api/v1/flows/run` — Returns `200 OK` with a `batch_id` to poll via [Run Status](/api-runs#run-status). ```bash theme={null} curl -X POST https://backend.autosana.ai/api/v1/flows/run \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "app_id": "your-app-id", "labels": ["smoke", "checkout"] }' ``` See the [Runs API](/api-runs) for the full request body, polling, and result shapes. *** ## CI Usage In CI, the simplest entry point is the [autosana-ci GitHub Action](/ci-cd-integration) — pass a comma-separated `labels` input to run the matching suites and flows after your build is uploaded, instead of listing `suite-ids` / `flow-ids`: ```yaml theme={null} - uses: autosana/autosana-ci@main with: api-key: ${{ secrets.AUTOSANA_KEY }} bundle-id: com.company.app platform: android build-path: app.apk labels: smoke,checkout # run everything carrying any of these labels ``` See [CI/CD Integration](/ci-cd-integration) for the full workflow setup. # API Reference Source: https://docs.autosana.ai/api-reference Programmatically manage test suites, flows, and app builds via the Autosana API The Autosana API allows you to programmatically create test suites, flows, and upload app builds. This is useful for integrating Autosana with your own automation platforms, AI agents, or CI/CD pipelines. For a simpler CI/CD setup using GitHub Actions, see [CI/CD Integration](/ci-cd-integration). The endpoints below are for custom integrations. ## Base URL ``` https://backend.autosana.ai ``` All API endpoints are relative to this base URL. *** ## Authentication All API requests require an API key passed in the `X-API-Key` header. ```bash theme={null} curl -X POST https://backend.autosana.ai/api/v1/suites \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "My Test Suite"}' ``` Your organization's API key. Get it from the welcome quickstart or [Settings → Integrations](https://autosana.ai/settings?tab=integrations). *** ## Available Endpoints List apps and manage default Chrome extension dependencies Create and list test suites and flows Manage labels, attach them to flows/suites, and run by label Trigger flows, poll for results, and fetch run details Manage setup, teardown, and runtime hook scripts List environments and manage their env vars (including secrets) Upload app builds programmatically *** ## Error Responses | Status Code | Description | | ----------- | ------------------------------------------------------ | | **400** | Bad request - missing or invalid parameters | | **401** | Missing API key | | **403** | Invalid API key | | **404** | Resource not found | | **422** | Validation error - check the response body for details | | **500** | Server error | #### Example Error Response ```json theme={null} { "detail": "Suite not found" } ``` *** ## Rate Limits The API is rate-limited to prevent abuse. If you receive a `429 Too Many Requests` response, wait a few seconds before retrying. *** ## Use Cases Generate test cases from requirements using AI, then push them to Autosana via the API Automatically create test suites when tickets move to "Ready for QA" status Keep Autosana tests in sync with your test management system Migrate existing test cases from spreadsheets or other tools *** ## Need Help? Learn how to write clear, reliable test instructions Reach out if you need help with your integration # Runs API Source: https://docs.autosana.ai/api-runs Trigger flow execution, poll for results, retrieve run results, and terminate in-progress runs programmatically All requests require an API key in the `X-API-Key` header. See [API Reference](/api-reference) for authentication details. *** ## List Devices Discover the device and OS permutations that can be requested for cloud runs. **GET** `/api/v1/devices` — Returns `200 OK` Optional query parameters are `platform` (`android` or `ios`) and `physical` (`true` or `false`). Each model appears once with its supported `os_versions`. ```bash theme={null} curl "https://backend.autosana.ai/api/v1/devices?physical=false" \ -H "X-API-Key: YOUR_API_KEY" ``` Example response (abbreviated): ```json theme={null} { "devices": [ { "platform": "android", "physical": false, "model": "Pixel 10 Pro", "os_versions": ["17", "16"] }, { "platform": "ios", "physical": false, "model": "iPhone 17 Pro", "os_versions": ["26.5"], "fast_os_versions": ["26.5"] }, ... ] } ``` Choose a model and one of its `os_versions`. For iOS simulators, `fast_os_versions` identifies choices with an optimized startup path. Every returned combination is supported. *** ## Run Flows Trigger flow execution. Returns a `batch_id` to poll via [Run Status](#run-status) and a `batch_url` to view or share the entire batch in the dashboard. **POST** `/api/v1/flows/run` — Returns `200 OK` ### Request Body App bundle identifier (mobile). Required if `app_id` is not provided. `ios` or `android`. Required with `bundle_id`. Web app identifier. Required if `bundle_id` is not provided. Optional. Pin the run to a specific build (UUID) instead of the app's active build — useful for testing a particular CI/CD build. The build must belong to the resolved app, otherwise the request returns `404`. Suite UUIDs to run. At least one of `suite_ids` or `flow_ids` is required. Optional suite execution override. Explicit `true` or `false` takes precedence over each suite's saved **Run flows in parallel** setting. Omit it or send `null` to use the saved suite setting. Flow UUIDs to run. At least one of `suite_ids`, `flow_ids`, or `labels` is required. When provided, suite auth instructions and suite-scoped variables are resolved automatically unless `resolve_suite_context` is set to `false`. See [Suite context for individual flows](#suite-context-for-individual-flows) below. Label **names** to run (e.g. `["smoke", "checkout"]`). Runs the union of the suites and flows carrying any of these labels; a flow already covered by a matched suite runs once. Returns `400` if nothing matches. See [Run by Label](/api-labels#run-by-label). When running individual flows via `flow_ids`, whether to look up the flow's suite and run auth instructions first. Defaults to `true`. Set to `false` to run the flow without suite auth or suite-scoped variables — useful for unauthenticated smoke tests or flows that handle login themselves. Optional. When a flow belongs to multiple suites, pass a JSON object mapping each ambiguous `flow_id` to the `suite_id` whose auth instructions and variables should be used, e.g. `{"": ""}`. Ignored when `resolve_suite_context` is `false`. Key-value variables to attach to the build being run (the pinned `app_build_id` if provided, otherwise the active build) and inject into flow instructions via `${env:KEY}`. Accepts a string (`"KEY1=VALUE1,KEY2=VALUE2"`) or a JSON object (`{"KEY1": "VALUE1"}`). See [Build Variables](/variables#build-variables). For web apps: which browser to run the flow in. One of: * `chrome` — real Google Chrome (default). Includes proprietary codecs (H.264, AAC) and Widevine DRM. Use this unless you have a specific reason to pick another browser. * `chromium` — open-source Chromium. No proprietary codecs, no DRM, deterministic and vendor-neutral. * `firefox` — Mozilla Firefox stable. * `edge` — Microsoft Edge stable. Aliases accepted: `Chrome` / `Google Chrome` → `chrome`; `msedge` / `Microsoft Edge` → `edge`. Ignored for mobile runs. Optional for web apps. Override the app's default Chrome extensions for this run. Each entry can be an extension app UUID or an object that pins an exact build: ```json theme={null} [ "extension-app-uuid", { "app_id": "another-extension-app-uuid", "app_build_id": "extension-build-uuid" } ] ``` Omit this field to inherit the web app's attached defaults. Pass `[]` to run without extensions. When extensions load, `chrome`, `edge`, and `chromium` run in Chromium; `firefox` is rejected because Chrome extensions are not supported there. Optional for mobile apps. Selects the execution target and, when specified, the exact supported device permutation. Fields are `physical` (defaults to `false`), `model` (for example, `"Pixel 10 Pro"`), and `os_version` (for example, `"17"`). Set `model` or `os_version` to `"latest"` to explicitly request rolling resolution for that field. This lets you pin one while keeping the other current. Use [List Devices](#list-devices) to discover valid combinations. Omit it to use the recommended default. A request with no supported match fails fast with a clear error. For an iOS simulator, use `physical: false` with a listed iPhone or iPad model and iOS version. For a real iOS device, use `physical: true` and an `.ipa` build. Required instead of `device` for a two-device flow. Pass exactly two entries in Device 1, Device 2 order. Both entries must select virtual devices (`physical: false`) or both must select real devices (`physical: true`). Model and OS can be exact, `"latest"`, or omitted for rolling resolution. Both devices use the run's app build and platform. ```json theme={null} [ { "physical": true, "model": "iPhone 17 Pro", "os_version": "26" }, { "physical": true, "model": "iPhone 16 Pro", "os_version": "18" } ] ``` See [Multi-Device Testing](/multi-device-testing) for a complete setup and execution guide. For example, keep the model and OS current on every run: ```json theme={null} { "device": { "physical": false, "model": "latest", "os_version": "latest" } } ``` ### Suite context for individual flows When you trigger a single flow via `flow_ids`, Autosana resolves the suite it belongs to and runs that suite's **Auth Instructions** first — the same behavior as running the flow manually from the dashboard. ```bash theme={null} # Runs auth instructions first, then the flow (default) curl -X POST https://backend.autosana.ai/api/v1/flows/run \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "app_id": "your-app-id", "flow_ids": ["660e8400-e29b-41d4-a716-446655440001"] }' # Skip suite auth — run only the flow instructions curl -X POST https://backend.autosana.ai/api/v1/flows/run \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "app_id": "your-app-id", "flow_ids": ["660e8400-e29b-41d4-a716-446655440001"], "resolve_suite_context": false }' ``` If a flow belongs to multiple suites, the request returns `400` unless you pass `suite_overrides` mapping that flow to one of its suite UUIDs, e.g. `{"660e8400-e29b-41d4-a716-446655440001": "550e8400-e29b-41d4-a716-446655440000"}`. ### Example Request ```bash theme={null} curl -X POST https://backend.autosana.ai/api/v1/flows/run \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "bundle_id": "com.company.app", "platform": "android", "suite_ids": ["550e8400-e29b-41d4-a716-446655440000"], "variables": "PR_NUMBER=42,BRANCH=feature/login" }' ``` ### Example: Run with Chrome extensions ```bash theme={null} curl -X POST https://backend.autosana.ai/api/v1/flows/run \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "app_id": "your-web-app-uuid", "flow_ids": ["660e8400-e29b-41d4-a716-446655440001"], "dependencies": [ "extension-app-uuid", { "app_id": "wallet-extension-app-uuid", "app_build_id": "wallet-extension-build-uuid" } ] }' ``` All dependency apps and pinned builds must belong to the same organization as the primary web app. Duplicate app IDs are loaded once; conflicting build pins return `400`. ### Example Response ```json theme={null} { "status": "success", "batch_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "batch_url": "https://autosana.ai/runs/batches/a1b2c3d4-e5f6-7890-abcd-ef1234567890", "flow_group_run_ids": ["f1a2b3c4-d5e6-7890-abcd-ef1234567890"], "flow_run_count": 5 } ``` *** ## Run Status Poll execution status for a batch triggered by [Run Flows](#run-flows). **GET** `/api/v1/runs/status` — Returns `200 OK` ### Query Parameters The `batch_id` from `/api/v1/flows/run`. ### Example Request ```bash theme={null} curl -X GET "https://backend.autosana.ai/api/v1/runs/status?batch_id=BATCH_ID" \ -H "X-API-Key: YOUR_API_KEY" ``` ### Example Response ```json theme={null} { "batch_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "batch_url": "https://autosana.ai/runs/batches/a1b2c3d4-e5f6-7890-abcd-ef1234567890", "is_complete": true, "timestamp": "2025-01-31T12:34:56.789Z", "app": { "id": "com.company.app", "name": "My App", "platform": "android", "url": "https://storage.example.com/builds/app.apk" }, "git": { "commit_sha": "abc123", "branch": "main" }, "summary": { "total_groups": 2, "passed_groups": 1, "failed_groups": 1, "total_flows": 10, "passed_flows": 8, "failed_flows": 2, "error_flows": 0, "terminated_flows": 0, "skipped_flows": 0 }, "run_groups": [ { "name": "Login Suite", "status": "passed", "url": "https://autosana.ai/runs/groups/group-uuid-1", "source": "ci", "actor": { "id": "770e8400-e29b-41d4-a716-446655440077", "name": "CI Key", "type": "api_key" }, "dependency_app_build_ids": ["extension-build-uuid"], "runs": [ { "id": "run-uuid-1", "name": "Login with valid credentials", "status": "passed", "url": "https://autosana.ai/runs/flow/run-uuid-1", "summary": "Successfully logged in and verified the dashboard loaded." } ] }, { "name": "Checkout Suite", "status": "failed", "url": "https://autosana.ai/runs/groups/group-uuid-2", "source": "schedule", "actor": { "id": "f8d8fb05-6292-4daa-9bf4-b0cc3b40a366", "name": "Autosana", "type": "system" }, "dependency_app_build_ids": [], "runs": [ { "id": "run-uuid-2", "name": "Checkout with expired card", "status": "failed", "url": "https://autosana.ai/runs/flow/run-uuid-2" } ] } ] } ``` Each run group also includes: * `status`: common values include `creating`, `pending`, `queued`, `running`, `reviewing`, `verifying`, `terminating`, `terminated`, `passed`, `failed`, `error`, and `skipped`. Treat this list as non-exhaustive. Poll until a terminal status rather than switching on every value. * `source`: how it was triggered (`ci`, `dashboard`, `schedule`, or `mcp`) * `actor`: who triggered it, as `{ id, name, type }`, where `type` is `user`, `system`, or `api_key` * `dependency_app_build_ids`: exact Chrome extension build UUIDs loaded for the group Runs triggered with an API key (this endpoint, MCP, CI) are attributed to the key itself: `type` is `api_key`, `id` is the key's ID, and `name` is the key's name from your dashboard. `source` and `actor` may be `null` for older runs. *** ## Get Run Results Get full details of a run, including metadata, summary, the raw run recording URL, and all actions with screenshot URLs. Use the `id` from individual runs in the [Run Status](#run-status) response. **GET** `/api/v1/runs/{run_id}` — Returns `200 OK` ### Path Parameters UUID of the run. Available in the `runs[].id` field from [Run Status](#run-status). ### Example Request ```bash theme={null} curl -X GET "https://backend.autosana.ai/api/v1/runs/run-uuid-1" \ -H "X-API-Key: YOUR_API_KEY" ``` ### Example Response ```json theme={null} { "run_id": "run-uuid-1", "flow_name": "Login with valid credentials", "platform": "web", "status": "passed", "started_at": "2025-01-31T12:34:50.000000+00:00", "completed_at": "2025-01-31T12:35:01.000000+00:00", "source": "ci", "actor": { "id": "770e8400-e29b-41d4-a716-446655440077", "name": "CI Key", "type": "api_key" }, "dependency_app_build_ids": ["extension-build-uuid"], "summary": "Successfully logged in and verified the dashboard loaded.", "issues": [ { "type": "ux", "severity": "minor", "title": "No loading indicator on login", "description": "After tapping 'Sign In', there is no visual loading state before the dashboard appears.", "actions": [4,5] } ], "url": "https://autosana.ai/runs/flow/run-uuid-1", "recording_url": "https://storage.example.com/recordings/run-uuid-1.mp4", "performance_data_url": "https://storage.example.com/perf/run-uuid-1.json", "device_log_url": "https://storage.example.com/logs/run-uuid-1/device.log?token=...", "network_log_url": "https://storage.example.com/logs/run-uuid-1/network.jsonl?token=...", "actions": [ { "id": "action-uuid-1", "position": 1, "type": "tap", "status": "passed", "description": "Tap the 'Sign In' button", "value": null, "screenshot_url": "https://storage-path/screenshot1.png", "annotated_screenshot_url": "https://example.com/screenshot1_annotated.png", "executed_at": "2025-01-31T12:34:56.789000+00:00" }, { "id": "action-uuid-2", "position": 2, "type": "send_keys", "status": "passed", "description": "Type email into the email field", "value": "user@example.com", "screenshot_url": "https://storage-path/screenshot2.png", "annotated_screenshot_url": "https://example.com/screenshot2_annotated.png", "executed_at": "2025-01-31T12:34:58.789000+00:00" }, { "id": "action-uuid-3", "position": 3, "type": "pass", "status": "passed", "description": "Login completed successfully — dashboard is visible", "value": null, "screenshot_url": "https://storage-path/screenshot3.png", "annotated_screenshot_url": null, "executed_at": "2025-01-31T12:35:00.789000+00:00" } ] } ``` ### Artifact URLs Exact Chrome extension build UUIDs loaded for this run. Per-device metadata and artifact URLs for a two-device run, ordered by `device_index`. Each entry includes model and OS metadata and that device's recording, performance, device-log, and network-log URLs. Single-device runs return an empty array and continue to use the top-level artifact fields below. Public URL for the raw, unannotated MP4 recording of the run. `null` when the run did not produce a recording. Time-series performance metrics (memory, CPU, web vitals) as JSON. Device log (Android `logcat` / iOS syslog) or browser console log (web), as plain text or JSONL. HTTP requests captured during the run, as JSONL — one entry per request with `method`, `url`, `status`, `resource_type`, `duration_ms`, `response_size`, etc. Failed requests (DNS, abort, CORS) appear with `status: 0` and an `error` string. Produced for **web** and **iOS** runs (iOS from the cloud simulator; apps that pin their TLS certificate aren't captured). `null` when no traffic was captured. See [Network Traffic](/network-traffic). Any of these may be `null` if the artifact wasn't produced for that run. Actions from a two-device run include `device_index` (`1` or `2`). *** ## Terminate Run Terminate an in-progress flow group run. `run_id` may be a flow group run ID from [Run Flows](#run-flows) (`flow_group_run_ids`) or a flow run ID from [Run Status](#run-status) / [Get Run Results](#get-run-results). Passing a flow run ID terminates the **entire parent group**, including every sibling flow in that suite or batch, not just the one run. **POST** `/api/v1/runs/terminate` returns `200 OK` ### Request Body UUID of a flow group run or a flow run. Pending runs are terminated immediately. Queued or running groups are marked `terminating`. Already-terminal runs return `action: "none"`. Unknown or other-organization IDs return `404`. To watch termination finish, reuse the `batch_id` from [Run Flows](#run-flows) and poll [Run Status](#run-status) until that group's `status` is terminal (`terminated`, `skipped`, `passed`, `failed`, or `error`). The terminate response does not include `batch_id`. You can also poll [Get Run Results](#get-run-results) with the original flow run ID until that flow reaches any terminal status. Children that never started may be `skipped` rather than `terminated`. ### Example Request ```bash theme={null} curl -X POST https://backend.autosana.ai/api/v1/runs/terminate \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "run_id": "660e8400-e29b-41d4-a716-446655440001" }' ``` This example passes a **flow run** ID. The response echoes that ID in `run_id` and the resolved parent group in `flow_group_run_id`. ### Example Response ```json theme={null} { "run_id": "660e8400-e29b-41d4-a716-446655440001", "flow_group_run_id": "f1a2b3c4-d5e6-7890-abcd-ef1234567890", "status": "terminating", "message": "Marked for termination (worker will finalize)", "action": "terminating" } ``` The `run_id` you sent (flow group run or flow run). The parent group that was actually terminated. Same as `run_id` when you passed a group ID. Current group status after the request. Typically `terminated` or `terminating`. When `action` is `none`, this is the group's existing terminal status (`passed`, `failed`, `error`, `terminated`, or `skipped`). Human-readable outcome, for example `"Marked for termination (worker will finalize)"` or `"Already in terminal state"`. `terminated` (pending run stopped immediately), `terminating` (worker will finalize), or `none` (already terminal). *** ## Polling Poll `/api/v1/runs/status` every 10-15 seconds until `is_complete` is `true`. ```bash theme={null} while true; do RESPONSE=$(curl -s "https://backend.autosana.ai/api/v1/runs/status?batch_id=$BATCH_ID" \ -H "X-API-Key: YOUR_API_KEY") if [ "$(echo "$RESPONSE" | jq -r '.is_complete')" = "true" ]; then echo "$RESPONSE" | jq '.summary' break fi sleep 15 done ``` # Suites & Flows API Source: https://docs.autosana.ai/api-suites-flows Create and list test suites and flows programmatically These endpoints allow you to manage test suites and flows via the API. All requests require an API key in the `X-API-Key` header. See [API Reference](/api-reference) for authentication details. ## Suites ### List Suites Get all test suites for your organization. **GET** `/api/v1/suites` — Returns `200 OK` #### Example Request ```bash theme={null} curl -X GET https://backend.autosana.ai/api/v1/suites \ -H "X-API-Key: YOUR_API_KEY" ``` #### Response Fields List of suite objects Unique identifier (UUID) Name of the suite Description of what the suite tests Free-form **suite-level context** injected into the AI agent's system prompt for every flow run in the suite. See [Suite Context](/suites#suite-context). UUID of the authentication setup flow, if the suite has auth instructions configured The authentication/login instructions for the suite's setup flow, if configured ISO 8601 timestamp of when the suite was created Who created the suite, as `{ id, name, type }` (`type` is always `user`). `null` for older suites. Total number of suites returned #### Example Response ```json theme={null} { "suites": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Login Feature Tests", "description": "Tests for user authentication", "instructions": "Treat the welcome tour as expected on a fresh login — dismiss it and continue.", "setup_flow_id": "770e8400-e29b-41d4-a716-446655440099", "auth_instructions": "Tap Login\nEnter ${env:USERNAME}\nEnter ${env:PASSWORD}\nTap Submit", "created_at": "2025-01-15T10:30:00Z", "created_by": { "id": "990e8400-e29b-41d4-a716-446655440088", "name": "Ada Lovelace", "type": "user" } }, { "id": "550e8400-e29b-41d4-a716-446655440001", "name": "Checkout Flow Tests", "description": null, "instructions": null, "setup_flow_id": null, "auth_instructions": null, "created_at": "2025-01-14T09:00:00Z", "created_by": null } ], "count": 2 } ``` *** ### Create Suite Create a new test suite to organize your flows. **POST** `/api/v1/suites` — Returns `201 Created` #### Request Body Name of the test suite (1-255 characters) Optional description of what the suite tests Optional free-form **suite-level context** that gets injected into the AI agent's system prompt for every flow run in the suite. Use this for product knowledge, conventions, or constraints that apply to all flows in the suite — e.g. *"All flows assume the user is already onboarded; if you see the welcome tour, dismiss it."* This is not executed as a flow; for actions that should run before each flow, use `auth_instructions` instead. See [Suite Context](/suites#suite-context). Optional login/authentication instructions that run before each flow in the suite. Use `${env:VAR_NAME}` placeholders for sensitive values like credentials — configure these in your [Environment Settings](https://autosana.ai/settings?tab=environments). #### Example Request ```bash theme={null} curl -X POST https://backend.autosana.ai/api/v1/suites \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "JIRA-1234: User Authentication", "description": "Auto-generated tests for login feature", "instructions": "Treat any \"Beta\" badge in the UI as expected — do not fail because of it.", "auth_instructions": "Tap Login\nEnter ${env:USERNAME} into the username field\nEnter ${env:PASSWORD} into the password field\nTap Submit" }' ``` #### Response Fields Unique identifier (UUID) of the created suite Name of the suite Description of what the suite tests Suite-level context echoed back when `instructions` was provided. UUID of the authentication setup flow. Present when `auth_instructions` was provided. The authentication/login instructions for the suite's setup flow. Echoed back when `auth_instructions` was provided. ISO 8601 timestamp of when the suite was created Who created the suite, as `{ id, name, type }` (`type` is always `user`). `null` for older suites. #### Example Response ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "JIRA-1234: User Authentication", "description": "Auto-generated tests for login feature", "instructions": "Treat any \"Beta\" badge in the UI as expected — do not fail because of it.", "setup_flow_id": "770e8400-e29b-41d4-a716-446655440099", "auth_instructions": "Tap Login\nEnter ${env:USERNAME} into the username field\nEnter ${env:PASSWORD} into the password field\nTap Submit", "created_at": "2025-01-15T10:30:00Z", "created_by": { "id": "990e8400-e29b-41d4-a716-446655440088", "name": "Ada Lovelace", "type": "user" } } ``` *** ### Get Suite **GET** `/api/v1/suites/{suite_id}` — Returns `200 OK` (same shape as Create Suite), or `404` if the suite does not exist. UUID of the suite. ```bash theme={null} curl -X GET https://backend.autosana.ai/api/v1/suites/SUITE_UUID \ -H "X-API-Key: YOUR_API_KEY" ``` *** ### Update Suite Update any subset of a suite's fields — omitted fields are unchanged. Returns the same shape as Create Suite. **PATCH** `/api/v1/suites/{suite_id}` — Returns `200 OK`. `400` if no recognized fields are provided. `404` if the suite does not exist. UUID of the suite. New name (1-255 characters). New description. Pass `null` to clear. New suite-level context. Pass `null` to clear. See [Suite Context](/suites#suite-context). New login instructions that run before each flow in the suite. Use `${env:VAR_NAME}` placeholders for credentials — configure them in your [Environment Settings](https://autosana.ai/settings?tab=environments). To remove auth entirely, use the dashboard. ```bash theme={null} curl -X PATCH https://backend.autosana.ai/api/v1/suites/SUITE_UUID \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "instructions": "All flows assume the staging tenant; dismiss the welcome tour if it appears." }' ``` *** ### Delete Suite Deletes the suite. Flows inside the suite are **not** deleted — delete them separately if needed. **DELETE** `/api/v1/suites/{suite_id}` — Returns `204 No Content`, or `404` if the suite does not exist. UUID of the suite. ```bash theme={null} curl -X DELETE https://backend.autosana.ai/api/v1/suites/SUITE_UUID \ -H "X-API-Key: YOUR_API_KEY" ``` *** ## Flows ### List Flows Get all test flows for your organization. Optionally filter by suite. **GET** `/api/v1/flows` — Returns `200 OK` #### Query Parameters Optional. Filter flows by suite ID to get only flows in a specific suite (ordered by position). #### Example Request ```bash theme={null} # Get all flows curl -X GET https://backend.autosana.ai/api/v1/flows \ -H "X-API-Key: YOUR_API_KEY" # Get flows in a specific suite curl -X GET "https://backend.autosana.ai/api/v1/flows?suite_id=550e8400-e29b-41d4-a716-446655440000" \ -H "X-API-Key: YOUR_API_KEY" ``` #### Response Fields List of flow objects Unique identifier (UUID) Name of the flow Natural language test instructions Whether action caching is enabled for this flow. Defaults to `false` for flows created via the API — enable from the dashboard if you want caching. Number of devices required by the flow: `1` or `2`. ISO 8601 timestamp of when the flow was created Who created the flow, as `{ id, name, type }` (`type` is always `user`). `null` for older flows. Total number of flows returned #### Example Response ```json theme={null} { "flows": [ { "id": "660e8400-e29b-41d4-a716-446655440001", "name": "Login with valid credentials", "instructions": "Enter valid email and password, tap login, verify home screen", "caching_enabled": false, "device_count": 1, "created_at": "2025-01-15T10:31:00Z", "created_by": { "id": "990e8400-e29b-41d4-a716-446655440088", "name": "Ada Lovelace", "type": "user" } }, { "id": "660e8400-e29b-41d4-a716-446655440002", "name": "Login with invalid password", "instructions": "Enter valid email but wrong password, verify error message", "caching_enabled": false, "device_count": 1, "created_at": "2025-01-15T10:32:00Z", "created_by": null } ], "count": 2 } ``` *** ### Create Flow Create a new test flow (test case). Optionally attach it to a suite. **POST** `/api/v1/flows` — Returns `201 Created` #### Request Body Name of the test flow (1-255 characters) Natural language instructions for the test. See [Writing Effective Flow Instructions](/writing-effective-flow-instructions) for best practices. Optional UUID of a suite to attach this flow to. The flow will be added to the end of the suite. Number of devices the agent controls. Allowed values are `1` and `2`. A flow attached to a suite must match every member and auth flow in that suite. #### Example Request ```bash theme={null} curl -X POST https://backend.autosana.ai/api/v1/flows \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Login with valid credentials", "instructions": "1. Tap the Sign In button\n2. Enter test@example.com in the email field\n3. Enter password123 in the password field\n4. Tap the Login button\n5. Verify that the home screen appears with Welcome message", "suite_id": "550e8400-e29b-41d4-a716-446655440000", "device_count": 2 }' ``` #### Response Fields Unique identifier (UUID) of the created flow Name of the flow Natural language test instructions Always `false` for flows created via this endpoint. Enable from the dashboard if you want caching. Number of devices required by the flow: `1` or `2`. UUID of the suite this flow belongs to (if attached) Position of this flow within the suite (0-indexed, if attached) ISO 8601 timestamp of when the flow was created Who created the flow, as `{ id, name, type }` (`type` is always `user`). `null` for older flows. #### Example Response ```json theme={null} { "id": "660e8400-e29b-41d4-a716-446655440001", "name": "Login with valid credentials", "instructions": "1. Tap the Sign In button\n2. Enter test@example.com in the email field\n3. Enter password123 in the password field\n4. Tap the Login button\n5. Verify that the home screen appears with Welcome message", "caching_enabled": false, "suite_id": "550e8400-e29b-41d4-a716-446655440000", "position": 0, "created_at": "2025-01-15T10:31:00Z", "created_by": { "id": "990e8400-e29b-41d4-a716-446655440088", "name": "Ada Lovelace", "type": "user" } } ``` *** ### Get Flow **GET** `/api/v1/flows/{flow_id}` — Returns `200 OK` with the same shape as items in [List Flows](#list-flows), or `404` if the flow does not exist. UUID of the flow. ```bash theme={null} curl -X GET https://backend.autosana.ai/api/v1/flows/FLOW_UUID \ -H "X-API-Key: YOUR_API_KEY" ``` *** ### Update Flow Update any subset of a flow's fields — omitted fields are unchanged. Returns the same shape as items in [List Flows](#list-flows). To move a flow from one suite to another, use [Add Flow to Suite](#add-flow-to-suite) on the target suite and [Remove Flow from Suite](#remove-flow-from-suite) on the source suite. Run history is preserved. **PATCH** `/api/v1/flows/{flow_id}` — Returns `200 OK`. `400` if no recognized fields are provided. `404` if the flow does not exist. UUID of the flow. New name (1-255 characters). Natural-language test instructions. New device requirement: `1` or `2`. The update is rejected when it would make a suite's member or auth flow counts inconsistent. ```bash theme={null} curl -X PATCH https://backend.autosana.ai/api/v1/flows/FLOW_UUID \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "instructions": "1. Tap Sign In\n2. Enter the email\n3. Tap Submit" }' ``` *** ### Delete Flow Deletes the flow. Past runs are preserved. **DELETE** `/api/v1/flows/{flow_id}` — Returns `204 No Content`, or `404` if the flow does not exist. UUID of the flow. ```bash theme={null} curl -X DELETE https://backend.autosana.ai/api/v1/flows/FLOW_UUID \ -H "X-API-Key: YOUR_API_KEY" ``` *** ## Suite Membership Move a flow between suites without losing run history. ### Add Flow to Suite Attaches an existing flow to the end of a suite. **POST** `/api/v1/suites/{suite_id}/flows/{flow_id}` — Returns `201 Created`. `404` if the suite or flow does not exist. `409` if the flow is already in the suite. UUID of the suite. UUID of the flow to attach. #### Response Fields UUID of the suite the flow was attached to. UUID of the flow that was attached. 0-indexed position the flow was placed at — always the next slot at the end of the suite. ```bash theme={null} curl -X POST https://backend.autosana.ai/api/v1/suites/SUITE_UUID/flows/FLOW_UUID \ -H "X-API-Key: YOUR_API_KEY" ``` #### Example Response ```json theme={null} { "suite_id": "550e8400-e29b-41d4-a716-446655440000", "flow_id": "660e8400-e29b-41d4-a716-446655440001", "position": 3 } ``` *** ### Remove Flow from Suite Detaches a flow from a suite. The flow itself is **not** deleted. **DELETE** `/api/v1/suites/{suite_id}/flows/{flow_id}` — Returns `204 No Content`. `404` if the suite does not exist, or if the flow is not in this suite. UUID of the suite. UUID of the flow to detach. ```bash theme={null} curl -X DELETE https://backend.autosana.ai/api/v1/suites/SUITE_UUID/flows/FLOW_UUID \ -H "X-API-Key: YOUR_API_KEY" ``` *** ## Example Workflow Here's a typical workflow for creating a test suite with multiple test cases: First, create a suite to organize your test cases. If your app requires login, include `auth_instructions` — these run automatically before each flow: ```bash theme={null} curl -X POST https://backend.autosana.ai/api/v1/suites \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Login Feature Tests", "auth_instructions": "Tap Sign In\nEnter ${env:USERNAME}\nEnter ${env:PASSWORD}\nTap Submit" }' ``` Save the returned `id` for the next step. Create test flows and attach them to the suite: ```bash theme={null} # Positive test case curl -X POST https://backend.autosana.ai/api/v1/flows \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Login with valid credentials", "instructions": "Enter valid email and password, tap login, verify home screen appears", "suite_id": "SUITE_ID_FROM_STEP_1" }' # Negative test case curl -X POST https://backend.autosana.ai/api/v1/flows \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Login with invalid password", "instructions": "Enter valid email but wrong password, tap login, verify error message appears", "suite_id": "SUITE_ID_FROM_STEP_1" }' ``` Run your tests from the [Flows page](https://autosana.ai/flows), trigger them via the [Runs API](/api-runs) (auth instructions run automatically for individual flows), or set up [Automations](/automations) to run them on a schedule. # Building Your Mobile App for Our Cloud Source: https://docs.autosana.ai/app-build-guide Step-by-step guide to building your mobile app for our virtual device cloud **Testing a website?** You don't need to build anything - just enter your URL when creating an app. This guide is for mobile apps (iOS and Android) only. Add our [MCP Server](/mcp-setup) to help you build your app for our cloud. ## Build Requirements Autosana runs your mobile apps on both our **virtual device cloud** (iOS Simulators and Android Emulators) and **[real devices in the cloud](/real-device-testing)**. The build you upload decides where it can run: | Platform | Build | Runs on | | -------- | ----------------------------- | ------------------------------ | | iOS | `.app` (compressed as `.zip`) | Simulator only | | iOS | `.ipa` | Real devices only | | Android | `.apk` / `.aab` | Emulators **and** real devices | ### iOS Builds * **Simulator** — a `.app` bundle compressed as `.zip`, built for the simulator SDK (`arm64`). Cheapest and fastest; covers most testing. * **Real device** — a `.ipa` built for physical hardware (`iphoneos`). Required for [real-device testing](/real-device-testing). Building on an M1+ Mac will use `arm64` by default. Intel Macs will produce `x86_64` builds, which won't work on our simulators. The simulator (`.app`) build steps are below; jump to [Real device builds](#real-device-builds-ipa) for `.ipa`. ### Android Builds * **Format**: `.apk` or `.aab` * **Architecture**: a **universal** APK/AAB (recommended) runs on both emulators and real devices. An `x86_64`-only APK runs on emulators but not real devices. Most build commands create universal APKs that include all architectures by default, so the same Android build works on both emulators and real devices — no separate build needed. *** Select your framework to see build instructions: **Important:** React Native apps must be built in Release mode. Debug builds will attempt to connect to Metro bundler and fail to run on the device. Release builds bundle the JavaScript code directly into the app, making them standalone. ## React Native (iOS) 1. Build it with: ```bash theme={null} npx react-native run-ios --mode Release ``` 2. App file should be here: ```bash theme={null} ios/build/Build/Products/Release-iphonesimulator/[YourApp].app ``` In macOS, it should just be `YourApp` because macOS hides `.app` extensions 3. Go to Finder, compress `YourApp` to a `.zip` and upload/drag it into the Autosana app upload dialog ### React Native with Expo (iOS) For Expo projects, use EAS Build to create simulator builds: 1. Add this profile to your `eas.json`: ```json theme={null} { "build": { "autosana-simulator": { "distribution": "internal", "ios": { "simulator": true } } } } ``` 2. Run the build command: ```bash theme={null} eas build --platform ios --profile autosana-simulator ``` 3. Once the build completes, EAS will provide a download URL for the build 4. Download and extract it to get the `.app` file 5. Compress the `.app` to a `.zip` and upload it to Autosana This profile only creates a Simulator artifact; it does not enable APNs. If the app uses `expo-notifications`, add its config plugin. During Expo Prebuild or Continuous Native Generation, that plugin adds `aps-environment=development` to the iOS app. If the app does not use `expo-notifications`, declare the entitlement under `ios.entitlements` or configure it in the native Xcode project. Follow [Testing Push Notifications: Configure an Expo EAS build](/guides-push-notifications#configure-an-expo-eas-build). ## React Native (Android) 1. Navigate to your Android directory and build the release APK: ```bash theme={null} cd android && ./gradlew assembleRelease ``` 2. The APK will be located at: ```bash theme={null} android/app/build/outputs/apk/release/app-release.apk ``` 3. Upload the `.apk` file to Autosana ### React Native with Expo (Android) For Expo projects, use EAS Build to create APK builds: 1. Add this profile to your `eas.json`: ```json theme={null} { "build": { "preview": { "distribution": "internal", "channel": "preview", "android": { "buildType": "apk" } } } } ``` 2. Run the build command: ```bash theme={null} eas build --platform android --profile preview ``` 3. Once the build completes, EAS will provide a download URL for the `.apk` file 4. Download the APK and upload it to Autosana ## Flutter (iOS) This builds a `.app` for the **simulator**. To test on real iPhones, build a `.ipa` instead — see [Real device builds](#real-device-builds-ipa). 1. Build the app for simulator: ```bash theme={null} flutter build ios --simulator ``` If your app uses flavors, add the `--flavor` flag: ```bash theme={null} flutter build ios --simulator --flavor [flavorName] ``` 2. The `.app` file will be located at: ```bash theme={null} build/ios/iphonesimulator/Runner.app ``` 3. Compress the `.app` to a `.zip` file 4. Drag or upload the `.zip` file into the Autosana app upload dialog ## Flutter (Android) 1. Build apk in debug mode: ```bash theme={null} flutter build apk --debug ``` If your app uses flavors, add the `--flavor` flag: ```bash theme={null} flutter build apk --debug --flavor [flavorName] ``` 2. File is located at: ```bash theme={null} build/app/outputs/flutter-apk/app-debug.apk ``` 3. Drag or upload the `.apk` file into the Autosana app upload dialog ## Native iOS (Xcode/SwiftUI) ### Option 1: Using Xcode (Graphical Interface) 1. Open your project in Xcode 2. At the top of Xcode, set: * The scheme to your app target (e.g., "MyApp") * The device to a simulator (e.g., "iPhone 16 Pro") 3. In Xcode menu, select **Product > Scheme > Edit Scheme** 4. Under **Run**, change **Build Configuration** to **Release** 5. Press **Command + B** to build the project 6. After the build finishes, the `.app` file will be located at: ```bash theme={null} ~/Library/Developer/Xcode/DerivedData//Build/Products/Release-iphonesimulator/.app ``` 7. Right click and open in Finder 8. Right click and compress it to make a `.zip` file 9. Drag or upload the `.zip` file into the Autosana app upload dialog **Finding the build folder in Finder:** * Open Finder * Press **Command + Shift + G** * Paste the path and press Enter ### Option 2A: Ordinary unsigned Simulator build 1. Open Terminal and navigate to the root of your Xcode project: ```bash theme={null} cd /path/to/your/project ``` 2. Run this command to build the Release version for simulator: ```bash theme={null} xcodebuild -scheme YourAppScheme \ -sdk iphonesimulator \ -configuration Release \ -destination 'platform=iOS Simulator,OS=latest,name=iPhone 16 Pro' \ -derivedDataPath ./build \ ARCHS=arm64 \ ONLY_ACTIVE_ARCH=NO \ CODE_SIGN_IDENTITY="" \ CODE_SIGNING_REQUIRED=NO \ CODE_SIGNING_ALLOWED=NO \ build ``` 3. After the build completes, your `.app` file will be located: ```bash theme={null} ./build/Build/Products/Release-iphonesimulator/YourAppName.app ``` 4. Right click and open in Finder 5. Right click and compress it to make a `.zip` file 6. Drag or upload the `.zip` file into the Autosana app upload dialog This recipe explicitly disables signing. It cannot be used for APNs push notification testing or any feature that requires a signed entitlement. ### Option 2B: Push-capable signed Simulator build First enable **Push Notifications** for the app target under **Signing & Capabilities** in Xcode. Then build without the settings that disable signing: ```bash theme={null} xcodebuild -scheme YourAppScheme \ -sdk iphonesimulator \ -configuration Release \ -destination 'platform=iOS Simulator,OS=latest,name=iPhone 16 Pro' \ -derivedDataPath ./build \ ARCHS=arm64 \ ONLY_ACTIVE_ARCH=NO \ build ``` The signed `.app` is located at: ```bash theme={null} ./build/Build/Products/Release-iphonesimulator/YourAppName.app ``` Before compressing it, confirm that the signed entitlements contain the APNs development environment: ```bash theme={null} codesign -d --entitlements :- \ ./build/Build/Products/Release-iphonesimulator/YourAppName.app ``` The output must contain `aps-environment` with the value `development`. Then run the complete architecture, entitlement, and bundle-ID preflight in [Testing Push Notifications](/guides-push-notifications#verify-the-simulator-artifact), compress the `.app`, and upload the resulting `.zip`. **Why Release configuration?** Release builds provide production parity with optimizations enabled, matching what your users experience. Debug builds work, but they are not recommended because they do not reflect the production environment. ## Native Android (Kotlin/Java) ### Option 1: Debug Build (Recommended for Quick Testing) Debug builds work immediately without any signing configuration. **Using Android Studio:** 1. Open your project in Android Studio 2. From the menu bar, select **Build > Build Bundle(s) / APK(s) > Build APK(s)** 3. Wait for the build to complete 4. Click on **locate** in the notification that appears, or navigate to: ```bash theme={null} app/build/outputs/apk/debug/app-debug.apk ``` 5. Upload the `.apk` file to Autosana **Using Terminal:** ```bash theme={null} cd /path/to/your/android/project ./gradlew assembleDebug ``` APK location: `app/build/outputs/apk/debug/app-debug.apk` ### Option 2: Release Build (Production Parity) Release builds provide better production parity but require signing configuration. **Step 1: Configure Signing (if not already set up)** Add this to your `app/build.gradle`: ```gradle theme={null} android { buildTypes { release { // Use debug keystore for testing (not for Play Store) signingConfig signingConfigs.debug minifyEnabled false } } } ``` This uses the debug keystore for convenience. For Play Store releases, you'll need a proper release keystore. **Step 2: Build the Release APK** **Using Terminal:** ```bash theme={null} cd /path/to/your/android/project ./gradlew assembleRelease ``` APK location: `app/build/outputs/apk/release/app-release.apk` **Using Android Studio:** 1. Open your project in Android Studio 2. From the menu bar, select **Build > Select Build Variant** 3. Change from "debug" to "release" 4. Select **Build > Build Bundle(s) / APK(s) > Build APK(s)** **Which should I use?** * **Debug**: Faster builds, works immediately, easier debugging * **Release**: Production-like optimizations, catches minification issues *** ## Real device builds (`.ipa`) To run on [real iPhones](/real-device-testing) instead of the simulator, upload an **`.ipa`** built for physical hardware (`iphoneos`) — not a zipped `.app`. The file extension is how we route the build: `.ipa` → real device, `.app` / `.zip` → simulator. **Signing matters.** Use a **Development** or **Ad Hoc** signed `.ipa`. You do **not** need to register our device UDIDs — builds are automatically re-signed for the test device. Enterprise-signed builds fail to launch on our devices, and App Store builds aren't supported. Re-signing strips some entitlements (e.g. Push Notifications, Apple Pay, App Groups), so features depending on them won't work during tests. If re-signing breaks Team-ID-prefixed Keychain access groups, expand **Advanced** during IPA upload and enable **Fix Keychain access after re-signing**. Most apps do not need this option. Android needs no separate build — a universal `.apk` / `.aab` already runs on both emulators and real devices. Use an EAS profile with internal distribution — note there is **no** `simulator: true`: ```json theme={null} { "build": { "device": { "distribution": "internal" } } } ``` ```bash theme={null} eas build --platform ios --profile device ``` EAS provides a download URL for the `.ipa`. Upload that file to Autosana. ```bash theme={null} flutter build ipa --export-method development ``` (The default export method is `app-store`, which won't launch on our devices.) The `.ipa` is written to: ```bash theme={null} build/ios/ipa/[YourApp].ipa ``` Upload that `.ipa` to Autosana. **Using Xcode:** 1. Set the run destination to **Any iOS Device** (not a simulator) 2. **Product > Archive** 3. In the Organizer, click **Distribute App** and choose **Debugging** (development) or **Release Testing** (ad hoc) — not Enterprise or App Store Connect 4. Upload the exported `.ipa` to Autosana **Using Terminal:** ```bash theme={null} xcodebuild -scheme YourAppScheme \ -sdk iphoneos \ -configuration Release \ -archivePath ./build/YourApp.xcarchive \ archive xcodebuild -exportArchive \ -archivePath ./build/YourApp.xcarchive \ -exportPath ./build/ipa \ -exportOptionsPlist ExportOptions.plist ``` With `ExportOptions.plist` setting the signing method to `debugging` (or `release-testing`): ```xml theme={null} method debugging ``` The `.ipa` lands in `./build/ipa/`. Upload it to Autosana. After uploading your `.ipa`, start a run with **Target: Real device** (or `device.physical: true` via the API). See [Real Device Testing](/real-device-testing) for how to run and pick a device. # Apps Source: https://docs.autosana.ai/apps Upload and manage your apps and websites in Autosana Before you can run any flows, you need to add your app or website to Autosana.