Chapter 73 - Interop
73
Nostr Implementation Possibility - Hex № 73

External Content IDs

Two small tags that point a Nostr event at a book, a podcast, a movie, a webpage, or a blockchain transaction — so every client can find all events about the same thing.

1 Overview

The world already has global content identifiers. A book has an ISBN. A podcast feed has a GUID. A movie has an ISAN. A paper has a DOI. This NIP lets an event reference these identifiers in a standard way.

An event adds an i tag with the external identifier. It adds a k tag with the identifier kind. Relays index single-letter tags (NIP-01). So a client can query one filter and get every event about one book, one episode, or one transaction.

This NIP defines no event kind of its own. Any event kind may carry i and k tags. Comment threads on external content (NIP-22) are the main consumer.

Hint!

Think of the i tag as a library card catalog number. The k tag names the catalog. Query {"#i": [<id>]} for one item. Query {"#k": [<kind>]} for the whole catalog.

Book isbn:97807… Podcast podcast:guid:… Blockchain bitcoin:tx:… Event ["i", id] ["k", kind] Relay #i / #k index REQ {"#i": ["isbn:97807…"]}
Fig. 73-1 — One id, many events. Each event copies the external identifier into an i tag. The relay index makes the identifier queryable.

2 The i and k Tags

Use the i tag to reference an external content identifier. Use the k tag to state the identifier kind. The k value is a fixed string per identifier type. It lets clients query all events for a whole type, not just one item.

Tag shapes
["i", <external id, formatted per §3>, <url hint, optional — see §5>]
["k", <external id kind, e.g. "isbn", "web", "podcast:guid">]

Both tags are single-letter tags. Relays index their first value (NIP-01). Clients query them with the #i and #k filter keys.

3 Supported ID Types

The table lists every supported identifier type. The i tag value must follow the exact format shown. The k tag value is the fixed string in the last column.

Supported external identifiers
Typei tag valuek tag value
URLs<URL, normalized, no fragment>web
Booksisbn:<id, without hyphens>isbn
Geohashesgeo:<geohash, lowercase>geo
Countriesiso3166:<code, uppercase>iso3166
Moviesisan:<id, without version part>isan
Papersdoi:<id, lowercase>doi
Hashtags#<topic, lowercase>#
Podcast Feedspodcast:guid:<guid>podcast:guid
Podcast Episodespodcast:item:guid:<guid>podcast:item:guid
Podcast Publisherspodcast:publisher:guid:<guid>podcast:publisher:guid
Blockchain Transaction<blockchain>:[<chainId>:]tx:<txid, hex, lowercase><blockchain>:tx
Blockchain Address<blockchain>:[<chainId>:]address:<address><blockchain>:address
Tech Note

The source spec says URLs are "normalized" but defines no normalization algorithm. Only "no fragment" is explicit. Two clients may normalize the same URL differently and miss each other's events. Also note: the hashtag form here overlaps with the t tag from NIP-24; the spec does not say how the two relate.

URLs (webpages)

Use the normalized URL with the fragment removed. The k tag is the hardcoded string web. For the page https://myblog.example.com/post/2012-03-27/hello-world, the tags are:

Webpage reference
[
  ["i", "https://myblog.example.com/post/2012-03-27/hello-world"],
  ["k", "web"]
]

Books (ISBN)

A book identifier is isbn: plus the ISBN without hyphens. Example: ["i", "isbn:9780765382030"]. Many book search APIs return ISBNs without hyphens. Removing hyphens is trivial. Adding them back needs a library. So the hyphen-free form is the canonical one.

Geohashes

A geohash encodes a geographic location as a short string of letters and digits. The identifier is geo: plus the geohash. Geohashes must be lowercase. Example: ["i", "geo:ezs42e44yx96"].

Countries and subdivisions (ISO 3166)

ISO 3166 codes reference countries (ISO 3166-1 alpha-2) or subdivisions such as states and provinces (ISO 3166-2). The codes must be uppercase.

  • Country (Venezuela): ["i", "iso3166:VE"]
  • Subdivision (California, USA): ["i", "iso3166:US-CA"]

Movies (ISAN)

A movie identifier is isan: plus the ISAN without the version part. Versions and edits of a movie are not relevant here. Example: ["i", "isan:0000-0000-401A-0000-7"].

Papers (DOI)

A paper identifier is doi: plus the DOI in lowercase. The k tag is doi.

Hashtags

A hashtag identifier is # plus the topic in lowercase. The k tag is the single character #.

Podcasts

Podcast identifiers reuse the GUIDs from the podcast namespace. Three levels exist:

  • Feed: ["i", "podcast:guid:c90e609a-df1e-596a-bd5e-57bcc8aad6cc"]
  • Episode (RSS item): ["i", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f"]
  • Publisher: ["i", "podcast:publisher:guid:18bcbf10-6701-4ffb-b255-bc057390d738"]

4 Blockchain IDs

<blockchain> can be any layer-1 chain: bitcoin, ethereum, solana, and so on. When one name covers many chains — for example the EVM chains under ethereum — insert a numeric <chainId> segment. The k tag never includes the chain id. It is always <blockchain>:tx or <blockchain>:address.

Blockchain id anatomy — i tag value ethereum 100 tx 0x98f7812be496… chain name chainId (optional) tx | address txid or address Segments join with ":" — the k tag drops the chainId: ["k", "ethereum:tx"]
Fig. 73-2 — Blockchain id segments. The chain id lives only in the i tag. The k tag stays chain-id-free.

Bitcoin

A txid is hex and lowercase. An address is bech32 lowercase, or base58 with its case preserved:

Bitcoin formats and examples
bitcoin:address:<bech32, lowercase | base58, case sensitive>
bitcoin:tx:<txid, hex, lowercase>

[
  ["i", "bitcoin:tx:a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"],
  ["k", "bitcoin:tx"]
]
[
  ["i", "bitcoin:address:1HQ3Go3ggs8pFnXuHVHRytPCq5fGG8Hbhx"],
  ["k", "bitcoin:address"]
]

Ethereum and other EVM chains

EVM identifiers carry an integer chain id. Transaction hashes and addresses are hex and lowercase — lowercase the checksummed address before you tag it:

EVM formats and examples
ethereum:<chainId, integer>:tx:<txHash, hex, lowercase>
ethereum:<chainId, integer>:address:<hex, lowercase>

-- mainnet address (chainId 1) --
[
  ["i", "ethereum:1:address:0xd8da6bf26964af9d7eed9e03e53415d37aa96045"],
  ["k", "ethereum:address"]
]
-- Gnosis chain transaction (chainId 100) --
[
  ["i", "ethereum:100:tx:0x98f7812be496f97f80e2e98d66358d1fc733cf34176a8356d171ea7fbbe97ccd"],
  ["k", "ethereum:tx"]
]

5 Optional URL Hints

Each i tag may carry a URL hint as its second value. The hint sends people to a website when the client has no opinion about how to open the identifier:

i tags with url hints
["i", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f",
     "https://fountain.fm/episode/z1y9TMQRuqXl2awyrQxg"]
["i", "isan:0000-0000-401A-0000-7",
     "https://www.imdb.com/title/tt0120737"]
Tech Note

In the source spec, both URL-hint examples print the hint URL without quote marks. As written they are not valid JSON. The examples above restore the quotes. The intent — a plain URL string as the third tag element — is not in doubt.

Warning!

Only the first tag value is indexed by relays. Do not put data you need to query into the hint. The hint is a courtesy link, nothing more.

6 Worked Example

A user reviews a book in a kind-1 note. The note tags the ISBN. A second client later queries every event about that book. Orange lines go to the relay. Blue lines come from the relay.

Session transcript - wss://relay.example.com
-- publish a note about a book --
→ ["EVENT", {"id":"7fe19a3c…","pubkey":"9d2f04b1…","created_at":1749772800,
     "kind":1,
     "tags":[["i","isbn:9780765382030","https://isbnsearch.org/isbn/9780765382030"],
             ["k","isbn"]],
     "content":"Finished this one in two nights. Read it.",
     "sig":"c02b1745…"}]
← ["OK", "7fe19a3c…", true, ""]

-- another client asks: all events about this book --
→ ["REQ", "book", {"#i": ["isbn:9780765382030"]}]
← ["EVENT", "book", {"id":"7fe19a3c…", "kind":1, "content":"Finished this one…", …}]
← ["EOSE", "book"]
→ ["CLOSE", "book"]

-- or: every event that references any book --
→ ["REQ", "shelf", {"#k": ["isbn"], "limit": 50}]

7 Version History

Oscar Merry drafted this NIP for podcast apps. It grew into a general identifier registry. Notable changes, newest first:

  • 20262026-01Added ISO 3166 country and subdivision codes.
  • 20252025-04Added blockchain transaction and address identifiers with optional chain ids.
  • 20252025-03Hardcoded the webpage k tag to the string web.
  • 20242024-09Expanded the supported id list beyond the original media types.
  • 20242024-08Adopted the NIP-73 number; added podcast publisher GUIDs.
  • 20242024-06Added the optional URL hint as a second i tag value.
  • 20242024-05Removed the media type prefix; switched movies to ISAN with real examples.
  • Origin2024-04Oscar Merry drafted "External Content IDs".

Original Proposal - Credits

AuthorOscar Merry
First commit2024-04-19 — “Draft External Content IDs” (took the number 73 on 2024-08-05)
Statusdraft - optional
Source73.md on GitHub - full change history

8 See Also

  • NIP-01 — single-letter tag indexing; the #i and #k filters build on it.
  • NIP-22 — comments; uses these external ids as the scope of a comment thread.
  • NIP-24 — the t hashtag tag that overlaps with the hashtag form here.
  • NIP-12 — the origin of generic tag queries, now merged into NIP-01.