upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--01.md2
-rw-r--r--15.md78
-rw-r--r--44.md4
-rw-r--r--46.md2
-rw-r--r--50.md2
-rw-r--r--51.md1
-rw-r--r--53.md2
-rw-r--r--58.md2
-rw-r--r--94.md2
-rw-r--r--96.md302
-rw-r--r--README.md1
11 files changed, 390 insertions, 8 deletions
diff --git a/01.md b/01.md
index ecac6f6..80ee792 100644
--- a/01.md
+++ b/01.md
@@ -116,7 +116,7 @@ Clients can send 3 types of messages, which must be JSON arrays, according to th
116 * `["REQ", <subscription_id>, <filters1>, <filters2>, ...]`, used to request events and subscribe to new updates. 116 * `["REQ", <subscription_id>, <filters1>, <filters2>, ...]`, used to request events and subscribe to new updates.
117 * `["CLOSE", <subscription_id>]`, used to stop previous subscriptions. 117 * `["CLOSE", <subscription_id>]`, used to stop previous subscriptions.
118 118
119`<subscription_id>` is an arbitrary, non-empty string of max length 64 chars. It represents a subscription per connection. Relays MUST manage `<subscription_id>`s independently for each WebSocket connection. `<subscription_id>`s are not guarantueed to be globally unique. 119`<subscription_id>` is an arbitrary, non-empty string of max length 64 chars. It represents a subscription per connection. Relays MUST manage `<subscription_id>`s independently for each WebSocket connection. `<subscription_id>`s are not guaranteed to be globally unique.
120 120
121`<filtersX>` is a JSON object that determines what events will be sent in that subscription, it can have the following attributes: 121`<filtersX>` is a JSON object that determines what events will be sent in that subscription, it can have the following attributes:
122 122
diff --git a/15.md b/15.md
index 1c3154a..e2ba639 100644
--- a/15.md
+++ b/15.md
@@ -149,7 +149,6 @@ The `merchant` and the `customer` can exchange JSON messages that represent diff
149| 1 | Merchant | Payment Request | 149| 1 | Merchant | Payment Request |
150| 2 | Merchant | Order Status Update | 150| 2 | Merchant | Order Status Update |
151 151
152
153### Step 1: `customer` order (event) 152### Step 1: `customer` order (event)
154The below json goes in content of [NIP04](https://github.com/nostr-protocol/nips/blob/master/04.md). 153The below json goes in content of [NIP04](https://github.com/nostr-protocol/nips/blob/master/04.md).
155 154
@@ -229,7 +228,9 @@ The below json goes in `content` of [NIP04](https://github.com/nostr-protocol/ni
229 "shipped": <bool: has been shipped>, 228 "shipped": <bool: has been shipped>,
230} 229}
231``` 230```
231
232## Customize Marketplace 232## Customize Marketplace
233
233Create a customized user experience using the `naddr` from [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md#shareable-identifiers-with-extra-metadata). The use of `naddr` enables easy sharing of marketplace events while incorporating a rich set of metadata. This metadata can include relays, merchant profiles, and more. Subsequently, it allows merchants to be grouped into a market, empowering the market creator to configure the marketplace's user interface and user experience, and share that marketplace. This customization can encompass elements such as market name, description, logo, banner, themes, and even color schemes, offering a tailored and unique marketplace experience. 234Create a customized user experience using the `naddr` from [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md#shareable-identifiers-with-extra-metadata). The use of `naddr` enables easy sharing of marketplace events while incorporating a rich set of metadata. This metadata can include relays, merchant profiles, and more. Subsequently, it allows merchants to be grouped into a market, empowering the market creator to configure the marketplace's user interface and user experience, and share that marketplace. This customization can encompass elements such as market name, description, logo, banner, themes, and even color schemes, offering a tailored and unique marketplace experience.
234 235
235### Event `30019`: Create or update marketplace UI/UX 236### Event `30019`: Create or update marketplace UI/UX
@@ -253,6 +254,81 @@ Create a customized user experience using the `naddr` from [NIP-19](https://gith
253 254
254This event leverages naddr to enable comprehensive customization and sharing of marketplace configurations, fostering a unique and engaging marketplace environment. 255This event leverages naddr to enable comprehensive customization and sharing of marketplace configurations, fostering a unique and engaging marketplace environment.
255 256
257## Auctions
258
259### Event `30020`: Create or update a product sold as an auction
260
261**Event Content**:
262```json
263{
264 "id": <String, UUID generated by the merchant. Sequential IDs (`0`, `1`, `2`...) are discouraged>,
265 "stall_id": <String, UUID of the stall to which this product belong to>,
266 "name": <String, product name>,
267 "description": <String (optional), product description>,
268 "images": <[String], array of image URLs, optional>,
269 "starting_bid": <int>,
270 "start_date": <int (optional) UNIX timestamp, date the auction started / will start>,
271 "duration": <int, number of seconds the auction will run for, excluding eventual time extensions that might happen>,
272 "specs": [
273 [<String, spec key>, <String, spec value>]
274 ],
275 "shipping": [
276 {
277 "id": <String, UUID of the shipping zone. Must match one of the zones defined for the stall>,
278 "cost": <float, extra cost for shipping. The currency is defined at the stall level>,
279 }
280 ]
281}
282```
283
284> [!NOTE]
285> Items sold as an auction are very similar in structure to fixed-price items, with some important differences worth noting.
286
287* The `start_date` can be set to a date in the future if the auction is scheduled to start on that date, or can be omitted if the start date is unknown/hidden. If the start date is not specified, the auction will have to be edited later to set an actual date.
288
289* The auction runs for an initial number of seconds after the `start_date`, specified by `duration`.
290
291### Event `1021`: Bid
292
293```json
294{
295 "content": <int, amount of sats>,
296 "tags": [["e", <event ID of the auction to bid on>]],
297}
298```
299
300Bids are simply events of kind `1021` with a `content` field specifying the amount, in the currency of the auction. Bids must reference an auction.
301
302> [!NOTE]
303> Auctions can be edited as many times as desired (they are "parameterized replaceable events") by the author - even after the start_date, but they cannot be edited after they have received the first bid! This is enforced by the fact that bids reference the event ID of the auction (rather than the product UUID), which changes with every new version of the auctioned product. So a bid is always attached to one "version". Editing the auction after a bid would result in the new product losing the bid!
304
305### Event `1022`: Bid confirmation
306
307**Event Content**:
308
309```json
310{
311 "status": <String, "accepted" | "rejected" | "pending" | "winner">,
312 "message": <String (optional)>,
313 "duration_extended": <int (optional), number of seconds>,
314}
315```
316
317**Event Tags**:
318```json
319 "tags": [["e" <event ID of the bid being confirmed>], ["e", <event ID of the auction>]],
320```
321
322Bids should be confirmed by the merchant before being considered as valid by other clients. So clients should subscribe to *bid confirmation* events (kind `1022`) for every auction that they follow, in addition to the actual bids and should check that the pubkey of the bid confirmation matches the pubkey of the merchant (in addition to checking the signature).
323
324The `content` field is a JSON which includes *at least* a `status`. `winner` is how the *winning bid* is replied to after the auction ends and the winning bid is picked by the merchant.
325
326The reasons for which a bid can be marked as `rejected` or `pending` are up to the merchant's implementation and configuration - they could be anything from basic validation errors (amount too low) to the bidder being blacklisted or to the bidder lacking sufficient *trust*, which could lead to the bid being marked as `pending` until sufficient verification is performed. The difference between the two is that `pending` bids *might* get approved after additional steps are taken by the bidder, whereas `rejected` bids can not be later approved.
327
328An additional `message` field can appear in the `content` JSON to give further context as of why a bid is `rejected` or `pending`.
329
330Another thing that can happen is - if bids happen very close to the end date of the auction - for the merchant to decide to extend the auction duration for a few more minutes. This is done by passing a `duration_extended` field as part of a bid confirmation, which would contain a number of seconds by which the initial duration is extended. So the actual end date of an auction is always `start_date + duration + (SUM(c.duration_extended) FOR c in all confirmations`.
331
256## Customer support events 332## Customer support events
257 333
258Customer support is handled over whatever communication method was specified. If communicating via nostr, NIP-04 is used https://github.com/nostr-protocol/nips/blob/master/04.md. 334Customer support is handled over whatever communication method was specified. If communicating via nostr, NIP-04 is used https://github.com/nostr-protocol/nips/blob/master/04.md.
diff --git a/44.md b/44.md
index 0efa482..f3071ea 100644
--- a/44.md
+++ b/44.md
@@ -161,7 +161,7 @@ validation rules, refer to BIP-340.
161### Implementation pseudocode 161### Implementation pseudocode
162 162
163The following is a collection of python-like pseudocode functions which implement the above primitives, 163The following is a collection of python-like pseudocode functions which implement the above primitives,
164intended to guide impelmenters. A collection of implementations in different languages is available at https://github.com/paulmillr/nip44. 164intended to guide implementers. A collection of implementations in different languages is available at https://github.com/paulmillr/nip44.
165 165
166```py 166```py
167# Calculates length of the padded byte array. 167# Calculates length of the padded byte array.
@@ -286,7 +286,7 @@ Example of a test vector from the file:
286The file also contains intermediate values. A quick guidance with regards to its usage: 286The file also contains intermediate values. A quick guidance with regards to its usage:
287 287
288- `valid.get_conversation_key`: calculate conversation_key from secret key sec1 and public key pub2 288- `valid.get_conversation_key`: calculate conversation_key from secret key sec1 and public key pub2
289- `valid.get_message_keys`: calculate chacha_key, chacha_nocne, hmac_key from conversation_key and nonce 289- `valid.get_message_keys`: calculate chacha_key, chacha_nonce, hmac_key from conversation_key and nonce
290- `valid.calc_padded_len`: take unpadded length (first value), calculate padded length (second value) 290- `valid.calc_padded_len`: take unpadded length (first value), calculate padded length (second value)
291- `valid.encrypt_decrypt`: emulate real conversation. Calculate pub2 from sec2, verify conversation_key from (sec1, pub2), encrypt, verify payload, then calculate pub1 from sec1, verify conversation_key from (sec2, pub1), decrypt, verify plaintext. 291- `valid.encrypt_decrypt`: emulate real conversation. Calculate pub2 from sec2, verify conversation_key from (sec1, pub2), encrypt, verify payload, then calculate pub1 from sec1, verify conversation_key from (sec2, pub1), decrypt, verify plaintext.
292- `valid.encrypt_decrypt_long_msg`: same as previous step, but instead of a full plaintext and payload, their checksum is provided. 292- `valid.encrypt_decrypt_long_msg`: same as previous step, but instead of a full plaintext and payload, their checksum is provided.
diff --git a/46.md b/46.md
index 1f8e6c8..05247c5 100644
--- a/46.md
+++ b/46.md
@@ -34,7 +34,7 @@ The signer scans the QR code and sends a `connect` message to the client in the
34 34
35## Event payloads 35## Event payloads
36 36
37Event payloads are [NIP-04](04.md)-encrypted JSON blobs that look like JSONRPC messages (their format is specified inside the `.content` of the event formats nelow). 37Event payloads are [NIP-04](04.md)-encrypted JSON blobs that look like JSONRPC messages (their format is specified inside the `.content` of the event formats below).
38 38
39Events sent by the client to the remote signer have the following format: 39Events sent by the client to the remote signer have the following format:
40 40
diff --git a/50.md b/50.md
index 2bdf1ff..9d1a54c 100644
--- a/50.md
+++ b/50.md
@@ -41,7 +41,7 @@ implementation details between relays.
41Clients MAY verify that events returned by a relay match the specified query in a way that suits the 41Clients MAY verify that events returned by a relay match the specified query in a way that suits the
42client's use case, and MAY stop querying relays that have low precision. 42client's use case, and MAY stop querying relays that have low precision.
43 43
44Relays SHOULD exclude spam from search results by default if they supports some form of spam filtering. 44Relays SHOULD exclude spam from search results by default if they support some form of spam filtering.
45 45
46## Extensions 46## Extensions
47 47
diff --git a/51.md b/51.md
index 507c515..47ed899 100644
--- a/51.md
+++ b/51.md
@@ -46,6 +46,7 @@ Aside from their main identifier, the `"d"` tag, sets can optionally have a `"ti
46| Relay sets | 30002 | user-defined relay groups the user can easily pick and choose from during various operations | `"relay"` (relay URLs) | 46| Relay sets | 30002 | user-defined relay groups the user can easily pick and choose from during various operations | `"relay"` (relay URLs) |
47| Bookmark sets | 30003 | user-defined bookmarks categories , for when bookmarks must be in labeled separate groups | `"e"` (kind:1 notes), `"a"` (kind:30023 articles), `"t"` (hashtags), `"r" (URLs)` | 47| Bookmark sets | 30003 | user-defined bookmarks categories , for when bookmarks must be in labeled separate groups | `"e"` (kind:1 notes), `"a"` (kind:30023 articles), `"t"` (hashtags), `"r" (URLs)` |
48| Curation sets | 30004 | groups of articles picked by users as interesting and/or belonging to the same category | `"a"` (kind:30023 articles), `"e"` (kind:1 notes) | 48| Curation sets | 30004 | groups of articles picked by users as interesting and/or belonging to the same category | `"a"` (kind:30023 articles), `"e"` (kind:1 notes) |
49| Curation sets | 30005 | groups of videos picked by users as interesting and/or belonging to the same category | `"a"` (kind:34235 videos) |
49| Interest sets | 30015 | interest topics represented by a bunch of "hashtags" | `"t"` (hashtags) | 50| Interest sets | 30015 | interest topics represented by a bunch of "hashtags" | `"t"` (hashtags) |
50| Emoji sets | 30030 | categorized emoji groups | `"emoji"` (see [NIP-30](30.md)) | 51| Emoji sets | 30030 | categorized emoji groups | `"emoji"` (see [NIP-30](30.md)) |
51 52
diff --git a/53.md b/53.md
index e17f965..fad2622 100644
--- a/53.md
+++ b/53.md
@@ -94,7 +94,7 @@ Common use cases include meeting rooms/workshops, watch-together activities, or
94 ["title", "Adult Swim Metalocalypse"], 94 ["title", "Adult Swim Metalocalypse"],
95 ["summary", "Live stream from IPTV-ORG collection"], 95 ["summary", "Live stream from IPTV-ORG collection"],
96 ["streaming", "https://adultswim-vodlive.cdn.turner.com/live/metalocalypse/stream.m3u8"], 96 ["streaming", "https://adultswim-vodlive.cdn.turner.com/live/metalocalypse/stream.m3u8"],
97 ["starts", "1687182672"] 97 ["starts", "1687182672"],
98 ["status", "live"], 98 ["status", "live"],
99 ["t", "animation"], 99 ["t", "animation"],
100 ["t", "iptv"], 100 ["t", "iptv"],
diff --git a/58.md b/58.md
index e2a9f20..0ecef25 100644
--- a/58.md
+++ b/58.md
@@ -28,7 +28,7 @@ The following tags MAY be present:
28- A `name` tag with a short name for the badge. 28- A `name` tag with a short name for the badge.
29- `image` tag whose value is the URL of a high-resolution image representing the badge. The second value optionally specifies the dimensions of the image as `width`x`height` in pixels. Badge recommended dimensions is 1024x1024 pixels. 29- `image` tag whose value is the URL of a high-resolution image representing the badge. The second value optionally specifies the dimensions of the image as `width`x`height` in pixels. Badge recommended dimensions is 1024x1024 pixels.
30- A `description` tag whose value MAY contain a textual representation of the 30- A `description` tag whose value MAY contain a textual representation of the
31image, the meaning behind the badge, or the reason of it's issuance. 31image, the meaning behind the badge, or the reason of its issuance.
32- One or more `thumb` tags whose first value is an URL pointing to a thumbnail version of the image referenced in the `image` tag. The second value optionally specifies the dimensions of the thumbnail as `width`x`height` in pixels. 32- One or more `thumb` tags whose first value is an URL pointing to a thumbnail version of the image referenced in the `image` tag. The second value optionally specifies the dimensions of the thumbnail as `width`x`height` in pixels.
33 33
34### Badge Award event 34### Badge Award event
diff --git a/94.md b/94.md
index 84a3d12..eb8117a 100644
--- a/94.md
+++ b/94.md
@@ -15,6 +15,7 @@ This NIP specifies the use of the `1063` event type, having in `content` a descr
15* `url` the url to download the file 15* `url` the url to download the file
16* `m` a string indicating the data type of the file. The [MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) format must be used, and they should be lowercase. 16* `m` a string indicating the data type of the file. The [MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) format must be used, and they should be lowercase.
17* `x` containing the SHA-256 hexencoded string of the file. 17* `x` containing the SHA-256 hexencoded string of the file.
18* `ox` containing the SHA-256 hexencoded string of the original file, before any transformations done by the upload server
18* `size` (optional) size of file in bytes 19* `size` (optional) size of file in bytes
19* `dim` (optional) size of file in pixels in the form `<width>x<height>` 20* `dim` (optional) size of file in pixels in the form `<width>x<height>`
20* `magnet` (optional) URI to magnet file 21* `magnet` (optional) URI to magnet file
@@ -32,6 +33,7 @@ This NIP specifies the use of the `1063` event type, having in `content` a descr
32 ["url",<string with URI of file>], 33 ["url",<string with URI of file>],
33 ["m", <MIME type>], 34 ["m", <MIME type>],
34 ["x",<Hash SHA-256>], 35 ["x",<Hash SHA-256>],
36 ["ox",<Hash SHA-256>],
35 ["size", <size of file in bytes>], 37 ["size", <size of file in bytes>],
36 ["dim", <size of file in pixels>], 38 ["dim", <size of file in pixels>],
37 ["magnet",<magnet URI> ], 39 ["magnet",<magnet URI> ],
diff --git a/96.md b/96.md
new file mode 100644
index 0000000..661bd45
--- /dev/null
+++ b/96.md
@@ -0,0 +1,302 @@
1NIP-96
2======
3
4HTTP File Storage Integration
5-----------------------------
6
7`draft` `optional`
8
9## Introduction
10
11This NIP defines a REST API for HTTP file storage servers intended to be used in conjunction with the nostr network.
12The API will enable nostr users to upload files and later reference them by url on nostr notes.
13
14The spec DOES NOT use regular nostr events through websockets for
15storing, requesting nor retrieving data because, for simplicity, the server
16will not have to learn anything about nostr relays.
17
18## Server Adaptation
19
20File storage servers wishing to be accessible by nostr users should opt-in by making available an https route at `/.well-known/nostr/nip96.json` with `api_url`:
21
22```js
23{
24 // Required
25 // File upload and deletion are served from this url
26 // Also downloads if "download_url" field is absent or empty string
27 "api_url": "https://your-file-server.example/custom-api-path",
28 // Optional
29 // If absent, downloads are served from the api_url
30 "download_url": "https://a-cdn.example/a-path",
31 // Optional
32 // Note: This field is not meant to be set by HTTP Servers.
33 // Use this if you are a nostr relay using your /.well-known/nostr/nip96.json
34 // just to redirect to someone else's http file storage server's /.well-known/nostr/nip96.json
35 // In this case, "api_url" field must be an empty string
36 "delegated_to_url": "https://your-file-server.example",
37 // Optional
38 "supported_nips": [60],
39 // Optional
40 "tos_url": "https://your-file-server.example/terms-of-service",
41 // Optional
42 "content_types": ["image/jpeg", "video/webm", "audio/*"],
43 // Optional
44 "plans": {
45 // "free" is the only standardized plan key and
46 // clients may use its presence to learn if server offers free storage
47 "free": {
48 "name": "Free Tier",
49 // Default is true
50 // All plans MUST support NIP-98 uploads
51 // but some plans may also allow uploads without it
52 "is_nip98_required": true,
53 "url": "https://...", // plan's landing page if there is one
54 "max_byte_size": 10485760,
55 // Range in days / 0 for no expiration
56 // [7, 0] means it may vary from 7 days to unlimited persistence,
57 // [0, 0] means it has no expiration
58 // early expiration may be due to low traffic or any other factor
59 "file_expiration": [14, 90],
60 "media_transformations": {
61 "image": [
62 'resizing'
63 ]
64 }
65 }
66 }
67}
68```
69
70### Relay Hints
71
72Note: This section is not meant to be used by HTTP Servers.
73
74A nostr relay MAY redirect to someone else's HTTP file storage server by
75adding a `/.well-known/nostr/nip96.json` with "delegated_to_url" field
76pointing to the url where the server hosts its own
77`/.well-known/nostr/nip96.json`. In this case, the "api_url" field must
78be an empty string and all other fields must be absent.
79
80If the nostr relay is also an HTTP file storage server,
81it must use the "api_url" field instead.
82
83### List of Supporting File Storage Servers
84
85| Name | Domain |
86| ------------- | ------------------------- |
87| nostrcheck.me | https://nostrcheck.me |
88| nostrage | https://nostrage.com |
89| sove | https://sove.rent |
90| nostr.build | https://nostr.build |
91| sovbit | https://files.sovbit.host |
92| void.cat | https://void.cat |
93
94## Upload
95
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.
97
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).
99If using an html form, use an `Authorization` form data field instead.
100
101These following **optional** form data fields MAY be used by `servers` and SHOULD be sent by `clients`:
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
109
110Others custom form data fields may be used depending on specific `server` support.
111The `server` isn't required to store any metadata sent by `clients`.
112
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.
116The 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
124The `server` MUST reject with 413 Payload Too Large if file size exceeds limits.
125
126The `server` MUST reject with 400 Bad Request status if some fields are invalid.
127
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.
129
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.).
131
132On successful uploads the `server` MUST reply with **201 Created** HTTP status code or **202 Accepted** if a `processing_url` field is added
133to the response so that the `client` can follow the processing status (see [Delayed Processing](#delayed-processing) section).
134
135The upload response is a json object as follows:
136
137```js
138{
139 // "success" if successful or "error" if not
140 status: "success",
141 // Free text success, failure or info message
142 message: "Upload successful.",
143 // Optional. See "Delayed Processing" section
144 processing_url: "...",
145 // This uses the NIP-94 event format but DO NOT need
146 // to fill some fields like "id", "pubkey", "created_at" and "sig"
147 //
148 // This holds the download url ("url"),
149 // the ORIGINAL file hash before server transformations ("ox")
150 // and, optionally, all file metadata the server wants to make available
151 //
152 // nip94_event field is absent if unsuccessful upload
153 nip94_event: {
154 // Required tags: "url" and "ox"
155 tags: [
156 // Can be same from /.well-known/nostr/nip96.json's "download_url" field
157 // (or "api_url" field if "download_url" is absent or empty) with appended
158 // original file hash.
159 //
160 // Note we appended .png file extension to the `ox` value
161 // (it is optional but extremely recommended to add the extension as it will help nostr clients
162 // with detecting the file type by using regular expression)
163 //
164 // Could also be any url to download the file
165 // (using or not using the /.well-known/nostr/nip96.json's "download_url" prefix),
166 // for load balancing purposes for example.
167 ["url", "https://your-file-server.example/custom-api-path/719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b.png"],
168 // SHA-256 hash of the ORIGINAL file, before transformations.
169 // The server MUST store it even though it represents the ORIGINAL file because
170 // users may try to download/delete the transformed file using this value
171 ["ox", "719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b"],
172 // Optional. SHA-256 hash of the saved file after any server transformations.
173 // The server can but does not need to store this value.
174 ["x", "543244319525d9d08dd69cb716a18158a249b7b3b3ec4bbde5435543acb34443"],
175 // Optional. Recommended for helping clients to easily know file type before downloading it.
176 ["m", "image/png"]
177 // Optional. Recommended for helping clients to reserve an adequate UI space to show the file before downloading it.
178 ["dim", "800x600"]
179 // ... other optional NIP-94 tags
180 ],
181 content: ""
182 },
183 // ... other custom fields (please consider adding them to this NIP or to NIP-94 tags)
184}
185```
186
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.
188
189`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.
191
192Alternatively, instead of using NIP-94, the `client` can share or embed on a nostr note just the above url with added "ox" [NIP-54](54.md) inline metadata field and optionally other ones.
193
194### Delayed Processing
195
196Sometimes the server may want to place the uploaded file in a processing queue for deferred file processing.
197
198In that case, the server MUST serve the original file while the processing isn't done, then swap the original file for the processed one when the processing is over. The upload response is the same as usual but some optional metadata like `nip94_event.tags.*.x` and `nip94_event.tags.*.size` won't be available.
199
200The expected resulting metadata that is known in advance should be returned on the response.
201For example, if the file processing would change a file from "jpg" to "webp",
202use ".webp" extension on the `nip94_event.tags.*.url` field value and set "image/webp" to the `nip94_event.tags.*.m` field.
203If some metadata are unknown before processing ends, omit them from the response.
204
205The upload response MAY include a `processing_url` field informing a temporary url that may be used by clients to check if
206the file processing is done.
207
208If the processing isn't done, the server should reply at the `processing_url` url with **200 OK** and the following JSON:
209
210```
211{
212 // It should be "processing". If "error" it would mean the processing failed.
213 status: "processing",
214 message: "Processing. Please check again later for updated status.",
215 percentage: 15 // Processing percentage. An integer between 0 and 100.
216}
217```
218
219When the processing is over, the server replies at the `processing_url` url with **201 Created** status and a regular successful JSON response already mentioned before (now **without** a `processing_url` field), possibly including optional metadata at `nip94_event.tags.*` fields
220that weren't available before processing.
221
222### File compression
223
224File compression and other transformations like metadata stripping can be applied by the server.
225However, for all file actions, such as download and deletion, the **original** file SHA-256 hash is what identifies the file in the url string.
226
227## Download
228
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.
230
231The 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).
233If 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
235to try downloading a file on any NIP-96 compatible server by knowing just the SHA-256 file hash.
236
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.
238
239Supporting ".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).
241The file extension may be absent because the hash is the only needed string to uniquely identify a file.
242
243Example: `https://your-file-server.example/custom-api-path/719171db19525d9d08dd69cb716a18158a249b7b3b3ec4bbdec5698dca104b7b.png`
244
245### Media Transformations
246
247`Servers` may respond to some media transformation query parameters and ignore those they don't support by serving
248the original media file without transformations.
249
250#### Image Transformations
251
252##### Resizing
253
254Upon 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.
256`Servers` can then serve the variant with the closest width to the parameter value
257or an image variant generated on the fly.
258
259Example: `https://your-file-server.example/custom-api-path/<sha256-file-hash>.png?w=32`
260
261## Deletion
262
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.
264
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.
266
267The extension is optional as the file hash is the only needed file identification.
268
269`Clients` should send a `DELETE` request to the server deletion route in the above format. It must include a NIP-98 `Authorization` header.
270
271The `server` should reject deletes from users other than the original uploader. The `pubkey` encoded on the header value identifies the user.
272
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
274in the same file hash).
275
276The successfull response is a 200 OK one with just basic JSON fields:
277
278```
279{
280 status: "success",
281 message: "File deleted."
282}
283```
284
285## Selecting a Server
286
287Note: HTTP File Storage Server developers may skip this section. This is meant for client developers.
288
289A File Server Preference event is a kind 10096 replaceable event meant to select one or more servers the user wants
290to upload files to. Servers are listed as `server` tags:
291
292```js
293{
294 // ...
295 "kind": 10096,
296 "content": "",
297 "tags": [
298 ["server", "https://file.server.one"],
299 ["server", "https://file.server.two"]
300 ]
301}
302```
diff --git a/README.md b/README.md
index bf340a8..678818d 100644
--- a/README.md
+++ b/README.md
@@ -72,6 +72,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
72- [NIP-89: Recommended Application Handlers](89.md) 72- [NIP-89: Recommended Application Handlers](89.md)
73- [NIP-90: Data Vending Machines](90.md) 73- [NIP-90: Data Vending Machines](90.md)
74- [NIP-94: File Metadata](94.md) 74- [NIP-94: File Metadata](94.md)
75- [NIP-96: HTTP File Storage Integration](96.md)
75- [NIP-98: HTTP Auth](98.md) 76- [NIP-98: HTTP Auth](98.md)
76- [NIP-99: Classified Listings](99.md) 77- [NIP-99: Classified Listings](99.md)
77 78