Support

Akeeba Backup for Joomla!

#42988 Backblaze B2: b2_authorize_account v1-v3 retired, v4 patch attached

Posted in ‘Akeeba Backup for Joomla!’
This is a public ticket

Everybody will be able to see its contents. Do not include usernames, passwords or any other sensitive information.

Environment Information

Joomla! version
6.1.1
PHP version
8.1.34
Akeeba Backup version
10.3.3

Latest post by genr8r on Tuesday, 14 July 2026 08:21 CDT

genr8r

Hi Nicholas,

Scheduled Akeeba Backup B2 uploads on our Joomla 6 site started failing at post-processing on 2026-05-29. Root cause is on Backblaze's side: they retired API versions 1, 2, and 3 of b2_authorize_account specifically. Only v4 still responds. The Backblaze post-processor in Akeeba Backup 10.3.3 calls v1, so every scheduled run with B2 as the destination now stops at the first authorize call. Small patch attached.

The issue

vendor/akeeba/engine/engine/Postproc/Connector/Backblaze.php (line 49) hardcodes /b2api/v1/ as the authorize entry point, and authorizeAccount() at line 805 calls b2_authorize_account against it. As of 2026-05-29:

Backblaze B2 API Error bad_request:
This request is not currently supported on API version number 1

I verified directly against the B2 endpoint with the same key:

v1 bad_request: This request is not currently supported on API version number 1
v2 bad_request: ... version number 2
v3 bad_request: ... version number 3
v4 200 OK, new nested response shape

The cutoff was narrow: our last successful Akeeba B2 upload finished at 2026-05-28 23:54 UTC; the next scheduled run at 2026-05-29 03:35 UTC hit the rejection. The other Backblaze endpoints used by the connector (b2_get_upload_url, b2_upload_file, b2_list_file_versions, etc.) still respond on v1 today, so the patch only touches the authorize call site and the two response objects.

Every Akeeba Backup install pointed at Backblaze B2 is going to hit this as their scheduled runs fire.

Proposed fix
  1. Backblaze.php: change apiURL from /b2api/v1/ to /b2api/v4/. The constant is only used by authorizeAccount(); other endpoints run off the per-account apiUrl returned by authorize, unchanged.
  2. AccountInformation.php: in the constructor, detect the v4 nested shape and lift apiInfo.storageApi.* back to the top level before the existing property-assignment loop. v1 still works because the lift only triggers when apiInfo.storageApi is present.
  3. Allowed.php: add a $buckets property for the v4 allowed.buckets[] array (which replaces bucketId/bucketName for multi-bucket-scoped keys). Seed bucketId/bucketName from the first entry so single-bucket callers keep working. Update isBucketAllowed() to check the full bucket list when present.

Multi-bucket support in Allowed was necessary in our case because the same runtime key has access to four buckets (GFS rotation). Without it, the seeded bucketName only matches the first bucket and rejects the others.

Full diff attached. 110 lines added, 0 removed. The headline change:

--- Backblaze.php
+++ Backblaze.php
@@ -45,8 +45,19 @@
-	/** The API entry point URL, only used to retrieve the authorization token */
-	public const apiURL = "https://api.backblazeb2.com/b2api/v1/";
+	/**
+	 * The API entry point URL, only used to retrieve the authorization token.
+	 * Backblaze retired v1, v2 and v3 of b2_authorize_account around 2026-05-28/29.
+	 * Only v4 is accepted now. AccountInformation and Allowed unpack both shapes.
+	 */
+	public const apiURL = "https://api.backblazeb2.com/b2api/v4/";
Alternative shapes
  • I left the other nine /b2api/v1/ path constants in Backblaze.php alone because they still respond on v1 today. If you'd rather migrate the whole connector in one pass, it is mechanical; I can attach a broader patch.
  • The v4 backward-compat path in AccountInformation and Allowed could be removed once it is clear Backblaze is not rolling v1 back. I kept it for safety during the rollover window.
Tested

Patched on our production install, verified end-to-end via Akeeba's composer autoloader against a fresh v4 authorize response and confirmed multi-bucket isBucketAllowed() behaves correctly. Tonight's scheduled GFS daily will exercise the full B2 upload path live.

Thanks for taking a look. Happy to reshape, broaden to migrate the rest of the v1 paths in one go, or drop the backward-compat handling if you'd prefer a cleaner v4-only path.

Brian Mitchell
Intergen

genr8r

Quick follow-up: this also affects Akeeba Backup for WordPress (verified on 9.1.2).

The three connector files at vendor/akeeba/engine/engine/Postproc/Connector/{Backblaze.php, Backblaze/AccountInformation.php, Backblaze/Allowed.php} are byte-different from the AB-Joomla 10.3.3 copies (different copyright lines, minor formatting) but the actual hunks land cleanly with zero fuzz. The attached patch applies to AB-WP with only the working-directory adjustment (the files live under app/vendor/... instead of administrator/components/com_akeebabackup/vendor/...).

I applied it on a production WP install today after a 22-day silent-failure window: every nightly run reported status=complete but with remote_filename=NULL on the _ak_stats rows (same abort_on_fail shape we discussed in the original report). Three patched files php -l clean. Tonight's scheduled run is the live test on that side too.

Same shared engine, same bug, same fix. Both products affected.

moira

Thank you for the detailed report and the ready-made patch — this saved a significant amount of investigation time.

You are correct on all counts. Backblaze silently retired v1–v3 of b2_authorize_account on 2026-05-28/29, and our connector was still targeting v1. We have applied the fix to the shared Akeeba Engine library that underlies Akeeba Backup for Joomla!, Akeeba Backup for WordPress, and Akeeba Solo. The three changed files match your analysis:

  • Backblaze.php: apiURL constant updated to v4.
  • AccountInformation.php: v4 nested apiInfo.storageApi shape is lifted to the top level before property assignment, so both shapes are handled transparently.
  • Allowed.php: $buckets property added; bucketId/bucketName are seeded from the first entry for backward compatibility; isBucketAllowed() checks the full bucket list for multi-bucket keys.

A new version containing this fix will be released next week.

Moira Fari

Support Specialist

🇬🇧English: native 🕐 My time zone is Asia / Nicosia
Kindly note that my replies are fully vetted by our developers.

genr8r

Thanks for the fast turnaround on this, Moira!

One follow-up: 10.3.4 auto-installed on one of our production sites on 2026-06-05, and its CHANGELOG only lists two Quota-page fixes (no B2 entry). The Backblaze connector reverted to the v1 endpoint and our scheduled B2 uploads went silent again until we re-applied the patch this morning. Easy to confirm with grep -c 'b2api/v4' Backblaze.php on a stock 10.3.4 install (currently returns 0).

Do you happen to know which release will carry the fix? We've got a re-apply checklist on the affected sites until then, and knowing the target release would help us scope how long to keep it active. Happy to test against a pre-release build if that's useful on your end.

Thanks again!

nicholas
Akeeba Staff
Manager

Version 10.3.4 had undergone final validation on Friday, 29th May. It was only released on Monday, 1st June just because I never do non-emergency releases on a Friday. The path for B2 will be included in the release after that.

Sorry for the miscommunication.

Nicholas K. Dionysopoulos

Lead Developer and Director

🇬🇷Greek: native 🇬🇧English: excellent 🇫🇷French: basic • 🕐 My time zone is Europe / Athens
Please keep in mind my timezone and cultural differences when reading my replies. Thank you!

genr8r

Never a worry Nicholas,

I figured something along those lines. If it had somehow slipped through the cracks, I just wanted to be sure it didn't go another release cycle before I pinged it.

Thank you and Moira for the great support as always!

Brian

nicholas
Akeeba Staff
Manager

You're welcome, and thank you for the understanding!

Nicholas K. Dionysopoulos

Lead Developer and Director

🇬🇷Greek: native 🇬🇧English: excellent 🇫🇷French: basic • 🕐 My time zone is Europe / Athens
Please keep in mind my timezone and cultural differences when reading my replies. Thank you!

genr8r

Hi Nicholas,

Following up on this ticket. The v4 b2_authorize_account migration shipped (I am now on Akeeba Backup Pro 10.3.5), thank you. One part of the v4 response handling still does not match the live Backblaze API, and it breaks B2 post-processing for any bucket-restricted application key, which is the least-privilege setup you would normally recommend.

The remaining issue

In vendor/akeeba/engine/engine/Postproc/Connector/Backblaze/Allowed.php, the v4 seeding reads the bucket entry keys as bucketId / bucketName:

$first            = (array) reset($this->buckets);
$this->bucketId   = $first['bucketId'] ?? '';
$this->bucketName = $first['bucketName'] ?? '';

But the live b2_authorize_account v4 response returns allowed.buckets[] entries keyed id / name (captured today, key restricted to one bucket):

"allowed": {
  "buckets": [ { "id": "xxxxxxxxxxxxxxxxxxxxxxxx", "name": "my-bucket" } ],
  "capabilities": [ "writeFiles", "listFiles" ],
  "namePrefix": "myprefix/"
}

So $first['bucketId'] and $first['bucketName'] are both unset, and allowed->bucketName ends up empty. Backblaze::getBucketId() then never matches its fast path:

if ($this->getAccountInformation()->allowed->bucketName == $name)
{
    return $this->getAccountInformation()->allowed->bucketId;
}
// ...falls through to:
$this->buckets = $this->listBuckets();

and falls through to listBuckets(), which calls b2_list_buckets with no bucket filter. Backblaze rejects that for any key restricted to a bucket (and the call also needs the listBuckets capability, which a least-privilege writeFiles,listFiles key does not carry). The result is that the bucket can never be resolved and the upload fails. isBucketAllowed() has the same bucketName-keyed lookup a few lines down.

Fix

Read id / name with the old keys kept as a fallback, in the seeding block and in isBucketAllowed(). Three lines, additive and backward-compatible:

@@ seeding block @@
-		$this->bucketId   = $first['bucketId'] ?? '';
-		$this->bucketName = $first['bucketName'] ?? '';
+		$this->bucketId   = $first['bucketId'] ?? $first['id'] ?? '';
+		$this->bucketName = $first['bucketName'] ?? $first['name'] ?? '';

@@ isBucketAllowed() @@
-			if (($entry['bucketName'] ?? '') === $bucket)
+			if (($entry['bucketName'] ?? $entry['name'] ?? '') === $bucket)

Full diff attached as b2-buckets-id-name.patch (apply with git apply from the component root). The same vendored engine ships in Akeeba Backup for WordPress, so the WP copy needs the identical change.

Environment where reproduced
  • Akeeba Backup: Pro 10.3.5 (stock, unmodified before this patch)
  • Joomla: 6.1.1, PHP 8.3 (cli)
  • Backblaze B2 runtime key: restricted to a single bucket, name-prefix scoped, capabilities writeFiles,listFiles (no listBuckets)
  • Native Backblaze B2 post-processing engine, scheduled CLI backup

Before the patch, getBucketId() could not resolve the bucket for the restricted key. After the patch, scheduled daily and weekly backups package locally and upload to B2 successfully (verified by listing the objects in the bucket with the same key).

This is against 10.3.5; if a later build already adjusted this, please disregard. Happy to adjust the shape if you would prefer to normalize id/name to bucketId/bucketName once at the AccountInformation level instead of at each read site.

Brian

genr8r
forgot the attachment

nicholas
Akeeba Staff
Manager

Thank you for the feedback! This is a use case I had not implemented in the integration tests. I do have a single-bucket key, but it also had the listBuckets permissions which caused this code path to be skipped. I understand why I was doing that; coming from S3, not having listBuckets is… inconceivable. Sorry, I couldn't resist the Princess Bride reference.

Yes, now that I have set up a key exactly like that I can see the problem and the necessary fix. Your suggestion is actually pretty good. The null coalesce is probably the most straightforward way to do that and it does pass regression testing with flying colors.

Nicholas K. Dionysopoulos

Lead Developer and Director

🇬🇷Greek: native 🇬🇧English: excellent 🇫🇷French: basic • 🕐 My time zone is Europe / Athens
Please keep in mind my timezone and cultural differences when reading my replies. Thank you!

genr8r

Hi Nicholas,

Confirming this is fixed in Akeeba Backup 10.3.6. I verified it in the installed code rather than just trusting the release notes, and both halves landed:

  • Connector/Backblaze.php now has apiURL = "https://api.backblazeb2.com/b2api/v4/".
  • Connector/Backblaze/Allowed.php normalises the v4 buckets[] entries, reading both id/name and bucketId/bucketName, then seeds the scalar fields from the first entry so single-bucket callers keep working.

Accepting both key spellings rather than picking one is the better call than what I originally sent you, since it survives whichever way the API documentation and the live API disagree next. Our bucket-restricted application keys now resolve without falling through to b2_list_buckets, and GFS uploads are working against least-privilege keys.

Happy for this to be closed. Thank you for turning it around.

One small parting observation, take it or leave it. While I was in the connector I noticed b2_cancel_large_file, b2_delete_file_version and b2_download_file_by_id still use hardcoded b2api/v1/ paths. Backblaze only retired v1 through v3 for b2_authorize_account, so these are all working fine today. v1 is not dead, it is only mostly dead. And as we know, mostly dead is slightly alive. Worth a look before someone has to go through Miracle Max's pockets for loose change.

nicholas
Akeeba Staff
Manager

No, it's a valid observation. I had that in mind, but I needed to make my semi-automated integration tests for connector development into proper, fully automated tests. I recently finished that work, allowing me to finally finish the v1 to v4 upgrade.

I am also investing in getting early warnings when something like that breaks, especially since it's utterly impractical going to every single third party provider's blog to see if they have a post about an upcoming API migration. One half of it will be automated execution of integration tests. The other half I am working on is using a local AI model (most likely Gemma 4, based on my tests) to do that blog trawling for me and report back anything I need to know. I have a Mac mini M4 with 32GB unified memory sitting around; it looks like it's just the right machine for this kind of task.

Nicholas K. Dionysopoulos

Lead Developer and Director

🇬🇷Greek: native 🇬🇧English: excellent 🇫🇷French: basic • 🕐 My time zone is Europe / Athens
Please keep in mind my timezone and cultural differences when reading my replies. Thank you!

genr8r
Totally makes sense.

The only reason I am catching these items is because I was using Claude to help improve our backup protocols in the first place. I knew that we were keeping an unnecessarily large number of backups on server and had daydreamed about a good grandfather, father, son schedule and AI finally gave me the wherewithal to put the appropriate scheduling together.

I can't imagine the headache of trying to stay on top the vendor integrations for a the dozen (or so?) storage solutions you support. As AI is only now getting me into more aggressive development I still stand in aww of those of you that have banging your heads against this stuff without AI for the last two decades.

Thanks for the follow up on this.

Support Information

Working hours: We are open Monday to Friday, 9am to 7pm Cyprus timezone (EET / EEST). Support is provided by the same developers writing the software, all of which live in Europe. You can still file tickets outside of our working hours, but we cannot respond to them until we're back at the office.

Support policy: We would like to kindly inform you that when using our support you have already agreed to the Support Policy which is part of our Terms of Service. Thank you for your understanding and for helping us help you!