1 Overview
The source repository marks this NIP unrecommended. The stated reason: "this got totally out of control, prefer use-case-specific microstandards." No single NIP replaces it. New designs should define a small, dedicated event kind for each use case instead of this generic job framework. This chapter documents the full spec for readers who maintain existing DVM software.
A Data Vending Machine (DVM) turns Nostr into a marketplace for on-demand computation. A user wants some data processed — speech-to-text, translation, summarization. The user does not care who does the work. The user cares only about the output and the price.
This NIP defines two actors. A customer is an npub that requests a job. A service provider is an npub that fulfills jobs. The customer publishes a job request event. Any service provider can see it, work on it, and publish a result. Providers compete to deliver the best output.
This is not a 1:1 marketplace. The customer announces a desired output and a willingness to pay. The customer does not hire one provider. Several providers may answer the same request. Payment travels over Lightning: a bolt11 invoice or a zap.
2 Reserved Kinds
This NIP reserves the kind range 5000-7000 for data vending machine use. The range splits into three roles:
| Kind | Description |
|---|---|
| 5000-5999 | Job request kinds. Published by customers. |
| 6000-6999 | Job results. Published by service providers. |
| 7000 | Job feedback. Published by service providers. |
A job result always uses a kind number that is exactly 1000 higher than its job request kind. A request with kind:5001 gets a result with kind:6001. The individual job request types are not defined here. They live in a separate registry: the nostr-protocol/data-vending-machines repository on GitHub.
The job-type registry sits outside the NIPs repository, so this spec is incomplete on its own. Also, the request example includes a ["t", "bitcoin"] tag, but the tag list in the spec never defines t. Read it as a topic label borrowed from common Nostr practice; the NIP-89 appendix example uses it the same way.
3 Job Request (kind 5000-5999)
A customer publishes a job request to signal interest in the result of some compute. The event looks like this:
{
"kind": <5xxx - kind in the 5000-5999 range>,
"content": "",
"tags": [
["i", <data>, <input-type>, <relay>, <marker>],
["output", <mime-type>],
["param", <key>, <value>],
["bid", <msat-amount>],
["relays", "wss://..."],
["p", <provider pubkey>],
["t", "bitcoin"]
]
// other fields...
}
All tags are optional. A request may carry zero or more i inputs.
| Tag | Meaning |
|---|---|
| i | One input for the job: ["i", <data>, <input-type>, <relay>, <marker>]. See the input-type table below. The marker is optional; it says how the provider should use this input within the job. |
| output | Expected output format as a MIME type. Each job request kind defines this more precisely. |
| param | An optional job parameter as a key (first argument) and value (second argument), for example ["param", "lang", "es"]. Each job request kind defines its parameters more precisely. |
| bid | The maximum amount, in millisats, that the customer is willing to pay. |
| relays | The relays where service providers should publish their responses. |
| p | A service provider the customer is interested in. Other providers might still choose to process the job. |
| Type | Meaning of <data> |
|---|---|
| url | A URL. The provider fetches it to get the data to process. |
| event | A Nostr event ID. The relay element names the relay where the event was published. |
| job | The event ID of a previous job. The provider builds on that job's output. The provider decides which output to use — it may wait for a signal from the customer or for a payment. The relay element names the relay where the job was published. |
| text | The literal input value. No resolution is needed. |
For the url and text input types, the relay element is optional or an empty string.
Encrypted params
A customer can keep the job inputs secret. Take the i and param tags out of the tags array. Encrypt them with NIP-04, using the customer's private key and the provider's public key for the shared secret. Put the ciphertext in the content field. Add an ["encrypted"] tag as a marker, and keep a p tag with the provider's pubkey. Plaintext before encryption:
[ ["i", "what is the capital of France? ", "text"], ["param", "model", "LLaMA-2"], ["param", "max_tokens", "512"], ["param", "temperature", "0.5"], ["param", "top-k", "50"], ["param", "top-p", "0.7"], ["param", "frequency_penalty", "1"] ]
{
"content": "BE2Y4xvS6HIY7TozIgbEl3sAHkdZoXyLRRkZv4fLPh3R7LtviLKAJM5qpkC7D6VtMbgIt4iNcMpLtpo...",
"tags": [
["p", "04f74530a6ede6b24731b976b8e78fb449ea61f40ff10e3d869a3030c4edc91f"],
["encrypted"]
]
// other fields...
}
Two issues here. First, the spec mandates NIP-04 encryption, and NIP-04 is itself deprecated in favor of NIP-44. Second, the spec says "all tags are optional" for requests, yet the encrypted flow says the p tag "should be present" — an unresolved soft contradiction. In practice an encrypted request without p cannot be decrypted by anyone.
4 Job Result (kind 6000-6999)
A service provider publishes the job output as a result event. The provider tags the original job request event ID and the customer's pubkey. The output payload goes in content.
{
"pubkey": <service-provider pubkey>,
"content": <payload>,
"kind": <6xxx - request kind + 1000>,
"tags": [
["request", <job-request>],
["e", <job-request-id>, <relay-hint>],
["i", <input-data>],
["p", <customer's pubkey>],
["amount", <requested-payment-amount>, <optional-bolt11>]
]
// other fields...
}
| Tag | Meaning |
|---|---|
| request | The whole job request event as stringified JSON. |
| e | The job request event ID, with an optional relay hint. |
| i | The original input(s) from the request, repeated. |
| p | The customer's pubkey. |
| amount | The millisats the provider requests as payment. An optional third value is a bolt11 invoice. |
Encrypted output
If the request had encrypted params, encrypt the output too. Put the ciphertext in content. Add an ["encrypted"] tag. Do not include an i tag with the input data in clear text — that would leak the secret input.
{
"pubkey": <service-provider pubkey>,
"content": <encrypted payload>,
"kind": <6xxx>,
"tags": [
["request", <job-request>],
["e", <job-request-id>, <relay-hint>],
["p", <customer's pubkey>],
["amount", <requested-payment-amount>, <optional-bolt11>],
["encrypted"]
]
// other fields...
}
5 Job Feedback (kind 7000)
A service provider can report progress on a job back to the customer with a kind:7000 event. The content field is empty, or it carries a job result — for example a sample of partial results, as described in the Job Result section. If the request required encrypted input, encrypt the feedback content the same way, keyed by the p tag.
{
"kind": 7000,
"content": <empty-or-payload>,
"tags": [
["status", <status>, <extra-info>],
["amount", <requested-payment-amount>, <bolt11>],
["e", <job-request-id>, <relay-hint>],
["p", <customer's pubkey>]
]
// other fields...
}
The amount tag works exactly as in the job result. The status tag should name one of five defined states. The provider may add extra human-readable information as the third element.
| Status | Description |
|---|---|
| payment-required | The provider requires payment before continuing. |
| processing | The provider is processing the job. |
| error | The provider was unable to process the job. |
| success | The provider successfully processed the job. |
| partial | The provider partially processed the job. The content might include a sample of the partial results. |
6 Protocol Flow and Payment
The full lifecycle has four steps:
- The customer publishes a job request, for example a
kind:5000speech-to-text job. - Service providers may publish
kind:7000feedback events:payment-required,processing,error, and so on. - On completion, the provider publishes the result as a
kind:6xxxjob result event. - At any point, if an
amountis pending as instructed by the provider, the customer pays the included bolt11 invoice or zaps the provider's event.
An amount tag on a feedback or result event is only a suggestion to pay. One signal is strict: a provider must send a payment-required feedback event when payment is a hard requirement. After that event, the provider performs no further work until the customer pays. The customer can always pay the bolt11 invoice or zap (NIP-57) the event that requests payment. A provider that includes a bolt11 invoice should monitor for both payment paths.
payment-required has a strict meaning.The flow is deliberately ambiguous. Each provider models its own risk. Some providers demand payment-required first, before any processing event or any result. Some serve a partial sample and demand payment for the rest. Some judge an npub's past behavior, deliver the full result first, and request payment after — the best possible UX. This NIP does not define how a vending machine runs its business.
Cancellation
A customer can cancel a job request. Publish a kind:5 delete request event (NIP-09) that tags the job request event.
7 Appendix 1: Job Chaining
A customer may request several jobs as a chain. The output of one job is the input of the next. Example: podcast transcription, then summarization of the transcript. To chain, set the input of the second request to the first job's event ID with the job input type.
job-type input. The chain can continue.A provider may start a chained job the moment it sees the prior job's result. In practice, most wait for a zap on the prior result first. This creates a risk: the provider of job #1 might delay publishing the zap event to gain an advantage. Providers must mitigate this risk themselves. A provider can also decide that job #1's provider is reliable enough, and not wait for an explicit zap to treat the job as accepted. This gives providers a high level of flexibility — sophisticated providers would take it anyway.
8 Appendix 2: Provider Discoverability
A service provider may advertise its supported job kinds with a NIP-89 handler announcement (kind:31990). Each supported job request kind goes in a k tag. Customers use NIP-89 to see which providers their follows use.
{
"kind": 31990,
"pubkey": <pubkey>,
"content": "{\"name\": \"Translating DVM\",
\"about\": \"I'm a DVM specialized in translating Bitcoin content.\"}",
"tags": [
["k", "5005"], // supported job request kind, e.g. translation
["t", "bitcoin"] // optional specialization topic
]
// other fields...
}
9 Worked Example
A customer wants a podcast episode transcribed. The transcript costs 5000 msats. The session below shows the events, in publish order, on the relay named in the relays tag. Orange lines are the customer's events. Blue lines are the provider's events.
-- 1. customer publishes the job request -- → {"kind":5000, "pubkey":"aa31f7e2…", "content":"", "tags":[["i","https://cdn.example.com/ep42.mp3","url","",""], ["output","text/plain"], ["param","lang","en"], ["bid","10000"], ["relays","wss://relay.example.com"]], …} -- 2. provider asks for payment before working -- ← {"kind":7000, "pubkey":"04f74530…", "content":"", "tags":[["status","payment-required","5000 msats upfront"], ["amount","5000","lnbc50n1…"], ["e","<request-id>","wss://relay.example.com"], ["p","aa31f7e2…"]], …} -- 3. customer pays the bolt11 invoice (off-Nostr, Lightning) -- -- 4. provider reports progress -- ← {"kind":7000, "content":"", "tags":[["status","processing"], ["e","<request-id>","wss://relay.example.com"], ["p","aa31f7e2…"]], …} -- 5. provider publishes the result (kind = 5000 + 1000) -- ← {"kind":6000, "pubkey":"04f74530…", "content":"Welcome back to episode forty-two…", "tags":[["request","{\"kind\":5000,…}"], ["e","<request-id>","wss://relay.example.com"], ["i","https://cdn.example.com/ep42.mp3"], ["p","aa31f7e2…"], ["amount","5000"]], …}
Want the summary too? Publish a second request whose input is ["i", "<result-id>", "job", "wss://relay.example.com"]. That is job chaining from §7 — the summarizer builds on the transcript event.
10 Version History
NIP-90 grew fast in mid-2023, spawned an external kind registry and a whole DVM ecosystem, and was marked unrecommended in 2026. Notable changes, newest first:
- 20262026-05fiatjaf added the
unrecommendedtag and the warning: "this got totally out of control, prefer use-case-specific microstandards." - 2024-252024-2025Typo, link, heading-level, and JSON formatting fixes; deletion events renamed to delete requests per NIP-09.
- 20232023-10Big refactor by Pablo Fernandez plus consistency changes by Semisol: the request/result/feedback structure settled into its final shape.
- 20232023-07Renamed from
vending-machine.mdto90.mdand merged into the NIPs repository. Each job type got its own request kind; results moved to request kind + 1000. - 20232023-07Optional bolt11 invoices added to the
amounttag; feedback moved off kind 7 onto dedicated kind 7000. - Origin2023-07pablof7z drafted the first Data Vending Machine spec as
vending-machine.md, with examples following two days later.
Original Proposal - Credits
| Author | pablof7z (Pablo Fernandez) |
| First commit | 2023-07-25 — “rename file” (drafted as vending-machine.md from 2023-07-03) |
| Status | draft - unrecommended - optional — deprecated, kept for existing DVM software |
| Source | 90.md on GitHub - full change history |
11 See Also
- NIP-01 — the base event format, kinds, and tags this NIP builds on.
- NIP-89 — handler announcements; how providers advertise supported job kinds.
- NIP-04 — the (deprecated) encryption scheme mandated for private job params.
- NIP-44 — the modern encryption standard that replaced NIP-04.
- NIP-09 — delete requests; used to cancel a job request.
- NIP-57 — zaps; one of the two payment paths for job results.