@medalsocial/sdk 1.6.0 → 1.8.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.
@@ -21,6 +21,16 @@ tags:
21
21
  description: Manage workspace CRM contacts.
22
22
  - name: Deals
23
23
  description: Manage sponsorship and brand deals.
24
+ - name: Bookings
25
+ description: >-
26
+ Appointment bookings — the service catalogue, free slots, staff actions
27
+ on a booking, and the customer-facing manage-token routes. Money is
28
+ always integer øre.
29
+ - name: Portal
30
+ description: >-
31
+ Customer self-service portal — e-mail one-time-code login, then
32
+ session-bound access to the signed-in contact's own profile, bookings,
33
+ data export and erasure. Session routes carry `X-Portal-Session`.
24
34
  - name: GDPR
25
35
  description: Manage exports, consent records, and cookie consent.
26
36
  - name: Workspaces
@@ -543,6 +553,534 @@ paths:
543
553
  $ref: "#/components/schemas/ApiResponse_DealRemoveResult"
544
554
  default:
545
555
  $ref: "#/components/responses/ApiError"
556
+ /api/v1/bookings:
557
+ get:
558
+ tags: [Bookings]
559
+ operationId: listBookings
560
+ summary: List bookings
561
+ parameters:
562
+ - $ref: "#/components/parameters/Limit"
563
+ - $ref: "#/components/parameters/Cursor"
564
+ - name: from_ts
565
+ in: query
566
+ description: Only bookings starting at or after this instant.
567
+ schema:
568
+ $ref: "#/components/schemas/BookingTimestampInput"
569
+ - name: to_ts
570
+ in: query
571
+ description: Only bookings starting before this instant.
572
+ schema:
573
+ $ref: "#/components/schemas/BookingTimestampInput"
574
+ - name: status
575
+ in: query
576
+ schema:
577
+ $ref: "#/components/schemas/BookingStatus"
578
+ - name: resource_id
579
+ in: query
580
+ schema:
581
+ type: string
582
+ responses:
583
+ "200":
584
+ description: Bookings page.
585
+ content:
586
+ application/json:
587
+ schema:
588
+ $ref: "#/components/schemas/PaginatedResponse_Booking"
589
+ default:
590
+ $ref: "#/components/responses/ApiError"
591
+ post:
592
+ tags: [Bookings]
593
+ operationId: createBooking
594
+ summary: Create a booking or party
595
+ description: >-
596
+ Books every item or none of them. Each created booking carries a
597
+ `manage_token` returned exactly once — only its hash is stored, and an
598
+ idempotent replay omits it.
599
+ requestBody:
600
+ required: true
601
+ content:
602
+ application/json:
603
+ schema:
604
+ $ref: "#/components/schemas/CreateBookingInput"
605
+ responses:
606
+ "201":
607
+ description: Created bookings, in request order.
608
+ content:
609
+ application/json:
610
+ schema:
611
+ $ref: "#/components/schemas/ApiResponse_BookingCreateResult"
612
+ default:
613
+ $ref: "#/components/responses/ApiError"
614
+ /api/v1/bookings/services:
615
+ get:
616
+ tags: [Bookings]
617
+ operationId: listBookingServices
618
+ summary: List bookable services
619
+ parameters:
620
+ - name: include_inactive
621
+ in: query
622
+ description: >-
623
+ Include services with `active: false`. Defaults to active-only.
624
+ schema:
625
+ type: boolean
626
+ responses:
627
+ "200":
628
+ description: Bookable service catalogue.
629
+ content:
630
+ application/json:
631
+ schema:
632
+ $ref: "#/components/schemas/ApiResponse_BookingServiceArray"
633
+ default:
634
+ $ref: "#/components/responses/ApiError"
635
+ /api/v1/bookings/resources:
636
+ get:
637
+ tags: [Bookings]
638
+ operationId: listBookingResources
639
+ summary: List bookable resources
640
+ responses:
641
+ "200":
642
+ description: Bookable resources — staff, rooms, and equipment.
643
+ content:
644
+ application/json:
645
+ schema:
646
+ $ref: "#/components/schemas/ApiResponse_BookingResourceArray"
647
+ default:
648
+ $ref: "#/components/responses/ApiError"
649
+ /api/v1/bookings/availability:
650
+ get:
651
+ tags: [Bookings]
652
+ operationId: listBookingAvailability
653
+ summary: List free slots for a service
654
+ description: >-
655
+ Slots reflect opening hours, time off, buffers, and existing bookings
656
+ at the moment of the call. They are not held.
657
+ parameters:
658
+ - name: service_id
659
+ in: query
660
+ required: true
661
+ schema:
662
+ type: string
663
+ - name: from_ts
664
+ in: query
665
+ required: true
666
+ description: Start of the window.
667
+ schema:
668
+ $ref: "#/components/schemas/BookingTimestampInput"
669
+ - name: to_ts
670
+ in: query
671
+ required: true
672
+ description: End of the window; must be after `from_ts`.
673
+ schema:
674
+ $ref: "#/components/schemas/BookingTimestampInput"
675
+ - name: resource_id
676
+ in: query
677
+ description: Restrict slots to one resource.
678
+ schema:
679
+ type: string
680
+ responses:
681
+ "200":
682
+ description: Free slots.
683
+ content:
684
+ application/json:
685
+ schema:
686
+ $ref: "#/components/schemas/ApiResponse_BookingSlotArray"
687
+ default:
688
+ $ref: "#/components/responses/ApiError"
689
+ /api/v1/bookings/schedule:
690
+ get:
691
+ tags: [Bookings]
692
+ operationId: listBookingSchedule
693
+ summary: List the dates a service can be booked on
694
+ description: >-
695
+ The other half of `availability`. Availability returns free slots and
696
+ nothing else, so a closed day, an evening past closing and a fully
697
+ booked day all come back as the same empty array. This returns one
698
+ entry per date the workspace keeps opening hours on; a date ABSENT from
699
+ the response is closed. `last_start_ts` is the last start this service
700
+ could occupy on that date (it depends on the service's duration and
701
+ buffers, not just the closing time) and is `null` for a date with
702
+ posted hours that is shut outright, such as a public holiday.
703
+ parameters:
704
+ - name: service_id
705
+ in: query
706
+ required: true
707
+ schema:
708
+ type: string
709
+ - name: from_ts
710
+ in: query
711
+ required: true
712
+ description: Start of the window.
713
+ schema:
714
+ $ref: "#/components/schemas/BookingTimestampInput"
715
+ - name: to_ts
716
+ in: query
717
+ required: true
718
+ description: End of the window; must be after `from_ts`.
719
+ schema:
720
+ $ref: "#/components/schemas/BookingTimestampInput"
721
+ - name: resource_id
722
+ in: query
723
+ description: Restrict to one resource's hours.
724
+ schema:
725
+ type: string
726
+ responses:
727
+ "200":
728
+ description: Open dates.
729
+ content:
730
+ application/json:
731
+ schema:
732
+ $ref: "#/components/schemas/ApiResponse_BookingScheduleDayArray"
733
+ default:
734
+ $ref: "#/components/responses/ApiError"
735
+ /api/v1/bookings/{id}:
736
+ parameters:
737
+ - $ref: "#/components/parameters/Id"
738
+ get:
739
+ tags: [Bookings]
740
+ operationId: getBooking
741
+ summary: Get a booking
742
+ responses:
743
+ "200":
744
+ description: Booking.
745
+ content:
746
+ application/json:
747
+ schema:
748
+ $ref: "#/components/schemas/ApiResponse_Booking"
749
+ default:
750
+ $ref: "#/components/responses/ApiError"
751
+ patch:
752
+ tags: [Bookings]
753
+ operationId: updateBooking
754
+ summary: Annotate a booking
755
+ requestBody:
756
+ required: true
757
+ content:
758
+ application/json:
759
+ schema:
760
+ $ref: "#/components/schemas/UpdateBookingInput"
761
+ responses:
762
+ "200":
763
+ description: The updated booking.
764
+ content:
765
+ application/json:
766
+ schema:
767
+ $ref: "#/components/schemas/ApiResponse_Booking"
768
+ default:
769
+ $ref: "#/components/responses/ApiError"
770
+ /api/v1/bookings/{id}/cancel:
771
+ parameters:
772
+ - $ref: "#/components/parameters/Id"
773
+ post:
774
+ tags: [Bookings]
775
+ operationId: cancelBooking
776
+ summary: Cancel a booking as the business
777
+ description: >-
778
+ Staff semantics — the workspace's cancel window is bypassed and the
779
+ cancellation is attributed to staff.
780
+ requestBody:
781
+ required: true
782
+ content:
783
+ application/json:
784
+ schema:
785
+ $ref: "#/components/schemas/CancelBookingInput"
786
+ responses:
787
+ "200":
788
+ description: Cancel result.
789
+ content:
790
+ application/json:
791
+ schema:
792
+ $ref: "#/components/schemas/ApiResponse_BookingActionResult"
793
+ default:
794
+ $ref: "#/components/responses/ApiError"
795
+ /api/v1/bookings/{id}/reschedule:
796
+ parameters:
797
+ - $ref: "#/components/parameters/Id"
798
+ post:
799
+ tags: [Bookings]
800
+ operationId: rescheduleBooking
801
+ summary: Reschedule a booking as the business
802
+ description: >-
803
+ Staff semantics — the reschedule window is bypassed. The old booking is
804
+ cancelled and a new one inserted, so the response carries a new booking
805
+ id and a freshly minted manage token.
806
+ requestBody:
807
+ required: true
808
+ content:
809
+ application/json:
810
+ schema:
811
+ $ref: "#/components/schemas/RescheduleBookingInput"
812
+ responses:
813
+ "200":
814
+ description: Reschedule result.
815
+ content:
816
+ application/json:
817
+ schema:
818
+ $ref: "#/components/schemas/ApiResponse_BookingRescheduleResult"
819
+ default:
820
+ $ref: "#/components/responses/ApiError"
821
+ /api/v1/bookings/{id}/no-show:
822
+ parameters:
823
+ - $ref: "#/components/parameters/Id"
824
+ post:
825
+ tags: [Bookings]
826
+ operationId: markBookingNoShow
827
+ summary: Mark a booking as a no-show
828
+ responses:
829
+ "200":
830
+ description: No-show result.
831
+ content:
832
+ application/json:
833
+ schema:
834
+ $ref: "#/components/schemas/ApiResponse_BookingActionResult"
835
+ default:
836
+ $ref: "#/components/responses/ApiError"
837
+ /api/v1/bookings/manage/{token}:
838
+ parameters:
839
+ - $ref: "#/components/parameters/ManageToken"
840
+ get:
841
+ tags: [Bookings]
842
+ operationId: getManagedBooking
843
+ summary: Get the customer manage summary for a token
844
+ responses:
845
+ "200":
846
+ description: >-
847
+ Manage summary. An unknown token and a token belonging to another
848
+ workspace both answer 404, so this is not an existence oracle.
849
+ content:
850
+ application/json:
851
+ schema:
852
+ $ref: "#/components/schemas/ApiResponse_ManageSummary"
853
+ default:
854
+ $ref: "#/components/responses/ApiError"
855
+ /api/v1/bookings/manage/{token}/cancel:
856
+ parameters:
857
+ - $ref: "#/components/parameters/ManageToken"
858
+ post:
859
+ tags: [Bookings]
860
+ operationId: cancelManagedBooking
861
+ summary: Cancel on the customer's behalf
862
+ description: >-
863
+ Customer semantics — the workspace's cancel window is ENFORCED and the
864
+ cancellation is attributed to the customer.
865
+ requestBody:
866
+ required: true
867
+ content:
868
+ application/json:
869
+ schema:
870
+ $ref: "#/components/schemas/CancelBookingInput"
871
+ responses:
872
+ "200":
873
+ description: Cancel result.
874
+ content:
875
+ application/json:
876
+ schema:
877
+ $ref: "#/components/schemas/ApiResponse_BookingActionResult"
878
+ default:
879
+ $ref: "#/components/responses/ApiError"
880
+ /api/v1/bookings/manage/{token}/reschedule:
881
+ parameters:
882
+ - $ref: "#/components/parameters/ManageToken"
883
+ post:
884
+ tags: [Bookings]
885
+ operationId: rescheduleManagedBooking
886
+ summary: Reschedule on the customer's behalf
887
+ description: >-
888
+ Customer semantics — the reschedule window is ENFORCED. The response
889
+ carries a new booking id and a new manage token; the old token stops
890
+ working.
891
+ requestBody:
892
+ required: true
893
+ content:
894
+ application/json:
895
+ schema:
896
+ $ref: "#/components/schemas/RescheduleBookingInput"
897
+ responses:
898
+ "200":
899
+ description: Reschedule result.
900
+ content:
901
+ application/json:
902
+ schema:
903
+ $ref: "#/components/schemas/ApiResponse_BookingRescheduleResult"
904
+ default:
905
+ $ref: "#/components/responses/ApiError"
906
+ /api/v1/portal/login/start:
907
+ post:
908
+ tags: [Portal]
909
+ operationId: startPortalLogin
910
+ summary: E-mail a one-time login code
911
+ description: >-
912
+ Always answers 202 `{ status: "sent" }`, whether or not the address
913
+ belongs to a contact — enumeration-safe by design. Rate-limited per
914
+ address and per caller (`429 RATE_LIMITED`). Not idempotency-keyed.
915
+ Errors: `400 VALIDATION_ERROR`, `403 FORBIDDEN` (key lacks
916
+ `write:portal`), `429 RATE_LIMITED`.
917
+ requestBody:
918
+ required: true
919
+ content:
920
+ application/json:
921
+ schema:
922
+ $ref: "#/components/schemas/PortalLoginStartInput"
923
+ responses:
924
+ "202":
925
+ description: Code queued (or silently not, for an unknown address).
926
+ content:
927
+ application/json:
928
+ schema:
929
+ $ref: "#/components/schemas/ApiResponse_PortalLoginStartResult"
930
+ default:
931
+ $ref: "#/components/responses/ApiError"
932
+ /api/v1/portal/login/verify:
933
+ post:
934
+ tags: [Portal]
935
+ operationId: verifyPortalLogin
936
+ summary: Exchange the e-mailed code for a session
937
+ description: >-
938
+ A wrong, burned or expired code all answer `401 PORTAL_CODE_INVALID` —
939
+ the three are not distinguished, so the response is not an oracle for
940
+ which codes exist. The returned `session_token` is a bearer credential
941
+ for ONE contact: keep it in an HttpOnly cookie on the site's server and
942
+ never hand it to the browser. Not idempotency-keyed. Errors:
943
+ `400 VALIDATION_ERROR`, `401 PORTAL_CODE_INVALID`, `403 FORBIDDEN`,
944
+ `429 RATE_LIMITED`.
945
+ requestBody:
946
+ required: true
947
+ content:
948
+ application/json:
949
+ schema:
950
+ $ref: "#/components/schemas/PortalVerifyInput"
951
+ responses:
952
+ "200":
953
+ description: The new session.
954
+ content:
955
+ application/json:
956
+ schema:
957
+ $ref: "#/components/schemas/ApiResponse_PortalSession"
958
+ default:
959
+ $ref: "#/components/responses/ApiError"
960
+ /api/v1/portal/logout:
961
+ parameters:
962
+ - $ref: "#/components/parameters/PortalSession"
963
+ post:
964
+ tags: [Portal]
965
+ operationId: logoutPortal
966
+ summary: Revoke the session
967
+ description: >-
968
+ Revoking twice reaches the same state — the second call answers
969
+ `401 PORTAL_SESSION_INVALID`. Not idempotency-keyed. Errors:
970
+ `401 PORTAL_SESSION_REQUIRED` (header missing),
971
+ `401 PORTAL_SESSION_INVALID` (unknown, expired or revoked),
972
+ `403 FORBIDDEN`, `429 RATE_LIMITED`.
973
+ responses:
974
+ "204":
975
+ description: Session revoked.
976
+ default:
977
+ $ref: "#/components/responses/ApiError"
978
+ /api/v1/portal/me:
979
+ parameters:
980
+ - $ref: "#/components/parameters/PortalSession"
981
+ get:
982
+ tags: [Portal]
983
+ operationId: getPortalProfile
984
+ summary: The signed-in contact's profile
985
+ description: >-
986
+ Errors: `401 PORTAL_SESSION_REQUIRED`, `401 PORTAL_SESSION_INVALID`,
987
+ `403 FORBIDDEN` (key lacks `read:portal`), `429 RATE_LIMITED`.
988
+ responses:
989
+ "200":
990
+ description: Profile.
991
+ content:
992
+ application/json:
993
+ schema:
994
+ $ref: "#/components/schemas/ApiResponse_PortalProfile"
995
+ default:
996
+ $ref: "#/components/responses/ApiError"
997
+ patch:
998
+ tags: [Portal]
999
+ operationId: updatePortalProfile
1000
+ summary: Update the signed-in contact's profile
1001
+ description: >-
1002
+ Only the supplied fields change. `phone: null` clears the number,
1003
+ `family` replaces the whole list, and `marketing_consent` records a
1004
+ `marketing_email` consent decision with source `portal`. Answers the
1005
+ profile as it is after the change. Errors: `400 VALIDATION_ERROR`,
1006
+ `401 PORTAL_SESSION_REQUIRED`, `401 PORTAL_SESSION_INVALID`,
1007
+ `403 FORBIDDEN` (key lacks `write:portal`), `429 RATE_LIMITED`.
1008
+ requestBody:
1009
+ required: true
1010
+ content:
1011
+ application/json:
1012
+ schema:
1013
+ $ref: "#/components/schemas/PortalProfilePatch"
1014
+ responses:
1015
+ "200":
1016
+ description: Updated profile.
1017
+ content:
1018
+ application/json:
1019
+ schema:
1020
+ $ref: "#/components/schemas/ApiResponse_PortalProfile"
1021
+ default:
1022
+ $ref: "#/components/responses/ApiError"
1023
+ /api/v1/portal/me/bookings:
1024
+ parameters:
1025
+ - $ref: "#/components/parameters/PortalSession"
1026
+ get:
1027
+ tags: [Portal]
1028
+ operationId: listPortalBookings
1029
+ summary: The signed-in contact's bookings
1030
+ description: >-
1031
+ Split into `upcoming` and `past`. An upcoming booking still inside the
1032
+ workspace's policy windows carries `manage_token` and
1033
+ `can_manage: true`; the token opens the site's manage page. Errors:
1034
+ `401 PORTAL_SESSION_REQUIRED`, `401 PORTAL_SESSION_INVALID`,
1035
+ `403 FORBIDDEN`, `429 RATE_LIMITED`.
1036
+ responses:
1037
+ "200":
1038
+ description: Bookings.
1039
+ content:
1040
+ application/json:
1041
+ schema:
1042
+ $ref: "#/components/schemas/ApiResponse_PortalBookings"
1043
+ default:
1044
+ $ref: "#/components/responses/ApiError"
1045
+ /api/v1/portal/me/export:
1046
+ parameters:
1047
+ - $ref: "#/components/parameters/PortalSession"
1048
+ post:
1049
+ tags: [Portal]
1050
+ operationId: exportPortalData
1051
+ summary: Export everything held about the signed-in contact
1052
+ description: >-
1053
+ A synchronous GDPR Art. 15 export of the contact's profile, family,
1054
+ consents and bookings, as one JSON document. Read-only, so not
1055
+ idempotency-keyed. Errors: `401 PORTAL_SESSION_REQUIRED`,
1056
+ `401 PORTAL_SESSION_INVALID`, `403 FORBIDDEN`, `429 RATE_LIMITED`.
1057
+ responses:
1058
+ "200":
1059
+ description: The export.
1060
+ content:
1061
+ application/json:
1062
+ schema:
1063
+ $ref: "#/components/schemas/ApiResponse_PortalExport"
1064
+ default:
1065
+ $ref: "#/components/responses/ApiError"
1066
+ /api/v1/portal/me/delete:
1067
+ parameters:
1068
+ - $ref: "#/components/parameters/PortalSession"
1069
+ post:
1070
+ tags: [Portal]
1071
+ operationId: deletePortalAccount
1072
+ summary: Erase the signed-in contact
1073
+ description: >-
1074
+ GDPR Art. 17 erasure. The session is revoked as part of the deletion,
1075
+ so a retry answers `401 PORTAL_SESSION_INVALID` rather than deleting
1076
+ anything else. Not idempotency-keyed. Errors:
1077
+ `401 PORTAL_SESSION_REQUIRED`, `401 PORTAL_SESSION_INVALID`,
1078
+ `403 FORBIDDEN` (key lacks `write:portal`), `429 RATE_LIMITED`.
1079
+ responses:
1080
+ "204":
1081
+ description: Contact erased and session revoked.
1082
+ default:
1083
+ $ref: "#/components/responses/ApiError"
546
1084
  /api/v1/gdpr/export:
547
1085
  post:
548
1086
  tags: [GDPR]
@@ -1177,6 +1715,27 @@ components:
1177
1715
  required: true
1178
1716
  schema:
1179
1717
  type: string
1718
+ ManageToken:
1719
+ name: token
1720
+ in: path
1721
+ required: true
1722
+ description: >-
1723
+ The show-once manage token handed out when the booking was created.
1724
+ Possession of it authorizes the customer's own cancel or reschedule.
1725
+ schema:
1726
+ type: string
1727
+ PortalSession:
1728
+ name: X-Portal-Session
1729
+ in: header
1730
+ required: true
1731
+ description: >-
1732
+ The `session_token` returned by `verifyPortalLogin`. A bearer
1733
+ credential for ONE contact — the site's server keeps it in an HttpOnly
1734
+ cookie and forwards it here. Missing answers
1735
+ `401 PORTAL_SESSION_REQUIRED`; unknown, expired or revoked answers
1736
+ `401 PORTAL_SESSION_INVALID`.
1737
+ schema:
1738
+ type: string
1180
1739
  Cursor:
1181
1740
  name: cursor
1182
1741
  in: query
@@ -2035,22 +2594,512 @@ components:
2035
2594
  type: string
2036
2595
  brand_website:
2037
2596
  type: string
2038
- format: uri
2597
+ format: uri
2598
+ contact_id:
2599
+ type: [string, "null"]
2600
+ contact_name:
2601
+ type: string
2602
+ contact_email:
2603
+ type: string
2604
+ format: email
2605
+ start_date:
2606
+ type: string
2607
+ format: date
2608
+ end_date:
2609
+ type: string
2610
+ format: date
2611
+ notes:
2612
+ type: string
2613
+ BookingTimestampInput:
2614
+ description: >-
2615
+ A timestamp on the way in — Unix milliseconds, or an ISO 8601
2616
+ date-time string. Responses always render timestamps as ISO 8601.
2617
+ type: [integer, string]
2618
+ BookingStatus:
2619
+ type: string
2620
+ enum: [pending, confirmed, completed, cancelled, no_show]
2621
+ BookingCancelledBy:
2622
+ description: >-
2623
+ Who cancelled. `staff` for the id-addressed cancel, `customer` for the
2624
+ manage-token cancel, `system` for an automated one.
2625
+ type: string
2626
+ enum: [customer, staff, system]
2627
+ BookingPaymentStatus:
2628
+ type: string
2629
+ enum: [none, reserved, captured, refunded]
2630
+ BookingCreatedVia:
2631
+ type: string
2632
+ enum: [web, dashboard, walk_in, api]
2633
+ BookingResourceType:
2634
+ type: string
2635
+ enum: [staff, room, equipment]
2636
+ Booking:
2637
+ type: object
2638
+ required:
2639
+ - id
2640
+ - contact_id
2641
+ - service_id
2642
+ - resource_id
2643
+ - start_ts
2644
+ - end_ts
2645
+ - booked_for_name
2646
+ - booked_for_birth_year
2647
+ - party_sequence_id
2648
+ - status
2649
+ - cancelled_by
2650
+ - cancel_reason
2651
+ - rescheduled_from_id
2652
+ - payment_status
2653
+ - amount_ore
2654
+ - notes
2655
+ - internal_notes
2656
+ - created_via
2657
+ - created_at
2658
+ - updated_at
2659
+ properties:
2660
+ id:
2661
+ type: string
2662
+ contact_id:
2663
+ type: [string, "null"]
2664
+ service_id:
2665
+ type: [string, "null"]
2666
+ resource_id:
2667
+ type: [string, "null"]
2668
+ start_ts:
2669
+ type: [string, "null"]
2670
+ format: date-time
2671
+ end_ts:
2672
+ type: [string, "null"]
2673
+ format: date-time
2674
+ booked_for_name:
2675
+ type: [string, "null"]
2676
+ booked_for_birth_year:
2677
+ description: A birth year, not a birthdate — the age bracket is all that is stored.
2678
+ type: [integer, "null"]
2679
+ party_sequence_id:
2680
+ description: Shared by every booking created in the same party request.
2681
+ type: [string, "null"]
2682
+ status:
2683
+ $ref: "#/components/schemas/BookingStatus"
2684
+ cancelled_by:
2685
+ oneOf:
2686
+ - $ref: "#/components/schemas/BookingCancelledBy"
2687
+ - type: "null"
2688
+ cancel_reason:
2689
+ type: [string, "null"]
2690
+ rescheduled_from_id:
2691
+ description: On a booking created by a reschedule, the booking it replaced.
2692
+ type: [string, "null"]
2693
+ payment_status:
2694
+ $ref: "#/components/schemas/BookingPaymentStatus"
2695
+ amount_ore:
2696
+ description: Price in integer øre. Never a float and never kroner.
2697
+ type: [integer, "null"]
2698
+ notes:
2699
+ description: Customer-visible note.
2700
+ type: [string, "null"]
2701
+ internal_notes:
2702
+ description: Staff-only note; never shown to the customer.
2703
+ type: [string, "null"]
2704
+ created_via:
2705
+ oneOf:
2706
+ - $ref: "#/components/schemas/BookingCreatedVia"
2707
+ - type: "null"
2708
+ created_at:
2709
+ type: [string, "null"]
2710
+ format: date-time
2711
+ updated_at:
2712
+ type: [string, "null"]
2713
+ format: date-time
2714
+ BookingService:
2715
+ type: object
2716
+ required:
2717
+ - id
2718
+ - name
2719
+ - description
2720
+ - category
2721
+ - duration_minutes
2722
+ - buffer_before_minutes
2723
+ - buffer_after_minutes
2724
+ - price_ore
2725
+ - weekend_surcharge_pct
2726
+ - resource_requirements
2727
+ - bookable_online
2728
+ - max_per_booking
2729
+ - color
2730
+ - sort_order
2731
+ - active
2732
+ - created_at
2733
+ - updated_at
2734
+ properties:
2735
+ id:
2736
+ type: string
2737
+ name:
2738
+ type: [string, "null"]
2739
+ description:
2740
+ type: [string, "null"]
2741
+ category:
2742
+ type: [string, "null"]
2743
+ duration_minutes:
2744
+ type: [integer, "null"]
2745
+ buffer_before_minutes:
2746
+ type: [integer, "null"]
2747
+ buffer_after_minutes:
2748
+ type: [integer, "null"]
2749
+ price_ore:
2750
+ description: Price in integer øre.
2751
+ type: [integer, "null"]
2752
+ weekend_surcharge_pct:
2753
+ type: [number, "null"]
2754
+ resource_requirements:
2755
+ description: Resource types this service needs, e.g. `["staff"]`.
2756
+ type: array
2757
+ items:
2758
+ type: string
2759
+ bookable_online:
2760
+ type: boolean
2761
+ max_per_booking:
2762
+ type: [integer, "null"]
2763
+ color:
2764
+ type: [string, "null"]
2765
+ sort_order:
2766
+ type: [integer, "null"]
2767
+ active:
2768
+ type: boolean
2769
+ created_at:
2770
+ type: [string, "null"]
2771
+ format: date-time
2772
+ updated_at:
2773
+ type: [string, "null"]
2774
+ format: date-time
2775
+ BookingResource:
2776
+ type: object
2777
+ required:
2778
+ - id
2779
+ - type
2780
+ - name
2781
+ - photo_url
2782
+ - bio
2783
+ - service_ids
2784
+ - capacity
2785
+ - sort_order
2786
+ - active
2787
+ - created_at
2788
+ - updated_at
2789
+ properties:
2790
+ id:
2791
+ type: string
2792
+ type:
2793
+ oneOf:
2794
+ - $ref: "#/components/schemas/BookingResourceType"
2795
+ - type: "null"
2796
+ name:
2797
+ type: [string, "null"]
2798
+ photo_url:
2799
+ type: [string, "null"]
2800
+ format: uri
2801
+ bio:
2802
+ type: [string, "null"]
2803
+ service_ids:
2804
+ description: Services this resource can perform.
2805
+ type: array
2806
+ items:
2807
+ type: string
2808
+ capacity:
2809
+ type: [integer, "null"]
2810
+ sort_order:
2811
+ type: [integer, "null"]
2812
+ active:
2813
+ type: boolean
2814
+ created_at:
2815
+ type: [string, "null"]
2816
+ format: date-time
2817
+ updated_at:
2818
+ type: [string, "null"]
2819
+ format: date-time
2820
+ BookingSlot:
2821
+ type: object
2822
+ required: [start_ts, end_ts, resource_id]
2823
+ properties:
2824
+ start_ts:
2825
+ type: [string, "null"]
2826
+ format: date-time
2827
+ end_ts:
2828
+ type: [string, "null"]
2829
+ format: date-time
2830
+ resource_id:
2831
+ type: [string, "null"]
2832
+ BookingScheduleDay:
2833
+ description: One date a service can be booked on. `date` is the workspace's own calendar date, not a timestamp.
2834
+ type: object
2835
+ required: [date, opens_ts, closes_ts, last_start_ts]
2836
+ properties:
2837
+ date:
2838
+ type: [string, "null"]
2839
+ format: date
2840
+ opens_ts:
2841
+ type: [string, "null"]
2842
+ format: date-time
2843
+ closes_ts:
2844
+ type: [string, "null"]
2845
+ format: date-time
2846
+ last_start_ts:
2847
+ description: Last start this service could occupy on the date; `null` when the date is shut outright.
2848
+ type: [string, "null"]
2849
+ format: date-time
2850
+ BookingContactInput:
2851
+ description: The person the booking is made under. Phone is the CRM dedupe key.
2852
+ type: object
2853
+ required: [phone]
2854
+ properties:
2855
+ phone:
2856
+ type: string
2857
+ maxLength: 40
2858
+ email:
2859
+ type: string
2860
+ maxLength: 320
2861
+ name:
2862
+ type: string
2863
+ maxLength: 200
2864
+ CreateBookingItemInput:
2865
+ description: One line of a party — a single service on a single slot.
2866
+ type: object
2867
+ required: [service_id, start_ts]
2868
+ properties:
2869
+ service_id:
2870
+ type: string
2871
+ resource_id:
2872
+ description: Omit to let the engine pick a free resource.
2873
+ type: string
2874
+ start_ts:
2875
+ $ref: "#/components/schemas/BookingTimestampInput"
2876
+ booked_for_name:
2877
+ type: string
2878
+ maxLength: 200
2879
+ booked_for_birth_year:
2880
+ type: integer
2881
+ minimum: 1900
2882
+ maximum: 2200
2883
+ CreateBookingInput:
2884
+ type: object
2885
+ required: [items, contact]
2886
+ properties:
2887
+ items:
2888
+ description: >-
2889
+ A PARTY — one request books a whole family in one all-or-nothing
2890
+ transaction.
2891
+ type: array
2892
+ minItems: 1
2893
+ maxItems: 50
2894
+ items:
2895
+ $ref: "#/components/schemas/CreateBookingItemInput"
2896
+ contact:
2897
+ $ref: "#/components/schemas/BookingContactInput"
2898
+ notes:
2899
+ type: string
2900
+ maxLength: 5000
2901
+ created_via:
2902
+ description: >-
2903
+ Where the booking came from. Defaults to `api`. A workspace's OWN
2904
+ website should send `web`, so its bookings can be told apart from
2905
+ integrations. `dashboard` and `walk_in` are staff-only and are
2906
+ rejected with 400 — a bearer token proves which workspace is calling,
2907
+ not that a member typed the booking in.
2908
+ type: string
2909
+ enum: [web, api]
2910
+ CreatedBooking:
2911
+ type: object
2912
+ required: [id]
2913
+ properties:
2914
+ id:
2915
+ type: string
2916
+ manage_token:
2917
+ description: >-
2918
+ Show-once secret for the customer's manage link. Only its hash is
2919
+ stored, and an idempotent replay omits this field entirely.
2920
+ type: string
2921
+ BookingCreateResult:
2922
+ type: object
2923
+ required: [bookings, contact_id]
2924
+ properties:
2925
+ bookings:
2926
+ description: One entry per created booking, in request order.
2927
+ type: array
2928
+ items:
2929
+ $ref: "#/components/schemas/CreatedBooking"
2930
+ contact_id:
2931
+ type: string
2932
+ BookingActionResult:
2933
+ type: object
2934
+ required: [success]
2935
+ properties:
2936
+ success:
2937
+ type: boolean
2938
+ const: true
2939
+ BookingRescheduleResult:
2940
+ type: object
2941
+ required: [success, booking_id]
2942
+ properties:
2943
+ success:
2944
+ type: boolean
2945
+ const: true
2946
+ booking_id:
2947
+ description: >-
2948
+ The NEW booking's id — a reschedule cancels the old row and inserts
2949
+ a new one.
2950
+ type: string
2951
+ manage_token:
2952
+ description: >-
2953
+ Freshly minted manage token for the new booking; omitted on an
2954
+ idempotent replay.
2955
+ type: string
2956
+ ManageSummary:
2957
+ description: >-
2958
+ What the holder of a manage token may see and do. `can_cancel` and
2959
+ `can_reschedule` already apply the workspace's policy windows.
2960
+ type: object
2961
+ required:
2962
+ - booking_id
2963
+ - contact_id
2964
+ - status
2965
+ - cancelled_by
2966
+ - cancel_reason
2967
+ - rescheduled_from_id
2968
+ - start_ts
2969
+ - end_ts
2970
+ - service_id
2971
+ - service_name
2972
+ - resource_id
2973
+ - resource_name
2974
+ - booked_for_name
2975
+ - party_sequence_id
2976
+ - amount_ore
2977
+ - payment_status
2978
+ - time_zone
2979
+ - cancel_window_hours
2980
+ - reschedule_window_hours
2981
+ - can_cancel
2982
+ - can_reschedule
2983
+ properties:
2984
+ booking_id:
2985
+ type: string
2039
2986
  contact_id:
2040
2987
  type: [string, "null"]
2041
- contact_name:
2042
- type: string
2043
- contact_email:
2044
- type: string
2045
- format: email
2046
- start_date:
2047
- type: string
2048
- format: date
2049
- end_date:
2988
+ status:
2989
+ oneOf:
2990
+ - $ref: "#/components/schemas/BookingStatus"
2991
+ - type: "null"
2992
+ cancelled_by:
2993
+ oneOf:
2994
+ - $ref: "#/components/schemas/BookingCancelledBy"
2995
+ - type: "null"
2996
+ cancel_reason:
2997
+ type: [string, "null"]
2998
+ rescheduled_from_id:
2999
+ type: [string, "null"]
3000
+ start_ts:
3001
+ type: [string, "null"]
3002
+ format: date-time
3003
+ end_ts:
3004
+ type: [string, "null"]
3005
+ format: date-time
3006
+ service_id:
3007
+ type: [string, "null"]
3008
+ service_name:
3009
+ type: [string, "null"]
3010
+ resource_id:
3011
+ type: [string, "null"]
3012
+ resource_name:
3013
+ type: [string, "null"]
3014
+ booked_for_name:
3015
+ type: [string, "null"]
3016
+ party_sequence_id:
3017
+ type: [string, "null"]
3018
+ amount_ore:
3019
+ description: Price in integer øre.
3020
+ type: [integer, "null"]
3021
+ payment_status:
3022
+ oneOf:
3023
+ - $ref: "#/components/schemas/BookingPaymentStatus"
3024
+ - type: "null"
3025
+ time_zone:
3026
+ description: IANA zone the booking's local times should be rendered in.
3027
+ type: [string, "null"]
3028
+ cancel_window_hours:
3029
+ type: [number, "null"]
3030
+ reschedule_window_hours:
3031
+ type: [number, "null"]
3032
+ can_cancel:
3033
+ type: boolean
3034
+ can_reschedule:
3035
+ type: boolean
3036
+ UpdateBookingInput:
3037
+ description: >-
3038
+ At least one of `notes` or `internal_notes` is required — the API
3039
+ rejects a body carrying neither with a 400. `""` is a meaningful value
3040
+ that clears the field, so the constraint is on presence.
3041
+ # Spelled out as two fully-typed branches rather than the terser
3042
+ # `anyOf: [{required: [notes]}, {required: [internal_notes]}]`, because a
3043
+ # branch carrying only `required` generates as `unknown` — and `T |
3044
+ # unknown` collapses to `unknown`, which accepts everything and is worse
3045
+ # than the unconstrained object. Each branch therefore repeats the
3046
+ # properties so openapi-typescript emits a real discriminating union.
3047
+ #
3048
+ # `minProperties: 1` is deliberately absent: `anyOf` on two named
3049
+ # properties already implies it and is strictly stronger (it also rejects
3050
+ # `{ unrelated_key: 1 }`), so keeping both would be two statements of one
3051
+ # invariant to drift apart.
3052
+ anyOf:
3053
+ - type: object
3054
+ required: [notes]
3055
+ properties:
3056
+ notes:
3057
+ description: Customer-visible note.
3058
+ type: string
3059
+ maxLength: 5000
3060
+ internal_notes:
3061
+ description: Staff-only note.
3062
+ type: string
3063
+ maxLength: 5000
3064
+ - type: object
3065
+ required: [internal_notes]
3066
+ properties:
3067
+ notes:
3068
+ description: Customer-visible note.
3069
+ type: string
3070
+ maxLength: 5000
3071
+ internal_notes:
3072
+ description: Staff-only note.
3073
+ type: string
3074
+ maxLength: 5000
3075
+ CancelBookingInput:
3076
+ type: object
3077
+ properties:
3078
+ reason:
2050
3079
  type: string
2051
- format: date
2052
- notes:
3080
+ maxLength: 500
3081
+ RescheduleBookingInput:
3082
+ type: object
3083
+ required: [new_start_ts]
3084
+ properties:
3085
+ new_start_ts:
3086
+ $ref: "#/components/schemas/BookingTimestampInput"
3087
+ new_resource_id:
2053
3088
  type: string
3089
+ BookingsPagination:
3090
+ description: >-
3091
+ Pagination for a bookings page. `truncated` is the extra statement:
3092
+ the underlying read is capped, and when the cap binds there are
3093
+ matching bookings no cursor from this call reaches — narrow the window.
3094
+ type: object
3095
+ required: [has_more, next_cursor, truncated]
3096
+ properties:
3097
+ has_more:
3098
+ type: boolean
3099
+ next_cursor:
3100
+ type: [string, "null"]
3101
+ truncated:
3102
+ type: boolean
2054
3103
  GdprExport:
2055
3104
  type: object
2056
3105
  required: [id, request_type, status, submitted_at, completed_at]
@@ -2743,6 +3792,34 @@ components:
2743
3792
  $ref: "#/components/schemas/Envelope_DealUpdateResult"
2744
3793
  ApiResponse_DealRemoveResult:
2745
3794
  $ref: "#/components/schemas/Envelope_DealRemoveResult"
3795
+ ApiResponse_Booking:
3796
+ $ref: "#/components/schemas/Envelope_Booking"
3797
+ ApiResponse_BookingServiceArray:
3798
+ $ref: "#/components/schemas/Envelope_BookingServiceArray"
3799
+ ApiResponse_BookingResourceArray:
3800
+ $ref: "#/components/schemas/Envelope_BookingResourceArray"
3801
+ ApiResponse_BookingSlotArray:
3802
+ $ref: "#/components/schemas/Envelope_BookingSlotArray"
3803
+ ApiResponse_BookingScheduleDayArray:
3804
+ $ref: "#/components/schemas/Envelope_BookingScheduleDayArray"
3805
+ ApiResponse_BookingCreateResult:
3806
+ $ref: "#/components/schemas/Envelope_BookingCreateResult"
3807
+ ApiResponse_BookingActionResult:
3808
+ $ref: "#/components/schemas/Envelope_BookingActionResult"
3809
+ ApiResponse_BookingRescheduleResult:
3810
+ $ref: "#/components/schemas/Envelope_BookingRescheduleResult"
3811
+ ApiResponse_ManageSummary:
3812
+ $ref: "#/components/schemas/Envelope_ManageSummary"
3813
+ ApiResponse_PortalLoginStartResult:
3814
+ $ref: "#/components/schemas/Envelope_PortalLoginStartResult"
3815
+ ApiResponse_PortalSession:
3816
+ $ref: "#/components/schemas/Envelope_PortalSession"
3817
+ ApiResponse_PortalProfile:
3818
+ $ref: "#/components/schemas/Envelope_PortalProfile"
3819
+ ApiResponse_PortalBookings:
3820
+ $ref: "#/components/schemas/Envelope_PortalBookings"
3821
+ ApiResponse_PortalExport:
3822
+ $ref: "#/components/schemas/Envelope_PortalExport"
2746
3823
  ApiResponse_GdprExportRequest:
2747
3824
  $ref: "#/components/schemas/Envelope_GdprExportRequest"
2748
3825
  ApiResponse_GdprExportArray:
@@ -2832,6 +3909,16 @@ components:
2832
3909
  $ref: "#/components/schemas/Deal"
2833
3910
  pagination:
2834
3911
  $ref: "#/components/schemas/Pagination"
3912
+ PaginatedResponse_Booking:
3913
+ type: object
3914
+ required: [data, pagination]
3915
+ properties:
3916
+ data:
3917
+ type: array
3918
+ items:
3919
+ $ref: "#/components/schemas/Booking"
3920
+ pagination:
3921
+ $ref: "#/components/schemas/BookingsPagination"
2835
3922
  PaginatedResponse_HelpdeskConversation:
2836
3923
  type: object
2837
3924
  required: [data, pagination]
@@ -3002,6 +4089,309 @@ components:
3002
4089
  properties:
3003
4090
  data:
3004
4091
  $ref: "#/components/schemas/DealRemoveResult"
4092
+ Envelope_Booking:
4093
+ type: object
4094
+ required: [data]
4095
+ properties:
4096
+ data:
4097
+ $ref: "#/components/schemas/Booking"
4098
+ Envelope_BookingServiceArray:
4099
+ type: object
4100
+ required: [data]
4101
+ properties:
4102
+ data:
4103
+ type: array
4104
+ items:
4105
+ $ref: "#/components/schemas/BookingService"
4106
+ Envelope_BookingResourceArray:
4107
+ type: object
4108
+ required: [data]
4109
+ properties:
4110
+ data:
4111
+ type: array
4112
+ items:
4113
+ $ref: "#/components/schemas/BookingResource"
4114
+ Envelope_BookingSlotArray:
4115
+ type: object
4116
+ required: [data]
4117
+ properties:
4118
+ data:
4119
+ type: array
4120
+ items:
4121
+ $ref: "#/components/schemas/BookingSlot"
4122
+ Envelope_BookingScheduleDayArray:
4123
+ type: object
4124
+ required: [data]
4125
+ properties:
4126
+ data:
4127
+ type: array
4128
+ items:
4129
+ $ref: "#/components/schemas/BookingScheduleDay"
4130
+ Envelope_BookingCreateResult:
4131
+ type: object
4132
+ required: [data]
4133
+ properties:
4134
+ data:
4135
+ $ref: "#/components/schemas/BookingCreateResult"
4136
+ Envelope_BookingActionResult:
4137
+ type: object
4138
+ required: [data]
4139
+ properties:
4140
+ data:
4141
+ $ref: "#/components/schemas/BookingActionResult"
4142
+ Envelope_BookingRescheduleResult:
4143
+ type: object
4144
+ required: [data]
4145
+ properties:
4146
+ data:
4147
+ $ref: "#/components/schemas/BookingRescheduleResult"
4148
+ Envelope_ManageSummary:
4149
+ type: object
4150
+ required: [data]
4151
+ properties:
4152
+ data:
4153
+ $ref: "#/components/schemas/ManageSummary"
4154
+ PortalLoginStartInput:
4155
+ type: object
4156
+ required: [email]
4157
+ properties:
4158
+ email:
4159
+ type: string
4160
+ format: email
4161
+ description: The address the code is sent to.
4162
+ locale:
4163
+ type: string
4164
+ description: Locale for the e-mail (e.g. `nb`, `en`); the workspace default when omitted.
4165
+ PortalLoginStartResult:
4166
+ description: >-
4167
+ Always `sent`, whether or not the address is a known contact — the
4168
+ route is enumeration-safe by design.
4169
+ type: object
4170
+ required: [status]
4171
+ properties:
4172
+ status:
4173
+ type: string
4174
+ const: sent
4175
+ PortalVerifyInput:
4176
+ type: object
4177
+ required: [email, code]
4178
+ properties:
4179
+ email:
4180
+ type: string
4181
+ format: email
4182
+ code:
4183
+ type: string
4184
+ description: The one-time code from the e-mail.
4185
+ PortalContactSummary:
4186
+ type: object
4187
+ required: [contact_id, first_name]
4188
+ properties:
4189
+ contact_id:
4190
+ type: string
4191
+ first_name:
4192
+ type: [string, "null"]
4193
+ PortalSession:
4194
+ description: >-
4195
+ A portal session. `session_token` is a bearer credential for ONE
4196
+ contact — keep it in an HttpOnly cookie on the site's server and never
4197
+ hand it to the browser.
4198
+ type: object
4199
+ required: [session_token, expires_at, contact]
4200
+ properties:
4201
+ session_token:
4202
+ type: string
4203
+ expires_at:
4204
+ type: integer
4205
+ description: Unix timestamp in milliseconds.
4206
+ contact:
4207
+ $ref: "#/components/schemas/PortalContactSummary"
4208
+ PortalFamilyMember:
4209
+ type: object
4210
+ required: [name, birth_year]
4211
+ properties:
4212
+ name:
4213
+ type: string
4214
+ birth_year:
4215
+ type: integer
4216
+ PortalProfile:
4217
+ type: object
4218
+ required:
4219
+ - contact_id
4220
+ - email
4221
+ - first_name
4222
+ - last_name
4223
+ - phone
4224
+ - family
4225
+ - marketing_consent
4226
+ - created_at
4227
+ properties:
4228
+ contact_id:
4229
+ type: string
4230
+ email:
4231
+ type: string
4232
+ first_name:
4233
+ type: [string, "null"]
4234
+ last_name:
4235
+ type: [string, "null"]
4236
+ phone:
4237
+ type: [string, "null"]
4238
+ family:
4239
+ type: array
4240
+ items:
4241
+ $ref: "#/components/schemas/PortalFamilyMember"
4242
+ marketing_consent:
4243
+ type: boolean
4244
+ created_at:
4245
+ type: integer
4246
+ description: Unix timestamp in milliseconds.
4247
+ PortalProfilePatch:
4248
+ description: Only the supplied fields change.
4249
+ type: object
4250
+ properties:
4251
+ first_name:
4252
+ type: string
4253
+ last_name:
4254
+ type: string
4255
+ phone:
4256
+ type: [string, "null"]
4257
+ description: "`null` clears the number."
4258
+ family:
4259
+ type: array
4260
+ description: Replaces the whole list.
4261
+ items:
4262
+ $ref: "#/components/schemas/PortalFamilyMember"
4263
+ marketing_consent:
4264
+ type: boolean
4265
+ description: Records a `marketing_email` consent change with source `portal`.
4266
+ PortalBooking:
4267
+ type: object
4268
+ required:
4269
+ - booking_id
4270
+ - status
4271
+ - start_ts
4272
+ - end_ts
4273
+ - service_id
4274
+ - service_name
4275
+ - resource_id
4276
+ - resource_name
4277
+ - booked_for_name
4278
+ - amount_ore
4279
+ - notes
4280
+ - manage_token
4281
+ - can_manage
4282
+ properties:
4283
+ booking_id:
4284
+ type: string
4285
+ status:
4286
+ $ref: "#/components/schemas/BookingStatus"
4287
+ start_ts:
4288
+ type: integer
4289
+ description: Unix timestamp in milliseconds.
4290
+ end_ts:
4291
+ type: integer
4292
+ description: Unix timestamp in milliseconds.
4293
+ service_id:
4294
+ type: [string, "null"]
4295
+ service_name:
4296
+ type: [string, "null"]
4297
+ resource_id:
4298
+ type: [string, "null"]
4299
+ resource_name:
4300
+ type: [string, "null"]
4301
+ booked_for_name:
4302
+ type: [string, "null"]
4303
+ amount_ore:
4304
+ type: [integer, "null"]
4305
+ description: Integer øre, or `null` when the service has no price.
4306
+ notes:
4307
+ type: [string, "null"]
4308
+ manage_token:
4309
+ type: [string, "null"]
4310
+ description: >-
4311
+ Present only while the booking is upcoming and manageable; opens
4312
+ the site's manage page.
4313
+ can_manage:
4314
+ type: boolean
4315
+ PortalBookings:
4316
+ type: object
4317
+ required: [upcoming, past]
4318
+ properties:
4319
+ upcoming:
4320
+ type: array
4321
+ items:
4322
+ $ref: "#/components/schemas/PortalBooking"
4323
+ past:
4324
+ type: array
4325
+ items:
4326
+ $ref: "#/components/schemas/PortalBooking"
4327
+ PortalConsentRecord:
4328
+ type: object
4329
+ required: [consent_type, granted, granted_at, revoked_at, source]
4330
+ properties:
4331
+ consent_type:
4332
+ type: string
4333
+ granted:
4334
+ type: boolean
4335
+ granted_at:
4336
+ type: [integer, "null"]
4337
+ description: Unix timestamp in milliseconds, or `null`.
4338
+ revoked_at:
4339
+ type: [integer, "null"]
4340
+ description: Unix timestamp in milliseconds, or `null`.
4341
+ source:
4342
+ type: string
4343
+ PortalExport:
4344
+ description: Everything the workspace holds about the signed-in contact (GDPR Art. 15).
4345
+ type: object
4346
+ required: [exported_at, contact, family, consents, bookings]
4347
+ properties:
4348
+ exported_at:
4349
+ type: integer
4350
+ description: Unix timestamp in milliseconds.
4351
+ contact:
4352
+ $ref: "#/components/schemas/PortalProfile"
4353
+ family:
4354
+ type: array
4355
+ items:
4356
+ $ref: "#/components/schemas/PortalFamilyMember"
4357
+ consents:
4358
+ type: array
4359
+ items:
4360
+ $ref: "#/components/schemas/PortalConsentRecord"
4361
+ bookings:
4362
+ type: array
4363
+ items:
4364
+ $ref: "#/components/schemas/PortalBooking"
4365
+ Envelope_PortalLoginStartResult:
4366
+ type: object
4367
+ required: [data]
4368
+ properties:
4369
+ data:
4370
+ $ref: "#/components/schemas/PortalLoginStartResult"
4371
+ Envelope_PortalSession:
4372
+ type: object
4373
+ required: [data]
4374
+ properties:
4375
+ data:
4376
+ $ref: "#/components/schemas/PortalSession"
4377
+ Envelope_PortalProfile:
4378
+ type: object
4379
+ required: [data]
4380
+ properties:
4381
+ data:
4382
+ $ref: "#/components/schemas/PortalProfile"
4383
+ Envelope_PortalBookings:
4384
+ type: object
4385
+ required: [data]
4386
+ properties:
4387
+ data:
4388
+ $ref: "#/components/schemas/PortalBookings"
4389
+ Envelope_PortalExport:
4390
+ type: object
4391
+ required: [data]
4392
+ properties:
4393
+ data:
4394
+ $ref: "#/components/schemas/PortalExport"
3005
4395
  ScanCreateInput:
3006
4396
  type: object
3007
4397
  description: Provide exactly one of `url`, `orgnr`, or `name`.