/** * OpenAPI 3.0 Specification * * Full API documentation for Cashumints.space */ export const openApiSpec = { openapi: '3.0.3', info: { title: 'Cashumints.space API', description: ` # Cashumints.space API A decentralized observability, discovery, and reputation API for the Cashu mint ecosystem. ## Overview This API provides: - **Mint Discovery** - Track Cashu mints across the ecosystem - **Uptime Monitoring** - Continuous probing with historical data - **Metadata Tracking** - NUT-06 compliant metadata with change history - **Nostr Reviews** - NIP-87 based review ingestion from relays - **Trust Scores** - Transparent, explainable reputation scoring - **Analytics** - Pageviews, trending, and popularity metrics ## Key Concepts ### Mint Identity Each mint is identified by a stable \`mint_id\` (UUID). A mint can have multiple URLs (clearnet, Tor, mirrors). All URLs resolve to the same mint identity. ### Mint Status Mints cycle through states: \`unknown\` → \`online\` → \`degraded\` → \`offline\` → \`abandoned\` ### Trust Scores Scores range from 0-100 and are computed from: - Uptime reliability (40 points max) - Response speed (25 points max) - Nostr reviews (20 points max) - Identity completeness (10 points max) - Penalties for instability (up to -15 points) ## Authentication This is a public, read-only API. No authentication required for read access. ## Rate Limiting 100 requests per minute per IP. Rate limit headers are included in responses. ## Admin API Admin endpoints require the \`X-Admin-Api-Key\` header. All admin actions are audited. Admin operations never delete raw data - they annotate, correct routing, or trigger recomputation. `, version: '1.0.0', contact: { name: 'Cashumints.space', url: 'https://cashumints.space' }, license: { name: 'MIT', url: 'https://opensource.org/licenses/MIT' } }, externalDocs: { description: 'Cashu Protocol Documentation', url: 'https://docs.cashu.space' }, servers: [{ url: '/v1', description: 'Current server (relative)' }, { url: 'https://api.cashumints.space/v1', description: 'Production API' }, { url: 'http://localhost:3000/v1', description: 'Local development' } ], tags: [{ name: 'System', description: 'Health checks and system statistics' }, { name: 'Mints', description: 'Mint discovery and listing' }, { name: 'Mint Details', description: 'Detailed mint information' }, { name: 'Metadata', description: 'NUT-06 metadata endpoints' }, { name: 'Uptime', description: 'Uptime and reliability metrics' }, { name: 'Trust', description: 'Trust scores and reputation' }, { name: 'Reviews', description: 'Nostr-based reviews (NIP-87)' }, { name: 'Analytics', description: 'Pageviews and popularity' }, { name: 'Admin', description: 'Admin-only endpoints (requires X-Admin-Api-Key header)' } ], paths: { '/health': { get: { tags: ['System'], summary: 'Health check', description: 'Returns the health status of the API', operationId: 'getHealth', responses: { 200: { description: 'API is healthy', content: { 'application/json': { schema: { $ref: '#/components/schemas/HealthResponse' } } } }, 503: { description: 'API is unhealthy', content: { 'application/json': { schema: { $ref: '#/components/schemas/HealthResponse' } } } } } } }, '/stats': { get: { tags: ['System'], summary: 'System statistics', description: 'Returns overall system statistics including mint counts, activity, and trending data', operationId: 'getStats', responses: { 200: { description: 'System statistics', content: { 'application/json': { schema: { $ref: '#/components/schemas/StatsResponse' } } } } } } }, '/reviews': { get: { tags: ['Reviews'], summary: 'Global reviews feed', description: 'Returns all reviews across the ecosystem with optional filtering', operationId: 'getGlobalReviews', parameters: [{ name: 'mint_id', in: 'query', description: 'Filter by mint ID', schema: { type: 'string', format: 'uuid' } }, { name: 'mint_url', in: 'query', description: 'Filter by mint URL', schema: { type: 'string' } }, { name: 'since', in: 'query', description: 'Unix timestamp - only reviews after this time', schema: { type: 'integer' } }, { name: 'until', in: 'query', description: 'Unix timestamp - only reviews before this time', schema: { type: 'integer' } }, { name: 'limit', in: 'query', description: 'Maximum number of results', schema: { type: 'integer', default: 50, maximum: 200 } }, { name: 'offset', in: 'query', description: 'Number of results to skip', schema: { type: 'integer', default: 0 } } ], responses: { 200: { description: 'Review list', content: { 'application/json': { schema: { $ref: '#/components/schemas/GlobalReviewsResponse' } } } } } } }, '/analytics/uptime': { get: { tags: ['Analytics'], summary: 'Ecosystem uptime analytics', description: 'Returns ecosystem-wide uptime and reliability metrics', operationId: 'getUptimeAnalytics', parameters: [{ name: 'window', in: 'query', description: 'Time window for analytics', schema: { type: 'string', enum: ['24h', '7d', '30d'], default: '24h' } }], responses: { 200: { description: 'Uptime analytics', content: { 'application/json': { schema: { $ref: '#/components/schemas/UptimeAnalytics' } } } } } } }, '/analytics/versions': { get: { tags: ['Analytics'], summary: 'Version distribution analytics', description: 'Returns mint version distribution across the ecosystem', operationId: 'getVersionAnalytics', responses: { 200: { description: 'Version analytics', content: { 'application/json': { schema: { $ref: '#/components/schemas/VersionAnalytics' } } } } } } }, '/analytics/nuts': { get: { tags: ['Analytics'], summary: 'NUT support analytics', description: 'Returns NUT support distribution across all mints', operationId: 'getNutsAnalytics', responses: { 200: { description: 'NUT analytics', content: { 'application/json': { schema: { $ref: '#/components/schemas/NutsAnalytics' } } } } } } }, '/mints/trending': { get: { tags: ['Mints'], summary: 'Get trending mints', description: 'Returns mints ranked by view velocity (popularity)', operationId: 'getTrendingMints', parameters: [{ name: 'window', in: 'query', description: 'Time window for trending calculation', schema: { type: 'string', enum: ['24h', '7d'], default: '7d' } }, { name: 'limit', in: 'query', description: 'Maximum number of results', schema: { type: 'integer', default: 10, maximum: 50 } } ], responses: { 200: { description: 'Trending mints', content: { 'application/json': { schema: { $ref: '#/components/schemas/TrendingMintsResponse' } } } } } } }, '/mints': { get: { tags: ['Mints'], summary: 'List all mints', description: 'Returns a paginated list of all tracked mints with optional filtering', operationId: 'listMints', parameters: [{ name: 'status', in: 'query', description: 'Filter by mint status', schema: { type: 'string', enum: ['unknown', 'online', 'degraded', 'offline', 'abandoned'] } }, { name: 'limit', in: 'query', description: 'Maximum number of results', schema: { type: 'integer', default: 100, maximum: 500 } }, { name: 'offset', in: 'query', description: 'Number of results to skip', schema: { type: 'integer', default: 0 } }, { name: 'sort_by', in: 'query', description: 'Sort field', schema: { type: 'string', enum: ['created_at', 'updated_at', 'name', 'trust_score'], default: 'created_at' } }, { name: 'sort_order', in: 'query', description: 'Sort order', schema: { type: 'string', enum: ['asc', 'desc'], default: 'desc' } } ], responses: { 200: { description: 'List of mints', content: { 'application/json': { schema: { $ref: '#/components/schemas/MintListResponse' } } } } } } }, '/mints/submit': { post: { tags: ['Mints'], summary: 'Submit a new mint', description: 'Submit a mint URL for tracking. The mint will be probed and added to the index.', operationId: 'submitMint', requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/MintSubmission' } } } }, responses: { 201: { description: 'Mint submitted successfully', content: { 'application/json': { schema: { $ref: '#/components/schemas/MintSubmissionResponse' } } } }, 400: { description: 'Invalid URL or submission error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/mints/{mint_id}': { get: { tags: ['Mint Details'], summary: 'Get mint by ID', description: 'Returns detailed information about a specific mint', operationId: 'getMintById', parameters: [ { $ref: '#/components/parameters/mintId' } ], responses: { 200: { description: 'Mint details', content: { 'application/json': { schema: { $ref: '#/components/schemas/Mint' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/by-url': { get: { tags: ['Mint Details'], summary: 'Get mint by URL', description: 'Returns detailed information about a mint by its URL', operationId: 'getMintByUrl', parameters: [ { $ref: '#/components/parameters/mintUrl' } ], responses: { 200: { description: 'Mint details', content: { 'application/json': { schema: { $ref: '#/components/schemas/Mint' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/{mint_id}/urls': { get: { tags: ['Mint Details'], summary: 'Get mint URLs', description: 'Returns all known URLs for a mint (clearnet, Tor, mirrors)', operationId: 'getMintUrls', parameters: [ { $ref: '#/components/parameters/mintId' } ], responses: { 200: { description: 'Mint URLs', content: { 'application/json': { schema: { $ref: '#/components/schemas/MintUrlsResponse' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/{mint_id}/status': { get: { tags: ['Mint Details'], summary: 'Get mint status', description: 'Returns lightweight status information for quick checks', operationId: 'getMintStatus', parameters: [ { $ref: '#/components/parameters/mintId' } ], responses: { 200: { description: 'Mint status', content: { 'application/json': { schema: { $ref: '#/components/schemas/MintStatus' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/{mint_id}/metadata': { get: { tags: ['Metadata'], summary: 'Get mint metadata', description: 'Returns NUT-06 compliant metadata for a mint', operationId: 'getMintMetadata', parameters: [ { $ref: '#/components/parameters/mintId' } ], responses: { 200: { description: 'Mint metadata', content: { 'application/json': { schema: { $ref: '#/components/schemas/MintMetadata' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/{mint_id}/metadata/history': { get: { tags: ['Metadata'], summary: 'Get metadata history', description: 'Returns history of metadata changes for a mint', operationId: 'getMintMetadataHistory', parameters: [ { $ref: '#/components/parameters/mintId' }, { name: 'limit', in: 'query', description: 'Maximum number of history entries', schema: { type: 'integer', default: 50 } } ], responses: { 200: { description: 'Metadata history', content: { 'application/json': { schema: { $ref: '#/components/schemas/MetadataHistoryResponse' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/{mint_id}/uptime': { get: { tags: ['Uptime'], summary: 'Get uptime statistics', description: 'Returns uptime and reliability metrics for a mint', operationId: 'getMintUptime', parameters: [ { $ref: '#/components/parameters/mintId' }, { name: 'window', in: 'query', description: 'Time window for statistics', schema: { type: 'string', enum: ['24h', '7d', '30d'], default: '24h' } } ], responses: { 200: { description: 'Uptime statistics', content: { 'application/json': { schema: { $ref: '#/components/schemas/UptimeStats' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/{mint_id}/uptime/timeseries': { get: { tags: ['Uptime'], summary: 'Get uptime timeseries', description: 'Returns time-bucketed uptime data for charting', operationId: 'getMintUptimeTimeseries', parameters: [ { $ref: '#/components/parameters/mintId' }, { name: 'window', in: 'query', description: 'Time window', schema: { type: 'string', enum: ['24h', '7d', '30d'], default: '24h' } }, { name: 'bucket', in: 'query', description: 'Time bucket size', schema: { type: 'string', enum: ['5m', '15m', '1h'], default: '1h' } } ], responses: { 200: { description: 'Uptime timeseries', content: { 'application/json': { schema: { $ref: '#/components/schemas/UptimeTimeseriesResponse' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/{mint_id}/incidents': { get: { tags: ['Uptime'], summary: 'Get incidents', description: 'Returns downtime incidents for a mint', operationId: 'getMintIncidents', parameters: [ { $ref: '#/components/parameters/mintId' }, { name: 'limit', in: 'query', description: 'Maximum number of incidents', schema: { type: 'integer', default: 50 } } ], responses: { 200: { description: 'Incident list', content: { 'application/json': { schema: { $ref: '#/components/schemas/IncidentsResponse' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/{mint_id}/trust': { get: { tags: ['Trust'], summary: 'Get trust score', description: 'Returns the computed trust score with full breakdown', operationId: 'getMintTrust', parameters: [ { $ref: '#/components/parameters/mintId' } ], responses: { 200: { description: 'Trust score', content: { 'application/json': { schema: { $ref: '#/components/schemas/TrustScore' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/{mint_id}/reviews': { get: { tags: ['Reviews'], summary: 'Get Nostr reviews', description: 'Returns NIP-87 reviews from Nostr relays', operationId: 'getMintReviews', parameters: [ { $ref: '#/components/parameters/mintId' }, { name: 'limit', in: 'query', description: 'Maximum number of reviews', schema: { type: 'integer', default: 50 } }, { name: 'offset', in: 'query', description: 'Number of reviews to skip', schema: { type: 'integer', default: 0 } } ], responses: { 200: { description: 'Review list', content: { 'application/json': { schema: { $ref: '#/components/schemas/ReviewsResponse' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/{mint_id}/views': { get: { tags: ['Analytics'], summary: 'Get pageview statistics', description: 'Returns popularity metrics based on pageviews', operationId: 'getMintViews', parameters: [ { $ref: '#/components/parameters/mintId' } ], responses: { 200: { description: 'Pageview statistics', content: { 'application/json': { schema: { $ref: '#/components/schemas/ViewStats' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/{mint_id}/features': { get: { tags: ['Mint Details'], summary: 'Get derived features', description: 'Returns features derived from metadata (supported NUTs, Bolt11 support, etc.)', operationId: 'getMintFeatures', parameters: [ { $ref: '#/components/parameters/mintId' } ], responses: { 200: { description: 'Derived features', content: { 'application/json': { schema: { $ref: '#/components/schemas/MintFeatures' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/by-url/urls': { get: { tags: ['Mint Details'], summary: 'Get mint URLs by URL', description: 'Returns all known URLs for a mint identified by URL', operationId: 'getMintUrlsByUrl', parameters: [ { $ref: '#/components/parameters/mintUrl' } ], responses: { 200: { description: 'Mint URLs', content: { 'application/json': { schema: { $ref: '#/components/schemas/MintUrlsResponse' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/by-url/status': { get: { tags: ['Mint Details'], summary: 'Get mint status by URL', description: 'Returns lightweight status information by URL', operationId: 'getMintStatusByUrl', parameters: [ { $ref: '#/components/parameters/mintUrl' } ], responses: { 200: { description: 'Mint status', content: { 'application/json': { schema: { $ref: '#/components/schemas/MintStatus' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/by-url/metadata': { get: { tags: ['Metadata'], summary: 'Get mint metadata by URL', description: 'Returns NUT-06 compliant metadata for a mint by URL', operationId: 'getMintMetadataByUrl', parameters: [ { $ref: '#/components/parameters/mintUrl' } ], responses: { 200: { description: 'Mint metadata', content: { 'application/json': { schema: { $ref: '#/components/schemas/MintMetadata' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/by-url/metadata/history': { get: { tags: ['Metadata'], summary: 'Get metadata history by URL', description: 'Returns history of metadata changes for a mint by URL', operationId: 'getMintMetadataHistoryByUrl', parameters: [ { $ref: '#/components/parameters/mintUrl' }, { name: 'limit', in: 'query', description: 'Maximum number of history entries', schema: { type: 'integer', default: 50 } } ], responses: { 200: { description: 'Metadata history', content: { 'application/json': { schema: { $ref: '#/components/schemas/MetadataHistoryResponse' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/by-url/uptime': { get: { tags: ['Uptime'], summary: 'Get uptime statistics by URL', description: 'Returns uptime and reliability metrics for a mint by URL', operationId: 'getMintUptimeByUrl', parameters: [ { $ref: '#/components/parameters/mintUrl' }, { name: 'window', in: 'query', description: 'Time window for statistics', schema: { type: 'string', enum: ['24h', '7d', '30d'], default: '24h' } } ], responses: { 200: { description: 'Uptime statistics', content: { 'application/json': { schema: { $ref: '#/components/schemas/UptimeStats' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/by-url/uptime/timeseries': { get: { tags: ['Uptime'], summary: 'Get uptime timeseries by URL', description: 'Returns time-bucketed uptime data for charting by URL', operationId: 'getMintUptimeTimeseriesByUrl', parameters: [ { $ref: '#/components/parameters/mintUrl' }, { name: 'window', in: 'query', description: 'Time window', schema: { type: 'string', enum: ['24h', '7d', '30d'], default: '24h' } }, { name: 'bucket', in: 'query', description: 'Time bucket size', schema: { type: 'string', enum: ['5m', '15m', '1h'], default: '1h' } } ], responses: { 200: { description: 'Uptime timeseries', content: { 'application/json': { schema: { $ref: '#/components/schemas/UptimeTimeseriesResponse' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/by-url/incidents': { get: { tags: ['Uptime'], summary: 'Get incidents by URL', description: 'Returns downtime incidents for a mint by URL', operationId: 'getMintIncidentsByUrl', parameters: [ { $ref: '#/components/parameters/mintUrl' }, { name: 'limit', in: 'query', description: 'Maximum number of incidents', schema: { type: 'integer', default: 50 } } ], responses: { 200: { description: 'Incident list', content: { 'application/json': { schema: { $ref: '#/components/schemas/IncidentsResponse' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/by-url/trust': { get: { tags: ['Trust'], summary: 'Get trust score by URL', description: 'Returns the computed trust score with full breakdown by URL', operationId: 'getMintTrustByUrl', parameters: [ { $ref: '#/components/parameters/mintUrl' } ], responses: { 200: { description: 'Trust score', content: { 'application/json': { schema: { $ref: '#/components/schemas/TrustScore' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/by-url/reviews': { get: { tags: ['Reviews'], summary: 'Get Nostr reviews by URL', description: 'Returns NIP-87 reviews from Nostr relays by URL', operationId: 'getMintReviewsByUrl', parameters: [ { $ref: '#/components/parameters/mintUrl' }, { name: 'limit', in: 'query', description: 'Maximum number of reviews', schema: { type: 'integer', default: 50 } }, { name: 'offset', in: 'query', description: 'Number of reviews to skip', schema: { type: 'integer', default: 0 } } ], responses: { 200: { description: 'Review list', content: { 'application/json': { schema: { $ref: '#/components/schemas/ReviewsResponse' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/by-url/views': { get: { tags: ['Analytics'], summary: 'Get pageview statistics by URL', description: 'Returns popularity metrics based on pageviews by URL', operationId: 'getMintViewsByUrl', parameters: [ { $ref: '#/components/parameters/mintUrl' } ], responses: { 200: { description: 'Pageview statistics', content: { 'application/json': { schema: { $ref: '#/components/schemas/ViewStats' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/mints/by-url/features': { get: { tags: ['Mint Details'], summary: 'Get derived features by URL', description: 'Returns features derived from metadata by URL', operationId: 'getMintFeaturesByUrl', parameters: [ { $ref: '#/components/parameters/mintUrl' } ], responses: { 200: { description: 'Derived features', content: { 'application/json': { schema: { $ref: '#/components/schemas/MintFeatures' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, // ========================================== // ADMIN ENDPOINTS // ========================================== '/admin/mints': { post: { tags: ['Admin'], summary: 'Manually add a mint', description: 'Add a mint to the system manually. Used for trusted bootstrap or recovery.', operationId: 'adminCreateMint', security: [{ AdminApiKey: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['mint_url'], properties: { mint_url: { type: 'string', format: 'uri' }, notes: { type: 'string', description: 'Internal notes' } } } } } }, responses: { 201: { description: 'Mint created', content: { 'application/json': { schema: { $ref: '#/components/schemas/AdminMintResponse' } } } }, 200: { description: 'Mint already exists', content: { 'application/json': { schema: { $ref: '#/components/schemas/AdminMintResponse' } } } }, 401: { $ref: '#/components/responses/Unauthorized' }, 403: { $ref: '#/components/responses/Forbidden' } } } }, '/admin/mints/{mint_id}/urls': { post: { tags: ['Admin'], summary: 'Add URL to mint', description: 'Attach an additional URL (clearnet, Tor, mirror) to an existing mint.', operationId: 'adminAddMintUrl', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintId' }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['url'], properties: { url: { type: 'string', format: 'uri' }, type: { type: 'string', enum: ['clearnet', 'tor', 'mirror'] }, active: { type: 'boolean', default: true } } } } } }, responses: { 201: { description: 'URL added' }, 404: { $ref: '#/components/responses/NotFound' }, 409: { description: 'URL already attached to another mint' } } } }, '/admin/mints/merge': { post: { tags: ['Admin'], summary: 'Merge two mints', description: 'Merge two mints that represent the same operator. All data is reassigned to target.', operationId: 'adminMergeMints', security: [{ AdminApiKey: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['source_mint_id', 'target_mint_id'], properties: { source_mint_id: { type: 'string', format: 'uuid' }, target_mint_id: { type: 'string', format: 'uuid' }, reason: { type: 'string' } } } } } }, responses: { 200: { description: 'Mints merged', content: { 'application/json': { schema: { $ref: '#/components/schemas/MergeResponse' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/admin/mints/split': { post: { tags: ['Admin'], summary: 'Undo a merge', description: 'Revert a previous mint merge operation.', operationId: 'adminSplitMints', security: [{ AdminApiKey: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['merge_id'], properties: { merge_id: { type: 'string', format: 'uuid' } } } } } }, responses: { 200: { description: 'Merge reverted' }, 404: { description: 'Merge not found' }, 409: { description: 'Merge already reverted' } } } }, '/admin/mints/{mint_id}/disable': { post: { tags: ['Admin'], summary: 'Disable mint', description: 'Hide a mint from public listings. Mint continues to be probed.', operationId: 'adminDisableMint', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintId' }], requestBody: { content: { 'application/json': { schema: { type: 'object', properties: { reason: { type: 'string' } } } } } }, responses: { 200: { description: 'Mint disabled' }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/admin/mints/{mint_id}/enable': { post: { tags: ['Admin'], summary: 'Enable mint', description: 'Re-enable a previously hidden mint.', operationId: 'adminEnableMint', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintId' }], responses: { 200: { description: 'Mint enabled' }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/admin/mints/{mint_id}/metadata/refresh': { post: { tags: ['Admin'], summary: 'Force metadata refresh', description: 'Force metadata fetch, bypassing the hourly limit.', operationId: 'adminForceMetadata', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintId' }], responses: { 200: { description: 'Metadata refresh scheduled' }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/admin/mints/{mint_id}/trust/recompute': { post: { tags: ['Admin'], summary: 'Force trust recompute', description: 'Force trust score recomputation using current data.', operationId: 'adminForceTrust', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintId' }], responses: { 200: { description: 'Trust recomputation scheduled' }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/admin/mints/{mint_id}/status/reset': { post: { tags: ['Admin'], summary: 'Reset mint status', description: 'Clear stuck mint state. Resets consecutive failures and schedules probe.', operationId: 'adminResetStatus', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintId' }], responses: { 200: { description: 'Status reset, probe scheduled' }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/admin/jobs': { get: { tags: ['Admin'], summary: 'Inspect job queue', description: 'View background job queue status.', operationId: 'adminGetJobs', security: [{ AdminApiKey: [] }], parameters: [ { name: 'status', in: 'query', schema: { type: 'string', enum: ['pending', 'running', 'completed', 'failed'] } }, { name: 'type', in: 'query', schema: { type: 'string' } }, { name: 'limit', in: 'query', schema: { type: 'integer', default: 100 } }, { name: 'offset', in: 'query', schema: { type: 'integer', default: 0 } } ], responses: { 200: { description: 'Job list', content: { 'application/json': { schema: { $ref: '#/components/schemas/JobListResponse' } } } } } } }, '/admin/system/metrics': { get: { tags: ['Admin'], summary: 'System metrics', description: 'High-level system health and metrics.', operationId: 'adminGetMetrics', security: [{ AdminApiKey: [] }], responses: { 200: { description: 'System metrics', content: { 'application/json': { schema: { $ref: '#/components/schemas/SystemMetrics' } } } } } } }, '/admin/audit': { get: { tags: ['Admin'], summary: 'Audit log', description: 'View admin action audit log.', operationId: 'adminGetAudit', security: [{ AdminApiKey: [] }], parameters: [ { name: 'action', in: 'query', schema: { type: 'string' } }, { name: 'target_type', in: 'query', schema: { type: 'string' } }, { name: 'admin_id', in: 'query', schema: { type: 'string' } }, { name: 'mint_id', in: 'query', description: 'Filter by mint ID', schema: { type: 'string', format: 'uuid' } }, { name: 'limit', in: 'query', schema: { type: 'integer', default: 100 } }, { name: 'offset', in: 'query', schema: { type: 'integer', default: 0 } } ], responses: { 200: { description: 'Audit log entries', content: { 'application/json': { schema: { $ref: '#/components/schemas/AuditLogResponse' } } } } } } }, '/admin/mints/{mint_id}/probe': { post: { tags: ['Admin'], summary: 'Force probe', description: 'Force an immediate probe of a mint.', operationId: 'adminForceProbeMint', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintId' }], responses: { 200: { description: 'Probe scheduled' }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/admin/mints/{mint_id}/visibility': { get: { tags: ['Admin'], summary: 'Get mint visibility', description: 'Get the current visibility status of a mint.', operationId: 'adminGetMintVisibility', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintId' }], responses: { 200: { description: 'Visibility status', content: { 'application/json': { schema: { $ref: '#/components/schemas/MintVisibility' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/admin/mints/by-url/disable': { post: { tags: ['Admin'], summary: 'Disable mint by URL', description: 'Hide a mint from public listings by URL.', operationId: 'adminDisableMintByUrl', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintUrl' }], requestBody: { content: { 'application/json': { schema: { type: 'object', properties: { reason: { type: 'string' } } } } } }, responses: { 200: { description: 'Mint disabled' }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/admin/mints/by-url/enable': { post: { tags: ['Admin'], summary: 'Enable mint by URL', description: 'Re-enable a hidden mint by URL.', operationId: 'adminEnableMintByUrl', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintUrl' }], responses: { 200: { description: 'Mint enabled' }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/admin/mints/by-url/metadata/refresh': { post: { tags: ['Admin'], summary: 'Force metadata refresh by URL', description: 'Force metadata fetch by URL, bypassing the hourly limit.', operationId: 'adminForceMetadataByUrl', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintUrl' }], responses: { 200: { description: 'Metadata refresh scheduled' }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/admin/mints/by-url/trust/recompute': { post: { tags: ['Admin'], summary: 'Force trust recompute by URL', description: 'Force trust score recomputation by URL.', operationId: 'adminForceTrustByUrl', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintUrl' }], responses: { 200: { description: 'Trust recomputation scheduled' }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/admin/mints/by-url/status/reset': { post: { tags: ['Admin'], summary: 'Reset mint status by URL', description: 'Clear stuck mint state by URL.', operationId: 'adminResetStatusByUrl', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintUrl' }], responses: { 200: { description: 'Status reset, probe scheduled' }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/admin/mints/by-url/probe': { post: { tags: ['Admin'], summary: 'Force probe by URL', description: 'Force an immediate probe by URL.', operationId: 'adminForceProbeByUrl', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintUrl' }], responses: { 200: { description: 'Probe scheduled' }, 404: { $ref: '#/components/responses/NotFound' } } } }, '/admin/mints/by-url/visibility': { get: { tags: ['Admin'], summary: 'Get mint visibility by URL', description: 'Get visibility status by URL.', operationId: 'adminGetMintVisibilityByUrl', security: [{ AdminApiKey: [] }], parameters: [{ $ref: '#/components/parameters/mintUrl' }], responses: { 200: { description: 'Visibility status', content: { 'application/json': { schema: { $ref: '#/components/schemas/MintVisibility' } } } }, 404: { $ref: '#/components/responses/NotFound' } } } } }, components: { securitySchemes: { AdminApiKey: { type: 'apiKey', in: 'header', name: 'X-Admin-Api-Key', description: 'Admin API key for protected endpoints' } }, parameters: { mintId: { name: 'mint_id', in: 'path', required: true, description: 'Unique mint identifier (UUID)', schema: { type: 'string', format: 'uuid' } }, mintUrl: { name: 'url', in: 'query', required: true, description: 'Mint URL (will be normalized)', schema: { type: 'string', format: 'uri' } } }, responses: { NotFound: { description: 'Mint not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, RateLimited: { description: 'Rate limit exceeded', headers: { 'Retry-After': { description: 'Seconds until rate limit resets', schema: { type: 'integer' } }, 'X-RateLimit-Limit': { description: 'Maximum requests per window', schema: { type: 'integer' } }, 'X-RateLimit-Remaining': { description: 'Remaining requests in window', schema: { type: 'integer' } } }, content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, Unauthorized: { description: 'Missing authentication', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, Forbidden: { description: 'Invalid authentication', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } }, schemas: { Error: { type: 'object', properties: { error: { type: 'string', description: 'Error message' } }, required: ['error'] }, HealthResponse: { type: 'object', properties: { status: { type: 'string', enum: ['healthy', 'unhealthy'] }, database: { type: 'string', enum: ['connected', 'disconnected'] }, timestamp: { type: 'string', format: 'date-time' }, version: { type: 'string' } } }, StatsResponse: { type: 'object', properties: { mints: { type: 'object', properties: { total: { type: 'integer' }, by_status: { type: 'object', additionalProperties: { type: 'integer' } } } }, activity: { type: 'object', properties: { probes_24h: { type: 'integer' }, reviews_total: { type: 'integer' }, incidents_7d: { type: 'integer' } } }, computed_at: { type: 'string', format: 'date-time' } } }, Mint: { type: 'object', properties: { mint_id: { type: 'string', format: 'uuid', description: 'Unique mint identifier' }, canonical_url: { type: 'string', format: 'uri', description: 'Primary mint URL' }, urls: { type: 'array', items: { type: 'string', format: 'uri' }, description: 'All known URLs for this mint' }, name: { type: 'string', nullable: true, description: 'Mint name from metadata' }, icon_url: { type: 'string', format: 'uri', nullable: true, description: 'Mint icon URL' }, status: { type: 'string', enum: ['unknown', 'online', 'degraded', 'offline', 'abandoned'], description: 'Current mint status' }, offline_since: { type: 'string', format: 'date-time', nullable: true, description: 'When the mint went offline' }, last_success_at: { type: 'string', format: 'date-time', nullable: true, description: 'Last successful probe' }, last_failure_at: { type: 'string', format: 'date-time', nullable: true, description: 'Last failed probe' }, uptime_24h: { type: 'number', nullable: true, description: '24-hour uptime percentage' }, uptime_7d: { type: 'number', nullable: true, description: '7-day uptime percentage' }, uptime_30d: { type: 'number', nullable: true, description: '30-day uptime percentage' }, incidents_7d: { type: 'integer', description: 'Incident count in last 7 days' }, incidents_30d: { type: 'integer', description: 'Incident count in last 30 days' }, trust_score: { type: 'integer', nullable: true, minimum: 0, maximum: 100, description: 'Trust score (0-100)' }, trust_level: { type: 'string', enum: ['unknown', 'low', 'medium', 'high', 'excellent'], nullable: true, description: 'Trust level category' } } }, MintListResponse: { type: 'object', properties: { mints: { type: 'array', items: { $ref: '#/components/schemas/Mint' } }, total: { type: 'integer' }, limit: { type: 'integer' }, offset: { type: 'integer' } } }, MintSubmission: { type: 'object', required: ['mint_url'], properties: { mint_url: { type: 'string', format: 'uri', description: 'URL of the Cashu mint to submit' } } }, MintSubmissionResponse: { type: 'object', properties: { success: { type: 'boolean' }, mint_id: { type: 'string', format: 'uuid' }, status: { type: 'string' }, message: { type: 'string' } } }, MintUrlsResponse: { type: 'object', properties: { canonical_url: { type: 'string', format: 'uri' }, urls: { type: 'array', items: { type: 'object', properties: { url: { type: 'string', format: 'uri' }, type: { type: 'string', enum: ['clearnet', 'tor', 'mirror'] }, active: { type: 'boolean' }, discovered_at: { type: 'string', format: 'date-time' }, last_seen_at: { type: 'string', format: 'date-time' } } } } } }, MintStatus: { type: 'object', properties: { status: { type: 'string', enum: ['unknown', 'online', 'degraded', 'offline', 'abandoned'] }, offline_since: { type: 'string', format: 'date-time', nullable: true }, last_checked_at: { type: 'string', format: 'date-time', nullable: true }, current_rtt_ms: { type: 'integer', nullable: true, description: 'Current response time in milliseconds' } } }, MintMetadata: { type: 'object', description: 'NUT-06 compliant mint metadata', properties: { name: { type: 'string', nullable: true }, pubkey: { type: 'string', nullable: true, description: 'Mint public key (hex)' }, version: { type: 'string', nullable: true }, description: { type: 'string', nullable: true }, description_long: { type: 'string', nullable: true }, contact: { type: 'object', nullable: true, additionalProperties: { type: 'string' } }, motd: { type: 'string', nullable: true, description: 'Message of the day' }, icon_url: { type: 'string', format: 'uri', nullable: true }, urls: { type: 'array', nullable: true, items: { type: 'string', format: 'uri' } }, tos_url: { type: 'string', format: 'uri', nullable: true, description: 'Terms of service URL' }, nuts: { type: 'object', nullable: true, description: 'Supported NUTs configuration', additionalProperties: { type: 'object' } }, server_time: { type: 'string', format: 'date-time', nullable: true }, last_fetched_at: { type: 'string', format: 'date-time' } } }, MetadataHistoryResponse: { type: 'object', properties: { history: { type: 'array', items: { type: 'object', properties: { fetched_at: { type: 'string', format: 'date-time' }, change_type: { type: 'string', enum: ['initial', 'update', 'error'] }, diff: { type: 'object', nullable: true, description: 'JSON diff of changes' }, version: { type: 'string', nullable: true } } } } } }, UptimeStats: { type: 'object', properties: { uptime_pct: { type: 'number', description: 'Uptime percentage' }, downtime_seconds: { type: 'integer', description: 'Total downtime in seconds' }, avg_rtt_ms: { type: 'number', nullable: true, description: 'Average response time' }, p95_rtt_ms: { type: 'number', nullable: true, description: '95th percentile response time' }, total_checks: { type: 'integer' }, ok_checks: { type: 'integer' } } }, UptimeTimeseriesResponse: { type: 'object', properties: { data: { type: 'array', items: { type: 'object', properties: { timestamp: { type: 'string', format: 'date-time' }, state: { type: 'string', enum: ['up', 'down', 'degraded'] }, ok: { type: 'integer' }, total: { type: 'integer' }, rtt_ms: { type: 'integer', nullable: true } } } } } }, IncidentsResponse: { type: 'object', properties: { incidents: { type: 'array', items: { type: 'object', properties: { started_at: { type: 'string', format: 'date-time' }, resolved_at: { type: 'string', format: 'date-time', nullable: true }, duration_seconds: { type: 'integer', nullable: true }, severity: { type: 'string', enum: ['minor', 'major', 'critical'] } } } } } }, TrustScore: { type: 'object', properties: { score_total: { type: 'integer', minimum: 0, maximum: 100, nullable: true }, score_level: { type: 'string', enum: ['unknown', 'low', 'medium', 'high', 'excellent'] }, breakdown: { type: 'object', nullable: true, description: 'Detailed score breakdown by component', properties: { uptime: { type: 'object', properties: { score: { type: 'number' }, max: { type: 'number' }, details: { type: 'object' } } }, speed: { type: 'object', properties: { score: { type: 'number' }, max: { type: 'number' }, details: { type: 'object' } } }, reviews: { type: 'object', properties: { score: { type: 'number' }, max: { type: 'number' }, details: { type: 'object' } } }, identity: { type: 'object', properties: { score: { type: 'number' }, max: { type: 'number' }, details: { type: 'object' } } }, penalties: { type: 'object', properties: { score: { type: 'number' }, max: { type: 'number' }, details: { type: 'object' } } } } }, computed_at: { type: 'string', format: 'date-time', nullable: true } } }, ReviewsResponse: { type: 'object', properties: { reviews: { type: 'array', items: { type: 'object', properties: { event_id: { type: 'string', description: 'Nostr event ID' }, pubkey: { type: 'string', description: 'Reviewer Nostr pubkey' }, created_at: { type: 'string', format: 'date-time' }, rating: { type: 'integer', minimum: 1, maximum: 5, nullable: true }, content: { type: 'string', nullable: true, description: 'Review text' } } } } } }, ViewStats: { type: 'object', properties: { views_24h: { type: 'integer' }, views_7d: { type: 'integer' }, views_30d: { type: 'integer' }, unique_sessions_30d: { type: 'integer' }, view_velocity: { type: 'number', description: 'Average views per day' } } }, MintFeatures: { type: 'object', properties: { supported_nuts: { type: 'array', items: { type: 'integer' }, description: 'List of supported NUT numbers' }, supports_bolt11: { type: 'boolean', description: 'Whether Bolt11 Lightning is supported' }, min_amount: { type: 'integer', nullable: true, description: 'Minimum transaction amount in sats' }, max_amount: { type: 'integer', nullable: true, description: 'Maximum transaction amount in sats' }, has_tor_endpoint: { type: 'boolean', description: 'Whether a .onion URL is available' }, has_multiple_urls: { type: 'boolean', description: 'Whether multiple URLs are configured' }, feature_completeness_score: { type: 'integer', minimum: 0, maximum: 100, description: 'Overall feature completeness (0-100)' } } }, AdminMintResponse: { type: 'object', properties: { mint_id: { type: 'string', format: 'uuid' }, status: { type: 'string' }, message: { type: 'string' }, created: { type: 'boolean' } } }, MergeResponse: { type: 'object', properties: { merge_id: { type: 'string', format: 'uuid' }, source_mint_id: { type: 'string', format: 'uuid' }, target_mint_id: { type: 'string', format: 'uuid' }, message: { type: 'string' } } }, JobListResponse: { type: 'object', properties: { jobs: { type: 'array', items: { type: 'object', properties: { id: { type: 'integer' }, type: { type: 'string' }, status: { type: 'string', enum: ['pending', 'running', 'completed', 'failed'] }, payload: { type: 'object' }, run_at: { type: 'string', format: 'date-time' }, retries: { type: 'integer' }, error_message: { type: 'string', nullable: true } } } }, total: { type: 'integer' }, limit: { type: 'integer' }, offset: { type: 'integer' } } }, SystemMetrics: { type: 'object', properties: { total_mints: { type: 'integer' }, online_mints: { type: 'integer' }, degraded_mints: { type: 'integer' }, offline_mints: { type: 'integer' }, abandoned_mints: { type: 'integer' }, unknown_mints: { type: 'integer' }, probes_last_minute: { type: 'integer' }, failed_probes_last_minute: { type: 'integer' }, job_backlog: { type: 'integer' }, oldest_job_age_seconds: { type: 'integer' }, worker_heartbeat_seconds: { type: 'integer', nullable: true }, computed_at: { type: 'string', format: 'date-time' } } }, AuditLogResponse: { type: 'object', properties: { entries: { type: 'array', items: { type: 'object', properties: { id: { type: 'integer' }, admin_id: { type: 'string' }, action: { type: 'string' }, target_type: { type: 'string' }, target_id: { type: 'string', nullable: true }, before_state: { type: 'object', nullable: true }, after_state: { type: 'object', nullable: true }, notes: { type: 'string', nullable: true }, ip_address: { type: 'string', nullable: true }, created_at: { type: 'string', format: 'date-time' } } } }, total: { type: 'integer' }, limit: { type: 'integer' }, offset: { type: 'integer' } } }, GlobalReviewsResponse: { type: 'object', properties: { reviews: { type: 'array', items: { type: 'object', properties: { event_id: { type: 'string' }, mint_id: { type: 'string', format: 'uuid', nullable: true }, mint_name: { type: 'string', nullable: true }, mint_url: { type: 'string', nullable: true }, pubkey: { type: 'string' }, created_at: { type: 'string', format: 'date-time' }, rating: { type: 'integer', minimum: 1, maximum: 5, nullable: true }, content: { type: 'string', nullable: true } } } }, total: { type: 'integer' }, limit: { type: 'integer' }, offset: { type: 'integer' } } }, TrendingMintsResponse: { type: 'object', properties: { window: { type: 'string', enum: ['24h', '7d'] }, mints: { type: 'array', items: { type: 'object', properties: { mint_id: { type: 'string', format: 'uuid' }, canonical_url: { type: 'string' }, name: { type: 'string', nullable: true }, icon_url: { type: 'string', nullable: true }, status: { type: 'string' }, trust_score: { type: 'integer', nullable: true }, trust_level: { type: 'string', nullable: true }, view_count: { type: 'integer' }, view_velocity: { type: 'number' }, unique_sessions: { type: 'integer' } } } } } }, UptimeAnalytics: { type: 'object', properties: { window: { type: 'string' }, overall: { type: 'object', properties: { total_probes: { type: 'integer' }, successful_probes: { type: 'integer' }, uptime_percent: { type: 'number', nullable: true }, avg_rtt_ms: { type: 'number', nullable: true }, min_rtt_ms: { type: 'number', nullable: true }, max_rtt_ms: { type: 'number', nullable: true } } }, by_status: { type: 'object', additionalProperties: { type: 'object', properties: { mint_count: { type: 'integer' }, probe_count: { type: 'integer' }, successful_probes: { type: 'integer' } } } }, rtt_distribution: { type: 'object', properties: { fast: { type: 'integer', description: '< 500ms' }, normal: { type: 'integer', description: '500-1000ms' }, slow: { type: 'integer', description: '1000-2000ms' }, degraded: { type: 'integer', description: '> 2000ms' } } }, computed_at: { type: 'string', format: 'date-time' } } }, VersionAnalytics: { type: 'object', properties: { versions: { type: 'array', items: { type: 'object', properties: { version: { type: 'string' }, count: { type: 'integer' }, mints: { type: 'array', items: { type: 'string' } } } } }, total_with_version: { type: 'integer' }, total_mints: { type: 'integer' }, coverage_percent: { type: 'integer' }, computed_at: { type: 'string', format: 'date-time' } } }, NutsAnalytics: { type: 'object', properties: { nuts: { type: 'array', items: { type: 'object', properties: { nut: { type: 'integer' }, count: { type: 'integer' }, percent: { type: 'integer' } } } }, key_nuts: { type: 'object', additionalProperties: { type: 'integer' } }, total_mints_analyzed: { type: 'integer' }, computed_at: { type: 'string', format: 'date-time' } } }, MintVisibility: { type: 'object', properties: { mint_id: { type: 'string', format: 'uuid' }, canonical_url: { type: 'string' }, visibility: { type: 'string', enum: ['public', 'hidden'] }, status: { type: 'string' } } } } } };