docs(auth,devices): Beam WO5 batch 3 — 15 endpoints backfilled, registry pinned

Beam backfilled auth core (login/register/refresh/logout/profile) and devices
core (list/update/delete/shelf CRUD/sidecar/register flow) with frontmatter,
provenance, and labeled examples. All claimed shapes verified against
handlers: AuthResponse {access_token, refresh_token omitempty, token_type,
expires_in, user}, update device {device_updated, device}, shelf wrappers,
DeviceAuthStatusResponse.

Claude registry verification: 15 entries pinned by type (LoginRequest,
RegisterRequest, AuthResponse, RefreshTokenRequest/Response, UserProfile,
DeviceListResponse, DeviceUpdateRequest, AddToShelfRequest, SidecarConfig,
DeviceRegistrationRequest/Response, DeviceAuthStatusRequest/Response).
Removed revoke_device.md — name-only duplicate of delete_device.md (same
DELETE /api/devices/:id route; was the last audit-orphan doc).

Gate catches this round (subtle Go serialization facts, now pinned in docs):
- uuid.UUID arrays never serialize as omitted: register/status response
  ALWAYS carries device_id (all-zeros while pending) despite omitempty
- update device request binds device_name, not name
- register response includes device_id alongside registration_id
- list devices response is a {devices, total} wrapper
This commit is contained in:
John O'Keefe committed 2026-10-24 15:48:18 -04:00
1 parent eacc33bfac
commit ae9588f1c6
17 files changed
+274 -99

No files matched your search

+10 -3
View File
@@ -1,3 +1,11 @@
---
method: POST
path: /api/auth/login
auth: none
area: authentication
---
<!-- source: internal/handlers/auth.go:378 AuthHandler.Login -->
# Login User
Authenticate with email and password.
@@ -15,7 +23,7 @@ Authenticate with email and password.
### Example Request
```json
```json example-request
{
"login": "user@example.com",
"password": "SecureP@ss123!"
@@ -24,10 +32,9 @@ Authenticate with email and password.
## Response (200 OK)
```json
```json example-response
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "d4f5g6h7...",
"token_type": "Bearer",
"expires_in": 604800,
"user": {
+14 -2
View File
@@ -1,3 +1,11 @@
---
method: POST
path: /api/auth/logout
auth: none
area: authentication
---
<!-- source: internal/handlers/refresh_token.go:76 AuthHandler.Logout -->
# Logout User
Invalidate the current JWT token.
@@ -19,9 +27,13 @@ POST /api/auth/logout
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
## Response (204 No Content)
## Response (200 OK)
No response body.
```json example-response
{
"message": "logged out successfully"
}
```
## Error Responses
@@ -1,3 +1,11 @@
---
method: POST
path: /api/auth/refresh
auth: none
area: authentication
---
<!-- source: internal/handlers/refresh_token.go:36 AuthHandler.RefreshAccessToken -->
# Refresh Token
Obtain a new JWT access token using a refresh token.
@@ -14,7 +22,7 @@ Obtain a new JWT access token using a refresh token.
### Example Request
```json
```json example-request
{
"refresh_token": "d4f5g6h7..."
}
@@ -22,7 +30,7 @@ Obtain a new JWT access token using a refresh token.
## Response (200 OK)
```json
```json example-response
{
"access_token": "new-jwt-token",
"token_type": "Bearer",
+12 -7
View File
@@ -1,3 +1,11 @@
---
method: POST
path: /api/auth/register
auth: none
area: authentication
---
<!-- source: internal/handlers/auth.go:153 AuthHandler.Register -->
# Register User
Create a new user account.
@@ -18,7 +26,7 @@ Create a new user account.
### Example Request
```json
```json example-request
{
"email": "user@example.com",
"username": "john",
@@ -30,10 +38,9 @@ Create a new user account.
## Response (201 Created)
```json
```json example-response
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "d4f5g6h7...",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 604800,
"user": {
@@ -42,9 +49,7 @@ Create a new user account.
"username": "john",
"first_name": "John",
"last_name": "Doe",
"role": "user",
"theme": "tokyo-night",
"created_at": "2026-01-31T10:00:00Z"
"role": "user"
}
}
```
+22 -9
View File
@@ -1,3 +1,11 @@
---
method: POST
path: /api/devices/:id/shelves
auth: jwt
area: devices
---
<!-- source: internal/handlers/media.go:256 MediaHandler.AddToShelf -->
# Add to Shelf
Add a media item to a device's shelf (Kobo reading shelf).
@@ -15,24 +23,29 @@ Add a media item to a device's shelf (Kobo reading shelf).
## Request Body
| Field | Type | Required | Description |
| ------------- | ------------- | -------- | ------------------------------- |
| media_item_id | string (UUID) | Yes | Media item UUID to add to shelf |
| -------------- | --------------- | -------- | ------------------------------- |
| media_item_ids | array of string | Yes | Media item UUIDs to add to shelf |
| shelf_name | string | No | Shelf name (default "Default") |
| shelf_position | int | No | Starting shelf position |
### Example Request
```json
```json example-request
{
"media_item_id": "550e8400-e29b-41d4-a716-446655440001"
"media_item_ids": ["550e8400-e29b-41d4-a716-446655440001"],
"shelf_name": "Default",
"shelf_position": 0
}
```
## Response (201 Created)
## Response (200 OK)
```json
```json example-response
{
"message": "Item added to shelf successfully",
"device_id": "uuid",
"media_item_id": "uuid"
"status": "success",
"added_count": 1,
"shelf_name": "Default",
"total_count": 1
}
```
+15 -2
View File
@@ -1,3 +1,11 @@
---
method: DELETE
path: /api/devices/:id/shelves/clear
auth: jwt
area: devices
---
<!-- source: internal/handlers/media.go:413 MediaHandler.ClearShelf -->
# Clear Shelf
Remove all items from a device's shelf.
@@ -24,9 +32,14 @@ DELETE /api/devices/550e8400-e29b-41d4-a716-446655440000/shelves/clear
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
## Response (204 No Content)
## Response (200 OK)
Shelf cleared successfully.
```json example-response
{
"status": "success",
"message": "all shelves cleared"
}
```
## Error Responses
@@ -1,3 +1,11 @@
---
method: DELETE
path: /api/devices/:id
auth: jwt
area: devices
---
<!-- source: internal/handlers/devices.go:413 DeviceHandler.DeleteDevice -->
# Delete Device
Delete a device and revoke its access.
+17 -13
View File
@@ -1,3 +1,11 @@
---
method: POST
path: /api/devices/register/status
auth: none
area: devices
---
<!-- source: internal/handlers/devices.go:178 DeviceHandler.CheckRegistrationStatus -->
# Get Device / Check Registration Status
Check device registration status or get device details.
@@ -14,7 +22,7 @@ Check device registration status or get device details.
### Example Request (Status Check)
```json
```json example-request
{
"registration_id": "registration-uuid"
}
@@ -22,22 +30,18 @@ Check device registration status or get device details.
## Response (200 OK)
```json
```json example-response
{
"status": "pending|approved",
"message": "awaiting user approval",
"expires_in": 123,
"auth_token": "device-bearer-token...",
"device_id": "uuid",
"sync_endpoints": {
"progress": "https://bookhoard.com/api/sync/progress",
"metadata": "https://bookhoard.com/api/sync/metadata",
"annotations": "https://bookhoard.com/api/sync/annotations"
}
"status": "pending",
"device_id": "00000000-0000-0000-0000-000000000000"
}
```
`status` is `pending` or `approved`. While pending, the response includes `message` and `expires_in` (seconds remaining). Once approved, the response includes `auth_token`, `device_id`, and `sync_endpoints`; `auth_token` fields are empty when pending.
`device_id` is always present (a uuid.UUID array never serializes as omitted —
it reads as all-zeros while pending). `status` is `pending` or `approved`.
While pending, the response includes `message` and `expires_in` (seconds
remaining). Once approved, the response includes `auth_token`, `device_id`,
and `sync_endpoints`; `auth_token` fields are empty when pending.
**The approved response is single-use**: the registration is deleted from the pending map once returned, so store the `auth_token` immediately. A repeat status check for the same `registration_id` returns 404.
+19 -4
View File
@@ -1,3 +1,11 @@
---
method: GET
path: /api/devices/:id/shelves
auth: jwt
area: devices
---
<!-- source: internal/handlers/media.go:301 MediaHandler.GetShelf -->
# Get Shelf
Get all items on a device's shelf.
@@ -26,15 +34,22 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
## Response (200 OK)
```json
```json example-response
{
"items": [
"status": "success",
"device_id": "550e8400-e29b-41d4-a716-446655440000",
"shelf_name": "",
"books": [
{
"id": "uuid",
"media_item_id": "uuid",
"title": "Book Title",
"authors": ["Author Name"],
"cover_url": "/api/media-items/uuid/cover",
"author": "Author Name",
"file_path": "",
"mime_type": "",
"shelf_name": "",
"shelf_position": 0,
"revision_number": 0,
"added_at": "2026-02-08T10:00:00Z"
}
],
@@ -1,3 +1,11 @@
---
method: GET
path: /api/devices/:id/sidecar
auth: jwt
area: devices
---
<!-- source: internal/handlers/sidecar.go:67 SidecarHandler.GetSidecarConfig -->
# Get Device Sidecar Config
Returns the KOReader/Kobo sidecar configuration (`.bookhoard.json`) for a device: server endpoints, the user's books (keyed by SHA-256 with UUID fallback), and collections. Used by the Bookhoard KOReader plugin to self-configure after approval.
@@ -7,7 +15,7 @@ Returns the KOReader/Kobo sidecar configuration (`.bookhoard.json`) for a device
### Response (200 OK)
```json
```json example-response
{
"version": "1",
"bookhoard": {
+11 -2
View File
@@ -1,3 +1,11 @@
---
method: GET
path: /api/devices
auth: jwt
area: devices
---
<!-- source: internal/handlers/devices.go:259 DeviceHandler.ListDevices -->
# List User Devices
Retrieve all devices registered to the current user.
@@ -20,7 +28,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
## Response (200 OK)
```json
```json example-response
{
"devices": [
{
@@ -33,7 +41,8 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
"auto_sync": true,
"sync_frequency_minutes": 5
}
]
],
"total": 1
}
```
+11 -2
View File
@@ -1,3 +1,11 @@
---
method: POST
path: /api/devices/register
auth: none
area: devices
---
<!-- source: internal/handlers/devices.go:123 DeviceHandler.InitiateRegistration -->
# Register Device
Register a new device for sync.
@@ -16,7 +24,7 @@ Register a new device for sync.
### Example Request
```json
```json example-request
{
"device_name": "My Kobo Clara",
"device_type": "kobo",
@@ -26,8 +34,9 @@ Register a new device for sync.
## Response (201 Created)
```json
```json example-response
{
"device_id": "550e8400-e29b-41d4-a716-446655440000",
"registration_id": "registration-uuid",
"auth_url": "https://bookhoard.com/devices/approve/abc123",
"qr_code": "data:image/png;base64,iVBORw0KG...",
@@ -1,3 +1,11 @@
---
method: DELETE
path: /api/devices/:id/shelves
auth: jwt
area: devices
---
<!-- source: internal/handlers/media.go:384 MediaHandler.RemoveFromShelf -->
# Remove from Shelf
Remove a media item from a device's shelf.
@@ -26,9 +34,14 @@ Remove a media item from a device's shelf.
}
```
## Response (204 No Content)
## Response (200 OK)
Item removed from shelf successfully.
```json example-response
{
"status": "success",
"message": "book removed from shelf"
}
```
## Error Responses
@@ -1,37 +0,0 @@
# Revoke Device
Revoke access to a device.
**Endpoint**: `DELETE /api/devices/{device_id}`
**Auth**: Required
## Path Parameters
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ----------- |
| device_id | string | Yes | Device UUID |
## Request Headers
| Header | Type | Required | Description |
| ------------- | ------ | -------- | ------------ |
| Authorization | string | Yes | Bearer token |
### Example Request
```http
DELETE /api/devices/uuid
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
## Response (204 No Content)
Device revoked successfully.
## Error Responses
| Code | Description |
| ---- | ----------------------------- |
| 401 | Invalid or expired token |
| 403 | User does not own this device |
| 404 | Device not found |
+24 -8
View File
@@ -1,3 +1,11 @@
---
method: PUT
path: /api/devices/:id
auth: jwt
area: devices
---
<!-- source: internal/handlers/devices.go:336 DeviceHandler.UpdateDevice -->
# Update Device
Update a device's information.
@@ -21,24 +29,32 @@ Update a device's information.
### Example Request
```json
```json example-request
{
"name": "My Kobo Clara",
"device_type": "kobo"
"device_name": "My Kobo Clara",
"sync_enabled": true,
"auto_sync": true,
"sync_frequency_minutes": 60
}
```
## Response (200 OK)
```json
```json example-response
{
"device_updated": true,
"device": {
"id": "uuid",
"name": "My Kobo Clara",
"device_name": "My Kobo Clara",
"device_type": "kobo",
"user_id": "user-uuid",
"status": "active",
"last_sync": null,
"last_seen": null,
"sync_enabled": true,
"auto_sync": true,
"sync_frequency_minutes": 5,
"created_at": "2026-02-08T10:00:00Z",
"updated_at": "2026-02-08T11:00:00Z"
"auth_token": ""
}
}
```
+10 -5
View File
@@ -1,3 +1,11 @@
---
method: GET
path: /api/auth/profile
auth: jwt
area: authentication
---
<!-- source: internal/handlers/auth.go:530 AuthHandler.GetProfile -->
# Get Current User Profile
Retrieve the current authenticated user's profile.
@@ -20,17 +28,14 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
## Response (200 OK)
```json
```json example-response
{
"id": "uuid",
"email": "user@example.com",
"username": "john",
"first_name": "John",
"last_name": "Doe",
"theme": "tokyo-night",
"role": "user",
"max_devices": 10,
"created_at": "2026-01-31T10:00:00Z"
"role": "user"
}
```
+67
View File
@@ -372,4 +372,71 @@ var Schemas = map[string]Schema{
"DELETE /api/queue/devices/:device_id/clear": {
Response: json.RawMessage(`{"message": ""}`),
},
// --- Beam batch 3: auth core + devices core ---
"POST /api/auth/login": {
Request: handlers.LoginRequest{},
Response: handlers.AuthResponse{},
},
"POST /api/auth/register": {
Request: handlers.RegisterRequest{},
Response: handlers.AuthResponse{},
},
"POST /api/auth/refresh": {
Request: handlers.RefreshTokenRequest{},
Response: handlers.RefreshTokenResponse{},
},
"POST /api/auth/logout": {
Response: json.RawMessage(`{"message": ""}`),
},
"GET /api/auth/profile": {
Response: handlers.UserProfile{},
},
"GET /api/devices": {
Response: handlers.DeviceListResponse{},
},
"PUT /api/devices/:id": {
Request: handlers.DeviceUpdateRequest{},
Response: json.RawMessage(`{
"device_updated": false,
"device": {
"id": "", "device_name": "", "device_type": "",
"last_sync": null, "last_seen": null,
"sync_enabled": false, "auto_sync": false,
"sync_frequency_minutes": 0, "created_at": "",
"device_metadata": null, "auth_token": ""
}
}`),
},
"DELETE /api/devices/:id": {},
"GET /api/devices/:id/shelves": {
Response: json.RawMessage(`{
"status": "", "device_id": "", "shelf_name": "",
"books": [{}], "total": 0
}`),
},
"POST /api/devices/:id/shelves": {
Request: handlers.AddToShelfRequest{},
Response: json.RawMessage(`{
"status": "", "added_count": 0,
"shelf_name": "", "total_count": 0
}`),
},
"DELETE /api/devices/:id/shelves": {
Response: json.RawMessage(`{"status": "", "message": ""}`),
},
"DELETE /api/devices/:id/shelves/clear": {
Response: json.RawMessage(`{"status": "", "message": ""}`),
},
"GET /api/devices/:id/sidecar": {
Response: handlers.SidecarConfig{},
},
"POST /api/devices/register": {
Request: handlers.DeviceRegistrationRequest{},
Response: handlers.DeviceRegistrationResponse{},
},
"POST /api/devices/register/status": {
Request: handlers.DeviceAuthStatusRequest{},
Response: handlers.DeviceAuthStatusResponse{},
},
}