openapi: 3.1.0 info: title: NIP-05 API description: Single-domain NIP-05 identity service with Lightning-paid registration. version: 1.0.0 servers: - url: / tags: - name: Public description: Anonymous, infrastructure-level endpoints. Cacheable, no auth. - name: User description: Anonymous flows for end users — lookup, availability, payment. - name: Admin description: Privileged operations. Require an `X-API-Key` header. components: securitySchemes: AdminAPIKey: type: apiKey in: header name: X-API-Key schemas: Error: type: object properties: error: { type: string } detail: { type: string } Pricing: type: object properties: yearly_sats: { type: integer } lifetime_sats: { type: integer } lightning_enabled: { type: boolean } Invoice: type: object properties: payment_hash: { type: string } payment_request: { type: string } amount_sats: { type: integer } expires_at: { type: string, format: date-time } username: { type: string } is_renewal: { type: boolean } InvoiceStatus: type: object properties: payment_hash: { type: string } status: { type: string, enum: [pending, paid, expired] } username: { type: string } User: type: object properties: pubkey: { type: string } npub: { type: string } username: { type: string } subscription_type: { type: string, enum: [yearly, lifetime] } is_active: { type: boolean } expires_at: { type: string, format: date-time, nullable: true } deactivated_at: { type: string, format: date-time, nullable: true } UserLookup: type: object properties: pubkey: { type: string } npub: { type: string } is_whitelisted: { type: boolean } username: { type: string } expires_at: { type: string, format: date-time, nullable: true } in_grace: { type: boolean } reserved_username: { type: string } expired_at: { type: string, format: date-time } paths: /.well-known/nostr.json: get: tags: [Public] summary: NIP-05 lookup parameters: - in: query name: name schema: { type: string } responses: '200': description: NIP-05 names map content: application/json: schema: type: object properties: names: type: object additionalProperties: { type: string } relays: type: object additionalProperties: type: array items: { type: string } /healthz: get: tags: [Public] summary: Health check responses: '200': { description: OK } '503': { description: Down } /v1/pricing: get: tags: [Public] summary: Pricing info responses: '200': description: Pricing content: application/json: schema: { $ref: '#/components/schemas/Pricing' } /v1/invoices: post: tags: [User] summary: Create payment invoice requestBody: required: true content: application/json: schema: type: object required: [pubkey] properties: pubkey: { type: string, description: "Hex pubkey or npub" } username: type: string description: "Optional. Auto-generated from the pubkey if omitted." subscription_type: type: string enum: [yearly, lifetime] description: "Optional. Defaults to lifetime." years: type: integer minimum: 1 description: "Optional. Defaults to 1 when subscription_type is yearly; ignored for lifetime." responses: '200': description: Invoice content: application/json: schema: { $ref: '#/components/schemas/Invoice' } '400': { description: Validation error, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } } '403': { description: Forbidden — user already has lifetime access, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } } '409': { description: Conflict — username unavailable or pending invoice already exists, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } } '503': { description: Lightning unavailable, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } } /v1/invoices/{payment_hash}: get: tags: [User] summary: Invoice status parameters: - in: path name: payment_hash required: true schema: { type: string } responses: '200': description: Status content: application/json: schema: { $ref: '#/components/schemas/InvoiceStatus' } '404': { description: Not found } /v1/users/{pubkey}: get: tags: [User] summary: Lookup user by pubkey (npub or hex) parameters: - in: path name: pubkey required: true schema: { type: string } responses: '200': description: User lookup content: application/json: schema: { $ref: '#/components/schemas/UserLookup' } '404': { description: Never registered } /v1/usernames/{name}/available: get: tags: [User] summary: Username availability parameters: - in: path name: name required: true schema: { type: string } responses: '200': description: Availability content: application/json: schema: type: object properties: username: { type: string } available: { type: boolean } /v1/admin/users: post: tags: [Admin] summary: Add user (admin) security: [{ AdminAPIKey: [] }] requestBody: required: true content: application/json: schema: type: object required: [pubkey, username, subscription_type] properties: pubkey: { type: string } username: { type: string } subscription_type: { type: string, enum: [yearly, lifetime] } years: { type: integer } responses: '201': description: Created content: { application/json: { schema: { $ref: '#/components/schemas/User' } } } '401': { description: Unauthorized } get: tags: [Admin] summary: List users (admin) security: [{ AdminAPIKey: [] }] parameters: - in: query name: active schema: { type: boolean } - in: query name: q schema: { type: string } responses: '200': description: User list content: application/json: schema: type: array items: { $ref: '#/components/schemas/User' } '401': { description: Unauthorized } /v1/admin/users/{pubkey}: put: tags: [Admin] summary: Update username (admin) security: [{ AdminAPIKey: [] }] parameters: - in: path name: pubkey required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [username] properties: username: { type: string } responses: '200': { description: Updated } '401': { description: Unauthorized } '404': { description: Not found } '409': { description: Conflict } delete: tags: [Admin] summary: Delete user (admin) security: [{ AdminAPIKey: [] }] parameters: - in: path name: pubkey required: true schema: { type: string } responses: '200': { description: Deleted } '401': { description: Unauthorized } '404': { description: Not found } /v1/admin/users/{pubkey}/extend: post: tags: [Admin] summary: Extend subscription (admin) security: [{ AdminAPIKey: [] }] parameters: - in: path name: pubkey required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object properties: years: { type: integer } subscription_type: { type: string, enum: [yearly, lifetime] } responses: '200': { description: Extended } '401': { description: Unauthorized } '404': { description: Not found }