{
  "openapi": "3.1.0",
  "info": {
    "title": "SuperPreneur identity provider: partner API",
    "version": "1.0.0",
    "description": "The routes a partner server calls. Everything else a partner needs is a route on the partner's own server, described in the guide."
  },
  "servers": [
    {
      "url": "https://superapp.ideapreneurnepal.com",
      "description": "Hosted service"
    },
    {
      "url": "https://superapp.ideapreneurnepal.com/sandbox",
      "description": "Sandbox: the same verify and keys routes with fake users"
    }
  ],
  "paths": {
    "/api/launch/verify": {
      "post": {
        "operationId": "verifyLaunchToken",
        "summary": "Verify a launch token",
        "description": "Proves which user opened your app. Call it from your server with your client credentials, passing the token the page received and the nonce your server issued for that page load. On success the token is used up: a second call with the same token answers `replayed`. A call that fails because of a wrong nonce or wrong credentials does not use the token up.",
        "security": [
          {
            "clientBasic": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyRequest"
              },
              "example": {
                "token": "eyJhbGciOiJFZERTQSIsImtp...Cjw40TDnu8Dg",
                "nonce": "8nZk2Z-Msfpa9GWFqIiP__s6vgeizMt0"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The token is valid. `name` is present only if the user approved the `profile` scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyResult"
                },
                "example": {
                  "sub": "6wVFC8ZW-uir4EjfnDOwK95teeinCnN1NrjjLgXvNgQ",
                  "scopes": [
                    "profile"
                  ],
                  "name": "Demo Player"
                }
              }
            }
          },
          "400": {
            "description": "The request or the token was refused. See the error codes below.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "replayed": {
                    "summary": "The token was already used",
                    "value": {
                      "error": "replayed"
                    }
                  },
                  "nonce_mismatch": {
                    "summary": "The nonce is not the one the token was issued for",
                    "value": {
                      "error": "nonce_mismatch"
                    }
                  },
                  "invalid_token": {
                    "summary": "Not a valid token",
                    "value": {
                      "error": "invalid_token"
                    }
                  },
                  "invalid_request": {
                    "summary": "A field is missing or malformed",
                    "value": {
                      "error": "invalid_request",
                      "detail": "nonce must be 16 to 128 characters: letters, digits, hyphen and underscore only.",
                      "fields": {
                        "nonce": [
                          "nonce must be 16 to 128 characters: letters, digits, hyphen and underscore only."
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Wrong client id or client secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_client"
                }
              }
            }
          },
          "403": {
            "description": "Your app has been suspended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "app_suspended"
                }
              }
            }
          },
          "429": {
            "description": "More than 600 calls a minute from one address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rate_limited",
                  "detail": "Request was throttled. Expected available in 30 seconds.",
                  "retry_after_seconds": 31
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/jwks.json": {
      "get": {
        "operationId": "getSigningKeys",
        "summary": "Get the public signing keys",
        "description": "Optional. The public keys that launch tokens are signed with, as a JSON Web Key Set. Use them only if you also want to check a token's signature yourself; the verify route is still required. Cache the keys and fetch again when you meet a `kid` you do not know.",
        "security": [],
        "responses": {
          "200": {
            "description": "The key set. Tokens are signed with EdDSA (Ed25519).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Jwks"
                },
                "example": {
                  "keys": [
                    {
                      "kty": "OKP",
                      "crv": "Ed25519",
                      "alg": "EdDSA",
                      "use": "sig",
                      "kid": "oGSRh-ZVy5_mJPgS",
                      "x": "sMZWbe0Cf1cFc3PEcU_oJk-Gl9PenmDSj1l-JvNTmwE"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/sandbox/api/launch/token": {
      "post": {
        "operationId": "mintSandboxToken",
        "summary": "Sandbox only: get a launch token",
        "description": "In production the SuperPreneur app delivers the launch token to your page. The sandbox has no app, so you ask for one here, for the nonce your server issued. Only the sandbox has this route. The token lasts 60 seconds and works once. Choose a `scenario` to get a token your server must refuse.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SandboxTokenRequest"
              },
              "example": {
                "nonce": "8nZk2Z-Msfpa9GWFqIiP__s6vgeizMt0",
                "user": "sita",
                "scenario": "ok"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A token for the fake user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxToken"
                },
                "example": {
                  "token": "eyJhbGciOiJFZERTQSIs...",
                  "expires_in": 60,
                  "user": "Sita Gurung",
                  "scenario": "ok"
                }
              }
            }
          },
          "400": {
            "description": "The nonce is malformed, or the user, client or scenario is not one of the allowed values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_request",
                  "detail": "nonce must be 16 to 128 characters: letters, digits, hyphen and underscore only.",
                  "fields": {
                    "nonce": [
                      "nonce must be 16 to 128 characters: letters, digits, hyphen and underscore only."
                    ]
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "clientBasic": {
        "type": "http",
        "scheme": "basic",
        "description": "HTTP Basic: your `client_id` is the user name and your `client_secret` is the password."
      }
    },
    "schemas": {
      "VerifyRequest": {
        "type": "object",
        "required": [
          "token",
          "nonce"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "The launch token your page received from SuperPreneur."
          },
          "nonce": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{16,128}$",
            "description": "The nonce your server issued for this page load."
          }
        }
      },
      "VerifyResult": {
        "type": "object",
        "required": [
          "sub",
          "scopes"
        ],
        "properties": {
          "sub": {
            "type": "string",
            "description": "The user's private id in your app only. Use it as the key for the user's data."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What the user approved. Today only `profile`."
          },
          "name": {
            "type": "string",
            "description": "The user's name. Present only when `profile` was approved."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "A short machine-readable code.",
            "enum": [
              "expired",
              "wrong_audience",
              "invalid_token",
              "nonce_mismatch",
              "replayed",
              "revoked",
              "invalid_request",
              "invalid_json",
              "invalid_client",
              "app_suspended",
              "rate_limited"
            ]
          },
          "detail": {
            "type": "string",
            "description": "A readable sentence. Present on most errors."
          },
          "fields": {
            "type": "object",
            "description": "For `invalid_request`: field name to a list of messages."
          },
          "retry_after_seconds": {
            "type": "integer",
            "description": "For `rate_limited`: how long to wait."
          }
        }
      },
      "Jwks": {
        "type": "object",
        "required": [
          "keys"
        ],
        "properties": {
          "keys": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "kty",
                "crv",
                "alg",
                "use",
                "kid",
                "x"
              ],
              "properties": {
                "kty": {
                  "type": "string",
                  "description": "Key type: `OKP`."
                },
                "crv": {
                  "type": "string",
                  "description": "Curve: `Ed25519`."
                },
                "alg": {
                  "type": "string",
                  "description": "Algorithm: `EdDSA`."
                },
                "use": {
                  "type": "string",
                  "description": "Key use: `sig`."
                },
                "kid": {
                  "type": "string",
                  "description": "Key id, also in each token's header."
                },
                "x": {
                  "type": "string",
                  "description": "The public key, Base64URL."
                }
              }
            }
          }
        }
      },
      "SandboxTokenRequest": {
        "type": "object",
        "required": [
          "nonce"
        ],
        "properties": {
          "nonce": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{16,128}$",
            "description": "The nonce your server issued."
          },
          "client_id": {
            "type": "string",
            "enum": [
              "sandbox_app",
              "sandbox_suspended"
            ],
            "description": "Which test app the token is for. Default `sandbox_app`."
          },
          "user": {
            "type": "string",
            "enum": [
              "sita",
              "bikash",
              "asha"
            ],
            "description": "Which fake user signs in. Default `sita`."
          },
          "scenario": {
            "type": "string",
            "enum": [
              "ok",
              "expired",
              "revoked",
              "wrong_audience",
              "invalid_token"
            ],
            "description": "What kind of token to make. Default `ok`."
          }
        }
      },
      "SandboxToken": {
        "type": "object",
        "required": [
          "token",
          "expires_in",
          "user",
          "scenario"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "The launch token."
          },
          "expires_in": {
            "type": "integer",
            "description": "Seconds it stays valid."
          },
          "user": {
            "type": "string",
            "description": "The fake user's name."
          },
          "scenario": {
            "type": "string",
            "description": "The scenario used."
          }
        }
      }
    }
  }
}
