1 Overview
Before NIP-20, a client that published an event had no reliable way to know if a relay accepted it. A relay could only send a free-text NOTICE message, which no program could parse with confidence.
NIP-20 fixed this with a new message type, OK. A relay sends exactly one OK for every EVENT command it receives, well-formed or not. The message carries a machine-readable status: true or false, plus a prefixed reason string.
NIP-20 is closed and carries no separate rules of its own anymore. The OK message and its status prefixes now live in NIP-01, section 5 (Client ↔ Relay Messages). Read this page for history, then read NIP-01 for the current spec.
2 What NIP-20 Defined
NIP-20 stated these rules, restated here in Simplified Technical English:
["OK", <event_id>, <true|false>, <message>]
- The relay must send
truewhen it saved the event, including when the event was already a duplicate. - The relay must send
falsewhen it rejected the event and did not save it. - The relay must not send
OKfor ephemeral events unless something failed. - If the
EVENTcommand itself is malformed and cannot be parsed, the relay sendsNOTICEinstead ofOK.
NIP-20 defined the message prefix convention: a single lower-case word, then a colon, then a human-readable reason. It defined five prefixes:
| Prefix | Meaning |
|---|---|
| duplicate | The event was already saved. |
| blocked | The pubkey or network address is blocked or not on a whitelist. |
| invalid | The event fails validation, for example a bad signature or an out-of-range timestamp. |
| pow | The event does not meet the relay's required proof-of-work difficulty. |
| rate-limited | The client is sending events too fast. |
| error | Anything else, such as a server-side failure. |
Where It Lives Now
NIP-01 kept the OK message and every original prefix. It added two more: restricted, for a client that is not allowed to write, and mute, for an ephemeral event nobody was listening for. NIP-01 also added a related message type, CLOSED, which reports subscription-level failures using the same prefix convention. CLOSED did not come from NIP-20; it was added directly to NIP-01 later.
3 Historical Example
These are examples from the original NIP-20 text. They still match the wire format under NIP-01 today.
← ["OK", "b1a649ebe8…", true, ""] -- accepted, with a reason -- ← ["OK", "b1a649ebe8…", true, "pow: difficulty 25>=24"] -- rejected: banned pubkey -- ← ["OK", "b1a649ebe8…", false, "blocked: you are banned from posting here"] -- rejected: rate limited -- ← ["OK", "b1a649ebe8…", false, "rate-limited: slow down there chief"] -- rejected: bad timestamp -- ← ["OK", "b1a649ebe8…", false, "invalid: event creation date is too far off from the current time. Is your system clock in sync?"]
4 Version History
- Closed2023-08-13Merged into NIP-01 along with NIP-12, NIP-16, and NIP-33 (PR #703). All four were already "final, mandatory" — folding them into the root spec meant a client or relay only had to read one document to reach baseline compliance.
- 20232023-11-15Repo-wide cleanup removed the per-file author badge from the now-stub page.
- 20232023-03-02ennmichael fixed a typo.
- 20222022-12-16sgmoore made minor grammar fixes.
- Origin2022-11-10William Casarin (jb55) published NIP-20 and, the same day, added the
invalidandpowprefixes, clarified malformed-versus-invalid handling, and noted that server errors need their own prefix too.
Original Proposal - Credits
| Author | William Casarin (jb55) |
| First commit | 2022-11-10 — “NIP-20: Command Results” |
| Status | final mandatory — merged, no longer a standalone document |
| Source | 20.md on GitHub - full change history |