{
  "openapi": "3.1.0",
  "info": {
    "title": "JDHQ API",
    "version": "v1",
    "description": "Public REST API for Johnny.Decimal HQ. Manage your IDs, email subscriptions, and system downloads."
  },
  "servers": [
    {
      "url": "https://johnnydecimal.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Clerk API key. Generate one at https://accounts.johnnydecimal.com/user/api-keys."
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "example": "NOT_FOUND"
              },
              "message": {
                "type": "string",
                "example": "Category not found"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "ToggleSubscriptionRequest": {
        "type": "object",
        "properties": {
          "subscribe": {
            "type": "boolean"
          }
        },
        "required": [
          "subscribe"
        ]
      },
      "DownloadSystemRequest": {
        "type": "object",
        "properties": {
          "system": {
            "type": "string",
            "enum": [
              "las",
              "sbs"
            ]
          },
          "settings": {
            "type": "object",
            "properties": {
              "useEmoji": {
                "type": "boolean",
                "default": true,
                "description": "Append the emoji configured for each category/ID after the title.",
                "example": true
              },
              "useHeaderSquare": {
                "type": "boolean",
                "default": true,
                "description": "Prefix header IDs with the ■ glyph so they stand out from regular IDs.",
                "example": true
              },
              "includeStandardZeros": {
                "type": "boolean",
                "default": false,
                "description": "Include the A0 management categories and the full set of standard-zero IDs (.00, .02-.08). When false (default), only .01 Inbox and .09 Archive remain.",
                "example": false
              },
              "vaultName": {
                "type": "string",
                "description": "Obsidian vault name used in the Open-in-Obsidian URL scheme. Ignored unless the user opens the Obsidian bundle."
              },
              "casing": {
                "type": "string",
                "enum": [
                  "preserve",
                  "lower"
                ],
                "default": "preserve",
                "description": "Lowercase every name in the ZIP. Useful for shells/filesystems where case matters. Numeric prefixes (e.g. 11.10) are unaffected.",
                "example": "lower"
              },
              "spaceHandling": {
                "type": "string",
                "enum": [
                  "preserve",
                  "underscore",
                  "camelCase"
                ],
                "default": "preserve",
                "description": "Replace spaces in every name. `underscore` produces 11.10_personal_records. `camelCase` produces 11.10PersonalRecords. Combined with `casing: lower` you get 11.10personalRecords.",
                "example": "underscore"
              }
            }
          }
        },
        "required": [
          "system",
          "settings"
        ]
      }
    },
    "parameters": {}
  },
  "paths": {
    "/api/v1/subscriptions": {
      "put": {
        "summary": "Toggle email subscription",
        "description": "Subscribe or unsubscribe the authenticated user from the Johnny.Decimal mailing list. The user's email is resolved from their Clerk account.",
        "tags": [
          "Subscriptions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ToggleSubscriptionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        },
                        "subscribed": {
                          "type": "boolean",
                          "example": true
                        }
                      },
                      "required": [
                        "success",
                        "subscribed"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or no email found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/downloads": {
      "post": {
        "summary": "Download system as ZIP",
        "description": "Generate and download a Johnny.Decimal system (LAS or SBS) as a ZIP file containing the folder structure and JDex files. Requires product access to the requested system. (The endpoint also accepts Clerk session authentication for use by the in-browser SystemDownloadButton; that path is not part of the public API contract.)",
        "tags": [
          "Downloads"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DownloadSystemRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ZIP file containing the system folder structure and JDex",
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "User does not have access to the requested system",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {}
}