Skip to content

Latest commit

 

History

History
281 lines (220 loc) · 11 KB

105.md

File metadata and controls

281 lines (220 loc) · 11 KB

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:

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

Calculating Pricing

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.

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.

(optional) Zap Request / Receipt

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.

Offer Event

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.

Zap Request

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.

Lightning Invoice

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.

Zap Receipt

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.

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'
  }

  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 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, 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

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

Example Implementations