Skip to main content

Legacy API (v1 Compatibility)

If you are migrating from TestFairy, your existing CI/CD scripts and plugins will continue to work without changes. The legacy API endpoints are fully supported alongside the new API v3.

Authentication​

All endpoints require authentication. You can authenticate using any of the following methods:

MethodExample
X-API-Key headercurl -H "X-API-Key: YOUR_KEY" ...
api_key POST paramcurl -F api_key=YOUR_KEY ...

Response Format​

All responses return JSON with a status field ("ok" or "fail").

Success

{ "status": "ok", ... }

Error

{ "status": "fail", "code": 5, "message": "..." }

Upload​

POST /api/upload​

Upload an APK, AAB, or IPA file. The app is automatically matched by package name, or created if it doesn't exist.

curl https://app.testfairy.com/api/upload \
-F api_key=YOUR_API_KEY \
-F file=@app-release.apk \
-F changelog="Bug fixes and improvements" \
-F notify=on \
-F groups="QA,Beta"
ParameterRequiredDefaultDescription
fileYes—Binary file (.apk, .aab, or .ipa). Also accepted as apk_file
team_idNoAuto-detectedTeam to upload to. Required when you belong to several teams and the app doesn't exist yet, or when it exists on more than one of your teams (error code 136)
folder_nameNoNoneAssign the app to a folder
app_versionNoDetected from the fileOverride the version string. Also accepted as version
version_codeNoDetected from the fileOverride the version code
changelogNoNoneRelease notes. Also accepted as comment or release_notes
tagsNoNoneComma-separated tags for the build
groupsNoUnchangedComma-separated tester group names or IDs to grant the app to. Replaces existing grants; send none to remove all. Combine with notify=on to email them
notifyNooffSet to on to email the app's tester groups about the new build
symbols_fileNoNoneDebug symbols to attach (iOS dSYM or Android mapping file). Also accepted as proguard_file
sync_to_saucelabsNooffSet to on to also copy the build to Sauce Labs App Storage
community_tokenNoUnchangedURL alias for the app's landing page. 6-63 characters: letters, digits, dot, hyphen, or underscore (error codes 124 invalid, 125 in use)
landing_page_modeNoUnchangedLanding page visibility: open or closed (error code 135 otherwise)
metadata_*NoNoneAny metadata_<key> field is stored as custom build metadata and returned under metadata

Projects​

GET /api/1/projects/​

List all apps in the organization.

curl -H "X-API-Key: YOUR_KEY" https://app.testfairy.com/api/1/projects/

Response includes: id, self, name, folderName, packageName, platform, icon, landingPageMode.

Builds​

GET /api/1/projects/{projectId}/builds/​

List all builds for an app.

GET /api/1/projects/{projectId}/builds/{buildId}​

Get a single build.

Response includes: id, projectId, appName, appDisplayName, appVersion, appVersionCode, iconUrl, appUrl, platform, comment, tags, downloads, uploadedAt, uploadedVia, isDistributionEnabled.

PATCH /api/1/projects/{projectId}/builds/{buildId}/​

Update a build's metadata.

ParameterDescription
commentUpdate release notes
tagsComma-separated tags

DELETE /api/1/projects/{projectId}/builds/{buildId}​

Delete a build. Requires admin permissions.

GET /api/1/projects/{projectId}/builds/{buildId}/download/​

Download a build. Responds with an HTTP 302 redirect to a pre-signed download URL (or to the install page when file storage isn't configured), so follow redirects, for example with curl -L.

POST /api/1/projects/{projectId}/builds/{buildId}/invites/​

Send install invitations to testers for a build.

Testers​

GET /api/1/testers​

List all testers in the organization.

Response includes: id, email, invitationStatus, isBlocked, groups, lastLogin, createdAt, plus hasUdidAccess, allowAll, hasPushToken, emailBounce, onlyAccount, account, allDevices, appleDevices.

POST /api/1/testers/​

Add a tester. Creates the user if they don't exist. Requires admin permissions.

ParameterRequiredDescription
emailYesTester's email address
groupNoGroup name to add the tester to

GET /api/1/testers/{testerId}​

Get a single tester's details.

DELETE /api/1/testers/{testerId}​

Remove a tester from the organization. Requires admin permissions.

POST /api/1/testers/{testerId}/block/​

Block a tester. Requires admin permissions.

DELETE /api/1/testers/{testerId}/block/​

Unblock a tester. Requires admin permissions.

Tester Groups​

GET /api/1/testers/groups​

List all tester groups. Response includes: id, name, testers (a nested list of { "email" } objects).

POST /api/1/testers/groups​

Create a tester group. Requires admin permissions.

ParameterRequiredDescription
groupNameYesName for the new group

POST /api/1/testers/groups/{groupId}​

Add an existing tester to a group by email. If the email doesn't belong to a tester in your organization, the call returns { "status": "ok", "testers": [] } and nothing changes. Requires admin permissions.

ParameterRequiredDescription
emailYesTester's email address

DELETE /api/1/testers/groups/{groupId}​

Remove a tester from a group by email. Requires admin permissions.

ParameterRequiredDescription
emailYesTester's email address (POST body or query param)

Groups​

GET /api/1/groups/​

List all groups in the organization. Response includes: id, name, private.

GET /api/1/groups/{groupId}​

Get a single group.

GET /api/1/groups/{groupId}/testers/​

List all testers in a group. Each entry contains email only. Paginated with page and per_page (default 50, max 200).

GET /api/1/groups/{groupId}/projects/​

List all apps assigned to a group. Response includes: id, name. Paginated with page and per_page (default 25, max 100).

Webhooks​

GET /api/1/webhooks/​

List all webhooks for the organization.

Response includes: id, name, url, status, actions (comma-separated), projectIds (comma-separated, or * for all apps).

POST /api/1/webhooks/​

Create a webhook. Requires admin permissions.

ParameterRequiredDescription
urlYesWebhook callback URL
nameYesDisplay name. Also accepted as webhook-name. Missing name returns code 104
actionsNoComma-separated event types to listen for
project_idsNoComma-separated app IDs (empty = all apps)

GET /api/1/webhooks/{webhookId}​

Get a single webhook.

POST /api/1/webhooks/{webhookId}​

Update a webhook. Requires admin permissions. Accepts the same parameters as create (all optional).

DELETE /api/1/webhooks/{webhookId}​

Delete a webhook. Requires admin permissions.

Sites (Teams)​

In the legacy API, "sites" correspond to "teams" in the current platform.

GET /api/1/sites/​

List all sites (teams) in the organization.

Response is { "site": { "accounts": [...], "managers": [...] } }. Each account includes id, name, buildsCount and users (each with email and role); each manager includes email. Only the account owner or an org admin can call this endpoint; other roles receive { "status": "fail", "code": 1, "message": "Feature is not enabled" } with HTTP 200.

GET /api/1/sites/{siteId}​

Get a single site (team).

POST /api/1/sites/​

Create a site (team). Requires admin permissions.

ParameterRequiredDescription
nameYesSite (team) name

Audit Logs​

Requires admin permissions. GET /api/1/audits/ ignores query parameters and always returns up to 1000 app-download events. The /api/2 audit endpoints support the following query parameters:

ParameterDescription
pagePage number (default: 1)
limitResults per page (default: 50, max: 100). per_page is also accepted and takes precedence
actionFilter by action type
searchSearch in email and action data
fromStart date (ISO 8601)
toEnd date (ISO 8601)

GET /api/1/audits/​

List audit log entries.

GET /api/2/audits/​

List audit log entries (v2 format with pagination metadata).

GET /api/2/audits/admin-trail/​

List admin activity audit trail.

GET /api/2/audits/tester-trail/​

List tester activity audit trail.

Response includes: id, timestamp, enterpriseId, siteName, userId, userEmail, ipAddress, actionType, actionLabel, actionData, plus pagination object.

Error Codes​

CodeHTTP StatusMeaning
1400Missing or invalid required parameter
2400Duplicate resource (already exists)
5401/403API key valid but no organization membership (401), or admin permissions required (403). On /api/upload, an invalid key also returns code 5 with HTTP 200
104401Missing or invalid API key (/api/1 and /api/2 endpoints)
112400Empty file uploaded
121400Invalid file type
124400Invalid community_token
125400community_token already in use by another app
133400Organization not configured (no team found)
135400Unsupported landing_page_mode (use open or closed)
136400team_id required or invalid: you belong to several teams, or the app exists on more than one of them
150502App bundle could not be converted to an installable APK
400200Tester not found (for example, Invalid Tester)
404200Group not found

CI/CD Examples​

Gradle (Android)

curl https://app.testfairy.com/api/upload \
-F api_key=$API_KEY \
-F file=@app/build/outputs/apk/release/app-release.apk \
-F changelog="$(git log -1 --pretty=%B)"

Xcode (iOS)

curl https://app.testfairy.com/api/upload \
-F api_key=$API_KEY \
-F file=@build/MyApp.ipa \
-F changelog="$(git log -1 --pretty=%B)" \
-F notify=on

List apps and builds

# List apps
curl -H "X-API-Key: $API_KEY" https://app.testfairy.com/api/1/projects/

# List builds for an app
curl -H "X-API-Key: $API_KEY" https://app.testfairy.com/api/1/projects/123/builds/

# Get download URL
curl -H "X-API-Key: $API_KEY" https://app.testfairy.com/api/1/projects/123/builds/456/download/

Migration to API v3​

To migrate, see the API Migration Guide.