@nasebanal/cli 0.1.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.
@@ -0,0 +1,2774 @@
1
+ openapi: 3.0.3
2
+ info:
3
+ title: NASEBANAL Recorder API
4
+ description: >-
5
+ Time-series data recorder API built with Cloudflare Workers and D1.
6
+
7
+
8
+ ## Authentication
9
+
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
+
14
+
15
+ Tokens use the format `nbpat_` followed by 40 random characters.
16
+
17
+
18
+ **Usage:**
19
+
20
+ ```
21
+
22
+ Authorization: Bearer nbpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
23
+
24
+ ```
25
+
26
+
27
+ **curl example:**
28
+
29
+ ```bash
30
+
31
+ curl -H "Authorization: Bearer nbpat_xxxxx..." https://api.example.com/api/v1/records
32
+
33
+ ```
34
+
35
+
36
+ **Plan limits:**
37
+
38
+ - Free: API tokens not available
39
+
40
+ - Pro: up to 5 tokens
41
+
42
+ - Family: up to 20 tokens
43
+
44
+ - Enterprise: unlimited tokens
45
+
46
+
47
+ ## SNS Shared Graph Embed
48
+
49
+
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
+
56
+
57
+ | Parameter | Type | Required | Description |
58
+
59
+ |-----------|------|----------|-------------|
60
+
61
+ | `lang` | path | Yes | Language: `ja` or `en` |
62
+
63
+ | `id` | path | Yes | Shared graph ID |
64
+
65
+
66
+ **Features:** Line chart with auto-scale, goal lines, statistics. No navigation header or footer — designed for
67
+ iframe embedding.
68
+
69
+
70
+ **Auth:** Not required (public access).
71
+
72
+
73
+ **iframe example:**
74
+
75
+ ```html
76
+
77
+ <iframe src=\"https://recorder.nasebanal.com/shared/ja/{id}/embed\" width=\"100%\" height=\"500\"
78
+ frameborder=\"0\"></iframe>
79
+
80
+ ```
81
+
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
86
+ license:
87
+ name: MIT
88
+ servers:
89
+ - url: http://localhost:8787/api/v1
90
+ description: Local dev (wrangler dev)
91
+ - url: https://api.recorder.nasebanal.com/api/v1
92
+ description: Production
93
+ security:
94
+ - BearerAuth: []
95
+ paths:
96
+ /health:
97
+ get:
98
+ summary: Health check
99
+ description: Check if the API is running
100
+ operationId: getHealth
101
+ tags:
102
+ - Health
103
+ # Public — the health check is unauthenticated in the implementation.
104
+ # Override the global BearerAuth requirement so it stays open.
105
+ security: []
106
+ responses:
107
+ '200':
108
+ description: API is healthy
109
+ content:
110
+ application/json:
111
+ schema:
112
+ type: object
113
+ properties:
114
+ success:
115
+ type: boolean
116
+ enum:
117
+ - true
118
+ example: true
119
+ data:
120
+ type: object
121
+ properties:
122
+ status:
123
+ type: string
124
+ example: ok
125
+ timestamp:
126
+ type: string
127
+ format: date-time
128
+ /me:
129
+ get:
130
+ summary: Get current user profile
131
+ description: Retrieve the authenticated user's profile data
132
+ operationId: getCurrentUser
133
+ tags:
134
+ - User
135
+ responses:
136
+ '200':
137
+ description: Current user profile
138
+ content:
139
+ application/json:
140
+ schema:
141
+ type: object
142
+ properties:
143
+ success:
144
+ type: boolean
145
+ enum:
146
+ - true
147
+ example: true
148
+ data:
149
+ $ref: '#/components/schemas/User'
150
+ '401':
151
+ $ref: '#/components/responses/Unauthorized'
152
+ '500':
153
+ $ref: '#/components/responses/InternalError'
154
+ /records:
155
+ get:
156
+ summary: Get all records
157
+ description: Retrieve all records with optional filtering
158
+ operationId: getRecords
159
+ tags:
160
+ - Records
161
+ parameters:
162
+ - name: name
163
+ in: query
164
+ description: Filter by record name
165
+ schema:
166
+ type: string
167
+ - name: start_date
168
+ in: query
169
+ description: Filter by start date (ISO 8601)
170
+ schema:
171
+ type: string
172
+ format: date-time
173
+ - name: end_date
174
+ in: query
175
+ description: Filter by end date (ISO 8601)
176
+ schema:
177
+ type: string
178
+ format: date-time
179
+ - name: limit
180
+ 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.'
182
+ schema:
183
+ type: integer
184
+ default: 100
185
+ maximum: 1000
186
+ - name: offset
187
+ in: query
188
+ description: Number of records to skip
189
+ schema:
190
+ type: integer
191
+ default: 0
192
+ responses:
193
+ '200':
194
+ description: List of records
195
+ content:
196
+ application/json:
197
+ schema:
198
+ $ref: '#/components/schemas/RecordListResponse'
199
+ '500':
200
+ $ref: '#/components/responses/InternalError'
201
+ post:
202
+ summary: Create a new record
203
+ description: Create a new time-series record
204
+ operationId: createRecord
205
+ tags:
206
+ - Records
207
+ requestBody:
208
+ required: true
209
+ content:
210
+ application/json:
211
+ schema:
212
+ $ref: '#/components/schemas/CreateRecordInput'
213
+ responses:
214
+ '201':
215
+ description: Record created successfully
216
+ content:
217
+ application/json:
218
+ schema:
219
+ $ref: '#/components/schemas/RecordResponse'
220
+ '400':
221
+ $ref: '#/components/responses/ValidationError'
222
+ '500':
223
+ $ref: '#/components/responses/InternalError'
224
+ /records/{id}:
225
+ get:
226
+ summary: Get a single record
227
+ description: Retrieve a record by ID
228
+ operationId: getRecord
229
+ tags:
230
+ - Records
231
+ parameters:
232
+ - $ref: '#/components/parameters/RecordId'
233
+ responses:
234
+ '200':
235
+ description: Record details
236
+ content:
237
+ application/json:
238
+ schema:
239
+ $ref: '#/components/schemas/RecordResponse'
240
+ '404':
241
+ $ref: '#/components/responses/NotFound'
242
+ '500':
243
+ $ref: '#/components/responses/InternalError'
244
+ put:
245
+ summary: Update a record
246
+ description: Update an existing record by ID
247
+ operationId: updateRecord
248
+ tags:
249
+ - Records
250
+ parameters:
251
+ - $ref: '#/components/parameters/RecordId'
252
+ requestBody:
253
+ required: true
254
+ content:
255
+ application/json:
256
+ schema:
257
+ $ref: '#/components/schemas/UpdateRecordInput'
258
+ responses:
259
+ '200':
260
+ description: Record updated successfully
261
+ content:
262
+ application/json:
263
+ 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'
271
+ delete:
272
+ summary: Delete a record
273
+ description: Delete a record by ID
274
+ operationId: deleteRecord
275
+ tags:
276
+ - Records
277
+ parameters:
278
+ - $ref: '#/components/parameters/RecordId'
279
+ responses:
280
+ '200':
281
+ description: Record deleted successfully
282
+ content:
283
+ application/json:
284
+ schema:
285
+ type: object
286
+ properties:
287
+ success:
288
+ type: boolean
289
+ enum:
290
+ - true
291
+ example: true
292
+ data:
293
+ type: object
294
+ properties:
295
+ success:
296
+ type: boolean
297
+ enum:
298
+ - true
299
+ example: true
300
+ '404':
301
+ $ref: '#/components/responses/NotFound'
302
+ '500':
303
+ $ref: '#/components/responses/InternalError'
304
+ /records/{id}/visibility:
305
+ patch:
306
+ summary: Toggle record visibility
307
+ description: Show or hide a record
308
+ operationId: toggleRecordVisibility
309
+ tags:
310
+ - Records
311
+ parameters:
312
+ - $ref: '#/components/parameters/RecordId'
313
+ requestBody:
314
+ required: true
315
+ content:
316
+ application/json:
317
+ schema:
318
+ type: object
319
+ properties:
320
+ hide:
321
+ type: boolean
322
+ required:
323
+ - hide
324
+ responses:
325
+ '200':
326
+ description: Visibility updated
327
+ content:
328
+ application/json:
329
+ schema:
330
+ type: object
331
+ properties:
332
+ success:
333
+ type: boolean
334
+ enum:
335
+ - true
336
+ example: true
337
+ data:
338
+ type: object
339
+ properties:
340
+ id:
341
+ type: integer
342
+ is_hidden:
343
+ type: integer
344
+ description: 0 = visible, 1 = hidden
345
+ '404':
346
+ $ref: '#/components/responses/NotFound'
347
+ '500':
348
+ $ref: '#/components/responses/InternalError'
349
+ /records/bulk-delete:
350
+ post:
351
+ summary: Bulk delete records
352
+ description: Delete multiple records at once by providing an array of IDs
353
+ operationId: bulkDeleteRecords
354
+ tags:
355
+ - Records
356
+ requestBody:
357
+ required: true
358
+ content:
359
+ application/json:
360
+ schema:
361
+ type: object
362
+ properties:
363
+ ids:
364
+ type: array
365
+ items:
366
+ type: integer
367
+ description: Array of record IDs to delete
368
+ example:
369
+ - 1
370
+ - 2
371
+ - 3
372
+ required:
373
+ - ids
374
+ responses:
375
+ '200':
376
+ description: Records deleted successfully
377
+ content:
378
+ application/json:
379
+ schema:
380
+ type: object
381
+ properties:
382
+ success:
383
+ type: boolean
384
+ enum:
385
+ - true
386
+ example: true
387
+ data:
388
+ type: object
389
+ properties:
390
+ deleted:
391
+ type: integer
392
+ description: Number of records deleted
393
+ '400':
394
+ $ref: '#/components/responses/ValidationError'
395
+ '500':
396
+ $ref: '#/components/responses/InternalError'
397
+ /tags:
398
+ get:
399
+ summary: Get all tags
400
+ description: Retrieve all tags for the authenticated user
401
+ operationId: getTags
402
+ tags:
403
+ - Tags
404
+ responses:
405
+ '200':
406
+ description: List of tags
407
+ content:
408
+ application/json:
409
+ schema:
410
+ type: object
411
+ properties:
412
+ success:
413
+ type: boolean
414
+ enum:
415
+ - true
416
+ example: true
417
+ data:
418
+ type: object
419
+ properties:
420
+ data:
421
+ type: array
422
+ items:
423
+ $ref: '#/components/schemas/Tag'
424
+ total:
425
+ type: integer
426
+ '500':
427
+ $ref: '#/components/responses/InternalError'
428
+ /tags/{id}:
429
+ put:
430
+ summary: Update a tag
431
+ description: Update an existing tag by ID
432
+ operationId: updateTag
433
+ tags:
434
+ - Tags
435
+ parameters:
436
+ - name: id
437
+ in: path
438
+ required: true
439
+ schema:
440
+ type: integer
441
+ minimum: 1
442
+ requestBody:
443
+ required: true
444
+ content:
445
+ application/json:
446
+ schema:
447
+ type: object
448
+ properties:
449
+ name:
450
+ type: string
451
+ unit:
452
+ type: string
453
+ 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.
458
+ chart_type:
459
+ type: string
460
+ enum:
461
+ - line
462
+ - smooth
463
+ - step
464
+ data_type:
465
+ 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.
477
+ enum:
478
+ - binary
479
+ - decimal
480
+ - integer
481
+ - time
482
+ responses:
483
+ '200':
484
+ description: Tag updated successfully
485
+ content:
486
+ application/json:
487
+ schema:
488
+ type: object
489
+ properties:
490
+ success:
491
+ type: boolean
492
+ enum:
493
+ - true
494
+ example: true
495
+ 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`.
504
+ content:
505
+ application/json:
506
+ schema:
507
+ $ref: '#/components/schemas/ErrorResponse'
508
+ example:
509
+ success: false
510
+ error:
511
+ 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'
517
+ delete:
518
+ summary: Delete a tag
519
+ description: Delete a tag and all associated records by ID
520
+ operationId: deleteTag
521
+ tags:
522
+ - Tags
523
+ parameters:
524
+ - name: id
525
+ in: path
526
+ required: true
527
+ schema:
528
+ type: integer
529
+ minimum: 1
530
+ responses:
531
+ '200':
532
+ description: Tag deleted successfully
533
+ content:
534
+ application/json:
535
+ schema:
536
+ type: object
537
+ properties:
538
+ success:
539
+ type: boolean
540
+ enum:
541
+ - true
542
+ example: true
543
+ data:
544
+ type: object
545
+ properties:
546
+ success:
547
+ type: boolean
548
+ enum:
549
+ - true
550
+ example: true
551
+ '404':
552
+ $ref: '#/components/responses/NotFound'
553
+ '500':
554
+ $ref: '#/components/responses/InternalError'
555
+ /tags/{id}/visibility:
556
+ patch:
557
+ summary: Toggle tag visibility
558
+ description: Show or hide a tag
559
+ operationId: toggleTagVisibility
560
+ tags:
561
+ - Tags
562
+ parameters:
563
+ - name: id
564
+ in: path
565
+ required: true
566
+ schema:
567
+ type: integer
568
+ minimum: 1
569
+ requestBody:
570
+ required: true
571
+ content:
572
+ application/json:
573
+ schema:
574
+ type: object
575
+ properties:
576
+ hide:
577
+ type: boolean
578
+ required:
579
+ - hide
580
+ responses:
581
+ '200':
582
+ description: Visibility updated
583
+ content:
584
+ application/json:
585
+ schema:
586
+ type: object
587
+ properties:
588
+ success:
589
+ type: boolean
590
+ enum:
591
+ - true
592
+ example: true
593
+ data:
594
+ $ref: '#/components/schemas/Tag'
595
+ '404':
596
+ $ref: '#/components/responses/NotFound'
597
+ '500':
598
+ $ref: '#/components/responses/InternalError'
599
+ /tags/bulk-delete:
600
+ post:
601
+ summary: Bulk delete tags
602
+ description: Delete multiple tags and their associated records at once
603
+ operationId: bulkDeleteTags
604
+ tags:
605
+ - Tags
606
+ requestBody:
607
+ required: true
608
+ content:
609
+ application/json:
610
+ schema:
611
+ type: object
612
+ properties:
613
+ ids:
614
+ type: array
615
+ items:
616
+ type: integer
617
+ description: Array of tag IDs to delete
618
+ example:
619
+ - 1
620
+ - 2
621
+ - 3
622
+ required:
623
+ - ids
624
+ responses:
625
+ '200':
626
+ description: Tags deleted successfully
627
+ content:
628
+ application/json:
629
+ schema:
630
+ type: object
631
+ properties:
632
+ success:
633
+ type: boolean
634
+ enum:
635
+ - true
636
+ example: true
637
+ data:
638
+ type: object
639
+ properties:
640
+ deleted:
641
+ type: integer
642
+ description: Number of tags deleted
643
+ '400':
644
+ $ref: '#/components/responses/ValidationError'
645
+ '500':
646
+ $ref: '#/components/responses/InternalError'
647
+ /shares:
648
+ get:
649
+ summary: Get my shares
650
+ description: Retrieve shares created by the authenticated user
651
+ operationId: getMyShares
652
+ tags:
653
+ - Shares
654
+ responses:
655
+ '200':
656
+ description: List of shares
657
+ content:
658
+ application/json:
659
+ schema:
660
+ type: object
661
+ properties:
662
+ success:
663
+ type: boolean
664
+ enum:
665
+ - true
666
+ example: true
667
+ data:
668
+ type: object
669
+ properties:
670
+ data:
671
+ type: array
672
+ items:
673
+ $ref: '#/components/schemas/Share'
674
+ total:
675
+ type: integer
676
+ '500':
677
+ $ref: '#/components/responses/InternalError'
678
+ post:
679
+ 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.
685
+ operationId: createShare
686
+ tags:
687
+ - Shares
688
+ requestBody:
689
+ required: true
690
+ content:
691
+ application/json:
692
+ schema:
693
+ type: object
694
+ properties:
695
+ shared_with_email:
696
+ type: string
697
+ format: email
698
+ tag_id:
699
+ type: integer
700
+ nullable: true
701
+ permission:
702
+ type: string
703
+ enum:
704
+ - read
705
+ - write
706
+ required:
707
+ - shared_with_email
708
+ - permission
709
+ responses:
710
+ '201':
711
+ description: Share created successfully
712
+ content:
713
+ application/json:
714
+ schema:
715
+ type: object
716
+ properties:
717
+ success:
718
+ type: boolean
719
+ enum:
720
+ - true
721
+ example: true
722
+ data:
723
+ $ref: '#/components/schemas/Share'
724
+ '400':
725
+ $ref: '#/components/responses/ValidationError'
726
+ '500':
727
+ $ref: '#/components/responses/InternalError'
728
+ /shares/with-me:
729
+ get:
730
+ 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.
735
+ operationId: getSharedWithMe
736
+ tags:
737
+ - Shares
738
+ parameters:
739
+ - name: status
740
+ in: query
741
+ required: false
742
+ schema:
743
+ type: string
744
+ enum:
745
+ - accepted
746
+ - pending
747
+ - rejected
748
+ - all
749
+ default: accepted
750
+ description: Filter by approval status.
751
+ responses:
752
+ '200':
753
+ description: List of shares shared with the user
754
+ content:
755
+ application/json:
756
+ schema:
757
+ type: object
758
+ properties:
759
+ success:
760
+ type: boolean
761
+ enum:
762
+ - true
763
+ example: true
764
+ data:
765
+ type: object
766
+ properties:
767
+ data:
768
+ type: array
769
+ items:
770
+ $ref: '#/components/schemas/Share'
771
+ total:
772
+ type: integer
773
+ '500':
774
+ $ref: '#/components/responses/InternalError'
775
+ /shares/{id}:
776
+ put:
777
+ summary: Update a share
778
+ description: Update an existing share
779
+ operationId: updateShare
780
+ tags:
781
+ - Shares
782
+ parameters:
783
+ - name: id
784
+ in: path
785
+ required: true
786
+ schema:
787
+ type: integer
788
+ minimum: 1
789
+ requestBody:
790
+ required: true
791
+ content:
792
+ application/json:
793
+ schema:
794
+ type: object
795
+ properties:
796
+ tag_id:
797
+ type: integer
798
+ permission:
799
+ type: string
800
+ enum:
801
+ - read
802
+ - write
803
+ required:
804
+ - tag_id
805
+ - permission
806
+ responses:
807
+ '200':
808
+ description: Share updated
809
+ content:
810
+ application/json:
811
+ schema:
812
+ type: object
813
+ properties:
814
+ success:
815
+ type: boolean
816
+ enum:
817
+ - true
818
+ example: true
819
+ data:
820
+ $ref: '#/components/schemas/Share'
821
+ '404':
822
+ $ref: '#/components/responses/NotFound'
823
+ '500':
824
+ $ref: '#/components/responses/InternalError'
825
+ delete:
826
+ summary: Delete a share
827
+ description: Delete a share by ID
828
+ operationId: deleteShare
829
+ tags:
830
+ - Shares
831
+ parameters:
832
+ - name: id
833
+ in: path
834
+ required: true
835
+ schema:
836
+ type: integer
837
+ minimum: 1
838
+ responses:
839
+ '200':
840
+ description: Share deleted
841
+ content:
842
+ application/json:
843
+ schema:
844
+ type: object
845
+ properties:
846
+ success:
847
+ type: boolean
848
+ enum:
849
+ - true
850
+ example: true
851
+ data:
852
+ type: object
853
+ properties:
854
+ success:
855
+ type: boolean
856
+ enum:
857
+ - true
858
+ example: true
859
+ '404':
860
+ $ref: '#/components/responses/NotFound'
861
+ '500':
862
+ $ref: '#/components/responses/InternalError'
863
+ /shares/{id}/visibility:
864
+ patch:
865
+ summary: Toggle share visibility
866
+ description: Show or hide a share from the dashboard
867
+ operationId: toggleShareVisibility
868
+ tags:
869
+ - Shares
870
+ parameters:
871
+ - name: id
872
+ in: path
873
+ required: true
874
+ schema:
875
+ type: integer
876
+ minimum: 1
877
+ requestBody:
878
+ required: true
879
+ content:
880
+ application/json:
881
+ schema:
882
+ type: object
883
+ properties:
884
+ is_hidden:
885
+ type: boolean
886
+ required:
887
+ - is_hidden
888
+ responses:
889
+ '200':
890
+ description: Visibility updated
891
+ content:
892
+ application/json:
893
+ schema:
894
+ type: object
895
+ properties:
896
+ success:
897
+ type: boolean
898
+ enum:
899
+ - true
900
+ example: true
901
+ data:
902
+ $ref: '#/components/schemas/Share'
903
+ '404':
904
+ $ref: '#/components/responses/NotFound'
905
+ '500':
906
+ $ref: '#/components/responses/InternalError'
907
+ /shares/{id}/pause:
908
+ patch:
909
+ summary: Toggle share paused status
910
+ description: Pause or unpause a share (owner-side)
911
+ operationId: toggleSharePaused
912
+ tags:
913
+ - Shares
914
+ parameters:
915
+ - name: id
916
+ in: path
917
+ required: true
918
+ schema:
919
+ type: integer
920
+ minimum: 1
921
+ requestBody:
922
+ required: true
923
+ content:
924
+ application/json:
925
+ schema:
926
+ type: object
927
+ properties:
928
+ is_paused:
929
+ type: boolean
930
+ required:
931
+ - is_paused
932
+ responses:
933
+ '200':
934
+ description: Paused status updated
935
+ content:
936
+ application/json:
937
+ schema:
938
+ type: object
939
+ properties:
940
+ success:
941
+ type: boolean
942
+ enum:
943
+ - true
944
+ example: true
945
+ data:
946
+ $ref: '#/components/schemas/Share'
947
+ '404':
948
+ $ref: '#/components/responses/NotFound'
949
+ '500':
950
+ $ref: '#/components/responses/InternalError'
951
+ /shares/{id}/transfer:
952
+ post:
953
+ summary: Transfer tag ownership
954
+ description: Transfer tag ownership to the shared-with user
955
+ operationId: transferShareOwnership
956
+ tags:
957
+ - Shares
958
+ parameters:
959
+ - name: id
960
+ in: path
961
+ required: true
962
+ schema:
963
+ type: integer
964
+ minimum: 1
965
+ responses:
966
+ '200':
967
+ description: Ownership transferred successfully
968
+ content:
969
+ application/json:
970
+ schema:
971
+ type: object
972
+ properties:
973
+ success:
974
+ type: boolean
975
+ enum:
976
+ - true
977
+ example: true
978
+ data:
979
+ type: object
980
+ properties:
981
+ success:
982
+ type: boolean
983
+ enum:
984
+ - true
985
+ example: true
986
+ '404':
987
+ $ref: '#/components/responses/NotFound'
988
+ '500':
989
+ $ref: '#/components/responses/InternalError'
990
+ /shares/bulk-delete:
991
+ post:
992
+ summary: Bulk delete shares
993
+ description: Delete multiple shares at once
994
+ operationId: bulkDeleteShares
995
+ tags:
996
+ - Shares
997
+ requestBody:
998
+ required: true
999
+ content:
1000
+ application/json:
1001
+ schema:
1002
+ type: object
1003
+ properties:
1004
+ ids:
1005
+ type: array
1006
+ items:
1007
+ type: integer
1008
+ description: Array of share IDs to delete
1009
+ example:
1010
+ - 1
1011
+ - 2
1012
+ - 3
1013
+ required:
1014
+ - ids
1015
+ responses:
1016
+ '200':
1017
+ description: Shares deleted successfully
1018
+ content:
1019
+ application/json:
1020
+ schema:
1021
+ type: object
1022
+ properties:
1023
+ success:
1024
+ type: boolean
1025
+ enum:
1026
+ - true
1027
+ example: true
1028
+ data:
1029
+ type: object
1030
+ properties:
1031
+ deleted:
1032
+ type: integer
1033
+ '400':
1034
+ $ref: '#/components/responses/ValidationError'
1035
+ '500':
1036
+ $ref: '#/components/responses/InternalError'
1037
+ /shares/accept:
1038
+ post:
1039
+ 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.
1044
+ operationId: acceptShare
1045
+ tags:
1046
+ - Shares
1047
+ requestBody:
1048
+ required: true
1049
+ content:
1050
+ application/json:
1051
+ schema:
1052
+ type: object
1053
+ properties:
1054
+ token:
1055
+ type: string
1056
+ description: Opaque invitation token delivered via email. Hashed server-side before lookup.
1057
+ required:
1058
+ - token
1059
+ responses:
1060
+ '200':
1061
+ description: Share accepted
1062
+ content:
1063
+ application/json:
1064
+ schema:
1065
+ type: object
1066
+ properties:
1067
+ success:
1068
+ type: boolean
1069
+ enum:
1070
+ - true
1071
+ data:
1072
+ type: object
1073
+ properties:
1074
+ id:
1075
+ type: integer
1076
+ status:
1077
+ type: string
1078
+ enum:
1079
+ - accepted
1080
+ already:
1081
+ type: boolean
1082
+ description: True if the share was already accepted.
1083
+ '400':
1084
+ $ref: '#/components/responses/ValidationError'
1085
+ '404':
1086
+ $ref: '#/components/responses/NotFound'
1087
+ /shares/reject:
1088
+ post:
1089
+ 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}).
1095
+ operationId: rejectShareByToken
1096
+ tags:
1097
+ - Shares
1098
+ requestBody:
1099
+ required: true
1100
+ content:
1101
+ application/json:
1102
+ schema:
1103
+ type: object
1104
+ properties:
1105
+ token:
1106
+ type: string
1107
+ description: Opaque invitation token delivered via email. Hashed server-side before lookup.
1108
+ required:
1109
+ - token
1110
+ responses:
1111
+ '200':
1112
+ description: Share rejected
1113
+ content:
1114
+ application/json:
1115
+ schema:
1116
+ type: object
1117
+ properties:
1118
+ success:
1119
+ type: boolean
1120
+ enum:
1121
+ - true
1122
+ data:
1123
+ type: object
1124
+ properties:
1125
+ id:
1126
+ type: integer
1127
+ status:
1128
+ type: string
1129
+ enum:
1130
+ - rejected
1131
+ already:
1132
+ type: boolean
1133
+ description: True if the share was already rejected.
1134
+ '400':
1135
+ $ref: '#/components/responses/ValidationError'
1136
+ '404':
1137
+ $ref: '#/components/responses/NotFound'
1138
+ /shares/{id}/reject:
1139
+ post:
1140
+ 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).
1144
+ operationId: rejectShare
1145
+ tags:
1146
+ - Shares
1147
+ parameters:
1148
+ - name: id
1149
+ in: path
1150
+ required: true
1151
+ schema:
1152
+ type: integer
1153
+ responses:
1154
+ '200':
1155
+ description: Share rejected
1156
+ content:
1157
+ application/json:
1158
+ schema:
1159
+ type: object
1160
+ properties:
1161
+ success:
1162
+ type: boolean
1163
+ enum:
1164
+ - true
1165
+ data:
1166
+ type: object
1167
+ properties:
1168
+ id:
1169
+ type: integer
1170
+ status:
1171
+ type: string
1172
+ enum:
1173
+ - rejected
1174
+ '400':
1175
+ $ref: '#/components/responses/ValidationError'
1176
+ '404':
1177
+ $ref: '#/components/responses/NotFound'
1178
+ /shared-graphs:
1179
+ get:
1180
+ summary: Get my shared graphs
1181
+ description: Retrieve shared graphs created by the authenticated user
1182
+ operationId: getMySharedGraphs
1183
+ tags:
1184
+ - Shared Graphs
1185
+ responses:
1186
+ '200':
1187
+ description: List of shared graphs
1188
+ content:
1189
+ application/json:
1190
+ schema:
1191
+ type: object
1192
+ properties:
1193
+ success:
1194
+ type: boolean
1195
+ enum:
1196
+ - true
1197
+ example: true
1198
+ data:
1199
+ type: array
1200
+ items:
1201
+ $ref: '#/components/schemas/SharedGraph'
1202
+ '500':
1203
+ $ref: '#/components/responses/InternalError'
1204
+ post:
1205
+ summary: Create a shared graph
1206
+ description: Create a publicly shareable graph snapshot for SNS sharing
1207
+ operationId: createSharedGraph
1208
+ tags:
1209
+ - Shared Graphs
1210
+ requestBody:
1211
+ required: true
1212
+ content:
1213
+ application/json:
1214
+ schema:
1215
+ $ref: '#/components/schemas/CreateSharedGraphInput'
1216
+ responses:
1217
+ '201':
1218
+ description: Shared graph created
1219
+ content:
1220
+ application/json:
1221
+ schema:
1222
+ type: object
1223
+ properties:
1224
+ success:
1225
+ type: boolean
1226
+ enum:
1227
+ - true
1228
+ example: true
1229
+ data:
1230
+ $ref: '#/components/schemas/SharedGraph'
1231
+ '400':
1232
+ $ref: '#/components/responses/ValidationError'
1233
+ '500':
1234
+ $ref: '#/components/responses/InternalError'
1235
+ /shared-graphs/{id}:
1236
+ put:
1237
+ summary: Update a shared graph
1238
+ description: Update title or description of a shared graph
1239
+ operationId: updateSharedGraph
1240
+ tags:
1241
+ - Shared Graphs
1242
+ parameters:
1243
+ - name: id
1244
+ in: path
1245
+ required: true
1246
+ schema:
1247
+ type: string
1248
+ requestBody:
1249
+ required: true
1250
+ content:
1251
+ application/json:
1252
+ schema:
1253
+ type: object
1254
+ properties:
1255
+ title:
1256
+ type: string
1257
+ description:
1258
+ type: string
1259
+ responses:
1260
+ '200':
1261
+ description: Shared graph updated
1262
+ content:
1263
+ application/json:
1264
+ schema:
1265
+ type: object
1266
+ properties:
1267
+ success:
1268
+ type: boolean
1269
+ enum:
1270
+ - true
1271
+ example: true
1272
+ data:
1273
+ $ref: '#/components/schemas/SharedGraph'
1274
+ '404':
1275
+ $ref: '#/components/responses/NotFound'
1276
+ '500':
1277
+ $ref: '#/components/responses/InternalError'
1278
+ delete:
1279
+ summary: Delete a shared graph
1280
+ description: Delete a shared graph by ID
1281
+ operationId: deleteSharedGraph
1282
+ tags:
1283
+ - Shared Graphs
1284
+ parameters:
1285
+ - name: id
1286
+ in: path
1287
+ required: true
1288
+ schema:
1289
+ type: string
1290
+ responses:
1291
+ '200':
1292
+ description: Shared graph deleted
1293
+ content:
1294
+ application/json:
1295
+ schema:
1296
+ type: object
1297
+ properties:
1298
+ success:
1299
+ type: boolean
1300
+ enum:
1301
+ - true
1302
+ example: true
1303
+ data:
1304
+ type: object
1305
+ properties:
1306
+ success:
1307
+ type: boolean
1308
+ enum:
1309
+ - true
1310
+ example: true
1311
+ '404':
1312
+ $ref: '#/components/responses/NotFound'
1313
+ '500':
1314
+ $ref: '#/components/responses/InternalError'
1315
+ /shared-graphs/{id}/toggle:
1316
+ patch:
1317
+ summary: Toggle shared graph active status
1318
+ description: Activate or deactivate a shared graph
1319
+ operationId: toggleSharedGraphActive
1320
+ tags:
1321
+ - Shared Graphs
1322
+ parameters:
1323
+ - name: id
1324
+ in: path
1325
+ required: true
1326
+ schema:
1327
+ type: string
1328
+ responses:
1329
+ '200':
1330
+ description: Status toggled
1331
+ content:
1332
+ application/json:
1333
+ schema:
1334
+ type: object
1335
+ properties:
1336
+ success:
1337
+ type: boolean
1338
+ enum:
1339
+ - true
1340
+ example: true
1341
+ data:
1342
+ type: object
1343
+ properties:
1344
+ is_active:
1345
+ type: integer
1346
+ description: 1 for active, 0 for inactive
1347
+ '404':
1348
+ $ref: '#/components/responses/NotFound'
1349
+ '500':
1350
+ $ref: '#/components/responses/InternalError'
1351
+ /shared-graphs/bulk-delete:
1352
+ post:
1353
+ summary: Bulk delete shared graphs
1354
+ description: Delete multiple shared graphs at once
1355
+ operationId: bulkDeleteSharedGraphs
1356
+ tags:
1357
+ - Shared Graphs
1358
+ requestBody:
1359
+ required: true
1360
+ content:
1361
+ application/json:
1362
+ schema:
1363
+ type: object
1364
+ properties:
1365
+ ids:
1366
+ type: array
1367
+ items:
1368
+ type: string
1369
+ description: Array of shared graph IDs to delete
1370
+ example:
1371
+ - abc123
1372
+ - def456
1373
+ required:
1374
+ - ids
1375
+ responses:
1376
+ '200':
1377
+ description: Shared graphs deleted
1378
+ content:
1379
+ application/json:
1380
+ schema:
1381
+ type: object
1382
+ properties:
1383
+ success:
1384
+ type: boolean
1385
+ enum:
1386
+ - true
1387
+ example: true
1388
+ data:
1389
+ type: object
1390
+ properties:
1391
+ deleted:
1392
+ type: integer
1393
+ '400':
1394
+ $ref: '#/components/responses/ValidationError'
1395
+ '500':
1396
+ $ref: '#/components/responses/InternalError'
1397
+ /public/shared-graphs/{id}:
1398
+ get:
1399
+ summary: Get public shared graph
1400
+ description: Retrieve a shared graph's public data (no authentication required)
1401
+ operationId: getPublicSharedGraph
1402
+ tags:
1403
+ - Shared Graphs
1404
+ security: []
1405
+ parameters:
1406
+ - name: id
1407
+ in: path
1408
+ required: true
1409
+ schema:
1410
+ type: string
1411
+ responses:
1412
+ '200':
1413
+ description: Shared graph public data
1414
+ content:
1415
+ application/json:
1416
+ schema:
1417
+ type: object
1418
+ properties:
1419
+ success:
1420
+ type: boolean
1421
+ enum:
1422
+ - true
1423
+ example: true
1424
+ data:
1425
+ $ref: '#/components/schemas/SharedGraphPublic'
1426
+ '404':
1427
+ $ref: '#/components/responses/NotFound'
1428
+ /public/shared-graphs/{id}/og-image:
1429
+ get:
1430
+ summary: Generate OG image for shared graph
1431
+ description: Generate an Open Graph image for a shared graph (no authentication required)
1432
+ operationId: getSharedGraphOgImage
1433
+ tags:
1434
+ - Shared Graphs
1435
+ security: []
1436
+ parameters:
1437
+ - name: id
1438
+ in: path
1439
+ required: true
1440
+ schema:
1441
+ type: string
1442
+ responses:
1443
+ '200':
1444
+ description: OG image
1445
+ content:
1446
+ image/png:
1447
+ schema:
1448
+ type: string
1449
+ format: binary
1450
+ '404':
1451
+ $ref: '#/components/responses/NotFound'
1452
+ /records/export:
1453
+ get:
1454
+ summary: Export records as CSV
1455
+ description: Export records to CSV format with optional filtering
1456
+ operationId: exportRecords
1457
+ tags:
1458
+ - Records
1459
+ parameters:
1460
+ - name: name
1461
+ in: query
1462
+ description: Filter by record name (supports wildcards with *)
1463
+ schema:
1464
+ type: string
1465
+ - name: start_date
1466
+ in: query
1467
+ description: Filter by start date (ISO 8601)
1468
+ schema:
1469
+ type: string
1470
+ format: date-time
1471
+ - name: end_date
1472
+ in: query
1473
+ description: Filter by end date (ISO 8601)
1474
+ schema:
1475
+ type: string
1476
+ format: date-time
1477
+ responses:
1478
+ '200':
1479
+ description: CSV file containing records
1480
+ content:
1481
+ text/csv:
1482
+ schema:
1483
+ type: string
1484
+ format: binary
1485
+ example: |-
1486
+ Tag,Value,Unit,Recorded At
1487
+ "temperature",25.5,"°C","2024-01-01T10:00:00Z"
1488
+ '500':
1489
+ $ref: '#/components/responses/InternalError'
1490
+ /records/import:
1491
+ post:
1492
+ summary: Import records from CSV
1493
+ description: Import records from CSV file. Records with matching tag and recorded_at will be updated, others will be created.
1494
+ operationId: importRecords
1495
+ tags:
1496
+ - Records
1497
+ requestBody:
1498
+ required: true
1499
+ content:
1500
+ multipart/form-data:
1501
+ schema:
1502
+ type: object
1503
+ properties:
1504
+ file:
1505
+ type: string
1506
+ format: binary
1507
+ description: 'CSV file to import (columns: Tag, Value, Unit, Recorded At)'
1508
+ required:
1509
+ - file
1510
+ responses:
1511
+ '200':
1512
+ description: Import completed successfully
1513
+ content:
1514
+ application/json:
1515
+ schema:
1516
+ $ref: '#/components/schemas/ImportResponse'
1517
+ '400':
1518
+ $ref: '#/components/responses/ValidationError'
1519
+ '500':
1520
+ $ref: '#/components/responses/InternalError'
1521
+ /records/stats:
1522
+ get:
1523
+ summary: Get record statistics
1524
+ description: Get aggregated statistics for records with a specific name
1525
+ operationId: getRecordStats
1526
+ tags:
1527
+ - Statistics
1528
+ parameters:
1529
+ - name: name
1530
+ in: query
1531
+ required: true
1532
+ description: Record name to get statistics for
1533
+ schema:
1534
+ type: string
1535
+ responses:
1536
+ '200':
1537
+ description: Statistics for the specified record name
1538
+ content:
1539
+ application/json:
1540
+ 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'
1548
+ /media/upload:
1549
+ post:
1550
+ summary: Upload media file
1551
+ description: Upload a media file (image, video, or audio). Maximum file size is 100MB.
1552
+ operationId: uploadMedia
1553
+ tags:
1554
+ - Media
1555
+ requestBody:
1556
+ required: true
1557
+ content:
1558
+ multipart/form-data:
1559
+ schema:
1560
+ type: object
1561
+ properties:
1562
+ file:
1563
+ type: string
1564
+ format: binary
1565
+ description: Media file to upload (max 100MB)
1566
+ required:
1567
+ - file
1568
+ responses:
1569
+ '200':
1570
+ description: File uploaded successfully
1571
+ content:
1572
+ application/json:
1573
+ schema:
1574
+ type: object
1575
+ properties:
1576
+ success:
1577
+ type: boolean
1578
+ enum:
1579
+ - true
1580
+ example: true
1581
+ data:
1582
+ type: object
1583
+ properties:
1584
+ url:
1585
+ type: string
1586
+ description: URL of the uploaded file
1587
+ key:
1588
+ type: string
1589
+ description: Unique key for the uploaded file
1590
+ mediaType:
1591
+ type: string
1592
+ enum:
1593
+ - image
1594
+ - video
1595
+ - audio
1596
+ description: Type of media
1597
+ filename:
1598
+ type: string
1599
+ description: Original filename
1600
+ size:
1601
+ type: integer
1602
+ description: File size in bytes
1603
+ contentType:
1604
+ type: string
1605
+ description: MIME content type
1606
+ '400':
1607
+ $ref: '#/components/responses/ValidationError'
1608
+ '500':
1609
+ $ref: '#/components/responses/InternalError'
1610
+ /media/upload-url:
1611
+ post:
1612
+ summary: Get upload URL info
1613
+ description: Get upload URL information for a media file
1614
+ operationId: getMediaUploadUrl
1615
+ tags:
1616
+ - Media
1617
+ requestBody:
1618
+ required: true
1619
+ content:
1620
+ application/json:
1621
+ schema:
1622
+ type: object
1623
+ properties:
1624
+ filename:
1625
+ type: string
1626
+ description: Original filename
1627
+ contentType:
1628
+ type: string
1629
+ description: MIME content type
1630
+ size:
1631
+ type: integer
1632
+ description: File size in bytes
1633
+ required:
1634
+ - filename
1635
+ - contentType
1636
+ - size
1637
+ responses:
1638
+ '200':
1639
+ description: Upload URL info
1640
+ content:
1641
+ application/json:
1642
+ schema:
1643
+ type: object
1644
+ properties:
1645
+ success:
1646
+ type: boolean
1647
+ enum:
1648
+ - true
1649
+ example: true
1650
+ data:
1651
+ type: object
1652
+ properties:
1653
+ key:
1654
+ type: string
1655
+ description: Unique key for the file
1656
+ mediaType:
1657
+ type: string
1658
+ description: Type of media
1659
+ uploadMethod:
1660
+ type: string
1661
+ example: direct
1662
+ description: Upload method
1663
+ '400':
1664
+ $ref: '#/components/responses/ValidationError'
1665
+ '500':
1666
+ $ref: '#/components/responses/InternalError'
1667
+ /media/signed-url/{key}:
1668
+ get:
1669
+ summary: Get signed URL for media access
1670
+ description: Get a signed URL to access a media file
1671
+ operationId: getMediaSignedUrl
1672
+ tags:
1673
+ - Media
1674
+ parameters:
1675
+ - name: key
1676
+ in: path
1677
+ required: true
1678
+ description: Media file key
1679
+ schema:
1680
+ type: string
1681
+ responses:
1682
+ '200':
1683
+ description: Signed URL
1684
+ content:
1685
+ application/json:
1686
+ schema:
1687
+ type: object
1688
+ properties:
1689
+ success:
1690
+ type: boolean
1691
+ enum:
1692
+ - true
1693
+ example: true
1694
+ data:
1695
+ type: object
1696
+ properties:
1697
+ signedUrl:
1698
+ type: string
1699
+ description: Signed URL for accessing the media file
1700
+ '404':
1701
+ $ref: '#/components/responses/NotFound'
1702
+ '500':
1703
+ $ref: '#/components/responses/InternalError'
1704
+ /media/delete/{key}:
1705
+ delete:
1706
+ summary: Delete media file
1707
+ description: Delete a media file by key
1708
+ operationId: deleteMedia
1709
+ tags:
1710
+ - Media
1711
+ parameters:
1712
+ - name: key
1713
+ in: path
1714
+ required: true
1715
+ description: Media file key
1716
+ schema:
1717
+ type: string
1718
+ responses:
1719
+ '200':
1720
+ description: Media file deleted
1721
+ content:
1722
+ application/json:
1723
+ schema:
1724
+ type: object
1725
+ properties:
1726
+ success:
1727
+ type: boolean
1728
+ enum:
1729
+ - true
1730
+ example: true
1731
+ data:
1732
+ type: object
1733
+ properties:
1734
+ success:
1735
+ type: boolean
1736
+ enum:
1737
+ - true
1738
+ example: true
1739
+ '404':
1740
+ $ref: '#/components/responses/NotFound'
1741
+ '500':
1742
+ $ref: '#/components/responses/InternalError'
1743
+ /goals:
1744
+ get:
1745
+ summary: List all goals
1746
+ description: Retrieve all goals for the authenticated user
1747
+ operationId: getGoals
1748
+ tags:
1749
+ - Goals
1750
+ responses:
1751
+ '200':
1752
+ description: List of goals
1753
+ content:
1754
+ application/json:
1755
+ schema:
1756
+ type: object
1757
+ properties:
1758
+ success:
1759
+ type: boolean
1760
+ enum:
1761
+ - true
1762
+ example: true
1763
+ data:
1764
+ type: object
1765
+ properties:
1766
+ data:
1767
+ type: array
1768
+ items:
1769
+ $ref: '#/components/schemas/Goal'
1770
+ total:
1771
+ type: integer
1772
+ '500':
1773
+ $ref: '#/components/responses/InternalError'
1774
+ post:
1775
+ summary: Create a new goal
1776
+ description: Create a new goal for the authenticated user
1777
+ operationId: createGoal
1778
+ tags:
1779
+ - Goals
1780
+ requestBody:
1781
+ required: true
1782
+ content:
1783
+ application/json:
1784
+ schema:
1785
+ $ref: '#/components/schemas/CreateGoalInput'
1786
+ responses:
1787
+ '201':
1788
+ description: Goal created successfully
1789
+ content:
1790
+ application/json:
1791
+ schema:
1792
+ type: object
1793
+ properties:
1794
+ success:
1795
+ type: boolean
1796
+ enum:
1797
+ - true
1798
+ example: true
1799
+ data:
1800
+ $ref: '#/components/schemas/Goal'
1801
+ '400':
1802
+ $ref: '#/components/responses/ValidationError'
1803
+ '500':
1804
+ $ref: '#/components/responses/InternalError'
1805
+ /goals/{id}:
1806
+ put:
1807
+ summary: Update a goal
1808
+ description: Update an existing goal by ID
1809
+ operationId: updateGoal
1810
+ tags:
1811
+ - Goals
1812
+ parameters:
1813
+ - name: id
1814
+ in: path
1815
+ required: true
1816
+ schema:
1817
+ type: integer
1818
+ minimum: 1
1819
+ requestBody:
1820
+ required: true
1821
+ content:
1822
+ application/json:
1823
+ schema:
1824
+ $ref: '#/components/schemas/UpdateGoalInput'
1825
+ responses:
1826
+ '200':
1827
+ description: Goal updated successfully
1828
+ content:
1829
+ application/json:
1830
+ schema:
1831
+ type: object
1832
+ properties:
1833
+ success:
1834
+ type: boolean
1835
+ enum:
1836
+ - true
1837
+ example: true
1838
+ 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'
1846
+ delete:
1847
+ summary: Delete a goal
1848
+ description: Delete a goal by ID
1849
+ operationId: deleteGoal
1850
+ tags:
1851
+ - Goals
1852
+ parameters:
1853
+ - name: id
1854
+ in: path
1855
+ required: true
1856
+ schema:
1857
+ type: integer
1858
+ minimum: 1
1859
+ responses:
1860
+ '200':
1861
+ description: Goal deleted successfully
1862
+ content:
1863
+ application/json:
1864
+ schema:
1865
+ type: object
1866
+ properties:
1867
+ success:
1868
+ type: boolean
1869
+ enum:
1870
+ - true
1871
+ example: true
1872
+ data:
1873
+ type: object
1874
+ properties:
1875
+ success:
1876
+ type: boolean
1877
+ enum:
1878
+ - true
1879
+ example: true
1880
+ '404':
1881
+ $ref: '#/components/responses/NotFound'
1882
+ '500':
1883
+ $ref: '#/components/responses/InternalError'
1884
+ /goals/bulk-delete:
1885
+ post:
1886
+ summary: Bulk delete goals
1887
+ description: Delete multiple goals at once by providing an array of IDs
1888
+ operationId: bulkDeleteGoals
1889
+ tags:
1890
+ - Goals
1891
+ requestBody:
1892
+ required: true
1893
+ content:
1894
+ application/json:
1895
+ schema:
1896
+ type: object
1897
+ properties:
1898
+ ids:
1899
+ type: array
1900
+ items:
1901
+ type: integer
1902
+ description: Array of goal IDs to delete
1903
+ example:
1904
+ - 1
1905
+ - 2
1906
+ - 3
1907
+ required:
1908
+ - ids
1909
+ responses:
1910
+ '200':
1911
+ description: Goals deleted successfully
1912
+ content:
1913
+ application/json:
1914
+ schema:
1915
+ type: object
1916
+ properties:
1917
+ success:
1918
+ type: boolean
1919
+ enum:
1920
+ - true
1921
+ example: true
1922
+ data:
1923
+ type: object
1924
+ properties:
1925
+ deleted:
1926
+ type: integer
1927
+ description: Number of goals deleted
1928
+ '400':
1929
+ $ref: '#/components/responses/ValidationError'
1930
+ '500':
1931
+ $ref: '#/components/responses/InternalError'
1932
+ /goals/progress:
1933
+ get:
1934
+ 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.
1938
+ operationId: getGoalProgress
1939
+ tags:
1940
+ - Goals
1941
+ responses:
1942
+ '200':
1943
+ description: Goal progress list
1944
+ content:
1945
+ application/json:
1946
+ schema:
1947
+ type: object
1948
+ properties:
1949
+ success:
1950
+ type: boolean
1951
+ enum:
1952
+ - true
1953
+ example: true
1954
+ data:
1955
+ type: object
1956
+ properties:
1957
+ data:
1958
+ type: array
1959
+ items:
1960
+ $ref: '#/components/schemas/GoalProgress'
1961
+ '500':
1962
+ $ref: '#/components/responses/InternalError'
1963
+ /goals/{id}/visibility:
1964
+ patch:
1965
+ summary: Toggle goal visibility
1966
+ description: Show or hide a goal
1967
+ operationId: toggleGoalVisibility
1968
+ tags:
1969
+ - Goals
1970
+ parameters:
1971
+ - name: id
1972
+ in: path
1973
+ required: true
1974
+ schema:
1975
+ type: integer
1976
+ minimum: 1
1977
+ requestBody:
1978
+ required: true
1979
+ content:
1980
+ application/json:
1981
+ schema:
1982
+ type: object
1983
+ properties:
1984
+ hide:
1985
+ type: boolean
1986
+ required:
1987
+ - hide
1988
+ responses:
1989
+ '200':
1990
+ description: Visibility updated
1991
+ content:
1992
+ application/json:
1993
+ schema:
1994
+ type: object
1995
+ properties:
1996
+ success:
1997
+ type: boolean
1998
+ enum:
1999
+ - true
2000
+ example: true
2001
+ data:
2002
+ $ref: '#/components/schemas/Goal'
2003
+ '404':
2004
+ $ref: '#/components/responses/NotFound'
2005
+ '500':
2006
+ $ref: '#/components/responses/InternalError'
2007
+ components:
2008
+ securitySchemes:
2009
+ BearerAuth:
2010
+ type: http
2011
+ scheme: bearer
2012
+ description: Personal Access Token (nbpat_...)
2013
+ parameters:
2014
+ RecordId:
2015
+ name: id
2016
+ in: path
2017
+ required: true
2018
+ description: Record ID
2019
+ schema:
2020
+ type: integer
2021
+ minimum: 1
2022
+ schemas:
2023
+ GoalProgress:
2024
+ type: object
2025
+ properties:
2026
+ goal_id:
2027
+ type: integer
2028
+ description: Goal ID
2029
+ tag_name:
2030
+ type: string
2031
+ description: Tag name associated with the goal
2032
+ tag_unit:
2033
+ type: string
2034
+ nullable: true
2035
+ description: Unit of the tag (e.g. kg, km)
2036
+ tag_data_type:
2037
+ type: string
2038
+ description: Data type of the tag (number, time, rank)
2039
+ virtual_tag_key:
2040
+ type: string
2041
+ nullable: true
2042
+ description: Virtual tag key if applicable (e.g. BodyWeight/BMI~)
2043
+ goal_value:
2044
+ type: number
2045
+ nullable: true
2046
+ description: Goal value for the goal
2047
+ direction:
2048
+ type: string
2049
+ enum:
2050
+ - above
2051
+ - below
2052
+ description: Direction of the goal (above = higher is better, below = lower is better)
2053
+ current:
2054
+ type: number
2055
+ nullable: true
2056
+ description: Current value computed from latest record
2057
+ met:
2058
+ type: boolean
2059
+ description: Whether the goal is currently met
2060
+ pct:
2061
+ type: integer
2062
+ description: Progress percentage (0-100)
2063
+ Record:
2064
+ type: object
2065
+ properties:
2066
+ id:
2067
+ type: integer
2068
+ description: Unique record ID
2069
+ name:
2070
+ type: string
2071
+ description: Record name/identifier
2072
+ value:
2073
+ type: number
2074
+ description: Numeric value
2075
+ unit:
2076
+ type: string
2077
+ 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.
2082
+ recorded_at:
2083
+ type: string
2084
+ format: date-time
2085
+ description: Timestamp when the value was recorded
2086
+ created_at:
2087
+ type: string
2088
+ format: date-time
2089
+ description: Timestamp when the record was created
2090
+ updated_at:
2091
+ type: string
2092
+ format: date-time
2093
+ description: Timestamp when the record was last updated
2094
+ owner_email:
2095
+ 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.
2100
+ owner_name:
2101
+ 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.
2105
+ required:
2106
+ - id
2107
+ - name
2108
+ - value
2109
+ - recorded_at
2110
+ - created_at
2111
+ - updated_at
2112
+ CreateRecordInput:
2113
+ type: object
2114
+ properties:
2115
+ name:
2116
+ type: string
2117
+ description: Record name/identifier
2118
+ example: temperature
2119
+ value:
2120
+ type: number
2121
+ description: Numeric value
2122
+ example: 25.5
2123
+ unit:
2124
+ 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.
2129
+ example: °C
2130
+ recorded_at:
2131
+ type: string
2132
+ format: date-time
2133
+ description: Timestamp when the value was recorded (defaults to current time)
2134
+ required:
2135
+ - name
2136
+ - value
2137
+ UpdateRecordInput:
2138
+ type: object
2139
+ properties:
2140
+ name:
2141
+ type: string
2142
+ description: Record name/identifier
2143
+ value:
2144
+ type: number
2145
+ description: Numeric value
2146
+ unit:
2147
+ 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.
2152
+ recorded_at:
2153
+ type: string
2154
+ format: date-time
2155
+ description: Timestamp when the value was recorded
2156
+ RecordStats:
2157
+ type: object
2158
+ properties:
2159
+ tag_id:
2160
+ type: integer
2161
+ description: Tag ID
2162
+ tag_name:
2163
+ type: string
2164
+ description: Tag name
2165
+ count:
2166
+ type: integer
2167
+ description: Total number of records
2168
+ min:
2169
+ type: number
2170
+ description: Minimum value
2171
+ min_recorded_at:
2172
+ type: string
2173
+ format: date-time
2174
+ nullable: true
2175
+ description: Timestamp when the minimum value was recorded
2176
+ max:
2177
+ type: number
2178
+ description: Maximum value
2179
+ max_recorded_at:
2180
+ type: string
2181
+ format: date-time
2182
+ nullable: true
2183
+ description: Timestamp when the maximum value was recorded
2184
+ average:
2185
+ type: number
2186
+ description: Average value
2187
+ latest:
2188
+ description: Latest record
2189
+ oneOf:
2190
+ - $ref: '#/components/schemas/Record'
2191
+ - type: object
2192
+ nullable: true
2193
+ required:
2194
+ - tag_id
2195
+ - tag_name
2196
+ - count
2197
+ - min
2198
+ - min_recorded_at
2199
+ - max
2200
+ - max_recorded_at
2201
+ - average
2202
+ - latest
2203
+ RecordResponse:
2204
+ type: object
2205
+ properties:
2206
+ success:
2207
+ type: boolean
2208
+ enum:
2209
+ - true
2210
+ example: true
2211
+ data:
2212
+ $ref: '#/components/schemas/Record'
2213
+ RecordListResponse:
2214
+ type: object
2215
+ properties:
2216
+ success:
2217
+ type: boolean
2218
+ enum:
2219
+ - true
2220
+ example: true
2221
+ data:
2222
+ type: object
2223
+ properties:
2224
+ data:
2225
+ type: array
2226
+ items:
2227
+ $ref: '#/components/schemas/Record'
2228
+ total:
2229
+ type: integer
2230
+ description: Total number of records matching the filter
2231
+ limit:
2232
+ type: integer
2233
+ description: Maximum number of records returned
2234
+ offset:
2235
+ type: integer
2236
+ description: Number of records skipped
2237
+ StatsResponse:
2238
+ type: object
2239
+ properties:
2240
+ success:
2241
+ type: boolean
2242
+ enum:
2243
+ - true
2244
+ example: true
2245
+ data:
2246
+ $ref: '#/components/schemas/RecordStats'
2247
+ ImportResponse:
2248
+ type: object
2249
+ properties:
2250
+ success:
2251
+ type: boolean
2252
+ enum:
2253
+ - true
2254
+ example: true
2255
+ data:
2256
+ type: object
2257
+ properties:
2258
+ imported:
2259
+ type: integer
2260
+ description: Total number of records processed
2261
+ example: 10
2262
+ created:
2263
+ type: integer
2264
+ description: Number of new records created
2265
+ example: 7
2266
+ updated:
2267
+ type: integer
2268
+ description: Number of existing records updated
2269
+ example: 3
2270
+ required:
2271
+ - imported
2272
+ - created
2273
+ - updated
2274
+ Tag:
2275
+ type: object
2276
+ properties:
2277
+ id:
2278
+ type: integer
2279
+ user_id:
2280
+ type: integer
2281
+ name:
2282
+ type: string
2283
+ unit:
2284
+ type: string
2285
+ 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.
2290
+ data_type:
2291
+ type: string
2292
+ enum:
2293
+ - binary
2294
+ - decimal
2295
+ - integer
2296
+ - time
2297
+ chart_type:
2298
+ type: string
2299
+ enum:
2300
+ - line
2301
+ - smooth
2302
+ - step
2303
+ is_hidden:
2304
+ type: integer
2305
+ description: 0 = visible, 1 = hidden
2306
+ data_type_options:
2307
+ 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.
2316
+ items:
2317
+ type: object
2318
+ properties:
2319
+ value:
2320
+ type: string
2321
+ enum:
2322
+ - binary
2323
+ - decimal
2324
+ - integer
2325
+ - time
2326
+ selectable:
2327
+ type: boolean
2328
+ reason:
2329
+ 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.
2338
+ enum:
2339
+ - SCALE_CHANGE
2340
+ - NON_INTEGER_VALUES
2341
+ - NON_BINARY_VALUES
2342
+ required:
2343
+ - value
2344
+ - selectable
2345
+ created_at:
2346
+ type: string
2347
+ format: date-time
2348
+ updated_at:
2349
+ type: string
2350
+ format: date-time
2351
+ owner_email:
2352
+ 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.
2357
+ owner_name:
2358
+ 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.
2362
+ required:
2363
+ - id
2364
+ - user_id
2365
+ - name
2366
+ - data_type
2367
+ - chart_type
2368
+ - created_at
2369
+ - updated_at
2370
+ Share:
2371
+ type: object
2372
+ properties:
2373
+ id:
2374
+ type: integer
2375
+ owner_user_id:
2376
+ type: integer
2377
+ shared_with_user_id:
2378
+ type: integer
2379
+ tag_id:
2380
+ type: integer
2381
+ nullable: true
2382
+ permission:
2383
+ type: string
2384
+ enum:
2385
+ - read
2386
+ - write
2387
+ is_hidden:
2388
+ type: integer
2389
+ status:
2390
+ type: string
2391
+ enum:
2392
+ - pending
2393
+ - accepted
2394
+ - 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.
2398
+ expires_at:
2399
+ type: string
2400
+ format: date-time
2401
+ nullable: true
2402
+ description: Expiration timestamp for a pending invitation. After this time the invitation can no longer be accepted.
2403
+ created_at:
2404
+ type: string
2405
+ format: date-time
2406
+ updated_at:
2407
+ type: string
2408
+ format: date-time
2409
+ owner_email:
2410
+ type: string
2411
+ description: Email of the sharing owner. Set on shares addressed to the caller.
2412
+ owner_name:
2413
+ type: string
2414
+ description: >-
2415
+ Display name the sharing owner set in their profile. Absent when
2416
+ they set none.
2417
+ shared_with_email:
2418
+ type: string
2419
+ description: Email of the recipient. Set on shares the caller owns.
2420
+ shared_with_name:
2421
+ 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.
2426
+ required:
2427
+ - id
2428
+ - owner_user_id
2429
+ - shared_with_user_id
2430
+ - permission
2431
+ - status
2432
+ - created_at
2433
+ - updated_at
2434
+ SharedGraph:
2435
+ type: object
2436
+ properties:
2437
+ id:
2438
+ type: string
2439
+ tag_id:
2440
+ type: integer
2441
+ description: Primary tag ID (first tag). For legacy compatibility.
2442
+ tag_ids:
2443
+ type: array
2444
+ items:
2445
+ type: integer
2446
+ description: All tag IDs included in this shared graph.
2447
+ tag_names:
2448
+ type: array
2449
+ items:
2450
+ type: string
2451
+ description: All tag names included in this shared graph.
2452
+ title:
2453
+ type: string
2454
+ description:
2455
+ type: string
2456
+ nullable: true
2457
+ is_active:
2458
+ type: integer
2459
+ description: 1 = active, 0 = inactive
2460
+ view_count:
2461
+ type: integer
2462
+ created_at:
2463
+ type: string
2464
+ format: date-time
2465
+ required:
2466
+ - id
2467
+ - tag_id
2468
+ - tag_ids
2469
+ - tag_names
2470
+ - title
2471
+ - is_active
2472
+ - view_count
2473
+ - created_at
2474
+ SharedGraphPublic:
2475
+ type: object
2476
+ properties:
2477
+ id:
2478
+ type: string
2479
+ title:
2480
+ type: string
2481
+ nullable: true
2482
+ description:
2483
+ type: string
2484
+ nullable: true
2485
+ chart_data:
2486
+ type: string
2487
+ created_at:
2488
+ type: string
2489
+ format: date-time
2490
+ required:
2491
+ - id
2492
+ - chart_data
2493
+ - created_at
2494
+ CreateSharedGraphInput:
2495
+ type: object
2496
+ properties:
2497
+ tag_ids:
2498
+ type: array
2499
+ items:
2500
+ type: integer
2501
+ description: Tag IDs to include in the graph. Multiple tags can be specified.
2502
+ tag_id:
2503
+ type: integer
2504
+ description: Single tag ID (legacy, deprecated). Use tag_ids instead.
2505
+ title:
2506
+ type: string
2507
+ description: Title of the shared graph.
2508
+ description:
2509
+ type: string
2510
+ nullable: true
2511
+ start_date:
2512
+ type: string
2513
+ format: date
2514
+ description: Start date filter (YYYY-MM-DD). Defaults to 30 days ago if omitted.
2515
+ end_date:
2516
+ type: string
2517
+ format: date
2518
+ description: End date filter (YYYY-MM-DD).
2519
+ include_comments:
2520
+ type: boolean
2521
+ default: false
2522
+ description: Whether to include record comments in the snapshot.
2523
+ include_media:
2524
+ type: boolean
2525
+ default: false
2526
+ description: Whether to include media links (signed URLs) in the snapshot.
2527
+ auto_scale:
2528
+ type: boolean
2529
+ default: false
2530
+ description: Y-axis scale mode. true = auto scale to data range, false = start from 0.
2531
+ show_goals:
2532
+ type: boolean
2533
+ default: true
2534
+ description: Whether to show goal lines on the graph.
2535
+ badges:
2536
+ type: array
2537
+ items:
2538
+ type: string
2539
+ description: Earned badge IDs to include in the shared graph.
2540
+ required:
2541
+ - tag_ids
2542
+ - title
2543
+ User:
2544
+ type: object
2545
+ properties:
2546
+ id:
2547
+ type: integer
2548
+ description: User ID
2549
+ email:
2550
+ type: string
2551
+ format: email
2552
+ description: User email address
2553
+ name:
2554
+ type: string
2555
+ nullable: true
2556
+ description: Display name
2557
+ language:
2558
+ type: string
2559
+ nullable: true
2560
+ description: Preferred language
2561
+ plan:
2562
+ 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.
2566
+ subscription_status:
2567
+ type: string
2568
+ nullable: true
2569
+ description: Subscription status for that subscription, if any
2570
+ required:
2571
+ - id
2572
+ - email
2573
+ Goal:
2574
+ type: object
2575
+ properties:
2576
+ id:
2577
+ type: integer
2578
+ user_id:
2579
+ type: integer
2580
+ tag_id:
2581
+ type: integer
2582
+ goal_type:
2583
+ type: string
2584
+ enum:
2585
+ - goal_value
2586
+ goal_value:
2587
+ type: number
2588
+ nullable: true
2589
+ direction:
2590
+ type: string
2591
+ enum:
2592
+ - above
2593
+ - below
2594
+ - null
2595
+ nullable: true
2596
+ start_date:
2597
+ type: string
2598
+ nullable: true
2599
+ end_date:
2600
+ type: string
2601
+ nullable: true
2602
+ comment:
2603
+ type: string
2604
+ nullable: true
2605
+ virtual_tag_key:
2606
+ type: string
2607
+ nullable: true
2608
+ is_hidden:
2609
+ type: integer
2610
+ description: 0 = visible, 1 = hidden
2611
+ tag_name:
2612
+ type: string
2613
+ tag_unit:
2614
+ type: string
2615
+ nullable: true
2616
+ created_at:
2617
+ type: string
2618
+ format: date-time
2619
+ updated_at:
2620
+ type: string
2621
+ format: date-time
2622
+ required:
2623
+ - id
2624
+ - user_id
2625
+ - tag_id
2626
+ - goal_type
2627
+ - is_hidden
2628
+ - tag_name
2629
+ - created_at
2630
+ - updated_at
2631
+ CreateGoalInput:
2632
+ type: object
2633
+ properties:
2634
+ tag_id:
2635
+ type: integer
2636
+ description: Tag ID to associate with the goal
2637
+ goal_type:
2638
+ type: string
2639
+ enum:
2640
+ - goal_value
2641
+ description: Type of goal
2642
+ goal_value:
2643
+ type: number
2644
+ description: Goal value (for goal_value type)
2645
+ direction:
2646
+ type: string
2647
+ enum:
2648
+ - above
2649
+ - below
2650
+ description: Direction for target value comparison
2651
+ start_date:
2652
+ type: string
2653
+ description: Goal start date
2654
+ end_date:
2655
+ type: string
2656
+ description: Goal end date
2657
+ comment:
2658
+ type: string
2659
+ description: Optional comment
2660
+ virtual_tag_key:
2661
+ type: string
2662
+ description: Virtual tag key
2663
+ required:
2664
+ - tag_id
2665
+ - goal_type
2666
+ UpdateGoalInput:
2667
+ type: object
2668
+ properties:
2669
+ tag_id:
2670
+ type: integer
2671
+ goal_type:
2672
+ type: string
2673
+ enum:
2674
+ - goal_value
2675
+ goal_value:
2676
+ type: number
2677
+ direction:
2678
+ type: string
2679
+ enum:
2680
+ - above
2681
+ - below
2682
+ start_date:
2683
+ type: string
2684
+ end_date:
2685
+ type: string
2686
+ comment:
2687
+ type: string
2688
+ virtual_tag_key:
2689
+ type: string
2690
+ ErrorResponse:
2691
+ type: object
2692
+ properties:
2693
+ success:
2694
+ type: boolean
2695
+ enum:
2696
+ - false
2697
+ example: false
2698
+ error:
2699
+ type: object
2700
+ properties:
2701
+ code:
2702
+ type: string
2703
+ description: Error code
2704
+ message:
2705
+ type: string
2706
+ description: Error message
2707
+ details:
2708
+ type: object
2709
+ description: Additional error details
2710
+ additionalProperties: true
2711
+ responses:
2712
+ ValidationError:
2713
+ description: Validation error
2714
+ content:
2715
+ application/json:
2716
+ schema:
2717
+ $ref: '#/components/schemas/ErrorResponse'
2718
+ example:
2719
+ success: false
2720
+ error:
2721
+ code: VALIDATION_ERROR
2722
+ message: Invalid input data
2723
+ NotFound:
2724
+ description: Resource not found
2725
+ content:
2726
+ application/json:
2727
+ schema:
2728
+ $ref: '#/components/schemas/ErrorResponse'
2729
+ example:
2730
+ success: false
2731
+ error:
2732
+ code: NOT_FOUND
2733
+ message: Record not found
2734
+ Unauthorized:
2735
+ description: Authentication required
2736
+ content:
2737
+ application/json:
2738
+ schema:
2739
+ $ref: '#/components/schemas/ErrorResponse'
2740
+ example:
2741
+ success: false
2742
+ error:
2743
+ code: AUTH_ERROR
2744
+ message: No authentication token provided
2745
+ InternalError:
2746
+ description: Internal server error
2747
+ content:
2748
+ application/json:
2749
+ schema:
2750
+ $ref: '#/components/schemas/ErrorResponse'
2751
+ example:
2752
+ success: false
2753
+ error:
2754
+ code: INTERNAL_ERROR
2755
+ message: An unexpected error occurred
2756
+ tags:
2757
+ - name: Health
2758
+ description: Health check endpoints
2759
+ - name: User
2760
+ description: User profile management
2761
+ - name: Records
2762
+ description: Time-series record management
2763
+ - name: Statistics
2764
+ description: Statistical analysis of records
2765
+ - name: Tags
2766
+ description: Tag management for organizing records
2767
+ - name: Shares
2768
+ description: Data sharing between users
2769
+ - name: Shared Graphs
2770
+ description: Public graph sharing for SNS
2771
+ - name: Media
2772
+ description: Media file upload and management
2773
+ - name: Goals
2774
+ description: Goal tracking and management