${escapeHtml(service.name)}
${escapeHtml(service.module)}${escapeHtml(service.role)}
(function () {
const state = {
section: null,
loaded: false,
loading: false,
spec: null,
routes: [],
schemas: [],
services: [],
providerCount: 0,
capabilityCount: 0,
groups: [],
search: '',
sectionReady: false,
loadPromise: null,
};
const FallbackRoutes = [
{ method: 'GET', path: '/health', summary: 'Core health check', group: 'Core App & Health' },
{ method: 'GET', path: '/api/health', summary: 'API health check', group: 'Core App & Health' },
{ method: 'GET', path: '/api/status', summary: 'System status summary', group: 'Core App & Health' },
{ method: 'GET', path: '/api/market', summary: 'Current market overview', group: 'Market Data & Signals' },
{ method: 'GET', path: '/api/sentiment', summary: 'Fear & Greed sentiment', group: 'Market Data & Signals' },
{ method: 'GET', path: '/api/resources/summary', summary: 'Resource summary', group: 'Resources & Registry' },
{ method: 'GET', path: '/api/providers/status', summary: 'Provider status summary', group: 'Resources & Registry' },
{ method: 'GET', path: '/api/short-hunter/health', summary: 'Short Hunter gateway health', group: 'Short Hunter Gateway' },
{ method: 'GET', path: '/api/short-hunter/capabilities', summary: 'Gateway capabilities', group: 'Short Hunter Gateway' },
{ method: 'GET', path: '/api/short-hunter/providers/status', summary: 'Gateway provider status', group: 'Short Hunter Gateway' },
{ method: 'GET', path: '/api/short-hunter/snapshot/{symbol}', summary: 'Normalized futures snapshot', group: 'Short Hunter Gateway' },
{ method: 'GET', path: '/api/coins/top', summary: 'Top coins compatibility endpoint', group: 'Legacy Compatibility' },
{ method: 'GET', path: '/api/ohlcv', summary: 'OHLCV compatibility endpoint', group: 'Legacy Compatibility' },
{ method: 'POST', path: '/api/sentiment/analyze', summary: 'Sentiment analysis', group: 'AI & Models' },
{ method: 'GET', path: '/api/news/latest', summary: 'Latest news articles', group: 'News & External Data' },
];
const FallbackSchemas = [
{ name: 'HealthResponse', description: 'Service health payload', sample: { status: 'healthy', timestamp: '2026-06-15T00:00:00Z' } },
{ name: 'ShortHunterSnapshot', description: 'Normalized market snapshot', sample: { success: true, sourceMode: 'LIVE', dataState: 'REAL', data: { ticker: {}, ohlcv: [] } } },
];
const ServiceManifest = [
{
name: 'FastAPI app',
module: 'api_server_extended.py',
role: 'Primary HTTP application, HTML shell, status routes, resource registry, diagnostics, models, news, and legacy compatibility routes.',
},
{
name: 'Short Hunter gateway',
module: 'short_hunter_routes.py',
role: 'Capability-based datasource gateway with rotation, fallback, cache, health, and no-trade guard responses.',
},
{
name: 'Legacy compatibility layer',
module: 'api_compat_routes.py',
role: 'Preserves older market, sentiment, news, provider, and indicator endpoints for existing consumers.',
},
{
name: 'Provider registry',
module: 'providers/registry.py',
role: 'Safely loads catalog JSON and exposes provider capabilities, priorities, and auth requirements.',
},
{
name: 'Smart router',
module: 'providers/router.py',
role: 'Chooses providers per capability and reports degraded or unavailable states honestly.',
},
{
name: 'Frontend API client',
module: 'static/js/apiClient.js',
role: 'Shared browser fetch wrapper for tabs and page widgets.',
},
];
const SectionIds = [
{ id: 'overview', label: 'Overview' },
{ id: 'apis', label: 'APIs' },
{ id: 'data-models', label: 'Data Models' },
{ id: 'integration', label: 'Integration Guide' },
{ id: 'examples', label: 'Examples' },
];
const methodTone = {
GET: 'success',
POST: 'info',
PUT: 'warning',
PATCH: 'warning',
DELETE: 'danger',
HEAD: 'info',
OPTIONS: 'info',
};
const icon = {
refresh: '',
copy: '',
search: '',
link: '',
};
function ensureStyles() {
if (document.getElementById('help-reference-styles')) return;
const style = document.createElement('style');
style.id = 'help-reference-styles';
style.textContent = `
.help-page-shell{display:grid;gap:16px}
.help-hero-card,.help-section,.help-toolbar,.help-service-card,.help-model-card,.help-example-card,.help-route-card,.help-route-group,.help-note-card{backdrop-filter:blur(18px)}
.help-hero-top,.help-toolbar,.help-section-head,.help-service-title,.help-model-head,.help-example-head,.help-route-summary,.help-route-summary-meta,.help-code-head,.help-copy-row{display:flex;gap:12px;align-items:flex-start}
.help-hero-top,.help-section-head,.help-example-head,.help-model-head,.help-service-title{justify-content:space-between}
.help-hero-card{padding:20px;background:linear-gradient(135deg,rgba(102,126,234,.18),rgba(16,185,129,.08));border:1px solid rgba(255,255,255,.08);border-radius:16px}
.help-kicker{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--text-secondary);margin-bottom:8px}
.help-hero-card h3{margin-bottom:10px;font-size:22px}
.help-hero-card p{max-width:72ch}
.help-hero-actions{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}
.help-action-btn,.help-copy-btn,.help-nav-link{border:none;cursor:pointer}
.help-stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px}
.help-stat-card{padding:14px 16px;border:1px solid var(--border);border-radius:12px;background:rgba(255,255,255,.03);display:grid;gap:4px}
.help-stat-label{color:var(--text-secondary);font-size:12px;text-transform:uppercase;letter-spacing:.06em}
.help-toolbar{justify-content:space-between;align-items:center;flex-wrap:wrap;padding:14px 16px}
.help-search{display:flex;align-items:center;gap:10px;flex:1;min-width:260px;padding:12px 14px;border:1px solid var(--border);border-radius:12px;background:rgba(255,255,255,.05)}
.help-search input{width:100%;border:none;outline:none;background:transparent;color:var(--text-primary);font:inherit}
.help-toolbar-meta{display:flex;gap:8px;flex-wrap:wrap}
.help-layout{display:grid;grid-template-columns:220px minmax(0,1fr);gap:16px;align-items:start}
.help-nav{position:sticky;top:16px;display:grid;gap:8px;padding:16px}
.help-nav-link{width:100%;text-align:left;padding:11px 12px;border:1px solid transparent;border-radius:10px;background:rgba(255,255,255,.03);color:var(--text-secondary);transition:all .2s ease}
.help-nav-link:hover{color:var(--text-primary);border-color:var(--border);background:rgba(255,255,255,.08)}
.help-content{display:grid;gap:16px}
.help-section{padding:18px;border:1px solid rgba(255,255,255,.08);border-radius:16px;background:rgba(8,12,24,.68)}
.help-grid-2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
.help-card-grid,.help-model-grid,.help-example-grid{display:grid;gap:14px}
.help-service-card,.help-model-card,.help-example-card{padding:16px;border:1px solid var(--border);border-radius:14px;background:rgba(255,255,255,.03)}
.help-route-group{border:1px solid var(--border);border-radius:14px;overflow:hidden;background:rgba(255,255,255,.02)}
.help-route-group>summary{list-style:none;cursor:pointer;padding:14px 16px;display:flex;align-items:center;justify-content:space-between;gap:12px;background:rgba(255,255,255,.04)}
.help-route-group>summary::-webkit-details-marker,.help-route-card>summary::-webkit-details-marker{display:none}
.help-route-list{display:grid;gap:12px;padding:12px}
.help-route-card{border:1px solid var(--border);border-radius:12px;background:rgba(0,0,0,.18);overflow:hidden}
.help-route-card>summary{cursor:pointer;list-style:none;padding:14px 16px;display:grid;gap:8px}
.help-route-path{font-family:'JetBrains Mono',monospace;color:var(--text-primary);word-break:break-word}
.help-route-body{padding:0 16px 16px;display:grid;gap:14px}
.help-copy-btn{display:inline-flex;align-items:center;gap:6px;padding:9px 11px;border:1px solid var(--border);border-radius:10px;background:rgba(255,255,255,.06);color:var(--text-primary);transition:all .2s ease}
.help-copy-btn:hover{background:rgba(255,255,255,.12);transform:translateY(-1px)}
.help-code-block{border:1px solid var(--border);border-radius:12px;overflow:hidden;background:rgba(0,0,0,.24)}
.help-code-head{justify-content:space-between;align-items:center;padding:10px 12px;border-bottom:1px solid var(--border)}
.help-code{margin:0;padding:12px;overflow:auto;white-space:pre-wrap;word-break:break-word;font-family:'JetBrains Mono',monospace;font-size:12px;line-height:1.6;color:var(--text-primary)}
.help-params{display:grid;gap:12px}
.help-param-row{display:grid;gap:8px;padding:12px;border-radius:12px;border:1px solid var(--border);background:rgba(255,255,255,.03)}
.help-param-meta{color:var(--text-secondary);font-size:12px;text-transform:uppercase;letter-spacing:.06em}
.help-inline-code{display:inline-block;padding:4px 8px;border-radius:8px;background:rgba(255,255,255,.06);color:var(--text-primary);font-family:'JetBrains Mono',monospace;font-size:12px;overflow-x:auto}
.help-empty-state{padding:18px;border:1px dashed var(--border);border-radius:12px;text-align:center;color:var(--text-secondary);background:rgba(255,255,255,.02)}
.help-note-card{padding:16px;border:1px solid var(--border);border-radius:12px;background:rgba(255,255,255,.03)}
.is-hidden{display:none!important}
@media (max-width:1080px){.help-layout{grid-template-columns:1fr}.help-nav{position:relative;top:auto;grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:768px){.help-hero-top,.help-toolbar,.help-section-head,.help-service-title,.help-model-head,.help-example-head,.help-grid-2{flex-direction:column}.help-nav{grid-template-columns:1fr}}
`;
document.head.appendChild(style);
}
function toast(type, message) {
const manager = window.toastManager || window.toast;
if (manager && typeof manager[type] === 'function') {
manager[type](message);
} else {
console.log(`[${type}] ${message}`);
}
}
function escapeHtml(value) {
return String(value ?? '')
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function toTitleCase(value) {
return String(value || '')
.replace(/[_-]+/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.replace(/\b\w/g, (m) => m.toUpperCase());
}
function chunk(value, max = 120) {
if (!value) return '';
return value.length > max ? `${value.slice(0, max)}...` : value;
}
function resolveRef(ref, components) {
if (!ref || typeof ref !== 'string' || !ref.startsWith('#/')) return null;
const parts = ref.replace(/^#\//, '').split('/');
let current = { components };
for (const part of parts) {
current = current?.[part];
if (!current) return null;
}
return current;
}
function unwrapSchema(schema, components, depth = 0) {
if (!schema || depth > 6) return null;
if (schema.$ref) return unwrapSchema(resolveRef(schema.$ref, components), components, depth + 1);
if (schema.allOf && schema.allOf.length) return unwrapSchema(schema.allOf[0], components, depth + 1);
if (schema.oneOf && schema.oneOf.length) return unwrapSchema(schema.oneOf[0], components, depth + 1);
if (schema.anyOf && schema.anyOf.length) return unwrapSchema(schema.anyOf[0], components, depth + 1);
return schema;
}
function sampleScalar(schema, key = '') {
const name = key.toLowerCase();
if (schema?.enum?.length) return schema.enum[0];
if (name.includes('symbol')) return 'BTCUSDT';
if (name.includes('provider')) return 'kucoin_futures';
if (name.includes('category')) return 'market';
if (name.includes('interval') || name.includes('timeframe')) return '1h';
if (name.includes('limit')) return 100;
if (name.includes('page')) return 1;
if (name.includes('query')) return 'bitcoin';
if (name.includes('text')) return 'Bitcoin breaks resistance';
if (name.includes('title')) return 'Example headline';
if (name.includes('body') || name.includes('content') || name.includes('message')) return 'Example content';
if (name.includes('coin')) return 'BTC';
if (name.includes('coin_id')) return 'bitcoin';
if (name.includes('export_type')) return 'json';
if (name.includes('name')) return 'Example name';
if (name.includes('id')) return 'example-id';
if (name.includes('mode')) return 'auto';
if (name.includes('source')) return 'all';
if (name.includes('window')) return '24h';
if (name.includes('sort')) return 'publishedAt';
if (name.includes('time')) return '2026-06-15T00:00:00Z';
if (schema?.format === 'date-time') return '2026-06-15T00:00:00Z';
if (schema?.format === 'date') return '2026-06-15';
if (schema?.format === 'uuid') return '00000000-0000-0000-0000-000000000000';
if (schema?.type === 'integer' || schema?.type === 'number') return 0;
if (schema?.type === 'boolean') return true;
return 'string';
}
function sampleFromSchema(schema, components, key = '', depth = 0) {
const resolved = unwrapSchema(schema, components, depth);
if (!resolved || depth > 6) return null;
if (resolved.example !== undefined) return resolved.example;
if (resolved.examples && !Array.isArray(resolved.examples)) {
const first = Object.values(resolved.examples)[0];
if (first && first.value !== undefined) return first.value;
}
if (resolved.enum?.length) return resolved.enum[0];
const type = resolved.type || (resolved.properties ? 'object' : null);
if (type === 'object' || resolved.properties) {
const output = {};
const props = resolved.properties || {};
const required = new Set(resolved.required || []);
const keys = [
...Object.keys(props).filter((item) => required.has(item)),
...Object.keys(props).filter((item) => !required.has(item)),
];
keys.slice(0, 10).forEach((propName) => {
output[propName] = sampleFromSchema(props[propName], components, propName, depth + 1);
});
return output;
}
if (type === 'array') {
const item = sampleFromSchema(resolved.items, components, key, depth + 1);
return item === null ? [] : [item];
}
return sampleScalar(resolved, key);
}
function schemaTypeLabel(schema, components) {
const resolved = unwrapSchema(schema, components);
if (!resolved) return 'unknown';
if (resolved.enum?.length) return `enum (${resolved.enum.length})`;
if (resolved.type) {
if (resolved.type === 'array') {
return `array<${schemaTypeLabel(resolved.items, components)}>`;
}
return resolved.type;
}
if (resolved.properties) return 'object';
return 'unknown';
}
function operationGroup(path, tags = []) {
const tag = tags[0] || '';
if (tag.includes('Short Hunter')) return 'Short Hunter Gateway';
if (tag.includes('Compat')) return 'Legacy Compatibility';
if (path.startsWith('/api/short-hunter')) return 'Short Hunter Gateway';
if (path.startsWith('/api/providers') || path.startsWith('/api/resources') || path.startsWith('/api/pools') || path.startsWith('/api/logs') || path.startsWith('/api/diagnostics') || path.startsWith('/api/apl')) {
return 'Resources & Operations';
}
if (path.startsWith('/api/models') || path.startsWith('/api/hf') || path.startsWith('/api/analyze') || path.startsWith('/api/ai') || path.startsWith('/api/trading/decision')) {
return 'AI & Models';
}
if (path.startsWith('/api/news') || path.startsWith('/api/sentiment') || path.startsWith('/api/market') || path.startsWith('/api/trending') || path.startsWith('/api/coins') || path.startsWith('/api/ohlcv') || path.startsWith('/api/klines') || path.startsWith('/api/history') || path.startsWith('/api/orderbook') || path.startsWith('/api/indicators') || path.startsWith('/api/defi')) {
return 'Market Data & Signals';
}
if (path === '/' || path.endsWith('.html') || path === '/health' || path === '/api/health' || path === '/api/status' || path === '/api/stats' || path === '/debug-info' || path === '/trading_pairs.txt') {
return 'Core App & UI';
}
return 'Legacy Compatibility';
}
function isJsonResponse(response) {
const content = response?.content || {};
return Object.keys(content).some((key) => key.includes('json'));
}
function getRequestBody(operation, components) {
const body = operation?.requestBody?.content || {};
const jsonContent = body['application/json'] || body['application/*+json'] || Object.values(body)[0];
if (!jsonContent) return null;
const schema = jsonContent.schema || null;
const sample = sampleFromSchema(schema, components);
return {
description: operation.requestBody.description || '',
sample,
};
}
function getResponses(operation, components) {
const responses = [];
Object.entries(operation?.responses || {}).forEach(([status, response]) => {
const content = response?.content || {};
const jsonContent = content['application/json'] || content['application/*+json'] || Object.values(content).find((item) => item?.schema);
const schema = jsonContent?.schema || null;
responses.push({
status,
description: response?.description || '',
schema,
sample: sampleFromSchema(schema, components),
});
});
return responses;
}
function getParameters(operation, components) {
return (operation?.parameters || []).map((param) => {
const schema = unwrapSchema(param.schema, components);
return {
name: param.name,
in: param.in,
required: !!param.required,
description: param.description || '',
type: schemaTypeLabel(schema || param.schema, components),
example: sampleFromSchema(schema || param.schema, components, param.name),
};
});
}
function buildQueryString(parameters) {
const query = new URLSearchParams();
parameters.filter((item) => item.in === 'query').forEach((param) => {
if (param.example !== undefined && param.example !== null && param.example !== '') {
query.set(param.name, String(param.example));
}
});
const value = query.toString();
return value ? `?${value}` : '';
}
function replacePathParams(path, parameters) {
return path.replace(/\{([^}]+)\}/g, (_, key) => {
const param = parameters.find((item) => item.name === key);
if (param?.example !== undefined && param.example !== null && param.example !== '') {
return encodeURIComponent(String(param.example));
}
return encodeURIComponent(key === 'symbol' ? 'BTCUSDT' : key);
});
}
function operationSearchText(operation) {
const parameters = Array.isArray(operation.parameters) ? operation.parameters : [];
return [
operation.method,
operation.path,
operation.summary,
operation.description,
operation.tags?.join(' '),
parameters.map((p) => `${p.name} ${p.description} ${p.type}`).join(' '),
JSON.stringify(operation.requestBody?.sample || {}),
JSON.stringify(operation.responses?.map((resp) => resp.sample || {}) || []),
]
.join(' ')
.toLowerCase();
}
function buildRouteDocs(spec) {
const components = spec?.components || {};
const routes = [];
Object.entries(spec?.paths || {}).forEach(([path, pathItem]) => {
Object.entries(pathItem || {}).forEach(([method, operation]) => {
if (!operation || typeof operation !== 'object' || method === 'parameters') return;
const upper = method.toUpperCase();
routes.push({
method: upper,
path,
tags: operation.tags || [],
summary: operation.summary || toTitleCase(path.split('/').filter(Boolean).slice(-1)[0] || path),
description: operation.description || '',
deprecated: !!operation.deprecated,
parameters: getParameters(operation, components),
requestBody: getRequestBody(operation, components),
responses: getResponses(operation, components),
operationId: operation.operationId || '',
group: operationGroup(path, operation.tags || []),
});
});
});
routes.sort((a, b) => {
if (a.group !== b.group) return a.group.localeCompare(b.group);
if (a.path !== b.path) return a.path.localeCompare(b.path);
return a.method.localeCompare(b.method);
});
const groups = [];
const index = new Map();
routes.forEach((route) => {
if (!index.has(route.group)) {
index.set(route.group, { name: route.group, routes: [] });
groups.push(index.get(route.group));
}
index.get(route.group).routes.push(route);
});
const schemas = Object.entries(components.schemas || {}).map(([name, schema]) => ({
name,
description: schema.description || '',
type: schemaTypeLabel(schema, components),
sample: sampleFromSchema(schema, components, name),
schema,
}));
return { routes, groups, schemas };
}
function buildServiceCards() {
return ServiceManifest.map((service) => `
${escapeHtml(service.role)}${escapeHtml(service.name)}
${escapeHtml(service.module)}
This page reads the live OpenAPI schema, the Short Hunter gateway, and the legacy compatibility surface so the docs stay synchronized with the backend.
What the system provides and how requests travel from the browser to the provider router and back.
api_server_extended.py provides the FastAPI app and legacy UI routes.api_compat_routes.py preserves older endpoints and aliases.short_hunter_routes.py exposes the gateway contract.providers/router.py handles rotation, fallback, cache, and cooldowns.The backend stays honest about which upstreams it depends on and where those integrations live.
Expandable route cards with methods, parameters, request bodies, response schemas, and example calls.
/openapi.json...OpenAPI component schemas used by the backend.
How the current frontend calls the backend, which headers are actually needed, and how to wire a consumer safely.
const response = await fetch('/api/short-hunter/snapshot/BTCUSDT');
const snapshot = await response.json();
import apiClient from './apiClient.js';
const data = await apiClient.get('/api/providers/status');
The inspected frontend does not require a user auth header for these routes. Provider secrets stay server-side through environment variables and the backend reads them internally.
The repository includes client-side WebSocket helpers, but no server-side @app.websocket route is exposed in the inspected FastAPI app. Mark real-time usage as undocumented unless a websocket route is added later.
The main UI keeps navigation and refresh behavior in static/js/app.js and tab-specific loaders. Consumers should mirror that pattern: fetch, normalize, and then render from the returned JSON.
Treat success: false, sourceMode: UNAVAILABLE, dataState: UNAVAILABLE, and noTradeGuard: true as truthful signals that the backend could not verify the data.
Real endpoints with generated request and response previews.
${escapeHtml(route.description)}
` : ''}${escapeHtml(param.description || 'undocumented')}
${escapeHtml(String(param.example))}
No parameters documented in OpenAPI.
'}${escapeHtml(responseLabel)}
${responseBody ? escapeHtml(responseBody) : 'undocumented'}
curl -X ${route.method} "${window.location.origin}${exampleUrl}"
${requestBody ? escapeHtml(requestBody) : 'No request body documented.'}
${escapeHtml(schema.description || 'undocumented')}
${escapeHtml(sample)}
${escapeHtml(example.path)}
${escapeHtml(example.purpose)}
${escapeHtml(request)}
${escapeHtml(response)}