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 ### 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 ```yaml
servers: servers:
@@ -22,10 +22,23 @@ After editing the file, rebuild the docs with `mkdocs build`.
### Authentication ### 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 ### 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. 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. 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 version: 2.0.0
contact: contact:
@@ -45,21 +73,21 @@ info:
servers: servers:
- url: https://ntfy.sh - url: https://ntfy.sh
description: Public ntfy server description: Public ntfy server
- url: https://MyNtfy.com - url: https://myntfyserver.editinyaml.com
description: My NTFY Server description: My NTFY Server
tags: tags:
- name: Publish - name: Publish
description: Publishing messages to topics description: Publishing messages to topics
- name: Subscribe - name: Subscribe
description: Subscribing to topics and receiving messages description: Subscribing to topics and receiving messages
- name: Account - name: Account - **EXPERIMENTAL**
description: User account management description: User account management (EXPERIMENTAL) - THESE ENDPOINTS MAY CHANGE, BE REMOVED, OR ADDED TO IN THE FUTURE.
- name: Admin - name: Admin - **EXPERIMENTAL**
description: Administrative operations description: Administrative operations (EXPERIMENTAL) - THESE ENDPOINTS MAY CHANGE, BE REMOVED, OR ADDED TO IN THE FUTURE.
- name: Matrix - name: Matrix
description: Matrix push gateway description: Matrix push gateway
- name: WebPush - name: WebPush
description: Web Push subscriptions description: Web Push subscriptions
paths: paths:
/{topic}: /{topic}:
post: post:
@@ -606,7 +634,7 @@ paths:
/v1/health: /v1/health:
get: get:
tags: tags:
- Admin - Admin - **EXPERIMENTAL**
summary: Health check summary: Health check
description: Returns server health status description: Returns server health status
operationId: healthCheck operationId: healthCheck
@@ -624,7 +652,7 @@ paths:
/v1/stats: /v1/stats:
get: get:
tags: tags:
- Admin - Admin - **EXPERIMENTAL**
summary: Server statistics summary: Server statistics
description: Returns public server statistics description: Returns public server statistics
operationId: getStats operationId: getStats
@@ -647,7 +675,7 @@ paths:
/v1/tiers: /v1/tiers:
get: get:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: List available tiers summary: List available tiers
description: Returns list of available subscription tiers with pricing description: Returns list of available subscription tiers with pricing
operationId: getTiers operationId: getTiers
@@ -682,7 +710,7 @@ paths:
/v1/users: /v1/users:
get: get:
tags: tags:
- Admin - Admin - **EXPERIMENTAL**
summary: List users summary: List users
description: Returns list of all users (admin only) description: Returns list of all users (admin only)
operationId: listUsers operationId: listUsers
@@ -703,7 +731,7 @@ paths:
$ref: '#/components/responses/Forbidden' $ref: '#/components/responses/Forbidden'
post: post:
tags: tags:
- Admin - Admin - **EXPERIMENTAL**
summary: Create user summary: Create user
description: Creates a new user (admin only) description: Creates a new user (admin only)
operationId: createUser operationId: createUser
@@ -746,7 +774,7 @@ paths:
$ref: '#/components/responses/TooManyRequests' $ref: '#/components/responses/TooManyRequests'
put: put:
tags: tags:
- Admin - Admin - **EXPERIMENTAL**
summary: Update user summary: Update user
description: Updates an existing user (admin only) description: Updates an existing user (admin only)
operationId: updateUser operationId: updateUser
@@ -783,7 +811,7 @@ paths:
$ref: '#/components/responses/NotFound' $ref: '#/components/responses/NotFound'
delete: delete:
tags: tags:
- Admin - Admin - **EXPERIMENTAL**
summary: Delete user summary: Delete user
description: Deletes a user (admin only) description: Deletes a user (admin only)
operationId: deleteUser operationId: deleteUser
@@ -812,7 +840,7 @@ paths:
/v1/users/access: /v1/users/access:
put: put:
tags: tags:
- Admin - Admin - **EXPERIMENTAL**
summary: Grant user access to topic summary: Grant user access to topic
description: Grants a user access to a topic or topic pattern (admin only) description: Grants a user access to a topic or topic pattern (admin only)
operationId: grantAccess operationId: grantAccess
@@ -851,7 +879,7 @@ paths:
$ref: '#/components/responses/TooManyRequests' $ref: '#/components/responses/TooManyRequests'
delete: delete:
tags: tags:
- Admin - Admin - **EXPERIMENTAL**
summary: Reset user access to topic summary: Reset user access to topic
description: Resets user access to a topic (admin only) description: Resets user access to a topic (admin only)
operationId: resetAccess operationId: resetAccess
@@ -881,7 +909,7 @@ paths:
/v1/account: /v1/account:
post: post:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Create account summary: Create account
description: Creates a new user account description: Creates a new user account
operationId: createAccount operationId: createAccount
@@ -916,7 +944,7 @@ paths:
$ref: '#/components/responses/TooManyRequests' $ref: '#/components/responses/TooManyRequests'
get: get:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Get account info summary: Get account info
description: Returns information about the authenticated user's account description: Returns information about the authenticated user's account
operationId: getAccount operationId: getAccount
@@ -934,7 +962,7 @@ paths:
$ref: '#/components/responses/Unauthorized' $ref: '#/components/responses/Unauthorized'
delete: delete:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Delete account summary: Delete account
description: Deletes the authenticated user's account description: Deletes the authenticated user's account
operationId: deleteAccount operationId: deleteAccount
@@ -963,7 +991,7 @@ paths:
/v1/account/password: /v1/account/password:
post: post:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Change password summary: Change password
description: Changes the authenticated user's password description: Changes the authenticated user's password
operationId: changePassword operationId: changePassword
@@ -998,7 +1026,7 @@ paths:
/v1/account/token: /v1/account/token:
post: post:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Create access token summary: Create access token
description: Creates a new API access token for the authenticated user description: Creates a new API access token for the authenticated user
operationId: createToken operationId: createToken
@@ -1030,7 +1058,7 @@ paths:
$ref: '#/components/responses/TooManyRequests' $ref: '#/components/responses/TooManyRequests'
patch: patch:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Update access token summary: Update access token
description: Updates an existing access token description: Updates an existing access token
operationId: updateToken operationId: updateToken
@@ -1067,7 +1095,7 @@ paths:
$ref: '#/components/responses/NotFound' $ref: '#/components/responses/NotFound'
delete: delete:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Delete access token summary: Delete access token
description: Deletes an access token description: Deletes an access token
operationId: deleteToken operationId: deleteToken
@@ -1095,7 +1123,7 @@ paths:
/v1/account/settings: /v1/account/settings:
patch: patch:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Update account settings summary: Update account settings
description: Updates the authenticated user's account settings description: Updates the authenticated user's account settings
operationId: updateSettings operationId: updateSettings
@@ -1130,7 +1158,7 @@ paths:
/v1/account/subscription: /v1/account/subscription:
post: post:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Add topic subscription summary: Add topic subscription
description: Subscribes to a topic description: Subscribes to a topic
operationId: addSubscription operationId: addSubscription
@@ -1159,7 +1187,7 @@ paths:
$ref: '#/components/responses/TooManyRequests' $ref: '#/components/responses/TooManyRequests'
patch: patch:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Update topic subscription summary: Update topic subscription
description: Updates a topic subscription description: Updates a topic subscription
operationId: updateSubscription operationId: updateSubscription
@@ -1188,7 +1216,7 @@ paths:
$ref: '#/components/responses/NotFound' $ref: '#/components/responses/NotFound'
delete: delete:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Delete topic subscription summary: Delete topic subscription
description: Unsubscribes from a topic description: Unsubscribes from a topic
operationId: deleteSubscription operationId: deleteSubscription
@@ -1216,7 +1244,7 @@ paths:
/v1/account/reservation: /v1/account/reservation:
post: post:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Reserve topic summary: Reserve topic
description: Reserves a topic for exclusive use description: Reserves a topic for exclusive use
operationId: reserveTopic operationId: reserveTopic
@@ -1255,7 +1283,7 @@ paths:
/v1/account/reservation/{topic}: /v1/account/reservation/{topic}:
delete: delete:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Delete topic reservation summary: Delete topic reservation
description: Removes a topic reservation description: Removes a topic reservation
operationId: deleteReservation operationId: deleteReservation
@@ -1280,7 +1308,7 @@ paths:
/v1/account/phone: /v1/account/phone:
put: put:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Add phone number summary: Add phone number
description: Adds and verifies a phone number for voice call notifications description: Adds and verifies a phone number for voice call notifications
operationId: addPhoneNumber operationId: addPhoneNumber
@@ -1312,7 +1340,7 @@ paths:
$ref: '#/components/responses/TooManyRequests' $ref: '#/components/responses/TooManyRequests'
delete: delete:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Delete phone number summary: Delete phone number
description: Removes a phone number description: Removes a phone number
operationId: deletePhoneNumber operationId: deletePhoneNumber
@@ -1341,7 +1369,7 @@ paths:
/v1/account/phone/verify: /v1/account/phone/verify:
put: put:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Request phone verification code summary: Request phone verification code
description: Requests a verification code via SMS description: Requests a verification code via SMS
operationId: verifyPhoneNumber operationId: verifyPhoneNumber
@@ -1376,7 +1404,7 @@ paths:
/v1/account/billing/portal: /v1/account/billing/portal:
post: post:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Create billing portal session summary: Create billing portal session
description: Creates a Stripe billing portal session description: Creates a Stripe billing portal session
operationId: createBillingPortal operationId: createBillingPortal
@@ -1399,7 +1427,7 @@ paths:
/v1/account/billing/subscription: /v1/account/billing/subscription:
post: post:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Create subscription summary: Create subscription
description: Creates a paid subscription via Stripe checkout description: Creates a paid subscription via Stripe checkout
operationId: createSubscription operationId: createSubscription
@@ -1434,7 +1462,7 @@ paths:
$ref: '#/components/responses/Unauthorized' $ref: '#/components/responses/Unauthorized'
put: put:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Update subscription summary: Update subscription
description: Modifies an existing subscription description: Modifies an existing subscription
operationId: updateSubscription operationId: updateSubscription
@@ -1461,7 +1489,7 @@ paths:
$ref: '#/components/responses/Unauthorized' $ref: '#/components/responses/Unauthorized'
delete: delete:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Cancel subscription summary: Cancel subscription
description: Cancels the paid subscription description: Cancels the paid subscription
operationId: cancelSubscription operationId: cancelSubscription
@@ -1476,7 +1504,7 @@ paths:
/v1/account/billing/subscription/success/{CHECKOUT_SESSION_ID}: /v1/account/billing/subscription/success/{CHECKOUT_SESSION_ID}:
get: get:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Subscription checkout success summary: Subscription checkout success
description: Handles successful Stripe checkout redirect description: Handles successful Stripe checkout redirect
operationId: subscriptionCheckoutSuccess operationId: subscriptionCheckoutSuccess
@@ -1500,7 +1528,7 @@ paths:
/v1/account/billing/webhook: /v1/account/billing/webhook:
post: post:
tags: tags:
- Account - Account - **EXPERIMENTAL**
summary: Stripe webhook handler summary: Stripe webhook handler
description: Handles incoming webhooks from Stripe description: Handles incoming webhooks from Stripe
operationId: stripeWebhook operationId: stripeWebhook
@@ -1695,7 +1723,7 @@ paths:
/metrics: /metrics:
get: get:
tags: tags:
- Admin - Admin - **EXPERIMENTAL**
summary: Prometheus metrics summary: Prometheus metrics
description: Returns Prometheus-compatible metrics description: Returns Prometheus-compatible metrics
operationId: getMetrics operationId: getMetrics
@@ -1806,14 +1834,11 @@ components:
schema: schema:
type: integer type: integer
enum: enum:
- 1
- 2
- 3 - 3
- 2
- 1
- 4 - 4
- 5 - 5
default:
value: 3
example: 3
min: min:
value: 1 value: 1
urgent: urgent:
@@ -1915,7 +1940,6 @@ components:
- false - false
- 1 - 1
- 0 - 0
default: true
XFirebase: XFirebase:
name: X-Firebase name: X-Firebase
in: header in: header
@@ -1927,7 +1951,6 @@ components:
- false - false
- 1 - 1
- 0 - 0
default: true
XMarkdown: XMarkdown:
name: X-Markdown name: X-Markdown
in: header in: header
@@ -1968,8 +1991,8 @@ components:
schema: schema:
type: string type: string
enum: enum:
- true
- false - false
- true
- 1 - 1
- 0 - 0
- up - 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 # The documentation uses 'mkdocs', which is written in Python
mkdocs-material mkdocs-material
mkdocs-minify-plugin mkdocs-minify-plugin
mkdocs-swagger-ui-tag