{
  "openapi": "3.1.0",
  "info": {
    "title": "VoidNote API",
    "version": "1.0.0",
    "description": "Zero-knowledge self-destructing notes. The server stores only encrypted blobs — plaintext never leaves the client. Encryption: AES-256-GCM. Key derivation: SHA-256(hex_decode(secret)).",
    "contact": {
      "email": "api@voidnote.net"
    }
  },
  "servers": [
    {
      "url": "https://voidnote.net",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from your dashboard (starts with vn_)"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "CreateNoteRequest": {
        "type": "object",
        "required": [
          "encrypted",
          "iv"
        ],
        "properties": {
          "encrypted": {
            "type": "string",
            "description": "AES-256-GCM ciphertext (hex-encoded)",
            "example": "a1b2c3..."
          },
          "iv": {
            "type": "string",
            "description": "12-byte GCM nonce (hex-encoded)",
            "example": "000102030405060708090a0b"
          },
          "viewLimit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 1,
            "description": "How many times the note can be read before destruction"
          },
          "expiresIn": {
            "type": "integer",
            "minimum": 60,
            "maximum": 2592000,
            "default": 3600,
            "description": "Lifetime in seconds (60–2592000, default 3600)"
          }
        }
      },
      "CreateNoteResponse": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "description": "32-char hex tokenId (server-generated)",
            "example": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
          },
          "url": {
            "type": "string",
            "description": "Base URL without secret — append #<secret> to get the full shareable link",
            "example": "https://voidnote.net/n/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "viewLimit": {
            "type": "integer"
          }
        }
      },
      "ConsumeNoteResponse": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string"
          },
          "encrypted": {
            "type": "string",
            "description": "AES-256-GCM ciphertext (hex). Decrypt with: key=SHA-256(hex_decode(secret))"
          },
          "iv": {
            "type": "string",
            "description": "12-byte GCM nonce (hex)"
          },
          "algorithm": {
            "type": "string",
            "enum": [
              "AES-256-GCM"
            ]
          },
          "keyDerivation": {
            "type": "string",
            "enum": [
              "SHA-256"
            ],
            "description": "key = SHA-256(hex_decode(secret_from_url_fragment))"
          },
          "viewsRemaining": {
            "type": "integer"
          },
          "destroyed": {
            "type": "boolean",
            "description": "true if this was the last view"
          }
        }
      },
      "NoteMetaResponse": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string"
          },
          "exists": {
            "type": "boolean"
          },
          "type": {
            "type": "string",
            "enum": [
              "secure",
              "pipe"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "viewLimit": {
            "type": "integer"
          },
          "viewsRemaining": {
            "type": "integer"
          }
        }
      }
    }
  },
  "paths": {
    "/api/v1/note": {
      "post": {
        "summary": "Create a note",
        "description": "Encrypt content client-side and store the ciphertext. The server generates a tokenId and never sees plaintext. Append #<your_secret> to the returned url to form the full shareable link.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateNoteRequest"
              },
              "example": {
                "encrypted": "a1b2c3...",
                "iv": "000102030405060708090a0b",
                "viewLimit": 1,
                "expiresIn": 3600
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Note created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateNoteResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body"
          },
          "401": {
            "description": "Not authenticated"
          },
          "402": {
            "description": "No credits remaining"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/v1/note/{tokenId}/consume": {
      "get": {
        "summary": "Consume (read + burn) a note",
        "description": "Returns the encrypted blob and decrements the view count. On the last view, the note is permanently destroyed. Decrypt locally: key = SHA-256(hex_decode(secret)), then AES-256-GCM decrypt.",
        "security": [],
        "parameters": [
          {
            "name": "tokenId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{32}$"
            },
            "description": "32-char hex tokenId from the shareable URL"
          }
        ],
        "responses": {
          "200": {
            "description": "Encrypted blob returned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConsumeNoteResponse"
                }
              }
            }
          },
          "404": {
            "description": "Note not found or already destroyed"
          },
          "410": {
            "description": "Note destroyed — all views used"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/v1/note/{tokenId}/meta": {
      "get": {
        "summary": "Peek at note metadata",
        "description": "Returns metadata without consuming a view. Safe to call before revealing.",
        "security": [],
        "parameters": [
          {
            "name": "tokenId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{32}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Note metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoteMetaResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/v1/vault/{tokenId}/meta": {
      "get": {
        "summary": "Peek at vault file metadata",
        "security": [],
        "parameters": [
          {
            "name": "tokenId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{32}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string"
                    },
                    "exists": {
                      "type": "boolean"
                    },
                    "fileName": {
                      "type": "string"
                    },
                    "sizeBytes": {
                      "type": "integer"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "expiresAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/vault/{tokenId}/consume": {
      "get": {
        "summary": "Download and destroy a vault file",
        "description": "Returns the raw encrypted binary. IV is in the X-Vault-IV response header. Decrypt with AES-256-GCM using key=SHA-256(hex_decode(secret)). Single-use — file is destroyed after download.",
        "security": [],
        "parameters": [
          {
            "name": "tokenId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{32}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Encrypted binary",
            "headers": {
              "X-Vault-IV": {
                "schema": {
                  "type": "string"
                },
                "description": "12-byte GCM nonce (hex)"
              },
              "X-VoidNote-Algorithm": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "AES-256-GCM"
                  ]
                }
              },
              "X-VoidNote-Key-Derivation": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "SHA-256"
                  ]
                }
              }
            },
            "content": {
              "application/octet-stream": {}
            }
          },
          "404": {
            "description": "File not found or already downloaded"
          }
        }
      }
    }
  }
}