303 lines
9.4 KiB
YAML
303 lines
9.4 KiB
YAML
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
|
|
description: |
|
|
Creates a new Lightning invoice. If this pubkey already has an unpaid, unexpired invoice
|
|
for the same subscription_type (and same years when yearly), that invoice is returned
|
|
(idempotent resume). If an unpaid invoice exists for a different plan, it is discarded and
|
|
a new invoice is created for the requested plan.
|
|
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, 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 }
|