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.
- 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
).
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
.
Pricing may be calculated using the equation: Total Cost = (cost_variable) * cost_units + cost_flat
This allows servers to offer fair & sustainable pricing based on the resources required to provide the service. Likewise this edit on the spec allows backward compatibility with previous pricing (which was flat only) as well as new use cases/business models.
One example would be a voice to text transcription service. The rate may be in seconds and the cost_variable may be 200 msats and cost_flat is 1000 msats. Therfore a 100 second transcription job should cost 21000 msats.
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.
This specification is designed to optionally support the use of zap requests and receipts as a form of proof of payment between the client and service. A receipt represents that the client has purchased a service from the endpoint, which can further be used for publishing metrics, reviews, and other forms of engagement over the nostr network.
When the service publishes an offer event, the service may include a receipt
tag, with the value set to true
.
Example: tags: [ [ "receipt": true ] ]
This indicates that the provider supports the issuance of NIP-57 zap receipts.
When the client makes the initial POST
request to the service endpoint
, the client may choose to include a zap-request
attribute in the header of the request. The value of the zap-request
attribute should be a valid kind 9734
zap request event, serialized as a JSON string.
The content field of the zap request should be identical to the body of the POST request, serialized as a JSON string.
The zap request should include an a
tag that references the kind:31402
offer event, as specified in NIP-01:
["a", <kind integer>:<32-bytes lowercase hex of a pubkey>:<d tag value>, <recommended relay URL, optional>]
The zap request should follow all standard conventions as defined in the NIP-57 specification.
When the service responds with a lightning invoice, if the client POST request has specified a valid zap-request
in the header, then the description hash of the invoice MUST match the sha256 hash of the complete zap request as a serialized JSON string. This behavior is defined as part of the NIP-57 specification.
If the service responds to a successAction
url request with a 200
range status code, and if the client POST request had previously specified a valid zap-request
in the header, the service should include a zap-receipt
attribute in the header of the response. This zap-receipt
attribute should include a valid kind 9735
zap receipt, serialized as a JSON string.
The zap request should follow all standard conventions as defined in the NIP-57 specification.
In addition to serving the zap-receipt
within the header of the response, the service may choose to publish the zap receipt to a relay.
kind:31402
.content
should be a JSON stringified version of the following JSON:
enum OfferingStatus {
up = 'UP',
down = 'DOWN',
closed = 'CLOSED'
}
enum CostUnits {
mins = 'MINS',
secs = 'SECS',
tokens = 'TOKENS'
}
interface OfferingContent = {
endpoint: string, // The POST endpoint you call to pay/fetch
status: OfferingStatus, // UP/DOWN/CLOSED
cost_fixed: number, // The fixed per call cost in mSats (b in y = mx + b)
cost_variable: number, // The variable cost based on request's units (i.e. 2000msats per min)
cost_units: number, //The units that denominate the variable cost
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.
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>"
}
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"
}
}
}
}
}
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.
- 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