This document covers all API endpoints available in Cave Acoustics.
Most API endpoints require authentication via session cookie. The session is managed by Better Auth and stored in Cloudflare KV.
License activation endpoints use license key authentication instead of session.
GET /api/licenses
Returns all licenses owned by the authenticated user.
Authentication: Required (session)
Response:
{
"licenses": [
{
"id": "license-uuid",
"licenseKey": "CAVE-XXXX-XXXX-XXXX-XXXX",
"productId": "product-uuid",
"product": {
"name": "Plugin Name",
"slug": "plugin-name"
},
"status": "active",
"activationCount": 2,
"maxActivations": 3,
"activations": [
{
"id": "activation-uuid",
"machineId": "hardware-id",
"machineName": "My MacBook",
"osType": "macos",
"status": "active",
"activatedAt": "2024-01-15T10:30:00Z"
}
]
}
]
}POST /api/licenses/authorize
Activates a license on a machine. Called by the plugin software.
Authentication: License key (in body)
Request Body:
{
"licenseKey": "CAVE-XXXX-XXXX-XXXX-XXXX",
"machineId": "unique-hardware-identifier",
"machineName": "My Computer",
"osType": "macos",
"productSlug": "plugin-name"
}Success Response (200):
{
"success": true,
"message": "License activated successfully",
"activationsRemaining": 2
}Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | Invalid request | Missing or invalid fields |
| 404 | Product not found | Invalid productSlug |
| 404 | License not found | Invalid licenseKey for product |
| 403 | License not active | License revoked or transferred |
| 403 | Maximum activations reached | Activation limit exceeded |
| 409 | Already activated | Machine already has active activation |
POST /api/licenses/deauthorize
Deactivates a license from a machine.
Authentication: License key (in body)
Request Body:
{
"licenseKey": "CAVE-XXXX-XXXX-XXXX-XXXX",
"machineId": "unique-hardware-identifier",
"productSlug": "plugin-name"
}Success Response (200):
{
"success": true,
"message": "License deactivated successfully"
}POST /api/licenses/transfer
Transfers a license to another user.
Authentication: Required (session)
Request Body:
{
"licenseId": "license-uuid",
"recipientEmail": "newowner@example.com"
}Success Response (200):
{
"success": true,
"message": "License transferred successfully"
}Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | Invalid request | Missing fields |
| 403 | Not authorized | User doesn't own license |
| 404 | Recipient not found | No account with email |
| 409 | Already owns product | Recipient has license for product |
GET /api/licenses/owned
Returns product IDs the user owns licenses for.
Authentication: Required (session)
Response:
{
"ownedProductIds": ["product-uuid-1", "product-uuid-2"]
}POST /api/licenses/register
Links an existing license key to a user account.
Authentication: Required (session)
Request Body:
{
"licenseKey": "CAVE-XXXX-XXXX-XXXX-XXXX"
}Better Auth handles authentication at /api/auth/*. See Better Auth documentation for details.
POST /api/auth/sign-in/email
Request Body:
{
"email": "user@example.com",
"password": "password123"
}POST /api/auth/sign-up/email
Request Body:
{
"name": "John Doe",
"email": "user@example.com",
"password": "password123"
}POST /api/auth/sign-out
GET /api/auth/get-session
POST /api/color-scheme
Sets the user's color scheme preference.
Request Body:
{
"colorScheme": "dark"
}Values: "light", "dark", "system"
POST /api/auth/*
Stripe webhooks are routed through Better Auth's endpoint. The handler verifies the Stripe signature and processes events.
Handled Events:
checkout.session.completed- Purchase/gift card completioncharge.refunded- Refund processing
All error responses follow this format:
{
"error": "Error message describing what went wrong"
}Or for validation errors:
{
"errors": {
"fieldName": ["Error message for this field"]
}
}Currently no rate limiting is implemented. For production, consider adding:
- Rate limiting on license activation endpoints
- Rate limiting on authentication endpoints
- IP-based blocking for abuse
The API is same-origin only. Cross-origin requests are not supported.
For plugin authorization endpoints, plugins should make requests directly (not through browser).