B2 Native API v3 client for the ACG B2 account: status, buckets, keys, files, bucket-size, usage/cost ($0.00695/GB), gated create/delete bucket+key, and gated lifecycle-based delete-prefix/lifecycle-remove for prefix purges. Read-only by default; destructive ops require --confirm. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9.2 KiB
Backblaze B2 Native API v3 Reference
Verified spec for the methods used by this skill, against the live ACG B2
account (accountId 46f69bc61163, region us-west-001). All facts below were
confirmed against the live API.
Authorization (v3)
- Authorize URL (fixed, global):
https://api.backblazeb2.com/b2api/v3/b2_authorize_account - Auth: HTTP Basic. Username = key id, password = application key.
- Method: GET in the B2 docs; this skill issues it as an HTTPS POST with the Basic header, which the endpoint accepts.
Authorize response (v3 shape)
{
"accountId": "46f69bc61163",
"authorizationToken": "<token, valid 24h>",
"apiInfo": {
"storageApi": {
"apiUrl": "https://api001.backblazeb2.com",
"s3ApiUrl": "https://s3.us-west-001.backblazeb2.com",
"downloadUrl": "https://f001.backblazeb2.com",
"recommendedPartSize": 100000000,
"absoluteMinimumPartSize": 5000000,
"capabilities": ["listBuckets", "listFiles", "..."],
"bucketId": null,
"namePrefix": null
}
}
}
- v3 nesting:
apiUrl/s3ApiUrl/downloadUrl/capabilities/bucketId/namePrefixlive underapiInfo.storageApi. (v2 returnedapiUrl/downloadUrlat the top level — do NOT parse them there for v3.) bucketId == nullandnamePrefix == nullmeans an account-wide key; non-null means the key is scoped to one bucket / name prefix.- The per-account
apiUrlis dynamic and comes ONLY from this response — never from a config file.
Cache model
The skill caches authorizationToken + apiUrl + accountId (plus the other
fields) in .claude/skills/b2/.cache/auth.json with an authorized_at
timestamp. It is treated as valid for ~23h (B2 tokens last 24h). A 401 with
code expired_auth_token or bad_auth_token triggers exactly one re-authorize
- retry. The cached token is a secret; the cache dir is gitignored.
Subsequent calls
- URL:
POST <apiUrl>/b2api/v3/<method> - Header:
Authorization: <authorizationToken>(raw token, not Basic). - Body: a JSON object.
- Content-Type:
application/json.
Error shape
B2 returns HTTP 4xx with a JSON body:
{ "status": 400, "code": "<string>", "message": "<text>" }
The skill surfaces status / code / message verbatim. On 401 with
expired_auth_token / bad_auth_token it re-authorizes once and retries; all
other errors raise. Destructive calls are never retried automatically.
Methods used
All POST to <apiUrl>/b2api/v3/<method>.
b2_list_buckets
- Body:
{"accountId": "<acct>", "bucketId": <opt>} - Returns:
{"buckets": [{bucketName, bucketId, bucketType, revision, fileLockConfiguration:{isFileLockEnabled}, lifecycleRules, options}]} - Pass
bucketIdto scope the result to a single bucket. revision(int) is the bucket's optimistic-concurrency version. NOTE: this account'sb2_update_bucketdoes NOT acceptifRevisionMatch, so this value is informational only — it is read but never sent back on a write.lifecycleRules(array) is the bucket's current lifecycle rule set (see the rule shape underb2_update_bucket). An empty array means no rules.
b2_list_keys
- Body:
{"accountId": "<acct>", "maxKeyCount": 1000, "startApplicationKeyId": <opt>} - Returns:
{"keys": [{keyName, applicationKeyId, capabilities:[...], bucketId, namePrefix, expirationTimestamp}], "nextApplicationKeyId": <null when done>} - Paginate on
nextApplicationKeyId.
b2_list_file_names
- Body:
{"bucketId": "<id>", "maxFileCount": 10000, "prefix": <opt>, "startFileName": <opt>} - Returns:
{"files": [{fileName, contentLength, action, uploadTimestamp, ...}], "nextFileName": <null when done>} - Lists the latest name for each file. Use for quick listings; NOT for size/cost.
b2_list_file_versions
- Body:
{"bucketId": "<id>", "maxFileCount": 10000, "prefix": <opt>, "startFileName": <opt>, "startFileId": <opt>} - Returns:
{"files": [{fileName, fileId, contentLength, action, ...}], "nextFileName": <null when done>, "nextFileId": ...} - Pagination: carry BOTH
nextFileNameANDnextFileIdinto the next request; stop whennextFileNameis null.
action types
| action | meaning | billed? |
|---|---|---|
upload |
a real stored object | YES |
hide |
a hide marker (contentLength 0) |
no |
start |
an unfinished large file | no |
folder |
a virtual folder placeholder | no |
For size/cost, sum contentLength over ALL versions where
action == "upload". B2 bills every stored version, not just the latest, so
size MUST use b2_list_file_versions, never b2_list_file_names.
b2_create_bucket (destructive — gated)
- Body:
{"accountId": "<acct>", "bucketName": "<name>", "bucketType": "allPrivate"} bucketTypedefaults toallPrivate; the CLI allowsallPublicvia--type.
b2_create_key (destructive — gated)
- Body:
{"accountId": "<acct>", "keyName": "<name>", "capabilities": [...], "bucketId": <opt>, "namePrefix": <opt>, "validDurationInSeconds": <opt>} - Returns:
{applicationKeyId, applicationKey, keyName, capabilities, bucketId, namePrefix}. applicationKeyis the SECRET and is shown ONCE on creation — it cannot be retrieved later. The CLI prints it with a prominent warning to store it in the vault immediately.bucketIdscopes the key to one bucket (the CLI resolves a bucket name to its id viab2_list_buckets).
b2_delete_bucket (destructive — gated)
- Body:
{"accountId": "<acct>", "bucketId": "<id>"} - B2 refuses to delete a non-empty bucket; that error is surfaced verbatim.
b2_delete_key (destructive — gated)
- Body:
{"applicationKeyId": "<id>"}
b2_update_bucket (destructive — gated)
Used for the prefix-purge feature: it writes the bucket's lifecycle rules.
- Body:
{"accountId": "<acct>", "bucketId": "<id>", "lifecycleRules": [...]} - Returns: the updated bucket object (same shape as a
b2_list_bucketsentry, including the NEWrevisionand the writtenlifecycleRules). lifecycleRulesREPLACES the entire array — it is NOT additive (caveat). To add/remove a single rule you MUST first read the current rules viab2_list_buckets(scoped to the bucketId), merge your change into the full array, then write the complete set back. Writing just the one rule you want silently drops every other rule.- No
ifRevisionMatchon this account/endpoint. This B2 account'sb2_update_bucketREJECTSifRevisionMatchwith HTTP 400bad_request("unknown field ... ifRevisionMatch"), so no optimistic-lock token is sent. Writes are therefore last-write-wins — the read-merge-write of the full rules array on every change is what keeps pre-existing rules intact. The skill does not implement a 409-conflict retry (there is no revision guard); it makes at most one defensive retry on a transient 5xx, never loops. - B2 allows up to 100 lifecycle rules per bucket.
Lifecycle rule shape
{ "fileNamePrefix": "MBS-<guid>/CBB_<machine>/",
"daysFromUploadingToHiding": 1,
"daysFromHidingToDeleting": 1 }
fileNamePrefix— files whose name starts with this string are governed by the rule.""means the WHOLE BUCKET.daysFromUploadingToHiding— after this many days, B2's daily lifecycle pass HIDES the file (creates a hide marker).daysFromHidingToDeleting— after this many days hidden, B2 DELETES the file version permanently.- With both
= 1, every version older than ~1 day is hidden then deleted on the next daily passes -> full server-side purge of the prefix within ~24-48h. This is the mechanism the skill uses to purge prefixes with 1.2M+ versions, where per-fileb2_delete_file_versionwould be impractical. Irreversible — no recycle bin.
b2_get_file_info / b2_delete_file_version
b2_get_file_infobody:{"fileId": "<id>"}(read).b2_delete_file_versionbody:{"fileName": "<name>", "fileId": "<id>"}(destructive; exposed only viaraw --confirmor the client helper).
Cost formula
GB = bytes / 1_000_000_000 # decimal GB (billing unit, NOT 2^30)
cost = GB * RATE_PER_GB_USD # RATE_PER_GB_USD = 0.00695 (ACG cost basis)
RATE_PER_GB_USD is ACG's cost basis, recorded in
.claude/memory/reference_backblaze_storage_rate.md, and used by the GuruRMM
mspbackups storage-cost calc. Override with --rate. The usage report sums
upload-version bytes per bucket, converts to decimal GB, multiplies by the rate,
sorts by cost desc, and prints a TOTAL row plus grand totals. For ACG-*
buckets it derives a client label by stripping the ACG- prefix.
Account structure (live)
- accountId
46f69bc61163, regionus-west-001. - 12 buckets (all
allPrivate): ACG-BST, ACG-Brett, ACG-Dataforth, ACG-GLAZTECH, ACG-IX, ACG-Internal, ACG-Lens, ACG-PST, ACG-REDNOUR, Horseshoe, MSPBackups20200311, VWP-Backup. Most hold MSP360/CloudBerry data (MBS-<guid>/CBB_<CLIENT>/...). - 2 application keys:
cloudberrykey(00146f69bc611630000000003, the MSP360/CloudBerry key) andClaudeTools(00146f69bc611630000000009, used by this skill).