{
  "openapi": "3.1.0",
  "info": {
    "title": "Nov Pax Public API",
    "version": "1.0.0",
    "summary": "Versioned public REST endpoints for agents and developers.",
    "description": "Nov Pax publishes a small, unauthenticated REST API under\nthe stable /api/v1 namespace. Every operation has a\nunique operationId, closed request and response schemas, structured\nJSON errors, and machine-readable rate-limit hints.\n\nAuthentication: no API key is required for v1. Session-backed\nSanctuary routes are product internals and are not part of this API.\n\nVersioning: breaking changes ship at a new URL version. v1\nreceives at least 90 days' notice before sunset.\nDeprecation is signalled with the RFC 9745 Deprecation header, a\nrel=deprecation Link, and the RFC 8594 Sunset header once a removal\ndate is scheduled.",
    "contact": {
      "name": "Nov Pax",
      "email": "contact@novpax.org",
      "url": "https://www.novpax.org/contact"
    }
  },
  "externalDocs": {
    "description": "Nov Pax developer portal, quickstarts, and API policy.",
    "url": "https://www.novpax.org/developers"
  },
  "servers": [
    {
      "url": "https://www.novpax.org",
      "description": "Production"
    }
  ],
  "x-versioning-policy": {
    "strategy": "url-path",
    "currentVersion": "1",
    "noticeDays": 90,
    "deprecationHeader": "Deprecation",
    "sunsetHeader": "Sunset",
    "policyUrl": "https://www.novpax.org/developers/api-policy"
  },
  "x-cli": {
    "package": "@novpax/cli",
    "command": "npx @novpax/cli",
    "documentationUrl": "https://www.novpax.org/developers#cli"
  },
  "tags": [
    {
      "name": "discovery",
      "description": "API capabilities and links."
    },
    {
      "name": "health",
      "description": "Public service liveness."
    },
    {
      "name": "waitlist",
      "description": "Aggregate founder-list statistics."
    },
    {
      "name": "contact",
      "description": "Contact message delivery."
    },
    {
      "name": "downloads",
      "description": "Client installer discovery."
    },
    {
      "name": "sandbox",
      "description": "Side-effect-free integration testing."
    }
  ],
  "paths": {
    "/api/v1": {
      "get": {
        "tags": [
          "discovery"
        ],
        "operationId": "getPublicApiIndex",
        "summary": "Discover the Nov Pax public API.",
        "description": "Returns the active version, authentication policy, documentation links, and operation catalog.",
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "description": "Request the documented JSON representation.",
            "schema": {
              "type": "string",
              "enum": [
                "application/json"
              ]
            }
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Public API discovery document.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiIndex"
                }
              }
            }
          },
          "429": {
            "description": "Global public API quota exhausted.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying the request.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/health": {
      "get": {
        "tags": [
          "health"
        ],
        "operationId": "getApiHealth",
        "summary": "Check public API liveness.",
        "description": "Returns a coarse heartbeat without running or exposing privileged dependency probes.",
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "description": "Request the documented JSON representation.",
            "schema": {
              "type": "string",
              "enum": [
                "application/json"
              ]
            }
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The public API is reachable.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthStatus"
                }
              }
            }
          },
          "429": {
            "description": "Global public API quota exhausted.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying the request.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/waitlist/stats": {
      "get": {
        "tags": [
          "waitlist"
        ],
        "operationId": "getWaitlistStats",
        "summary": "Read aggregate founder-list statistics.",
        "description": "Returns total, trailing-24-hour, and trailing-seven-day signup counts. A datastore outage degrades to zeroed counts with source=fallback.",
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "description": "Request the documented JSON representation.",
            "schema": {
              "type": "string",
              "enum": [
                "application/json"
              ]
            }
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Current or gracefully degraded counts.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaitlistStats"
                }
              }
            }
          },
          "429": {
            "description": "Global public API quota exhausted.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying the request.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/contact": {
      "post": {
        "tags": [
          "contact"
        ],
        "operationId": "submitContactMessage",
        "summary": "Submit a contact message.",
        "description": "Submit a contact message to Nov Pax. The request is rate limited per IP. Use a real reply-to address and keep the message under 5,000 characters. This public operation needs no API key.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message accepted.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactSubmissionResult"
                }
              }
            }
          },
          "400": {
            "description": "The JSON body or a field is invalid.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Contact or global API quota exhausted.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying the request.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "502": {
            "description": "The email delivery provider failed.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "503": {
            "description": "Email delivery is not configured.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/downloads/sanctuary-desktop": {
      "get": {
        "tags": [
          "downloads"
        ],
        "operationId": "getSanctuaryDesktopInstaller",
        "summary": "Resolve the Sanctuary Desktop installer.",
        "description": "Returns a typed JSON document containing the current Windows installer URL; it does not redirect.",
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "description": "Request the documented JSON representation.",
            "schema": {
              "type": "string",
              "enum": [
                "application/json"
              ]
            }
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Current installer metadata.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DesktopDownload"
                }
              }
            }
          },
          "429": {
            "description": "Global public API quota exhausted.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying the request.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "503": {
            "description": "No installer is currently published.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sandbox/echo": {
      "post": {
        "tags": [
          "sandbox"
        ],
        "operationId": "echoSandboxPayload",
        "summary": "Validate an integration without side effects.",
        "description": "Echoes a small typed payload. It sends no email, creates no account, stores no data, and needs no API key.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SandboxEchoRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The validated payload echoed back.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxEchoResult"
                }
              }
            }
          },
          "400": {
            "description": "The JSON body or a field is invalid.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "413": {
            "description": "The request body is too large.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Global public API quota exhausted.",
            "headers": {
              "RateLimit-Policy": {
                "description": "Quota policy as an IETF Structured Field (draft-ietf-httpapi-ratelimit-headers-11).",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";q=100;w=60"
              },
              "RateLimit": {
                "description": "Current conservative quota hint as an IETF Structured Field.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public\";r=1;t=60"
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying the request.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ApiError": {
        "type": "object",
        "additionalProperties": false,
        "description": "JSON error envelope returned by every v1 failure.",
        "required": [
          "error",
          "errorDetails"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Flat summary identical to errorDetails.message."
          },
          "errorDetails": {
            "$ref": "#/components/schemas/ApiErrorDetails"
          }
        }
      },
      "ApiErrorDetails": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:_[a-z0-9]+)*$",
            "description": "Stable machine-readable snake_case code."
          },
          "message": {
            "type": "string",
            "description": "Human-readable summary."
          },
          "hint": {
            "type": "string",
            "description": "Concrete resolution or retry guidance."
          }
        }
      },
      "ApiIndex": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "version",
          "authentication",
          "documentation",
          "policy",
          "catalog",
          "openapi",
          "sandbox",
          "operations"
        ],
        "properties": {
          "name": {
            "type": "string",
            "const": "Nov Pax Public API"
          },
          "version": {
            "type": "string",
            "const": "v1"
          },
          "authentication": {
            "type": "string",
            "const": "none"
          },
          "documentation": {
            "type": "string",
            "format": "uri"
          },
          "policy": {
            "type": "string",
            "format": "uri"
          },
          "catalog": {
            "type": "string",
            "format": "uri"
          },
          "openapi": {
            "type": "string",
            "format": "uri"
          },
          "sandbox": {
            "type": "string",
            "format": "uri"
          },
          "operations": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "operationId",
                "method",
                "path"
              ],
              "properties": {
                "operationId": {
                  "type": "string"
                },
                "method": {
                  "type": "string",
                  "enum": [
                    "GET",
                    "POST"
                  ]
                },
                "path": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "HealthStatus": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "service",
          "version",
          "checkedAt"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "service": {
            "type": "string",
            "const": "Nov Pax Public API"
          },
          "version": {
            "type": "string",
            "const": "v1"
          },
          "checkedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WaitlistStats": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "total",
          "recent24h",
          "weeklyCount",
          "source"
        ],
        "properties": {
          "total": {
            "type": "integer",
            "minimum": 0
          },
          "recent24h": {
            "type": "integer",
            "minimum": 0
          },
          "weeklyCount": {
            "type": "integer",
            "minimum": 0
          },
          "source": {
            "type": "string",
            "enum": [
              "live",
              "fallback"
            ]
          }
        }
      },
      "ContactRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "email",
          "message"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 320
          },
          "topic": {
            "type": "string",
            "maxLength": 80
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "maxLength": 5000
          }
        }
      },
      "ContactSubmissionResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "receivedAt"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "receivedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DesktopDownload": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "platform",
          "downloadUrl"
        ],
        "properties": {
          "platform": {
            "type": "string",
            "const": "windows"
          },
          "downloadUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "SandboxEchoRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "message"
        ],
        "properties": {
          "message": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "metadata": {
            "type": "object",
            "maxProperties": 10,
            "additionalProperties": {
              "type": "string",
              "maxLength": 200
            }
          }
        }
      },
      "SandboxEchoResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "sandbox",
          "echo",
          "receivedAt"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "sandbox": {
            "type": "boolean",
            "const": true
          },
          "echo": {
            "$ref": "#/components/schemas/SandboxEchoRequest"
          },
          "receivedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  }
}