@nasebanal/cli 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,88 +1,53 @@
1
1
  openapi: 3.0.3
2
2
  info:
3
3
  title: NASEBANAL Recorder API
4
- description: >-
4
+ description: |-
5
5
  Time-series data recorder API built with Cloudflare Workers and D1.
6
6
 
7
-
8
7
  ## Authentication
9
8
 
10
-
11
- Create a Personal Access Token (PAT) in **Profile Settings > API Tokens** (requires Pro plan or above), then include
12
- it in the `Authorization` header.
13
-
9
+ Create a Personal Access Token (PAT) in **Profile Settings > API Tokens** (requires Pro plan or above), then include it in the `Authorization` header.
14
10
 
15
11
  Tokens use the format `nbpat_` followed by 40 random characters.
16
12
 
17
-
18
13
  **Usage:**
19
-
20
14
  ```
21
-
22
15
  Authorization: Bearer nbpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
23
-
24
16
  ```
25
17
 
26
-
27
18
  **curl example:**
28
-
29
19
  ```bash
30
-
31
20
  curl -H "Authorization: Bearer nbpat_xxxxx..." https://api.example.com/api/v1/records
32
-
33
21
  ```
34
22
 
35
-
36
23
  **Plan limits:**
37
-
38
24
  - Free: API tokens not available
39
-
40
25
  - Pro: up to 5 tokens
41
-
42
26
  - Family: up to 20 tokens
43
-
44
27
  - Enterprise: unlimited tokens
45
28
 
46
-
47
29
  ## SNS Shared Graph Embed
48
30
 
31
+ Publicly shared graphs can be embedded in external sites (blogs, portfolios, etc.) via iframe without authentication.
49
32
 
50
- Publicly shared graphs can be embedded in external sites (blogs, portfolios, etc.) via iframe without
51
- authentication.
52
-
53
-
54
- **Endpoint:** `GET /shared/{lang}/{id}/embed`
55
-
33
+ **Endpoint:** `GET https://growth.nasebanal.com/{lang}/recorder/shared/{id}/embed` (served by NASEBANAL Growth, not by this API)
56
34
 
57
35
  | Parameter | Type | Required | Description |
58
-
59
36
  |-----------|------|----------|-------------|
60
-
61
37
  | `lang` | path | Yes | Language: `ja` or `en` |
62
-
63
38
  | `id` | path | Yes | Shared graph ID |
64
39
 
65
-
66
- **Features:** Line chart with auto-scale, goal lines, statistics. No navigation header or footer — designed for
67
- iframe embedding.
68
-
40
+ **Features:** Line chart with auto-scale, goal lines, statistics. No navigation header or footer — designed for iframe embedding.
69
41
 
70
42
  **Auth:** Not required (public access).
71
43
 
72
-
73
44
  **iframe example:**
74
-
75
45
  ```html
76
-
77
- <iframe src=\"https://recorder.nasebanal.com/shared/ja/{id}/embed\" width=\"100%\" height=\"500\"
78
- frameborder=\"0\"></iframe>
79
-
46
+ <iframe src=\"https://growth.nasebanal.com/ja/recorder/shared/{id}/embed\" width=\"100%\" height=\"500\" frameborder=\"0\"></iframe>
80
47
  ```
81
48
 
82
-
83
- **Note:** The embed displays a snapshot of the data at the time the SNS share was created. To update the data,
84
- recreate the SNS share.
85
- version: 1.5.0
49
+ **Note:** The embed displays a snapshot of the data at the time the SNS share was created. To update the data, recreate the SNS share.
50
+ version: 1.7.0
86
51
  license:
87
52
  name: MIT
88
53
  servers:
@@ -100,11 +65,9 @@ paths:
100
65
  operationId: getHealth
101
66
  tags:
102
67
  - Health
103
- # Public — the health check is unauthenticated in the implementation.
104
- # Override the global BearerAuth requirement so it stays open.
105
68
  security: []
106
69
  responses:
107
- '200':
70
+ "200":
108
71
  description: API is healthy
109
72
  content:
110
73
  application/json:
@@ -133,7 +96,7 @@ paths:
133
96
  tags:
134
97
  - User
135
98
  responses:
136
- '200':
99
+ "200":
137
100
  description: Current user profile
138
101
  content:
139
102
  application/json:
@@ -146,11 +109,11 @@ paths:
146
109
  - true
147
110
  example: true
148
111
  data:
149
- $ref: '#/components/schemas/User'
150
- '401':
151
- $ref: '#/components/responses/Unauthorized'
152
- '500':
153
- $ref: '#/components/responses/InternalError'
112
+ $ref: "#/components/schemas/User"
113
+ "401":
114
+ $ref: "#/components/responses/Unauthorized"
115
+ "500":
116
+ $ref: "#/components/responses/InternalError"
154
117
  /records:
155
118
  get:
156
119
  summary: Get all records
@@ -161,9 +124,19 @@ paths:
161
124
  parameters:
162
125
  - name: name
163
126
  in: query
164
- description: Filter by record name
127
+ description: "Filter by record name. A name is unique only per owner: a tag shared with you can have the same name as one of your own, and this then matches both. Use `tag_id` to pick exactly one tag."
165
128
  schema:
166
129
  type: string
130
+ - name: tag_id
131
+ in: query
132
+ description: Filter by tag id; repeat the parameter for several tags. An id names exactly one tag, so unlike `name` it cannot mix in a same-named tag of another owner. When `tag_id` is given, `name` is ignored. An id of a tag you cannot read (neither yours nor shared with you) matches nothing.
133
+ style: form
134
+ explode: true
135
+ schema:
136
+ type: array
137
+ items:
138
+ type: integer
139
+ minimum: 1
167
140
  - name: start_date
168
141
  in: query
169
142
  description: Filter by start date (ISO 8601)
@@ -178,7 +151,7 @@ paths:
178
151
  format: date-time
179
152
  - name: limit
180
153
  in: query
181
- description: 'Maximum number of records to return for unbounded queries (max: 1000). When start_date or end_date is supplied, all records in the range are returned — the date filter itself bounds the payload.'
154
+ description: "Maximum number of records to return for unbounded queries (max: 1000). When start_date or end_date is supplied, all records in the range are returned — the date filter itself bounds the payload."
182
155
  schema:
183
156
  type: integer
184
157
  default: 100
@@ -190,14 +163,14 @@ paths:
190
163
  type: integer
191
164
  default: 0
192
165
  responses:
193
- '200':
166
+ "200":
194
167
  description: List of records
195
168
  content:
196
169
  application/json:
197
170
  schema:
198
- $ref: '#/components/schemas/RecordListResponse'
199
- '500':
200
- $ref: '#/components/responses/InternalError'
171
+ $ref: "#/components/schemas/RecordListResponse"
172
+ "500":
173
+ $ref: "#/components/responses/InternalError"
201
174
  post:
202
175
  summary: Create a new record
203
176
  description: Create a new time-series record
@@ -209,18 +182,18 @@ paths:
209
182
  content:
210
183
  application/json:
211
184
  schema:
212
- $ref: '#/components/schemas/CreateRecordInput'
185
+ $ref: "#/components/schemas/CreateRecordInput"
213
186
  responses:
214
- '201':
187
+ "201":
215
188
  description: Record created successfully
216
189
  content:
217
190
  application/json:
218
191
  schema:
219
- $ref: '#/components/schemas/RecordResponse'
220
- '400':
221
- $ref: '#/components/responses/ValidationError'
222
- '500':
223
- $ref: '#/components/responses/InternalError'
192
+ $ref: "#/components/schemas/RecordResponse"
193
+ "400":
194
+ $ref: "#/components/responses/ValidationError"
195
+ "500":
196
+ $ref: "#/components/responses/InternalError"
224
197
  /records/{id}:
225
198
  get:
226
199
  summary: Get a single record
@@ -229,18 +202,18 @@ paths:
229
202
  tags:
230
203
  - Records
231
204
  parameters:
232
- - $ref: '#/components/parameters/RecordId'
205
+ - $ref: "#/components/parameters/RecordId"
233
206
  responses:
234
- '200':
207
+ "200":
235
208
  description: Record details
236
209
  content:
237
210
  application/json:
238
211
  schema:
239
- $ref: '#/components/schemas/RecordResponse'
240
- '404':
241
- $ref: '#/components/responses/NotFound'
242
- '500':
243
- $ref: '#/components/responses/InternalError'
212
+ $ref: "#/components/schemas/RecordResponse"
213
+ "404":
214
+ $ref: "#/components/responses/NotFound"
215
+ "500":
216
+ $ref: "#/components/responses/InternalError"
244
217
  put:
245
218
  summary: Update a record
246
219
  description: Update an existing record by ID
@@ -248,26 +221,26 @@ paths:
248
221
  tags:
249
222
  - Records
250
223
  parameters:
251
- - $ref: '#/components/parameters/RecordId'
224
+ - $ref: "#/components/parameters/RecordId"
252
225
  requestBody:
253
226
  required: true
254
227
  content:
255
228
  application/json:
256
229
  schema:
257
- $ref: '#/components/schemas/UpdateRecordInput'
230
+ $ref: "#/components/schemas/UpdateRecordInput"
258
231
  responses:
259
- '200':
232
+ "200":
260
233
  description: Record updated successfully
261
234
  content:
262
235
  application/json:
263
236
  schema:
264
- $ref: '#/components/schemas/RecordResponse'
265
- '400':
266
- $ref: '#/components/responses/ValidationError'
267
- '404':
268
- $ref: '#/components/responses/NotFound'
269
- '500':
270
- $ref: '#/components/responses/InternalError'
237
+ $ref: "#/components/schemas/RecordResponse"
238
+ "400":
239
+ $ref: "#/components/responses/ValidationError"
240
+ "404":
241
+ $ref: "#/components/responses/NotFound"
242
+ "500":
243
+ $ref: "#/components/responses/InternalError"
271
244
  delete:
272
245
  summary: Delete a record
273
246
  description: Delete a record by ID
@@ -275,9 +248,9 @@ paths:
275
248
  tags:
276
249
  - Records
277
250
  parameters:
278
- - $ref: '#/components/parameters/RecordId'
251
+ - $ref: "#/components/parameters/RecordId"
279
252
  responses:
280
- '200':
253
+ "200":
281
254
  description: Record deleted successfully
282
255
  content:
283
256
  application/json:
@@ -297,10 +270,10 @@ paths:
297
270
  enum:
298
271
  - true
299
272
  example: true
300
- '404':
301
- $ref: '#/components/responses/NotFound'
302
- '500':
303
- $ref: '#/components/responses/InternalError'
273
+ "404":
274
+ $ref: "#/components/responses/NotFound"
275
+ "500":
276
+ $ref: "#/components/responses/InternalError"
304
277
  /records/{id}/visibility:
305
278
  patch:
306
279
  summary: Toggle record visibility
@@ -309,7 +282,7 @@ paths:
309
282
  tags:
310
283
  - Records
311
284
  parameters:
312
- - $ref: '#/components/parameters/RecordId'
285
+ - $ref: "#/components/parameters/RecordId"
313
286
  requestBody:
314
287
  required: true
315
288
  content:
@@ -322,7 +295,7 @@ paths:
322
295
  required:
323
296
  - hide
324
297
  responses:
325
- '200':
298
+ "200":
326
299
  description: Visibility updated
327
300
  content:
328
301
  application/json:
@@ -342,10 +315,10 @@ paths:
342
315
  is_hidden:
343
316
  type: integer
344
317
  description: 0 = visible, 1 = hidden
345
- '404':
346
- $ref: '#/components/responses/NotFound'
347
- '500':
348
- $ref: '#/components/responses/InternalError'
318
+ "404":
319
+ $ref: "#/components/responses/NotFound"
320
+ "500":
321
+ $ref: "#/components/responses/InternalError"
349
322
  /records/bulk-delete:
350
323
  post:
351
324
  summary: Bulk delete records
@@ -372,7 +345,7 @@ paths:
372
345
  required:
373
346
  - ids
374
347
  responses:
375
- '200':
348
+ "200":
376
349
  description: Records deleted successfully
377
350
  content:
378
351
  application/json:
@@ -390,10 +363,10 @@ paths:
390
363
  deleted:
391
364
  type: integer
392
365
  description: Number of records deleted
393
- '400':
394
- $ref: '#/components/responses/ValidationError'
395
- '500':
396
- $ref: '#/components/responses/InternalError'
366
+ "400":
367
+ $ref: "#/components/responses/ValidationError"
368
+ "500":
369
+ $ref: "#/components/responses/InternalError"
397
370
  /tags:
398
371
  get:
399
372
  summary: Get all tags
@@ -402,7 +375,7 @@ paths:
402
375
  tags:
403
376
  - Tags
404
377
  responses:
405
- '200':
378
+ "200":
406
379
  description: List of tags
407
380
  content:
408
381
  application/json:
@@ -420,11 +393,11 @@ paths:
420
393
  data:
421
394
  type: array
422
395
  items:
423
- $ref: '#/components/schemas/Tag'
396
+ $ref: "#/components/schemas/Tag"
424
397
  total:
425
398
  type: integer
426
- '500':
427
- $ref: '#/components/responses/InternalError'
399
+ "500":
400
+ $ref: "#/components/responses/InternalError"
428
401
  /tags/{id}:
429
402
  put:
430
403
  summary: Update a tag
@@ -451,10 +424,7 @@ paths:
451
424
  unit:
452
425
  type: string
453
426
  nullable: true
454
- description: >-
455
- Unit of measurement. For a `time`-typed tag, must be
456
- `hour`, `minute`, or `second`, controlling whether the
457
- value formats as H:MM:SS, MM:SS, or decimal seconds.
427
+ description: Unit of measurement. For a `time`-typed tag, must be `hour`, `minute`, or `second`, controlling whether the value formats as H:MM:SS, MM:SS, or decimal seconds.
458
428
  chart_type:
459
429
  type: string
460
430
  enum:
@@ -463,24 +433,14 @@ paths:
463
433
  - step
464
434
  data_type:
465
435
  type: string
466
- description: >-
467
- The scale every stored value is written against (a `time` tag's 90 means 90
468
- minutes). Changing it does NOT convert existing values, so it is
469
- accepted only when no stored value would change meaning: widening
470
- (`integer` → `decimal`) is always allowed, narrowing only while every
471
- stored value is representable in the new type, and moving into or out of
472
- `time` never is once the tag holds data. Otherwise the request is refused
473
- with 409 `TAG_HAS_DATA`, whose `error.details.reason` carries the same
474
- code as `Tag.data_type_options[].reason`. Read `data_type_options` off
475
- `GET /tags` rather than re-deriving this. Sending the tag's current value
476
- is not a change and is always accepted.
436
+ description: "The scale every stored value is written against (a `time` tag's 90 means 90 minutes). Changing it does NOT convert existing values, so it is accepted only when no stored value would change meaning: widening (`integer` → `decimal`) is always allowed, narrowing only while every stored value is representable in the new type, and moving into or out of `time` never is once the tag holds data. Otherwise the request is refused with 409 `TAG_HAS_DATA`, whose `error.details.reason` carries the same code as `Tag.data_type_options[].reason`. Read `data_type_options` off `GET /tags` rather than re-deriving this. Sending the tag's current value is not a change and is always accepted."
477
437
  enum:
478
438
  - binary
479
439
  - decimal
480
440
  - integer
481
441
  - time
482
442
  responses:
483
- '200':
443
+ "200":
484
444
  description: Tag updated successfully
485
445
  content:
486
446
  application/json:
@@ -493,27 +453,22 @@ paths:
493
453
  - true
494
454
  example: true
495
455
  data:
496
- $ref: '#/components/schemas/Tag'
497
- '404':
498
- $ref: '#/components/responses/NotFound'
499
- '409':
500
- description: >-
501
- Refused: the requested `data_type` would change the meaning of values
502
- already stored. `error.details.reason` is one of `SCALE_CHANGE`,
503
- `NON_INTEGER_VALUES`, `NON_BINARY_VALUES`.
456
+ $ref: "#/components/schemas/Tag"
457
+ "404":
458
+ $ref: "#/components/responses/NotFound"
459
+ "409":
460
+ description: "Refused: the requested `data_type` would change the meaning of values already stored. `error.details.reason` is one of `SCALE_CHANGE`, `NON_INTEGER_VALUES`, `NON_BINARY_VALUES`."
504
461
  content:
505
462
  application/json:
506
463
  schema:
507
- $ref: '#/components/schemas/ErrorResponse'
464
+ $ref: "#/components/schemas/ErrorResponse"
508
465
  example:
509
466
  success: false
510
467
  error:
511
468
  code: TAG_HAS_DATA
512
- message: >-
513
- Cannot change data_type: this tag already has records or
514
- goals written against its current scale.
515
- '500':
516
- $ref: '#/components/responses/InternalError'
469
+ message: "Cannot change data_type: this tag already has records or goals written against its current scale."
470
+ "500":
471
+ $ref: "#/components/responses/InternalError"
517
472
  delete:
518
473
  summary: Delete a tag
519
474
  description: Delete a tag and all associated records by ID
@@ -528,7 +483,7 @@ paths:
528
483
  type: integer
529
484
  minimum: 1
530
485
  responses:
531
- '200':
486
+ "200":
532
487
  description: Tag deleted successfully
533
488
  content:
534
489
  application/json:
@@ -548,10 +503,10 @@ paths:
548
503
  enum:
549
504
  - true
550
505
  example: true
551
- '404':
552
- $ref: '#/components/responses/NotFound'
553
- '500':
554
- $ref: '#/components/responses/InternalError'
506
+ "404":
507
+ $ref: "#/components/responses/NotFound"
508
+ "500":
509
+ $ref: "#/components/responses/InternalError"
555
510
  /tags/{id}/visibility:
556
511
  patch:
557
512
  summary: Toggle tag visibility
@@ -578,7 +533,7 @@ paths:
578
533
  required:
579
534
  - hide
580
535
  responses:
581
- '200':
536
+ "200":
582
537
  description: Visibility updated
583
538
  content:
584
539
  application/json:
@@ -591,11 +546,11 @@ paths:
591
546
  - true
592
547
  example: true
593
548
  data:
594
- $ref: '#/components/schemas/Tag'
595
- '404':
596
- $ref: '#/components/responses/NotFound'
597
- '500':
598
- $ref: '#/components/responses/InternalError'
549
+ $ref: "#/components/schemas/Tag"
550
+ "404":
551
+ $ref: "#/components/responses/NotFound"
552
+ "500":
553
+ $ref: "#/components/responses/InternalError"
599
554
  /tags/bulk-delete:
600
555
  post:
601
556
  summary: Bulk delete tags
@@ -622,7 +577,7 @@ paths:
622
577
  required:
623
578
  - ids
624
579
  responses:
625
- '200':
580
+ "200":
626
581
  description: Tags deleted successfully
627
582
  content:
628
583
  application/json:
@@ -640,10 +595,10 @@ paths:
640
595
  deleted:
641
596
  type: integer
642
597
  description: Number of tags deleted
643
- '400':
644
- $ref: '#/components/responses/ValidationError'
645
- '500':
646
- $ref: '#/components/responses/InternalError'
598
+ "400":
599
+ $ref: "#/components/responses/ValidationError"
600
+ "500":
601
+ $ref: "#/components/responses/InternalError"
647
602
  /shares:
648
603
  get:
649
604
  summary: Get my shares
@@ -652,7 +607,7 @@ paths:
652
607
  tags:
653
608
  - Shares
654
609
  responses:
655
- '200':
610
+ "200":
656
611
  description: List of shares
657
612
  content:
658
613
  application/json:
@@ -670,18 +625,14 @@ paths:
670
625
  data:
671
626
  type: array
672
627
  items:
673
- $ref: '#/components/schemas/Share'
628
+ $ref: "#/components/schemas/Share"
674
629
  total:
675
630
  type: integer
676
- '500':
677
- $ref: '#/components/responses/InternalError'
631
+ "500":
632
+ $ref: "#/components/responses/InternalError"
678
633
  post:
679
634
  summary: Create a share (sends approval email)
680
- description: >-
681
- Create a tag share addressed to another user by email. The share is created in status `pending` and does NOT
682
- grant data access until the recipient clicks the approval link in the invitation email delivered via the Account
683
- API (Resend). Invitations expire after 7 days; after expiry the recipient must be re-invited. The raw invitation
684
- token is never stored — only a SHA-256 hash — so even database access does not expose reusable accept links.
635
+ description: Create a tag share addressed to another user by email. The share is created in status `pending` and does NOT grant data access until the recipient clicks the approval link in the invitation email delivered via the Account API (Resend). Invitations expire after 7 days; after expiry the recipient must be re-invited. The raw invitation token is never stored — only a SHA-256 hash — so even database access does not expose reusable accept links.
685
636
  operationId: createShare
686
637
  tags:
687
638
  - Shares
@@ -707,7 +658,7 @@ paths:
707
658
  - shared_with_email
708
659
  - permission
709
660
  responses:
710
- '201':
661
+ "201":
711
662
  description: Share created successfully
712
663
  content:
713
664
  application/json:
@@ -720,18 +671,15 @@ paths:
720
671
  - true
721
672
  example: true
722
673
  data:
723
- $ref: '#/components/schemas/Share'
724
- '400':
725
- $ref: '#/components/responses/ValidationError'
726
- '500':
727
- $ref: '#/components/responses/InternalError'
674
+ $ref: "#/components/schemas/Share"
675
+ "400":
676
+ $ref: "#/components/responses/ValidationError"
677
+ "500":
678
+ $ref: "#/components/responses/InternalError"
728
679
  /shares/with-me:
729
680
  get:
730
681
  summary: Get shares shared with me
731
- description: >-
732
- Retrieve shares that other users have shared with the authenticated user. Returns only `accepted` shares by
733
- default. Pass `?status=pending` to list invitations awaiting approval, `?status=rejected` for declined
734
- invitations, or `?status=all` for every row.
682
+ description: Retrieve shares that other users have shared with the authenticated user. Returns only `accepted` shares by default. Pass `?status=pending` to list invitations awaiting approval, `?status=rejected` for declined invitations, or `?status=all` for every row.
735
683
  operationId: getSharedWithMe
736
684
  tags:
737
685
  - Shares
@@ -749,7 +697,7 @@ paths:
749
697
  default: accepted
750
698
  description: Filter by approval status.
751
699
  responses:
752
- '200':
700
+ "200":
753
701
  description: List of shares shared with the user
754
702
  content:
755
703
  application/json:
@@ -767,11 +715,11 @@ paths:
767
715
  data:
768
716
  type: array
769
717
  items:
770
- $ref: '#/components/schemas/Share'
718
+ $ref: "#/components/schemas/Share"
771
719
  total:
772
720
  type: integer
773
- '500':
774
- $ref: '#/components/responses/InternalError'
721
+ "500":
722
+ $ref: "#/components/responses/InternalError"
775
723
  /shares/{id}:
776
724
  put:
777
725
  summary: Update a share
@@ -804,7 +752,7 @@ paths:
804
752
  - tag_id
805
753
  - permission
806
754
  responses:
807
- '200':
755
+ "200":
808
756
  description: Share updated
809
757
  content:
810
758
  application/json:
@@ -817,11 +765,11 @@ paths:
817
765
  - true
818
766
  example: true
819
767
  data:
820
- $ref: '#/components/schemas/Share'
821
- '404':
822
- $ref: '#/components/responses/NotFound'
823
- '500':
824
- $ref: '#/components/responses/InternalError'
768
+ $ref: "#/components/schemas/Share"
769
+ "404":
770
+ $ref: "#/components/responses/NotFound"
771
+ "500":
772
+ $ref: "#/components/responses/InternalError"
825
773
  delete:
826
774
  summary: Delete a share
827
775
  description: Delete a share by ID
@@ -836,7 +784,7 @@ paths:
836
784
  type: integer
837
785
  minimum: 1
838
786
  responses:
839
- '200':
787
+ "200":
840
788
  description: Share deleted
841
789
  content:
842
790
  application/json:
@@ -856,10 +804,10 @@ paths:
856
804
  enum:
857
805
  - true
858
806
  example: true
859
- '404':
860
- $ref: '#/components/responses/NotFound'
861
- '500':
862
- $ref: '#/components/responses/InternalError'
807
+ "404":
808
+ $ref: "#/components/responses/NotFound"
809
+ "500":
810
+ $ref: "#/components/responses/InternalError"
863
811
  /shares/{id}/visibility:
864
812
  patch:
865
813
  summary: Toggle share visibility
@@ -886,7 +834,7 @@ paths:
886
834
  required:
887
835
  - is_hidden
888
836
  responses:
889
- '200':
837
+ "200":
890
838
  description: Visibility updated
891
839
  content:
892
840
  application/json:
@@ -899,11 +847,11 @@ paths:
899
847
  - true
900
848
  example: true
901
849
  data:
902
- $ref: '#/components/schemas/Share'
903
- '404':
904
- $ref: '#/components/responses/NotFound'
905
- '500':
906
- $ref: '#/components/responses/InternalError'
850
+ $ref: "#/components/schemas/Share"
851
+ "404":
852
+ $ref: "#/components/responses/NotFound"
853
+ "500":
854
+ $ref: "#/components/responses/InternalError"
907
855
  /shares/{id}/pause:
908
856
  patch:
909
857
  summary: Toggle share paused status
@@ -930,7 +878,7 @@ paths:
930
878
  required:
931
879
  - is_paused
932
880
  responses:
933
- '200':
881
+ "200":
934
882
  description: Paused status updated
935
883
  content:
936
884
  application/json:
@@ -943,11 +891,11 @@ paths:
943
891
  - true
944
892
  example: true
945
893
  data:
946
- $ref: '#/components/schemas/Share'
947
- '404':
948
- $ref: '#/components/responses/NotFound'
949
- '500':
950
- $ref: '#/components/responses/InternalError'
894
+ $ref: "#/components/schemas/Share"
895
+ "404":
896
+ $ref: "#/components/responses/NotFound"
897
+ "500":
898
+ $ref: "#/components/responses/InternalError"
951
899
  /shares/{id}/transfer:
952
900
  post:
953
901
  summary: Transfer tag ownership
@@ -963,7 +911,7 @@ paths:
963
911
  type: integer
964
912
  minimum: 1
965
913
  responses:
966
- '200':
914
+ "200":
967
915
  description: Ownership transferred successfully
968
916
  content:
969
917
  application/json:
@@ -983,10 +931,10 @@ paths:
983
931
  enum:
984
932
  - true
985
933
  example: true
986
- '404':
987
- $ref: '#/components/responses/NotFound'
988
- '500':
989
- $ref: '#/components/responses/InternalError'
934
+ "404":
935
+ $ref: "#/components/responses/NotFound"
936
+ "500":
937
+ $ref: "#/components/responses/InternalError"
990
938
  /shares/bulk-delete:
991
939
  post:
992
940
  summary: Bulk delete shares
@@ -1013,7 +961,7 @@ paths:
1013
961
  required:
1014
962
  - ids
1015
963
  responses:
1016
- '200':
964
+ "200":
1017
965
  description: Shares deleted successfully
1018
966
  content:
1019
967
  application/json:
@@ -1030,17 +978,14 @@ paths:
1030
978
  properties:
1031
979
  deleted:
1032
980
  type: integer
1033
- '400':
1034
- $ref: '#/components/responses/ValidationError'
1035
- '500':
1036
- $ref: '#/components/responses/InternalError'
981
+ "400":
982
+ $ref: "#/components/responses/ValidationError"
983
+ "500":
984
+ $ref: "#/components/responses/InternalError"
1037
985
  /shares/accept:
1038
986
  post:
1039
987
  summary: Accept a pending share invitation
1040
- description: >-
1041
- Called from the /share/accept frontend page after the recipient clicks the link in their invitation email.
1042
- Requires the authenticated user to be the invited recipient (`shared_with_user_id`). On success the share
1043
- transitions from `pending` to `accepted` and data access is enabled.
988
+ description: Called from the /share/accept frontend page after the recipient clicks the link in their invitation email. Requires the authenticated user to be the invited recipient (`shared_with_user_id`). On success the share transitions from `pending` to `accepted` and data access is enabled.
1044
989
  operationId: acceptShare
1045
990
  tags:
1046
991
  - Shares
@@ -1057,7 +1002,7 @@ paths:
1057
1002
  required:
1058
1003
  - token
1059
1004
  responses:
1060
- '200':
1005
+ "200":
1061
1006
  description: Share accepted
1062
1007
  content:
1063
1008
  application/json:
@@ -1080,18 +1025,14 @@ paths:
1080
1025
  already:
1081
1026
  type: boolean
1082
1027
  description: True if the share was already accepted.
1083
- '400':
1084
- $ref: '#/components/responses/ValidationError'
1085
- '404':
1086
- $ref: '#/components/responses/NotFound'
1028
+ "400":
1029
+ $ref: "#/components/responses/ValidationError"
1030
+ "404":
1031
+ $ref: "#/components/responses/NotFound"
1087
1032
  /shares/reject:
1088
1033
  post:
1089
1034
  summary: Decline a pending share invitation via emailed token
1090
- description: >-
1091
- Called from the /share/reject frontend page after the recipient clicks the decline link in their invitation
1092
- email. Same token and auth model as /shares/accept (requires the authenticated user to be the invited
1093
- recipient); on success the share transitions from `pending` to `rejected`. Cannot be used on already-accepted
1094
- shares (delete instead, via DELETE /shares/{id}).
1035
+ description: Called from the /share/reject frontend page after the recipient clicks the decline link in their invitation email. Same token and auth model as /shares/accept (requires the authenticated user to be the invited recipient); on success the share transitions from `pending` to `rejected`. Cannot be used on already-accepted shares (delete instead, via DELETE /shares/{id}).
1095
1036
  operationId: rejectShareByToken
1096
1037
  tags:
1097
1038
  - Shares
@@ -1108,7 +1049,7 @@ paths:
1108
1049
  required:
1109
1050
  - token
1110
1051
  responses:
1111
- '200':
1052
+ "200":
1112
1053
  description: Share rejected
1113
1054
  content:
1114
1055
  application/json:
@@ -1131,16 +1072,14 @@ paths:
1131
1072
  already:
1132
1073
  type: boolean
1133
1074
  description: True if the share was already rejected.
1134
- '400':
1135
- $ref: '#/components/responses/ValidationError'
1136
- '404':
1137
- $ref: '#/components/responses/NotFound'
1075
+ "400":
1076
+ $ref: "#/components/responses/ValidationError"
1077
+ "404":
1078
+ $ref: "#/components/responses/NotFound"
1138
1079
  /shares/{id}/reject:
1139
1080
  post:
1140
1081
  summary: Reject a pending share invitation
1141
- description: >-
1142
- Called from the recipient's SharingManager UI. Marks the share as `rejected`; cannot be used on already-accepted
1143
- shares (delete instead).
1082
+ description: Called from the recipient's SharingManager UI. Marks the share as `rejected`; cannot be used on already-accepted shares (delete instead).
1144
1083
  operationId: rejectShare
1145
1084
  tags:
1146
1085
  - Shares
@@ -1151,7 +1090,7 @@ paths:
1151
1090
  schema:
1152
1091
  type: integer
1153
1092
  responses:
1154
- '200':
1093
+ "200":
1155
1094
  description: Share rejected
1156
1095
  content:
1157
1096
  application/json:
@@ -1171,10 +1110,10 @@ paths:
1171
1110
  type: string
1172
1111
  enum:
1173
1112
  - rejected
1174
- '400':
1175
- $ref: '#/components/responses/ValidationError'
1176
- '404':
1177
- $ref: '#/components/responses/NotFound'
1113
+ "400":
1114
+ $ref: "#/components/responses/ValidationError"
1115
+ "404":
1116
+ $ref: "#/components/responses/NotFound"
1178
1117
  /shared-graphs:
1179
1118
  get:
1180
1119
  summary: Get my shared graphs
@@ -1183,7 +1122,7 @@ paths:
1183
1122
  tags:
1184
1123
  - Shared Graphs
1185
1124
  responses:
1186
- '200':
1125
+ "200":
1187
1126
  description: List of shared graphs
1188
1127
  content:
1189
1128
  application/json:
@@ -1198,9 +1137,9 @@ paths:
1198
1137
  data:
1199
1138
  type: array
1200
1139
  items:
1201
- $ref: '#/components/schemas/SharedGraph'
1202
- '500':
1203
- $ref: '#/components/responses/InternalError'
1140
+ $ref: "#/components/schemas/SharedGraph"
1141
+ "500":
1142
+ $ref: "#/components/responses/InternalError"
1204
1143
  post:
1205
1144
  summary: Create a shared graph
1206
1145
  description: Create a publicly shareable graph snapshot for SNS sharing
@@ -1212,9 +1151,9 @@ paths:
1212
1151
  content:
1213
1152
  application/json:
1214
1153
  schema:
1215
- $ref: '#/components/schemas/CreateSharedGraphInput'
1154
+ $ref: "#/components/schemas/CreateSharedGraphInput"
1216
1155
  responses:
1217
- '201':
1156
+ "201":
1218
1157
  description: Shared graph created
1219
1158
  content:
1220
1159
  application/json:
@@ -1227,11 +1166,11 @@ paths:
1227
1166
  - true
1228
1167
  example: true
1229
1168
  data:
1230
- $ref: '#/components/schemas/SharedGraph'
1231
- '400':
1232
- $ref: '#/components/responses/ValidationError'
1233
- '500':
1234
- $ref: '#/components/responses/InternalError'
1169
+ $ref: "#/components/schemas/SharedGraph"
1170
+ "400":
1171
+ $ref: "#/components/responses/ValidationError"
1172
+ "500":
1173
+ $ref: "#/components/responses/InternalError"
1235
1174
  /shared-graphs/{id}:
1236
1175
  put:
1237
1176
  summary: Update a shared graph
@@ -1257,7 +1196,7 @@ paths:
1257
1196
  description:
1258
1197
  type: string
1259
1198
  responses:
1260
- '200':
1199
+ "200":
1261
1200
  description: Shared graph updated
1262
1201
  content:
1263
1202
  application/json:
@@ -1270,11 +1209,11 @@ paths:
1270
1209
  - true
1271
1210
  example: true
1272
1211
  data:
1273
- $ref: '#/components/schemas/SharedGraph'
1274
- '404':
1275
- $ref: '#/components/responses/NotFound'
1276
- '500':
1277
- $ref: '#/components/responses/InternalError'
1212
+ $ref: "#/components/schemas/SharedGraph"
1213
+ "404":
1214
+ $ref: "#/components/responses/NotFound"
1215
+ "500":
1216
+ $ref: "#/components/responses/InternalError"
1278
1217
  delete:
1279
1218
  summary: Delete a shared graph
1280
1219
  description: Delete a shared graph by ID
@@ -1288,7 +1227,7 @@ paths:
1288
1227
  schema:
1289
1228
  type: string
1290
1229
  responses:
1291
- '200':
1230
+ "200":
1292
1231
  description: Shared graph deleted
1293
1232
  content:
1294
1233
  application/json:
@@ -1308,10 +1247,10 @@ paths:
1308
1247
  enum:
1309
1248
  - true
1310
1249
  example: true
1311
- '404':
1312
- $ref: '#/components/responses/NotFound'
1313
- '500':
1314
- $ref: '#/components/responses/InternalError'
1250
+ "404":
1251
+ $ref: "#/components/responses/NotFound"
1252
+ "500":
1253
+ $ref: "#/components/responses/InternalError"
1315
1254
  /shared-graphs/{id}/toggle:
1316
1255
  patch:
1317
1256
  summary: Toggle shared graph active status
@@ -1326,7 +1265,7 @@ paths:
1326
1265
  schema:
1327
1266
  type: string
1328
1267
  responses:
1329
- '200':
1268
+ "200":
1330
1269
  description: Status toggled
1331
1270
  content:
1332
1271
  application/json:
@@ -1344,10 +1283,10 @@ paths:
1344
1283
  is_active:
1345
1284
  type: integer
1346
1285
  description: 1 for active, 0 for inactive
1347
- '404':
1348
- $ref: '#/components/responses/NotFound'
1349
- '500':
1350
- $ref: '#/components/responses/InternalError'
1286
+ "404":
1287
+ $ref: "#/components/responses/NotFound"
1288
+ "500":
1289
+ $ref: "#/components/responses/InternalError"
1351
1290
  /shared-graphs/bulk-delete:
1352
1291
  post:
1353
1292
  summary: Bulk delete shared graphs
@@ -1373,7 +1312,7 @@ paths:
1373
1312
  required:
1374
1313
  - ids
1375
1314
  responses:
1376
- '200':
1315
+ "200":
1377
1316
  description: Shared graphs deleted
1378
1317
  content:
1379
1318
  application/json:
@@ -1390,10 +1329,10 @@ paths:
1390
1329
  properties:
1391
1330
  deleted:
1392
1331
  type: integer
1393
- '400':
1394
- $ref: '#/components/responses/ValidationError'
1395
- '500':
1396
- $ref: '#/components/responses/InternalError'
1332
+ "400":
1333
+ $ref: "#/components/responses/ValidationError"
1334
+ "500":
1335
+ $ref: "#/components/responses/InternalError"
1397
1336
  /public/shared-graphs/{id}:
1398
1337
  get:
1399
1338
  summary: Get public shared graph
@@ -1409,7 +1348,7 @@ paths:
1409
1348
  schema:
1410
1349
  type: string
1411
1350
  responses:
1412
- '200':
1351
+ "200":
1413
1352
  description: Shared graph public data
1414
1353
  content:
1415
1354
  application/json:
@@ -1422,9 +1361,9 @@ paths:
1422
1361
  - true
1423
1362
  example: true
1424
1363
  data:
1425
- $ref: '#/components/schemas/SharedGraphPublic'
1426
- '404':
1427
- $ref: '#/components/responses/NotFound'
1364
+ $ref: "#/components/schemas/SharedGraphPublic"
1365
+ "404":
1366
+ $ref: "#/components/responses/NotFound"
1428
1367
  /public/shared-graphs/{id}/og-image:
1429
1368
  get:
1430
1369
  summary: Generate OG image for shared graph
@@ -1440,15 +1379,15 @@ paths:
1440
1379
  schema:
1441
1380
  type: string
1442
1381
  responses:
1443
- '200':
1382
+ "200":
1444
1383
  description: OG image
1445
1384
  content:
1446
1385
  image/png:
1447
1386
  schema:
1448
1387
  type: string
1449
1388
  format: binary
1450
- '404':
1451
- $ref: '#/components/responses/NotFound'
1389
+ "404":
1390
+ $ref: "#/components/responses/NotFound"
1452
1391
  /records/export:
1453
1392
  get:
1454
1393
  summary: Export records as CSV
@@ -1475,7 +1414,7 @@ paths:
1475
1414
  type: string
1476
1415
  format: date-time
1477
1416
  responses:
1478
- '200':
1417
+ "200":
1479
1418
  description: CSV file containing records
1480
1419
  content:
1481
1420
  text/csv:
@@ -1485,8 +1424,8 @@ paths:
1485
1424
  example: |-
1486
1425
  Tag,Value,Unit,Recorded At
1487
1426
  "temperature",25.5,"°C","2024-01-01T10:00:00Z"
1488
- '500':
1489
- $ref: '#/components/responses/InternalError'
1427
+ "500":
1428
+ $ref: "#/components/responses/InternalError"
1490
1429
  /records/import:
1491
1430
  post:
1492
1431
  summary: Import records from CSV
@@ -1504,47 +1443,52 @@ paths:
1504
1443
  file:
1505
1444
  type: string
1506
1445
  format: binary
1507
- description: 'CSV file to import (columns: Tag, Value, Unit, Recorded At)'
1446
+ description: "CSV file to import (columns: Tag, Value, Unit, Recorded At)"
1508
1447
  required:
1509
1448
  - file
1510
1449
  responses:
1511
- '200':
1450
+ "200":
1512
1451
  description: Import completed successfully
1513
1452
  content:
1514
1453
  application/json:
1515
1454
  schema:
1516
- $ref: '#/components/schemas/ImportResponse'
1517
- '400':
1518
- $ref: '#/components/responses/ValidationError'
1519
- '500':
1520
- $ref: '#/components/responses/InternalError'
1455
+ $ref: "#/components/schemas/ImportResponse"
1456
+ "400":
1457
+ $ref: "#/components/responses/ValidationError"
1458
+ "500":
1459
+ $ref: "#/components/responses/InternalError"
1521
1460
  /records/stats:
1522
1461
  get:
1523
1462
  summary: Get record statistics
1524
- description: Get aggregated statistics for records with a specific name
1463
+ description: Get aggregated statistics for the records of one tag, picked by `name` or, unambiguously, by `tag_id`. Send one of the two.
1525
1464
  operationId: getRecordStats
1526
1465
  tags:
1527
1466
  - Statistics
1528
1467
  parameters:
1529
1468
  - name: name
1530
1469
  in: query
1531
- required: true
1532
- description: Record name to get statistics for
1470
+ description: Record name to get statistics for. A name is unique only per owner, so a tag shared with you can share it with one of your own; prefer `tag_id`.
1533
1471
  schema:
1534
1472
  type: string
1473
+ - name: tag_id
1474
+ in: query
1475
+ description: Id of the tag to get statistics for. When given, `name` is ignored. An id of a tag you cannot read matches nothing.
1476
+ schema:
1477
+ type: integer
1478
+ minimum: 1
1535
1479
  responses:
1536
- '200':
1480
+ "200":
1537
1481
  description: Statistics for the specified record name
1538
1482
  content:
1539
1483
  application/json:
1540
1484
  schema:
1541
- $ref: '#/components/schemas/StatsResponse'
1542
- '400':
1543
- $ref: '#/components/responses/ValidationError'
1544
- '404':
1545
- $ref: '#/components/responses/NotFound'
1546
- '500':
1547
- $ref: '#/components/responses/InternalError'
1485
+ $ref: "#/components/schemas/StatsResponse"
1486
+ "400":
1487
+ $ref: "#/components/responses/ValidationError"
1488
+ "404":
1489
+ $ref: "#/components/responses/NotFound"
1490
+ "500":
1491
+ $ref: "#/components/responses/InternalError"
1548
1492
  /media/upload:
1549
1493
  post:
1550
1494
  summary: Upload media file
@@ -1566,7 +1510,7 @@ paths:
1566
1510
  required:
1567
1511
  - file
1568
1512
  responses:
1569
- '200':
1513
+ "200":
1570
1514
  description: File uploaded successfully
1571
1515
  content:
1572
1516
  application/json:
@@ -1603,10 +1547,10 @@ paths:
1603
1547
  contentType:
1604
1548
  type: string
1605
1549
  description: MIME content type
1606
- '400':
1607
- $ref: '#/components/responses/ValidationError'
1608
- '500':
1609
- $ref: '#/components/responses/InternalError'
1550
+ "400":
1551
+ $ref: "#/components/responses/ValidationError"
1552
+ "500":
1553
+ $ref: "#/components/responses/InternalError"
1610
1554
  /media/upload-url:
1611
1555
  post:
1612
1556
  summary: Get upload URL info
@@ -1635,7 +1579,7 @@ paths:
1635
1579
  - contentType
1636
1580
  - size
1637
1581
  responses:
1638
- '200':
1582
+ "200":
1639
1583
  description: Upload URL info
1640
1584
  content:
1641
1585
  application/json:
@@ -1660,10 +1604,10 @@ paths:
1660
1604
  type: string
1661
1605
  example: direct
1662
1606
  description: Upload method
1663
- '400':
1664
- $ref: '#/components/responses/ValidationError'
1665
- '500':
1666
- $ref: '#/components/responses/InternalError'
1607
+ "400":
1608
+ $ref: "#/components/responses/ValidationError"
1609
+ "500":
1610
+ $ref: "#/components/responses/InternalError"
1667
1611
  /media/signed-url/{key}:
1668
1612
  get:
1669
1613
  summary: Get signed URL for media access
@@ -1679,7 +1623,7 @@ paths:
1679
1623
  schema:
1680
1624
  type: string
1681
1625
  responses:
1682
- '200':
1626
+ "200":
1683
1627
  description: Signed URL
1684
1628
  content:
1685
1629
  application/json:
@@ -1697,10 +1641,10 @@ paths:
1697
1641
  signedUrl:
1698
1642
  type: string
1699
1643
  description: Signed URL for accessing the media file
1700
- '404':
1701
- $ref: '#/components/responses/NotFound'
1702
- '500':
1703
- $ref: '#/components/responses/InternalError'
1644
+ "404":
1645
+ $ref: "#/components/responses/NotFound"
1646
+ "500":
1647
+ $ref: "#/components/responses/InternalError"
1704
1648
  /media/delete/{key}:
1705
1649
  delete:
1706
1650
  summary: Delete media file
@@ -1716,7 +1660,7 @@ paths:
1716
1660
  schema:
1717
1661
  type: string
1718
1662
  responses:
1719
- '200':
1663
+ "200":
1720
1664
  description: Media file deleted
1721
1665
  content:
1722
1666
  application/json:
@@ -1736,10 +1680,10 @@ paths:
1736
1680
  enum:
1737
1681
  - true
1738
1682
  example: true
1739
- '404':
1740
- $ref: '#/components/responses/NotFound'
1741
- '500':
1742
- $ref: '#/components/responses/InternalError'
1683
+ "404":
1684
+ $ref: "#/components/responses/NotFound"
1685
+ "500":
1686
+ $ref: "#/components/responses/InternalError"
1743
1687
  /goals:
1744
1688
  get:
1745
1689
  summary: List all goals
@@ -1748,7 +1692,7 @@ paths:
1748
1692
  tags:
1749
1693
  - Goals
1750
1694
  responses:
1751
- '200':
1695
+ "200":
1752
1696
  description: List of goals
1753
1697
  content:
1754
1698
  application/json:
@@ -1766,11 +1710,11 @@ paths:
1766
1710
  data:
1767
1711
  type: array
1768
1712
  items:
1769
- $ref: '#/components/schemas/Goal'
1713
+ $ref: "#/components/schemas/Goal"
1770
1714
  total:
1771
1715
  type: integer
1772
- '500':
1773
- $ref: '#/components/responses/InternalError'
1716
+ "500":
1717
+ $ref: "#/components/responses/InternalError"
1774
1718
  post:
1775
1719
  summary: Create a new goal
1776
1720
  description: Create a new goal for the authenticated user
@@ -1782,9 +1726,9 @@ paths:
1782
1726
  content:
1783
1727
  application/json:
1784
1728
  schema:
1785
- $ref: '#/components/schemas/CreateGoalInput'
1729
+ $ref: "#/components/schemas/CreateGoalInput"
1786
1730
  responses:
1787
- '201':
1731
+ "201":
1788
1732
  description: Goal created successfully
1789
1733
  content:
1790
1734
  application/json:
@@ -1797,11 +1741,11 @@ paths:
1797
1741
  - true
1798
1742
  example: true
1799
1743
  data:
1800
- $ref: '#/components/schemas/Goal'
1801
- '400':
1802
- $ref: '#/components/responses/ValidationError'
1803
- '500':
1804
- $ref: '#/components/responses/InternalError'
1744
+ $ref: "#/components/schemas/Goal"
1745
+ "400":
1746
+ $ref: "#/components/responses/ValidationError"
1747
+ "500":
1748
+ $ref: "#/components/responses/InternalError"
1805
1749
  /goals/{id}:
1806
1750
  put:
1807
1751
  summary: Update a goal
@@ -1821,9 +1765,9 @@ paths:
1821
1765
  content:
1822
1766
  application/json:
1823
1767
  schema:
1824
- $ref: '#/components/schemas/UpdateGoalInput'
1768
+ $ref: "#/components/schemas/UpdateGoalInput"
1825
1769
  responses:
1826
- '200':
1770
+ "200":
1827
1771
  description: Goal updated successfully
1828
1772
  content:
1829
1773
  application/json:
@@ -1836,13 +1780,13 @@ paths:
1836
1780
  - true
1837
1781
  example: true
1838
1782
  data:
1839
- $ref: '#/components/schemas/Goal'
1840
- '400':
1841
- $ref: '#/components/responses/ValidationError'
1842
- '404':
1843
- $ref: '#/components/responses/NotFound'
1844
- '500':
1845
- $ref: '#/components/responses/InternalError'
1783
+ $ref: "#/components/schemas/Goal"
1784
+ "400":
1785
+ $ref: "#/components/responses/ValidationError"
1786
+ "404":
1787
+ $ref: "#/components/responses/NotFound"
1788
+ "500":
1789
+ $ref: "#/components/responses/InternalError"
1846
1790
  delete:
1847
1791
  summary: Delete a goal
1848
1792
  description: Delete a goal by ID
@@ -1857,7 +1801,7 @@ paths:
1857
1801
  type: integer
1858
1802
  minimum: 1
1859
1803
  responses:
1860
- '200':
1804
+ "200":
1861
1805
  description: Goal deleted successfully
1862
1806
  content:
1863
1807
  application/json:
@@ -1877,10 +1821,10 @@ paths:
1877
1821
  enum:
1878
1822
  - true
1879
1823
  example: true
1880
- '404':
1881
- $ref: '#/components/responses/NotFound'
1882
- '500':
1883
- $ref: '#/components/responses/InternalError'
1824
+ "404":
1825
+ $ref: "#/components/responses/NotFound"
1826
+ "500":
1827
+ $ref: "#/components/responses/InternalError"
1884
1828
  /goals/bulk-delete:
1885
1829
  post:
1886
1830
  summary: Bulk delete goals
@@ -1907,7 +1851,7 @@ paths:
1907
1851
  required:
1908
1852
  - ids
1909
1853
  responses:
1910
- '200':
1854
+ "200":
1911
1855
  description: Goals deleted successfully
1912
1856
  content:
1913
1857
  application/json:
@@ -1925,21 +1869,19 @@ paths:
1925
1869
  deleted:
1926
1870
  type: integer
1927
1871
  description: Number of goals deleted
1928
- '400':
1929
- $ref: '#/components/responses/ValidationError'
1930
- '500':
1931
- $ref: '#/components/responses/InternalError'
1872
+ "400":
1873
+ $ref: "#/components/responses/ValidationError"
1874
+ "500":
1875
+ $ref: "#/components/responses/InternalError"
1932
1876
  /goals/progress:
1933
1877
  get:
1934
1878
  summary: Get goal progress
1935
- description: >-
1936
- Retrieve progress for all visible goals of the authenticated user. Computes current values from latest records,
1937
- including virtual tags (e.g. BMI). Used by NASEBANAL Target for cross-app goal tracking.
1879
+ description: Retrieve progress for all visible goals of the authenticated user. Computes current values from latest records, including virtual tags (e.g. BMI). Used by NASEBANAL Target for cross-app goal tracking.
1938
1880
  operationId: getGoalProgress
1939
1881
  tags:
1940
1882
  - Goals
1941
1883
  responses:
1942
- '200':
1884
+ "200":
1943
1885
  description: Goal progress list
1944
1886
  content:
1945
1887
  application/json:
@@ -1957,9 +1899,9 @@ paths:
1957
1899
  data:
1958
1900
  type: array
1959
1901
  items:
1960
- $ref: '#/components/schemas/GoalProgress'
1961
- '500':
1962
- $ref: '#/components/responses/InternalError'
1902
+ $ref: "#/components/schemas/GoalProgress"
1903
+ "500":
1904
+ $ref: "#/components/responses/InternalError"
1963
1905
  /goals/{id}/visibility:
1964
1906
  patch:
1965
1907
  summary: Toggle goal visibility
@@ -1986,7 +1928,7 @@ paths:
1986
1928
  required:
1987
1929
  - hide
1988
1930
  responses:
1989
- '200':
1931
+ "200":
1990
1932
  description: Visibility updated
1991
1933
  content:
1992
1934
  application/json:
@@ -1999,11 +1941,11 @@ paths:
1999
1941
  - true
2000
1942
  example: true
2001
1943
  data:
2002
- $ref: '#/components/schemas/Goal'
2003
- '404':
2004
- $ref: '#/components/responses/NotFound'
2005
- '500':
2006
- $ref: '#/components/responses/InternalError'
1944
+ $ref: "#/components/schemas/Goal"
1945
+ "404":
1946
+ $ref: "#/components/responses/NotFound"
1947
+ "500":
1948
+ $ref: "#/components/responses/InternalError"
2007
1949
  components:
2008
1950
  securitySchemes:
2009
1951
  BearerAuth:
@@ -2075,10 +2017,7 @@ components:
2075
2017
  unit:
2076
2018
  type: string
2077
2019
  nullable: true
2078
- description: >-
2079
- Unit of measurement. For a `time`-typed tag, `hour`, `minute`, or
2080
- `second`, controlling whether the value formats as H:MM:SS,
2081
- MM:SS, or decimal seconds.
2020
+ description: Unit of measurement. For a `time`-typed tag, `hour`, `minute`, or `second`, controlling whether the value formats as H:MM:SS, MM:SS, or decimal seconds.
2082
2021
  recorded_at:
2083
2022
  type: string
2084
2023
  format: date-time
@@ -2093,15 +2032,10 @@ components:
2093
2032
  description: Timestamp when the record was last updated
2094
2033
  owner_email:
2095
2034
  type: string
2096
- description: >-
2097
- Email of the tag's owner. Set on the caller's own rows too (it then
2098
- holds the caller's own address), so compare it with the caller's to
2099
- tell shared-in data apart.
2035
+ description: Email of the tag's owner. Set on the caller's own rows too (it then holds the caller's own address), so compare it with the caller's to tell shared-in data apart.
2100
2036
  owner_name:
2101
2037
  type: string
2102
- description: >-
2103
- Display name the owner set in their profile. Absent when they set
2104
- none. Clients name the owner by this, falling back to a masked email.
2038
+ description: Display name the owner set in their profile. Absent when they set none. Clients name the owner by this, falling back to a masked email.
2105
2039
  required:
2106
2040
  - id
2107
2041
  - name
@@ -2122,10 +2056,7 @@ components:
2122
2056
  example: 25.5
2123
2057
  unit:
2124
2058
  type: string
2125
- description: >-
2126
- Unit of measurement. For a `time`-typed tag, must be `hour`,
2127
- `minute`, or `second`, controlling whether the value formats as
2128
- H:MM:SS, MM:SS, or decimal seconds.
2059
+ description: Unit of measurement. For a `time`-typed tag, must be `hour`, `minute`, or `second`, controlling whether the value formats as H:MM:SS, MM:SS, or decimal seconds.
2129
2060
  example: °C
2130
2061
  recorded_at:
2131
2062
  type: string
@@ -2145,10 +2076,7 @@ components:
2145
2076
  description: Numeric value
2146
2077
  unit:
2147
2078
  type: string
2148
- description: >-
2149
- Unit of measurement. For a `time`-typed tag, must be `hour`,
2150
- `minute`, or `second`, controlling whether the value formats as
2151
- H:MM:SS, MM:SS, or decimal seconds.
2079
+ description: Unit of measurement. For a `time`-typed tag, must be `hour`, `minute`, or `second`, controlling whether the value formats as H:MM:SS, MM:SS, or decimal seconds.
2152
2080
  recorded_at:
2153
2081
  type: string
2154
2082
  format: date-time
@@ -2187,7 +2115,7 @@ components:
2187
2115
  latest:
2188
2116
  description: Latest record
2189
2117
  oneOf:
2190
- - $ref: '#/components/schemas/Record'
2118
+ - $ref: "#/components/schemas/Record"
2191
2119
  - type: object
2192
2120
  nullable: true
2193
2121
  required:
@@ -2209,7 +2137,7 @@ components:
2209
2137
  - true
2210
2138
  example: true
2211
2139
  data:
2212
- $ref: '#/components/schemas/Record'
2140
+ $ref: "#/components/schemas/Record"
2213
2141
  RecordListResponse:
2214
2142
  type: object
2215
2143
  properties:
@@ -2224,7 +2152,7 @@ components:
2224
2152
  data:
2225
2153
  type: array
2226
2154
  items:
2227
- $ref: '#/components/schemas/Record'
2155
+ $ref: "#/components/schemas/Record"
2228
2156
  total:
2229
2157
  type: integer
2230
2158
  description: Total number of records matching the filter
@@ -2243,7 +2171,7 @@ components:
2243
2171
  - true
2244
2172
  example: true
2245
2173
  data:
2246
- $ref: '#/components/schemas/RecordStats'
2174
+ $ref: "#/components/schemas/RecordStats"
2247
2175
  ImportResponse:
2248
2176
  type: object
2249
2177
  properties:
@@ -2271,6 +2199,45 @@ components:
2271
2199
  - imported
2272
2200
  - created
2273
2201
  - updated
2202
+ SeriesKind:
2203
+ type: string
2204
+ enum:
2205
+ - raw
2206
+ - diff
2207
+ - diff_pct
2208
+ - sum
2209
+ - bmi
2210
+ - annual_income
2211
+ description: "Stable code of a derived series — not the display suffix (`/Diff` etc.). `diff` difference from the previous record, `diff_pct` percent change from the previous record, `sum` running total, `bmi` BMI (body-weight category), `annual_income` annual income (currency category). The raw tag is `raw`: it is the default on the referencing side and never appears in a `derived_series` list."
2212
+ DerivedSeries:
2213
+ type: object
2214
+ description: "A series derived from a tag. No row of its own: it is derived from the tag's settings (`derived_config`, `category`). Identified by the source tag's `id` plus `kind` — no name in it, so a rename cannot break a reference."
2215
+ properties:
2216
+ kind:
2217
+ $ref: "#/components/schemas/SeriesKind"
2218
+ label:
2219
+ type: string
2220
+ description: Display name (e.g. `Body Weight/BMI`). Not an identifier.
2221
+ unit:
2222
+ type: string
2223
+ nullable: true
2224
+ data_type:
2225
+ type: string
2226
+ enum:
2227
+ - decimal
2228
+ - integer
2229
+ - time
2230
+ chart_type:
2231
+ type: string
2232
+ enum:
2233
+ - line
2234
+ - smooth
2235
+ - step
2236
+ required:
2237
+ - kind
2238
+ - label
2239
+ - data_type
2240
+ - chart_type
2274
2241
  Tag:
2275
2242
  type: object
2276
2243
  properties:
@@ -2283,10 +2250,7 @@ components:
2283
2250
  unit:
2284
2251
  type: string
2285
2252
  nullable: true
2286
- description: >-
2287
- Unit of measurement. For a `time`-typed tag, must be `hour`,
2288
- `minute`, or `second`, controlling whether the value formats as
2289
- H:MM:SS, MM:SS, or decimal seconds.
2253
+ description: Unit of measurement. For a `time`-typed tag, must be `hour`, `minute`, or `second`, controlling whether the value formats as H:MM:SS, MM:SS, or decimal seconds.
2290
2254
  data_type:
2291
2255
  type: string
2292
2256
  enum:
@@ -2303,16 +2267,14 @@ components:
2303
2267
  is_hidden:
2304
2268
  type: integer
2305
2269
  description: 0 = visible, 1 = hidden
2270
+ derived_series:
2271
+ type: array
2272
+ description: Derived series this tag can produce. Excludes the raw series (`raw`).
2273
+ items:
2274
+ $ref: "#/components/schemas/DerivedSeries"
2306
2275
  data_type_options:
2307
2276
  type: array
2308
- description: >-
2309
- Which `data_type` this tag may be changed to, and why not for the
2310
- rest. A change is safe exactly when no stored value changes meaning,
2311
- which depends on the direction and on what is already stored — so
2312
- this is server-computed rather than re-derived per client, and it
2313
- carries a reason per option because the reason differs per option.
2314
- A tag shared in from another user lists only its current type, not
2315
- selectable: the recipient does not own the master data.
2277
+ description: "Which `data_type` this tag may be changed to, and why not for the rest. A change is safe exactly when no stored value changes meaning, which depends on the direction and on what is already stored — so this is server-computed rather than re-derived per client, and it carries a reason per option because the reason differs per option. A tag shared in from another user lists only its current type, not selectable: the recipient does not own the master data."
2316
2278
  items:
2317
2279
  type: object
2318
2280
  properties:
@@ -2327,14 +2289,7 @@ components:
2327
2289
  type: boolean
2328
2290
  reason:
2329
2291
  type: string
2330
- description: >-
2331
- Why this type is unavailable; absent when selectable.
2332
- `SCALE_CHANGE` — into or out of `time`, whose stored minutes
2333
- would be re-read rather than converted (90 becomes 1:30).
2334
- `NON_INTEGER_VALUES` — a stored value is not a whole number,
2335
- so it would display rounded while the stored value stays
2336
- fractional. `NON_BINARY_VALUES` — a stored value is neither 0
2337
- nor 1.
2292
+ description: Why this type is unavailable; absent when selectable. `SCALE_CHANGE` — into or out of `time`, whose stored minutes would be re-read rather than converted (90 becomes 1:30). `NON_INTEGER_VALUES` — a stored value is not a whole number, so it would display rounded while the stored value stays fractional. `NON_BINARY_VALUES` — a stored value is neither 0 nor 1.
2338
2293
  enum:
2339
2294
  - SCALE_CHANGE
2340
2295
  - NON_INTEGER_VALUES
@@ -2350,15 +2305,10 @@ components:
2350
2305
  format: date-time
2351
2306
  owner_email:
2352
2307
  type: string
2353
- description: >-
2354
- Email of the tag's owner. Set on the caller's own tags too (it then
2355
- holds the caller's own address), so compare it with the caller's to
2356
- tell shared-in tags apart.
2308
+ description: Email of the tag's owner. Set on the caller's own tags too (it then holds the caller's own address), so compare it with the caller's to tell shared-in tags apart.
2357
2309
  owner_name:
2358
2310
  type: string
2359
- description: >-
2360
- Display name the owner set in their profile. Absent when they set
2361
- none. Clients name the owner by this, falling back to a masked email.
2311
+ description: Display name the owner set in their profile. Absent when they set none. Clients name the owner by this, falling back to a masked email.
2362
2312
  required:
2363
2313
  - id
2364
2314
  - user_id
@@ -2392,9 +2342,7 @@ components:
2392
2342
  - pending
2393
2343
  - accepted
2394
2344
  - rejected
2395
- description: >-
2396
- Approval status. New shares are created in 'pending' and require the recipient to click the accept link
2397
- delivered via email. Only 'accepted' shares grant data access.
2345
+ description: Approval status. New shares are created in 'pending' and require the recipient to click the accept link delivered via email. Only 'accepted' shares grant data access.
2398
2346
  expires_at:
2399
2347
  type: string
2400
2348
  format: date-time
@@ -2411,18 +2359,13 @@ components:
2411
2359
  description: Email of the sharing owner. Set on shares addressed to the caller.
2412
2360
  owner_name:
2413
2361
  type: string
2414
- description: >-
2415
- Display name the sharing owner set in their profile. Absent when
2416
- they set none.
2362
+ description: Display name the sharing owner set in their profile. Absent when they set none.
2417
2363
  shared_with_email:
2418
2364
  type: string
2419
2365
  description: Email of the recipient. Set on shares the caller owns.
2420
2366
  shared_with_name:
2421
2367
  type: string
2422
- description: >-
2423
- Display name the recipient set in their profile. Set on shares the
2424
- caller owns, and only once the share is `accepted`, so an invitation
2425
- does not reveal the profile of someone who has not accepted it.
2368
+ description: Display name the recipient set in their profile. Set on shares the caller owns, and only once the share is `accepted`, so an invitation does not reveal the profile of someone who has not accepted it.
2426
2369
  required:
2427
2370
  - id
2428
2371
  - owner_user_id
@@ -2560,9 +2503,7 @@ components:
2560
2503
  description: Preferred language
2561
2504
  plan:
2562
2505
  type: string
2563
- description: >-
2564
- Plan for this service, derived from the account's subscriptions.
2565
- `free` when the account holds no active subscription for Recorder.
2506
+ description: Plan for this service, derived from the account's subscriptions. `free` when the account holds no active subscription for Recorder.
2566
2507
  subscription_status:
2567
2508
  type: string
2568
2509
  nullable: true
@@ -2605,6 +2546,10 @@ components:
2605
2546
  virtual_tag_key:
2606
2547
  type: string
2607
2548
  nullable: true
2549
+ tag_kind:
2550
+ allOf:
2551
+ - $ref: "#/components/schemas/SeriesKind"
2552
+ description: Which series of the `tag_id` tag the goal targets. Absent or `raw` means the raw tag. Successor to `virtual_tag_key`, with no name in it.
2608
2553
  is_hidden:
2609
2554
  type: integer
2610
2555
  description: 0 = visible, 1 = hidden
@@ -2660,6 +2605,10 @@ components:
2660
2605
  virtual_tag_key:
2661
2606
  type: string
2662
2607
  description: Virtual tag key
2608
+ tag_kind:
2609
+ allOf:
2610
+ - $ref: "#/components/schemas/SeriesKind"
2611
+ description: Which series of the `tag_id` tag the goal targets. Absent or `raw` means the raw tag. Successor to `virtual_tag_key`, with no name in it.
2663
2612
  required:
2664
2613
  - tag_id
2665
2614
  - goal_type
@@ -2687,6 +2636,10 @@ components:
2687
2636
  type: string
2688
2637
  virtual_tag_key:
2689
2638
  type: string
2639
+ tag_kind:
2640
+ allOf:
2641
+ - $ref: "#/components/schemas/SeriesKind"
2642
+ description: Which series of the `tag_id` tag the goal targets. Absent or `raw` means the raw tag. Successor to `virtual_tag_key`, with no name in it.
2690
2643
  ErrorResponse:
2691
2644
  type: object
2692
2645
  properties:
@@ -2714,7 +2667,7 @@ components:
2714
2667
  content:
2715
2668
  application/json:
2716
2669
  schema:
2717
- $ref: '#/components/schemas/ErrorResponse'
2670
+ $ref: "#/components/schemas/ErrorResponse"
2718
2671
  example:
2719
2672
  success: false
2720
2673
  error:
@@ -2725,7 +2678,7 @@ components:
2725
2678
  content:
2726
2679
  application/json:
2727
2680
  schema:
2728
- $ref: '#/components/schemas/ErrorResponse'
2681
+ $ref: "#/components/schemas/ErrorResponse"
2729
2682
  example:
2730
2683
  success: false
2731
2684
  error:
@@ -2736,7 +2689,7 @@ components:
2736
2689
  content:
2737
2690
  application/json:
2738
2691
  schema:
2739
- $ref: '#/components/schemas/ErrorResponse'
2692
+ $ref: "#/components/schemas/ErrorResponse"
2740
2693
  example:
2741
2694
  success: false
2742
2695
  error:
@@ -2747,7 +2700,7 @@ components:
2747
2700
  content:
2748
2701
  application/json:
2749
2702
  schema:
2750
- $ref: '#/components/schemas/ErrorResponse'
2703
+ $ref: "#/components/schemas/ErrorResponse"
2751
2704
  example:
2752
2705
  success: false
2753
2706
  error: