NIP-105 ====== API Service Marketplace ------------------------------- `draft` `optional` `author:coachchuckff` `author:unclejim21` `author:cmdruid` This NIP defines `kind:31402` (a _parameterized replaceable event_) for broadcasting API services, endpoints and their costs in mSats. API services are offered ala carte to be paid via lightning invoices. API service providers will issue a `kind:31402` event as an API service offering. Clients can fetch `s` (service) tag offerings, call their endpoints, and pay per use. Creating an API service marketplace. ## Protocol flow 1. API Service provider creates and hosts API service offerings, they then issue a `kind:31402` event for each `s` ( service ) tag. 2. Client fetches API offerings by `s` tag. 3. Client chooses a provider(s) and POSTs to their `endpoint`. The POST body should match exactly what is required by the underlying API less the API key. ( Or following the note's `schema` ) 4. The service provider should validate their request and return a lightning invoice with a `successAction` url. The `successAction` url should be formatted as such: `//get_result` 5. The client pays. 6. The client will poll the `successAction` until the service provider has returned the result(s) matching exactly what the underlying API will produce ( Or following the note's `outputSchema` ). ## Server Functions ### Create Invoice Initally, the client will make a POST request to the `endpoint`. The POST body should be exactly what the underlying API is expecting less the API key. This information should also be represented in the `schema` portion of the `content` field. The service provider will then do the following: 1. Validate their API request 2. Issue a lightning invoice The issued lightning invoice will contain a `successAction` url formatted as so: ```typescript { tag: "url", url: `https://example.api/chat/${paymentHash}/get_result`, description: "Open to get the confirmation code for your purchase." } ``` From here the service provider can either listen and execute their API request "on-payment" or wait until the client calls `get_results`. ### Get Results Once the client has gotten the `successAction` url, they can issue a GET request. The service provider should respond in one of three ways: 1. 200 ( DONE ) - the service provider returns the results of the API as if it was coming from the product itself ( Errors included ) 2. 202 ( WORKING ) - the service provider returns a status that indicates the results are not ready yet. 3. 402 ( NOT PAID YET ) - the service provider should respond with a 402 error if the client has not paid yet. It is up to the Client to poll the `successAction` until a terminal result is reached. ## Event Reference and Examples ### Offering Event `kind:31402` `.content` should be a JSON stringified version of the following JSON: ```typescript enum OfferingStatus { up = 'UP', down = 'DOWN', closed = 'CLOSED' } interface OfferingContent = { endpoint: string, // The POST endpoint you call to pay/fetch status: OfferingStatus, // UP/DOWN/CLOSED cost: number, // The cost per call in mSats schema?: Object, // Reccomended - JSON schema for the POST body of the endpoint outputSchema?: Object, // Reccomended - JSON schema for the response of the call description?: string // Optional - Description for the end user } ``` `.tag` MUST include the following: - `s`, the service tag should simply be the underlying API endpoint of the service provided. For example if you are offering a ChatGPT service, you would set `s` = `https://api.openai.com/v1/chat/completions`. This way the service they are buying is implicit. - `d`, following **parameterized replaceable events** in [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md), this tag allows this event to be replaceable. Since there should only be one service per pubkey, the `d` tag should match the `s` tag. ### Example Service Event ```typescript const content = { endpoint: "https://example.api/chat/", status: "UP", cost: 5000, schema: {...}, outputSchema: {...}, description: "This will call the ChatGPT endpoint" } const event = { "kind": 31402, "created_at": 1675642635, "content": JSON.stringify(content), "tags": [ ["s", "https://api.openai.com/v1/chat/completions"] ["d", "https://api.openai.com/v1/chat/completions"], ], "pubkey": "", "id": "" } ``` ### Example Schema The following is for the `gpt-3.5-turbo` input schema: ```json { "type": "object", "properties": { "model": { "type": "string", "enum": ["gpt-3.5-turbo"] }, "messages": { "type": "array", "items": { "type": "object", "properties": { "role": { "type": "string", "enum": ["system", "user"] }, "content": { "type": "string" } }, "required": ["role", "content"] }, "minItems": 1 } }, "required": ["model", "messages"] } ``` The following is for the `gpt-3.5-turbo` output schema: ```json { "type": "object", "required": ["id", "object", "created", "model", "choices", "usage"], "properties": { "id": { "type": "string", "pattern": "^chatcmpl-[a-zA-Z0-9-]+$" }, "object": { "type": "string", "enum": ["chat.completion"] }, "created": { "type": "integer" }, "model": { "type": "string" }, "choices": { "type": "array", "items": { "type": "object", "required": ["index", "message"], "properties": { "index": { "type": "integer" }, "message": { "type": "object", "required": ["role", "content"], "properties": { "role": { "type": "string", "enum": ["assistant"] }, "content": { "type": "string" } } }, "finish_reason": { "type": "string", "enum": ["stop"] } } } }, "usage": { "type": "object", "required": ["prompt_tokens", "completion_tokens", "total_tokens"], "properties": { "prompt_tokens": { "type": "integer" }, "completion_tokens": { "type": "integer" }, "total_tokens": { "type": "integer" } } } } } ``` ### Safey It is not mandatory, but to raise the barrier to entry, clients should screen service provider's NIP-05 identifier. The domain used in their NIP-05, should be the same domain used for their endpoint. Clients may wish to create a whitelist of trusted service providers once tested. ### Problems - No data integrity - service providers can store/redistribute any data passed to them - Service providers could take payment and never return the product - Service providers are not gaurenteed to call the endpoint specified in the `s` tag - No recourse for errored API fetches - The `cost` field may not match the actual final price - No proof of purchase ### Example Implementations - [Server](https://github.com/Team-Pleb-TabConf-2023/nip-105-server) - [Client](https://github.com/Team-Pleb-TabConf-2023/nip-105-client)