ガイド
選定・比較
ドキュメント
オブジェクトストレージを使う(ベータ版)
画像や動画などのファイルを、アプリから保存・配信する。バケットの作成と削除、アクセスキーの発行、画面でのファイルの表示と利用量、APIでのアップロードと表示まで。
hanamii では、ワークスペースごとに、画像や動画などのファイルを保存して配信できます(ベータ版)。ファイルの保存と配信は、国内のさくらのクラウドで行います。
ワークスペースの設定の オブジェクトストレージ では、バケット・ファイル・アクセスキー・利用量・使い方 のタブで、バケットとアクセスキーの管理、ファイルの一覧と表示、利用量の確認ができます。使い方 のタブには、このガイドの手順を短くまとめた例があります。ファイルのアップロードと削除は、発行したアクセスキーを使って、アプリのサーバーから API で行います。S3 互換ではないため、S3 用のツールや SDK はそのままでは使えません。
| 有料プラン(Light・Pro) | 無料プラン | |
|---|---|---|
| バケット数 | 30 個まで | 1 個まで |
| 保存できる容量(すべてのバケットの合計) | 100GB まで | 1GB まで |
| ファイル数(すべてのバケットの合計) | 10 万件まで | 1,000 件まで |
| 1 ファイルの大きさ | 2GB まで | 50MB まで |
| 公開バケット | 作れる | 作れない(非公開だけ) |
名前は、英小文字・数字・ハイフン(-)で 3〜63 文字です。先頭と末尾は英小文字か数字にしてください。同じワークスペースの中で同じ名前は使えません(削除した名前は使い直せます)。
| 公開設定 | 開ける人 | 向いているもの |
|---|---|---|
| 公開 | URL を知っている人は誰でも。URL はずっと同じです | Web サイトやアプリにそのまま載せる画像、商品の写真、ブログの画像 |
| 非公開 | 期限付きの URL を持っている人だけ | 請求書、予約確認書、会員だけに見せる資料 |
キーと接続先は、アプリのサーバーの環境変数などに保存してください(例:HANAMII_STORAGE_KEY・HANAMII_STORAGE_URL)。環境変数の例とアップロード・表示の例は、画面の 使い方 のタブにあります。キーが使えるかは、下の「API の基本」のコマンドで確かめられます。
利用量 のタブで、ワークスペースのすべてのバケットの合計を、プランの上限と並べて見られます。
バケットの作成・削除、アクセスキーの発行・無効化、ファイル一覧・閲覧用URL、利用量の取得は、他機能と同じ Hanamii APIトークン を使う Hanamii REST API でも操作できます。以下のファイル操作APIとは、接続先とキーが異なります。Hanamii REST APIのMarkdownには、バケット作成から公開・非公開と読み取り専用キーの確認までのcurl手順があります。
HANAMII_STORAGE_URL に入れて使いますAuthorization: Bearer <アクセスキー> を付けます。下の例では、キーを環境変数 HANAMII_STORAGE_KEY に入れて使います| やりたいこと | API | 必要な権限 |
|---|---|---|
| ファイルの一覧を見る | GET /v1/files | 読み取り・読み書き |
| アップロード用の URL を取る | POST /v1/files/uploads | 読み書き |
| アップロードの完了を知らせる | POST /v1/files/{fileId}/complete | 読み書き |
| ファイルを表示・ダウンロードする | GET /v1/files/{fileId}/download | 読み取り・読み書き |
| 非公開のファイルを期限付きで渡す | POST /v1/files/{fileId}/share-url | 読み取り・読み書き |
| ファイルを削除する | DELETE /v1/files/{fileId} | 読み書き |
キーが使えるかは、ファイルの一覧で確かめられます。
curl -s "$HANAMII_STORAGE_URL/v1/files" \
-H "Authorization: Bearer $HANAMII_STORAGE_KEY"
# 応答の例:{"items":[],"nextCursor":null}
アップロードは 3 段階です。ファイルの中身は hanamii のサーバーを通らず、保存先へ直接送られます。
uploadUrl へ、返ってきた headers(4 つ)をすべてそのまま付けて、ファイルの中身を PUT します。1 つでも欠けたり値が違ったりすると、断られます。uploadUrl は 15 分で使えなくなりますcomplete を呼ぶと、ファイルが使える状態になりますuploadUrl はファイルを送るためだけの URL で、ファイルを開くことには使えません。完了の後、ファイルは確認を経て配信用の場所へ移ります。開くときは、下の「ファイルを表示する・渡す」の方法を使ってください。
POST /v1/files/uploads の JSON 本文には、次の4項目がすべて必須です。
| 項目 | 型 | 指定する値 |
|---|---|---|
name | string | 保存するファイル名。1〜255文字で、改行は使えません |
sizeBytes | string | ファイルのバイト数を表す10進文字列。0バイトは不可です |
contentType | string | image/jpeg などのMIMEタイプ。1〜255文字で、改行は使えません |
contentMd5 | string | ファイル全体のMD5(16バイト)を標準Base64でエンコードした値。24文字で末尾は == です |
contentMd5 の省略・形式不正は HTTP 400(VALIDATION_ERROR) になります。アプリ側でファイルの内容から計算して送ってください。16進数のMD5文字列をそのまま指定することはできません。返された headers の Content-MD5 も、ファイル本体を送るときにそのまま付けます。
complete のような本文のないPOSTをcurlで呼ぶときは、Content-Length: 0 を付けてください。未指定だと、実行環境によってHTTP 411(No Content Length)で拒否されます。--data でJSON本文を送る要求では、curlが本文の長さを設定します。
FILE=photo.jpg
SIZE=$(wc -c < "$FILE" | tr -d ' ')
MD5=$(openssl dgst -md5 -binary "$FILE" | openssl base64 -A)
# 1. アップロード用のURLを取る
curl -s -X POST "$HANAMII_STORAGE_URL/v1/files/uploads" \
-H "Authorization: Bearer $HANAMII_STORAGE_KEY" \
-H "Content-Type: application/json" \
-d "{\"name\":\"$FILE\",\"sizeBytes\":\"$SIZE\",\"contentType\":\"image/jpeg\",\"contentMd5\":\"$MD5\"}"
# 応答の例:{"fileId":"...","uploadUrl":"https://...","headers":{"Content-Type":"image/jpeg","Content-Length":"...","Content-Disposition":"attachment; filename*=UTF-8''photo.jpg","Content-MD5":"..."},"expiresAt":"..."}
# 2. ファイルを送る(応答のuploadUrlとheadersを使う。Content-Lengthはcurlが付ける)
curl -s -X PUT "<uploadUrl>" \
-H "Content-Type: image/jpeg" \
-H "Content-MD5: $MD5" \
-H "Content-Disposition: <応答のheadersのContent-Dispositionの値>" \
--data-binary @"$FILE"
# 3. 完了を知らせる
curl -s -X POST "$HANAMII_STORAGE_URL/v1/files/<fileId>/complete" \
-H "Authorization: Bearer $HANAMII_STORAGE_KEY" \
-H "Content-Length: 0"
import { createHash } from 'node:crypto'
import { readFile } from 'node:fs/promises'
const STORAGE_URL = process.env.HANAMII_STORAGE_URL!
const KEY = process.env.HANAMII_STORAGE_KEY!
async function upload(path: string, name: string, contentType: string) {
const body = await readFile(path)
const contentMd5 = createHash('md5').update(body).digest('base64')
// 1. アップロード用のURLを取る
const res = await fetch(`${STORAGE_URL}/v1/files/uploads`, {
method: 'POST',
headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ name, sizeBytes: String(body.length), contentType, contentMd5 }),
})
if (res.status !== 201) throw new Error(`アップロード用のURLを取れませんでした: ${res.status}`)
const { fileId, uploadUrl, headers } = await res.json()
// 2. ファイルを送る
const put = await fetch(uploadUrl, { method: 'PUT', headers, body })
if (!put.ok) {
await cancel(fileId)
throw new Error(`ファイルを送れませんでした: ${put.status}`)
}
// 3. 完了を知らせる(409のときは、少し待って同じ連絡を送り直す)
for (let i = 0; i < 10; i++) {
const done = await fetch(`${STORAGE_URL}/v1/files/${fileId}/complete`, {
method: 'POST',
headers: { Authorization: `Bearer ${KEY}` },
})
if (done.ok) return done.json()
if (done.status !== 409) {
await cancel(fileId)
throw new Error(`完了を知らせられませんでした: ${done.status}`)
}
await new Promise((resolve) => setTimeout(resolve, 3000))
}
// まだ処理中のため取り消さず、fileId を残して、時間をおいて同じ完了の連絡を送る
throw new Error(`完了の確認が時間内に終わりませんでした(fileId: ${fileId})`)
}
// 失敗したアップロードを取り消して、同じ名前と容量の予約を空ける
async function cancel(fileId: string) {
await fetch(`${STORAGE_URL}/v1/files/${fileId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${KEY}` },
})
}
DELETE /v1/files/{fileId} で取り消すと、同じ名前と容量の予約がすぐに空きます(上の例の cancel)利用者のブラウザから送りたいときも、キーはブラウザへ渡しません。
uploadUrl と headers だけをブラウザへ渡し、ブラウザから PUT する(上の 2)GET /v1/files/{fileId}/download を呼ぶと、ファイルの表示用の URL へ転送されます(302)。アプリのサーバーでは転送を追わずに、応答の Location から表示用の URL を受け取り、<img src="..."> などに入れます。
表示用の URL をブラウザで直接開いたり、リンクにしたりすると、ファイルは表示されずに、元のファイル名でダウンロードされます。ページの中で見せるときは、<img> や <video> などに入れてください。
const res = await fetch(`${STORAGE_URL}/v1/files/${encodeURIComponent(fileId)}/download`, {
headers: { Authorization: `Bearer ${KEY}` },
redirect: 'manual',
})
const url = res.headers.get('location') // 表示用の URL。取れなければ null
非公開のファイルを、期限を決めて人に渡すときは、share-url を使います。期限は 1 秒から最大 7 日(604800 秒)までです。公開バケットのファイルには使えません(409)。
curl -s -X POST "$HANAMII_STORAGE_URL/v1/files/<fileId>/share-url" \
-H "Authorization: Bearer $HANAMII_STORAGE_KEY" \
-H "Content-Type: application/json" \
-d '{"expiresInSeconds":86400}'
# 応答の例:{"url":"https://...","expiresAt":"..."}
GET /v1/files で、完了したファイルを新しい順に返します。items(id・name・sizeBytes・contentType・createdAt・updatedAt など)と、続きがあるときは nextCursor が返ります。1 回に 50 件まで返し、?limit= で 1〜100 件を指定できます。続きは ?cursor=<nextCursor> で取りますDELETE /v1/files/{fileId} で削除します(204)。公開バケットのファイルは、配信のキャッシュのため、削除後も最大 10 分ほど見えることがありますエラーの本文は {"error":{"code":"...","message":"...","requestId":"..."}} の形です。お問い合わせのときは、requestId を伝えてください。
| 状態コード | 主な理由 |
|---|---|
| 400 | 必須項目の省略・形式不正(contentMd5 が未指定・標準Base64形式でない、limit が 1〜100 の外など) |
| 401 | キーが違う、または無効化されている |
| 403 | 読み取りのキーで、アップロード・削除をしようとした |
| 404 | ファイルが見つからない |
| 409 | 同じ名前のファイルがある、完了の処理中、公開バケットのファイルに share-url を使った |
| 422 | プランの上限(容量・ファイル数・1 ファイルの大きさ)を超える。お支払いが止まっている間のアップロード。画面の 利用量 のタブで、使っている量と上限の目安を確かめられます(アップロード中の分は含みません) |
| 500 番台 | 一時的な失敗。時間をおいて、同じ要求を送り直してください |
S3 用のツールや SDK は使えますか?
使えません。hanamii 独自の API です。上の例のように、HTTP で呼んでください。
次のガイド
カスタムドメインを設定する
日本の空に咲くクラウド
Calm. Fast. Trusted.
© 2026 hanamii. All rights reserved. 🌸