NIP-20: Command Results
Publishing without feedback is a terrible developer experience
NIP-20 exists because a client needs to know what happened after it sends an EVENT to a relay. Without a standard result message, the user might press publish, see nothing and never know whether the relay stored the event, rejected it, already had it, required proof-of-work or blocked the pubkey.
The current file now only says that NIP-20 moved to NIP-01, but the behavior is everywhere in the base protocol. A relay responds with an OK message containing the event ID, a boolean and a message string. The boolean says accepted or rejected. The message gives a machine-readable prefix plus human-readable explanation when something needs explaining.
This is one of those standards that users rarely notice when it works. They notice when it fails: posts that vanish, wallets that cannot publish auth events, clients that retry duplicates forever, or moderation decisions that appear as generic errors instead of clear relay feedback.
A small result grammar for relays and clients
The important pattern is ["OK", event_id, true_or_false, message]. When the relay accepts the event, the boolean is true. The message can be empty, but it can also include a positive note such as pow: when proof-of-work satisfied policy or duplicate: when the relay already had the event.
When the relay rejects the event, the boolean is false and the message contains a single-word machine prefix followed by a colon and readable text. NIP-01 lists duplicate, pow, blocked, rate-limited, invalid, restricted, mute and error as standardized prefixes.
The same prefix style also appears in CLOSED messages, which tell a client a subscription was refused or ended by the relay. NOTICE remains intentionally loose: the base protocol defines no rules for how notices are sent or treated. That separation matters. OK is structured command feedback; NOTICE is not a reliable application API.
William Casarin turned relay replies into a contract
William Casarin added NIP-20 in November 2022. The same day saw several follow-up commits: an invalid-message suggestion, a proof-of-work suggestion, a note about client handling, clearer malformed-versus-invalid wording and the recognition that server errors happen.
Those commits read like someone taking real relay behavior seriously. A relay is not just a storage box. It is a service boundary where validation, policy, rate limits, authentication, storage failure and spam defense meet. NIP-20 gave clients a predictable way to respond to that boundary.
In August 2023, PR #703 merged NIP-20 into NIP-01 along with other foundational behavior. That move made the NIP file look sparse, but it raised the rule's status. Command results are now part of the base client-relay language that every meaningful relay implementation has to understand.
Relays speak it; clients decide whether users understand it
Implementation appears across relay and library code because OK is not optional in practice. The Rust Nostr crate models relay messages and machine-readable prefixes. nostr-rs-relay lists NIP-20 support in its README and relay documentation. Older Go libraries expose command result structures. Public relay tools also surface support because clients depend on this feedback loop.
A client can use the prefix to choose behavior. duplicate: may be treated as success because the event already exists. rate-limited: can trigger a retry delay or a user-facing warning. pow: can point the publisher toward NIP-13 mining. blocked: or restricted: may send the user to relay policy, payment or authentication.
The message text still matters. A bare error: gives software a category but not a fix. A clear relay message can tell the user the event timestamp is too far from current time, the pubkey is banned, the relay requires registration or the filter is unsupported. NIP-20 makes feedback possible; product writing makes it humane.
Bad result messages create phantom bugs
The main risk is sloppy prefix use. If every rejection becomes error:, clients cannot help users recover. If a relay calls a duplicate a failure, clients may keep retrying. If a relay accepts an event but returns confusing text, users may believe publishing failed.
The second risk is leaking policy through vague language. Relays have the right to refuse writes, but a user needs to be able to tell whether the reason is malformed JSON, a bad signature, proof-of-work, rate limiting, moderation, payment or infrastructure failure. Command results are where protocol precision becomes user trust.
Read NIP-20 in the wild
NIP-20 makes relay command results readable. Instead of a client guessing whether an event was accepted, rejected, duplicated or rate-limited, the relay can respond with explicit status and reason.
That feedback is infrastructure empathy. A good client shows the difference between a bad signature, a policy rejection, a rate limit and a temporary relay problem. Otherwise users blame the whole network for one relay's answer.
What changes when you actually use it
For you, NIP-20: Command Results is felt when an app either behaves predictably or suddenly loses context. The visible symptom may be a missing reply, a broken link, a strange reaction count, an empty result or a relay error that looks like the whole network failed. The official terms the event kind, tags, content field, relay behavior and signature path are where that visible behavior begins, so the source is not background material; it is the place where the product promise gets its limits.
What changes for builders and operators
For builders, NIP-20: Command Results is compatibility discipline. Implement the event kind, tags, content field, relay behavior and signature path against more than one relay and more than one library, then test malformed, missing and duplicated data. Core standards fail most painfully when the happy path looks fine and the second client exposes the shortcut.
What the official file makes concrete
Read it beside NIP-01 before treating it as isolated.
NIP-20: Command Results is a shared contract between independent software. The smallest field can become user-visible when two clients disagree about it.
Where it breaks
The failure mode in NIP-20: Command Results is often indirect. Nobody complains about the event kind, tags, content field, relay behavior and signature path; they complain that the feed is wrong, the reply vanished or the relay behaved strangely. Use the official file to diagnose the hidden cause instead of patching only the visual symptom.
Where this appears outside the markdown
In the ecosystem, NIP-20: Command Results is not something most people choose directly. It is the invisible grammar behind clients, relays, crawlers, search tools and archives. When a product team treats the event kind, tags, content field, relay behavior and signature path as implementation detail only, the mistake eventually reaches the surface as missing history, bad threading or state that cannot be reconstructed after a client switch.
The nearby-standard trap
The nearby-standard trap in NIP-20: Command Results is assuming the base layer solves the higher-level feature. This NIP may define the common grammar, but publishing, wallets, moderation, media or groups still need their own constraints. Read NIP-01 to see where the base contract ends and the product-specific promise begins.
Language that keeps the feature honest
Good product copy for NIP-20: Command Results does not say "the protocol handles it" and move on. It explains the visible consequence: what was sent, what was accepted, what was rejected, what is still loading and what another relay or client may show differently.
What this page does not promise
NIP-20: Command Results does not promise a finished social product. It gives software a shared grammar. Feed design, moderation, ranking, notifications, storage duration and recovery remain separate product decisions. That distinction matters because a client can be technically compatible and still give you a weak experience if it hides relay errors, drops context or treats optional fields as if every app understood them.
Read it as a field test
Start NIP-20: Command Results with the visible product symptom, then trace it back to the event kind, tags, content field, relay behavior and signature path. That order keeps the article grounded: you see why the field exists, which relay or client behavior depends on it, and where adjacent standards change the story. A core NIP is strong only when it explains both the normal path and the awkward edge case.
Where the standard earns trust
The source links give you places to test the interpretation in public: NIP-01 relay messages, Rust Nostr relay messages, nostr-rs-relay, Relay Runner: nostr-rs-relay. Use those links to move from the spec to live libraries, mirrors, pull requests, guides or products.
Official NIP-20 source is the anchor for exact wording, and NIP-20 commit history shows how that wording moved over time. The strongest secondary clues here are NIP-01 relay messages, Rust Nostr relay messages, nostr-rs-relay. Treat this evidence chain as part of the article, not as footnotes. A NIP page becomes useful when you can move from claim to source to working behavior without guessing.
Keep the chain visible for NIP-20: Command Results: first the human promise, then the event kind, tags, content field, relay behavior and signature path, then the implementation record, then the real-world failure case. That order keeps NIP-20 useful without turning it into marketing copy or protocol trivia.
Three questions to carry forward
- Can two independent clients read the same the main event fields without a hidden compatibility rule?
- Does the UI explain relay rejection, missing context or state replacement without blaming the whole network?
- Which adjacent standard, especially NIP-01, changes the behavior once the base event leaves the happy path?
What to verify before you rely on it
- Find the main event shape in the official file and check how the UI exposes it.
- Read NIP-01 as context before treating NIP-20 as a complete product story.
- Open at least one implementation, mirror, pull request or library source from the source links before trusting that the idea is mature.
- Test the unhappy path: missing relays, stale metadata, invalid signatures, blocked events, expired state, revoked permissions or unavailable media.
- Write the user-facing copy in plain language. If a standard changes authority, privacy, money, moderation or recovery, say that before the click.
Direct sources
Use these sources for NIP-20: Command Results in that order: Official NIP-20 source for the current wording; NIP-20 commit history for the change record; NIP-01 relay messages, Rust Nostr relay messages, nostr-rs-relay for public context. The article gives you the consequence in plain language, but the source trail is where exact fields, status notes, unresolved debates and implementation proof stay checkable.





