NIP-YY ====== Nostr Service Definition ------------------- `draft` `optional` `author:wakoinc` A standardised service definition event allowing services to broadcast services they provide, and client apps to impliment support for a service type that is inter-changable ### Motivation Nostr is an ecosystem for apps and services built on a common layer. If we can define services using a common definition model, we can allow better user control and choice of services, and reduce implementation costs for adding new services for client apps. This MVP1 is focused on REST/HTTP endpoints. This will allow - Client applications to add support for a service type, and users to select interchangable providers - Service providers to optionally advertise paid services (that may require auth) - Users to compare and pick the best value provider based on their needs Note: This NIP has been written to ignore commercials and price information, as it's a separate concern. ### Example use-cases - File hosting - Premium relays - CDNs - Translation services - NIP-05 providers ### Examples Nostr Event ```json { "kind": 30YYY, "tags": [], "content": , ...other fields ``` nostrBuild File Upload Service ```json { "service_type": "file_upload", "service_name": "nostrBuild media hosting", "auth_methods": [], "endpoint": "https://nostr.build/api/upload/ios.php", "Inputs": { "fileToUpload": "file" }, "headers": { "accept": "application/json" }, "validation" : { "fileToUpload": { "content_types": ["image/jpeg","image/gif","image/png","image/wepb","video/mp4"], "max_size_bytes": 26214400, "accept_multiple": false, "auth_required": false } } "Response": { "Success": "@", "Error": null, "Result": "@" } } ``` nostrimg File Upload Service ```json { "service_type": "file_upload", "service_name": "nostrimg media hosting", "auth_methods": [], "endpoint": "https://nostrimg.com/api/upload", "inputs": { "image": "file" }, "headers": { "accept": "application/json" }, "validation" : { "fileToUpload": { "content_types": ["image/jpeg","image/gif","image/png","image/wepb"], "max_size_bytes": 26214400, "accept_multiple": false, "auth_required": false } } "Response": { "Success": "success == `true` && status == `200`", "Error": "success != `true`", "Result": "data.link" } } ``` DeepL Free translation Service ```json { "service_type": "text-translation", "service_name": "DeepL Free translation", "auth_methods": ["API_KEY"], "auth_required": true, "endpoint": "https://api-free.deepl.com/v2/translate", "inputs": { "text": "text", "source_lang": "text", "target_lang": "text", }, "headers": { "accept": "application/json", "DeepL-Auth-Key": "${API_KEY}" }, "validation" : { "text": { "max-length": 2048 }, "source_lang": { "values": ["$ISO 639-1"], "transforms": ["uppercase"] }, "target_lang": { "values": ["$ISO 639-1"], "transforms": ["uppercase"] }, } "Response": { "Success": null, "Error": null, "Result": "@[0].text" } } ``` DeepL Pro translation Service ```json { "service_type": "text-translation", "service_name": "DeepL Pro translation", "auth_methods": ["API_KEY"], "auth_required": true, "endpoint": "https://api.deepl.com/v2/translate", "inputs": { "text": "text", "source_lang": "text", "target_lang": "text", }, "headers": { "accept": "application/json", "DeepL-Auth-Key": "${API_KEY}" }, "validation" : { "text": { "max-length": 10240 }, "source_lang": { "values": ["BG","ZH","CS","DA","NL","EN","ET","FI","FR","DE","EL","HU","ID","IT","JA","KO","LV","LT","NO","PL","PT","RO","RU","SK","SL","ES","SV","TR","UK"], "transforms": ["uppercase"] }, "target_lang": { "values": ["BG","ZH","CS","DA","NL","EN","ET","FI","FR","DE","EL","HU","ID","IT","JA","KO","LV","LT","NO","PL","PT","RO","RU","SK","SL","ES","SV","TR","UK"], "transforms": ["uppercase"] }, } "Response": { "Success": null, "Error": null, "Result": "@[0].text" } } ``` nokyctranslate translation Service ```json { "service_type": "text-translation", "service_name": "nokyctranslate translation", "auth_methods": ["API_KEY"], "auth_required": true, "endpoint": "https://translate.nokyctranslate.com/translate", // POST "inputs": { "text": "text", // transform field to "q" "source_lang": "text", // transform field to "source" "target_lang": "text", // transform field to "target" "api_key": "$API_KEY", }, "headers": { "accept": "application/json", }, "validation" : { "text": { "max-length": 1024 }, "source": { "values": ["BG","ZH","CS","DA","NL","EN","ET","FI","FR","DE","EL","HU","ID","IT","JA","KO","LV","LT","NO","PL","PT","RO","RU","SK","SL","ES","SV","TR","UK"], "transforms": ["uppercase"] }, "target": { "values": ["BG","ZH","CS","DA","NL","EN","ET","FI","FR","DE","EL","HU","ID","IT","JA","KO","LV","LT","NO","PL","PT","RO","RU","SK","SL","ES","SV","TR","UK"], "transforms": ["uppercase"] }, } "Response": { "Success": null, "Error": null, "Result": "@.translatedText" } } ``` nostr.wine filter Service ```json { "service_type": "relay", "service_name": "nostr.wine filter service", "auth_methods": ["RELAY_AUTH"], "auth_required": true, "endpoint": "wss://filter.nostr.wine", "inputs": {}, "headers": {}, "validation": {}, "Response": {} } ``` ## Areas that need work * How to verify/validate the service provider isn't an imposter * How to link service definitions to paid service offers * Defined list of auth types * How to allow AUTH for API keys (need dynamic inputs) * How to manage service API updates / lifecycle * We need to formalise the validation approach (syntax + transformations) * Can/do we need the ability to link status codes to success/fail? * How best to automate request API_KEY - as we likely don't need human interaction * Best way to query for services by provider, or by service type * Best way to query for service offers (membership or paid options) ## Appendix For response validation and data extraction, we're using [JMESPath](https://jmespath.org/) to help check for success or error, and also extract the response data ## Prior work https://github.com/michaelhall923/nostr-media-spec