# How to Integrate Retool with Backblaze B2 Cloud Storage

- Tool: Retool
- Difficulty: Intermediate
- Time required: 25 minutes
- Last updated: April 2026

## TL;DR

Connect Retool to Backblaze B2 Cloud Storage using the native B2 API — not the S3-compatible API. Authenticate by calling b2_authorize_account with your key ID and application key to get an authorization token and API URL, then use that token for all subsequent B2 API calls including bucket listing, object management, and download authorization. This approach unlocks B2-specific features unavailable through the S3-compatible layer.

## Why Use B2's Native API Instead of the S3-Compatible Endpoint?

Backblaze B2 supports two API interfaces: an S3-compatible layer that mirrors AWS S3 conventions, and the native B2 API that was B2's original interface. The native API unlocks capabilities that the S3-compatible layer does not expose: granular download authorization tokens with time-limited access and prefix restrictions, server-side encryption configuration, lifecycle rule management, detailed bandwidth and transaction accounting, and bucket-level event notifications. For teams that need more than basic file browsing — particularly those managing private content delivery, compliance-driven lifecycle policies, or cost tracking — the native B2 API is the better choice.

The key difference in the native API is its dynamic authentication model. Unlike the S3-compatible API's static credential signing, the native B2 API requires you to first call b2_authorize_account to receive a session-scoped authorization token and an API URL that may change between sessions. This two-step flow adds a small amount of complexity to the Retool setup — you chain an authorization query before every bucket operation session — but the approach works reliably within Retool's query chaining and event handler system.

Retool's server-side proxying handles both the authorization call and subsequent API calls securely. Your B2 application key ID and key are stored as configuration variables in Retool's settings, never exposed in the browser. The authorization token received from B2 is used only within Retool's server-side query execution layer, making this integration appropriate even for buckets containing sensitive or regulated content.

## Before you start

- A Retool account (Cloud or self-hosted) with permission to add Resources
- A Backblaze account with at least one B2 bucket
- A B2 application key with appropriate capabilities — create in Backblaze Dashboard → App Keys → Add a New Application Key
- Your B2 application key ID (keyId) and application key value — save the key value immediately at creation as it is shown only once
- Basic familiarity with Retool's query chaining (event handlers triggering subsequent queries) since the B2 native API requires an authorization step before data queries

## Step-by-step guide

### 1. Create a B2 application key with the required capabilities

B2's native API access is controlled by application keys with explicit capability grants. Navigate to the Backblaze web console (secure.backblaze.com) and go to App Keys → Add a New Application Key.

Set the application key configuration:
- Name of Key: 'retool-native-api' (descriptive name for tracking purposes)
- Allow access to Bucket(s): All (or select specific buckets for tighter access control)
- Type of Access: Read and Write — for a monitoring dashboard, Read Only is sufficient
- File name prefix: leave blank for full bucket access
- Duration: leave blank for a non-expiring key

For the native B2 API, the relevant capabilities are:
- listBuckets — allows b2_list_buckets
- listFiles — allows b2_list_file_names and b2_list_file_versions
- readFiles — allows b2_download_file_by_name and b2_download_file_by_id
- writeFiles — allows b2_upload_file (only needed for upload features)
- deleteFiles — allows b2_delete_file_version (only needed for delete features)
- readBucketEncryption — allows reading SSE configuration
- writeBucketLifecycle — allows b2_update_bucket lifecycle settings

Click Create New Key. Immediately copy both the applicationKeyId (the long numeric/alphanumeric ID) and the applicationKey (the secret value). The applicationKey is shown only once — if you navigate away without copying it, you must create a new key.

Next, add these two values as Retool configuration variables: go to Retool Settings → Configuration Variables. Create B2_KEY_ID with the applicationKeyId value (mark as secret), and B2_APPLICATION_KEY with the applicationKey value (mark as secret).

**Expected result:** A B2 application key exists with appropriate capabilities. The applicationKeyId and applicationKey are saved as secret configuration variables in Retool Settings → Configuration Variables.

### 2. Configure the REST API Resource and authorization query

Create a Retool REST API Resource for the B2 authorization endpoint. Go to Resources tab → Add Resource → REST API:
- Name: 'Backblaze B2 Native API'
- Base URL: https://api.backblazeb2.com
- Authentication: Basic Auth
- Username: {{ retoolContext.configVars.B2_KEY_ID }}
- Password: {{ retoolContext.configVars.B2_APPLICATION_KEY }}

Note: Basic Auth here is only used for the b2_authorize_account call. The base URL is B2's fixed authorization endpoint — subsequent API calls will use a dynamic URL returned by the authorization response.

Save the resource. Now create an authorization query in your Retool app. Go to the Code panel, create a new query, select the Backblaze B2 Native API resource:
- Method: GET
- Path: /b2api/v3/b2_authorize_account

This call returns a JSON response containing:
- authorizationToken — used as the bearer token for all subsequent B2 API calls
- apiInfo.storageApi.apiUrl — the dynamic API URL for your account's storage operations
- downloadUrl — base URL for direct file downloads
- accountId — your B2 account ID

Run this query manually and confirm the response. Store the auth token query as 'b2AuthQuery'. Other queries will reference {{ b2AuthQuery.data.authorizationToken }} and {{ b2AuthQuery.data.apiInfo.storageApi.apiUrl }}.

Create a second REST API Resource for authenticated B2 API calls:
- Name: 'B2 Storage API'
- Base URL: {{ b2AuthQuery.data.apiInfo.storageApi.apiUrl }} (note: this is dynamic)
- Authentication: Bearer Token
- Token: {{ b2AuthQuery.data.authorizationToken }}

**Expected result:** The b2_authorize_account query returns a JSON response containing the authorizationToken, apiUrl, and downloadUrl. The 'B2 Storage API' resource is configured with these dynamic values from the auth query response.

### 3. Query bucket listings and file inventories

Create a query to list all accessible B2 buckets. In the Code panel, create a new query and select the 'B2 Storage API' resource:
- Method: GET
- Path: /b2api/v3/b2_list_buckets
- URL parameter: accountId → {{ b2AuthQuery.data.accountId }}

The response returns a buckets array where each bucket includes bucketId, bucketName, bucketType (allPublic, allPrivate, snapshot), and bucketInfo (custom metadata). Bind a Select component to this data with displayField set to bucketName and valueField set to bucketId.

Create a file listing query using b2_list_file_names:
- Method: GET  
- Path: /b2api/v3/b2_list_file_names
- URL parameter: bucketId → {{ bucketSelect.value }}
- URL parameter: maxFileCount → 500
- URL parameter: prefix → {{ prefixInput.value || '' }}
- URL parameter: delimiter → / (for folder-like navigation)

The response includes a files array where each file has: fileId, fileName, size, contentType, uploadTimestamp, and fileInfo (custom metadata dictionary). Add a JavaScript transformer to format sizes and timestamps, then bind a Table component to {{ fileListQuery.data }}.

Enable 'Run automatically when inputs change' on the file list query so it refreshes when the bucket selection changes. Add a Text Input component for prefix filtering to enable folder-like navigation within the bucket.

```
// Transformer for b2_list_file_names response
const files = data.files || [];
return files.map(file => {
  const sizeBytes = file.contentLength || 0;
  let sizeDisplay;
  if (sizeBytes >= 1073741824) {
    sizeDisplay = (sizeBytes / 1073741824).toFixed(2) + ' GB';
  } else if (sizeBytes >= 1048576) {
    sizeDisplay = (sizeBytes / 1048576).toFixed(2) + ' MB';
  } else {
    sizeDisplay = (sizeBytes / 1024).toFixed(1) + ' KB';
  }
  return {
    file_id: file.fileId,
    file_name: file.fileName,
    short_name: file.fileName.split('/').pop(),
    content_type: file.contentType || '',
    size_bytes: sizeBytes,
    size_display: sizeDisplay,
    upload_time: file.uploadTimestamp
      ? new Date(file.uploadTimestamp).toLocaleString()
      : '',
    action: file.action, // 'upload', 'hide', or 'folder'
    sha1: file.contentSha1 || ''
  };
});
```

**Expected result:** The bucket selector populates with all accessible B2 buckets. Selecting a bucket populates the file table with filenames, sizes, upload timestamps, and content types. The prefix filter allows navigating into sub-'folders'.

### 4. Add download authorization token generation for private buckets

B2's native API provides b2_get_download_authorization for generating time-limited, prefix-scoped download tokens — a B2-specific feature not available through the S3-compatible API. This is essential for sharing private content without making entire buckets public.

Create a query to generate download authorization:
- Method: POST
- Path: /b2api/v3/b2_get_download_authorization
- Body type: JSON
- Body:
```json
{
  "bucketId": "{{ bucketSelect.value }}",
  "fileNamePrefix": "{{ fileListTable.selectedRow?.file_name || '' }}",
  "validDurationInSeconds": "{{ tokenDurationSelect.value }}"
}
```

Add a Select component for token duration with options: 3600 (1 hour), 86400 (24 hours), 604800 (7 days). Wire it to the validDurationInSeconds parameter.

The response returns an authorizationToken specifically for download operations. Construct the authorized download URL in a JavaScript query:
```
const baseUrl = b2AuthQuery.data.downloadUrl;
const bucket = bucketsQuery.data.buckets.find(b => b.bucketId === bucketSelect.value)?.bucketName;
const fileName = encodeURIComponent(fileListTable.selectedRow.file_name);
const token = downloadAuthQuery.data.authorizationToken;
return `${baseUrl}/file/${bucket}/${fileName}?Authorization=${token}`;
```

Display the generated URL in a Text Input component (set to read-only) with a Copy to Clipboard button. Add a Container component that shows the token expiration time: new Date(Date.now() + tokenDurationSelect.value * 1000).toLocaleString().

```
// JavaScript query to construct authorized download URL
// Runs after downloadAuthQuery succeeds
const baseUrl = b2AuthQuery.data.downloadUrl;
const selectedBucketId = bucketSelect.value;
const buckets = bucketsQuery.data.buckets || [];
const bucket = buckets.find(b => b.bucketId === selectedBucketId);
if (!bucket) return { url: '', error: 'Bucket not found' };
const selectedFile = fileListTable.selectedRow;
if (!selectedFile) return { url: '', error: 'No file selected' };
const encodedName = selectedFile.file_name.split('/').map(encodeURIComponent).join('/');
const token = downloadAuthQuery.data.authorizationToken;
const expiresAt = new Date(Date.now() + (tokenDurationSelect.value * 1000)).toLocaleString();
return {
  url: `${baseUrl}/file/${bucket.bucketName}/${encodedName}?Authorization=${token}`,
  expires_at: expiresAt,
  file_name: selectedFile.file_name,
  duration_label: tokenDurationSelect.label
};
```

**Expected result:** Selecting a file and clicking 'Generate Download Link' triggers the download authorization query and constructs a time-limited URL displayed in the panel. The URL opens the private B2 file without requiring the user to have B2 credentials.

### 5. Add bucket metadata, lifecycle display, and a storage summary

Add a bucket detail panel that shows the selected bucket's configuration, including lifecycle rules, server-side encryption settings, and bucket type. Create a query for bucket details:
- Method: GET
- Path: /b2api/v3/b2_list_buckets
- URL parameter: accountId → {{ b2AuthQuery.data.accountId }}
- URL parameter: bucketId → {{ bucketSelect.value }}
- URL parameter: bucketTypes → allPrivate,allPublic,snapshot

The response includes the full bucket object with lifecycleRules array, defaultServerSideEncryption, and bucketInfo. Display lifecycle rules in an editable table showing daysFromHidingToDeleting and daysFromUploadingToHiding for each rule.

For storage usage, B2's b2_list_buckets response includes a filesAndVersionsCount and storageBytesUsed per bucket — use this for a storage summary without needing to enumerate all files. Create a summary transformer that reads from the buckets list to display total storage across all buckets.

Drag a Stats Summary section at the top of the dashboard with Statistic components showing:
- Total buckets
- Total storage (sum of storageBytesUsed in GB)
- Estimated monthly cost ($0.006 × total GB)
- Total files (sum of filesAndVersionsCount)

For complex integrations involving multiple B2 buckets, custom download authorization workflows, and automated lifecycle management via Retool Workflows, RapidDev's team can help architect and build your Retool solution.

```
// Storage summary transformer — reads from b2_list_buckets response
// data.buckets is the array of all bucket objects
const buckets = data.buckets || [];
const totalBytes = buckets.reduce((sum, b) => sum + (b.storageBytesUsed || 0), 0);
const totalFiles = buckets.reduce((sum, b) => sum + (b.filesAndVersionsCount || 0), 0);
const totalGB = totalBytes / (1024 * 1024 * 1024);
const monthlyCost = totalGB * 0.006;
return {
  bucket_count: buckets.length,
  total_files: totalFiles.toLocaleString(),
  total_storage_gb: totalGB.toFixed(3),
  total_storage_display: totalGB >= 1 ? totalGB.toFixed(2) + ' GB' : (totalBytes / 1048576).toFixed(1) + ' MB',
  monthly_cost_estimate: '$' + monthlyCost.toFixed(4),
  buckets_summary: buckets.map(b => ({
    name: b.bucketName,
    type: b.bucketType,
    files: b.filesAndVersionsCount || 0,
    size_gb: ((b.storageBytesUsed || 0) / 1073741824).toFixed(4)
  }))
};
```

**Expected result:** A storage summary panel shows total buckets, files, storage, and estimated cost. The bucket detail panel shows lifecycle rules and encryption settings for the selected bucket. Download authorization URLs are generated with configurable expiration times.

## Best practices

- Trigger b2_authorize_account on page load so all subsequent queries have a valid token; add a manual 'Refresh Connection' button as a fallback for sessions that span more than 24 hours
- Store your B2 applicationKeyId and applicationKey as secret configuration variables in Retool Settings → Configuration Variables — never inline them in query parameters or body fields
- Use bucket-scoped application keys for file operation queries and reserve account-level keys for bucket listing and aggregate monitoring queries, following least-privilege principles
- Scope download authorization tokens to the minimum required prefix and shortest practical duration — 1-hour tokens for one-time sharing, 24-hour tokens for workflow-based access
- Add pagination handling for b2_list_file_names by checking the nextFileName field in the response — for large buckets with thousands of files, implement a 'Load next page' button rather than trying to load all files at once
- Cache the bucket list query (30-60 seconds) since bucket configurations rarely change, but do not cache the authorization query — always fetch a fresh token to ensure validity
- Test your integration against a non-production bucket first to verify lifecycle rule queries before running them against buckets with critical production data

## Use cases

### Build a private content delivery management panel

Create a Retool panel for managing private B2 buckets used for content delivery. Display bucket contents, generate time-limited download authorization tokens for selected files, and track download counts per file. Add a bulk token generation tool that creates download URLs with configurable expiration times for a set of selected objects.

Prompt example:

```
Build a content delivery panel that lists files in a private B2 bucket. When a file is selected, show a 'Generate Download Link' panel with an expiration time picker (1 hour, 24 hours, 7 days) and a Generate button. The generated URL should be displayed and copyable. Add a second panel showing a log of recently generated download tokens stored in the internal content_tokens database table.
```

### Build a lifecycle policy management dashboard

Create a Retool panel that displays current B2 lifecycle rules for each bucket, allows editing rule parameters like daysFromHidingToDeleting and daysFromUploadingToHiding, and shows the impact of proposed rule changes on estimated storage costs. Add a Workflow that periodically audits bucket lifecycle configurations against company retention policies.

Prompt example:

```
Build a lifecycle management panel showing all B2 buckets in a table with their current lifecycle rule settings. When a bucket is selected, display the lifecycle rules in an editable form showing daysFromHidingToDeleting and daysFromUploadingToHiding values. Add an estimated impact calculator that projects how many bytes and files would be affected by the rule change based on current bucket contents. Include a Save Changes button that calls b2_update_bucket.
```

### Build a storage cost and bandwidth monitoring dashboard

Create a Retool dashboard that uses B2's b2_list_buckets endpoint (which returns storageBytesUsed per bucket) to display storage usage across all buckets, estimated monthly costs, and download bandwidth consumed. Add Chart components for storage growth over time and a comparison view between buckets to identify cost optimization opportunities.

Prompt example:

```
Build a storage cost dashboard that queries all B2 buckets and displays storageBytesUsed for each. Show a summary card with total storage in GB and estimated monthly cost ($0.006/GB). Add a pie chart showing storage distribution by bucket. Include a trend section that compares current usage against last week's snapshot stored in the internal storage_snapshots table.
```

## Troubleshooting

### b2_authorize_account returns 401 Unauthorized

Cause: The applicationKeyId or applicationKey is incorrect, or Basic Auth credentials are being passed in the wrong order. In B2's native API, the Basic Auth username is the applicationKeyId and the password is the applicationKey — these are swapped from what some developers expect.

Solution: Verify the applicationKeyId is in the Retool resource's Basic Auth Username field and the applicationKey (the long secret value) is in the Password field. Double-check both values in Retool's configuration variables by editing them — character truncation can occur if the values were pasted with surrounding whitespace. If in doubt, generate a new application key in Backblaze Dashboard and update the configuration variables.

### File listing query returns 401 even after successful authorization

Cause: The dynamic API URL from b2_authorize_account's response (apiInfo.storageApi.apiUrl) is not being used correctly as the base URL for the 'B2 Storage API' resource, or the authorization token has expired.

Solution: Confirm the 'B2 Storage API' resource base URL references {{ b2AuthQuery.data.apiInfo.storageApi.apiUrl }} and that b2AuthQuery has run successfully before the file listing query. Since the base URL is dynamic, it resolves at query execution time — trigger b2AuthQuery first using a page load trigger or a manual 'Connect' button. If the token expired (after 24 hours), re-run b2AuthQuery to get a fresh token.

### b2_list_buckets returns an empty buckets array even though buckets exist

Cause: The application key was scoped to specific buckets rather than all buckets, or the accountId parameter in the query doesn't match the account the key belongs to.

Solution: Verify the accountId URL parameter matches {{ b2AuthQuery.data.accountId }} — do not hardcode it. If using a bucket-scoped key, b2_list_buckets will only return the bucket(s) the key has access to, not all account buckets. For a full multi-bucket dashboard, create an account-level master key with listBuckets capability for the listing query, and separate bucket-scoped keys for file operations.

### Download authorization URL returns 403 when accessed in a browser

Cause: The token included in the URL has expired, or the file path doesn't match the fileNamePrefix scope the token was authorized for.

Solution: Check that the token expiration time has not passed. The validDurationInSeconds you specified starts from when b2_get_download_authorization was called, not when the URL is accessed. Verify the fileName in the URL exactly matches or is within the fileNamePrefix scope used when generating the token. File names are case-sensitive in B2. Regenerate the download authorization with a longer duration if needed.

## Frequently asked questions

### When should I use B2's native API versus the S3-compatible API in Retool?

Use the native B2 API when you need B2-specific features: download authorization tokens for private content, lifecycle rule management (daysFromHidingToDeleting), server-side encryption configuration, or access to B2's detailed account and bucket metadata. Use the S3-compatible API when you want simpler setup, already use S3 patterns in other Retool resources, or only need basic bucket browsing and file management without B2-specific features.

### How long do B2 authorization tokens last, and how do I handle expiration in Retool?

B2 authorization tokens from b2_authorize_account are valid for 24 hours by default. In Retool, set the b2_authorize_account query to run on page load so each new session starts with a fresh token. For long-running sessions, add a timer component or a manual 'Reconnect' button that re-triggers the authorization query. When the token expires, all file listing and download queries will return 401 errors — refreshing the auth query resolves this immediately.

### Can I upload files to B2 from Retool using the native API?

Yes, but file uploads with B2's native API require a two-step process: first call b2_get_upload_url to get an upload URL and upload authorization token, then POST the file content to that URL with the token as a header. This is more complex than the S3-compatible PUT operation. For upload functionality, you'll need a File Input component in Retool and a query chain: get upload URL → upload file. Large files (over 100MB) should use B2's large file API (b2_start_large_file, b2_get_upload_part_url, b2_upload_part, b2_finish_large_file).

### Is there a way to connect Retool to both the B2 native API and S3-compatible API simultaneously?

Yes. Create two separate REST API Resources: one for the native B2 API (api.backblazeb2.com with Basic Auth for authorization calls) and one for the S3-compatible API (s3.REGION.backblazeb2.com with AWS v4 auth). Different queries in the same Retool app can target different resources — use the native API resource for authorization, lifecycle management, and download token generation, and the S3-compatible resource for standard file listing and download operations if preferred.

### Does the B2 native API support server-side encryption and lifecycle rules that I can manage from Retool?

Yes. B2's b2_update_bucket endpoint accepts serverSideEncryption settings (SSE-B2 or SSE-C modes) and lifecycleRules arrays. From Retool, you can build a form that reads current bucket settings via b2_list_buckets, allows editing lifecycle rules (daysFromHidingToDeleting, daysFromUploadingToHiding, fileNamePrefix per rule), and submits updates via b2_update_bucket. Add confirmation dialogs before applying lifecycle changes since incorrectly configured rules can cause unintended file deletion.

---

Source: https://www.rapidevelopers.com/retool-integrations/backblaze-b2-cloud-storage
© RapidDev — https://www.rapidevelopers.com/retool-integrations/backblaze-b2-cloud-storage
