TesterArmyTesterArmy
TesterArmyTesterArmy
OverviewTest RunsMobile AppsWebhooks

Mobile Apps

Use the mobile apps API to upload project binaries for mobile runs and webhook-triggered batches.

Upload a mobile app

Uploading is a two-step process: first request a presigned upload URL, then upload the file directly to storage, and finally confirm the upload.

Step 1 - Initiate the upload

Test Runs

Previous Page

Webhooks

Next Page

On this page

Upload a mobile appStep 1 - Initiate the uploadResponseStep 2 - Upload the fileStep 3 - Confirm the uploadResponseFull exampleList mobile appsExampleResponseDelete a mobile appExampleResponseUse with group webhooks
POST /projects/{projectId}/mobile/upload

Send a JSON body:

FieldTypeRequiredDescription
filenamestringYes.app.zip, .app.tar.gz, .zip, .tgz, or .apk
fileSizeinteger (bytes)YesSize of the file in bytes
removeAfterinteger secondsNoAuto-delete the upload after this many seconds

Response

{
  "uploadUrl": "https://storage.example.com/presigned-put-url...",
  "storageKey": "files/{projectId}/1234-abcd.zip",
  "filename": "MyApp.app.zip",
  "fileSize": 123456789,
  "removeAfter": 3600
}

Step 2 - Upload the file

Upload the binary directly to the presigned URL returned in step 1. The presigned URL is valid for 30 minutes.

curl -X PUT "<uploadUrl>" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @MyApp.app.zip

Step 3 - Confirm the upload

POST /projects/{projectId}/mobile/upload/confirm

Send a JSON body with the values from step 1:

FieldTypeRequiredDescription
storageKeystringYesThe storageKey returned in step 1
filenamestringYesThe filename returned in step 1
fileSizeinteger (bytes)YesThe fileSize returned in step 1
removeAfterinteger secondsNoAuto-delete the upload after this many seconds

Response

{
  "app": {
    "id": "<YOUR APP UPLOAD ID>",
    "platform": "ios",
    "filename": "MyApp",
    "bundleId": "com.example.app",
    "appVersion": "1.2.3",
    "buildVersion": "45",
    "fileSize": 123456789,
    "source": 




Full example

# Step 1: Initiate
RESPONSE=$(curl -s -X POST https://tester.army/api/v1/projects/{projectId}/mobile/upload \
  -H "Authorization: Bearer <YOUR API KEY>" \
  -H "Content-Type: application/json" \
  -d "{
    \"filename\": \"MyApp.app.zip\",
    \"fileSize\": $(stat -f%z MyApp.app.zip),
    \"removeAfter



















If removeAfter is omitted, the upload is permanent.

If the expiration time is reached while queued or running tests are still using the app, cleanup waits until those runs finish.

List mobile apps

GET /projects/{projectId}/mobile

Example

curl https://tester.army/api/v1/projects/{projectId}/mobile \
  -H "Authorization: Bearer <YOUR API KEY>"

Response

{
  "apps": [
    {
      "id": "<YOUR APP UPLOAD ID>",
      "platform": "ios",
      "filename": "MyApp",
      "bundleId": "com.example.app",
      "appVersion": "1.2.3",
      "buildVersion": "45",
      "fileSize": 123456789,
      "source"





Delete a mobile app

DELETE /projects/{projectId}/mobile/{appId}

Example

curl -X DELETE https://tester.army/api/v1/projects/{projectId}/mobile/{appId} \
  -H "Authorization: Bearer <YOUR API KEY>"

Response

{
  "deleted": true
}

Use with group webhooks

After uploading, trigger a mobile group webhook with the uploaded app's appId or bundleId.

By app ID (recommended when multiple builds share the same bundle ID):

curl -X POST https://tester.army/api/v1/groups/webhook/{id}/{secret} \
  -H "Content-Type: application/json" \
  -d '{
    "mobile": {
      "appId": "<YOUR APP UPLOAD ID>"
    }
  }'

By bundle ID (resolves to the latest upload matching that identifier):

curl -X POST https://tester.army/api/v1/groups/webhook/{id}/{secret} \
  -H "Content-Type: application/json" \
  -d '{
    "mobile": {
      "bundleId": "com.example.app"
    }
  }'

Only one of appId, bundleId, or artifactUrl can be provided per request. See Group Webhooks for full details.

"api_upload"
,
"expiresAt": "2026-04-05T12:34:56.000Z",
"removeAfter": 3600,
"createdAt": "2026-04-05T11:34:56.000Z"
}
}
\"
: 3600
}")
UPLOAD_URL=$(echo "$RESPONSE" | jq -r '.uploadUrl')
STORAGE_KEY=$(echo "$RESPONSE" | jq -r '.storageKey')
# Step 2: Upload directly to storage
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/octet-stream" \
--data-binary @MyApp.app.zip
# Step 3: Confirm
curl -X POST https://tester.army/api/v1/projects/{projectId}/mobile/upload/confirm \
-H "Authorization: Bearer <YOUR API KEY>" \
-H "Content-Type: application/json" \
-d "{
\"storageKey\": \"$STORAGE_KEY\",
\"filename\": \"MyApp.app.zip\",
\"fileSize\": $(stat -f%z MyApp.app.zip),
\"removeAfter\": 3600
}"
:
"api_upload"
,
"expiresAt": null,
"removeAfter": null,
"createdAt": "2026-04-05T11:34:56.000Z"
}
]
}