Skip to content

Instantly share code, notes, and snippets.

@blakejakopovic
Last active May 20, 2023 10:27
Show Gist options
  • Save blakejakopovic/eb82a2e417fd0846f2afcfc380988520 to your computer and use it in GitHub Desktop.
Save blakejakopovic/eb82a2e417fd0846f2afcfc380988520 to your computer and use it in GitHub Desktop.
NIP-YY - Nostr Service Definition

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

{
  "kind": 30YYY,
  "tags": [],
  "content": <services JSON array>,
  ...other fields

nostrBuild File Upload Service

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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 to help check for success or error, and also extract the response data

Prior work

https://github.com/michaelhall923/nostr-media-spec

@fishcakeday
Copy link

In general, service could broadcast a note with the offer and has an expiration. Clients could fetch it from the relays and interpret it.

@blakejakopovic
Copy link
Author

@fishcakeday

"How to verify/validate the service provider isn't an imposter" can be easily addressed with NIP-05. The service (I imagine) would have a domain. Domain would have HTTPS enabled. We could also use DNS TXT records if needed, publish npub in a special DNS TXT record and consider it an authentication.

Yep - that was my thought too. However I wasn't sure which NIP-05 it should use? [email protected] -- but that makes it dangerous for NIP providers to allow that service username. I think the DNS TXT record makes the most sense.

"Best way to query for services by provider, or by service type" we can utilize the hashtag NIP that is in place already.
I think we can likely query by event kind, and then perhaps by pubkey, or domain (NIP), and like you said, some kind of tag for service/s offered. I'm hoping we can put multiple services in a single event - at least different options like a paid and unpaid service (e.g. DeepL).

In general, service could broadcast a note with the offer and has an expiration. Clients could fetch it from the relays and interpret it.
I like the expiry idea, however maybe just a replaceable event it enough - but ideally perhaps both. Client apps would cache the service event when those services are chosen, and perhaps can/should query for any updated definitions. I'd like to avoid service definitions.. but perhaps it's valuable -- maybe event_id is good enough for versioning.

Thanks for feedback!

@fishcakeday
Copy link

I don't think event_id should be used as a version. It's best to have a separate field for that.

@Semisol
Copy link

Semisol commented May 20, 2023

Yep - that was my thought too. However I wasn't sure which NIP-05 it should use? [email protected]

Browsers cannot validate it. services: {} entry in nostr.json or a nostr-services.json is better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment