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 every build of the app to. Also accepted as app_permission_groups, which is used only when groups is not sent. Replaces existing app-level grants; send none to remove all app-level grants. Build-level grants from testers_groups are not affected. Names that don't resolve are returned in invalid_groups. Combine with notify=on to email them
testers_groupsNoUnchangedComma-separated tester group names or IDs to grant access to this build only. Requires notify (on or 1); ignored otherwise. Adds to existing grants; send none to remove this build's own grants. Names that don't resolve are returned in invalid_build_groups. Also accepted as testers-groups or invitation_groups; if more than one is sent, invitation_groups is used first, then testers-groups
notifyNooffSet to on (or 1) 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
upload_to_saucelabsNooffLegacy name for sync_to_saucelabs. Only the exact value on copies 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
note

iOS apps signed with an ad-hoc or development certificate are closed beta unless your organization turns on Allow ad-hoc apps to be open beta in Organization Settings. Without that setting, landing_page_mode=open is ignored for these apps, and the upload still succeeds. A landing page that an upload creates for such an app starts closed; send landing_page_mode=open to open it after the setting is on. If Require login before download is on, every app is closed beta, whatever landing_page_mode says.

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. Also accepted as changelog or release_notes
tagsComma-separated tags. Replaces the build's tags
landing_page_modeLanding page visibility for the build's app: open or closed (error code 135 otherwise)
community_tokenURL alias for the app's landing page, same rules as on upload (error codes 124 invalid, 125 in use)
metadata_*Custom build metadata. Merged with the build's existing metadata, so you can update one key at a time

landing_page_mode and community_token change the whole app, not just this build. The ad-hoc rules in the /api/upload note above apply to this endpoint too: without Allow ad-hoc apps to be open beta, open is ignored for ad-hoc apps and the request still succeeds.

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.