@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,1540 @@
1
+ openapi: 3.0.3
2
+ info:
3
+ title: NASEBANAL - Target API
4
+ description: >-
5
+ Target API for NASEBANAL. Provides hierarchical target management: users define goals and organize them in a tree
6
+ structure. Parent-child links are persisted via `targets.parent_target_id` (authoritative since migration 0017); a
7
+ legacy cell-based storage layer is kept in sync best-effort for backward compatibility. Targets can be shared with
8
+ other users (read/write, optionally including the subtree and linked Recorder data), and ownership of a personal
9
+ target subtree can be transferred to a recipient via an email-approved migration flow. Supports user authentication
10
+ via Auth0 JWT and Personal Access Tokens (PAT).
11
+ version: 1.4.1
12
+ contact:
13
+ name: NASEBANAL
14
+ url: https://www.nasebanal.com
15
+ servers:
16
+ - url: http://localhost:8790
17
+ description: Local dev (wrangler dev)
18
+ - url: https://api.target.nasebanal.com
19
+ description: Production
20
+ security:
21
+ - BearerAuth: []
22
+ paths:
23
+ /api/v1/health:
24
+ get:
25
+ operationId: getHealth
26
+ tags:
27
+ - Health
28
+ summary: Health check
29
+ description: Returns the health status of the API.
30
+ security: []
31
+ responses:
32
+ '200':
33
+ description: API is healthy
34
+ content:
35
+ application/json:
36
+ schema:
37
+ type: object
38
+ required:
39
+ - success
40
+ - data
41
+ properties:
42
+ success:
43
+ type: boolean
44
+ enum: [true]
45
+ data:
46
+ type: object
47
+ properties:
48
+ status:
49
+ type: string
50
+ example: ok
51
+ timestamp:
52
+ type: string
53
+ format: date-time
54
+ /api/v1/me:
55
+ get:
56
+ operationId: getCurrentUser
57
+ tags:
58
+ - User
59
+ summary: Get current user profile
60
+ description: Returns the authenticated user's profile information (resolved from the Account API).
61
+ responses:
62
+ '200':
63
+ description: User profile
64
+ content:
65
+ application/json:
66
+ schema:
67
+ $ref: '#/components/schemas/UserResponse'
68
+ '401':
69
+ $ref: '#/components/responses/Unauthorized'
70
+ /api/v1/targets:
71
+ get:
72
+ operationId: listTargets
73
+ tags:
74
+ - Targets
75
+ summary: List targets
76
+ description: >-
77
+ Returns all targets belonging to the authenticated user in the active namespace (personal, or the active
78
+ organization), ordered by last updated. By default each target includes its storage cells so clients do not
79
+ need to issue a follow-up GET /targets/{id} per row. Pass `fields=summary` to omit `cells` and reduce payload
80
+ size for callers (e.g. a tree/chart view) that only need `progress` — fetch full detail on demand via
81
+ GET /targets/{id}.
82
+ parameters:
83
+ - name: fields
84
+ in: query
85
+ required: false
86
+ schema:
87
+ type: string
88
+ enum:
89
+ - full
90
+ - summary
91
+ default: full
92
+ description: >-
93
+ `full` (default) returns every target's complete `cells` array — today's exact behavior, unchanged for
94
+ callers that omit this param. `summary` omits `cells`; `progress` is present in both modes.
95
+ responses:
96
+ '200':
97
+ description: List of targets with cells (or, with fields=summary, without cells)
98
+ content:
99
+ application/json:
100
+ schema:
101
+ type: object
102
+ required:
103
+ - success
104
+ - data
105
+ properties:
106
+ success:
107
+ type: boolean
108
+ enum: [true]
109
+ data:
110
+ type: object
111
+ required:
112
+ - data
113
+ - total
114
+ properties:
115
+ data:
116
+ type: array
117
+ items:
118
+ $ref: '#/components/schemas/TargetWithCells'
119
+ total:
120
+ type: integer
121
+ examples:
122
+ full:
123
+ summary: Default (fields omitted or fields=full)
124
+ value:
125
+ success: true
126
+ data:
127
+ data:
128
+ - id: tgt_a1b2c3d4
129
+ user_id: 42
130
+ created_by_user_id: 42
131
+ organization_id: null
132
+ parent_target_id: null
133
+ title: Learn Japanese
134
+ description: null
135
+ memo: null
136
+ recorder_tag: null
137
+ recorder_goal_id: null
138
+ progress: 40
139
+ own_progress: 40
140
+ created_at: '2026-05-14T12:00:00Z'
141
+ updated_at: '2026-05-14T12:00:00Z'
142
+ owner_email: user@example.com
143
+ owner_name: Sample User
144
+ created_by_email: user@example.com
145
+ created_by_name: Sample User
146
+ created_by_deleted: false
147
+ cells:
148
+ - id: 1
149
+ target_id: tgt_a1b2c3d4
150
+ position: 40
151
+ text: Learn Japanese
152
+ progress: 40
153
+ created_by_user_id: 42
154
+ created_at: '2026-05-14T12:00:00Z'
155
+ updated_at: '2026-05-14T12:00:00Z'
156
+ total: 1
157
+ summary:
158
+ summary: fields=summary
159
+ value:
160
+ success: true
161
+ data:
162
+ data:
163
+ - id: tgt_a1b2c3d4
164
+ user_id: 42
165
+ created_by_user_id: 42
166
+ organization_id: null
167
+ parent_target_id: null
168
+ title: Learn Japanese
169
+ description: null
170
+ memo: null
171
+ recorder_tag: null
172
+ recorder_goal_id: null
173
+ progress: 40
174
+ own_progress: 40
175
+ created_at: '2026-05-14T12:00:00Z'
176
+ updated_at: '2026-05-14T12:00:00Z'
177
+ owner_email: user@example.com
178
+ owner_name: Sample User
179
+ created_by_email: user@example.com
180
+ created_by_name: Sample User
181
+ created_by_deleted: false
182
+ total: 1
183
+ '401':
184
+ $ref: '#/components/responses/Unauthorized'
185
+ post:
186
+ operationId: createTarget
187
+ tags:
188
+ - Targets
189
+ summary: Create target
190
+ description: >-
191
+ Creates a new target. The storage cells are initialized with the target title pre-populated at position 40 (the
192
+ slot reserved for the target's own label). Optionally nests the new target under an existing parent.
193
+ requestBody:
194
+ required: true
195
+ content:
196
+ application/json:
197
+ schema:
198
+ $ref: '#/components/schemas/CreateTargetInput'
199
+ responses:
200
+ '201':
201
+ description: Target created with its storage cells
202
+ content:
203
+ application/json:
204
+ schema:
205
+ $ref: '#/components/schemas/TargetWithCellsResponse'
206
+ '400':
207
+ $ref: '#/components/responses/ValidationError'
208
+ '401':
209
+ $ref: '#/components/responses/Unauthorized'
210
+ '403':
211
+ $ref: '#/components/responses/Forbidden'
212
+ '404':
213
+ $ref: '#/components/responses/NotFound'
214
+ /api/v1/targets/{id}:
215
+ get:
216
+ operationId: getTarget
217
+ tags:
218
+ - Targets
219
+ summary: Get target
220
+ description: >-
221
+ Returns a single target with its storage cells. Accessible by the owner, by a user the target is shared with,
222
+ or by an admin of the organization that owns the target.
223
+ parameters:
224
+ - $ref: '#/components/parameters/TargetId'
225
+ responses:
226
+ '200':
227
+ description: Target with cells
228
+ content:
229
+ application/json:
230
+ schema:
231
+ $ref: '#/components/schemas/TargetWithCellsResponse'
232
+ '401':
233
+ $ref: '#/components/responses/Unauthorized'
234
+ '404':
235
+ $ref: '#/components/responses/NotFound'
236
+ put:
237
+ operationId: updateTarget
238
+ tags:
239
+ - Targets
240
+ summary: Update target
241
+ description: Updates a target's title, description, memo, and/or its Recorder tag binding.
242
+ parameters:
243
+ - $ref: '#/components/parameters/TargetId'
244
+ requestBody:
245
+ required: true
246
+ content:
247
+ application/json:
248
+ schema:
249
+ $ref: '#/components/schemas/UpdateTargetInput'
250
+ responses:
251
+ '200':
252
+ description: Target updated
253
+ content:
254
+ application/json:
255
+ schema:
256
+ type: object
257
+ required:
258
+ - success
259
+ - data
260
+ properties:
261
+ success:
262
+ type: boolean
263
+ enum: [true]
264
+ data:
265
+ $ref: '#/components/schemas/Target'
266
+ '400':
267
+ $ref: '#/components/responses/ValidationError'
268
+ '401':
269
+ $ref: '#/components/responses/Unauthorized'
270
+ '404':
271
+ $ref: '#/components/responses/NotFound'
272
+ delete:
273
+ operationId: deleteTarget
274
+ tags:
275
+ - Targets
276
+ summary: Delete target
277
+ description: Deletes a target and all its cells.
278
+ parameters:
279
+ - $ref: '#/components/parameters/TargetId'
280
+ responses:
281
+ '200':
282
+ description: Target deleted
283
+ content:
284
+ application/json:
285
+ schema:
286
+ $ref: '#/components/schemas/SuccessFlagResponse'
287
+ '401':
288
+ $ref: '#/components/responses/Unauthorized'
289
+ '404':
290
+ $ref: '#/components/responses/NotFound'
291
+ /api/v1/targets/{id}/cells/{position}:
292
+ put:
293
+ operationId: updateTargetCell
294
+ tags:
295
+ - Target Cells
296
+ summary: Update target cell
297
+ description: Updates the text and/or progress of a specific storage cell on a target.
298
+ parameters:
299
+ - $ref: '#/components/parameters/TargetId'
300
+ - name: position
301
+ in: path
302
+ required: true
303
+ schema:
304
+ type: integer
305
+ minimum: 0
306
+ maximum: 80
307
+ description: Cell position in the storage layout (0-80; position 40 is reserved for the target title)
308
+ requestBody:
309
+ required: true
310
+ content:
311
+ application/json:
312
+ schema:
313
+ $ref: '#/components/schemas/UpdateTargetCellInput'
314
+ responses:
315
+ '200':
316
+ description: Cell updated
317
+ content:
318
+ application/json:
319
+ schema:
320
+ type: object
321
+ required:
322
+ - success
323
+ - data
324
+ properties:
325
+ success:
326
+ type: boolean
327
+ enum: [true]
328
+ data:
329
+ $ref: '#/components/schemas/TargetCell'
330
+ '400':
331
+ $ref: '#/components/responses/ValidationError'
332
+ '401':
333
+ $ref: '#/components/responses/Unauthorized'
334
+ '404':
335
+ $ref: '#/components/responses/NotFound'
336
+ /api/v1/targets/{id}/parent:
337
+ put:
338
+ operationId: setTargetParent
339
+ tags:
340
+ - Targets
341
+ summary: Set target parent
342
+ description: >-
343
+ Authoritative tree-move endpoint (migration 0017+). Sets or clears `parent_target_id` on the target. Pass
344
+ `null` to detach (move to root). The legacy sub-goal cells are updated best-effort to keep older clients in
345
+ sync. Parent and child must share the same organization namespace and the same owner; cycles are rejected.
346
+ parameters:
347
+ - $ref: '#/components/parameters/TargetId'
348
+ requestBody:
349
+ required: true
350
+ content:
351
+ application/json:
352
+ schema:
353
+ $ref: '#/components/schemas/SetTargetParentInput'
354
+ responses:
355
+ '200':
356
+ description: Parent updated; returns the updated target
357
+ content:
358
+ application/json:
359
+ schema:
360
+ type: object
361
+ required:
362
+ - success
363
+ - data
364
+ properties:
365
+ success:
366
+ type: boolean
367
+ enum: [true]
368
+ data:
369
+ $ref: '#/components/schemas/Target'
370
+ '400':
371
+ description: Invalid move (self-parent, namespace mismatch, or cycle)
372
+ content:
373
+ application/json:
374
+ schema:
375
+ $ref: '#/components/schemas/Error'
376
+ '401':
377
+ $ref: '#/components/responses/Unauthorized'
378
+ '403':
379
+ $ref: '#/components/responses/Forbidden'
380
+ '404':
381
+ $ref: '#/components/responses/NotFound'
382
+ /api/v1/shared-targets:
383
+ get:
384
+ operationId: listSharedTargets
385
+ tags:
386
+ - Sharing
387
+ summary: List targets shared with me
388
+ description: >-
389
+ Returns targets that other users have shared with the authenticated user and accepted (not paused, not hidden),
390
+ scoped to the active namespace via each share's `shared_with_org_id`. When `include_descendants` was set on the
391
+ share, the whole subtree is expanded. Each row carries the sharing owner and the effective permission.
392
+ responses:
393
+ '200':
394
+ description: Shared targets
395
+ content:
396
+ application/json:
397
+ schema:
398
+ type: object
399
+ required:
400
+ - success
401
+ - data
402
+ properties:
403
+ success:
404
+ type: boolean
405
+ enum: [true]
406
+ data:
407
+ type: object
408
+ required:
409
+ - data
410
+ - total
411
+ properties:
412
+ data:
413
+ type: array
414
+ items:
415
+ $ref: '#/components/schemas/SharedTarget'
416
+ total:
417
+ type: integer
418
+ '401':
419
+ $ref: '#/components/responses/Unauthorized'
420
+ /api/v1/shares:
421
+ get:
422
+ operationId: listMyShares
423
+ tags:
424
+ - Sharing
425
+ summary: List shares I created
426
+ description: >-
427
+ Returns shares the authenticated user owns (i.e. shares of their own targets), scoped to the active namespace.
428
+ Each row is enriched with the recipient email, the owner's plan, and the owner-side target chain.
429
+ responses:
430
+ '200':
431
+ description: Shares owned by the caller
432
+ content:
433
+ application/json:
434
+ schema:
435
+ $ref: '#/components/schemas/ShareListResponse'
436
+ '401':
437
+ $ref: '#/components/responses/Unauthorized'
438
+ post:
439
+ operationId: createShare
440
+ tags:
441
+ - Sharing
442
+ summary: Create a share invitation
443
+ description: >-
444
+ Shares a target the caller owns with another user by email. Sends an invitation email containing an accept
445
+ link; the share is `pending` until accepted. Optionally includes the subtree (`include_descendants`) and the
446
+ linked Recorder tag data (`include_data`, which cascades matching tag shares to the Recorder API).
447
+ `share_context: organization` routes the share into the caller's active organization view and requires the
448
+ recipient to be a member of that organization.
449
+ requestBody:
450
+ required: true
451
+ content:
452
+ application/json:
453
+ schema:
454
+ $ref: '#/components/schemas/CreateShareInput'
455
+ responses:
456
+ '201':
457
+ description: Share created (pending acceptance)
458
+ content:
459
+ application/json:
460
+ schema:
461
+ type: object
462
+ required:
463
+ - success
464
+ - data
465
+ properties:
466
+ success:
467
+ type: boolean
468
+ enum: [true]
469
+ data:
470
+ $ref: '#/components/schemas/Share'
471
+ '400':
472
+ $ref: '#/components/responses/ValidationError'
473
+ '401':
474
+ $ref: '#/components/responses/Unauthorized'
475
+ '403':
476
+ description: Sharing not permitted (plan limit, or recipient not in organization)
477
+ content:
478
+ application/json:
479
+ schema:
480
+ $ref: '#/components/schemas/Error'
481
+ '404':
482
+ description: Target not found, or recipient email not registered
483
+ content:
484
+ application/json:
485
+ schema:
486
+ $ref: '#/components/schemas/Error'
487
+ '409':
488
+ $ref: '#/components/responses/Conflict'
489
+ /api/v1/shares/with-me:
490
+ get:
491
+ operationId: listSharesWithMe
492
+ tags:
493
+ - Sharing
494
+ summary: List shares addressed to me
495
+ description: >-
496
+ Returns shares where the authenticated user is the recipient, scoped to the active namespace. Filter by status
497
+ with the `status` query parameter. Each row is enriched with the owner email and the owner-side target chain.
498
+ parameters:
499
+ - name: status
500
+ in: query
501
+ required: false
502
+ schema:
503
+ type: string
504
+ enum: [accepted, pending, rejected, all]
505
+ default: accepted
506
+ description: Which share statuses to return (default `accepted`).
507
+ responses:
508
+ '200':
509
+ description: Shares addressed to the caller
510
+ content:
511
+ application/json:
512
+ schema:
513
+ $ref: '#/components/schemas/ShareListResponse'
514
+ '401':
515
+ $ref: '#/components/responses/Unauthorized'
516
+ /api/v1/shares/recipient-context-options:
517
+ get:
518
+ operationId: getRecipientContextOptions
519
+ tags:
520
+ - Sharing
521
+ summary: Resolve sharing-context options for a recipient
522
+ description: >-
523
+ Returns which sharing contexts the caller may offer for a given recipient email. Always includes `personal`;
524
+ additionally includes `organization` when the caller is acting under an organization context and the recipient
525
+ is a member of that same organization.
526
+ parameters:
527
+ - name: email
528
+ in: query
529
+ required: true
530
+ schema:
531
+ type: string
532
+ format: email
533
+ description: Recipient email to evaluate.
534
+ responses:
535
+ '200':
536
+ description: Available sharing contexts
537
+ content:
538
+ application/json:
539
+ schema:
540
+ type: object
541
+ required:
542
+ - success
543
+ - data
544
+ properties:
545
+ success:
546
+ type: boolean
547
+ enum: [true]
548
+ data:
549
+ type: object
550
+ required:
551
+ - options
552
+ properties:
553
+ options:
554
+ type: array
555
+ items:
556
+ type: string
557
+ enum: [personal, organization]
558
+ example: [personal, organization]
559
+ '400':
560
+ $ref: '#/components/responses/ValidationError'
561
+ '401':
562
+ $ref: '#/components/responses/Unauthorized'
563
+ /api/v1/shares/accept:
564
+ post:
565
+ operationId: acceptShareByToken
566
+ tags:
567
+ - Sharing
568
+ summary: Accept a share invitation by token
569
+ description: Accepts a pending share invitation using the token from the invitation email.
570
+ requestBody:
571
+ required: true
572
+ content:
573
+ application/json:
574
+ schema:
575
+ $ref: '#/components/schemas/TokenInput'
576
+ responses:
577
+ '200':
578
+ description: Share accepted (or already accepted)
579
+ content:
580
+ application/json:
581
+ schema:
582
+ $ref: '#/components/schemas/ShareStatusResponse'
583
+ '400':
584
+ description: Invitation expired or already declined
585
+ content:
586
+ application/json:
587
+ schema:
588
+ $ref: '#/components/schemas/Error'
589
+ '401':
590
+ $ref: '#/components/responses/Unauthorized'
591
+ '404':
592
+ $ref: '#/components/responses/NotFound'
593
+ /api/v1/shares/migrate/accept:
594
+ post:
595
+ operationId: acceptMigrationByToken
596
+ tags:
597
+ - Sharing
598
+ summary: Accept an ownership transfer by token
599
+ description: >-
600
+ Accepts a pending ownership-transfer (data migration) using the token from the migration approval email. On
601
+ success the target (and, if the share included descendants, its subtree) is transferred to the caller, and a
602
+ reverse read-only share is created so the original owner keeps access.
603
+ requestBody:
604
+ required: true
605
+ content:
606
+ application/json:
607
+ schema:
608
+ $ref: '#/components/schemas/TokenInput'
609
+ responses:
610
+ '200':
611
+ description: Ownership transferred
612
+ content:
613
+ application/json:
614
+ schema:
615
+ $ref: '#/components/schemas/MigrationStatusResponse'
616
+ '400':
617
+ description: Migration invitation no longer active or expired
618
+ content:
619
+ application/json:
620
+ schema:
621
+ $ref: '#/components/schemas/Error'
622
+ '401':
623
+ $ref: '#/components/responses/Unauthorized'
624
+ '404':
625
+ $ref: '#/components/responses/NotFound'
626
+ /api/v1/shares/{id}:
627
+ delete:
628
+ operationId: deleteShare
629
+ tags:
630
+ - Sharing
631
+ summary: Delete a share
632
+ description: >-
633
+ Removes a share the caller owns. If the share was created with `include_data`, the matching Recorder tag shares
634
+ are revoked unless `cascade_tag_shares=false` is passed. A share with a pending ownership transfer cannot be
635
+ deleted until the migration is cancelled.
636
+ parameters:
637
+ - $ref: '#/components/parameters/ShareId'
638
+ - name: cascade_tag_shares
639
+ in: query
640
+ required: false
641
+ schema:
642
+ type: string
643
+ enum: ['true', 'false']
644
+ default: 'true'
645
+ description: Set to `false` to keep the linked Recorder tag shares when deleting.
646
+ responses:
647
+ '200':
648
+ description: Share deleted
649
+ content:
650
+ application/json:
651
+ schema:
652
+ $ref: '#/components/schemas/SuccessFlagResponse'
653
+ '400':
654
+ $ref: '#/components/responses/ValidationError'
655
+ '401':
656
+ $ref: '#/components/responses/Unauthorized'
657
+ '404':
658
+ $ref: '#/components/responses/NotFound'
659
+ '409':
660
+ $ref: '#/components/responses/Conflict'
661
+ /api/v1/shares/{id}/accept:
662
+ post:
663
+ operationId: acceptShareById
664
+ tags:
665
+ - Sharing
666
+ summary: Accept a share invitation (in-app)
667
+ description: In-app counterpart to POST /shares/accept — accepts a pending share addressed to the caller by id.
668
+ parameters:
669
+ - $ref: '#/components/parameters/ShareId'
670
+ responses:
671
+ '200':
672
+ description: Share accepted (or already accepted)
673
+ content:
674
+ application/json:
675
+ schema:
676
+ $ref: '#/components/schemas/ShareStatusResponse'
677
+ '400':
678
+ description: Invitation expired or already declined
679
+ content:
680
+ application/json:
681
+ schema:
682
+ $ref: '#/components/schemas/Error'
683
+ '401':
684
+ $ref: '#/components/responses/Unauthorized'
685
+ '404':
686
+ $ref: '#/components/responses/NotFound'
687
+ /api/v1/shares/{id}/reject:
688
+ post:
689
+ operationId: rejectShare
690
+ tags:
691
+ - Sharing
692
+ summary: Reject a share invitation
693
+ description: Declines a pending share addressed to the caller. An already-accepted share cannot be rejected (delete it instead).
694
+ parameters:
695
+ - $ref: '#/components/parameters/ShareId'
696
+ responses:
697
+ '200':
698
+ description: Share rejected (or already rejected)
699
+ content:
700
+ application/json:
701
+ schema:
702
+ $ref: '#/components/schemas/ShareStatusResponse'
703
+ '400':
704
+ $ref: '#/components/responses/ValidationError'
705
+ '401':
706
+ $ref: '#/components/responses/Unauthorized'
707
+ '404':
708
+ $ref: '#/components/responses/NotFound'
709
+ /api/v1/shares/{id}/pause:
710
+ patch:
711
+ operationId: toggleSharePaused
712
+ tags:
713
+ - Sharing
714
+ summary: Pause or resume a share
715
+ description: Owner-only. Pauses or resumes a share without deleting it; a paused share is hidden from the recipient's shared-targets list.
716
+ parameters:
717
+ - $ref: '#/components/parameters/ShareId'
718
+ requestBody:
719
+ required: true
720
+ content:
721
+ application/json:
722
+ schema:
723
+ type: object
724
+ required:
725
+ - is_paused
726
+ properties:
727
+ is_paused:
728
+ type: boolean
729
+ responses:
730
+ '200':
731
+ description: Updated share row
732
+ content:
733
+ application/json:
734
+ schema:
735
+ type: object
736
+ required:
737
+ - success
738
+ - data
739
+ properties:
740
+ success:
741
+ type: boolean
742
+ enum: [true]
743
+ data:
744
+ $ref: '#/components/schemas/Share'
745
+ '401':
746
+ $ref: '#/components/responses/Unauthorized'
747
+ '404':
748
+ $ref: '#/components/responses/NotFound'
749
+ /api/v1/shares/{id}/visibility:
750
+ patch:
751
+ operationId: toggleShareVisibility
752
+ tags:
753
+ - Sharing
754
+ summary: Hide or unhide a share
755
+ description: Recipient-only. Hides or unhides a share from the caller's own shared-targets list.
756
+ parameters:
757
+ - $ref: '#/components/parameters/ShareId'
758
+ requestBody:
759
+ required: true
760
+ content:
761
+ application/json:
762
+ schema:
763
+ type: object
764
+ required:
765
+ - is_hidden
766
+ properties:
767
+ is_hidden:
768
+ type: boolean
769
+ responses:
770
+ '200':
771
+ description: Updated share row
772
+ content:
773
+ application/json:
774
+ schema:
775
+ type: object
776
+ required:
777
+ - success
778
+ - data
779
+ properties:
780
+ success:
781
+ type: boolean
782
+ enum: [true]
783
+ data:
784
+ $ref: '#/components/schemas/Share'
785
+ '401':
786
+ $ref: '#/components/responses/Unauthorized'
787
+ '404':
788
+ $ref: '#/components/responses/NotFound'
789
+ /api/v1/shares/{id}/transfer:
790
+ post:
791
+ operationId: transferOwnership
792
+ tags:
793
+ - Sharing
794
+ summary: Start an ownership transfer
795
+ description: >-
796
+ Owner-only. Begins transferring ownership of the shared target (subtree if the share included descendants) to
797
+ the recipient by emailing them an approval link. The share must already be accepted. Organization-owned targets
798
+ cannot be transferred. The transfer only completes when the recipient accepts.
799
+ parameters:
800
+ - $ref: '#/components/parameters/ShareId'
801
+ responses:
802
+ '200':
803
+ description: Migration invitation issued
804
+ content:
805
+ application/json:
806
+ schema:
807
+ type: object
808
+ required:
809
+ - success
810
+ - data
811
+ properties:
812
+ success:
813
+ type: boolean
814
+ enum: [true]
815
+ data:
816
+ type: object
817
+ required:
818
+ - id
819
+ - migration_status
820
+ properties:
821
+ id:
822
+ type: integer
823
+ migration_status:
824
+ type: string
825
+ enum: [pending]
826
+ expires_at:
827
+ type: string
828
+ format: date-time
829
+ '400':
830
+ $ref: '#/components/responses/ValidationError'
831
+ '401':
832
+ $ref: '#/components/responses/Unauthorized'
833
+ '403':
834
+ $ref: '#/components/responses/Forbidden'
835
+ '404':
836
+ $ref: '#/components/responses/NotFound'
837
+ '409':
838
+ $ref: '#/components/responses/Conflict'
839
+ /api/v1/shares/{id}/migrate/accept:
840
+ post:
841
+ operationId: acceptMigrationById
842
+ tags:
843
+ - Sharing
844
+ summary: Accept an ownership transfer (in-app)
845
+ description: In-app counterpart to POST /shares/migrate/accept — accepts a pending ownership transfer addressed to the caller by id.
846
+ parameters:
847
+ - $ref: '#/components/parameters/ShareId'
848
+ responses:
849
+ '200':
850
+ description: Ownership transferred
851
+ content:
852
+ application/json:
853
+ schema:
854
+ $ref: '#/components/schemas/MigrationStatusResponse'
855
+ '400':
856
+ description: Migration invitation no longer active or expired
857
+ content:
858
+ application/json:
859
+ schema:
860
+ $ref: '#/components/schemas/Error'
861
+ '401':
862
+ $ref: '#/components/responses/Unauthorized'
863
+ '404':
864
+ $ref: '#/components/responses/NotFound'
865
+ /api/v1/shares/{id}/migrate/reject:
866
+ post:
867
+ operationId: rejectMigration
868
+ tags:
869
+ - Sharing
870
+ summary: Reject an ownership transfer
871
+ description: Recipient-only. Declines a pending ownership transfer; the underlying share keeps its existing permission.
872
+ parameters:
873
+ - $ref: '#/components/parameters/ShareId'
874
+ responses:
875
+ '200':
876
+ description: Migration rejected
877
+ content:
878
+ application/json:
879
+ schema:
880
+ $ref: '#/components/schemas/MigrationStatusResponse'
881
+ '400':
882
+ $ref: '#/components/responses/ValidationError'
883
+ '401':
884
+ $ref: '#/components/responses/Unauthorized'
885
+ '404':
886
+ $ref: '#/components/responses/NotFound'
887
+ /api/v1/shares/{id}/migrate/cancel:
888
+ post:
889
+ operationId: cancelMigration
890
+ tags:
891
+ - Sharing
892
+ summary: Cancel an ownership transfer
893
+ description: Owner-only. Cancels an outstanding ownership-transfer invitation before the recipient acts.
894
+ parameters:
895
+ - $ref: '#/components/parameters/ShareId'
896
+ responses:
897
+ '200':
898
+ description: Migration cancelled
899
+ content:
900
+ application/json:
901
+ schema:
902
+ type: object
903
+ required:
904
+ - success
905
+ - data
906
+ properties:
907
+ success:
908
+ type: boolean
909
+ enum: [true]
910
+ data:
911
+ type: object
912
+ required:
913
+ - id
914
+ - migration_status
915
+ properties:
916
+ id:
917
+ type: integer
918
+ migration_status:
919
+ type: string
920
+ nullable: true
921
+ enum: [null]
922
+ description: Always null after a cancel — the in-flight migration is cleared.
923
+ '400':
924
+ $ref: '#/components/responses/ValidationError'
925
+ '401':
926
+ $ref: '#/components/responses/Unauthorized'
927
+ '404':
928
+ $ref: '#/components/responses/NotFound'
929
+ components:
930
+ securitySchemes:
931
+ BearerAuth:
932
+ type: http
933
+ scheme: bearer
934
+ bearerFormat: JWT or PAT
935
+ description: Use either an Auth0 JWT token or a Personal Access Token (PAT) starting with `nbpat_`.
936
+ parameters:
937
+ TargetId:
938
+ name: id
939
+ in: path
940
+ required: true
941
+ schema:
942
+ type: string
943
+ description: Target ID
944
+ ShareId:
945
+ name: id
946
+ in: path
947
+ required: true
948
+ schema:
949
+ type: integer
950
+ description: Share ID
951
+ schemas:
952
+ User:
953
+ type: object
954
+ required:
955
+ - id
956
+ - auth0_sub
957
+ - email
958
+ - name
959
+ - language
960
+ - plan
961
+ properties:
962
+ id:
963
+ type: integer
964
+ auth0_sub:
965
+ type: string
966
+ email:
967
+ type: string
968
+ format: email
969
+ name:
970
+ type: string
971
+ language:
972
+ type: string
973
+ enum:
974
+ - ja
975
+ - en
976
+ plan:
977
+ type: string
978
+ enum:
979
+ - free
980
+ - pro
981
+ - family
982
+ - enterprise
983
+ subscription_status:
984
+ type: string
985
+ enum:
986
+ - active
987
+ - canceled
988
+ - past_due
989
+ - trialing
990
+ - null
991
+ nullable: true
992
+ subscription_expires_at:
993
+ type: string
994
+ format: date-time
995
+ nullable: true
996
+ UserResponse:
997
+ type: object
998
+ required:
999
+ - success
1000
+ - data
1001
+ properties:
1002
+ success:
1003
+ type: boolean
1004
+ enum: [true]
1005
+ data:
1006
+ $ref: '#/components/schemas/User'
1007
+ Target:
1008
+ type: object
1009
+ properties:
1010
+ id:
1011
+ type: string
1012
+ description: Unique target ID
1013
+ user_id:
1014
+ type: integer
1015
+ description: Owner user id
1016
+ created_by_user_id:
1017
+ type: integer
1018
+ nullable: true
1019
+ description: User who created the target. Distinct from ownership; preserved across ownership transfer.
1020
+ organization_id:
1021
+ type: integer
1022
+ nullable: true
1023
+ description: >-
1024
+ Organization namespace. NULL = personal data. Non-null = created under an Enterprise org context; the org
1025
+ admin can read it and the member-creator owns it day-to-day but cannot transfer or delete it.
1026
+ parent_target_id:
1027
+ type: string
1028
+ nullable: true
1029
+ description: >-
1030
+ Direct parent reference (migration 0017+). Authoritative source of tree structure. NULL = root target.
1031
+ title:
1032
+ type: string
1033
+ description: Goal title
1034
+ maxLength: 200
1035
+ description:
1036
+ type: string
1037
+ nullable: true
1038
+ memo:
1039
+ type: string
1040
+ nullable: true
1041
+ recorder_tag:
1042
+ type: string
1043
+ nullable: true
1044
+ description: Optional tag linking to the Recorder app
1045
+ recorder_goal_id:
1046
+ type: integer
1047
+ nullable: true
1048
+ description: >-
1049
+ Recorder Goal id pinned at the moment the tag was bound. Progress for this target is computed against this
1050
+ specific goal so that later Goal period changes in Recorder do not silently re-route which goal is being
1051
+ tracked.
1052
+ created_at:
1053
+ type: string
1054
+ format: date-time
1055
+ updated_at:
1056
+ type: string
1057
+ format: date-time
1058
+ owner_email:
1059
+ type: string
1060
+ format: email
1061
+ description: Resolved owner email (present in response payloads).
1062
+ owner_name:
1063
+ type: string
1064
+ description: >-
1065
+ Display name the owner set in their profile. Absent when they set
1066
+ none. Clients name the owner by this, falling back to a masked email.
1067
+ created_by_email:
1068
+ type: string
1069
+ format: email
1070
+ description: >-
1071
+ Resolved creator email (present in response payloads). Absent when `created_by_deleted` is true — there is no
1072
+ creator left to name, and the owner must NOT be substituted.
1073
+ created_by_name:
1074
+ type: string
1075
+ description: >-
1076
+ Display name the creator set in their profile. Absent when they set
1077
+ none, or when `created_by_deleted` is true.
1078
+ created_by_deleted:
1079
+ type: boolean
1080
+ description: >-
1081
+ True when the author deleted their NASEBANAL account. The row itself survives because it belongs to this
1082
+ target's owner; only the authorship is gone. Clients render a "deleted user" label (JA: 退会済ユーザー) rather
1083
+ than falling back to `owner_email`, which would show the owner as the author of an edit they never made.
1084
+ TargetCell:
1085
+ type: object
1086
+ properties:
1087
+ id:
1088
+ type: integer
1089
+ target_id:
1090
+ type: string
1091
+ position:
1092
+ type: integer
1093
+ minimum: 0
1094
+ maximum: 80
1095
+ description: Cell position in the storage layout (0-80; position 40 is reserved for the target title)
1096
+ text:
1097
+ type: string
1098
+ maxLength: 500
1099
+ description: Cell content
1100
+ progress:
1101
+ type: integer
1102
+ minimum: 0
1103
+ maximum: 100
1104
+ description: Progress percentage
1105
+ created_by_user_id:
1106
+ type: integer
1107
+ nullable: true
1108
+ created_at:
1109
+ type: string
1110
+ format: date-time
1111
+ updated_at:
1112
+ type: string
1113
+ format: date-time
1114
+ TargetWithCells:
1115
+ allOf:
1116
+ - $ref: '#/components/schemas/Target'
1117
+ - type: object
1118
+ required:
1119
+ - progress
1120
+ - own_progress
1121
+ properties:
1122
+ cells:
1123
+ type: array
1124
+ items:
1125
+ $ref: '#/components/schemas/TargetCell'
1126
+ description: >-
1127
+ All storage cells ordered by position. Present when the list was requested without `fields`, or with
1128
+ `fields=full` (the default); omitted when requested with `fields=summary` — see `progress` and
1129
+ `own_progress` instead.
1130
+ progress:
1131
+ type: integer
1132
+ minimum: 0
1133
+ maximum: 100
1134
+ description: >-
1135
+ A target's own roll-up progress, computed server-side: the average of its children's (recursively
1136
+ rolled-up) progress when it has sub-goals, or its own storage cell (position 40, `own_progress`)
1137
+ when it's a leaf. Always present, regardless of `fields`.
1138
+ own_progress:
1139
+ type: integer
1140
+ minimum: 0
1141
+ maximum: 100
1142
+ description: >-
1143
+ The target's own storage cell (position 40) value, unrolled — what the "clear" toggle sets directly
1144
+ to 0 or 100, independent of any sub-goals. Distinct from `progress`: a target with sub-goals can
1145
+ have `progress = 100` (every child rolled up to 100) while `own_progress` stays whatever it was
1146
+ last explicitly set to, since a parent's own cell is never averaged into its own `progress`. Always
1147
+ present, regardless of `fields` — use this, not `progress`, to tell whether a target itself has
1148
+ been marked cleared.
1149
+ TargetWithCellsResponse:
1150
+ type: object
1151
+ required:
1152
+ - success
1153
+ - data
1154
+ properties:
1155
+ success:
1156
+ type: boolean
1157
+ enum: [true]
1158
+ data:
1159
+ $ref: '#/components/schemas/TargetWithCells'
1160
+ SharedTarget:
1161
+ allOf:
1162
+ - $ref: '#/components/schemas/TargetWithCells'
1163
+ - type: object
1164
+ properties:
1165
+ shared_by_user_id:
1166
+ type: integer
1167
+ description: User id of the sharing owner
1168
+ shared_by_email:
1169
+ type: string
1170
+ format: email
1171
+ shared_by_name:
1172
+ type: string
1173
+ description: Display name the sharing owner set in their profile. Absent when they set none.
1174
+ share_permission:
1175
+ type: string
1176
+ enum: [read, write]
1177
+ share_root_target_id:
1178
+ type: string
1179
+ description: The root target id of the share that grants access to this row
1180
+ share_show_embed:
1181
+ type: integer
1182
+ enum: [0, 1]
1183
+ description: Whether the share permits embedding the linked Recorder graph
1184
+ Share:
1185
+ type: object
1186
+ properties:
1187
+ id:
1188
+ type: integer
1189
+ owner_user_id:
1190
+ type: integer
1191
+ shared_with_user_id:
1192
+ type: integer
1193
+ target_id:
1194
+ type: string
1195
+ permission:
1196
+ type: string
1197
+ enum: [read, write]
1198
+ status:
1199
+ type: string
1200
+ enum: [pending, accepted, rejected]
1201
+ expires_at:
1202
+ type: string
1203
+ format: date-time
1204
+ nullable: true
1205
+ migration_status:
1206
+ type: string
1207
+ enum: [pending, null]
1208
+ nullable: true
1209
+ description: Ownership-transfer state. NULL = no migration in flight.
1210
+ migration_expires_at:
1211
+ type: string
1212
+ format: date-time
1213
+ nullable: true
1214
+ is_hidden:
1215
+ type: integer
1216
+ enum: [0, 1]
1217
+ is_paused:
1218
+ type: integer
1219
+ enum: [0, 1]
1220
+ show_embed:
1221
+ type: integer
1222
+ enum: [0, 1]
1223
+ include_descendants:
1224
+ type: integer
1225
+ enum: [0, 1]
1226
+ include_data:
1227
+ type: integer
1228
+ enum: [0, 1]
1229
+ parent_share_id:
1230
+ type: integer
1231
+ nullable: true
1232
+ created_at:
1233
+ type: string
1234
+ format: date-time
1235
+ updated_at:
1236
+ type: string
1237
+ format: date-time
1238
+ target_title:
1239
+ type: string
1240
+ description: Enriched — title of the shared target.
1241
+ target_chain:
1242
+ type: string
1243
+ description: Enriched — owner-side "Parent > Child" breadcrumb for the target.
1244
+ owner_email:
1245
+ type: string
1246
+ format: email
1247
+ description: Enriched — present when listing shares addressed to the caller.
1248
+ owner_name:
1249
+ type: string
1250
+ description: >-
1251
+ Display name the sharing owner set in their profile. Absent when they set none.
1252
+ shared_with_email:
1253
+ type: string
1254
+ format: email
1255
+ description: Enriched — present when listing shares the caller owns.
1256
+ shared_with_name:
1257
+ type: string
1258
+ description: >-
1259
+ Display name the recipient set in their profile. Set on shares the caller owns, and only once the share is
1260
+ `accepted`, so an invitation does not reveal the profile of someone who has not accepted it.
1261
+ owner_plan:
1262
+ type: string
1263
+ description: Enriched — the owner's plan, present when listing shares the caller owns.
1264
+ ShareListResponse:
1265
+ type: object
1266
+ required:
1267
+ - success
1268
+ - data
1269
+ properties:
1270
+ success:
1271
+ type: boolean
1272
+ enum: [true]
1273
+ data:
1274
+ type: object
1275
+ required:
1276
+ - data
1277
+ - total
1278
+ - limit
1279
+ - offset
1280
+ properties:
1281
+ data:
1282
+ type: array
1283
+ items:
1284
+ $ref: '#/components/schemas/Share'
1285
+ total:
1286
+ type: integer
1287
+ limit:
1288
+ type: integer
1289
+ offset:
1290
+ type: integer
1291
+ ShareStatusResponse:
1292
+ type: object
1293
+ required:
1294
+ - success
1295
+ - data
1296
+ properties:
1297
+ success:
1298
+ type: boolean
1299
+ enum: [true]
1300
+ data:
1301
+ type: object
1302
+ required:
1303
+ - id
1304
+ - status
1305
+ properties:
1306
+ id:
1307
+ type: integer
1308
+ status:
1309
+ type: string
1310
+ enum: [pending, accepted, rejected]
1311
+ already:
1312
+ type: boolean
1313
+ description: True when the share was already in the requested state.
1314
+ MigrationStatusResponse:
1315
+ type: object
1316
+ required:
1317
+ - success
1318
+ - data
1319
+ properties:
1320
+ success:
1321
+ type: boolean
1322
+ enum: [true]
1323
+ data:
1324
+ type: object
1325
+ required:
1326
+ - id
1327
+ - migration_status
1328
+ properties:
1329
+ id:
1330
+ type: integer
1331
+ migration_status:
1332
+ type: string
1333
+ enum: [accepted, rejected]
1334
+ SuccessFlagResponse:
1335
+ type: object
1336
+ required:
1337
+ - success
1338
+ - data
1339
+ properties:
1340
+ success:
1341
+ type: boolean
1342
+ enum: [true]
1343
+ data:
1344
+ type: object
1345
+ properties:
1346
+ success:
1347
+ type: boolean
1348
+ enum: [true]
1349
+ CreateTargetInput:
1350
+ type: object
1351
+ required:
1352
+ - title
1353
+ properties:
1354
+ title:
1355
+ type: string
1356
+ minLength: 1
1357
+ maxLength: 200
1358
+ example: Become a professional athlete
1359
+ description:
1360
+ type: string
1361
+ memo:
1362
+ type: string
1363
+ parent_id:
1364
+ type: string
1365
+ description: Optional parent target id to nest the new target under at creation time.
1366
+ UpdateTargetInput:
1367
+ type: object
1368
+ properties:
1369
+ title:
1370
+ type: string
1371
+ minLength: 1
1372
+ maxLength: 200
1373
+ description:
1374
+ type: string
1375
+ nullable: true
1376
+ memo:
1377
+ type: string
1378
+ nullable: true
1379
+ recorder_tag:
1380
+ type: string
1381
+ nullable: true
1382
+ description: Tag to link with Recorder app
1383
+ recorder_goal_id:
1384
+ type: integer
1385
+ nullable: true
1386
+ description: >-
1387
+ Pin a specific Recorder Goal to this target. Typically resolved by the client at bind time from the active
1388
+ goal for the selected tag.
1389
+ UpdateTargetCellInput:
1390
+ type: object
1391
+ properties:
1392
+ text:
1393
+ type: string
1394
+ maxLength: 500
1395
+ description: Cell content
1396
+ progress:
1397
+ type: integer
1398
+ minimum: 0
1399
+ maximum: 100
1400
+ description: Progress percentage
1401
+ SetTargetParentInput:
1402
+ type: object
1403
+ properties:
1404
+ parent_target_id:
1405
+ type: string
1406
+ nullable: true
1407
+ description: New parent target id, or null to detach to root.
1408
+ CreateShareInput:
1409
+ type: object
1410
+ required:
1411
+ - shared_with_email
1412
+ - target_id
1413
+ properties:
1414
+ shared_with_email:
1415
+ type: string
1416
+ format: email
1417
+ description: Email of the user to share with (must be a registered user).
1418
+ target_id:
1419
+ type: string
1420
+ permission:
1421
+ type: string
1422
+ enum: [read, write]
1423
+ default: read
1424
+ include_descendants:
1425
+ type: boolean
1426
+ default: false
1427
+ description: Share the whole subtree rooted at the target, not just the target itself.
1428
+ include_data:
1429
+ type: boolean
1430
+ default: false
1431
+ description: Also cascade-share the linked Recorder tag data so the recipient can view embedded graphs.
1432
+ share_context:
1433
+ type: string
1434
+ enum: [personal, organization]
1435
+ default: personal
1436
+ description: >-
1437
+ `organization` routes the share into the caller's active organization view; requires the recipient to be a
1438
+ member of that organization.
1439
+ extra_tag_names:
1440
+ type: array
1441
+ items:
1442
+ type: string
1443
+ description: Additional real Recorder tag names to include when cascading data shares.
1444
+ TokenInput:
1445
+ type: object
1446
+ required:
1447
+ - token
1448
+ properties:
1449
+ token:
1450
+ type: string
1451
+ description: The invitation token from the email link.
1452
+ Error:
1453
+ type: object
1454
+ required:
1455
+ - success
1456
+ - error
1457
+ properties:
1458
+ success:
1459
+ type: boolean
1460
+ enum: [false]
1461
+ error:
1462
+ type: object
1463
+ required:
1464
+ - code
1465
+ - message
1466
+ properties:
1467
+ code:
1468
+ type: string
1469
+ message:
1470
+ type: string
1471
+ details:
1472
+ type: object
1473
+ additionalProperties: true
1474
+ responses:
1475
+ Unauthorized:
1476
+ description: Authentication required or invalid token
1477
+ content:
1478
+ application/json:
1479
+ schema:
1480
+ $ref: '#/components/schemas/Error'
1481
+ example:
1482
+ success: false
1483
+ error:
1484
+ code: AUTH_ERROR
1485
+ message: No authentication token provided
1486
+ Forbidden:
1487
+ description: The caller is not allowed to perform this action
1488
+ content:
1489
+ application/json:
1490
+ schema:
1491
+ $ref: '#/components/schemas/Error'
1492
+ example:
1493
+ success: false
1494
+ error:
1495
+ code: FORBIDDEN
1496
+ message: Cannot transfer ownership of organization-owned target. Contact the organization admin.
1497
+ NotFound:
1498
+ description: Resource not found
1499
+ content:
1500
+ application/json:
1501
+ schema:
1502
+ $ref: '#/components/schemas/Error'
1503
+ example:
1504
+ success: false
1505
+ error:
1506
+ code: NOT_FOUND
1507
+ message: Target not found
1508
+ Conflict:
1509
+ description: The request conflicts with the current state of the resource
1510
+ content:
1511
+ application/json:
1512
+ schema:
1513
+ $ref: '#/components/schemas/Error'
1514
+ example:
1515
+ success: false
1516
+ error:
1517
+ code: CONFLICT
1518
+ message: 'Share already exists (status: pending)'
1519
+ ValidationError:
1520
+ description: Validation error
1521
+ content:
1522
+ application/json:
1523
+ schema:
1524
+ $ref: '#/components/schemas/Error'
1525
+ example:
1526
+ success: false
1527
+ error:
1528
+ code: VALIDATION_ERROR
1529
+ message: Title is required
1530
+ tags:
1531
+ - name: Health
1532
+ description: Health check endpoints
1533
+ - name: User
1534
+ description: User profile endpoints
1535
+ - name: Targets
1536
+ description: Target management
1537
+ - name: Target Cells
1538
+ description: Individual storage cell management on a target
1539
+ - name: Sharing
1540
+ description: Sharing targets with other users and transferring ownership