803 lines
26 KiB
YAML
803 lines
26 KiB
YAML
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: Schedule
|
|
description: A user's daily listening schedule, consumed by the kiosk.
|
|
- 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/schedule:
|
|
get:
|
|
tags: [Schedule]
|
|
summary: Get a user's listening schedule (query-param auth)
|
|
description: |
|
|
Machine-to-app endpoint used by the RadiantWave kiosk. Authenticates via
|
|
the paired `email` + `licenseKey` **query parameters** and validates only
|
|
that the key belongs to the account for that email. License
|
|
active/entitlement is deliberately not checked here: an invalid license
|
|
would already lock the kiosk out, so this endpoint only checks the
|
|
pairing.
|
|
|
|
Returns every Block with its start/end times and the ordered list of
|
|
selected Proclamation IDs (the legacy daily flattening in `blocks`), and
|
|
is extended additively with `version`, `type`, `daily`, and `weekly`.
|
|
Overnight blocks (an end clock time before the start) are supported and
|
|
flagged with `overnight: true` and a wrapping `durationMinutes`. Each
|
|
block also carries `dynamicAudio`; when true, the kiosk enables dynamic
|
|
audio for the block's duration, overriding any audio selections made on
|
|
the device. Proclamation content is not included; the kiosk fetches it
|
|
separately via `/api/v2/proclamations`. For `type: "weekly"`, `blocks` is
|
|
an empty array (the server cannot flatten weekly in the kiosk's
|
|
timezone). This endpoint emits no ETag and never returns 304.
|
|
operationId: getSchedule
|
|
parameters:
|
|
- in: query
|
|
name: email
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: email
|
|
description: Account email.
|
|
- in: query
|
|
name: licenseKey
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'
|
|
description: License key. The legacy alias `key` is also accepted.
|
|
responses:
|
|
'200':
|
|
description: The user's schedule (possibly empty).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ScheduleResponse'
|
|
example:
|
|
email: jane@example.com
|
|
blocks:
|
|
- id: 3f1c2b7a-9d4e-4c8a-b2f6-1a2b3c4d5e6f
|
|
startTime: '21:00'
|
|
startMinutes: 1260
|
|
endTime: '09:00'
|
|
endMinutes: 540
|
|
overnight: true
|
|
durationMinutes: 720
|
|
dynamicAudio: true
|
|
proclamationIds:
|
|
- b40fdcb9-703c-420e-af32-a081964c6f08
|
|
- 7c9e1a2b-3d4e-5f60-8a9b-0c1d2e3f4a5b
|
|
'400':
|
|
description: |
|
|
Missing/invalid parameters, unknown key or user, or the key does not
|
|
belong to the email's account. Possible messages: `not-authorized`,
|
|
`this key doesn't appear to belong to you`.
|
|
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/licenses/{licenseKey}/schedule:
|
|
get:
|
|
tags: [Schedule]
|
|
summary: Get a License Key's schedule (Kiosk-facing, conditional)
|
|
description: |
|
|
Kiosk-facing, License-Key-only schedule read. Returns the target payload
|
|
(`version`, `type`, `daily`, `weekly`, `blocks`). `version` is a
|
|
content-derived opaque string and is also the `ETag`; a matching
|
|
`If-None-Match` returns `304`. An unknown License Key is `404`; a key
|
|
with no Schedule returns `type: null` with empty lists. No
|
|
authentication (see the API description) beyond the 10/min/IP limit.
|
|
operationId: getLicenseSchedule
|
|
parameters:
|
|
- in: path
|
|
name: licenseKey
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'
|
|
- in: header
|
|
name: If-None-Match
|
|
required: false
|
|
schema:
|
|
type: string
|
|
description: Prior ETag; when it matches, returns 304.
|
|
responses:
|
|
'200':
|
|
description: The target schedule payload.
|
|
headers:
|
|
ETag:
|
|
schema:
|
|
type: string
|
|
description: The quoted `version`.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/SchedulePayload'
|
|
'304':
|
|
description: Not modified; the caller keeps its cached payload.
|
|
headers:
|
|
ETag:
|
|
schema:
|
|
type: string
|
|
'404':
|
|
description: 'Unknown license (`unknown_license`).'
|
|
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/licenses/{licenseKey}/system-name:
|
|
put:
|
|
tags: [License]
|
|
summary: Set a License Key's System Name (Kiosk-only)
|
|
description: |
|
|
Sets the human-readable System Name for a License Key. The Kiosk is the
|
|
only writer; the Portal displays the name read-only. The name is unique
|
|
per account, compared case-insensitively, when non-null. Idempotent:
|
|
re-sending the current name returns 200 without a write.
|
|
operationId: setSystemName
|
|
parameters:
|
|
- in: path
|
|
name: licenseKey
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [systemName]
|
|
properties:
|
|
systemName:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 40
|
|
responses:
|
|
'200':
|
|
description: The stored name.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [systemName, systemNameStatus]
|
|
properties:
|
|
systemName:
|
|
type: string
|
|
systemNameStatus:
|
|
type: string
|
|
enum: [ok]
|
|
'400':
|
|
description: 'Invalid name (`invalid_name`); empty/whitespace is invalid.'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorMessage'
|
|
'404':
|
|
description: 'Unknown license (`unknown_license`).'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorMessage'
|
|
'409':
|
|
description: 'Name already used on the account (`name_taken`).'
|
|
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
|
|
systemName:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 40
|
|
description: |
|
|
Optional. The Kiosk's System Name (trimmed). Omitted means the stored
|
|
name is left untouched; empty/whitespace is invalid, never a clear.
|
|
LicenseResponse:
|
|
type: object
|
|
required: [key, email_address, systemNameStatus]
|
|
properties:
|
|
key:
|
|
type: string
|
|
example: ABCD-EFGH-IJKL-MNOP
|
|
email_address:
|
|
type: string
|
|
format: email
|
|
systemNameStatus:
|
|
type: string
|
|
enum: [ok, unchanged, conflict, invalid]
|
|
description: |
|
|
Advisory outcome of the optional `systemName`. Validation itself
|
|
always succeeds. `unchanged` is returned when the field is omitted.
|
|
ScheduleBlock:
|
|
type: object
|
|
required:
|
|
[
|
|
id,
|
|
startTime,
|
|
startMinutes,
|
|
endTime,
|
|
endMinutes,
|
|
overnight,
|
|
durationMinutes,
|
|
dynamicAudio,
|
|
proclamationIds
|
|
]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
startTime:
|
|
type: string
|
|
example: '06:00'
|
|
description: Wall-clock start time as `HH:MM`.
|
|
startMinutes:
|
|
type: integer
|
|
minimum: 0
|
|
maximum: 1440
|
|
description: Minutes from midnight.
|
|
endTime:
|
|
type: string
|
|
example: '08:00'
|
|
description: Wall-clock end time as `HH:MM`.
|
|
endMinutes:
|
|
type: integer
|
|
minimum: 0
|
|
maximum: 1440
|
|
description: Minutes from midnight.
|
|
overnight:
|
|
type: boolean
|
|
description: True when the end clock time is at or before the start, i.e. the block crosses midnight.
|
|
durationMinutes:
|
|
type: integer
|
|
minimum: 1
|
|
description: Block length in minutes. An overnight block counts the wrap past midnight as the next day.
|
|
dynamicAudio:
|
|
type: boolean
|
|
description: |
|
|
When true, dynamic audio is enabled for the block's duration, overriding any
|
|
audio selections made on the device while the block is active.
|
|
proclamationIds:
|
|
type: array
|
|
description: IDs of the selected Proclamations, in play order.
|
|
items:
|
|
type: string
|
|
format: uuid
|
|
ScheduleResponse:
|
|
type: object
|
|
required: [email, blocks]
|
|
properties:
|
|
email:
|
|
type: string
|
|
format: email
|
|
version:
|
|
type: string
|
|
type:
|
|
type: string
|
|
nullable: true
|
|
enum: [daily, weekly, null]
|
|
daily:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ScheduleBlock'
|
|
weekly:
|
|
type: object
|
|
additionalProperties:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ScheduleBlock'
|
|
blocks:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ScheduleBlock'
|
|
SchedulePayload:
|
|
type: object
|
|
required: [version, type, daily, weekly, blocks]
|
|
properties:
|
|
version:
|
|
type: string
|
|
description: Opaque content hash; doubles as the ETag.
|
|
type:
|
|
type: string
|
|
nullable: true
|
|
enum: [daily, weekly, null]
|
|
daily:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ScheduleBlock'
|
|
weekly:
|
|
type: object
|
|
description: Present with all seven keys when `type` is weekly.
|
|
additionalProperties:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ScheduleBlock'
|
|
blocks:
|
|
type: array
|
|
description: Legacy daily flattening; empty for weekly.
|
|
items:
|
|
$ref: '#/components/schemas/ScheduleBlock'
|
|
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
|