Files
radiantwave/portal_api_v2_schema.md
T

192 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Portal API v2 — current schema
The kiosk talks to `https://portal.radiantwavetech.com/api/v2`. This document
describes the current contract after the move to an **active/inactive license
model**. There are no subscription levels: a license is either **valid**
(subscribed) or not, and the kiosk treats a successful `POST /license`
validation as the device's "subscribed" flag.
Base URL: `https://portal.radiantwavetech.com/api/v2`
Authentication: `Authorization: Bearer sk_<hex>` on every request (the API key
is baked into the kiosk binary at build time).
---
## `POST /license`
Validates a license key / email pair.
Request:
```
POST /api/v2/license
Content-Type: application/json
Authorization: Bearer sk_<hex>
```
```json
{
"license_key": "XXXX-XXXX-XXXX-XXXX",
"email_address": "user@example.com"
}
```
Response (200):
```json
{
"key": "XXXX-XXXX-XXXX-XXXX",
"email_address": "user@example.com"
}
```
Contract (kiosk `parseLicenseJson`):
- `key`: string, **required non-empty** (empty key ⇒ parse failure).
- `email_address`: string.
- Any other fields (e.g. the legacy `subscription_level`) are ignored. A 2xx
response means the license is valid and the subscription is **active**; the
kiosk stores no subscription state other than the `license_last_verified`
stamp that drives its 7-day offline grace window.
Errors:
| Code | Body | Meaning |
|------|------|---------|
| 200 | success object | License valid and owned by the email |
| 400 | `{"message": "license_key is required"}` | Missing `license_key` |
| 400 | `{"message": "email_address is required"}` | Missing `email_address` |
| 400 | `{"message": "license key not found"}` | No key with that value |
| 400 | `{"message": "license key is not active"}` | Key exists but inactive |
| 400 | `{"message": "user does not exist"}` | No user with that email |
| 400 | `{"message": "key does not belong to specified user"}` | Key owned by a different user |
| 401 | `{"error": "..."}` | Missing/invalid API key |
---
## `GET /proclamations`
Lists every proclamation. There is **no `subscriptionLevelId`** — any validated
license may access any proclamation.
Response (200):
```json
[
{
"id": "b40fdcb9-703c-420e-af32-a081964c6f08",
"name": "Peace & Relaxation",
"content": "<p>The text content, HTML formatted</p>",
"categoryId": 5,
"solfeggioFrequencyId": 5,
"colorPalette": ["#32CD32", "#3CB371", "#90EE90", "#98FB98", "#ADFFA9", "#C8FFD2", "#F0FFF0"]
}
]
```
Contract (kiosk `parseProclamationsJson` + `parseProclamationPalettesJson`):
- `id`: string UUID, **required non-empty** (empty ids are skipped).
- `name`: string, used as the display title. Kiosk strips HTML from it.
- `content`: string, **HTML** (the kiosk strips tags and unescapes entities
before rendering).
- `categoryId`: int, maps to a `/categories` id.
- `solfeggioFrequencyId`: int, **must be a canonical id (1–9)**.
- `colorPalette` (optional): array of exactly 7 `"#RRGGBB"` strings. When
absent, the kiosk falls back to `/color-palettes`, then to its own 528 Hz
seed.
- Legacy `subscriptionLevelId` fields in the payload are ignored.
Errors:
| Code | Body | Meaning |
|------|------|---------|
| 200 | array of proclamation records | Success |
| 400 | `{"message": "not-authorized"}` | Entitlement headers supplied but key or email does not exist |
| 401 | `{"error": "..."}` | Missing/invalid API key |
---
## `GET /categories`
Lists the proclamation categories.
Response (200):
```json
[
{
"id": 4,
"name": "Spiritual",
"parentId": 0,
"sortOrder": 0
}
]
```
Contract (kiosk `parseCategoriesJson`):
- `id`: int, **required non-zero** (zero ids are skipped).
- `name`: string.
- `parentId`: int, `null` maps to 0 (root).
- `sortOrder`: int.
---
## `POST /automations/create/account`
Creates (or reuses) a user account and issues a license key. **No
`subscription_level` is sent** — every created license is a standard active
license.
Request:
```
POST /api/v2/automations/create/account
Content-Type: application/json
Authorization: Bearer sk_<hex>
X-Timestamp: <unix seconds>
X-Nonce: <random uuid>
X-Signature: <hex HMAC-SHA256 of canonical request with the onboarding secret>
```
```json
{
"email": "user@example.com",
"name": "Optional Display Name"
}
```
- `email` (required, string).
- `name` (optional, string; defaults to the local part of `email`).
Response (200):
```json
{
"success": true,
"message": "Account and license key created",
"user_id": "uuid"
}
```
Errors:
| Code | Body | Meaning |
|------|------|---------|
| 200 | success object | Account (+ license key) created |
| 400 | `{"message": "email is required"}` | Missing/empty `email` |
| 401 | `{"error": "invalid signature"}` | Bad/expired/replayed HMAC signature |
| 429 | `{"message": "rate limit exceeded"}` | Too many requests from this IP/email |
| 500 | `{"message": "Failed to create account"}` | User creation failed |
---
## Readiness routes (not yet wired into the app)
- `GET /color-palettes` → `[{solfeggioFrequencyId, colors[7]}]`
- `GET /frequencies` → canonical id 1–9 + `value` in Hz
These exist for future sync steps; the kiosk does not call them yet.