Convert from swagger to API docs.

Renamed swagger_api/openapi.yaml to api/openapi.yaml and updated references in docs/api.md.
Removed swagger add to requirements.txt
This commit is contained in:
gitglubber
2026-01-20 10:11:14 -08:00
parent 0ae2ca43bd
commit 854aa1f783
5 changed files with 349 additions and 640 deletions

View File

@@ -6,9 +6,9 @@ This page contains the interactive API documentation for ntfy. You can try out t
### Server Selection
The Swagger UI includes a server selector dropdown at the top of the page. By default, it's configured to use the **public ntfy.sh server**.
The API reference includes a server selector dropdown at the top of the page. By default, it's configured to use the **public ntfy.sh server**.
To use your own ntfy instance, edit `docs/swagger_api/openapi.yaml` and add your server URL to the `servers` section:
To use your own ntfy instance, edit `docs/api/openapi.yaml` and add your server URL to the `servers` section:
```yaml
servers:
@@ -22,10 +22,23 @@ After editing the file, rebuild the docs with `mkdocs build`.
### Authentication
Click the **Authorize** button (lock icon) in Swagger UI to add your access token. Use the format `Bearer <your_token>` or `Basic <base64_encoded_credentials>`.
Click the **Authorize** button (lock icon) in the API reference to add your access token. Use the format `Bearer <your_token>` or `Basic <base64_encoded_credentials>`.
### Try It Out
Click **Try it out** on any endpoint to test it directly. Parameters will be empty by default - enter your own values to test.
<swagger-ui src="swagger_api/openapi.yaml"/>
---
<script>
// Redirect to standalone Scalar page - use absolute path from root
var currentPath = window.location.pathname;
// Get base path (everything before /api/)
var basePath = currentPath.substring(0, currentPath.indexOf('/api/'));
if (basePath === -1 || basePath === '') {
basePath = '';
}
window.location.href = basePath + '/api/scalar.html';
</script>
If you are not redirected automatically, [click here to view the API Reference](api/scalar.html).

View File

@@ -34,6 +34,34 @@ info:
Requests may be rate-limited based on IP address or user tier.
## CORS Proxy
If you are running ntfy locally and accessing this documentation from "localhost" and attempting to make requests to a hosted ntfy server (like ntfy.sh) you may need to enable the "CORS Proxy" checkbox in the top right corner of the page.
This will proxy the requests through the Scalar server to the hosted ntfy server. **BE Aware** this will count against the rate limit of the Scalar Proxy Server. So you may recieve 429 responses from the remote ntfy server..
## Experimental Endpoints
The following endpoints are experimental and may be changed or removed in the future. Use at your own risk.
- Account Management Endpoints
- Admin Endpoints
## Add your own ntfy server
To add your own ntfy server to the drop down menu, add the following to the `servers` section of "docs/api/openapi.yaml" (or replace the exsiting server with your own https://myntfyserver.editinyaml.com):
```yaml
servers:
- url: https://your-ntfy-instance.com
description: Your custom server
```
Once you have added your own custom server you will need to run `mkdocs build` to rebuild the documentation.
'
version: 2.0.0
contact:
@@ -45,21 +73,21 @@ info:
servers:
- url: https://ntfy.sh
description: Public ntfy server
- url: https://MyNtfy.com
- url: https://myntfyserver.editinyaml.com
description: My NTFY Server
tags:
- name: Publish
description: Publishing messages to topics
- name: Subscribe
description: Subscribing to topics and receiving messages
- name: Account
description: User account management
- name: Admin
description: Administrative operations
- name: Matrix
description: Matrix push gateway
- name: WebPush
description: Web Push subscriptions
- name: Account - **EXPERIMENTAL**
description: User account management (EXPERIMENTAL) - THESE ENDPOINTS MAY CHANGE, BE REMOVED, OR ADDED TO IN THE FUTURE.
- name: Admin - **EXPERIMENTAL**
description: Administrative operations (EXPERIMENTAL) - THESE ENDPOINTS MAY CHANGE, BE REMOVED, OR ADDED TO IN THE FUTURE.
- name: Matrix
description: Matrix push gateway
- name: WebPush
description: Web Push subscriptions
paths:
/{topic}:
post:
@@ -606,7 +634,7 @@ paths:
/v1/health:
get:
tags:
- Admin
- Admin - **EXPERIMENTAL**
summary: Health check
description: Returns server health status
operationId: healthCheck
@@ -624,7 +652,7 @@ paths:
/v1/stats:
get:
tags:
- Admin
- Admin - **EXPERIMENTAL**
summary: Server statistics
description: Returns public server statistics
operationId: getStats
@@ -647,7 +675,7 @@ paths:
/v1/tiers:
get:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: List available tiers
description: Returns list of available subscription tiers with pricing
operationId: getTiers
@@ -682,7 +710,7 @@ paths:
/v1/users:
get:
tags:
- Admin
- Admin - **EXPERIMENTAL**
summary: List users
description: Returns list of all users (admin only)
operationId: listUsers
@@ -703,7 +731,7 @@ paths:
$ref: '#/components/responses/Forbidden'
post:
tags:
- Admin
- Admin - **EXPERIMENTAL**
summary: Create user
description: Creates a new user (admin only)
operationId: createUser
@@ -746,7 +774,7 @@ paths:
$ref: '#/components/responses/TooManyRequests'
put:
tags:
- Admin
- Admin - **EXPERIMENTAL**
summary: Update user
description: Updates an existing user (admin only)
operationId: updateUser
@@ -783,7 +811,7 @@ paths:
$ref: '#/components/responses/NotFound'
delete:
tags:
- Admin
- Admin - **EXPERIMENTAL**
summary: Delete user
description: Deletes a user (admin only)
operationId: deleteUser
@@ -812,7 +840,7 @@ paths:
/v1/users/access:
put:
tags:
- Admin
- Admin - **EXPERIMENTAL**
summary: Grant user access to topic
description: Grants a user access to a topic or topic pattern (admin only)
operationId: grantAccess
@@ -851,7 +879,7 @@ paths:
$ref: '#/components/responses/TooManyRequests'
delete:
tags:
- Admin
- Admin - **EXPERIMENTAL**
summary: Reset user access to topic
description: Resets user access to a topic (admin only)
operationId: resetAccess
@@ -881,7 +909,7 @@ paths:
/v1/account:
post:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Create account
description: Creates a new user account
operationId: createAccount
@@ -916,7 +944,7 @@ paths:
$ref: '#/components/responses/TooManyRequests'
get:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Get account info
description: Returns information about the authenticated user's account
operationId: getAccount
@@ -934,7 +962,7 @@ paths:
$ref: '#/components/responses/Unauthorized'
delete:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Delete account
description: Deletes the authenticated user's account
operationId: deleteAccount
@@ -963,7 +991,7 @@ paths:
/v1/account/password:
post:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Change password
description: Changes the authenticated user's password
operationId: changePassword
@@ -998,7 +1026,7 @@ paths:
/v1/account/token:
post:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Create access token
description: Creates a new API access token for the authenticated user
operationId: createToken
@@ -1030,7 +1058,7 @@ paths:
$ref: '#/components/responses/TooManyRequests'
patch:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Update access token
description: Updates an existing access token
operationId: updateToken
@@ -1067,7 +1095,7 @@ paths:
$ref: '#/components/responses/NotFound'
delete:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Delete access token
description: Deletes an access token
operationId: deleteToken
@@ -1095,7 +1123,7 @@ paths:
/v1/account/settings:
patch:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Update account settings
description: Updates the authenticated user's account settings
operationId: updateSettings
@@ -1130,7 +1158,7 @@ paths:
/v1/account/subscription:
post:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Add topic subscription
description: Subscribes to a topic
operationId: addSubscription
@@ -1159,7 +1187,7 @@ paths:
$ref: '#/components/responses/TooManyRequests'
patch:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Update topic subscription
description: Updates a topic subscription
operationId: updateSubscription
@@ -1188,7 +1216,7 @@ paths:
$ref: '#/components/responses/NotFound'
delete:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Delete topic subscription
description: Unsubscribes from a topic
operationId: deleteSubscription
@@ -1216,7 +1244,7 @@ paths:
/v1/account/reservation:
post:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Reserve topic
description: Reserves a topic for exclusive use
operationId: reserveTopic
@@ -1255,7 +1283,7 @@ paths:
/v1/account/reservation/{topic}:
delete:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Delete topic reservation
description: Removes a topic reservation
operationId: deleteReservation
@@ -1280,7 +1308,7 @@ paths:
/v1/account/phone:
put:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Add phone number
description: Adds and verifies a phone number for voice call notifications
operationId: addPhoneNumber
@@ -1312,7 +1340,7 @@ paths:
$ref: '#/components/responses/TooManyRequests'
delete:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Delete phone number
description: Removes a phone number
operationId: deletePhoneNumber
@@ -1341,7 +1369,7 @@ paths:
/v1/account/phone/verify:
put:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Request phone verification code
description: Requests a verification code via SMS
operationId: verifyPhoneNumber
@@ -1376,7 +1404,7 @@ paths:
/v1/account/billing/portal:
post:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Create billing portal session
description: Creates a Stripe billing portal session
operationId: createBillingPortal
@@ -1399,7 +1427,7 @@ paths:
/v1/account/billing/subscription:
post:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Create subscription
description: Creates a paid subscription via Stripe checkout
operationId: createSubscription
@@ -1434,7 +1462,7 @@ paths:
$ref: '#/components/responses/Unauthorized'
put:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Update subscription
description: Modifies an existing subscription
operationId: updateSubscription
@@ -1461,7 +1489,7 @@ paths:
$ref: '#/components/responses/Unauthorized'
delete:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Cancel subscription
description: Cancels the paid subscription
operationId: cancelSubscription
@@ -1476,7 +1504,7 @@ paths:
/v1/account/billing/subscription/success/{CHECKOUT_SESSION_ID}:
get:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Subscription checkout success
description: Handles successful Stripe checkout redirect
operationId: subscriptionCheckoutSuccess
@@ -1500,7 +1528,7 @@ paths:
/v1/account/billing/webhook:
post:
tags:
- Account
- Account - **EXPERIMENTAL**
summary: Stripe webhook handler
description: Handles incoming webhooks from Stripe
operationId: stripeWebhook
@@ -1695,7 +1723,7 @@ paths:
/metrics:
get:
tags:
- Admin
- Admin - **EXPERIMENTAL**
summary: Prometheus metrics
description: Returns Prometheus-compatible metrics
operationId: getMetrics
@@ -1806,14 +1834,11 @@ components:
schema:
type: integer
enum:
- 1
- 2
- 3
- 2
- 1
- 4
- 5
default:
value: 3
example: 3
min:
value: 1
urgent:
@@ -1915,7 +1940,6 @@ components:
- false
- 1
- 0
default: true
XFirebase:
name: X-Firebase
in: header
@@ -1927,7 +1951,6 @@ components:
- false
- 1
- 0
default: true
XMarkdown:
name: X-Markdown
in: header
@@ -1968,8 +1991,8 @@ components:
schema:
type: string
enum:
- true
- false
- true
- 1
- 0
- up

260
docs/api/scalar.html Normal file
View File

@@ -0,0 +1,260 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ntfy API Reference</title>
<link rel="icon" type="image/svg+xml" href="https://ntfy.sh/static/img/favicon.svg">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: #ffffff;
}
/* Header matching docs.ntfy.sh */
.header {
background: linear-gradient(to right, #317f6f, #14b8a6);
padding: 16px 20px;
color: white;
}
.header-content {
max-width: 1400px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 20px;
justify-content: space-between;
}
.header-text {
flex: 1;
}
.header-text h1 {
font-size: 24px;
font-weight: 700;
margin: 0;
color: white;
line-height: 1.2;
}
.header-text p {
font-size: 13px;
margin: 2px 0 0 0;
color: rgba(255, 255, 255, 0.9);
}
.header-links {
display: flex;
gap: 20px;
align-items: center;
}
.header-links a {
color: white;
text-decoration: none;
font-size: 14px;
font-weight: 500;
opacity: 0.9;
transition: opacity 0.2s;
}
.header-links a:hover {
opacity: 1;
}
/* Proxy toggle checkbox */
.proxy-toggle {
display: flex;
align-items: center;
gap: 8px;
margin-left: 20px;
padding: 8px 12px;
background: rgba(255, 255, 255, 0.15);
border-radius: 6px;
cursor: pointer;
transition: background 0.2s;
}
.proxy-toggle:hover {
background: rgba(255, 255, 255, 0.25);
}
.proxy-toggle input[type="checkbox"] {
cursor: pointer;
width: 16px;
height: 16px;
}
.proxy-toggle label {
color: white;
font-size: 13px;
font-weight: 500;
cursor: pointer;
user-select: none;
}
/* Scalar container */
#scalar-api-reference {
width: 100%;
min-height: calc(100vh - 100px);
}
/* Responsive */
@media (max-width: 768px) {
.header-content {
flex-direction: column;
gap: 10px;
text-align: center;
}
.header-links {
flex-wrap: wrap;
justify-content: center;
}
}
</style>
</head>
<body>
<!-- Header -->
<div class="header">
<div class="header-content">
<a href="/" style="display: flex; align-items: center; text-decoration: none;">
<img src="https://docs.ntfy.sh/static/img/ntfy.png" width="35" height="35" alt="ntfy logo" style="margin-right: 10px;">
</a>
<div class="header-text">
<h1>ntfy</h1>
<p>API Reference</p>
</div>
<div class="header-links">
<a href="/">Documentation Home</a>
<a href="https://ntfy.sh">ntfy.sh</a>
<a href="https://github.com/binwiederhier/ntfy">GitHub</a>
<div class="proxy-toggle" title="Enable CORS proxy to make requests through Scalar's proxy server">
<input type="checkbox" id="proxy-toggle-checkbox" checked>
<label for="proxy-toggle-checkbox">Use CORS Proxy</label>
</div>
</div>
</div>
</div>
<!-- Scalar API Reference -->
<div id="scalar-api-reference"></div>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.43.8/dist/browser/style.css" />
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.43.8/dist/browser/standalone.js"></script>
<script>
// Store the Scalar instance globally so we can reinitialize it
let scalarInstance = null;
// Get proxy preference from localStorage (default: true)
function getProxyEnabled() {
const stored = localStorage.getItem('scalar-proxy-enabled');
return stored === null ? true : stored === 'true';
}
// Save proxy preference to localStorage
function setProxyEnabled(enabled) {
localStorage.setItem('scalar-proxy-enabled', enabled.toString());
}
// Initialize checkbox state
function initProxyCheckbox() {
const checkbox = document.getElementById('proxy-toggle-checkbox');
if (checkbox) {
checkbox.checked = getProxyEnabled();
// Add change listener
checkbox.addEventListener('change', function() {
const enabled = checkbox.checked;
setProxyEnabled(enabled);
// Reinitialize Scalar with new proxy setting
reinitializeScalar();
});
}
}
// Reinitialize Scalar with current proxy setting
function reinitializeScalar() {
const targetElement = document.getElementById('scalar-api-reference');
if (targetElement) {
// Clear the container
targetElement.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Reinitializing API Reference...</div>';
// Reinitialize
setTimeout(initScalar, 100);
}
}
function initScalar() {
const targetElement = document.getElementById('scalar-api-reference');
if (!targetElement) {
setTimeout(initScalar, 50);
return;
}
if (typeof Scalar === 'undefined' || typeof Scalar.createApiReference !== 'function') {
setTimeout(initScalar, 50);
return;
}
// Get proxy setting
const useProxy = getProxyEnabled();
// Build config object
const config = {
url: './openapi.yaml',
layout: 'modern',
theme: 'default',
configuration: {
hideSidebar: false,
hideSearch: false,
hideModels: false,
hideDownloadButton: false,
hideTabs: false,
hideServerUrl: false,
hideInfo: false,
darkMode: false,
withDefaultFonts: false,
},
};
// Only add proxyUrl if enabled
if (useProxy) {
config.proxyUrl = 'https://proxy.scalar.com';
}
try {
const apiRef = Scalar.createApiReference('#scalar-api-reference', config);
console.log('Scalar initialized successfully', useProxy ? 'with proxy' : 'without proxy');
} catch (error) {
console.error('Error initializing Scalar:', error);
targetElement.innerHTML = '<div style="padding: 20px; color: red;">Error loading API reference: ' + error.message + '</div>';
}
}
// Initialize checkbox first
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
initProxyCheckbox();
initScalar();
});
} else {
initProxyCheckbox();
setTimeout(initScalar, 100);
}
// Also try on window load as fallback
window.addEventListener('load', function() {
initProxyCheckbox();
setTimeout(initScalar, 200);
});
</script>
</body>
</html>

View File

@@ -1,586 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ntfy API Reference</title>
<link rel="icon" type="image/svg+xml" href="https://ntfy.sh/static/img/favicon.svg">
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: #f8f9fa;
}
/* Header matching docs.ntfy.sh */
.header {
background: linear-gradient(to right, #317f6f, #14b8a6);
padding: 16px 20px;
color: white;
}
.header-content {
max-width: 1400px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 20px;
justify-content: space-between;
}
.header-logo {
flex-shrink: 0;
}
.header-text {
flex: 1;
}
.header-text h1 {
font-size: 24px;
font-weight: 700;
margin: 0;
color: white;
line-height: 1.2;
}
.header-text p {
font-size: 13px;
margin: 2px 0 0 0;
color: rgba(255, 255, 255, 0.9);
}
.header-links {
display: flex;
gap: 20px;
align-items: center;
}
.header-links a {
color: white;
text-decoration: none;
font-size: 14px;
font-weight: 500;
opacity: 0.9;
transition: opacity 0.2s;
}
.header-links a:hover {
opacity: 1;
}
/* Server config section */
.config-section {
background: white;
border-bottom: 1px solid #e5e7eb;
padding: 20px;
}
.config-content {
max-width: 1400px;
margin: 0 auto;
}
.config-row {
display: flex;
align-items: center;
gap: 15px;
flex-wrap: wrap;
margin-bottom: 15px;
}
.config-row:last-child {
margin-bottom: 0;
}
.config-label {
font-weight: 600;
color: #1a1a1a;
font-size: 14px;
}
.config-input {
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 14px;
width: 300px;
font-family: inherit;
}
.config-input:focus {
outline: none;
border-color: #317f6f;
box-shadow: 0 0 0 3px rgba(49, 127, 111, 0.1);
}
.config-button {
background: #317f6f;
color: white;
border: none;
padding: 8px 20px;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
font-size: 14px;
font-family: inherit;
transition: background 0.2s;
}
.config-button:hover {
background: #266959;
}
.info-text {
color: #6b7280;
font-size: 13px;
line-height: 1.5;
}
.info-text code {
background: #f3f4f6;
padding: 2px 6px;
border-radius: 4px;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 12px;
color: #dc2626;
}
/* Hide default topbar and servers dropdown */
.topbar {
display: none !important;
}
.swagger-ui .servers {
display: none !important;
}
/* Swagger UI container */
.swagger-container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
/* Swagger UI theme overrides */
.swagger-ui .info {
margin: 20px 0;
}
.swagger-ui .info h1,
.swagger-ui .info h2,
.swagger-ui .info h3,
.swagger-ui .info h4,
.swagger-ui .info h5 {
color: #1a1a1a !important;
}
.swagger-ui .info p,
.swagger-ui .info div {
color: #374151 !important;
}
.swagger-ui h2,
.swagger-ui h3,
.swagger-ui h4,
.swagger-ui h5 {
color: #1a1a1a !important;
}
.swagger-ui p,
.swagger-ui span,
.swagger-ui div {
color: #374151 !important;
}
/* Authorize button */
.swagger-ui .btn.authorize {
background: #317f6f !important;
color: white !important;
border: none;
padding: 10px 20px;
font-weight: 600;
}
.swagger-ui .btn.authorize:hover {
background: #266959 !important;
}
/* Operation blocks */
.swagger-ui .opblock {
background: #ffffff;
border: 1px solid #d1d5db;
border-radius: 6px;
margin-bottom: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.swagger-ui .opblock .opblock-summary {
border-color: #d1d5db;
padding: 12px;
}
.swagger-ui .opblock .opblock-summary-description {
color: #374151 !important;
font-size: 14px;
}
.swagger-ui .opblock .opblock-section-header {
background: #f9fafb;
border-color: #e5e7eb;
color: #1a1a1a !important;
}
/* Method colors */
.swagger-ui .opblock.get {
border-color: #317f6f;
}
.swagger-ui .opblock.get .opblock-summary-method {
background: #317f6f;
color: white;
}
.swagger-ui .opblock.post {
border-color: #3b82f6;
}
.swagger-ui .opblock.post .opblock-summary-method {
background: #3b82f6;
color: white;
}
.swagger-ui .opblock.put {
border-color: #f59e0b;
}
.swagger-ui .opblock.put .opblock-summary-method {
background: #f59e0b;
color: white;
}
.swagger-ui .opblock.delete {
border-color: #ef4444;
}
.swagger-ui .opblock.delete .opblock-summary-method {
background: #ef4444;
color: white;
}
.swagger-ui .opblock.patch {
border-color: #8b5cf6;
}
.swagger-ui .opblock.patch .opblock-summary-method {
background: #8b5cf6;
color: white;
}
/* Tables */
.swagger-ui table {
color: #1a1a1a !important;
}
.swagger-ui table thead tr {
background: #f9fafb;
}
.swagger-ui table thead th {
color: #1a1a1a !important;
border-bottom: 2px solid #e5e7eb;
}
.swagger-ui table tbody td {
color: #374151 !important;
border-bottom: 1px solid #e5e7eb;
}
/* Inputs */
.swagger-ui input[type="text"],
.swagger-ui input[type="password"],
.swagger-ui textarea {
background: white !important;
border: 1px solid #d1d5db !important;
color: #1a1a1a !important;
}
.swagger-ui input[type="text"]:focus,
.swagger-ui input[type="password"]:focus,
.swagger-ui textarea:focus {
border-color: #317f6f !important;
}
/* Try it out button */
.swagger-ui .btn.try-out__btn {
background: #317f6f !important;
color: white !important;
}
.swagger-ui .try-out button {
background: #317f6f !important;
color: white !important;
}
/* Code blocks and responses */
.swagger-ui .microlight {
background: #0a2e23 !important;
color: #ccfbf1 !important;
border-radius: 6px !important;
padding: 16px !important;
font-size: 14px !important;
line-height: 1.6 !important;
}
.swagger-ui pre {
background: #0a2e23 !important;
color: #ccfbf1 !important;
border-radius: 6px !important;
padding: 16px !important;
}
.swagger-ui pre code {
background: transparent !important;
color: #ccfbf1 !important;
padding: 0 !important;
}
.swagger-ui code {
background: #e6fffa !important;
color: #134e4a !important;
padding: 2px 6px !important;
border-radius: 4px !important;
font-family: 'Monaco', 'Menlo', 'Courier New', monospace !important;
font-size: 13px !important;
font-weight: 600 !important;
}
/* Responsive */
@media (max-width: 768px) {
.header-content {
flex-direction: column;
gap: 10px;
text-align: center;
}
.header-links {
flex-wrap: wrap;
justify-content: center;
}
.config-row {
flex-direction: column;
align-items: stretch;
}
.config-input {
width: 100%;
}
}
</style>
</head>
<body>
<!-- Header -->
<div class="header">
<div class="header-content">
<a href="/" style="display: flex; align-items: center; text-decoration: none;">
<img src="https://docs.ntfy.sh/static/img/ntfy.png" width="35" height="35" alt="ntfy logo" style="margin-right: 10px;">
</a>
<div class="header-text">
<h1>ntfy</h1>
<p>API Reference</p>
</div>
<div class="header-links">
<a href="/">Documentation Home</a>
<a href="https://ntfy.sh">ntfy.sh</a>
<a href="https://github.com/binwiederhier/ntfy">GitHub</a>
</div>
</div>
</div>
<!-- Server Configuration -->
<div class="config-section">
<div class="config-content">
<div class="config-row">
<span class="config-label">Server URL:</span>
<input type="text" id="server-url" class="config-input" value="" placeholder="https://ntfy.sh">
<button class="config-button" onclick="updateServer()">Update Server</button>
</div>
<div class="config-row">
<span class="info-text">
<strong>Authentication:</strong> Click the <strong>Authorize</strong> button (lock icon) in the top right to add your access token (e.g., <code>tk_your_token_here</code>).<br>
<strong>Try It Out:</strong> Click "Try it out" on any endpoint to test it directly in your browser.
</span>
</div>
</div>
</div>
<!-- Swagger UI -->
<div class="swagger-container">
<div id="swagger-ui"></div>
</div>
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-bundle.js" charset="UTF-8"></script>
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-standalone-preset.js" charset="UTF-8"></script>
<script src="https://unpkg.com/js-yaml@4/dist/js-yaml.min.js"></script>
<script>
// Global server URL variable
let customServerUrl = 'https://ntfy.sh';
function updateServer() {
const input = document.getElementById('server-url');
let url = input.value.trim();
// Remove trailing slash
if (url.endsWith('/')) {
url = url.slice(0, -1);
}
// Validate URL
try {
new URL(url);
customServerUrl = url;
// Update the input value
input.value = url;
// Show confirmation
alert('Server URL updated to: ' + url + '\n\nAll API requests will now use this server.');
// Reload the page to apply changes
location.reload();
} catch (e) {
alert('Please enter a valid URL (e.g., https://ntfy.sh or http://localhost:8080)');
}
}
// Clear all Swagger UI persisted data on page load
function clearSwaggerUIStorage() {
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && (key.startsWith('swagger') || key.includes('authorized') || key.includes('auth'))) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => localStorage.removeItem(key));
const sessionKeysToRemove = [];
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i);
if (key && (key.startsWith('swagger') || key.includes('authorized') || key.includes('auth'))) {
sessionKeysToRemove.push(key);
}
}
sessionKeysToRemove.forEach(key => sessionStorage.removeItem(key));
}
clearSwaggerUIStorage();
window.addEventListener('beforeunload', function() {
clearSwaggerUIStorage();
if (window.ui) {
const auths = window.ui.authActions;
if (auths) {
auths.logout();
}
}
});
function clearAllParameters() {
const inputs = document.querySelectorAll('.swagger-ui input[type="text"], .swagger-ui input[type="password"], .swagger-ui textarea, .swagger-ui input[type="url"], .swagger-ui input[type="email"]');
inputs.forEach(input => {
input.value = '';
});
const fileInputs = document.querySelectorAll('.swagger-ui input[type="file"]');
fileInputs.forEach(input => {
input.value = '';
});
const checkboxes = document.querySelectorAll('.swagger-ui input[type="checkbox"]');
checkboxes.forEach(box => {
box.checked = false;
});
const selects = document.querySelectorAll('.swagger-ui select');
selects.forEach(select => {
select.selectedIndex = 0;
});
}
window.onload = function() {
fetch('./openapi.yaml')
.then(response => response.text())
.then(yamlSpec => {
const spec = jsyaml.load(yamlSpec);
// Remove example values from parameters to prevent Swagger UI from using them as defaults
function removeExamples(obj) {
if (!obj || typeof obj !== 'object') return;
if (Array.isArray(obj)) {
obj.forEach(removeExamples);
return;
}
for (const key in obj) {
if (key === 'example' || key === 'examples') {
delete obj[key];
} else {
removeExamples(obj[key]);
}
}
}
removeExamples(spec);
// Initialize Swagger UI
const ui = SwaggerUIBundle({
spec: spec,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout",
defaultModelsExpandDepth: 1,
defaultModelExpandDepth: 1,
docExpansion: "list",
filter: true,
tryItOutEnabled: true,
persistAuthorization: true,
validatorUrl: null,
displayRequestDuration: true,
displayOperationId: false,
supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
requestInterceptor: (request) => {
// Replace base URL with custom server URL
if (request.url && request.url.startsWith('/')) {
request.url = customServerUrl + request.url;
}
return request;
},
responseInterceptor: (response) => {
return response;
}
});
window.ui = ui;
// Clear all parameters after UI loads
setTimeout(clearAllParameters, 100);
setTimeout(clearAllParameters, 500);
setTimeout(clearAllParameters, 1000);
})
.catch(error => {
console.error('Error loading spec:', error);
document.getElementById('swagger-ui').innerHTML =
'<div style="padding: 20px; color: red;">Error loading OpenAPI spec. Please make sure openapi.yaml exists.</div>';
});
};
</script>
</body>
</html>

View File

@@ -1,4 +1,3 @@
# The documentation uses 'mkdocs', which is written in Python
mkdocs-material
mkdocs-minify-plugin
mkdocs-swagger-ui-tag