Finalize Place Photos and Reviews NIP drafts
CI / Lint (pull_request) Successful in 50s
CI / Test (pull_request) Successful in 1m9s
Release Drafter / Update release notes draft (pull_request) Successful in 31s

Photos (kind 360):
- scope to photos and videos; title "Place Photos and Videos"
- osm: scheme framed as extending NIP-73 (proposed upstream); geo:-vs-g and
  OSM-mutability rationale
- geohash: define high precision as length >= 9; resolutions 4/6/7/9
- content: MUST be empty or exactly the primary url; document the NIP-92 override
- imeta: NIP-92/94 field citations, alt as the only description channel,
  video guidance (place documentation, not social video), top-level m MAY
- add Querying, Client Behavior, Security Considerations, Future Work sections
- drop published_at; drop k; json fences; relative links on first mention

Reviews (kind 30360):
- same i/g/querying/terminology fixes; correct NIP-73 title
- fix missing addressable d tag (d = i; one review per author/place) and
  add update/deletion semantics

notes: refresh stale NIP-73/osm note, add live-data line
This commit is contained in:
raucao committed 2026-11-11 21:22:22 +04:00
1 parent a11203bb18
commit 33670283d1
3 files changed
+158 -60

No files matched your search

+91 -24
View File
@@ -1,12 +1,12 @@
# NIP-XX: Place Photos and Media
# NIP-XX: Place Photos and Videos
`draft` `optional`
## Abstract
This NIP defines a standardized event format for sharing photos, videos, and other visual media tied to specific real-world locations (e.g., OpenStreetMap POIs).
This NIP defines a standardized event format for sharing photos and videos tied to specific real-world locations (e.g., OpenStreetMap POIs). Each event carries exactly one media item.
While NIP-68 (Picture-first feeds) caters to general visual feeds, this NIP specifically targets map-based applications, travel logs, and location directories by mandating strict entity identifiers (`i` tags) and spatial indexing (`g` tags).
While [NIP-68](68.md) (Picture-first feeds) caters to general visual feeds, this NIP specifically targets map-based applications, travel logs, and location directories by mandating a strict entity identifier (`i` tag) and spatial indexing (`g` tag).
## Event Kind
@@ -14,40 +14,54 @@ While NIP-68 (Picture-first feeds) caters to general visual feeds, this NIP spec
## Content
The `.content` of the event SHOULD generally be empty. If a user wishes to provide a detailed description for a place, clients SHOULD encourage them to create a Place Review event (`kind: 30360`) instead.
The `.content` MUST be either empty or exactly the primary media `url`. Authors are encouraged to leave it empty so that photos do not surface out of context in generic feeds. Detailed place descriptions belong in a separate place-review event (a forthcoming Place Reviews NIP; not yet published). If `.content` is not empty, it MUST equal the primary `url` in the `imeta` tag.
## Tags
This NIP relies on existing Nostr tag conventions to link media to places and provide inline metadata.
This NIP reuses existing Nostr tags to link media (image or video, per the `m` tag) to places and provide inline metadata.
### Required Tags
#### 1. `i` — Entity Identifier
Identifies the exact place the media depicts using an external identifier (as defined in NIP-73). OpenStreetMap data is the default:
Identifies the exact place using an external content identifier (see [NIP-73](73.md)). This NIP defines the `osm:<type>:<id>` scheme, extending NIP-73's external content ids; it is proposed for inclusion in NIP-73. OpenStreetMap data is the default:
```json
["i", "osm:node:123456"]
```
- For OSM POIs, `<type>` MUST be one of: `node`, `way`, `relation`.
- For OSM POIs, `<type>` MUST be one of: `node`, `way`, `relation`. OSM element ids are unique only within a type (`node 123` ≠ `way 123`), which is why `<type>` is required.
- An event has exactly one `i` tag. A media item depicting multiple identifiable places uses the primary subject; this NIP does not define additional `i` tags.
#### 2. `g` — Geohash
Used for spatial indexing and discovery. Events MUST include at least one high-precision geohash. To optimize for map-based discovery across different zoom levels, clients SHOULD include geohashes at multiple resolutions:
The `g` tag was first defined in [NIP-52](52.md) (calendar events) and has since been reused by [NIP-99](99.md) (classified listings) and [NIP-CC](CC.md) (geocaching). This NIP reuses `g` for spatial indexing of place media.
This NIP adds the following requirements: events MUST include at least one high-precision geohash (length ≥ 9), and clients SHOULD include geohashes at multiple resolutions:
- length 4 — coarse (city-scale discovery)
- length 6 — medium (default query level, ~1 km)
- length 7 — fine (neighborhood, ~150 m)
- length 9 — high precision (~5 m, the required minimum)
```json
["g", "thrr"] // coarse (~city)
["g", "thrrn5"] // medium (~1km)
["g", "thrrn5k"] // fine (~150m)
["g", "thrrn5kxyz"] // exact
[
["g", "thrr"],
["g", "thrrn5"],
["g", "thrrn5k"],
["g", "thrrn5kxy"]
]
```
#### 3. `imeta` — Inline Media Metadata
An event MUST contain exactly one `imeta` tag representing a single media item. The primary `url` MAY also be appended to the event's `.content` for backwards compatibility with clients that do not parse `imeta` tags.
An event MUST contain exactly one `imeta` tag representing a single media item. Field definitions are per [NIP-92](92.md) and [NIP-94](94.md).
Clients SHOULD include `alt` (accessibility descriptions), `dim` (dimensions), `m` (MIME type), `thumb` (URL to a smaller thumbnail image), and `blurhash` where possible. Clients MAY also include `fallback` URLs if the media is hosted on multiple servers.
Clients SHOULD include `alt` (accessibility description — also the media's description/caption, see below), `dim` (dimensions), `m` (MIME type, image or video), `thumb` (URL to a smaller thumbnail image), and `blurhash` where possible. Clients MAY also include `fallback` URLs if the media is mirrored.
`alt` serves as the description of the media (not only accessibility text); clients SHOULD prompt authors for it and MAY display it as the media's caption. Since `.content` cannot carry a caption, `alt` is the only description channel for a kind 360 item. A generic or empty `alt` is little better than none.
Authors MAY set `.content` to the primary `url` so that clients which do not parse `imeta` can still render the media (see Content).
```json
[
@@ -62,11 +76,46 @@ Clients SHOULD include `alt` (accessibility descriptions), `dim` (dimensions), `
]
```
Clients MAY additionally include a top-level `["m", "<mime>"]` tag for `#m` filtering (e.g., photo-only vs video-capable clients). If present, it MUST equal the `imeta`'s `m` value.
**Video.** A kind 360 video is a moving record of a place's appearance — layout, size, and surroundings — serving the same purpose as a photo, for place documentation. It is not short-form or social video content. [NIP-71](71.md) is referenced solely for its `imeta` field definitions: kind 360 videos MAY include NIP-71's video-specific `imeta` fields (`duration`, `bitrate`, `waveform`, `image` preview, `l`/`ov` audio tracks; as applicable). Because an event contains exactly one `imeta` tag, a kind 360 video is single-representation: one URL, no resolution ladders, no multiple audio tracks.
### Optional Tags
- `t`: Hashtags for categorization (e.g., `["t", "food"]`, `["t", "architecture"]`).
- `content-warning`: If the media contains NSFW or sensitive imagery.
- `published_at`: Unix timestamp of when the photo was originally taken or published.
- `content-warning`: Optional reason as defined in [NIP-36](36.md); clients MAY hide the media until the user acts. Example: `["content-warning", "nudity"]`.
For kind 360, the warning applies to the media (rendering), not `.content`: clients SHOULD hide or blur the media (e.g., via the `blurhash` placeholder) until the user acts.
## Querying
Single-item tags are filterable per [NIP-01](01.md).
**All media for a place.** Filter by kind 360 and `#i` with the full identifier:
```json
{ "kinds": [360], "#i": ["osm:node:123456"] }
```
This returns every media item for the place regardless of geohash presence or precision, and does not require geohash tags.
**Spatial discovery.** Filter by kind 360 and `#g`:
```json
{ "kinds": [360], "#g": ["thrrn4", "thrrn5", "thrrn6", "thrrn7"] }
```
Clients SHOULD query a single geohash precision level per request, MAY include multiple geohash values to cover a bounding box, SHOULD limit values per query (e.g., ≤ 30), and MAY reduce precision or split queries when necessary.
Relays MAY not index arbitrary tags; clients SHOULD NOT assume all relays serve `#i`/`#g` queries for this kind.
## Client Behavior
**Publishing.** Clients MUST NOT publish events that violate the required tags or the content rule above.
**Parsing.** Clients SHOULD tolerate missing optional fields and unknown tags, and MAY use the first `imeta` if multiple are present. An event whose `imeta` lacks a `url` has no renderable media. If the `i` element cannot be resolved (e.g., a deleted OSM element), clients MAY fall back to the `g` geohash for placement, or skip the event.
**Rendering.** Clients MAY skip media they cannot render, SHOULD honor `content-warning` (media, not `.content` — see above), and SHOULD use `thumb`/`blurhash` placeholders while loading. Relays: see Querying for the `#i`/`#g` indexing caveat.
## Example Event
@@ -81,8 +130,8 @@ Clients SHOULD include `alt` (accessibility descriptions), `dim` (dimensions), `
["i", "osm:node:987654321"],
["g", "xn0m"],
["g", "xn0m7h"],
["g", "xn0m7hwq"],
["g", "xn0m7hw"],
["g", "xn0m7hwkq"],
[
"imeta",
"url https://blossom.example.com/a9c84e183789a74288b8e05d04cc61230e74f386925a953e6b29f957e8cc3a61.jpg",
@@ -93,7 +142,6 @@ Clients SHOULD include `alt` (accessibility descriptions), `dim` (dimensions), `
"thumb https://example.com/c5a528e20235e16cc1c18090b8f04179de76288ea4e410b0fcb8d1487e416a2d.jpg",
"blurhash UHI=0o~q4T-o~q%MozM{x]t7RjRPt7oKkCWB"
],
["t", "ramen"],
["t", "food"]
]
@@ -104,16 +152,35 @@ Clients SHOULD include `alt` (accessibility descriptions), `dim` (dimensions), `
### Kind 360
Easy to remember as a 360-degree view of places.
Easy to remember as a figurative 360-degree view of places: media giving a comprehensive impression of a place.
### Why not use NIP-68 (Picture-first feeds)?
NIP-68 is designed for general-purpose social feeds (like Instagram). Place photos require strict guarantees about what entity is being depicted to be useful for map clients, directories, and review aggregators. By mandating the `i` tag for POI linking and the `g` tag for spatial querying, this kind ensures interoperability for geo-spatial applications without cluttering general picture feeds with mundane POI images (like photos of storefronts or menus).
NIP-68 is designed for general-purpose social feeds (like Instagram). Place media require strict guarantees about what entity is being depicted to be useful for map clients, directories, and review aggregators. By mandating the `i` tag for place linking and the `g` tag for spatial querying, this kind ensures interoperability for geospatial applications without cluttering general picture feeds with everyday place images and videos (footage of what a place looks like — not social or short-form video content).
### Spatial Indexing and the `geo:` Identifier
`g` is used for spatial indexing; the `geo:` identifier form of NIP-73 is not used in `i`, which identifies the entity (`osm:<type>:<id>`), not a location.
### External Content Identifier (`osm:`)
This NIP extends NIP-73 with the `osm:<type>:<id>` scheme, which is proposed for inclusion in NIP-73; the scheme may evolve with upstream registration. OSM identifiers reference a live database: elements may be moved, retyped, or deleted, so a referenced element can change or disappear. This is why the `g` geohash is also required — it keeps media discoverable and placeable even if the identifier later becomes stale.
### Separation from Place Reviews
Reviews (kind 30360) and media have different lifecycles and data models. A user might upload 10 photos of a park without writing a review, or write a detailed review without attaching photos. Keeping them as separate events allows clients to query `imeta` attachments for a specific `i` tag to quickly build a photo gallery for a place, regardless of whether a review was attached.
Place Reviews (kind 30360) is a work-in-progress companion NIP, not yet published; when it is, reviews and media will interoperate as follows. Reviews and media have different lifecycles and data models. A user might upload 10 photos of a park without writing a review, or write a detailed review without attaching photos. Keeping them as separate events allows clients to query `imeta` attachments for a specific `i` tag to quickly build a media gallery for a place, regardless of whether a review was attached.
### Single Photo per Event
### Single Media Item per Event
Restricting events to a single `imeta` attachment (one photo per event) is an intentional design choice. Batching photos into a single event forces all engagement (likes, zaps) to apply to the entire batch, rendering granular tagging and sorting impossible. Single-photo events enable per-photo engagement, fine-grained categorization (e.g., tagging one photo as "food" and another as "menu"), and richer sorting algorithms based on individual photo popularity.
Restricting events to a single `imeta` attachment (one media item per event) is an intentional design choice. Batching media into a single event forces all engagement (likes, zaps) to apply to the entire batch, rendering granular tagging and sorting impossible. Single-item events enable per-item engagement, fine-grained categorization (e.g., tagging one photo as "food" and another as "menu"), and richer sorting algorithms based on individual item popularity.
## Security Considerations
- **Unauthenticated place association.** Any key can publish kind 360 media for any place; presence does not imply endorsement by, or connection to, the place or its owner. Media may misrepresent a place (e.g., fake or outdated interior/menu photos).
- **Spam.** Clients SHOULD NOT assume all events are legitimate and SHOULD apply their own moderation/trust mechanisms.
- **Untrusted media.** `url`, `thumb`, and `fallback` point to arbitrary servers; clients SHOULD treat fetched media as untrusted content. Fetching media reveals the viewer's interest in a place to the hosting server.
- **Identifier decay.** Referenced OSM elements may be deleted or retyped; see Client Behavior for parsing fallbacks.
## Future Work
- **Capture time (`taken_at`).** A future tag for when the photo was taken (e.g., from EXIF). If introduced: MUST NOT be required; MUST NOT be published without explicit user consent; MUST offer the user the option to change or remove it. (Capture time discloses where and when the author was.)
+54 -32
View File
@@ -4,7 +4,7 @@
## Abstract
This NIP defines a standardized event format for decentralized place reviews using Nostr. Reviews are tied to real-world locations (e.g. OpenStreetMap POIs) via tags, and include structured, multi-aspect ratings, a binary recommendation signal, and optional contextual metadata.
This NIP defines a standardized event format for decentralized place reviews using Nostr. Reviews are tied to real-world locations (e.g., OpenStreetMap POIs) via tags, and include structured, multi-aspect ratings, a binary recommendation signal, and optional contextual metadata.
The design prioritizes:
@@ -23,28 +23,33 @@ Additional tags MAY be included by clients but are not defined by this specifica
This NIP reuses and builds upon existing Nostr tag conventions:
- `i` tag: see NIP-73 (External Content Identifiers)
- `g` tag: geohash-based geotagging (community conventions)
- `i` tag: see [NIP-73](73.md) (External Content IDs). This NIP defines the `osm:<type>:<id>` scheme, extending NIP-73's external content ids; it is proposed for inclusion in NIP-73.
- `g` tag: first defined in [NIP-52](52.md) (calendar events) and reused by [NIP-99](99.md) (classified listings) and [NIP-CC](CC.md) (geocaching).
Where conflicts arise, this NIP specifies the behavior for review events.
### Required
#### `d` — Review Identifier
Kind 30360 is an addressable event and MUST include a `d` tag. The `d` value MUST equal the `i` tag value, so the addressable coordinate is `30360:<pubkey>:<i>`. This gives exactly one review per (author, place): republishing replaces the author's previous review.
#### `i` — Entity Identifier
Identifies the reviewed place using an external identifier. OpenStreetMap data is the default:
Identifies the reviewed place using an external content identifier (see [NIP-73](73.md)). This NIP defines the `osm:<type>:<id>` scheme, extending NIP-73's external content ids; it is proposed for inclusion in NIP-73. OpenStreetMap data is the default:
```
```json
["i", "osm:<type>:<id>"]
```
Requirements:
- For OSM POIs, `<type>` MUST be one of: `node`, `way`, `relation`
- For OSM POIs, `<type>` MUST be one of: `node`, `way`, `relation`. OSM element ids are unique only within a type (`node 123` ≠ `way 123`), which is why `<type>` is required.
- An event has exactly one `i` tag.
Examples:
```
```json
["i", "osm:node:123456"]
["i", "osm:way:987654"]
```
@@ -53,39 +58,47 @@ Examples:
#### `g` — Geohash
Geohash tags are used for spatial indexing and discovery.
The `g` tag was first defined in [NIP-52](52.md) (calendar events) and has since been reused by [NIP-99](99.md) (classified listings) and [NIP-CC](CC.md) (geocaching). This NIP reuses `g` for spatial indexing of reviews.
##### Requirements
- Clients MUST include at least one high-precision geohash (length ≥ 9)
##### Recommendations
Clients SHOULD include geohashes at the following resolutions:
Events MUST include at least one high-precision geohash (length ≥ 9). Clients SHOULD include geohashes at multiple resolutions:
- length 4 — coarse (city-scale discovery)
- length 6 — medium (default query level, ~1 km)
- length 7 — fine (neighborhood, ~150 m)
- length 9 — high precision (~5 m, the required minimum)
Example:
```
["g", "thrr"]
["g", "thrrn5"]
["g", "thrrn5k"]
["g", "thrrn5kxyz"]
```json
[
["g", "thrr"],
["g", "thrrn5"],
["g", "thrrn5k"],
["g", "thrrn5kxy"]
]
```
##### Querying
## Querying
Geospatial queries are performed using the `g` tag.
Single-item tags are filterable per [NIP-01](01.md).
- Clients SHOULD query using a single geohash precision level per request
- Clients MAY include multiple geohash values in a filter to cover a bounding box
- Clients SHOULD limit the number of geohash values per query (e.g. ≤ 30)
- Clients MAY reduce precision or split queries when necessary
**All reviews for a place.** Filter by kind 30360 and `#i` with the full identifier:
Note: Other queries (e.g. fetching reviews for a specific place) are performed using the `i` tag and are outside the scope of geospatial querying.
```json
{ "kinds": [30360], "#i": ["osm:node:123456"] }
```
This does not require geohash tags.
**Spatial discovery.** Filter by kind 30360 and `#g`:
```json
{ "kinds": [30360], "#g": ["thrrn4", "thrrn5", "thrrn6", "thrrn7"] }
```
Clients SHOULD query a single geohash precision level per request, MAY include multiple geohash values to cover a bounding box, SHOULD limit values per query (e.g., ≤ 30), and MAY reduce precision or split queries when necessary.
Relays MAY not index arbitrary tags; clients SHOULD NOT assume all relays serve `#i`/`#g` queries for this kind.
## Content (JSON)
@@ -183,13 +196,14 @@ The event `content` MUST be valid JSON matching the following schema.
#### Tags
```
```json
[
["d", "osm:node:123456"],
["i", "osm:node:123456"],
["g", "thrr"],
["g", "thrrn5"],
["g", "thrrn5k"],
["g", "thrrn5kxyz"]
["g", "thrrn5kxy"]
]
```
@@ -224,6 +238,10 @@ The event `content` MUST be valid JSON matching the following schema.
}
```
## Updates and Deletion
Kind 30360 is addressable, so republishing replaces the author's previous review in full; there are no partial updates. To delete a review, publish a [NIP-09](09.md) deletion request (kind 5) with an `a` tag referencing the review's address (`30360:<pubkey>:<i>`).
## Semantics
### Ratings
@@ -270,7 +288,7 @@ Optional metadata about the visit.
This specification defines a content payload only.
- In Nostr: place identity is conveyed via tags
- In other protocols (e.g. ActivityPub, AT Protocol): identity MUST be mapped to the equivalent field (e.g. `object`)
- In other protocols (e.g., ActivityPub, AT Protocol): identity MUST be mapped to the equivalent field (e.g., `object`)
Content payloads SHOULD NOT include place identifiers.
@@ -278,7 +296,11 @@ Content payloads SHOULD NOT include place identifiers.
### Kind 30360
Pairs with kind 360 (Place Photos). Easy to remember as a 360-degree review of all aspects of a place.
Pairs with kind 360 (Place Photos and Videos). Easy to remember as a 360-degree review of all aspects of a place.
### Spatial Indexing and the `geo:` Identifier
`g` is used for spatial indexing; the `geo:` identifier form of NIP-73 is not used in `i`, which identifies the entity (`osm:<type>:<id>`), not a location.
### No Place Field in Content
@@ -286,7 +308,7 @@ Avoids duplication and inconsistency with tags.
### Multi-Aspect Ratings
Separates concerns (e.g. quality vs service), improving signal quality.
Separates concerns (e.g., quality vs service), improving signal quality.
### Recommendation vs Score
+13 -4
View File
@@ -1,7 +1,16 @@
# Notes
- NIP-73 for external IDs ("osm:node:123456"): https://github.com/nostr-protocol/nips/blob/744bce8fcae0aca07b936b6662db635c8b4253dd/73.md
- NIP 68/92/94 for place photos and image metadata (add "i" tag for "osm:node:123456" to NIP-68)
- Places NIP-XX draft PR: https://github.com/nostr-protocol/nips/pull/927
- NPM package for generating multi-resolution geotags: https://sandwichfarm.github.io/nostr-geotags/#md:nostr-geotags
- NIP-73 (External Content IDs) is the basis for the `i` tag mechanism, which our
place NIPs extend with an `osm:` scheme; NIP-73 itself does not define `osm:`.
https://github.com/nostr-protocol/nips/blob/master/73.md
- Rejected alternative: add an `i` tag to NIP-68 (kind 20) picture events. Chosen: a
dedicated kind 360. NIP-68/92/94 remain background (92 `imeta` is load-bearing for
kind 360).
- Places NIP-XX draft PR: https://github.com/nostr-protocol/nips/pull/927 —
closed/unmerged; useful as prior art / reviewer prehistory, do not cite as
precedent.
- NPM package for generating multi-resolution geotags:
https://sandwichfarm.github.io/nostr-geotags/#md:nostr-geotags
- AppleSauce docs for AI agents: https://applesauce.build/introduction/mcp-server.html
- Live data (wss://nostr.kosmos.org, 2026-11-11): 149 kind 360 events, all
`i`=`osm:<type>:<id>`, none with a `k` tag, all with empty `.content`.