nips/105.md
Coach Chuck b6f3e249cd working
2023-09-13 07:34:27 -06:00

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

  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.

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:

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