Add OpenAPI v2 spec; remove draft docs
Track the portal v2 API as openapi.yaml and drop the superseded portal_api_v2_schema.md and user_manual_draft.md.
This commit is contained in:
+467
@@ -0,0 +1,467 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Radiant Wave Portal API (v2)
|
||||
version: 2.0.0
|
||||
description: |
|
||||
Read API used by the Radiant Wave kiosk plus the automation onboarding
|
||||
webhook.
|
||||
|
||||
## Authentication
|
||||
The v2 API is **not** API-key gated. The former bearer-token middleware in
|
||||
`src/hooks.server.ts` is present but disabled, so v2 routes are served
|
||||
without a global API key.
|
||||
|
||||
Two endpoints carry their own credential semantics:
|
||||
|
||||
- `GET /api/v2/proclamations` optionally accepts the paired
|
||||
`x-license-key` / `x-email` headers to scope results to an entitled user.
|
||||
Supplying neither returns the full catalogue; supplying only one, or a
|
||||
pair that does not match, is rejected.
|
||||
- `POST /api/v2/automations/create/account` is protected by an HMAC-SHA256
|
||||
request signature (see the operation description).
|
||||
servers:
|
||||
- url: https://portal.radiantwavetech.com
|
||||
description: Production
|
||||
- url: http://localhost:5173
|
||||
description: Local development
|
||||
tags:
|
||||
- name: Catalogue
|
||||
description: Reference data for categories, groups, frequencies and palettes.
|
||||
- name: Proclamations
|
||||
description: Proclamation content, optionally scoped by license entitlement.
|
||||
- name: License
|
||||
description: License key validation.
|
||||
- name: Automations
|
||||
description: Signed machine-to-machine webhooks.
|
||||
paths:
|
||||
/api/v2/categories:
|
||||
get:
|
||||
tags: [Catalogue]
|
||||
summary: List proclamation categories
|
||||
operationId: listCategories
|
||||
responses:
|
||||
'200':
|
||||
description: All proclamation categories.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Category'
|
||||
/api/v2/proclamation-categories:
|
||||
get:
|
||||
tags: [Catalogue]
|
||||
summary: List proclamation categories (dedicated alias)
|
||||
description: Alias of `/api/v2/categories`; returns the identical payload.
|
||||
operationId: listProclamationCategories
|
||||
responses:
|
||||
'200':
|
||||
description: All proclamation categories.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Category'
|
||||
/api/v2/groups:
|
||||
get:
|
||||
tags: [Catalogue]
|
||||
summary: List proclamation groups
|
||||
description: Groups are ordered by `sortOrder` ascending.
|
||||
operationId: listGroups
|
||||
responses:
|
||||
'200':
|
||||
description: All proclamation groups.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Group'
|
||||
/api/v2/proclamation-groups:
|
||||
get:
|
||||
tags: [Catalogue]
|
||||
summary: List proclamation groups (dedicated alias)
|
||||
description: Alias of `/api/v2/groups`; returns the identical payload.
|
||||
operationId: listProclamationGroups
|
||||
responses:
|
||||
'200':
|
||||
description: All proclamation groups.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Group'
|
||||
/api/v2/frequencies:
|
||||
get:
|
||||
tags: [Catalogue]
|
||||
summary: List canonical solfeggio frequencies
|
||||
description: |
|
||||
Returns the nine canonical frequencies (ids 1-9) the kiosk expects.
|
||||
These are static values, not the portal database autoincrement ids.
|
||||
operationId: listFrequencies
|
||||
responses:
|
||||
'200':
|
||||
description: The nine canonical frequencies.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
minItems: 9
|
||||
maxItems: 9
|
||||
items:
|
||||
$ref: '#/components/schemas/Frequency'
|
||||
/api/v2/color-palettes:
|
||||
get:
|
||||
tags: [Catalogue]
|
||||
summary: List color palettes per canonical frequency
|
||||
description: |
|
||||
Returns one 7-color palette for each canonical frequency id (1-9).
|
||||
Palette rows stored in the database take precedence; ids without a row
|
||||
fall back to the built-in canonical palette.
|
||||
operationId: listColorPalettes
|
||||
responses:
|
||||
'200':
|
||||
description: Nine palettes, one per canonical frequency id.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
minItems: 9
|
||||
maxItems: 9
|
||||
items:
|
||||
$ref: '#/components/schemas/ColorPalette'
|
||||
/api/v2/proclamations:
|
||||
get:
|
||||
tags: [Proclamations]
|
||||
summary: List proclamations
|
||||
description: |
|
||||
Without entitlement headers, returns the full proclamation catalogue.
|
||||
|
||||
With both `x-license-key` and `x-email`, returns only the proclamations
|
||||
assigned to the matching user. Both headers must be present together and
|
||||
the key must belong to the user.
|
||||
|
||||
Proclamations whose frequency has no canonical id (for example 936 Hz)
|
||||
are omitted from the response.
|
||||
operationId: listProclamations
|
||||
parameters:
|
||||
- in: header
|
||||
name: x-license-key
|
||||
schema:
|
||||
type: string
|
||||
required: false
|
||||
description: License key. Must be supplied together with `x-email`.
|
||||
- in: header
|
||||
name: x-email
|
||||
schema:
|
||||
type: string
|
||||
format: email
|
||||
required: false
|
||||
description: Account email. Must be supplied together with `x-license-key`.
|
||||
responses:
|
||||
'200':
|
||||
description: Proclamation records with canonical frequency ids and embedded palettes.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Proclamation'
|
||||
'400':
|
||||
description: |
|
||||
Missing entitlement or the supplied key/email pair is invalid.
|
||||
Message is `not-authorized`, or
|
||||
`this key doesn't appear to belong to you` when the key does not
|
||||
belong to the specified user.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorMessage'
|
||||
/api/v2/license:
|
||||
post:
|
||||
tags: [License]
|
||||
summary: Validate a license key
|
||||
description: |
|
||||
Validates that the license key exists, is active, and belongs to the
|
||||
account matching `email_address`. A successful validation records
|
||||
`lastValidatedAt` on the key.
|
||||
operationId: validateLicense
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LicenseRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: The validated key and its owner email.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LicenseResponse'
|
||||
'400':
|
||||
description: |
|
||||
Validation failure or malformed body. Possible messages:
|
||||
`content-type must be application/json`, `invalid JSON body`,
|
||||
`request body is required`, `license_key is required`,
|
||||
`email_address is required`, `license key not found`,
|
||||
`license key is not active`, `user does not exist`,
|
||||
`key does not belong to specified user`.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorMessage'
|
||||
'413':
|
||||
description: The request body exceeded the 64 KiB limit.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorMessage'
|
||||
'429':
|
||||
description: Rate limit exceeded (10 requests per minute per IP).
|
||||
headers:
|
||||
Retry-After:
|
||||
schema:
|
||||
type: integer
|
||||
description: Seconds to wait before retrying.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorMessage'
|
||||
/api/v2/automations/create/account:
|
||||
post:
|
||||
tags: [Automations]
|
||||
summary: Create an account and license key (signed webhook)
|
||||
description: |
|
||||
Machine-to-machine onboarding webhook. The request must be signed with
|
||||
HMAC-SHA256 using `API_V2_ONBOARDING_SECRET`.
|
||||
|
||||
The canonical string that is signed is the newline-joined concatenation
|
||||
of:
|
||||
|
||||
```
|
||||
POST
|
||||
<path>
|
||||
<x-timestamp>
|
||||
<x-nonce>
|
||||
<raw request body>
|
||||
```
|
||||
|
||||
where `<path>` is `/api/v2/automations/create/account`. The signature is
|
||||
the lowercase hex digest.
|
||||
|
||||
On success, creates the user if they do not already exist, generates a
|
||||
license key, emails it, and returns the user id. Invalid or missing
|
||||
signatures return `401 { "error": "invalid signature" }` (the same
|
||||
response is used for every signature failure, including replay and
|
||||
timestamp skew).
|
||||
operationId: createAutomationAccount
|
||||
parameters:
|
||||
- in: header
|
||||
name: x-timestamp
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Unix epoch seconds. Must be within 300 seconds of the server clock.
|
||||
- in: header
|
||||
name: x-nonce
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Unique value per request. Replays within the skew window are rejected.
|
||||
- in: header
|
||||
name: x-signature
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
pattern: '^[0-9a-f]{64}$'
|
||||
description: Lowercase hex HMAC-SHA256 of the canonical request string.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OnboardingRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Account and license key created.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OnboardingResponse'
|
||||
'400':
|
||||
description: |
|
||||
Malformed or invalid request. Possible messages:
|
||||
`request body is required`, `invalid JSON body`, or
|
||||
`email is required`.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorMessage'
|
||||
'401':
|
||||
description: Signature missing, malformed, expired, replayed, or incorrect.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [error]
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
example: invalid signature
|
||||
'413':
|
||||
description: The request body exceeded the 64 KiB limit.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorMessage'
|
||||
'429':
|
||||
description: |
|
||||
Rate limit exceeded. Limits are 5 requests per minute per IP and
|
||||
3 requests per 24 hours per email address.
|
||||
headers:
|
||||
Retry-After:
|
||||
schema:
|
||||
type: integer
|
||||
description: Seconds to wait before retrying.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorMessage'
|
||||
'500':
|
||||
description: The account could not be created.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorMessage'
|
||||
components:
|
||||
schemas:
|
||||
Category:
|
||||
type: object
|
||||
required: [id, name, sortOrder]
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
sortOrder:
|
||||
type: integer
|
||||
Group:
|
||||
type: object
|
||||
required: [id, name, sortOrder]
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
sortOrder:
|
||||
type: integer
|
||||
Frequency:
|
||||
type: object
|
||||
required: [id, name, value]
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 9
|
||||
description: Canonical id used by the kiosk.
|
||||
name:
|
||||
type: string
|
||||
value:
|
||||
type: integer
|
||||
description: Frequency in Hz.
|
||||
ColorPalette:
|
||||
type: object
|
||||
required: [solfeggioFrequencyId, colors]
|
||||
properties:
|
||||
solfeggioFrequencyId:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 9
|
||||
colors:
|
||||
type: array
|
||||
description: Seven hex colors, ordered light to dark.
|
||||
minItems: 1
|
||||
items:
|
||||
type: string
|
||||
pattern: '^#[0-9A-Fa-f]{6}$'
|
||||
Proclamation:
|
||||
type: object
|
||||
required: [id, name, content, categoryId, groupId, solfeggioFrequencyId]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
content:
|
||||
type: string
|
||||
description: HTML content.
|
||||
categoryId:
|
||||
type: integer
|
||||
groupId:
|
||||
type: integer
|
||||
solfeggioFrequencyId:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 9
|
||||
colorPalette:
|
||||
type: array
|
||||
description: Present only when a palette exists for the canonical frequency id.
|
||||
minItems: 1
|
||||
items:
|
||||
type: string
|
||||
pattern: '^#[0-9A-Fa-f]{6}$'
|
||||
LicenseRequest:
|
||||
type: object
|
||||
required: [license_key, email_address]
|
||||
properties:
|
||||
license_key:
|
||||
type: string
|
||||
pattern: '^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'
|
||||
example: ABCD-EFGH-IJKL-MNOP
|
||||
email_address:
|
||||
type: string
|
||||
format: email
|
||||
maxLength: 254
|
||||
LicenseResponse:
|
||||
type: object
|
||||
required: [key, email_address]
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
example: ABCD-EFGH-IJKL-MNOP
|
||||
email_address:
|
||||
type: string
|
||||
format: email
|
||||
OnboardingRequest:
|
||||
type: object
|
||||
required: [email]
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
maxLength: 254
|
||||
name:
|
||||
type: string
|
||||
maxLength: 120
|
||||
description: Optional display name. Defaults to the email local-part.
|
||||
OnboardingResponse:
|
||||
type: object
|
||||
required: [success, message, user_id]
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
example: Account and license key created
|
||||
user_id:
|
||||
type: string
|
||||
ErrorMessage:
|
||||
type: object
|
||||
required: [message]
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
Reference in New Issue
Block a user