{
  "openapi": "3.1.0",
  "info": {
    "title": "Halulu API",
    "version": "1.0.0",
    "summary": "The HTTP surface of Halulu, an AI restaurant finder.",
    "description": "Halulu reads the Google Maps reviews for restaurants near a person and says which one is actually good for the dish they asked for, with verbatim review quotes as proof.\n\n**The only functional endpoint is the MCP server at `POST /api/mcp`.** It speaks the Model Context Protocol over Streamable HTTP using JSON-RPC 2.0, not REST. There is no REST resource API, no GraphQL API, no SDK and no CLI. The other paths here exist to let an agent authenticate and discover.\n\nAuthentication is OAuth 2.1: open dynamic client registration, authorization code with PKCE S256, and a human approving in a browser. There is no API key and no machine-only path to a user's data. The step by step walkthrough is at https://www.halulu.food/auth.md .\n\nOne search costs one credit from the user's monthly allowance: 3 a month on the free tier, 30 on paid. Every other tool is free to call.",
    "termsOfService": "https://www.halulu.food/privacy",
    "contact": {
      "name": "Halulu",
      "email": "eat@halulu.food",
      "url": "https://www.halulu.food/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.halulu.food/privacy"
    }
  },
  "servers": [
    {
      "url": "https://www.halulu.food",
      "description": "Production. The apex halulu.food redirects here."
    }
  ],
  "externalDocs": {
    "description": "Agent integration guide",
    "url": "https://www.halulu.food/agents.md"
  },
  "tags": [
    {
      "name": "MCP",
      "description": "The Model Context Protocol server. The only functional API."
    },
    {
      "name": "Discovery",
      "description": "Documents that let an agent find and describe this API."
    },
    {
      "name": "OAuth",
      "description": "OAuth 2.1 authorization server endpoints."
    }
  ],
  "security": [
    {
      "oauth2": [
        "openid",
        "profile",
        "email"
      ]
    }
  ],
  "paths": {
    "/api/mcp": {
      "post": {
        "operationId": "callMcp",
        "tags": [
          "MCP"
        ],
        "summary": "Call the Halulu MCP server",
        "description": "A JSON-RPC 2.0 request over MCP Streamable HTTP. Use an MCP client library rather than hand-rolling this. Standard methods: `initialize`, `tools/list`, `tools/call`, `resources/read`.\n\nTools: `halulu_search_restaurants` (costs one credit), `halulu_save_favorite`, `halulu_list_favorites`, `halulu_remove_favorite`, `halulu_share_restaurant`, `halulu_search_credits`, `halulu_clear_location`, `halulu_subscribe`.\n\nSet `Accept: application/json, text/event-stream`. A tool that streams answers as SSE.",
        "security": [
          {
            "oauth2": [
              "openid",
              "profile",
              "email"
            ]
          },
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              },
              "examples": {
                "listTools": {
                  "summary": "List the tools",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "tools/list"
                  }
                },
                "search": {
                  "summary": "Search for a craving",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tools/call",
                    "params": {
                      "name": "halulu_search_restaurants",
                      "arguments": {
                        "craving": "best falafel",
                        "location": "Amman"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC result, or an SSE stream when the client accepts one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON-RPC request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or an expired one. Read the WWW-Authenticate header and start discovery.",
            "headers": {
              "WWW-Authenticate": {
                "description": "Names the RFC 9728 metadata document.",
                "schema": {
                  "type": "string"
                },
                "example": "Bearer resource_metadata=\"https://www.halulu.food/.well-known/oauth-protected-resource\", scope=\"openid profile email\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          },
          "402": {
            "description": "The user's monthly search allowance is exhausted. Stop searching and offer halulu_subscribe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          }
        }
      }
    },
    "/api/mcp/server-card": {
      "get": {
        "operationId": "getServerCard",
        "tags": [
          "MCP",
          "Discovery"
        ],
        "security": [],
        "summary": "MCP server card",
        "description": "Transport, protocol versions and identity for the MCP server. Also served at /.well-known/mcp/server-card.json .",
        "responses": {
          "200": {
            "description": "The server card.",
            "content": {
              "application/mcp-server-card+json": {
                "schema": {
                  "$ref": "#/components/schemas/ServerCard"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/mcp.json": {
      "get": {
        "operationId": "getMcpManifest",
        "tags": [
          "MCP",
          "Discovery"
        ],
        "security": [],
        "summary": "MCP manifest, including the tool list",
        "responses": {
          "200": {
            "description": "The manifest.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpManifest"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "operationId": "getProtectedResourceMetadata",
        "tags": [
          "Discovery",
          "OAuth"
        ],
        "security": [],
        "summary": "OAuth protected resource metadata (RFC 9728)",
        "description": "Names the protected resource and its authorization server. This is where the 401 challenge points.",
        "responses": {
          "200": {
            "description": "The metadata document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedResourceMetadata"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server/api/mcp-auth": {
      "get": {
        "operationId": "getAuthorizationServerMetadata",
        "tags": [
          "Discovery",
          "OAuth"
        ],
        "security": [],
        "summary": "OAuth authorization server metadata (RFC 8414)",
        "description": "The issuer https://www.halulu.food/api/mcp-auth carries a path, so RFC 8414 section 3.1 INSERTS the well-known segment before that path.",
        "responses": {
          "200": {
            "description": "The metadata document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthorizationServerMetadata"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/api-catalog": {
      "get": {
        "operationId": "getApiCatalog",
        "tags": [
          "Discovery"
        ],
        "security": [],
        "summary": "API catalog (RFC 9727)",
        "responses": {
          "200": {
            "description": "A linkset naming every API document.",
            "content": {
              "application/linkset+json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiCatalog"
                }
              }
            }
          }
        }
      }
    },
    "/api/mcp-auth/oauth2/register": {
      "post": {
        "operationId": "registerClient",
        "tags": [
          "OAuth"
        ],
        "security": [],
        "summary": "Dynamic client registration (RFC 7591)",
        "description": "Open, no credential required. Returns a client_id.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientRegistration"
              },
              "examples": {
                "public": {
                  "value": {
                    "client_name": "Your agent",
                    "redirect_uris": [
                      "https://your-app.example/callback"
                    ],
                    "grant_types": [
                      "authorization_code",
                      "refresh_token"
                    ],
                    "response_types": [
                      "code"
                    ],
                    "token_endpoint_auth_method": "none"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The registered client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientRegistrationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid registration request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/api/mcp-auth/oauth2/authorize": {
      "get": {
        "operationId": "authorize",
        "tags": [
          "OAuth"
        ],
        "security": [],
        "summary": "Authorization endpoint. A human approves here, in a browser.",
        "description": "PKCE is mandatory and code_challenge_method must be S256. Include offline_access or no refresh token is issued and the connection dies after one hour.",
        "parameters": [
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "const": "code"
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uri"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "openid profile email offline_access"
          },
          {
            "name": "code_challenge",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code_challenge_method",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "const": "S256"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the sign-in and consent pages, then back to redirect_uri with a code."
          },
          "400": {
            "description": "Missing or invalid parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/api/mcp-auth/oauth2/token": {
      "post": {
        "operationId": "exchangeToken",
        "tags": [
          "OAuth"
        ],
        "security": [],
        "summary": "Token endpoint",
        "description": "Grants: authorization_code, refresh_token, client_credentials. client_credentials carries no user identity, so it reaches no user's quota, favorites or searches.",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Access token. Access tokens last 1 hour, refresh tokens 30 days and rotate on use.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request, invalid_grant or unsupported_grant_type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "invalid_client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/api/mcp-auth/oauth2/revoke": {
      "post": {
        "operationId": "revokeToken",
        "tags": [
          "OAuth"
        ],
        "security": [],
        "summary": "Revoke an access or refresh token (RFC 7009)",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "client_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Revoked, or the token was already invalid."
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/api/mcp-auth/oauth2/introspect": {
      "post": {
        "operationId": "introspectToken",
        "tags": [
          "OAuth"
        ],
        "security": [],
        "summary": "Token introspection (RFC 7662)",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Introspection response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntrospectionResponse"
                }
              }
            }
          },
          "401": {
            "description": "invalid_client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/api/mcp-auth/jwks": {
      "get": {
        "operationId": "getJwks",
        "tags": [
          "OAuth",
          "Discovery"
        ],
        "security": [],
        "summary": "JSON Web Key Set",
        "responses": {
          "200": {
            "description": "The signing keys.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Jwks"
                }
              }
            }
          }
        }
      }
    },
    "/api": {
      "get": {
        "operationId": "getApiIndex",
        "tags": [
          "Discovery"
        ],
        "security": [],
        "summary": "API root. Answers 401 with the auth challenge and an endpoint index.",
        "description": "Every functional endpoint under /api requires an OAuth 2.1 bearer token, so the root answers 401 and points at the RFC 9728 metadata. The body lists the real endpoints so one request is enough to orient.",
        "responses": {
          "401": {
            "description": "Always. This root holds no resource of its own.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                },
                "example": "Bearer resource_metadata=\"https://www.halulu.food/.well-known/oauth-protected-resource\", scope=\"openid profile email\""
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiIndex"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.1 with PKCE S256 and open dynamic client registration. A human approves in a browser.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://www.halulu.food/api/mcp-auth/oauth2/authorize",
            "tokenUrl": "https://www.halulu.food/api/mcp-auth/oauth2/token",
            "refreshUrl": "https://www.halulu.food/api/mcp-auth/oauth2/token",
            "scopes": {
              "openid": "Identify the signed-in Halulu user",
              "profile": "Read the user's basic profile",
              "email": "Read the user's email address",
              "offline_access": "Issue a refresh token. Without it the connection dies after one hour."
            }
          }
        }
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "The access token obtained from the OAuth flow."
      }
    },
    "schemas": {
      "JsonRpcRequest": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "integer",
              "string"
            ]
          },
          "method": {
            "type": "string",
            "examples": [
              "initialize",
              "tools/list",
              "tools/call",
              "resources/read"
            ]
          },
          "params": {
            "type": "object"
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": [
          "jsonrpc"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "integer",
              "string",
              "null"
            ]
          },
          "result": {
            "type": "object"
          }
        }
      },
      "JsonRpcError": {
        "type": "object",
        "description": "A JSON-RPC 2.0 error. Every failure from the MCP endpoint uses this shape. Never HTML.",
        "required": [
          "jsonrpc",
          "error"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "integer",
              "string",
              "null"
            ]
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "integer",
                "description": "-32001 unauthorized, -32000 application error, -32600 invalid request, -32601 method not found, -32602 invalid params."
              },
              "message": {
                "type": "string"
              },
              "data": {
                "description": "Optional detail."
              }
            }
          }
        },
        "examples": [
          {
            "jsonrpc": "2.0",
            "error": {
              "code": -32001,
              "message": "Unauthorized: Missing bearer token."
            },
            "id": null
          }
        ]
      },
      "OAuthError": {
        "type": "object",
        "description": "An OAuth 2.0 error response (RFC 6749 section 5.2). JSON, never HTML.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "invalid_request, invalid_client, invalid_grant, unauthorized_client, unsupported_grant_type, invalid_scope."
          },
          "error_description": {
            "type": "string"
          }
        },
        "examples": [
          {
            "error": "invalid_request",
            "error_description": "client_id is required"
          }
        ]
      },
      "ClientRegistration": {
        "type": "object",
        "required": [
          "redirect_uris"
        ],
        "properties": {
          "client_name": {
            "type": "string"
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "response_types": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "enum": [
              "none",
              "client_secret_basic",
              "client_secret_post"
            ]
          }
        }
      },
      "TokenRequest": {
        "type": "object",
        "required": [
          "grant_type"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "authorization_code",
              "refresh_token",
              "client_credentials"
            ]
          },
          "code": {
            "type": "string"
          },
          "redirect_uri": {
            "type": "string",
            "format": "uri"
          },
          "client_id": {
            "type": "string"
          },
          "code_verifier": {
            "type": "string"
          },
          "refresh_token": {
            "type": "string"
          }
        }
      },
      "ServerCard": {
        "type": "object",
        "required": [
          "name",
          "version",
          "description"
        ],
        "properties": {
          "$schema": {
            "type": "string",
            "format": "uri"
          },
          "name": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "maxLength": 100
          },
          "websiteUrl": {
            "type": "string",
            "format": "uri"
          },
          "serverUrl": {
            "type": "string",
            "format": "uri"
          },
          "icons": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "remotes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "const": "streamable-http"
                },
                "url": {
                  "type": "string",
                  "format": "uri"
                },
                "supportedProtocolVersions": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "McpManifest": {
        "type": "object",
        "required": [
          "name",
          "url",
          "transport",
          "tools"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "icon": {
            "type": "string",
            "format": "uri"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "transport": {
            "type": "string",
            "const": "streamable-http"
          },
          "capabilities": {
            "type": "object",
            "properties": {
              "tools": {
                "type": "boolean"
              },
              "resources": {
                "type": "boolean"
              }
            }
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "name",
                "description"
              ],
              "properties": {
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "ui": {
                  "type": "string"
                },
                "parameters": {
                  "type": "object"
                }
              }
            }
          },
          "quota": {
            "type": "object",
            "properties": {
              "unit": {
                "type": "string"
              },
              "free": {
                "type": "string"
              },
              "paid": {
                "type": "string"
              }
            }
          }
        }
      },
      "ProtectedResourceMetadata": {
        "type": "object",
        "required": [
          "resource",
          "authorization_servers"
        ],
        "properties": {
          "resource": {
            "type": "string",
            "format": "uri"
          },
          "authorization_servers": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "bearer_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "AuthorizationServerMetadata": {
        "type": "object",
        "required": [
          "issuer",
          "authorization_endpoint",
          "token_endpoint"
        ],
        "properties": {
          "issuer": {
            "type": "string",
            "format": "uri"
          },
          "authorization_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "token_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "registration_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "jwks_uri": {
            "type": "string",
            "format": "uri"
          },
          "revocation_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "introspection_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "grant_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "code_challenge_methods_supported": {
            "type": "array",
            "items": {
              "type": "string",
              "const": "S256"
            }
          }
        }
      },
      "ApiCatalog": {
        "type": "object",
        "required": [
          "linkset"
        ],
        "properties": {
          "linkset": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "anchor"
              ],
              "properties": {
                "anchor": {
                  "type": "string",
                  "format": "uri"
                },
                "item": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                },
                "service-desc": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                },
                "service-doc": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                },
                "service-meta": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          }
        }
      },
      "Jwks": {
        "type": "object",
        "required": [
          "keys"
        ],
        "properties": {
          "keys": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "kty": {
                  "type": "string"
                },
                "kid": {
                  "type": "string"
                },
                "alg": {
                  "type": "string"
                },
                "use": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ClientRegistrationResponse": {
        "type": "object",
        "required": [
          "client_id"
        ],
        "properties": {
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string"
          },
          "client_id_issued_at": {
            "type": "integer"
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      },
      "TokenResponse": {
        "type": "object",
        "required": [
          "access_token",
          "token_type"
        ],
        "properties": {
          "access_token": {
            "type": "string"
          },
          "token_type": {
            "type": "string",
            "const": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "description": "Seconds. 3600."
          },
          "refresh_token": {
            "type": "string",
            "description": "Only when offline_access was granted. Lasts 30 days and rotates on use."
          },
          "scope": {
            "type": "string"
          }
        }
      },
      "IntrospectionResponse": {
        "type": "object",
        "required": [
          "active"
        ],
        "properties": {
          "active": {
            "type": "boolean"
          },
          "scope": {
            "type": "string"
          },
          "client_id": {
            "type": "string"
          },
          "exp": {
            "type": "integer"
          }
        }
      },
      "ApiIndex": {
        "type": "object",
        "required": [
          "error",
          "endpoints"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "error_description": {
            "type": "string"
          },
          "documentation": {
            "type": "string",
            "format": "uri"
          },
          "endpoints": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      }
    }
  }
}
