mirror of
https://github.com/nostr-protocol/nips.git
synced 2024-12-23 00:45:53 -05:00
working
This commit is contained in:
parent
70ede5e67d
commit
b6f3e249cd
229
105.md
Normal file
229
105.md
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
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: `<endpoint>/<invoice_hash>/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": "<pubkey>",
|
||||||
|
"id": "<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)
|
Loading…
Reference in New Issue
Block a user