upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/96.md
diff options
context:
space:
mode:
Diffstat (limited to '96.md')
-rw-r--r--96.md135
1 files changed, 84 insertions, 51 deletions
diff --git a/96.md b/96.md
index 3ebbef8..be70999 100644
--- a/96.md
+++ b/96.md
@@ -1,8 +1,6 @@
1NIP-96 1# NIP-96
2======
3 2
4HTTP File Storage Integration 3## HTTP File Storage Integration
5-----------------------------
6 4
7`draft` `optional` 5`draft` `optional`
8 6
@@ -82,55 +80,48 @@ it must use the "api_url" field instead.
82 80
83### List of Supporting File Storage Servers 81### List of Supporting File Storage Servers
84 82
85| Name | Domain | 83See https://github.com/aljazceru/awesome-nostr#nip-96-file-storage-servers.
86| ------------- | ------------------------- | 84
87| nostrcheck.me | https://nostrcheck.me | 85## Auth
88| nostrage | https://nostrage.com | 86
89| sove | https://sove.rent | 87When indicated, `clients` must add an [NIP-98](98.md) `Authorization` header (**optionally** with the encoded `payload` tag set to the base64-encoded 256-bit SHA-256 hash of the file - not the hash of the whole request body).
90| nostr.build | https://nostr.build |
91| sovbit | https://files.sovbit.host |
92| void.cat | https://void.cat |
93 88
94## Upload 89## Upload
95 90
96A file can be uploaded one at a time to `https://your-file-server.example/custom-api-path` (route from `https://your-file-server.example/.well-known/nostr/nip96.json` "api_url" field) as `multipart/form-data` content type using `POST` method with the file object set to the `file` form data field. 91`POST $api_url` as `multipart/form-data`.
97 92
98`Clients` must add an [NIP-98](98.md) `Authorization` header (**optionally** with the encoded `payload` tag set to the base64-encoded 256-bit SHA-256 hash of the file - not the hash of the whole request body). 93**AUTH required**
99If using an html form, use an `Authorization` form data field instead.
100 94
101These following **optional** form data fields MAY be used by `servers` and SHOULD be sent by `clients`: 95List of form fields:
102- `expiration`: string of the UNIX timestamp in seconds. Empty string if file should be stored forever. The server isn't required to honor this;
103- `size`: string of the file byte size. This is just a value the server can use to reject early if the file size exceeds the server limits;
104- `alt`: (recommended) strict description text for visibility-impaired users;
105- `caption`: loose description;
106- `media_type`: "avatar" or "banner". Informs the server if the file will be used as an avatar or banner. If absent, the server will interpret it as a normal upload, without special treatment;
107- `content_type`: mime type such as "image/jpeg". This is just a value the server can use to reject early if the mime type isn't supported.
108 96
97- `file`: **REQUIRED** the file to upload
98- `caption`: **RECOMMENDED** loose description;
99- `expiration`: UNIX timestamp in seconds. Empty string if file should be stored forever. The server isn't required to honor this.
100- `size`: File byte size. This is just a value the server can use to reject early if the file size exceeds the server limits.
101- `alt`: **RECOMMENDED** strict description text for visibility-impaired users.
102- `media_type`: "avatar" or "banner". Informs the server if the file will be used as an avatar or banner. If absent, the server will interpret it as a normal upload, without special treatment.
103- `content_type`: mime type such as "image/jpeg". This is just a value the server can use to reject early if the mime type isn't supported.
104- `no_transform`: "true" asks server not to transform the file and serve the uploaded file as is, may be rejected.
109 105
110Others custom form data fields may be used depending on specific `server` support. 106Others custom form data fields may be used depending on specific `server` support.
111The `server` isn't required to store any metadata sent by `clients`. 107The `server` isn't required to store any metadata sent by `clients`.
112 108
113Note for `clients`: if using an HTML form, it is important for the `file` form field to be the **last** one, or be re-ordered right before sending or be appended as the last field of XHR2's FormData object.
114
115The `filename` embedded in the file may not be honored by the `server`, which could internally store just the SHA-256 hash value as the file name, ignoring extra metadata. 109The `filename` embedded in the file may not be honored by the `server`, which could internally store just the SHA-256 hash value as the file name, ignoring extra metadata.
116The hash is enough to uniquely identify a file, that's why it will be used on the "download" and "delete" routes. 110The hash is enough to uniquely identify a file, that's why it will be used on the `download` and `delete` routes.
117
118The `server` MUST link the user's `pubkey` string (which is embedded in the decoded header value) as the owner of the file so to later allow them to delete the file.
119Note that if a file with the same hash of a previously received file (so the same file) is uploaded by another user, the server doesn't need to store the new file.
120It should just add the new user's `pubkey` to the list of the owners of the already stored file with said hash (if it wants to save space by keeping just one copy of the same file, because multiple uploads of the same file results in the same file hash).
121
122The `server` MAY also store the `Authorization` header/field value (decoded or not) for accountability purpose as this proves that the user with the unique pubkey did ask for the upload of the file with a specific hash. However, storing the pubkey is sufficient to establish ownership.
123 111
124The `server` MUST reject with 413 Payload Too Large if file size exceeds limits. 112The `server` MUST link the user's `pubkey` string as the owner of the file so to later allow them to delete the file.
125 113
126The `server` MUST reject with 400 Bad Request status if some fields are invalid. 114`no_transform` can be used to replicate a file to multiple servers for redundancy, clients can use the [server list](#selecting-a-server) to find alternative servers which might contain the same file. When uploading a file and requesting `no_transform` clients should check that the hash matches in the response in order to detect if the file was modified.
127 115
128The `server` MUST reply to the upload with 200 OK status if the `payload` tag value contains an already used SHA-256 hash (if file is already owned by the same pubkey) or reject the upload with 403 Forbidden status if it isn't the same of the received file. 116### Response codes
129 117
130The `server` MAY reject the upload with 402 Payment Required status if the user has a pending payment (Payment flow is not strictly required. Server owners decide if the storage is free or not. Monetization schemes may be added later to correlated NIPs.). 118- `200 OK`: File upload exists, but is successful (Existing hash)
131 119- `201 Created`: File upload successful (New hash)
132On successful uploads the `server` MUST reply with **201 Created** HTTP status code or **202 Accepted** if a `processing_url` field is added 120- `202 Accepted`: File upload is awaiting processing, see [Delayed Processing](#delayed-processing) section
133to the response so that the `client` can follow the processing status (see [Delayed Processing](#delayed-processing) section). 121- `413 Payload Too Large`: File size exceeds limit
122- `400 Bad Request`: Form data is invalid or not supported.
123- `403 Forbidden`: User is not allowed to upload or the uploaded file hash didnt match the hash included in the `Authorization` header `payload` tag.
124- `402 Payment Required`: Payment is required by the server, **this flow is undefined**.
134 125
135The upload response is a json object as follows: 126The upload response is a json object as follows:
136 127
@@ -186,11 +177,13 @@ The upload response is a json object as follows:
186 177
187Note that if the server didn't apply any transformation to the received file, both `nip94_event.tags.*.ox` and `nip94_event.tags.*.x` fields will have the same value. The server MUST link the saved file to the SHA-256 hash of the **original** file before any server transformations (the `nip94_event.tags.*.ox` tag value). The **original** file's SHA-256 hash will be used to identify the saved file when downloading or deleting it. 178Note that if the server didn't apply any transformation to the received file, both `nip94_event.tags.*.ox` and `nip94_event.tags.*.x` fields will have the same value. The server MUST link the saved file to the SHA-256 hash of the **original** file before any server transformations (the `nip94_event.tags.*.ox` tag value). The **original** file's SHA-256 hash will be used to identify the saved file when downloading or deleting it.
188 179
189`Clients` may upload the same file to one or many `servers`. 180`clients` may upload the same file to one or many `servers`.
190After successful upload, the `client` may optionally generate and send to any set of nostr `relays` a [NIP-94](94.md) event by including the missing fields. 181After successful upload, the `client` may optionally generate and send to any set of nostr `relays` a [NIP-94](94.md) event by including the missing fields.
191 182
192Alternatively, instead of using NIP-94, the `client` can share or embed on a nostr note just the above url. 183Alternatively, instead of using NIP-94, the `client` can share or embed on a nostr note just the above url.
193 184
185`clients` may also use the tags from the `nip94_event` to construct an `imeta` tag
186
194### Delayed Processing 187### Delayed Processing
195 188
196Sometimes the server may want to place the uploaded file in a processing queue for deferred file processing. 189Sometimes the server may want to place the uploaded file in a processing queue for deferred file processing.
@@ -226,7 +219,7 @@ However, for all file actions, such as download and deletion, the **original** f
226 219
227## Download 220## Download
228 221
229`Servers` must make available the route `https://your-file-server.example/custom-api-path/<sha256-file-hash>(.ext)` (route taken from `https://your-file-server.example/.well-known/nostr/nip96.json` "api_url" or "download_url" field) with `GET` method for file download. 222`GET $api_url/<sha256-hash>(.ext)`
230 223
231The primary file download url informed at the upload's response field `nip94_event.tags.*.url` 224The primary file download url informed at the upload's response field `nip94_event.tags.*.url`
232can be that or not (it can be any non-standard url the server wants). 225can be that or not (it can be any non-standard url the server wants).
@@ -234,17 +227,17 @@ If not, the server still MUST also respond to downloads at the standard url
234mentioned on the previous paragraph, to make it possible for a client 227mentioned on the previous paragraph, to make it possible for a client
235to try downloading a file on any NIP-96 compatible server by knowing just the SHA-256 file hash. 228to try downloading a file on any NIP-96 compatible server by knowing just the SHA-256 file hash.
236 229
237Note that the "\<sha256-file-hash\>" part is from the **original** file, **not** from the **transformed** file if the uploaded file went through any server transformation. 230Note that the "\<sha256-hash\>" part is from the **original** file, **not** from the **transformed** file if the uploaded file went through any server transformation.
238 231
239Supporting ".ext", meaning "file extension", is required for `servers`. It is optional, although recommended, for `clients` to append it to the path. 232Supporting ".ext", meaning "file extension", is required for `servers`. It is optional, although recommended, for `clients` to append it to the path.
240When present it may be used by `servers` to know which `Content-Type` header to send (e.g.: "Content-Type": "image/png" for ".png" extension). 233When present it may be used by `servers` to know which `Content-Type` header to send (e.g.: "Content-Type": "image/png" for ".png" extension).
241The file extension may be absent because the hash is the only needed string to uniquely identify a file. 234The file extension may be absent because the hash is the only needed string to uniquely identify a file.
242 235
243Example: `https://your-file-server.example/custom-api-path/719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b.png` 236Example: `$api_url/719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b.png`
244 237
245### Media Transformations 238### Media Transformations
246 239
247`Servers` may respond to some media transformation query parameters and ignore those they don't support by serving 240`servers` may respond to some media transformation query parameters and ignore those they don't support by serving
248the original media file without transformations. 241the original media file without transformations.
249 242
250#### Image Transformations 243#### Image Transformations
@@ -252,23 +245,23 @@ the original media file without transformations.
252##### Resizing 245##### Resizing
253 246
254Upon upload, `servers` may create resized image variants, such as thumbnails, respecting the original aspect ratio. 247Upon upload, `servers` may create resized image variants, such as thumbnails, respecting the original aspect ratio.
255`Clients` may use the `w` query parameter to request an image version with the desired pixel width. 248`clients` may use the `w` query parameter to request an image version with the desired pixel width.
256`Servers` can then serve the variant with the closest width to the parameter value 249`servers` can then serve the variant with the closest width to the parameter value
257or an image variant generated on the fly. 250or an image variant generated on the fly.
258 251
259Example: `https://your-file-server.example/custom-api-path/<sha256-file-hash>.png?w=32` 252Example: `$api_url/<sha256-hash>.png?w=32`
260 253
261## Deletion 254## Deletion
262 255
263`Servers` must make available the route `https://deletion.domain/deletion-path/<sha256-file-hash>(.ext)` (route taken from `https://your-file-server.example/.well-known/nostr/nip96.json` "api_url" field) with `DELETE` method for file deletion. 256`DELETE $api_url/<sha256-hash>(.ext)`
264 257
265Note that the "\<sha256-file-hash\>" part is from the **original** file, **not** from the **transformed** file if the uploaded file went through any server transformation. 258**AUTH required**
266 259
267The extension is optional as the file hash is the only needed file identification. 260Note that the `/<sha256-hash>` part is from the **original** file, **not** from the **transformed** file if the uploaded file went through any server transformation.
268 261
269`Clients` should send a `DELETE` request to the server deletion route in the above format. It must include a NIP-98 `Authorization` header. 262The extension is optional as the file hash is the only needed file identification.
270 263
271The `server` should reject deletes from users other than the original uploader. The `pubkey` encoded on the header value identifies the user. 264The `server` should reject deletes from users other than the original uploader with the appropriate http response code (403 Forbidden).
272 265
273It should be noted that more than one user may have uploaded the same file (with the same hash). In this case, a delete must not really delete the file but just remove the user's `pubkey` from the file owners list (considering the server keeps just one copy of the same file, because multiple uploads of the same file results 266It should be noted that more than one user may have uploaded the same file (with the same hash). In this case, a delete must not really delete the file but just remove the user's `pubkey` from the file owners list (considering the server keeps just one copy of the same file, because multiple uploads of the same file results
274in the same file hash). 267in the same file hash).
@@ -282,6 +275,46 @@ The successful response is a 200 OK one with just basic JSON fields:
282} 275}
283``` 276```
284 277
278## Listing files
279
280`GET $api_url?page=x&count=y`
281
282**AUTH required**
283
284Returns a list of files linked to the authenticated users pubkey.
285
286Example Response:
287
288```js
289{
290 "count": 1, // server page size, eg. max(1, min(server_max_page_size, arg_count))
291 "total": 1, // total number of files
292 "page": 0, // the current page number
293 "files": [
294 {
295 "tags": [
296 ["ox": "719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b"],
297 ["x": "5d2899290e0e69bcd809949ee516a4a1597205390878f780c098707a7f18e3df"],
298 ["size", "123456"],
299 ["alt", "a meme that makes you laugh"],
300 ["expiration", "1715691139"],
301 // ...other metadata
302 ]
303 "content": "haha funny meme", // caption
304 "created_at": 1715691130 // upload timestamp
305 },
306 ...
307 ]
308}
309```
310
311`files` contains an array of NIP-94 events
312
313### Query args
314
315- `page` page number (`offset=page*count`)
316- `count` number of items per page
317
285## Selecting a Server 318## Selecting a Server
286 319
287Note: HTTP File Storage Server developers may skip this section. This is meant for client developers. 320Note: HTTP File Storage Server developers may skip this section. This is meant for client developers.