---
title: TestingBot API Documentation. Use our API to retrieve info on tests
description: REST API to access your Selenium, Cypress and Playwright test results,
  manage devices, tunnels, storage and App Automate runs.
source_url:
  html: https://testingbot.com/support/api
  md: https://testingbot.com/support/api.md
---

# TestingBot API Documentation

Access and modify all your TestingBot data through our comprehensive REST API. Build powerful integrations and automate your testing workflows.

- **Endpoint:** api.testingbot.com
- **Version:** v1
- **Format:** JSON
- **Auth:** HTTP Basic

[Download OpenAPI 2.0 spec (JSON)](https://testingbot.com/api/v1/openapi.json)

## API Reference

The reference is split by resource. Every endpoint below is authenticated with the API key and secret described in [Authentication](https://testingbot.com#authentication).

- [Browsers & Devices 5 endpoints Discover which browsers, operating systems and real devices you can run on, and look up the IP ranges to allowlist.](https://testingbot.com/support/api/devices)
- [User & Team Management 16 endpoints Your own account and credentials, plus everything needed to provision team members and service accounts from a script.](https://testingbot.com/support/api/user)
- [Tests & Builds 12 endpoints Fetch test results, report pass/fail state back from your CI, and work with the builds that group them.](https://testingbot.com/support/api/tests)
- [Insights 5 endpoints The data behind the Test Analytics dashboard, exposed so you can build your own reporting.](https://testingbot.com/support/api/insights)
- [Screenshots 3 endpoints Kick off a screenshot run across browsers and fetch the images when it finishes.](https://testingbot.com/support/api/screenshots)
- [Tunnel 7 endpoints Manage the secure tunnels that let the grid reach your staging and internal environments.](https://testingbot.com/support/api/tunnel)
- [Codeless Automation & TestLab 29 endpoints Drive the codeless test builder and the TestLab suites that group those tests, end to end.](https://testingbot.com/support/api/codeless)
- [TestingBot Storage 5 endpoints Upload app binaries and test bundles once, then reference them from your capabilities with a tb:// URL.](https://testingbot.com/support/api/storage)
- [Webhooks 7 endpoints Manage the callbacks TestingBot posts when a test finishes, and verify them without waiting for a run.](https://testingbot.com/support/api/webhooks)
- [App Automate 33 endpoints Everything for native mobile test runs: Maestro flows, Espresso and XCUITest suites, and their reports.](https://testingbot.com/support/api/app-automate)

## API Clients

There are several open source client libraries available to easily interact with the TestingBot API:

- [NodeJS](https://github.com/testingbot/testingbot-api)
- [Python](https://github.com/testingbot/testingbotclient)
- [Java](https://github.com/testingbot/testingbot-java)
- [Ruby](https://github.com/testingbot/testingbot_ruby)
- [PHP](https://github.com/testingbot/testingbot-php)
- [.NET](https://github.com/testingbot/testingbot-dotnet)

## Authentication

Every TestingBot API request is authenticated with your **API key** and **API secret** using [HTTP Basic Auth](http://en.wikipedia.org/wiki/Basic_access_authentication) over HTTPS. The only unauthenticated endpoint is [GET /v1/browsers](https://testingbot.com#browsers).

### 1 · Find your credentials

Sign in to TestingBot and open [Account → Account Info](https://testingbot.com/members/user/edit). You'll find:

- **`key` string:** Your public API client key. Safe to share between your CI and the TestingBot grid; not a secret on its own.
- **`secret` string sensitive:** Treat this like a password. Never commit it to git, never paste it into screenshots, never embed it in client-side code. Use environment variables or a secret manager in CI.

### 2 · Send credentials on every request

Pass the credentials as the standard `Authorization` header. Most HTTP clients accept a `user:password` tuple and handle the Base64 encoding for you — examples on the right.

### 3 · Verify it works

Hit [GET /v1/user](https://testingbot.com#user). A JSON response with your name and plan means auth is set up correctly. A `401 Unauthorized` means the key or secret is wrong.

**Best practice.** Store credentials in environment variables — `TESTINGBOT_KEY` and `TESTINGBOT_SECRET` — and inject them into your CI/CD pipeline as secrets. Rotate the secret immediately if it leaks (Account → Reset API credentials). 

`Authentication Examples`
[cURL](https://testingbot.com#) [.NET](https://testingbot.com#) [Ruby](https://testingbot.com#) [Python](https://testingbot.com#) [PHP](https://testingbot.com#) [Java](https://testingbot.com#) [NodeJS](https://testingbot.com#)
Authenticated request

```bash
$ curl https://api.testingbot.com/v1/user \
  -u "$TESTINGBOT_KEY:$TESTINGBOT_SECRET"
```

```csharp
using TestingBot.Api;

// reads TESTINGBOT_KEY / TESTINGBOT_SECRET from the environment
using var client = TestingBotClient.FromEnvironment();

var user = await client.User.GetAsync();
```

```ruby
require 'testingbot'
api = TestingBot::Api.new(
  ENV['TESTINGBOT_KEY'],
  ENV['TESTINGBOT_SECRET']
)
api.get_user_info
```

```python
import os, testingbotclient
tb = testingbotclient.TestingBotClient(
  os.environ['TESTINGBOT_KEY'],
  os.environ['TESTINGBOT_SECRET']
)
tb.user.get_user_information()
```

```php
$api = new TestingBot\TestingBotAPI(
  getenv('TESTINGBOT_KEY'),
  getenv('TESTINGBOT_SECRET')
);
$api->getUserInfo();
```

```java
TestingbotREST restApi = new TestingbotREST(
  System.getenv("TESTINGBOT_KEY"),
  System.getenv("TESTINGBOT_SECRET")
);
TestingbotUser user = restApi.getUserInfo();
```

```javascript
const TestingBot = require('testingbot-api');

const api = new TestingBot({
  api_key: process.env.TESTINGBOT_KEY,
  api_secret: process.env.TESTINGBOT_SECRET
});

const userInfo = await api.getUserInfo();
```

## Reducing response size

Any endpoint accepts an optional `omit` query parameter to drop fields you don't need from the JSON response. Pass a comma-separated list of field names. This is handy for trimming large payloads, for example skipping the embedded `report` and `report_xml` on App Automate run details.

- **`omit` string:** Comma-separated field names to exclude. Matching is by field name and applies anywhere it appears in the response, including inside `data` list items and nested objects. Unknown names are ignored.

**Note.** `omit` never affects whether a request succeeds, only the shape of the body. Omitting nothing returns the full, default response. 

`omit example`

Skip heavy fields on a run

```bash
$ curl "https://api.testingbot.com/v1/app-automate/maestro/12/45?omit=report,report_xml" \
  -u "$TESTINGBOT_KEY:$TESTINGBOT_SECRET"
```

Response

```json
{
  "id": 45,
  "status": "DONE",
  "success": true,
  "created_at": "2026-06-01T10:00:00.000Z"
}
```

## Rate limits

Requests are counted in a rolling five-minute window. Authenticated requests count against your **API key** , so one busy CI job cannot spend a colleague's budget, and a key that leaks is capped rather than free.

- **`2000 requests / 5 minutes` per API key:** Roughly 6 requests per second sustained. Applies to every authenticated endpoint.
- **`300 requests / 5 minutes` per IP:** For requests sent without credentials, which can only reach the public endpoints such as [GET /v1/browsers](https://testingbot.com/support/api/devices#browsers).

Every [tunnel endpoint](https://testingbot.com/support/api/tunnel) is exempt. Tunnels are infrastructure rather than a data surface: the client polls while a tunnel boots, and the tunnel VM itself reports readiness, so a limit there would stall provisioning rather than stop abuse.

[Sending a test webhook delivery](https://testingbot.com/support/api/webhooks#testwebhook) has its own, much tighter allowance of **20 per 5 minutes per API key** , on top of the limit above. Each call makes TestingBot perform an outbound request to a URL you choose, and verifying a webhook is a setup-time action rather than something to do in bulk.

Every API response carries your current usage, so you can slow down before you are cut off rather than after:

- **`X-RateLimit-Limit` integer:** Requests allowed in the current window.
- **`X-RateLimit-Remaining` integer:** Requests left in the current window.
- **`X-RateLimit-Reset` integer:** UNIX timestamp at which the window resets and the allowance returns to full.

**Going over.** Requests past the limit return `429 Too Many Requests` with a `Retry-After` header giving the seconds to wait. The body is JSON in the usual shape, so an SDK surfaces it as an API error rather than a parse failure. Retrying after `Retry-After` is always safe. 

`rate limit headers`

On every response

```
X-RateLimit-Limit: 2000
X-RateLimit-Remaining: 1994
X-RateLimit-Reset: 1785736800
```

Over the limit

```json
{
  "error": "429 Too Many Requests. Retry in 300 seconds."
}
```
