| name | lnvps-api |
| description | Create and manage VPS instances using the LNVPS customer API with Bitcoin Lightning Network payments. Use when building integrations that provision virtual machines, handle Lightning invoice payments, manage SSH keys, or automate VPS lifecycle operations. |
| metadata | {"author":"lnvps","version":"1.0"} |
| compatibility | Requires network access to LNVPS API endpoints and a Nostr keypair for NIP-98 authentication. |
LNVPS Customer API
Latest version of this skill: https://lnvps.net/SKILL.md
To ensure you have the most up-to-date API information, fetch the latest version before use.
This skill enables interaction with the LNVPS customer-facing API to create and manage VPS instances paid via Bitcoin Lightning Network.
Base URL: https://api.lnvps.net
Authentication
All API requests (except public endpoints) require NIP-98 HTTP Authentication.
Creating a NIP-98 Auth Header
-
Create a Nostr event:
- Kind:
27235 (HttpAuth)
- Tags:
["u", "<full_url>"] and ["method", "<HTTP_METHOD>"]
- Created At: Current Unix timestamp (valid for 600 seconds)
- Content: Empty string
- Sign the event with your Nostr private key
-
Base64-encode the JSON event
-
Send as: Authorization: Nostr <base64_encoded_event>
Example event:
{
"kind": 27235,
"created_at": 1704067200,
"tags": [
["u", "https://api.lnvps.net/api/v1/vm"],
["method", "POST"]
],
"content": "",
"pubkey": "<your_nostr_pubkey>",
"id": "<event_id>",
"sig": "<signature>"
}
VM Creation Flow
Creating a VM follows these steps:
1. Add SSH key POST /api/v1/ssh-key
2. List resources GET /api/v1/image, GET /api/v1/vm/templates
3. Create VM order POST /api/v1/vm
4. Get invoice GET /api/v1/vm/{id}/renew?method=lightning
5. Pay Lightning invoice
6. Poll payment GET /api/v1/payment/{id} until is_paid=true
7. Poll VM status GET /api/v1/vm/{id} until status.state=running
8. Connect via SSH
Step 1: Add SSH Key
POST /api/v1/ssh-key
Content-Type: application/json
{"name": "my-key", "key_data": "ssh-ed25519 AAAAC3..."}
Response:
{ "data": { "id": 1, "name": "my-key", "created": "2024-01-01T00:00:00Z" } }
Step 2: List Available Resources
OS Images: GET /api/v1/image
{
"data": [
{
"id": 1,
"distribution": "ubuntu",
"version": "24.04",
"default_username": "ubuntu"
}
]
}
Templates: GET /api/v1/vm/templates
{
"data": [
{
"id": 1,
"name": "VPS-Small",
"cpu": 1,
"memory": 1073741824,
"disk_size": 21474836480,
"cost_plan": { "amount": 500, "currency": "EUR" }
}
]
}
Step 3: Create VM Order
POST /api/v1/vm
Content-Type: application/json
{"template_id": 1, "image_id": 1, "ssh_key_id": 1}
The VM is created in an unpaid state (expires = created).
Step 4: Generate Payment Invoice
GET /api/v1/vm/{id}/renew?method=lightning
Response:
{
"data": {
"id": "a1b2c3d4...",
"amount": 21000,
"currency": "BTC",
"is_paid": false,
"data": { "Lightning": "lnbc210u1pj..." },
"time": 2592000
}
}
amount is in satoshis (for BTC) or cents (for fiat)
time is seconds added to expiry upon payment
- Pay the invoice in
data.Lightning with any Lightning wallet
Step 5-7: Poll Until Ready
Poll GET /api/v1/payment/{id} until is_paid: true, then poll GET /api/v1/vm/{id} until status.state: "running".
Key Endpoints
| Action | Method | Endpoint |
|---|
| List VMs | GET | /api/v1/vm |
| Get VM | GET | /api/v1/vm/{id} |
| Create VM | POST | /api/v1/vm |
| Start VM | PATCH | /api/v1/vm/{id}/start |
| Stop VM | PATCH | /api/v1/vm/{id}/stop |
| Restart VM | PATCH | /api/v1/vm/{id}/restart |
| Reinstall VM | PATCH | /api/v1/vm/{id}/re-install |
| Renew VM | GET | /api/v1/vm/{id}/renew |
| Update VM | PATCH | /api/v1/vm/{id} |
| Upgrade quote | POST | /api/v1/vm/{id}/upgrade/quote |
| Upgrade VM | POST | /api/v1/vm/{id}/upgrade |
| VM history | GET | /api/v1/vm/{id}/history |
| VM time series | GET | /api/v1/vm/{id}/time-series |
| List SSH Keys | GET | /api/v1/ssh-key |
| Add SSH Key | POST | /api/v1/ssh-key |
| Get Account | GET | /api/v1/account |
| Update Account | PATCH | /api/v1/account |
| Verify Email | GET | /api/v1/account/verify-email |
| Payment methods | GET | /api/v1/payment/methods |
| Get Payment | GET | /api/v1/payment/{id} |
| List Subscriptions | GET | /api/v1/subscriptions |
| Renew Subscription | GET | /api/v1/subscriptions/{id}/renew |
| List IP Spaces | GET | /api/v1/ip_space |
| Contact form | POST | /api/v1/contact |
See REFERENCE.md for complete endpoint documentation.
Data Formats
Sizes are in bytes:
- 1 GB =
1073741824
- 1 TB =
1099511627776
Amounts are in smallest currency unit:
- BTC: satoshis
- EUR/USD: cents
Enum values (lowercase):
- Disk types:
hdd, ssd
- Disk interfaces:
sata, scsi, pcie
- VM states:
pending, running, stopped, failed, error, unknown
- Payment methods:
lightning, revolut, paypal, stripe, nwc
Response Format
Success:
{"data": { ... }}
Paginated:
{"data": [...], "total": 100, "limit": 20, "offset": 0}
Error:
{ "error": "Description of the error" }
Email Verification
LNVPS accounts require a verified email address. The verification flow works as follows:
- Set email on account —
PATCH /api/v1/account with {"email": "user@example.com"}. The server sends a verification link to that address.
- User clicks the link — the link includes a
token query parameter pointing to the API.
- Confirm the token —
GET /api/v1/account/verify-email?token=<token>. Returns 200 OK on success.
- Check status —
GET /api/v1/account returns email_verified: true once confirmed.
Check if email is verified
GET /api/v1/account
Look for email_verified: true in the response:
{
"data": {
"email": "user@example.com",
"email_verified": true,
...
}
}
Trigger verification email
PATCH /api/v1/account
Content-Type: application/json
{"email": "user@example.com"}
Sending a PATCH with an email address causes the server to send a verification email. If the email is already set, send it again to re-trigger the email.
Confirm the token
GET /api/v1/account/verify-email?token=<token>
The token value comes from the link in the verification email. Returns 200 OK with no body on success.
Common Tasks
Enable auto-renewal with NWC:
PATCH /api/v1/account
{"nwc_connection_string": "nostr+walletconnect://..."}
Set reverse DNS:
PATCH /api/v1/vm/{id}
{"reverse_dns": "myserver.example.com"}
Get upgrade quote:
POST /api/v1/vm/{id}/upgrade/quote
{"cpu": 4, "memory": 8589934592, "disk": 107374182400}
CLI Usage with nak curl
LLMs and agents can call the LNVPS API directly from the command line using nak. The nak curl subcommand works exactly like curl but automatically appends the NIP-98 Authorization header.
Setup
go install github.com/fiatjaf/nak@latest
nak key generate | nak encode nsec
mkdir -p ~/.nostr && chmod 700 ~/.nostr
echo "nsec1..." > ~/.nostr/lnvps.nsec && chmod 600 ~/.nostr/lnvps.nsec
To view your public key (npub):
nak key public $(cat ~/.nostr/lnvps.nsec) | nak encode npub
Usage
nak curl accepts all standard curl options. Set the NOSTR_SECRET_KEY environment variable and use nak curl like regular curl:
NOSTR_SECRET_KEY="nsec1..." nak curl [curl options] <url>
Important: Do NOT use --sec flag with nak curl - it doesn't work correctly. Always use the NOSTR_SECRET_KEY environment variable instead.
Examples
NOSTR_SECRET_KEY=$(cat ~/.nostr/lnvps.nsec) nak curl https://api.lnvps.net/api/v1/vm
NOSTR_SECRET_KEY=$(cat ~/.nostr/lnvps.nsec) nak curl -X POST -H "Content-Type: application/json" \
-d '{"name": "my-key", "key_data": "ssh-ed25519 AAAAC3..."}' \
https://api.lnvps.net/api/v1/ssh-key
NOSTR_SECRET_KEY=$(cat ~/.nostr/lnvps.nsec) nak curl -X POST -H "Content-Type: application/json" \
-d '{"template_id": 1, "image_id": 1, "ssh_key_id": 1}' \
https://api.lnvps.net/api/v1/vm
NOSTR_SECRET_KEY=$(cat ~/.nostr/lnvps.nsec) nak curl "https://api.lnvps.net/api/v1/vm/123/renew?method=lightning"
NOSTR_SECRET_KEY=$(cat ~/.nostr/lnvps.nsec) nak curl https://api.lnvps.net/api/v1/payment/PAYMENT_ID
NOSTR_SECRET_KEY=$(cat ~/.nostr/lnvps.nsec) nak curl -X PATCH https://api.lnvps.net/api/v1/vm/123/start
NOSTR_SECRET_KEY=$(cat ~/.nostr/lnvps.nsec) nak curl -X PATCH https://api.lnvps.net/api/v1/vm/123/stop
NOSTR_SECRET_KEY=$(cat ~/.nostr/lnvps.nsec) nak curl -X PATCH https://api.lnvps.net/api/v1/vm/123/restart
NOSTR_SECRET_KEY=$(cat ~/.nostr/lnvps.nsec) nak curl -X PATCH -H "Content-Type: application/json" \
-d '{"reverse_dns": "myserver.example.com"}' \
https://api.lnvps.net/api/v1/vm/123
Notes
- Use
NOSTR_SECRET_KEY env var inline — do NOT use --sec flag (it doesn't work with nak curl) and do NOT export it (inline per-command prevents leaking into child processes)
- Public endpoints (
/api/v1/image, /api/v1/vm/templates) work with regular curl
- Quote URLs containing
? to avoid shell interpretation
- Poll
GET /api/v1/payment/{id} until is_paid: true after paying
- Poll
GET /api/v1/vm/{id} until status.state: "running" after payment