{
  "openapi": "3.1.0",
  "info": {
    "title": "Novari Study API",
    "version": "1.0.0",
    "summary": "HTTP API behind the Novari Study AI study coach.",
    "description": "Novari Study turns a student's notes, PDFs and past papers into a daily study mission. This document covers the health endpoints and the read-only study endpoints an agent acting for a signed-in student can call. Authentication is a Supabase Auth access token sent as a Bearer token. Scopes below are least-privilege labels: they are declared so agents can request narrow access, but the API currently authorises by the student's session and does not yet reject tokens for lacking a scope. There is no self-serve API-key programme; see /developers.",
    "contact": {
      "name": "Novari Study support",
      "email": "support@novaristudy.site",
      "url": "https://novaristudy.site/contact"
    },
    "termsOfService": "https://novaristudy.site/terms"
  },
  "servers": [
    {
      "url": "https://api.novaristudy.site",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Developer documentation",
    "url": "https://novaristudy.site/developers"
  },
  "tags": [
    {
      "name": "Health",
      "description": "Unauthenticated liveness and dependency checks."
    },
    {
      "name": "Study",
      "description": "Signed-in student's study state."
    }
  ],
  "paths": {
    "/api/ping": {
      "get": {
        "operationId": "ping",
        "summary": "Liveness ping",
        "description": "Returns a fixed message. Not rate limited.",
        "tags": [
          "Health"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Alive.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string",
                      "example": "pong"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Service and dependency health",
        "description": "Reports database, cache and AI-provider health. `status` is `healthy` or `unhealthy`.",
        "tags": [
          "Health"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Health report.",
            "headers": {
              "RateLimit": {
                "description": "Remaining budget in the current window (IETF RateLimit header).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy, e.g. 800;w=900 (800 requests per 900 seconds).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "timestamp"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "healthy",
                        "unhealthy"
                      ]
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "database": {
                      "type": "object",
                      "properties": {
                        "healthy": {
                          "type": "boolean"
                        },
                        "configured": {
                          "type": "boolean"
                        }
                      }
                    },
                    "redis": {
                      "type": "object",
                      "properties": {
                        "healthy": {
                          "type": "boolean"
                        },
                        "configured": {
                          "type": "boolean"
                        }
                      }
                    },
                    "openrouter": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait Retry-After seconds; RateLimit and RateLimit-Policy headers describe the budget.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Health check failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/dashboard/up-next": {
      "get": {
        "operationId": "getUpNext",
        "summary": "What to study next",
        "description": "The single highest-impact item the coach picked for this student.",
        "tags": [
          "Study"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "study:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "JSON payload.",
            "headers": {
              "RateLimit": {
                "description": "Remaining budget in the current window (IETF RateLimit header).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy, e.g. 800;w=900 (800 requests per 900 seconds).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token (code MISSING_AUTH_HEADER / MISSING_BEARER_TOKEN).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait Retry-After seconds; RateLimit and RateLimit-Policy headers describe the budget.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/dashboard/daily-queue": {
      "get": {
        "operationId": "getDailyQueue",
        "summary": "Today's study queue",
        "description": "Items due today, ordered by the coach.",
        "tags": [
          "Study"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "study:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "JSON payload.",
            "headers": {
              "RateLimit": {
                "description": "Remaining budget in the current window (IETF RateLimit header).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy, e.g. 800;w=900 (800 requests per 900 seconds).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token (code MISSING_AUTH_HEADER / MISSING_BEARER_TOKEN).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait Retry-After seconds; RateLimit and RateLimit-Policy headers describe the budget.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/dashboard/mastery": {
      "get": {
        "operationId": "getMastery",
        "summary": "Mastery by topic",
        "description": "Per-topic mastery used for exam-readiness.",
        "tags": [
          "Study"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "study:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "JSON payload.",
            "headers": {
              "RateLimit": {
                "description": "Remaining budget in the current window (IETF RateLimit header).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy, e.g. 800;w=900 (800 requests per 900 seconds).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token (code MISSING_AUTH_HEADER / MISSING_BEARER_TOKEN).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait Retry-After seconds; RateLimit and RateLimit-Policy headers describe the budget.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/planner/plans": {
      "get": {
        "operationId": "listStudyPlans",
        "summary": "List study plans",
        "description": "Study plans the student has generated.",
        "tags": [
          "Study"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "study:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "JSON payload.",
            "headers": {
              "RateLimit": {
                "description": "Remaining budget in the current window (IETF RateLimit header).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy, e.g. 800;w=900 (800 requests per 900 seconds).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token (code MISSING_AUTH_HEADER / MISSING_BEARER_TOKEN).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait Retry-After seconds; RateLimit and RateLimit-Policy headers describe the budget.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/planner/upcoming-sessions": {
      "get": {
        "operationId": "listUpcomingSessions",
        "summary": "Upcoming planner sessions",
        "description": "Scheduled study sessions in the near future.",
        "tags": [
          "Study"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "study:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "JSON payload.",
            "headers": {
              "RateLimit": {
                "description": "Remaining budget in the current window (IETF RateLimit header).",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy, e.g. 800;w=900 (800 requests per 900 seconds).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token (code MISSING_AUTH_HEADER / MISSING_BEARER_TOKEN).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Wait Retry-After seconds; RateLimit and RateLimit-Policy headers describe the budget.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Supabase Auth access token of the signed-in student."
      },
      "oauth2": {
        "type": "oauth2",
        "description": "Scoped access. Tokens are issued by the Supabase Auth issuer; scope names are the least-privilege labels below.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://fewfjywcstisaifsvrbo.supabase.co/auth/v1/oauth/authorize",
            "tokenUrl": "https://fewfjywcstisaifsvrbo.supabase.co/auth/v1/oauth/token",
            "scopes": {
              "profile:read": "Read the signed-in student's profile and plan tier.",
              "study:read": "Read study state: daily queue, up-next mission, mastery and planner sessions.",
              "study:write": "Create or change study content: documents, flashcards, quizzes and plans."
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Error body. Gateway responses use the nested `error` object; some legacy API routes return `error` as a plain string with an optional `code`.",
        "properties": {
          "error": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "required": [
                  "code",
                  "message"
                ],
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  },
                  "hint": {
                    "type": "string"
                  },
                  "status": {
                    "type": "integer"
                  },
                  "docs": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            ]
          },
          "code": {
            "type": "string",
            "description": "Machine-readable code on legacy-shaped errors, e.g. MISSING_BEARER_TOKEN."
          }
        },
        "required": [
          "error"
        ]
      }
    }
  }
}