{
  "openapi": "3.1.0",
  "info": {
    "title": "life.ayushrai.site API (Ayush Rai)",
    "version": "1.0.0",
    "summary": "Read-only questions, sandbox, batch lookup, and a public-read token for the same facts.",
    "description": "Public knowledge API for life.ayushrai.site. Sandbox and /ask need no token. POST /api/v1/jobs returns HTTP 202 Accepted with a Location poll URL (async job pattern). /api/v1 returns 401 until you follow auth.md. Soft rate limit: 120 requests per 60 seconds per client. See /developers/deprecation/ for versioning.",
    "contact": {
      "name": "Ayush Rai",
      "url": "https://life.ayushrai.site/sites/contact"
    },
    "x-api-versioning": "url-path (/api/v1)",
    "x-deprecation-policy": "https://life.ayushrai.site/developers/deprecation/",
    "x-sunset-policy": "Breaking changes ship with a Deprecation header and a Sunset date at least 30 days out on the retiring path.",
    "x-pricing": {
      "model": "free",
      "free_tier": true,
      "signup_required": false,
      "self_serve_registration": "https://life.ayushrai.site/oauth/register",
      "url": "https://life.ayushrai.site/pricing.md"
    }
  },
  "externalDocs": {
    "description": "Versioning and deprecation policy",
    "url": "https://life.ayushrai.site/developers/deprecation/"
  },
  "servers": [
    { "url": "https://life.ayushrai.site" }
  ],
  "tags": [
    { "name": "ask", "description": "Synchronous public questions" },
    { "name": "jobs", "description": "Async job pattern: POST returns 202 Accepted + Location; GET polls status" },
    { "name": "sandbox", "description": "Zero-auth public catalog" },
    { "name": "auth", "description": "Free self-serve registration and the public-read bearer token" }
  ],
  "paths": {
    "/ask": {
      "get": {
        "operationId": "askGet",
        "summary": "Ask a public question",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": true,
            "schema": { "type": "string", "minLength": 1 }
          },
          {
            "name": "streaming",
            "in": "query",
            "schema": { "type": "string", "enum": ["true", "false"] }
          }
        ],
        "responses": {
          "200": {
            "description": "NLWeb JSON or server-sent events",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AskResponse" }
              }
            },
            "headers": {
              "RateLimit-Limit": { "schema": { "type": "integer" } },
              "RateLimit-Remaining": { "schema": { "type": "integer" } }
            }
          },
          "400": {
            "description": "Missing query",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "askPost",
        "summary": "Ask a public question as JSON",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": { "type": "string" },
            "description": "Optional client key echoed back. Asks are safe to retry."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": { "type": "string", "minLength": 1 },
                  "prefer": {
                    "type": "object",
                    "properties": { "streaming": { "type": "boolean" } }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "NLWeb JSON or server-sent events",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AskResponse" }
              }
            },
            "headers": {
              "Idempotency-Key": { "schema": { "type": "string" } },
              "RateLimit-Limit": { "schema": { "type": "integer" } }
            }
          },
          "400": {
            "description": "Missing query",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/api/v1/sandbox": {
      "get": {
        "operationId": "sandbox",
        "summary": "Sandbox of public facts",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "default": 1 }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }
          }
        ],
        "responses": {
          "200": {
            "description": "Public catalog with X-Sandbox: true",
            "headers": {
              "X-Sandbox": { "schema": { "type": "string", "enum": ["true"] } },
              "RateLimit-Limit": { "schema": { "type": "integer" } }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CatalogResponse" }
              }
            }
          }
        }
      }
    },
    "/api/v1/batch": {
      "post": {
        "operationId": "batchAsk",
        "summary": "Answer up to 10 public questions in one request",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["queries"],
                "properties": {
                  "queries": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 10,
                    "items": { "type": "string", "minLength": 1 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-query NLWeb answers",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BatchResponse" }
              }
            }
          },
          "400": {
            "description": "Invalid batch",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/oauth/register": {
      "post": {
        "tags": ["auth"],
        "operationId": "registerClient",
        "summary": "Self-serve public client registration (RFC 7591)",
        "description": "Free, instant dynamic client registration. Issues a public client_id (token_endpoint_auth_method none, no secret). Exchange at /oauth/token for the public-read bearer.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ClientMetadata" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Client registered",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ClientRegistration" }
              }
            }
          },
          "400": {
            "description": "invalid_client_metadata or invalid_redirect_uri",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/OAuthError" }
              }
            }
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "tags": ["auth"],
        "operationId": "issueToken",
        "summary": "Exchange for the public-read bearer token",
        "description": "Returns the non-secret Bearer public-read token (scope knowledge:read). Free; no credentials required.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "grant_type": { "type": "string", "default": "public" },
                  "client_id": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Access token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TokenResponse" }
              }
            }
          },
          "400": {
            "description": "invalid_grant",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/OAuthError" }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs": {
      "get": {
        "tags": ["jobs"],
        "operationId": "describeAskJobs",
        "summary": "Describe the async job pattern",
        "description": "Machine-readable description of POST 202 Accepted + Location polling for /api/v1/jobs/{job_id}.",
        "responses": {
          "200": {
            "description": "Job API map",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/JobsIndex" }
              }
            }
          }
        }
      },
      "post": {
        "tags": ["jobs"],
        "operationId": "createAskJob",
        "summary": "Accept a batch of public questions and return a poll URL",
        "description": "Async job pattern: returns HTTP 202 Accepted with a Location header and a JSON body containing job_id, status=pending, and poll/status_url. Poll GET /api/v1/jobs/{job_id} until status=completed. Jobs are read-only and encode the query list in the job id so no private store is required.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["queries"],
                "properties": {
                  "queries": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 10,
                    "items": { "type": "string", "minLength": 1 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job accepted; poll Location",
            "headers": {
              "Location": {
                "description": "Absolute URL to poll for job completion",
                "schema": { "type": "string", "format": "uri" },
                "required": true
              },
              "Retry-After": {
                "description": "Suggested seconds before first poll",
                "schema": { "type": "integer" }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/JobAccepted" }
              }
            }
          },
          "400": {
            "description": "Invalid job",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs/{job_id}": {
      "get": {
        "tags": ["jobs"],
        "operationId": "getAskJob",
        "summary": "Poll an async ask job",
        "description": "Returns status=completed with results when the job id is valid. Use the Location URL from the 202 response.",
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "minLength": 1 }
          }
        ],
        "responses": {
          "200": {
            "description": "Completed job with per-query answers",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/JobCompleted" }
              }
            }
          },
          "404": {
            "description": "Unknown job id",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/api/v1": {
      "get": {
        "operationId": "readApi",
        "summary": "Protected public-read API",
        "security": [{ "publicRead": [] }],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "default": 1 }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }
          }
        ],
        "responses": {
          "200": {
            "description": "Profile summary when Bearer public-read is sent",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CatalogResponse" }
              }
            }
          },
          "401": {
            "description": "WWW-Authenticate points at protected-resource metadata",
            "headers": {
              "WWW-Authenticate": { "schema": { "type": "string" } }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "publicRead": {
        "type": "http",
        "scheme": "bearer",
        "description": "Non-secret bearer public-read from the token endpoint. See /auth.md."
      }
    },
    "schemas": {
      "ErrorObject": {
        "type": "object",
        "required": ["code", "message", "status"],
        "properties": {
          "code": { "type": "string" },
          "message": { "type": "string" },
          "status": { "type": "integer" },
          "hint": { "type": "string", "description": "How to resolve the error" },
          "docs": { "type": "string", "format": "uri" },
          "auth": { "type": "string", "format": "uri" }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "$ref": "#/components/schemas/ErrorObject" },
          "resource_metadata": { "type": "string", "format": "uri" }
        }
      },
      "AskMeta": {
        "type": "object",
        "required": ["response_type", "version"],
        "properties": {
          "response_type": { "type": "string" },
          "version": { "type": "string" }
        }
      },
      "AskResult": {
        "type": "object",
        "properties": {
          "url": { "type": "string", "format": "uri" },
          "name": { "type": "string" },
          "description": { "type": "string" },
          "score": { "type": "number" }
        }
      },
      "AskResponse": {
        "type": "object",
        "required": ["_meta", "query", "results"],
        "properties": {
          "_meta": { "$ref": "#/components/schemas/AskMeta" },
          "query": { "type": "string" },
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/AskResult" }
          }
        }
      },
      "Pagination": {
        "type": "object",
        "required": ["page", "limit", "total", "has_more"],
        "properties": {
          "page": { "type": "integer" },
          "limit": { "type": "integer" },
          "total": { "type": "integer" },
          "has_more": { "type": "boolean" },
          "next_page": { "type": ["integer", "null"] }
        }
      },
      "PageItem": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "description": { "type": "string" }
        }
      },
      "CatalogResponse": {
        "type": "object",
        "properties": {
          "sandbox": { "type": "boolean" },
          "mode": { "type": "string" },
          "notice": { "type": "string" },
          "resource": { "type": "string", "format": "uri" },
          "scope": { "type": "string" },
          "summary": { "type": "string" },
          "pages": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/PageItem" }
          },
          "pagination": { "$ref": "#/components/schemas/Pagination" }
        }
      },
      "BatchResponse": {
        "type": "object",
        "required": ["resource", "count", "results"],
        "properties": {
          "resource": { "type": "string", "format": "uri" },
          "count": { "type": "integer" },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": { "type": "integer" },
                "query": { "type": "string" },
                "answer": { "$ref": "#/components/schemas/AskResponse" },
                "error": { "$ref": "#/components/schemas/ErrorObject" }
              }
            }
          }
        }
      },
      "ClientMetadata": {
        "type": "object",
        "properties": {
          "client_name": { "type": "string" },
          "client_uri": { "type": "string", "format": "uri" },
          "redirect_uris": { "type": "array", "items": { "type": "string", "format": "uri" } },
          "grant_types": {
            "type": "array",
            "items": { "type": "string", "enum": ["public", "urn:ietf:params:oauth:grant-type:jwt-bearer"] }
          },
          "token_endpoint_auth_method": { "type": "string", "enum": ["none"] },
          "contacts": { "type": "array", "items": { "type": "string" } }
        }
      },
      "ClientRegistration": {
        "type": "object",
        "required": ["client_id", "client_id_issued_at", "token_endpoint_auth_method", "grant_types"],
        "properties": {
          "client_id": { "type": "string" },
          "client_id_issued_at": { "type": "integer" },
          "token_endpoint_auth_method": { "type": "string", "enum": ["none"] },
          "grant_types": { "type": "array", "items": { "type": "string" } },
          "response_types": { "type": "array", "items": { "type": "string" } },
          "scope": { "type": "string" },
          "client_name": { "type": "string" },
          "token_endpoint": { "type": "string", "format": "uri" }
        }
      },
      "TokenResponse": {
        "type": "object",
        "required": ["access_token", "token_type"],
        "properties": {
          "access_token": { "type": "string" },
          "token_type": { "type": "string", "enum": ["Bearer"] },
          "scope": { "type": "string" },
          "expires_in": { "type": "integer" }
        }
      },
      "OAuthError": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string" },
          "error_description": { "type": "string" }
        }
      },
      "JobsIndex": {
        "type": "object",
        "required": ["pattern", "create", "poll"],
        "properties": {
          "name": { "type": "string" },
          "pattern": { "type": "string", "enum": ["async"] },
          "create": { "type": "object" },
          "poll": { "type": "object" },
          "openapi": { "type": "string", "format": "uri" },
          "docs": { "type": "string", "format": "uri" }
        }
      },
      "JobAccepted": {
        "type": "object",
        "required": ["job_id", "id", "status", "resource", "poll", "status_url"],
        "properties": {
          "job_id": { "type": "string" },
          "id": { "type": "string" },
          "status": { "type": "string", "enum": ["pending"] },
          "resource": { "type": "string", "format": "uri" },
          "poll": { "type": "string", "format": "uri" },
          "status_url": { "type": "string", "format": "uri" },
          "message": { "type": "string" }
        }
      },
      "JobCompleted": {
        "type": "object",
        "required": ["job_id", "status", "resource", "count", "results"],
        "properties": {
          "job_id": { "type": "string" },
          "status": { "type": "string", "enum": ["completed"] },
          "resource": { "type": "string", "format": "uri" },
          "count": { "type": "integer" },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": { "type": "integer" },
                "query": { "type": "string" },
                "answer": { "$ref": "#/components/schemas/AskResponse" },
                "error": { "$ref": "#/components/schemas/ErrorObject" }
              }
            }
          }
        }
      }
    }
  }
}
