hanamii
hanamii

特徴

料金

お知らせ

お問い合わせ

ガイド

hanamii をはじめる
hanamii の仕組み — コンテナで Web アプリが動くまで
GitHub からデプロイする
ZIP からデプロイする
ChatGPT や Claude で作ったアプリを公開する
AI で作ったアプリを GitHub 連携で公開する
Claude Code・Codex・Cursor から hanamii に公開する
Next.js 以外のアプリをデプロイする
環境変数の設定と反映
データベースを使う(ベータ版)
スケジューラーを使う(ベータ版)
オブジェクトストレージを使う(ベータ版)
カスタムドメインを設定する
ワークスペースとメンバー管理
メールで届く確認コードによる二段階認証
トラブルシューティング
料金とお支払いのしくみ
無料プランで長く使っていないプロジェクトの停止と再開
複数アプリ・追加枠の料金
10月1日より前からお使いの方へ:有料プラン開始と移行期間
支払い担当者向けガイド
REST API リファレンス

オブジェクトストレージを使う(ベータ版)

画像や動画などのファイルを、アプリから保存・配信する。バケットの作成と削除、アクセスキーの発行、画面でのファイルの表示と利用量、APIでのアップロードと表示まで。

hanamii では、ワークスペースごとに、画像や動画などのファイルを保存して配信できます(ベータ版)。ファイルの保存と配信は、国内のさくらのクラウドで行います。

ワークスペースの設定の オブジェクトストレージ では、バケット・ファイル・アクセスキー・利用量・使い方 のタブで、バケットとアクセスキーの管理、ファイルの一覧と表示、利用量の確認ができます。使い方 のタブには、このガイドの手順を短くまとめた例があります。ファイルのアップロードと削除は、発行したアクセスキーを使って、アプリのサーバーから API で行います。S3 互換ではないため、S3 用のツールや SDK はそのままでは使えません。

前提

  • バケットとファイルの一覧、ファイルを開くこと、利用量は、ワークスペースのメンバー全員が使えます
  • バケットの作成・削除と、アクセスキーの発行・無効化ができるのは、ワークスペースの オーナー と 管理者 です
  • 上限は、ワークスペースごとに次のとおりです
有料プラン(Light・Pro)無料プラン
バケット数30 個まで1 個まで
保存できる容量(すべてのバケットの合計)100GB まで1GB まで
ファイル数(すべてのバケットの合計)10 万件まで1,000 件まで
1 ファイルの大きさ2GB まで50MB まで
公開バケット作れる作れない(非公開だけ)
  • 容量は 10 進で数えます(1GB=10 億バイト)
  • 上限はそれぞれ別に数えます。バケット数はバケットを作るとき、容量・ファイル数・1 ファイルの大きさはアップロードするときに確かめます。たとえばバケットが 30 個に満たなくても、合計の容量が上限に達すると、どのバケットにもアップロードできません
  • 空きを作るには、使わないファイルを削除してください。すぐに空きます。バケットを削除した場合は、中のファイルの片付けが進むにつれて(約 5 分ごと)空きます
  • お支払いが止まっている間は、新しいバケットの作成とアップロードができません。すでにあるファイルは消えず、配信も続きます
  • プランを下げて上限を超えたときも、今あるバケット・ファイル・公開の設定はそのまま残ります。超えている上限に応じて、新しいバケットの作成やアップロードができなくなります

バケットを作る

  1. ワークスペースの設定の オブジェクトストレージ を開き、バケット のタブを選ぶ
  2. バケットを作成 を押す
  3. 名前と公開設定を選んで 作成する を押す

名前は、英小文字・数字・ハイフン(-)で 3〜63 文字です。先頭と末尾は英小文字か数字にしてください。同じワークスペースの中で同じ名前は使えません(削除した名前は使い直せます)。

公開設定開ける人向いているもの
公開URL を知っている人は誰でも。URL はずっと同じですWeb サイトやアプリにそのまま載せる画像、商品の写真、ブログの画像
非公開期限付きの URL を持っている人だけ請求書、予約確認書、会員だけに見せる資料
  • 公開設定は、作成した後に変えられません。変えたいときは、別のバケットを作ってください
  • 無料プランでは、公開バケットは作れません

アクセスキーを発行する

  1. アクセスキー のタブを開き、対象のバケット で、使うバケットを選ぶ
  2. アクセスキーを発行 を押し、権限を選んで 発行する を押す
    • 読み書き:アップロード・削除・一覧・表示ができます
    • 読み取り:一覧・表示・ダウンロードと、非公開ファイルの共有URL発行ができます。アップロード・完了連絡・削除はできません
  3. 表示されたキーをコピーする。キーは このとき一度だけ 表示されます。接続先 は、アクセスキー のタブの上に表示されています

キーと接続先は、アプリのサーバーの環境変数などに保存してください(例:HANAMII_STORAGE_KEY・HANAMII_STORAGE_URL)。環境変数の例とアップロード・表示の例は、画面の 使い方 のタブにあります。キーが使えるかは、下の「API の基本」のコマンドで確かめられます。

  • キーは 1 つのバケット専用です。別のバケットには使えません
  • キーをブラウザ・配布するアプリ(スマホアプリなど)・Git のリポジトリに入れないでください
  • 漏れたかもしれないときは、すぐに 無効化 して、新しいキーを発行してください。キーは複数発行できるので、新しいキーに入れ替えてから古いキーを無効化すれば、アプリを止めずに入れ替えられます。無効化しても、既に発行した署名付きのアップロードURL・非公開配信URLは期限まで使えます。即座にURLを止める操作ではありません

画面でファイルを見る

  1. ファイル のタブを開き、対象のバケット で、見たいバケットを選ぶ
  2. 一覧の 開く を押す
    • 画像(PNG・JPEG・GIF・WebP・AVIF・SVG)は、その場で表示します
    • 画像もそれ以外のファイルも、ダウンロード を押すと、元のファイル名で保存できます
  • 一覧は新しい順で、名前・サイズ・更新日を示します
  • 非公開バケットのファイルを開く URL は、1 時間だけ使えます。切れたら、閉じてもう一度 開く を押してください
  • 画面からはアップロード・削除はできません。API で行ってください

利用量を見る

利用量 のタブで、ワークスペースのすべてのバケットの合計を、プランの上限と並べて見られます。

  • 保存容量・ファイル数・バケット数を、上限に対する割合の棒で示します。1 ファイルの上限も示します
  • 今月の配信量(概算) は、今月(日本時間)にファイルが配信された量の目安です。上限ではありません
  • 保存容量・ファイル数の上限に達すると、新しいファイルはアップロードできません(プランを下げて超えた場合も同じです)。1ファイルの上限も別に適用します
  • バケット数の上限は新しいバケットの作成だけを止めます。容量・ファイル数に余裕があれば、既存バケットへのアップロードは続けられます
  • お支払いが止まっている間は、上限ではなく、新しいバケットの作成とアップロードが止まっていることを示します

バケットを削除する

  1. バケット のタブで、削除するバケットの行の 削除 を押す
  2. 確認の内容を読んで 削除する を押す
  • 削除すると一覧から消え、そのバケットのアクセスキーは使えなくなります。ファイルも、画面とアクセスキーのどちらからも使えなくなります
  • 公開 URL・共有 URL は、すぐには止まりません。約 5 分後から、ファイルの数に応じた時間をかけて止め、その後も配信のキャッシュに最大 10 分ほど残ります。止まる時刻は保証できません
  • 画面からは元に戻せません。削除から 30 日以内なら、ワークスペースのオーナーか管理者が お問い合わせ から依頼すると、ファイルのデータを受け取れます。元の URL やファイル ID には戻りません。ワークスペースを削除した後は、受け取れません
  • 削除したバケットの名前は、すぐに使い直せます
  • バケットが残っているワークスペースは削除できません。ワークスペースを削除するときは、先にバケットを削除してください

API の基本

バケットの作成・削除、アクセスキーの発行・無効化、ファイル一覧・閲覧用URL、利用量の取得は、他機能と同じ Hanamii APIトークン を使う Hanamii REST API でも操作できます。以下のファイル操作APIとは、接続先とキーが異なります。Hanamii REST APIのMarkdownには、バケット作成から公開・非公開と読み取り専用キーの確認までのcurl手順があります。

  • 接続先:オブジェクトストレージ の画面の アクセスキー のタブに表示される 接続先 の URL です(バケットを作ると表示されます)。下の例では、環境変数 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 のサーバーを通らず、保存先へ直接送られます。

  1. アップロード用の URL を取る:ファイル名・大きさ・種類・MD5 を送ります。大きさは文字列で、MD5 はファイル全体の MD5 を base64 にした値です。ファイル名と種類は 1〜255 文字で、改行は使えません。0 バイトのファイルはアップロードできません
  2. ファイルを送る:返ってきた uploadUrl へ、返ってきた headers(4 つ)をすべてそのまま付けて、ファイルの中身を PUT します。1 つでも欠けたり値が違ったりすると、断られます。uploadUrl は 15 分で使えなくなります
  3. 完了を知らせる:complete を呼ぶと、ファイルが使える状態になります

uploadUrl はファイルを送るためだけの URL で、ファイルを開くことには使えません。完了の後、ファイルは確認を経て配信用の場所へ移ります。開くときは、下の「ファイルを表示する・渡す」の方法を使ってください。

POST /v1/files/uploads の JSON 本文には、次の4項目がすべて必須です。

項目型指定する値
namestring保存するファイル名。1〜255文字で、改行は使えません
sizeBytesstringファイルのバイト数を表す10進文字列。0バイトは不可です
contentTypestringimage/jpeg などのMIMEタイプ。1〜255文字で、改行は使えません
contentMd5stringファイル全体の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が本文の長さを設定します。

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"

TypeScript(Node.js 18 以降)の例

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}` },
  })
}
  • 同じバケットに、同じ名前の有効なファイルやアップロード中のファイルがあると、新しいアップロードは断られます(409)。置き換えたいときは、先に古いファイルを削除してください
  • 完了を知らせる前のアップロードも、容量とファイル数に数えます。完了を知らせないままのものは 24 時間後に片付けられ、そのときに空きます。画面の 利用量 には、完了したファイルだけを示します
  • 送れなかった・完了できなかったアップロードは、DELETE /v1/files/{fileId} で取り消すと、同じ名前と容量の予約がすぐに空きます(上の例の cancel)
  • 完了の連絡に 409 が返ったときは、時間をおいて同じ連絡を送り直してください。まれに、最長 30 分ほどかかることがあります

ブラウザから直接アップロードする

利用者のブラウザから送りたいときも、キーはブラウザへ渡しません。

  1. アプリのサーバーで、アップロード用の URL を取る(上の 1)
  2. uploadUrl と headers だけをブラウザへ渡し、ブラウザから PUT する(上の 2)
  3. ブラウザから送り終わったら、アプリのサーバーで完了を知らせる(上の 3)。送れなかったときは、アプリのサーバーで取り消す

ファイルを表示する・渡す

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
  • 公開バケット:表示用の URL はずっと同じです。一度取って保存しておけば、そのまま使えます
  • 非公開バケット:表示用の URL は 1 時間だけ使えます。表示するたびに、アプリのサーバーで取り直してください。表示する前に、見てよい人かをアプリで確かめてください

非公開のファイルを、期限を決めて人に渡すときは、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":"..."}
  • HTML ファイルは、Web ページとして表示せず、テキストとして返します(なりすましのページに悪用されないため)

ファイルの一覧と削除

  • 一覧:GET /v1/files で、完了したファイルを新しい順に返します。items(id・name・sizeBytes・contentType・createdAt・updatedAt など)と、続きがあるときは nextCursor が返ります。1 回に 50 件まで返し、?limit= で 1〜100 件を指定できます。続きは ?cursor=<nextCursor> で取ります
  • 削除:DELETE /v1/files/{fileId} で削除します(204)。公開バケットのファイルは、配信のキャッシュのため、削除後も最大 10 分ほど見えることがあります
  • 削除したファイルは、削除から 30 日以内なら、お問い合わせからの依頼でデータを受け取れます。元の URL やファイル ID には戻りません

エラー

エラーの本文は {"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 で呼んでください。

次のガイド

カスタムドメインを設定する

hanamii

日本の空に咲くクラウド

Calm. Fast. Trusted.

プロダクト

SECURITY ACTION セキュリティ対策自己宣言 一つ星

© 2026 hanamii. All rights reserved. 🌸