blosstr docs

blosstr is a Blossom media server. Nostr clients upload to it with a signed event; apps can also use an API key from the dashboard. Every file is stored under its SHA-256 and served at https://blosstr.com/<sha256>.<ext>.

Quick start

  1. Get a plan for your pubkey on the pricing page.
  2. In your nostr client's media server settings, add https://blosstr.com.
  3. Attach a picture to a note. That's it.

From a script with an API key:

curl -X PUT https://blosstr.com/upload \
  -H "Authorization: Bearer blosstr_…" \
  -H "Content-Type: image/jpeg" \
  --data-binary @photo.jpg

The response is a Blossom blob descriptor:

{
  "url": "https://blosstr.com/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.jpg",
  "sha256": "b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553",
  "size": 184292,
  "type": "image/jpeg",
  "uploaded": 1793000000,
  "nip94": [["url", "…"], ["m", "image/jpeg"], ["x", "…"], ["size", "184292"]]
}

Blossom endpoints

RequestWhat it does
GET /<sha256>[.ext]The file. Supports Range requests. No auth. (BUD-01)
HEAD /<sha256>Whether the file exists, with its size and type.
PUT /uploadStore the raw request body. Send X-SHA-256 to have the hash checked. Images are stripped of EXIF/GPS. (BUD-02)
HEAD /uploadPreflight: send X-Content-Length, X-Content-Type and X-SHA-256 to find out whether an upload would be accepted before sending it. (BUD-06)
PUT /mediaSame as upload; images are always processed. (BUD-05)
PUT /mirror{"url": "https://…"}: blosstr fetches the file and stores it for you, up to 100 MB. (BUD-04)
GET /list/<pubkey>The pubkey's blobs, newest first. ?cursor=<sha256>&limit=, ?since/?until. Needs a signature from that pubkey. (BUD-12)
DELETE /<sha256>Delete a file you uploaded.

Authentication

Every write, list and delete needs an Authorization header. Reads never do.

Signed events (nostr clients)

Authorization: Nostr <base64 event>, where the event is kind 24242 with a t tag for the verb (upload, list, delete, media), an expiration tag, and for uploads and deletes an x tag with the blob's hash (BUD-11). Any Blossom client library does this for you.

API keys (your backend)

Authorization: Bearer blosstr_…. A key acts as the pubkey that made it, with that pubkey's plan and storage. No signing needed.

API keys

Log in and open Dashboard → API keys to make one. A key is shown once; we only store its hash. Keys work only while the plan is active, and you can revoke one at any time.

Keys belong on a server. Don't put one in a web page or a mobile app: anyone who can read it can upload and delete as you.

The /v1 API

JSON endpoints for apps, all authenticated with an API key (a signed kind 24242 event works too).

RequestReturns
GET /v1/accountPlan, expiry, storage quota, largest file allowed, files and bytes used.
GET /v1/files{files, next_cursor}, newest first. ?limit=1..500, ?cursor, ?type=image|video|audio|other.
GET /v1/files/<sha256>One file, with download count and where it was mirrored from.
DELETE /v1/files/<sha256>Deletes it.
GET /v1/usage?days=30Uploads and bytes per UTC day.
const res = await fetch("https://blosstr.com/v1/files?type=image&limit=50", {
  headers: { Authorization: `Bearer ${process.env.BLOSSTR_KEY}` },
});
const { files, next_cursor } = await res.json();

Large files

A single request can carry up to about 100 MB. For bigger files (up to 1 GB), upload in chunks. The finished file is checked against the hash you give before it's kept.

POST /multipart/initiate   {"filename", "contentType", "fileSize"}
  → {"sessionId", "chunkSize", "totalChunks"}
POST /multipart/<id>/chunk?partNumber=0   (raw bytes, exactly chunkSize except the last)
…
POST /multipart/<id>/complete   {"finalHash": "<sha256 of the whole file>"}
  → blob descriptor
GET    /multipart/<id>   progress
DELETE /multipart/<id>   abort

Every multipart request needs the same authorization as an upload, from the pubkey that started the session.

Errors

Failures use normal HTTP status codes. The reason is in the X-Reason header (as Blossom requires) and in the body as {"error": "…"}.

  • 401: missing, expired or invalid authorization.
  • 402/403: no active plan for this pubkey, or not your file.
  • 413: the file is bigger than your plan allows, or would go over your storage.
  • 416: a Range outside the file.

Plans & payments

PlanStoragePer filePrice
basic20 GB1 GB$240 / year
creator100 GB1 GB$360 / year
advanced500 GB1 GB$600 / year

Plans are paid in sats over lightning at the current BTC/USD rate. Paying again adds a year to the time left and switches to the plan just paid for. Your app can sell plans for its users with these public endpoints:

GET  https://blosstr.com/api/plans
  → {"plans": [{"id", "name", "yearly_usd", "yearly_sats", "storage_bytes", "max_file_size"}]}

POST https://blosstr.com/api/invoice   {"pubkey": "npub… or hex", "plan": "creator"}
  → {"paymentHash", "bolt11", "amountSats", "amountUsd", "expiresAt", "paid": false}

GET  https://blosstr.com/api/invoice/<paymentHash>
  → same shape; "paid": true once settled (checked with our wallet)

GET  https://blosstr.com/api/subscription/<npub or hex>
  → {"active", "plan", "expiresAt", "subscribed"}

Show the bolt11 to the user (QR code or WebLN), then poll the invoice every few seconds. Payments are credited even if the user closes the page.

Limits and rules

  • Files up to 1 GB; mirrors up to 100 MB.
  • Storage is counted per pubkey. Deleting a file frees its space.
  • When a plan expires, files stay up but new uploads are refused until it's renewed.
  • No illegal content. See the terms.