{"id":"e0d371eecb3000acafee4ecd1473624c7ecef3b2a57415c3d9d7f0d2cfc9de30","pubkey":"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd","created_at":1782665886,"kind":30817,"tags":[["d","bird-detection-birdex"],["title","Bird Detection & Birdex"],["alt","Nostr Implementation Possibility: Bird Detection & Birdex"],["k","2473","Bird Detection"],["k","12473","Birdex"],["client","NostrHub"]],"content":"# Bird Detection & Birdex\n\nThis NIP describes birding events on Nostr.\n\n## Kind 2473 — Bird Detection\n\nA regular event representing a single identified bird observation. Regular\nevents (1000 ≤ kind < 10000) are stored permanently by relays and are not\ndeduplicated.\n\nThe schema deliberately relies on existing NIPs (NIP-31 `alt`, NIP-73 `i`)\nrather than inventing parallel vocabulary, so that non-birding clients can\nstill display and aggregate detections usefully.\n\n### Rationale\n\nA bird detection is an **observation log entry**: a timestamped, immutable\nrecord that \"species X was heard at time T\", optionally annotated with a\nfree-form human note.\n\nWe reviewed existing NIPs before introducing a new kind:\n\n- **NIP-52 (Calendar Events)** models *planned* events at specific future\n  times, not observation logs. RSVPs and addressability make no sense for a\n  detection.\n- **NIP-32 (Labeling)** labels *another* event — it has no standalone meaning.\n  A detection is a primary artifact, not a label applied to something else.\n- **Kind 1 text notes** can contain \"I heard a robin!\" but provide no\n  structured data, so other clients cannot aggregate or filter by species.\n- The unofficial **Geocaching** kinds (7516/7517) follow a similar\n  observation-log pattern but are tightly coupled to geocache listings.\n\nNo existing kind covers a structured, standalone observation log, so we define\na new regular event kind.\n\n### `content`\n\nFree-form human-readable note about the detection. MAY be empty.\n\nExamples: `\"\"`, `\"Heard from my balcony near the oak tree.\"`\n\nStructured data MUST be placed in tags, not in `content`.\n\n### Tags\n\n#### Required\n\n- `alt` (NIP-31) — human-readable summary, e.g.\n  `\"Bird detection: American Robin (Turdus migratorius)\"`. Lets clients that\n  don't implement this NIP still render something meaningful.\n\n- `i` (NIP-73) — an external ID pointing at the species' **Wikidata\n  entity URI**. This is the canonical identity of the species, the\n  primary aggregation key, and the only species identifier in the\n  event:\n\n  ```jsonc\n  [\"i\", \"https://www.wikidata.org/entity/Q26825\"]\n  ```\n\n  Wikidata is language-neutral: a single Q-id designates the species across\n  every Wikipedia edition, taxonomy database, and localized common name. A\n  client rendering the event in any language can follow the entity URI to\n  get labels, images, and the sitelink to its preferred Wikipedia edition.\n\n  Requirements:\n\n  - The URL MUST match `https://www.wikidata.org/entity/Q<digits>` exactly:\n    `https` scheme, host `www.wikidata.org`, path `/entity/Q<digits>`, no\n    fragment, no query string, no trailing slash.\n  - The Q-id SHOULD resolve to a taxon-like entity (a species, subspecies,\n    or similarly specific rank). Publishers SHOULD NOT emit a detection\n    with a Q-id that has no corresponding taxon.\n\n  **Deviation from NIP-73.** NIP-73 pairs every `i` tag with a `k` tag\n  naming the ID kind (`\"web\"`, `\"isbn\"`, `\"geo\"`, etc.) so that clients\n  can query \"all events with any external ID of kind X\". Birdstar\n  deliberately omits the `k` tag: every `i` tag on a kind 2473 event is\n  known by the kind contract to be a Wikidata entity URI, so a `k` tag\n  would carry no information beyond what the `i` value's URL shape\n  already reveals. Queries over these events filter by the specific\n  Wikidata URI (or by `kinds: [2473]`), never by the generic `#k: [\"web\"]`\n  bucket which would return every blog post, book, and podcast on the\n  relay. The trade-off — Birdstar detections won't show up in generic\n  NIP-73 web-content aggregators — is intentional; a species Q-id is\n  not a webpage in the sense those aggregators care about.\n\n- `n` — the species' **scientific (binomial) name** as a single string,\n  e.g. `\"Turdus migratorius\"`. Lets clients render the detection\n  without round-tripping Wikidata just to get a species label.\n\n  ```jsonc\n  [\"n\", \"Turdus migratorius\"]\n  ```\n\n  The scientific name is advisory, not authoritative — the `i` tag is\n  the canonical species identity. If the two disagree (e.g. because a\n  taxon was renamed between publishing and rendering), clients SHOULD\n  trust the `i` tag's Q-id and treat `n` as a stale label.\n\n#### Optional\n\n- `g` (NIP-52) — coarse **geohash** of where the detection was made. Opt-in\n  only: Birdstar publishes this tag exclusively when the user has explicitly\n  enabled \"Publish location\" in Settings. Off by default.\n\n  ```jsonc\n  [\"g\", \"dp3\"]\n  ```\n\n  Birdstar emits a length-3 geohash (~156 km × 156 km cells), which is\n  the coarsest precision still useful for region-scale aggregation\n  without revealing the observer's neighborhood. Clients receiving a\n  detection MAY display it on a map, use it to filter \"detections near\n  me\", or ignore it entirely; the rest of the event is self-contained\n  without it.\n\n  The `g` tag value MUST match `/^[0-9bcdefghjkmnpqrstuvwxyz]+$/` —\n  the standard geohash base-32 alphabet (digits 0-9 plus the\n  consonants b-z with a, i, l, o removed). Publishers SHOULD NOT emit\n  geohashes finer than length 8 (~20 m) on a detection event — the\n  act of stamping a GPS-precise coordinate onto a signed, public,\n  permanent record is almost never what the observer intends.\n\nPublishers SHOULD NOT attach a per-event numeric confidence to a detection.\nPublishing a kind 2473 event IS the publisher's confidence signal: the act\nof authoring and signing the event asserts that the observation was\nreliable enough to commit to the record.\n\n### Example\n\n```jsonc\n{\n  \"kind\": 2473,\n  \"content\": \"Heard from my balcony around 7am — very clear song.\",\n  \"tags\": [\n    [\"alt\", \"Bird detection: American Robin (Turdus migratorius)\"],\n    [\"i\", \"https://www.wikidata.org/entity/Q26825\"],\n    [\"n\", \"Turdus migratorius\"]\n  ]\n}\n```\n\nWith the optional `g` tag, when the user has enabled location publishing\nin Settings:\n\n```jsonc\n{\n  \"kind\": 2473,\n  \"content\": \"\",\n  \"tags\": [\n    [\"alt\", \"Bird detection: American Robin (Turdus migratorius)\"],\n    [\"i\", \"https://www.wikidata.org/entity/Q26825\"],\n    [\"n\", \"Turdus migratorius\"],\n    [\"g\", \"dp3\"]\n  ]\n}\n```\n\n### Query Patterns\n\nAll detections of a given species across the network:\n\n```jsonc\n{\n  \"kinds\": [2473],\n  \"#i\": [\"https://www.wikidata.org/entity/Q26825\"]\n}\n```\n\nDropping the `kinds` filter broadens this to *all* Nostr content about the\nspecies — bird detections, comments, long-form posts, etc. — since the\nWikidata entity URI is a shared NIP-73 identifier:\n\n```jsonc\n{ \"#i\": [\"https://www.wikidata.org/entity/Q26825\"] }\n```\n\nAll detections by a specific observer:\n\n```jsonc\n{ \"kinds\": [2473], \"authors\": [\"<hex-pubkey>\"], \"limit\": 50 }\n```\n\n### Validation\n\nClients SHOULD ignore an event as a bird detection if:\n\n- `kind !== 2473`\n- No `i` tag with a normalized `https://www.wikidata.org/entity/Q<digits>`\n  URL is present\n\n### Publishing\n\nBirdstar publishes detections automatically for logged-in users as birds\nare identified, so that a user's sightings are preserved across sessions\nand devices via their own Nostr relays.\n\nTo avoid spamming relays when the same bird is heard repeatedly, publishers\nSHOULD apply a per-species cooldown: once a detection event has been\npublished for a given `i` tag, no new event for that same URL should be\npublished by the same author for at least 10 minutes.\n\nDetections from logged-out users are never published.\n\nDetections heard while offline are not lost. When a publish fails (no\nconnection, relay timeout, etc.) the detection is persisted to a local\nqueue and retried when connectivity returns, repeatedly if necessary,\nuntil the event lands on a relay. The event's `created_at` always\ncarries the original heard time, so a sighting published hours or days\nlate is still timestamped to when the bird was actually heard — readers\nshould expect kind 2473 events whose `created_at` precedes their arrival\nby an arbitrary amount, and SHOULD order detections by `created_at`\nrather than receipt time.\n\nBirdstar does not publish any location information on detections by\ndefault. The optional `g` tag is only attached when the user has\nexplicitly enabled \"Publish location\" in the Birds-mode Settings\nsheet, and is computed from the same coarse (1°-rounded) lat/lon that\ndrives the birdex's region filter — no additional precision is\nintroduced at publish time.\n\n### Deletion\n\nUsers can retract a previously published detection by issuing a standard\n**NIP-09** deletion request (kind 5) referencing the detection's event id:\n\n```jsonc\n{\n  \"kind\": 5,\n  \"tags\": [\n    [\"e\", \"<kind-2473-event-id>\"],\n    [\"k\", \"2473\"]\n  ],\n  \"content\": \"\"\n}\n```\n\nBirdstar hides any detection whose id appears in an `e` tag of a kind 5\nevent authored by the same pubkey.\n\n---\n\n## Kind 12473 — Birdex\n\nA **replaceable** event (kind `12473`, chosen as `1` prefixed onto the\n`2473` detection kind for mnemonic kinship) representing the author's\n**Birdex**: the cumulative set of every distinct bird species they\nhave ever published a kind 2473 detection for.\n\nReplaceable events (10000 ≤ kind < 20000) are deduplicated by relays on\n`pubkey + kind`, so each author has at most one Birdex on the network at\nany time. Republishing with a newer `created_at` replaces the prior\ncopy.\n\n### Rationale\n\nKind 2473 detections are the permanent log; the Birdex is the *index*\nderived from them. The log-vs-index split is deliberate:\n\n- **Without the Birdex**, reconstructing a user's species list requires\n  paginating their entire kind 2473 history across all relays — expensive\n  for clients, unreliable when some relays drop old events, and\n  impossible to express as a single filter.\n- **With it**, any client can fetch one replaceable event and render the\n  user's full species count, first-seen ordering, and profile-page\n  \"lifer badges\" in a single round-trip.\n- A new kind (rather than a generic list via NIP-51) is warranted\n  because the entries are tightly constrained (Wikidata taxon URIs only)\n  and the event has domain-specific semantics (first-seen ordering, tied\n  to the kind 2473 author's own detection history).\n\n### `content`\n\nFree-form human-readable description of the Birdex. MAY be empty.\nNon-birding clients rendering this event SHOULD prefer the `alt` tag.\n\n### Tags\n\n#### Required\n\n- `alt` (NIP-31) — human-readable summary, e.g.\n  `\"Birdex: 37 species\"`. Lets clients that don't implement this NIP\n  still render something meaningful.\n\n#### Species entries\n\nEach species on the Birdex is represented by an `i` tag (NIP-73)\nimmediately followed by an `n` tag carrying the scientific name — the\nsame vocabulary used on kind 2473 detections:\n\n```jsonc\n[\"i\", \"https://www.wikidata.org/entity/Q26825\"],\n[\"n\", \"Turdus migratorius\"]\n```\n\nThe same URL-shape rules as kind 2473 apply to the `i` value: `https`\nscheme, host `www.wikidata.org`, path `/entity/Q<digits>`, no fragment,\nno query string, no trailing slash. The `n` value is a free-form\nsingle-string scientific name (binomial), advisory only — see the\nkind 2473 `n` description for the trust model.\n\nAs with kind 2473, the Birdex deliberately omits the `k` tag that\nNIP-73 normally pairs with `i`. See the kind 2473 rationale — every\n`i` tag on a Birdex is known by the kind contract to be a Wikidata\nentity URI, and pairing with `k` would only add noise.\n\n**On positional pairing.** Each `n` tag is paired with the preceding\n`i` tag by position in the event's tag array. Publishers MUST emit\nevery species as exactly `[\"i\", \"...\"]` followed immediately by\n`[\"n\", \"...\"]` with no other tags interleaved between the pair. The\npairing is positional rather than keyed because a Wikidata URI is not\na unique-enough identifier to use as the `n` tag's second element\nwithout reconstructing most of the `i` value; positional pairing keeps\nthe wire format terse and the \"N species on my Birdex = 2N entries\"\ninvariant trivially checkable. Clients that need name-first lookups\nshould build an index once at parse time.\n\n#### Entry ordering\n\nThe `i`/`n` pairs MUST be emitted in **chronological order of first\ndetection**, oldest species first. The position of a species' pair in\nthe tag array is its ordinal rank on the Birdex (\"the 7th species I\never identified\"). Clients rendering a timeline of an author's birding\njourney rely on this ordering — it cannot be reconstructed from the\nreplaceable event alone once the underlying kind 2473 events have aged\noff relays.\n\nEach species MUST appear at most once. Republishing the event to add a\nnewly confirmed species means appending the new `i`/`n` pair to the\nend of the existing tag list, preserving the historical order of all\nprevious entries.\n\n### Example\n\nAn author whose Birdex holds three species — first a robin, then a\ncardinal, then a chickadee:\n\n```jsonc\n{\n  \"kind\": 12473,\n  \"content\": \"\",\n  \"tags\": [\n    [\"alt\", \"Birdex: 3 species\"],\n    [\"i\", \"https://www.wikidata.org/entity/Q26825\"],      // American Robin\n    [\"n\", \"Turdus migratorius\"],\n    [\"i\", \"https://www.wikidata.org/entity/Q27014\"],      // Northern Cardinal\n    [\"n\", \"Cardinalis cardinalis\"],\n    [\"i\", \"https://www.wikidata.org/entity/Q335669\"],     // Black-capped Chickadee\n    [\"n\", \"Poecile atricapillus\"]\n  ]\n}\n```\n\n### Query Patterns\n\nA specific user's Birdex:\n\n```jsonc\n{ \"kinds\": [12473], \"authors\": [\"<hex-pubkey>\"], \"limit\": 1 }\n```\n\nAll users whose Birdex includes a given species:\n\n```jsonc\n{ \"kinds\": [12473], \"#i\": [\"https://www.wikidata.org/entity/Q26825\"] }\n```\n\n### Validation\n\nClients SHOULD ignore an event as a Birdex if:\n\n- `kind !== 12473`\n- It is not authored by the pubkey whose Birdex is being queried\n  (replaceable events are per-author; the `authors` filter is the trust\n  boundary — never trust a 12473 event on its content alone).\n\nWhen parsing species entries, clients SHOULD:\n\n1. Walk the tag array in order.\n2. For each `i` tag whose value matches the canonical Wikidata entity\n   URI regex, take its scientific name from the **next** tag if that\n   tag is `[\"n\", \"<non-empty string>\"]`. `i` tags not followed by an\n   `n` tag are still accepted — the species is known by its Q-id but\n   has no local scientific-name label.\n3. Deduplicate by Wikidata URI, keeping the first occurrence — the\n   chronologically earliest entry wins.\n\n### Publishing\n\nBirdstar publishes the Birdex automatically for logged-in users. The\npublish flow is tied to kind 2473 detections:\n\n1. When a kind 2473 detection is about to be published for a species,\n   check whether that species' Wikidata URI is already present in the\n   author's existing Birdex (loaded via a single kind 12473 query on\n   login).\n2. If absent, publish the kind 2473 detection as normal, then publish a\n   new kind 12473 event containing every previously-known URI **plus**\n   the new one appended at the end.\n3. If present, only the kind 2473 event is (re)published subject to the\n   usual 10-minute cooldown; the Birdex does not change.\n\nThis couples Birdex growth to confirmed detections so the Birdex can\nnever diverge from the underlying log: every entry has a corresponding\nkind 2473 event from the same author.\n\n### Deletion\n\nTo retract a species from the Birdex, republish the event with that\nspecies' `i`/`n` pair removed. Because kind 12473 is replaceable, the\nnew event supersedes the old. Separately issuing a NIP-09 deletion on\nthe underlying kind 2473 events is also recommended if the user wants\nto remove the detection log, not just the Birdex entry.","sig":"d1e2e85b9d327b85f1e5502f27e1356c7536acc8daccf535950ae79fe4dac0ebcd97f5c9c49dbe4d1ed97a707d4913e5120f195ff85489fafadfcdf8196523c7"}