{
  "openapi": "3.1.0",
  "info": {
    "title": "Acornonaut API",
    "version": "1.0.0",
    "description": "API for Acornonaut \u2014 Convert YouTube playlists into spaced repetition flashcards for effective learning.\n\nA **Model Context Protocol (MCP) server** is also available, letting you connect Claude Desktop or any MCP-compatible AI client directly to your Acornonaut account. See [MCP documentation](/api-docs#mcp) for setup instructions and the full list of 16 available tools.",
    "contact": {
      "name": "Acornonaut Support",
      "url": "https://acornonaut.app"
    }
  },
  "servers": [
    {
      "url": "https://acornonaut.app/api/v1",
      "description": "Production server"
    },
    {
      "url": "http://localhost:3000/api/v1",
      "description": "Development server"
    }
  ],
  "paths": {
    "/playlists": {
      "get": {
        "summary": "List playlists",
        "description": "Returns all playlists for the authenticated user.",
        "operationId": "listPlaylists",
        "tags": ["Playlists"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["pending", "processing", "completed", "failed", "cancelled"]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Playlist list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlaylistListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "summary": "Create playlist",
        "description": "Create a new playlist from a YouTube URL and queue video processing.",
        "operationId": "createPlaylist",
        "tags": ["Playlists"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePlaylistRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Playlist created"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/playlists/{id}": {
      "get": {
        "summary": "Get playlist",
        "operationId": "getPlaylist",
        "tags": ["Playlists"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Playlist object"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "summary": "Delete playlist",
        "operationId": "deletePlaylist",
        "tags": ["Playlists"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/playlists/{id}/cards": {
      "get": {
        "summary": "Get playlist cards",
        "operationId": "getPlaylistCards",
        "tags": ["Cards"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          },
          {
            "name": "archived",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Card list"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/playlists/{id}/process": {
      "post": {
        "summary": "Trigger playlist processing",
        "operationId": "processPlaylist",
        "tags": ["Playlists"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Processing started"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Already processing"
          }
        }
      }
    },
    "/playlists/{id}/export": {
      "get": {
        "summary": "Export playlist",
        "description": "Export playlist cards in Anki (.apkg), CSV, JSON, Markdown, or other formats.",
        "operationId": "exportPlaylist",
        "tags": ["Export"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["apkg", "csv", "json", "markdown", "logseq", "quizlet", "remnote", "notion"],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File download"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/export/all": {
      "get": {
        "summary": "Bulk export all playlists",
        "operationId": "exportAll",
        "tags": ["Export"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["csv", "json"],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File download"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/videos/add-by-url": {
      "post": {
        "summary": "Add video by URL",
        "description": "Add a YouTube video by URL. Creates a playlist if needed and optionally queues card generation.",
        "operationId": "addVideoByUrl",
        "tags": ["Videos"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddVideoByUrlRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Video added"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/cards/generate": {
      "post": {
        "summary": "Generate cards from transcript",
        "operationId": "generateCards",
        "tags": ["Cards"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerateCardsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generated cards"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/cards/{id}/review": {
      "post": {
        "summary": "Record card review",
        "description": "Submit a spaced repetition review result for a card. Updates difficulty, interval, and next review date via SM-2.",
        "operationId": "recordReview",
        "tags": ["Cards"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReviewRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Review recorded"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/cards/{id}/archive": {
      "patch": {
        "summary": "Archive card",
        "operationId": "archiveCard",
        "tags": ["Cards"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Card archived"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/account/delete": {
      "delete": {
        "summary": "Delete account",
        "description": "Permanently delete the authenticated user account and all associated data. Irreversible.",
        "operationId": "deleteAccount",
        "tags": ["Account"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Account deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/account/downgrade-info": {
      "get": {
        "summary": "Get downgrade info",
        "description": "Returns current tier, playlist count, and free-tier limit. Used to warn users before/after downgrade.",
        "operationId": "getDowngradeInfo",
        "tags": ["Account"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Downgrade info"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/usage/budget": {
      "get": {
        "summary": "Get usage budget",
        "description": "Returns current monthly video processing usage and limits for the authenticated user.",
        "operationId": "getUsageBudget",
        "tags": ["Usage"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Usage budget"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/credits/balance": {
      "get": {
        "summary": "Get credit balance",
        "operationId": "getCreditBalance",
        "tags": ["Credits"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Credit balance"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/keys": {
      "get": {
        "summary": "List API keys",
        "operationId": "listApiKeys",
        "tags": ["API Keys"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "API key list"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/keys/generate": {
      "post": {
        "summary": "Generate API key",
        "operationId": "generateApiKey",
        "tags": ["API Keys"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "API key created"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ClerkAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Clerk session token"
      },
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key for programmatic access"
      }
    },
    "schemas": {
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "clerkId": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "string"
          },
          "tier": {
            "type": "string",
            "enum": ["free", "pro"]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": ["id", "clerkId", "email", "tier"]
      },
      "UserStatus": {
        "type": "object",
        "properties": {
          "allowed": {
            "type": "boolean"
          },
          "tier": {
            "type": "string",
            "enum": ["free", "pro"]
          },
          "current": {
            "type": "integer",
            "description": "Current number of playlists"
          },
          "limit": {
            "type": "integer",
            "description": "Maximum number of playlists allowed"
          }
        },
        "required": ["allowed", "tier", "current", "limit"]
      },
      "Playlist": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "youtubeUrl": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "videoCount": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "completed",
              "completed_with_errors",
              "cancelled",
              "failed"
            ]
          },
          "progress": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "userId": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "youtubeUrl",
          "title",
          "videoCount",
          "status",
          "progress",
          "userId",
          "createdAt"
        ]
      },
      "PlaylistInfo": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "videoCount": {
            "type": "integer"
          },
          "videos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VideoInfo"
            }
          }
        },
        "required": ["id", "title", "videoCount", "videos"]
      },
      "Video": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "youtubeId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "transcript": {
            "type": "string"
          },
          "duration": {
            "type": "integer"
          },
          "topics": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["pending", "processing", "completed", "failed"]
          },
          "errorMessage": {
            "type": "string"
          },
          "playlistId": {
            "type": "string"
          }
        },
        "required": ["id", "youtubeId", "title", "status", "playlistId"]
      },
      "VideoInfo": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "duration": {
            "type": "integer"
          },
          "url": {
            "type": "string"
          }
        },
        "required": ["id", "title"]
      },
      "Card": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "front": {
            "type": "string"
          },
          "back": {
            "type": "string"
          },
          "videoId": {
            "type": "string"
          },
          "difficulty": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "interval": {
            "type": "integer",
            "minimum": 1
          },
          "nextReview": {
            "type": "string",
            "format": "date-time"
          },
          "archived": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "front",
          "back",
          "videoId",
          "difficulty",
          "interval",
          "nextReview",
          "archived"
        ]
      },
      "ReviewSession": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "userId": {
            "type": "string"
          },
          "cardId": {
            "type": "string"
          },
          "rating": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4
          },
          "reviewedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": ["id", "userId", "cardId", "rating", "reviewedAt"]
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "key": {
            "type": "string",
            "description": "Masked API key"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": ["id", "key", "createdAt"]
      },
      "Error": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string"
          },
          "code": {
            "type": "string"
          }
        },
        "required": ["message"]
      },
      "PlaylistListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Playlist"
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer"
              },
              "page": {
                "type": "integer"
              },
              "limit": {
                "type": "integer"
              }
            }
          }
        }
      },
      "CreatePlaylistRequest": {
        "type": "object",
        "required": ["url"],
        "properties": {
          "url": {
            "type": "string",
            "description": "YouTube playlist or video URL"
          },
          "title": {
            "type": "string",
            "description": "Optional custom title"
          },
          "autoGenerateCards": {
            "type": "boolean",
            "default": true,
            "description": "Queue card generation immediately"
          }
        }
      },
      "AddVideoByUrlRequest": {
        "type": "object",
        "required": ["url"],
        "properties": {
          "url": {
            "type": "string",
            "description": "YouTube video URL"
          },
          "playlistName": {
            "type": "string",
            "description": "Target playlist name (creates if missing)"
          },
          "autoGenerateCards": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "GenerateCardsRequest": {
        "type": "object",
        "required": ["transcript"],
        "properties": {
          "transcript": {
            "type": "string"
          },
          "videoTitle": {
            "type": "string"
          },
          "options": {
            "type": "object",
            "properties": {
              "maxCards": {
                "type": "integer"
              },
              "difficulty": {
                "type": "string",
                "enum": ["easy", "medium", "hard"]
              },
              "focusAreas": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ReviewRequest": {
        "type": "object",
        "required": ["rating"],
        "properties": {
          "rating": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4,
            "description": "1=again, 2=hard, 3=good, 4=easy"
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Authentication required",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Invalid request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Insufficient permissions",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "InternalServerError": {
        "description": "Internal server error",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "message": {
                  "type": "string",
                  "example": "Internal server error"
                }
              }
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "User",
      "description": "User management and status"
    },
    {
      "name": "Playlists",
      "description": "YouTube playlist processing and management"
    },
    {
      "name": "Cards",
      "description": "Flashcard management and generation"
    },
    {
      "name": "Review",
      "description": "Spaced repetition review system"
    },
    {
      "name": "API Keys",
      "description": "API key management"
    },
    {
      "name": "Export",
      "description": "Data export functionality"
    },
    {
      "name": "Billing",
      "description": "Subscription and billing management"
    },
    {
      "name": "Pipeline",
      "description": "Background processing pipeline"
    },
    {
      "name": "Documentation",
      "description": "API documentation endpoints"
    }
  ]
}
