NAP-BLOSSOM / PR #71
Low-Level Blossom Blob Access
Full specification from the fetched upstream revision ↗. The document below is rendered from its original Markdown. Follow the latest source on GitHub ↗.
NAP-BLOSSOM
Low-Level Blossom Blob Access
draft
NAP ID: NAP-BLOSSOM
Domain: blossom
Depends:
identity-- capability · optional -- authenticated Blossom requests and BUD-03 server-list mutations use the current shell-user identity.relay-- capability · optional -- BUD-03 server-list helpers read and publish kind10063events through the runtime relay surface.relay-- wire · optional -- importsRelayEventResultfor BUD-03 server-list event returns.resource-- wire · optional -- importedRelayEventResult.sidecar.resources?carriesResourceSidecarEntry[], a type owned by theresourcedomain. Web binding (NIP-5D):window.napplet.blossom; domain presence signals availability.
Description
NAP-BLOSSOM gives napplets a low-level, shell-mediated interface to Blossom blob servers. A napplet names the server, blob hash, bytes, and endpoint intent. The shell performs the HTTP request, applies runtime network policy, constructs and signs any required Blossom authorization token, and returns endpoint-shaped results.
This is not a generic network escape hatch. It only covers Blossom server endpoints and Blossom URI helpers. NAP-RESOURCE remains the generic byte-fetch surface. NAP-UPLOAD remains the high-level "store these bytes somewhere" surface where the shell chooses NIP-96, Blossom, or another rail.
API Surface
| Operation | Parameters | Result | Wire |
|---|---|---|---|
check |
request (BlossomBlobCheckRequest) |
BlossomBlobCheckResult |
blossom.check / blossom.check.result |
get |
request (BlossomGetRequest) |
BlossomBytesResult |
blossom.get / blossom.get.result |
checkUpload |
request (BlossomCheckRequest) |
BlossomCheckResult |
blossom.checkUpload / blossom.checkUpload.result |
upload |
request (BlossomUploadRequest) |
BlossomDescriptorResult |
blossom.upload / blossom.upload.result |
mirror |
request (BlossomMirrorRequest) |
BlossomDescriptorResult |
blossom.mirror / blossom.mirror.result |
checkMedia |
request (BlossomCheckRequest) |
BlossomCheckResult |
blossom.checkMedia / blossom.checkMedia.result |
media |
request (BlossomMediaRequest) |
BlossomDescriptorResult |
blossom.media / blossom.media.result |
list |
request (BlossomListRequest) |
BlossomListResult |
blossom.list / blossom.list.result |
delete |
request (BlossomDeleteRequest) |
BlossomDeleteResult |
blossom.delete / blossom.delete.result |
servers |
none | BlossomServerListResult |
blossom.servers / blossom.servers.result |
addServer |
request (BlossomAddServerRequest) |
BlossomServerListResult |
blossom.addServer / blossom.addServer.result |
removeServer |
server (ServerUrl) |
BlossomServerListResult |
blossom.removeServer / blossom.removeServer.result |
reorderServers |
servers (list of ServerUrl) |
BlossomServerListResult |
blossom.reorderServers / blossom.reorderServers.result |
parseUri |
uri (tstr) |
BlossomUri |
local helper; no wire message |
formatUri |
uri (BlossomUri) |
tstr |
local helper; no wire message |
Schemas
RelayEventResult is owned by NAP-RELAY. NAP-BLOSSOM imports it by name for
kind 10063 server-list events returned by the server-list helpers.
Primitive aliases and enums:
| Name | Definition |
|---|---|
HexSha256 |
SHA-256 digest encoded as lowercase hex |
HexPubkey |
32-byte public key encoded as lowercase hex |
ServerUrl |
Blossom server URL |
BlobExtension |
blob filename extension text |
MimeType |
MIME type text |
ErrorCode |
machine-readable error text |
NostrTag |
list of text tag fields |
RelayEventResult |
external type owned by NAP-RELAY |
BlossomAuthMode |
auto, required, none |
Request records:
| Record | Required fields | Optional fields |
|---|---|---|
BlossomBlobCheckRequest |
server: ServerUrl, sha256: HexSha256 |
extension: BlobExtension, auth: BlossomAuthMode, purpose: tstr |
BlossomGetRequest |
server: ServerUrl, sha256: HexSha256 |
extension: BlobExtension, range: tstr, auth: BlossomAuthMode, purpose: tstr |
BlossomCheckRequest |
server: ServerUrl, sha256: HexSha256, size: uint, type: MimeType |
auth: BlossomAuthMode, purpose: tstr |
BlossomUploadRequest |
server: ServerUrl, data: bstr |
sha256: HexSha256, type: MimeType, size: uint, auth: BlossomAuthMode, purpose: tstr |
BlossomMirrorRequest |
server: ServerUrl, url: tstr |
sha256: HexSha256, auth: BlossomAuthMode, purpose: tstr |
BlossomMediaRequest |
server: ServerUrl, data: bstr |
sha256: HexSha256, type: MimeType, size: uint, auth: BlossomAuthMode, purpose: tstr |
BlossomListRequest |
server: ServerUrl, pubkey: HexPubkey |
cursor: HexSha256, limit: uint, auth: BlossomAuthMode, purpose: tstr |
BlossomDeleteRequest |
server: ServerUrl, sha256: HexSha256 |
extension: BlobExtension, auth: BlossomAuthMode, purpose: tstr |
When auth is omitted, shells use auto. purpose is shell-owned diagnostic
prompt text only.
BlossomHeaders fields:
| Field | Required | Type |
|---|---|---|
contentType |
no | MimeType |
contentLength |
no | unsigned integer |
acceptRanges |
no | text |
sunset |
no | text |
reason |
no | text |
BlossomDescriptor fields:
| Field | Required | Type |
|---|---|---|
url |
yes | text |
sha256 |
yes | HexSha256 |
size |
yes | unsigned integer |
type |
yes | MimeType |
uploaded |
yes | unsigned integer |
nip94 |
no | list of NostrTag |
| additional fields | no | any |
Result records:
| Record | Required fields | Optional fields |
|---|---|---|
BlossomBlobCheckResult |
ok: bool, status: uint, headers: BlossomHeaders |
error: ErrorCode |
BlossomBytesResult |
ok: bool, status: uint |
blob: bstr, headers: BlossomHeaders, error: ErrorCode |
BlossomCheckResult |
ok: bool, status: uint |
headers: BlossomHeaders, error: ErrorCode |
BlossomDescriptorResult |
ok: bool, status: uint |
descriptor: BlossomDescriptor, error: ErrorCode |
BlossomListResult |
ok: bool, status: uint, blobs: list of BlossomDescriptor |
cursor: HexSha256, error: ErrorCode |
BlossomDeleteResult |
ok: bool, status: uint |
error: ErrorCode |
BlossomAddServerRequest fields:
| Field | Required | Type |
|---|---|---|
server |
yes | ServerUrl |
position |
no | unsigned integer |
BlossomServerListResult fields:
| Field | Required | Type |
|---|---|---|
ok |
yes | boolean |
servers |
yes | list of ServerUrl |
eventId |
no | text |
result |
no | RelayEventResult |
error |
no | ErrorCode |
BlossomUri fields:
| Field | Required | Type |
|---|---|---|
sha256 |
yes | HexSha256 |
extension |
yes | BlobExtension |
servers |
yes | list of ServerUrl |
authors |
yes | list of HexPubkey |
size |
no | unsigned integer |
check(request) -- Checks whether server has the blob identified by
sha256, using the BUD-01 HEAD /<sha256> endpoint under the hood. Returns
status and safe metadata headers. It never returns bytes.
get(request) -- Performs BUD-01 GET /<sha256>. The shell MUST verify
that returned bytes hash to request.sha256 before delivering blob. When
range is provided, the result MAY be a partial byte range; the shell MUST NOT
claim whole-blob verification for partial content.
checkUpload(request) -- Performs BUD-06 HEAD /upload using
X-SHA-256, X-Content-Type, and X-Content-Length. The result is advisory.
A later upload can still fail.
upload(request) -- Performs BUD-02 PUT /upload. The shell sends the
exact supplied bytes. It MUST NOT transform them. When sha256 is supplied, the
shell MUST verify it against data before sending and use it for preflight or
authorization scoping.
mirror(request) -- Performs BUD-04 PUT /mirror. The shell submits the
remote URL to the target server. When sha256 is supplied, the shell MUST scope
authorization to that hash and reject a returned descriptor with a different
hash.
checkMedia(request) -- Performs BUD-05 HEAD /media. The result is
advisory. A later media can still fail.
media(request) -- Performs BUD-05 PUT /media. The Blossom server may
optimize or transform media. The returned descriptor describes the stored result,
not necessarily the original bytes.
list(request) -- Performs BUD-12 GET /list/<pubkey>. This endpoint is
optional and unrecommended by Blossom. Shells MUST expose server failure as a
normal result, not as proof that the user has no blobs.
delete(request) -- Performs BUD-12 DELETE /<sha256>. A delete request
acts on exactly one hash. Multiple authorization x tags, if the shell creates
them, MUST NOT be treated as a multi-delete request.
servers() -- Returns the shell-user's ordered BUD-03 server list from the
latest kind 10063 event. When the source event is returned, it is returned as
RelayEventResult. Returns an empty list when no list exists. This is a
Blossom-specific helper for the user's preferred upload/retrieval servers, not a
generic list editor.
addServer(request) -- Adds server to the shell-user's BUD-03 list and
publishes a replacement kind 10063 event. If server already exists, the
shell MAY treat the request as idempotent or move it to position when supplied.
The server URL MUST include http:// or https://, matching BUD-03.
removeServer(server) -- Removes server from the shell-user's BUD-03 list
and publishes the replacement kind 10063 event. Removing an absent server is
idempotent. Removing the final server MUST be rejected with
empty-server-list, because BUD-03 events require at least one server tag.
reorderServers(servers) -- Replaces the ordering of the shell-user's BUD-03
list. The supplied list MUST contain the same server set as the current list;
callers use addServer or removeServer to change membership. Empty lists MUST
be rejected with empty-server-list.
parseUri(uri) -- Parses a BUD-10 blossom: URI into hash, extension,
server hints, author hints, and size. It performs no network I/O.
formatUri(uri) -- Produces a BUD-10 blossom: URI. It MUST include the
hash and extension, defaulting the extension to .bin when no better extension
is known.
Wire Protocol
blossom.* messages use the NIP-5D wire format
({ "type": "domain.action", ...payload }).
| Type | Direction | Payload fields |
|---|---|---|
blossom.check |
napplet -> shell | id, request |
blossom.check.result |
shell -> napplet | id, result, error? |
blossom.get |
napplet -> shell | id, request |
blossom.get.result |
shell -> napplet | id, result, error? |
blossom.checkUpload |
napplet -> shell | id, request |
blossom.checkUpload.result |
shell -> napplet | id, result, error? |
blossom.upload |
napplet -> shell | id, request |
blossom.upload.result |
shell -> napplet | id, result, error? |
blossom.mirror |
napplet -> shell | id, request |
blossom.mirror.result |
shell -> napplet | id, result, error? |
blossom.checkMedia |
napplet -> shell | id, request |
blossom.checkMedia.result |
shell -> napplet | id, result, error? |
blossom.media |
napplet -> shell | id, request |
blossom.media.result |
shell -> napplet | id, result, error? |
blossom.list |
napplet -> shell | id, request |
blossom.list.result |
shell -> napplet | id, result, error? |
blossom.delete |
napplet -> shell | id, request |
blossom.delete.result |
shell -> napplet | id, result, error? |
blossom.servers |
napplet -> shell | id |
blossom.servers.result |
shell -> napplet | id, result, error? |
blossom.addServer |
napplet -> shell | id, request |
blossom.addServer.result |
shell -> napplet | id, result, error? |
blossom.removeServer |
napplet -> shell | id, server |
blossom.removeServer.result |
shell -> napplet | id, result, error? |
blossom.reorderServers |
napplet -> shell | id, servers |
blossom.reorderServers.result |
shell -> napplet | id, result, error? |
Key design notes:
- Request/result pairs use
idfor correlation. - Binary
dataandblobfields cross the boundary as projection-native byte objects. In the web projection that meansBloborArrayBuffer, not base64-encoded strings. - Result
statusis the upstream HTTP status after shell policy checks. Shell policy failures MAY usestatus: 0with a stableerror. headers.reasoncarries BUDX-Reasondiagnostics only. Napplets MUST NOT branch on it.- BUD-03 server-list helpers are shell-owned read/replace mutations over kind
10063. Returned server-list events useRelayEventResultso resource sidecars and relay hints follow the same shape as other raw event returns. Runtimes MAY implement them using the same internal machinery as a generic list API, but NAP-BLOSSOM exposes the Blossom-specific verbs directly. - Local helpers (
parseUri,formatUri) do not send wire messages.
Examples
Check whether a server has a blob:
-> { "type": "blossom.check", "id": "c1",
"request": { "server": "https://cdn.example.com", "sha256": "b167...", "extension": ".pdf" } }
<- { "type": "blossom.check.result", "id": "c1",
"result": { "ok": true, "status": 200, "headers": { "contentType": "application/pdf", "contentLength": 184292 } } }
Check, then upload exact bytes:
-> { "type": "blossom.checkUpload", "id": "b1",
"request": { "server": "https://cdn.example.com", "sha256": "b167...", "size": 184292, "type": "application/pdf" } }
<- { "type": "blossom.checkUpload.result", "id": "b1",
"result": { "ok": true, "status": 200, "headers": {} } }
-> { "type": "blossom.upload", "id": "b2",
"request": { "server": "https://cdn.example.com", "data": "<bytes>", "sha256": "b167...", "type": "application/pdf", "size": 184292 } }
<- { "type": "blossom.upload.result", "id": "b2",
"result": { "ok": true, "status": 201, "descriptor": { "url": "https://cdn.example.com/b167....pdf", "sha256": "b167...", "size": 184292, "type": "application/pdf", "uploaded": 1725105921 } } }
Add and reorder preferred servers:
-> { "type": "blossom.addServer", "id": "s1",
"request": { "server": "https://cdn.example.com", "position": 0 } }
<- { "type": "blossom.addServer.result", "id": "s1",
"result": { "ok": true, "servers": ["https://cdn.example.com"], "eventId": "ev1..." } }
-> { "type": "blossom.addServer", "id": "s2",
"request": { "server": "https://cdn2.example.com", "position": 1 } }
<- { "type": "blossom.addServer.result", "id": "s2",
"result": { "ok": true, "servers": ["https://cdn.example.com", "https://cdn2.example.com"], "eventId": "ev2..." } }
-> { "type": "blossom.reorderServers", "id": "s3",
"servers": ["https://cdn2.example.com", "https://cdn.example.com"] }
<- { "type": "blossom.reorderServers.result", "id": "s3",
"result": { "ok": true, "servers": ["https://cdn2.example.com", "https://cdn.example.com"], "eventId": "ev3..." } }
Retrieve by hash:
-> { "type": "blossom.get", "id": "b3",
"request": { "server": "https://cdn.example.com", "sha256": "b167...", "extension": ".pdf" } }
<- { "type": "blossom.get.result", "id": "b3",
"result": { "ok": true, "status": 200, "blob": "<bytes>", "headers": { "contentType": "application/pdf", "contentLength": 184292 } } }
Error Handling
All result messages use result.ok: false plus result.error for programmatic
failure. The top-level error? field is reserved for malformed envelopes where
no typed result can be constructed.
Common error codes: invalid-request, invalid-server, invalid-hash,
not-signed-in, user-denied, blocked-by-policy, not-found,
auth-required, payment-required, too-large, unsupported-media-type,
hash-mismatch, server-list-unavailable, invalid-server-list,
empty-server-list, network-error, timeout, server-error, unsupported.
Shell Behavior
- The shell MUST treat
server,url, hashes, MIME strings, URI hints, and descriptors as untrusted. - The shell MUST respond to every request with a result message carrying the
same
id. - The shell MUST restrict requests to Blossom endpoint shapes. It MUST NOT turn this interface into arbitrary HTTP method, path, or header access.
- The shell MUST apply its normal network policy before connecting to any Blossom server or mirror origin.
- The shell MUST perform all BUD-11 authorization token construction, signing, encoding, and header attachment. It MUST NOT expose authorization tokens or signing keys to the napplet.
- The shell MUST scope authorization tokens with
servertags derived from the lowercase Blossom server domain, not from a full URL. - The shell MUST scope authorization tokens with
xtags whenever the target endpoint implies a hash. - The shell MUST reject
auth: "required"requests and server-authenticated operations withnot-signed-inwhen no shell-user signer is connected. - The shell MUST publish BUD-03 server-list mutations as kind
10063events with emptycontentand orderedservertags. - The shell MUST reject BUD-03 server-list mutation requests that would publish
zero
servertags. - The shell MUST preserve the caller-visible BUD-03 server ordering exactly in
servers,addServer,removeServer, andreorderServersresults. - The shell SHOULD merge observed BUD-03 relay URLs into
RelayEventResult.sidecar.relayHintswhen it can disclose them. - The shell MUST reject server-list mutation requests with
not-signed-inwhen no shell-user signer is connected. - The shell MAY prompt, deny, rate-limit, cache, redact diagnostics, or route by napplet policy.
- The shell MAY try unsigned requests for public endpoints when
authis"auto"and no shell-user signer is connected. - The shell MAY send unsigned requests when
auth: "none"is supplied, except when runtime policy requires user consent.
Security Considerations
- Blossom server access is a network boundary. Shells MUST enforce SSRF protections, redirect limits, size limits, and per-napplet rate limits before delivering results.
- BUD-11 tokens are bearer credentials for their lifetime. Shells SHOULD keep
expirations short, scope tokens to exact server domains, and scope hash-based
operations with
xtags. mirrorasks a server to fetch a remote URL. Shells SHOULD apply policy to the origin URL before forwarding it and SHOULD make the destination server visible in consent UI.mediamay transform bytes. Napplets that require exact byte preservation MUST useupload, notmedia.listcan reveal a user's stored blobs. Shells SHOULD require explicit consent when listing a pubkey that belongs to the shell-user.- BUD-03 server lists are user preference and routing state. Shells SHOULD show the requesting napplet, server URL, and resulting order before publishing a mutation.
- Successful upload, mirror, delete, or media results prove only what the Blossom server reported. They do not prove future availability or deletion across other servers.
References
- Blossom
- BUD-01: Server requirements and blob retrieval
- BUD-02: Blob upload
- BUD-03: User Server List
- BUD-04: Mirroring blobs
- BUD-05: Media optimization endpoints
- BUD-06: Upload requirements
- BUD-10: Blossom URI Schema
- BUD-11: Nostr Authorization
- BUD-12: Blob management endpoints
Implementations
- (none yet)
Changelog
03ea0db- Introduced NAP-BLOSSOM as a constrained runtime capability for Blossom blob access.b6c1737- Added runtime-owned Blossom server-list operations and ownership rules.d48052f- Changed Blossom server-list events to use relay-owned result shape and declared the relay dependency.e92fb0b- Adopted injected-domain availability and linked the current upstream web binding.