7.6 KiB
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
- API Service provider creates and hosts API service offerings, they then issue a
kind:31402
event for eachs
( service ) tag. - Client fetches API offerings by
s
tag. - 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'sschema
) - The service provider should validate their request and return a lightning invoice with a
successAction
url. ThesuccessAction
url should be formatted as such:<endpoint>/<invoice_hash>/get_result
- The client pays.
- 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'soutputSchema
).
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:
- Validate their API request
- Issue a lightning invoice
The issued lightning invoice will contain a successAction
url formatted as so:
{
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:
- 200 ( DONE ) - the service provider returns the results of the API as if it was coming from the product itself ( Errors included )
- 202 ( WORKING ) - the service provider returns a status that indicates the results are not ready yet.
- 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:
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 sets
=https://api.openai.com/v1/chat/completions
. This way the service they are buying is implicit.d
, following parameterized replaceable events in NIP-01, this tag allows this event to be replaceable. Since there should only be one service per pubkey, thed
tag should match thes
tag.
Example Service Event
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:
{
"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:
{
"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