---
title: Storage API | TestingBot API Documentation
description: Upload your .ipa, .apk or Maestro flow ZIP to TestingBot Storage, then
  list, replace and delete the stored files.
source_url:
  html: https://testingbot.com/support/api/storage
  md: https://testingbot.com/support/api/storage.md
---

# TestingBot Storage

Upload app binaries and test bundles once, then reference them from your capabilities with a tb:// URL.

- **Endpoint:** api.testingbot.com
- **Version:** v1
- **Format:** JSON
- **Auth:** [HTTP Basic](https://testingbot.com/support/api#authentication)

POST `/v1/storage`

## Upload an app to storage
 Uploads an APK or IPA to TestingBot storage. Either send the binary as multipart `file` or pass a public `url` that TestingBot will fetch. The returned `app_url` (`tb://<appkey>`) can be set as the `app` capability on subsequent mobile sessions. Metadata (version, min\_device\_version, thumb) is extracted asynchronously: poll GET /storage/\<appkey\> until its `state` is `DONE` before reading those fields. 
### Arguments

- **`file` file:** Multipart binary upload of an .apk or .ipa.
- **`url` string:** Public HTTPS URL TestingBot will download the binary from.

POST `/v1/storage`
[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#)
Request

```bash
$ curl -X POST "https://api.testingbot.com/v1/storage" \
-u key:secret \
-F "file=@/path/to/app/file/Application-debug.apk"
```

```csharp
var client = new TestingBotClient(key, secret);
var file = await client.Storage.UploadAsync(new FileInfo("/path/to/Application-debug.apk"));
```

```ruby
require 'testingbot'
api = TestingBot::Api.new(key, secret)
api.upload_local_file(local_file_path)
```

```python
import testingbotclient
tb = testingbotclient.TestingBotClient(key, secret)
tb.storage.upload_local_file(local_file_path)
```

```php
$api = new TestingBot\TestingBotAPI($key, $secret);
$api->uploadLocalFileToStorage($pathToLocalFile);
```

```java
TestingbotREST restApi = new TestingbotREST(key, secret);
TestingbotStorageUploadResponse uploadResponse = restApi.uploadToStorage(file);
```

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

const api = new TestingBot({
  api_key: "your-tb-key",
  api_secret: "your-tb-secret"
});

const { app_url } = await api.uploadFile(localFilePath);
```

Response

```json
{
  "app_url": "tb://398eijf83i"
}
```

POST `/v1/storage/{path}`

## Update an app binary by appkey
 Replaces the binary stored under an existing `appkey`. The `app_url` (`tb://<appkey>`) stays the same so deployed CI configurations keep working without changes — useful for "always use the latest" CI flows. Metadata is re-extracted asynchronously: poll GET /storage/\<appkey\> until its `state` is `DONE`. 
### Arguments

- **`splat` integer required:** —
- **`file` file:** Multipart binary replacement.
- **`url` string:** Public HTTPS URL to fetch the new binary from.

POST `/v1/storage/{path}`
[cURL](https://testingbot.com#) [.NET](https://testingbot.com#) [Ruby](https://testingbot.com#) [Python](https://testingbot.com#) [PHP](https://testingbot.com#)
Request

```bash
$ curl -X POST "https://api.testingbot.com/v1/storage/{app_url}" \
-u key:secret \
-F "file=@/path/to/app/file/Application-debug.apk"
```

```csharp
var client = new TestingBotClient(key, secret);
var file = await client.Storage.ReplaceAsync(appKey, new FileInfo("/path/to/Application-debug.apk"));
```

```ruby
require 'testingbot'
api = TestingBot::Api.new(key, secret)
api.update_local_file(app_url, local_file_path)
```

```python
import testingbotclient
tb = testingbotclient.TestingBotClient(key, secret)
tb.storage.replace_local_file(app_url, local_file_path)
```

```php
$client = new TestingBot\Client($key, $secret);
$file = $client->storage()->replaceFile($appUrl, '/path/to/Application-debug.apk');
```

Response

```json
{
  "app_url": "tb://398eijf83i"
}
```

GET `/v1/storage/{path}`

## Get a stored app by appkey
 Returns metadata for a single uploaded app — its filename, type, version, icon, and download URL. 
### Arguments

- **`splat` integer required:** —

### Response fields

- **`id` integer:** Unique numeric storage object ID.
- **`app_url` string:** TestingBot storage URL (`tb://<appkey>`) — pass as a capability to reference this app in mobile tests.
- **`url` string:** Signed HTTPS URL to download the binary directly.
- **`filename` string:** Original uploaded filename.
- **`type` string:** Detected app type (ANDROID for .apk/.aab, IOS for .ipa/.zip, OTHER otherwise).
- **`version` string:** CFBundleShortVersionString / versionName extracted from the binary.
- **`min_device_version` string:** Minimum OS version required by the app.
- **`thumb` string:** App icon (signed PNG URL).
- **`created_at` timestamp:** Upload timestamp.
- **`state` string:** Metadata extraction state: PROCESSING right after upload, DONE once version / min\_device\_version / thumb are populated. Poll until DONE before relying on those fields.
- **`sim_only` boolean:** True for iOS simulator-only IPAs (no signing).

GET `/v1/storage/{path}`
[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#)
Request

```bash
$ curl "https://api.testingbot.com/v1/storage/{app_url}" \
-u key:secret
```

```csharp
var client = new TestingBotClient(key, secret);
var file = await client.Storage.GetAsync(appUrlOrKey);
```

```ruby
require 'testingbot'
api = TestingBot::Api.new(key, secret)
api.get_uploaded_file(app_url)
```

```python
import testingbotclient
tb = testingbotclient.TestingBotClient(key, secret)
tb.storage.get_stored_file(app_url)
```

```php
$api = new TestingBot\TestingBotAPI($key, $secret);
$api->getStorageFile($appUrl);
```

```java
TestingbotREST restApi = new TestingbotREST(key, secret);
TestingBotStorageFile storedFile = restApi.getStorageFile(appUrl);
```

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

const api = new TestingBot({
  api_key: "your-tb-key",
  api_secret: "your-tb-secret"
});

await api.getStorageFile(appUrl);
```

Response

```json
{
  "app_url": "tb://0ec522702cd81ec1374e9b3c",
  "url": "https://...",
  "id": 261,
  "type": "ANDROID",
  "filename": "sample.apk",
  "version": "1.0.1",
  "min_device_version": "12.0",
  "thumb": "https://...image.png",
  "created_at": "2019-03-08T08:58:32.000Z",
  "state": "DONE",
  "sim_only": false
}
```

GET `/v1/storage`

## List your storage apps
 Paginated list of every app you've uploaded to TestingBot storage, newest first. 
### Arguments

- **`offset` integer:** Skip this many apps from the start of the result set.
- **`count` integer max=`500`:** Number of apps to return .

### Response fields

- **`data` array of storage file objects:** Storage objects for this page.
- **`meta` meta object:** —

GET `/v1/storage`
[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#)
Request

```bash
$ curl "https://api.testingbot.com/v1/storage/" \
-u key:secret
```

```csharp
var client = new TestingBotClient(key, secret);
var files = await client.Storage.ListAsync();
```

```ruby
require 'testingbot'
api = TestingBot::Api.new(key, secret)
api.get_uploaded_files(0, 30)
```

```python
import testingbotclient
tb = testingbotclient.TestingBotClient(key, secret)
tb.storage.get_stored_files(offset=0, limit=30)
```

```php
$api = new TestingBot\TestingBotAPI($key, $secret);
$api->getStorageFiles();
```

```java
TestingbotREST restApi = new TestingbotREST(key, secret);
TestingBotStorageFileCollection fileList = restApi.getStorageFiles(0, 30);
```

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

const api = new TestingBot({
  api_key: "your-tb-key",
  api_secret: "your-tb-secret"
});

const files = await api.getStorageFiles(offset, count);
```

Response

```json
{
  "data": [
    {
      "app_url": "tb://….",
      "url": "…",
      "id": 44,
      "type": "ANDROID",
      "filename": "sample.apk",
      "version": "1.0.1",
      "min_device_version": "23",
      "thumb": "https://...image.png",
      "created_at": "2019-01-08T13:42:42.000Z",
      "state": "DONE",
      "sim_only": false
    }
  ],
  "meta": {
    "offset": 0,
    "count": 10,
    "total": 1
  }
}
```

DELETE `/v1/storage/{id}`

## Delete a stored app
 Permanently removes the binary and its metadata. Sessions referencing the `app_url` after deletion will fail at session-start with "app not found". 
### Arguments

- **`id` string required:** Numeric storage ID or appkey to delete.

DELETE `/v1/storage/{id}`
[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#)
Request

```bash
$ curl -X DELETE "https://api.testingbot.com/v1/storage/{app_url}" \
-u key:secret
```

```csharp
var client = new TestingBotClient(key, secret);
await client.Storage.DeleteAsync(idOrAppKey);
```

```ruby
require 'testingbot'
api = TestingBot::Api.new(key, secret)
api.delete_uploaded_file(file_identifier)
```

```python
import testingbotclient
tb = testingbotclient.TestingBotClient(key, secret)
tb.storage.remove_file(app_url)
```

```php
$api = new TestingBot\TestingBotAPI($key, $secret);
$api->deleteStorageFile($appUrl);
```

```java
TestingbotREST restApi = new TestingbotREST(key, secret);
boolean success = restApi.deleteStorageFile(appUrl);
```

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

const api = new TestingBot({
  api_key: "your-tb-key",
  api_secret: "your-tb-secret"
});

const success = await api.deleteStorageFile(fileId);
```

Response

```json
{
  "success": true
}
```

[Previous Codeless Automation & TestLab](https://testingbot.com/support/api/codeless) [Next Webhooks](https://testingbot.com/support/api/webhooks)
