Skip to main content
RapidDev - Software Development Agency
flutterflow-integrationsFlutterFlow API Call

Backblaze B2 Cloud Storage

Connect FlutterFlow to Backblaze B2 using the native B2 API — a two-step auth flow where b2_authorize_account returns a dynamic apiUrl and auth token, followed by b2_get_upload_url for each upload. Because the application key and the dynamic apiUrl cannot safely live in a compiled Flutter app, a Firebase Cloud Function or Supabase Edge Function handles the authorize step and caches the token. FlutterFlow calls the proxy to get upload URLs, then PUTs file bytes directly.

What you'll learn

  • Why the native B2 API uses a two-step authorize flow and why the dynamic apiUrl must be handled in a proxy
  • How to deploy a proxy function that calls b2_authorize_account and caches the token and apiUrl
  • How to get a one-time upload URL from the proxy and PUT file bytes from FlutterFlow
  • How to generate download authorization tokens for private-bucket file access
  • The key differences between the native B2 API and the B2 S3-compatible layer
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced15 min read75 minutesStorage & FilesLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to Backblaze B2 using the native B2 API — a two-step auth flow where b2_authorize_account returns a dynamic apiUrl and auth token, followed by b2_get_upload_url for each upload. Because the application key and the dynamic apiUrl cannot safely live in a compiled Flutter app, a Firebase Cloud Function or Supabase Edge Function handles the authorize step and caches the token. FlutterFlow calls the proxy to get upload URLs, then PUTs file bytes directly.

Quick facts about this guide
FactValue
ToolBackblaze B2 Cloud Storage
CategoryStorage & Files
MethodFlutterFlow API Call
DifficultyAdvanced
Time required75 minutes
Last updatedJuly 2026

Backblaze B2 native API in FlutterFlow: two-step auth, dynamic URLs, and exclusive B2 features

This page covers the native Backblaze B2 API — not the S3-compatible endpoint covered on the backblaze-b2 page. Choose the native API when you need features the S3 layer does not expose: download authorization tokens (per-file, time-limited access for private buckets), the large-file chunked upload API, and B2-specific lifecycle rules. For founders who just want cheap S3-compatible storage, the backblaze-b2 page is simpler.

The defining characteristic of the native B2 API is its dynamic base URL. Calling b2_authorize_account (Basic auth with your keyID:applicationKey) returns an authorizationToken AND an apiUrl that is specific to your account and changes with each token. Every subsequent call must use that apiUrl as the base — you cannot hardcode it in a FlutterFlow API Group. Upload URLs are even more dynamic: b2_get_upload_url returns a one-time URL and a per-upload auth token that is single-use. Reusing an upload URL causes an error.

Backblaze B2 pricing: 10 GB free, then approximately $6/TB/month for storage. First 3× the daily stored capacity is free for downloads, then approximately $0.01/GB egress. Verify current pricing at backblaze.com/b2/cloud-storage-pricing.html before sharing with users.

Integration method

FlutterFlow API Call

The native B2 API requires a two-step authorize call that returns a dynamic apiUrl — meaning the base URL for all subsequent calls is not fixed and cannot be hardcoded in a FlutterFlow API Group. A backend proxy (Firebase Cloud Function or Supabase Edge Function) calls b2_authorize_account, caches the auth token and apiUrl, and exposes stable endpoints to FlutterFlow for getting upload URLs and download tokens. FlutterFlow then PUTs file bytes directly to the one-time upload URL.

Prerequisites

  • A Backblaze account (free at backblaze.com — 10 GB free storage, no credit card required)
  • A B2 bucket created in the Backblaze console (choose private for files that need download authorization)
  • A B2 application key created with read/write permissions for the bucket
  • A Firebase project (for the Cloud Function proxy) or a Supabase project (for the Edge Function proxy)
  • A FlutterFlow project (API Calls available on all plans)

Step-by-step guide

1

Create a B2 bucket and application key in the Backblaze console

Log in to the Backblaze console at secure.backblaze.com. In the left sidebar, click B2 Cloud Storage → Buckets. Click Create a Bucket. Give the bucket a name (lowercase letters, numbers, and hyphens only). For Files in Bucket, choose Private — this is important if you want to use download authorization tokens. Leave the default encryption settings unless your compliance requirements need SSE-B2. Click Create a Bucket. Next, click App Keys in the left sidebar. Click Add a New Application Key. Give it a descriptive name. Under Allow access to Bucket(s), select the specific bucket you just created rather than all buckets — this limits the blast radius if the key is ever compromised. Under Type of Access, select Read and Write. Optionally set an expiration date. Click Create New Key. Copy both the keyID and the applicationKey that are displayed — the applicationKey is shown only once. These two values go into your backend function's environment variables only. The keyID is analogous to an AWS access key ID; the applicationKey is analogous to the secret access key. You will use them together as HTTP Basic Auth credentials in the b2_authorize_account call.

Pro tip: Create an application key scoped to a single bucket rather than using your master application key. The master key has access to all your B2 buckets and account settings — too broad for an app.

Expected result: You have a private B2 bucket and a scoped application key (keyID + applicationKey) copied and stored securely for use in your backend function.

2

Deploy a proxy function that handles b2_authorize_account and provides stable upload endpoints

The core of this integration is a backend proxy that handles the dynamic nature of the B2 auth flow. Deploy a Firebase Cloud Function or Supabase Edge Function with the following logic: The function calls b2_authorize_account at https://api.backblazeb2.com/b2api/v2/b2_authorize_account using HTTP Basic Auth (keyID:applicationKey Base64-encoded in the Authorization header). The response contains: authorizationToken (a Bearer token for subsequent calls), apiUrl (the account-specific base URL for all B2 API calls), and downloadUrl (the base URL for file downloads). Cache these three values in the function's memory or in Firestore — the token is valid for 24 hours. Next, expose a /getUploadUrl endpoint that: calls b2_get_upload_url at {apiUrl}/b2api/v2/b2_get_upload_url with the Authorization header set to {authorizationToken} and the bucketId in the request body. This returns a one-time uploadUrl and a per-upload authorizationToken. Return both to FlutterFlow. Also expose a /getDownloadAuth endpoint that calls b2_get_download_authorization with the bucket name, file name prefix, and validDurationInSeconds, returning a downloadAuthorizationToken. FlutterFlow uses this token as a query parameter to construct private download URLs. Finally, expose a /listFiles endpoint that calls b2_list_file_names and returns a clean JSON array of file objects.

index.js
1// Firebase Cloud Function — B2 native API proxy
2// functions/index.js (Node.js)
3const functions = require('firebase-functions');
4const axios = require('axios');
5
6const KEY_ID = functions.config().b2.key_id;
7const APP_KEY = functions.config().b2.app_key;
8const BUCKET_ID = functions.config().b2.bucket_id;
9const BUCKET_NAME = functions.config().b2.bucket_name;
10
11// Cache auth result (valid 24h)
12let cachedAuth = null;
13
14async function getAuth() {
15 if (cachedAuth && Date.now() < cachedAuth.expiresAt) return cachedAuth;
16 const credentials = Buffer.from(`${KEY_ID}:${APP_KEY}`).toString('base64');
17 const res = await axios.get(
18 'https://api.backblazeb2.com/b2api/v2/b2_authorize_account',
19 { headers: { Authorization: `Basic ${credentials}` } }
20 );
21 cachedAuth = {
22 token: res.data.authorizationToken,
23 apiUrl: res.data.apiUrl,
24 downloadUrl: res.data.downloadUrl,
25 expiresAt: Date.now() + 23 * 60 * 60 * 1000, // 23h to be safe
26 };
27 return cachedAuth;
28}
29
30exports.b2GetUploadUrl = functions.https.onRequest(async (req, res) => {
31 res.set('Access-Control-Allow-Origin', '*');
32 if (req.method === 'OPTIONS') return res.status(204).send('');
33 const auth = await getAuth();
34 const upload = await axios.post(
35 `${auth.apiUrl}/b2api/v2/b2_get_upload_url`,
36 { bucketId: BUCKET_ID },
37 { headers: { Authorization: auth.token } }
38 );
39 res.json({
40 upload_url: upload.data.uploadUrl,
41 upload_auth_token: upload.data.authorizationToken,
42 });
43});
44
45exports.b2GetDownloadAuth = functions.https.onRequest(async (req, res) => {
46 res.set('Access-Control-Allow-Origin', '*');
47 if (req.method === 'OPTIONS') return res.status(204).send('');
48 const { fileName, validSeconds } = req.body;
49 const auth = await getAuth();
50 const dlAuth = await axios.post(
51 `${auth.apiUrl}/b2api/v2/b2_get_download_authorization`,
52 { bucketName: BUCKET_NAME, fileNamePrefix: fileName, validDurationInSeconds: validSeconds || 900 },
53 { headers: { Authorization: auth.token } }
54 );
55 const downloadToken = dlAuth.data.authorizationToken;
56 const fileUrl = `${auth.downloadUrl}/file/${BUCKET_NAME}/${fileName}?Authorization=${downloadToken}`;
57 res.json({ download_url: fileUrl });
58});
59
60exports.b2ListFiles = functions.https.onRequest(async (req, res) => {
61 res.set('Access-Control-Allow-Origin', '*');
62 if (req.method === 'OPTIONS') return res.status(204).send('');
63 const auth = await getAuth();
64 const list = await axios.post(
65 `${auth.apiUrl}/b2api/v2/b2_list_file_names`,
66 { bucketId: BUCKET_ID, maxFileCount: 100 },
67 { headers: { Authorization: auth.token } }
68 );
69 res.json({ files: list.data.files.map(f => ({ name: f.fileName, size: f.contentLength, id: f.fileId })) });
70});
71

Pro tip: Cache the b2_authorize_account result in function memory for up to 23 hours. The token is valid for 24 hours, so re-authorizing on every request is wasteful and may hit rate limits.

Expected result: Three deployed Cloud Function URLs — b2GetUploadUrl, b2GetDownloadAuth, and b2ListFiles — that return usable B2 upload/download data to FlutterFlow without exposing B2 credentials.

3

Create FlutterFlow API Groups for the proxy and for the direct B2 upload

In FlutterFlow, create two API Groups. The first talks to your proxy to get upload URLs and download tokens. The second performs the actual file upload directly to B2's one-time upload URL. API Group 1 — name it B2Proxy. Set the base URL to your Firebase function base URL (https://us-central1-your-project.cloudfunctions.net). Add a Content-Type header: application/json. Add three API Calls: - getUploadUrl: method GET, endpoint /b2GetUploadUrl. In the Response & Test tab, add JSON paths $.upload_url and $.upload_auth_token. - getDownloadAuth: method POST, endpoint /b2GetDownloadAuth. Body: {"fileName":"{{fileName}}","validSeconds":900}. Add fileName variable (String). JSON path: $.download_url. - listFiles: method GET, endpoint /b2ListFiles. JSON paths: $.files[*].name, $.files[*].size, $.files[*].id. API Group 2 — name it B2Upload. This group has a variable base URL because B2's upload endpoint is returned dynamically. In practice, you will use the full upload_url returned by getUploadUrl as the endpoint for a single API Call named uploadFile. Set method to POST. Add these headers as variables (not static): Authorization bound to the upload_auth_token variable, Content-Type bound to the file MIME type, X-Bz-File-Name bound to the filename (URL-encoded), X-Bz-Content-Sha1 bound to the file content hash or the literal string do_not_verify_sha1 for simplicity during development. Body type: Binary.

api_groups_config.json
1// B2Proxy API Group — reference config
2{
3 "base_url": "https://us-central1-your-project.cloudfunctions.net",
4 "calls": [
5 {
6 "name": "getUploadUrl",
7 "method": "GET",
8 "endpoint": "/b2GetUploadUrl",
9 "response_paths": ["$.upload_url", "$.upload_auth_token"]
10 },
11 {
12 "name": "getDownloadAuth",
13 "method": "POST",
14 "endpoint": "/b2GetDownloadAuth",
15 "body": { "fileName": "{{fileName}}", "validSeconds": 900 },
16 "response_paths": ["$.download_url"]
17 },
18 {
19 "name": "listFiles",
20 "method": "GET",
21 "endpoint": "/b2ListFiles",
22 "response_paths": ["$.files[*].name", "$.files[*].size", "$.files[*].id"]
23 }
24 ]
25}
26
27// B2Upload API Call — headers from variables
28{
29 "name": "uploadFile",
30 "method": "POST",
31 "endpoint": "{{uploadUrl}}",
32 "headers": {
33 "Authorization": "{{uploadAuthToken}}",
34 "Content-Type": "{{contentType}}",
35 "X-Bz-File-Name": "{{fileName}}",
36 "X-Bz-Content-Sha1": "do_not_verify_sha1"
37 },
38 "body_type": "binary"
39}
40

Pro tip: Using do_not_verify_sha1 for the X-Bz-Content-Sha1 header tells B2 to skip checksum verification — acceptable during development. For production, compute the actual SHA1 of the file bytes in a Custom Action before uploading.

Expected result: Two API Groups appear in FlutterFlow: B2Proxy for proxy operations and B2Upload for direct file uploads to B2's one-time upload URL.

4

Wire the upload action flow and build the file list with download tokens

On your upload page, wire the following action sequence in the Action Flow Editor for an Upload button: Step 1: File Picker action — lets the user select a file. Store the file bytes in a Page State variable fileBytes and the file name in fileName. Step 2: API Call action → B2Proxy → getUploadUrl — this returns a one-time uploadUrl and an uploadAuthToken. Store both in Page State variables (uploadUrl, uploadAuthToken). The upload URL is single-use — request it immediately before the upload, not in advance. Step 3: API Call action → B2Upload → uploadFile — set the endpoint variable uploadUrl to the Page State variable uploadUrl, uploadAuthToken to the stored token, contentType to the appropriate MIME type (image/jpeg, application/pdf, etc.), fileName to the Page State fileName (URL-encode any spaces as %20), and the binary body to fileBytes. Step 4: On success (response code 200), add a Snackbar action (File uploaded successfully) and trigger a backend query refresh on your file list. For the file list, add a ListView with a Backend Query → B2Proxy → listFiles. Display $.files[*].name as the file name. For each file's View button, add an API Call action → B2Proxy → getDownloadAuth binding fileName to the list item's $.files[*].name. After the call, add a Launch URL action bound to the returned $.download_url to open the file in the device browser, or load it into a WebView widget.

Pro tip: B2 upload URLs are single-use — calling b2_get_upload_url once and reusing it for multiple uploads will fail. Always call getUploadUrl immediately before each upload, not during page initialization.

Expected result: Users can upload files to B2 via the two-step flow (get upload URL → PUT bytes). The file list shows B2 objects, and tapping View generates a private download token and opens the file.

5

Test on device and handle B2 error responses

Start testing in FlutterFlow's Run mode with a small image file (under 100 KB) to verify the proxy calls work. Because the upload goes to a dynamic URL returned by the proxy, you should see two API Call actions in sequence in the action flow — the first returning the uploadUrl and the second consuming it. Check common failure points: open your Firebase console → Functions → Logs while running a test upload. If b2_authorize_account fails, check that the keyID and applicationKey environment variables are set correctly. If the upload returns a 401, confirm the uploadAuthToken is being passed in the Authorization header and that you are not trying to reuse a single-use upload URL for a second file. For private bucket downloads, test the getDownloadAuth flow with a file you know exists. The returned download_url includes the Authorization token as a query parameter — this is how B2 authorizes private file access without requiring the app to send a Bearer header (which would mean putting credentials in the app). For large files (over 100 MB), the single-call upload path may time out on slow connections. The native B2 API's large-file API (b2_start_large_file → b2_get_upload_part_url → b2_upload_part × N → b2_finish_large_file) handles this. Add a proxy endpoint for each step and implement the chunked upload in a Custom Action in Dart. If the two-step auth proxy and dynamic URL wiring feels complex for your current stage, RapidDev's team builds FlutterFlow integrations like this regularly — get a free scoping call at rapidevelopers.com/contact.

Pro tip: B2 returns useful error codes in the response JSON: 503 Service Unavailable means the B2 API is temporarily overloaded, and 429 means your app is rate limited — both include a Retry-After style delay. Log the response body in the Firebase function to surface these details.

Expected result: Small file uploads succeed end-to-end through the proxy. Private file downloads work with the generated token. Large files are flagged for the chunked upload path.

Common use cases

Private document vault app with per-download authorization

A FlutterFlow app where users store sensitive documents (contracts, tax returns, medical records) in a private B2 bucket. The native B2 API's b2_get_download_authorization endpoint generates time-limited download tokens per file — so even if a download URL is shared, it expires and cannot be reused. The proxy generates fresh download tokens before each viewing session.

FlutterFlow Prompt

Build a secure document vault where users see a list of their private documents and can tap to view a document in a WebView using a time-limited B2 download token, with the token expiring after 15 minutes.

Copy this prompt to try it in FlutterFlow

Video upload app with large-file part uploads

A FlutterFlow app where creators upload long video files (over 100 MB) to B2. The native B2 API's b2_start_large_file, b2_get_upload_part_url, and b2_finish_large_file endpoints handle chunked uploads reliably. The proxy manages the large-file session and provides part upload URLs to FlutterFlow, which uploads each chunk sequentially.

FlutterFlow Prompt

Create a video upload screen where users select a video from their camera roll, see an upload progress bar as the video is chunked and sent to Backblaze B2, and receive a confirmation when processing is complete.

Copy this prompt to try it in FlutterFlow

App backup service using B2 lifecycle rules

A FlutterFlow app for small businesses to back up local data files to B2 with automatic cleanup. B2 native lifecycle rules delete old versions after a set number of days — a feature not exposed through the S3 layer. The proxy configures lifecycle rules at setup time and provides upload URLs for the backup jobs that run from the app.

FlutterFlow Prompt

Build a backup status dashboard that shows last backup time, total storage used in B2, and allows users to trigger a manual backup of their local data files to Backblaze B2.

Copy this prompt to try it in FlutterFlow

Troubleshooting

b2GetUploadUrl returns 401 Unauthorized from the B2 API

Cause: The b2_authorize_account call failed because the keyID or applicationKey in the function environment variables is incorrect, or the application key expired.

Solution: In your Firebase console, check Functions → Logs for the full B2 error message. Verify the b2.key_id and b2.app_key config values match exactly what is shown in the Backblaze console. If the key has expired, create a new application key and update the function config.

Upload returns 400 Bad Request with code bad_bucket_id or upload_url_is_wrong

Cause: The upload URL returned by b2_get_upload_url was reused for a second upload. B2 upload URLs are single-use — each individual upload requires its own fresh upload URL.

Solution: Ensure your action flow calls getUploadUrl immediately before every file upload, not once on page load. Move the getUploadUrl API Call action to be the first action in the upload button sequence, immediately followed by the uploadFile action using the freshly returned URL.

Private file download returns 401 or unauthorized with the download URL

Cause: The download authorization token expired (default validity is 15 minutes), or the fileNamePrefix used to generate the token does not match the requested file path exactly.

Solution: Generate download tokens close to when the user will use them — not in advance. In the getDownloadAuth call, set the fileNamePrefix to the exact file name (or a prefix that covers all files in a folder). If the token expired, call getDownloadAuth again to get a fresh URL.

b2_list_file_names returns an empty files array for a bucket that has files

Cause: The bucket ID in the proxy function is incorrect, or the application key does not have read access to that bucket.

Solution: Open the Backblaze console, navigate to the bucket, and copy the exact Bucket ID (not the bucket name — they are different). Verify in the Firebase function config that b2.bucket_id matches this ID. Also confirm the application key was created with access to this specific bucket.

Best practices

  • Never store the B2 application key in FlutterFlow or Dart code — keep it exclusively in your backend function's environment variables.
  • Cache the b2_authorize_account result in your proxy for up to 23 hours — the token is valid for 24 hours, and re-authorizing on every request wastes calls and risks rate limits.
  • Always request a fresh upload URL immediately before each file upload — B2 upload URLs are single-use and cannot be shared across multiple upload calls.
  • Use X-Bz-Content-Sha1: do_not_verify_sha1 during development to skip checksum computation; compute and send the real SHA1 in production to detect corruption during transfer.
  • Generate download authorization tokens server-side with short validity periods (15 minutes) rather than making the bucket public — this prevents URL sharing from being permanent.
  • Scope application keys to a single bucket rather than granting access to all buckets — limits exposure if the key is ever compromised.
  • Use the native B2 API (this page) instead of the S3 layer specifically when you need download authorization tokens or the large-file part upload API — otherwise the S3 layer (backblaze-b2) is simpler.

Alternatives

Frequently asked questions

What is the difference between this page and the backblaze-b2 page?

The backblaze-b2 page covers the B2 S3-compatible API layer, where you use standard AWS S3 tools and presigned URLs. This page covers the native B2 API, which has its own endpoint format (b2_authorize_account, b2_get_upload_url, b2_get_download_authorization) and gives access to features the S3 layer does not expose, such as per-file download authorization tokens and the large-file part upload API. Both pages write to the same Backblaze B2 storage — the API surface is different.

Why does the B2 apiUrl change with each authorization? Can I hardcode it?

The apiUrl is account-specific and determined by Backblaze's infrastructure routing. It is stable for a given account but is not guaranteed to remain the same forever, and it varies between accounts. You should not hardcode it — always use the apiUrl returned by b2_authorize_account and cache it for the token's lifetime (up to 24 hours). The proxy pattern in this tutorial handles this automatically.

Can I use B2 download authorization tokens to serve private video streams in FlutterFlow?

Yes. A B2 download authorization token generates a URL that includes the authorization as a query parameter — for example https://f001.backblazeb2.com/file/mybucket/video.mp4?Authorization=TOKEN. Pass this URL to a Video Player widget in FlutterFlow to stream the private video. The token expires after the validDurationInSeconds you specified, after which the user would need a fresh URL.

What happens if my backend function goes down? Will FlutterFlow be able to upload files?

No — the proxy is required for the native B2 API because FlutterFlow cannot call b2_authorize_account or b2_get_upload_url without exposing the application key. If the proxy goes down, uploads and downloads of private files will fail. Use Firebase's scaling and redundancy features, or set up health monitoring on your function endpoints.

Is the native B2 API faster or slower than the S3 layer for uploads?

Performance should be similar — both API surfaces ultimately write to the same Backblaze storage infrastructure. The native API adds one extra round trip (b2_get_upload_url) before the actual upload, but this is a small overhead compared to network transit time for the file bytes themselves. Choose between native and S3 based on feature requirements, not performance.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire FlutterFlow integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.