openapi: 3.1.0
info:
  title: DSS Public API
  description: Public OAuth 2.0 API for Digital Sea Service. See https://docs.digitalseaservice.com
    for the developer guide.
  version: 1.0.0
  contact:
    name: DSS Developer Support
    email: admin@digitalseaservice.com
    url: https://docs.digitalseaservice.com
  license:
    name: Proprietary
  termsOfService: https://digitalseaservice.com/terms
paths:
  /oauth/authorize:
    get:
      tags:
      - oauth
      summary: Authorize Get
      operationId: authorize_get_oauth_authorize_get
      parameters:
      - name: response_type
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: Response Type
      - name: client_id
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: Client Id
      - name: redirect_uri
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: Redirect Uri
      - name: scope
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: Scope
      - name: state
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: State
      - name: code_challenge
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: Code Challenge
      - name: code_challenge_method
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          default: S256
          title: Code Challenge Method
      - name: user_hint
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: User Hint
      - name: prompt
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: Prompt
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema: {}
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
    post:
      tags:
      - oauth
      summary: Authorize Post
      operationId: authorize_post_oauth_authorize_post
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/Body_authorize_post_oauth_authorize_post'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema: {}
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /oauth/token:
    post:
      tags:
      - oauth
      summary: Token Endpoint
      operationId: token_endpoint_oauth_token_post
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/Body_token_endpoint_oauth_token_post'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema: {}
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /oauth/revoke:
    post:
      tags:
      - oauth
      summary: Revoke Endpoint
      operationId: revoke_endpoint_oauth_revoke_post
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/Body_revoke_endpoint_oauth_revoke_post'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema: {}
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /oauth/userinfo:
    get:
      tags:
      - oauth
      summary: Userinfo Endpoint
      operationId: userinfo_endpoint_oauth_userinfo_get
      responses:
        '200':
          description: Basic profile for the authenticated user.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserInfo'
  /v1/me:
    get:
      tags:
      - v1
      summary: Get the authenticated user's profile
      operationId: get_me_v1_me_get
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MeResponse'
        '401':
          description: "`invalid_token` \u2014 bearer token missing, malformed, expired,\
            \ or revoked."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '403':
          description: "`insufficient_scope` \u2014 the token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '429':
          description: "`rate_limit_exceeded` \u2014 per-client request budget exhausted."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '404':
          description: "`not_found` \u2014 no matching record for the authenticated\
            \ user."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '500':
          description: "`internal_error` \u2014 an unexpected error occurred; reference\
            \ `instance`."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
      security:
      - oauth2:
        - profile:read
      - bearerAuth: []
  /v1/me/sea-time:
    get:
      tags:
      - sea-time
      summary: Get aggregate sea-time totals
      operationId: get_sea_time_v1_me_sea_time_get
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SeaTimeResponse'
        '304':
          description: Resource has not changed since the conditional header.
        '401':
          description: "`invalid_token` \u2014 bearer token missing, malformed, expired,\
            \ or revoked."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '403':
          description: "`insufficient_scope` \u2014 the token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '429':
          description: "`rate_limit_exceeded` \u2014 per-client request budget exhausted."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '404':
          description: "`not_found` \u2014 no matching record for the authenticated\
            \ user."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '500':
          description: "`internal_error` \u2014 an unexpected error occurred; reference\
            \ `instance`."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
      security:
      - oauth2:
        - seatime:read
      - bearerAuth: []
  /v1/me/sea-time/recent:
    get:
      tags:
      - sea-time
      summary: Get the rolling 12-month sea-time trend
      operationId: get_sea_time_recent_v1_me_sea_time_recent_get
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SeaTimeRecentResponse'
        '304':
          description: Resource has not changed since the conditional header.
        '401':
          description: "`invalid_token` \u2014 bearer token missing, malformed, expired,\
            \ or revoked."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '403':
          description: "`insufficient_scope` \u2014 the token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '429':
          description: "`rate_limit_exceeded` \u2014 per-client request budget exhausted."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '404':
          description: "`not_found` \u2014 no matching record for the authenticated\
            \ user."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '500':
          description: "`internal_error` \u2014 an unexpected error occurred; reference\
            \ `instance`."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
      security:
      - oauth2:
        - seatime:read
      - bearerAuth: []
  /v1/me/vessels:
    get:
      tags:
      - vessels
      summary: List the authenticated user's vessel history
      operationId: list_vessels_v1_me_vessels_get
      parameters:
      - name: cursor
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          description: Opaque cursor from a prior response.
          title: Cursor
        description: Opaque cursor from a prior response.
      - name: limit
        in: query
        required: false
        schema:
          anyOf:
          - type: integer
            maximum: 200
            minimum: 1
          - type: 'null'
          description: Page size; max 200.
          title: Limit
        description: Page size; max 200.
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VesselsResponse'
        '304':
          description: Resource has not changed since the conditional header.
        '422':
          description: "`invalid_request` \u2014 request validation failed; see the\
            \ `errors` array."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ValidationProblemDetails'
        '401':
          description: "`invalid_token` \u2014 bearer token missing, malformed, expired,\
            \ or revoked."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '403':
          description: "`insufficient_scope` \u2014 the token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '429':
          description: "`rate_limit_exceeded` \u2014 per-client request budget exhausted."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '400':
          description: "`invalid_request` \u2014 a query parameter (e.g. cursor) was\
            \ malformed."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '500':
          description: "`internal_error` \u2014 an unexpected error occurred; reference\
            \ `instance`."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
      security:
      - oauth2:
        - vessels:read
      - bearerAuth: []
  /v1/webhooks/test:
    get:
      tags:
      - webhooks
      summary: Fire a webhook.test event to your registered URL
      operationId: fire_test_webhook_v1_webhooks_test_get
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestResponse'
        '401':
          description: "`invalid_token` \u2014 bearer token missing, malformed, expired,\
            \ or revoked."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '403':
          description: "`partner_suspended` \u2014 the calling partner has been suspended."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '404':
          description: "`not_found` \u2014 the partner record backing the token was\
            \ not found."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '409':
          description: "`no_webhook_configured` \u2014 no webhook URL or signing secret\
            \ is registered."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '500':
          description: "`internal_error` \u2014 an unexpected error occurred; reference\
            \ `instance`."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
      security:
      - bearerAuth: []
      - oauth2: []
  /v1/partners/sponsorships:
    post:
      tags:
      - partners
      summary: Provision a crew member into a sponsored seat
      description: Auto-provision the account+vessel and grant Premium in one call
        (JIT).
      operationId: create_sponsorship_v1_partners_sponsorships_post
      parameters:
      - name: Idempotency-Key
        in: header
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: Idempotency-Key
      - name: authorization
        in: header
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: Authorization
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSponsorshipRequest'
      responses:
        '200':
          description: "Idempotent replay \u2014 an open seat already existed for\
            \ this email."
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateSponsorshipResponse'
        '201':
          description: A new sponsored member was provisioned and granted Premium.
        '422':
          description: "`invalid_request` \u2014 request validation failed; see the\
            \ `errors` array."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ValidationProblemDetails'
        '401':
          description: "`invalid_token` \u2014 Basic credentials missing, malformed,\
            \ or incorrect."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '403':
          description: "`insufficient_scope` \u2014 partner suspended, or this endpoint\
            \ is not available to the calling partner."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '429':
          description: "`rate_limit_exceeded` \u2014 per-client request budget exhausted."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '500':
          description: "`internal_error` \u2014 an unexpected error occurred; reference\
            \ `instance`."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
      security:
      - basicAuth: []
    get:
      tags:
      - partners
      summary: List this partner's sponsored seats
      description: Return a keyset-paginated, partner-scoped roster (newest first).
      operationId: list_sponsorships_v1_partners_sponsorships_get
      parameters:
      - name: status
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          description: Filter by status.
          title: Status
        description: Filter by status.
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          maximum: 200
          minimum: 1
          description: Page size (max 200).
          default: 50
          title: Limit
        description: Page size (max 200).
      - name: cursor
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          description: Opaque cursor from a prior page.
          title: Cursor
        description: Opaque cursor from a prior page.
      - name: authorization
        in: header
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: Authorization
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SponsorshipList'
        '422':
          description: "`invalid_request` \u2014 request validation failed; see the\
            \ `errors` array."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ValidationProblemDetails'
        '401':
          description: "`invalid_token` \u2014 Basic credentials missing, malformed,\
            \ or incorrect."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '403':
          description: "`insufficient_scope` \u2014 partner suspended, or this endpoint\
            \ is not available to the calling partner."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '429':
          description: "`rate_limit_exceeded` \u2014 per-client request budget exhausted."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '500':
          description: "`internal_error` \u2014 an unexpected error occurred; reference\
            \ `instance`."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
      security:
      - basicAuth: []
  /v1/partners/sponsorships/{sponsorship_id}:
    get:
      tags:
      - partners
      summary: Get one sponsored seat
      description: Fetch a single seat; a cross-partner id resolves to 404 (not 403).
      operationId: get_sponsorship_v1_partners_sponsorships__sponsorship_id__get
      parameters:
      - name: sponsorship_id
        in: path
        required: true
        schema:
          type: string
          title: Sponsorship Id
      - name: authorization
        in: header
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: Authorization
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SponsorshipItem'
        '422':
          description: "`invalid_request` \u2014 request validation failed; see the\
            \ `errors` array."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ValidationProblemDetails'
        '401':
          description: "`invalid_token` \u2014 Basic credentials missing, malformed,\
            \ or incorrect."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '403':
          description: "`insufficient_scope` \u2014 partner suspended, or this endpoint\
            \ is not available to the calling partner."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '429':
          description: "`rate_limit_exceeded` \u2014 per-client request budget exhausted."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '500':
          description: "`internal_error` \u2014 an unexpected error occurred; reference\
            \ `instance`."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
      security:
      - basicAuth: []
    delete:
      tags:
      - partners
      summary: Remove a sponsored seat
      description: "Remove a seat \u2014 downgrades the crew member to Free immediately.\n\
        \nPartner-scoped: a cross-partner id resolves to 404 before any teardown."
      operationId: delete_sponsorship_v1_partners_sponsorships__sponsorship_id__delete
      parameters:
      - name: sponsorship_id
        in: path
        required: true
        schema:
          type: string
          title: Sponsorship Id
      - name: authorization
        in: header
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: Authorization
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteSponsorshipResponse'
        '422':
          description: "`invalid_request` \u2014 request validation failed; see the\
            \ `errors` array."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ValidationProblemDetails'
        '401':
          description: "`invalid_token` \u2014 Basic credentials missing, malformed,\
            \ or incorrect."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '403':
          description: "`insufficient_scope` \u2014 partner suspended, or this endpoint\
            \ is not available to the calling partner."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '429':
          description: "`rate_limit_exceeded` \u2014 per-client request budget exhausted."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '500':
          description: "`internal_error` \u2014 an unexpected error occurred; reference\
            \ `instance`."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
      security:
      - basicAuth: []
  /v1/partners/sponsorships/reconcile:
    post:
      tags:
      - partners
      summary: Reconcile seats against the partner's authoritative active list
      description: 'Cancel seats the partner no longer lists (guarded, dry-run by
        default).


        Partner-scoped: only this partner''s live seats are considered. Safety rails:

        an empty ``active_refs`` is a 400; a mass-cancel over the threshold is refused

        unless ``override_threshold`` is set; nothing is cancelled unless ``apply``
        is

        true. See :func:`dss_api.sponsorship.roster.reconcile_roster`.'
      operationId: reconcile_sponsorships_v1_partners_sponsorships_reconcile_post
      parameters:
      - name: authorization
        in: header
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: Authorization
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReconcileRequest'
      responses:
        '200':
          description: The reconcile diff (dry run, or the result of an applied cancel).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReconcileResponse'
        '400':
          description: "active_refs was empty \u2014 rejected to prevent a cancel-all."
        '422':
          description: "`invalid_request` \u2014 request validation failed; see the\
            \ `errors` array."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ValidationProblemDetails'
        '401':
          description: "`invalid_token` \u2014 Basic credentials missing, malformed,\
            \ or incorrect."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '403':
          description: "`insufficient_scope` \u2014 partner suspended, or this endpoint\
            \ is not available to the calling partner."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '429':
          description: "`rate_limit_exceeded` \u2014 per-client request budget exhausted."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '500':
          description: "`internal_error` \u2014 an unexpected error occurred; reference\
            \ `instance`."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
      security:
      - basicAuth: []
  /v1/partners/members/left:
    post:
      tags:
      - partners
      summary: Notify DSS that a YWC member has left
      description: Close James's billed seat when the member is no longer in YWC.
      operationId: member_left_v1_partners_members_left_post
      parameters:
      - name: authorization
        in: header
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: Authorization
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MemberLeftRequest'
      responses:
        '200':
          description: Seat closed, or already closed (idempotent).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemberLeftResponse'
        '400':
          description: Neither user_id nor a resolvable external_ref was provided.
        '403':
          description: Caller is not Yacht Workers Council.
        '422':
          description: "`invalid_request` \u2014 request validation failed; see the\
            \ `errors` array."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ValidationProblemDetails'
        '401':
          description: "`invalid_token` \u2014 Basic credentials missing, malformed,\
            \ or incorrect."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '429':
          description: "`rate_limit_exceeded` \u2014 per-client request budget exhausted."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '503':
          description: "`service_unavailable` \u2014 the dashboard member-left call\
            \ failed; retry with backoff."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '500':
          description: "`internal_error` \u2014 an unexpected error occurred; reference\
            \ `instance`."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
      security:
      - basicAuth: []
  /v1/partners/sso/tickets:
    post:
      tags:
      - partners
      summary: Mint a one-time DSS login URL for a Connected YWC member
      description: Issue a 60-second, single-use login ticket bound to this YWC consent.
      operationId: create_sso_ticket_v1_partners_sso_tickets_post
      parameters:
      - name: authorization
        in: header
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          title: Authorization
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SsoTicketRequest'
      responses:
        '200':
          description: Ticket minted. Redirect the browser to login_url.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SsoTicketResponse'
        '400':
          description: user_id was missing.
        '403':
          description: Caller is not YWC, or the member has no live Connect consent.
        '422':
          description: "`invalid_request` \u2014 request validation failed; see the\
            \ `errors` array."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ValidationProblemDetails'
        '401':
          description: "`invalid_token` \u2014 Basic credentials missing, malformed,\
            \ or incorrect."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '429':
          description: "`rate_limit_exceeded` \u2014 per-client request budget exhausted."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '500':
          description: "`internal_error` \u2014 an unexpected error occurred; reference\
            \ `instance`."
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
      security:
      - basicAuth: []
components:
  schemas:
    Body_authorize_post_oauth_authorize_post:
      properties:
        action:
          type: string
          title: Action
        client_id:
          type: string
          title: Client Id
        redirect_uri:
          type: string
          title: Redirect Uri
        response_type:
          type: string
          title: Response Type
          default: code
        scope:
          type: string
          title: Scope
          default: ''
        state:
          type: string
          title: State
          default: ''
        code_challenge:
          type: string
          title: Code Challenge
        code_challenge_method:
          type: string
          title: Code Challenge Method
          default: S256
        user_id:
          anyOf:
          - type: string
          - type: 'null'
          title: User Id
        _admin_token:
          anyOf:
          - type: string
          - type: 'null'
          title: Admin Token
      type: object
      required:
      - action
      - client_id
      - redirect_uri
      - code_challenge
      title: Body_authorize_post_oauth_authorize_post
    Body_revoke_endpoint_oauth_revoke_post:
      properties:
        token:
          anyOf:
          - type: string
          - type: 'null'
          title: Token
        token_type_hint:
          anyOf:
          - type: string
          - type: 'null'
          title: Token Type Hint
        client_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Client Id
        client_secret:
          anyOf:
          - type: string
          - type: 'null'
          title: Client Secret
      type: object
      title: Body_revoke_endpoint_oauth_revoke_post
    Body_token_endpoint_oauth_token_post:
      properties:
        grant_type:
          anyOf:
          - type: string
          - type: 'null'
          title: Grant Type
        code:
          anyOf:
          - type: string
          - type: 'null'
          title: Code
        redirect_uri:
          anyOf:
          - type: string
          - type: 'null'
          title: Redirect Uri
        client_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Client Id
        client_secret:
          anyOf:
          - type: string
          - type: 'null'
          title: Client Secret
        code_verifier:
          anyOf:
          - type: string
          - type: 'null'
          title: Code Verifier
        refresh_token:
          anyOf:
          - type: string
          - type: 'null'
          title: Refresh Token
        scope:
          anyOf:
          - type: string
          - type: 'null'
          title: Scope
      type: object
      title: Body_token_endpoint_oauth_token_post
    CreateSponsorshipRequest:
      properties:
        email:
          type: string
          minLength: 3
          title: Email
          description: Crew member's email (normalised).
        first_name:
          type: string
          maxLength: 100
          minLength: 1
          title: First Name
          description: Crew member's first name.
        last_name:
          type: string
          maxLength: 100
          minLength: 1
          title: Last Name
          description: Crew member's last name.
        position:
          type: string
          minLength: 1
          title: Position
          description: Crew rank/role for the account.
        vessel_imo:
          anyOf:
          - type: string
            pattern: ^\d{7}$
          - type: 'null'
          title: Vessel Imo
          description: "7-digit IMO of the vessel to track. **Optional** \u2014 a\
            \ member between jobs, shoreside, or on a hull with no IMO still gets\
            \ their account and Premium, with the seat reported as unresolved. Re-submit\
            \ the same email later with an IMO to attach the vessel; that is a vessel\
            \ move, not a second seat."
        phone:
          anyOf:
          - type: string
          - type: 'null'
          title: Phone
          description: Crew member's phone (E.164).
        vessel_name:
          anyOf:
          - type: string
          - type: 'null'
          title: Vessel Name
          description: Vessel display name (optional).
        join_date:
          anyOf:
          - type: string
            format: date-time
          - type: 'null'
          title: Join Date
          description: Crew join date on the vessel.
        external_ref:
          anyOf:
          - type: string
          - type: 'null'
          title: External Ref
          description: Partner's own opaque reference.
      type: object
      required:
      - email
      - first_name
      - last_name
      - position
      title: CreateSponsorshipRequest
      description: "Attach an existing DSS member to a sponsored Premium seat.\n\n\
        The email must already have a Digital Sea Service account (normal signup).\n\
        New members are not created here \u2014 send them to the signup URL in\n``account_requires_signup``.\
        \ ``vessel_imo`` is optional."
      example:
        email: jane@example.com
        external_ref: ywc-beta-0042
        first_name: Jane
        join_date: '2026-07-01T00:00:00Z'
        last_name: Doe
        phone: '+447700900123'
        position: Chief Stewardess
        vessel_imo: '9074729'
        vessel_name: MY Example
    CreateSponsorshipResponse:
      properties:
        sponsorship_id:
          type: string
          title: Sponsorship Id
        status:
          type: string
          title: Status
        user_id:
          anyOf:
          - type: string
          - type: 'null'
          title: User Id
          description: "The member's DSS user id \u2014 the same id delivered in the\
            \ ``data.user_id`` of ``user.sea_time.updated`` webhooks. Persist it to\
            \ correlate future webhooks to your own member record. Null until the\
            \ seat is active (e.g. an invited-but-unclaimed seat)."
        vessel_resolved:
          type: boolean
          title: Vessel Resolved
          description: False when the IMO did not match a vessel (soft failure).
          default: false
        claim_url:
          anyOf:
          - type: string
          - type: 'null'
          title: Claim Url
          description: "Always null for JIT \u2014 there is no claim link."
        expires_at:
          anyOf:
          - type: string
            format: date-time
          - type: 'null'
          title: Expires At
          description: Per-user sponsored term end (activated_at + term_months).
      type: object
      required:
      - sponsorship_id
      - status
      title: CreateSponsorshipResponse
      description: "The provisioned seat \u2014 returned 201 on create, 200 on idempotent\
        \ replay."
      example:
        expires_at: '2027-07-24T09:00:00Z'
        sponsorship_id: 665f00000000000000000042
        status: active
        user_id: aQ0mtT8kRgVv7C3o1p2q3r4s5t6u
        vessel_resolved: true
    DeleteSponsorshipResponse:
      properties:
        sponsorship_id:
          type: string
          title: Sponsorship Id
        status:
          type: string
          title: Status
      type: object
      required:
      - sponsorship_id
      - status
      title: DeleteSponsorshipResponse
      description: Confirmation that a seat was removed and the crew member downgraded.
      example:
        sponsorship_id: 665f00000000000000000042
        status: cancelled
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    MeResponse:
      properties:
        user_id:
          type: string
          title: User Id
          description: Stable DSS user identifier.
        name:
          type: string
          title: Name
        role:
          anyOf:
          - type: string
          - type: 'null'
          title: Role
          description: The crew member's current rank/position.
        country:
          anyOf:
          - type: string
          - type: 'null'
          title: Country
          description: "Country of residence. Always ``null`` in V1 \u2014 DSS does\
            \ not yet carry this on the user record. See docs/internal/data-model-gaps.md."
        photo_url:
          anyOf:
          - type: string
          - type: 'null'
          title: Photo Url
          description: "Profile photo URL. Always ``null`` in V1 \u2014 DSS stores\
            \ avatars in a private bucket with no public URL. See docs/internal/data-model-gaps.md."
        record_updated_at:
          type: string
          format: date-time
          title: Record Updated At
        membership:
          $ref: '#/components/schemas/Membership'
          description: The member's current DSS membership. Requires ``profile:read``.
      type: object
      required:
      - user_id
      - name
      - record_updated_at
      - membership
      title: MeResponse
      description: The authenticated user's profile.
      example:
        membership:
          expires_at: '2027-07-25T09:14:00Z'
          since: '2026-07-25'
          source: sponsored
          sponsored_by_you: true
          tier: premium
        name: Alex Crew
        record_updated_at: '2026-05-26T09:14:00Z'
        role: Chief Officer
        user_id: sFmjAD7I8bQexmTiTbZZQwq10X43
    MemberLeftRequest:
      properties:
        user_id:
          anyOf:
          - type: string
          - type: 'null'
          title: User Id
          description: DSS user id from Connect (Firebase uid). Preferred.
        external_ref:
          anyOf:
          - type: string
          - type: 'null'
          title: External Ref
          description: Partner's own id, e.g. ywc-user-123. Used if user_id is omitted.
        reason:
          anyOf:
          - type: string
            enum:
            - account_deleted
            - membership_lapsed
          - type: 'null'
          title: Reason
          description: Why they left. Both values close the billed seat the same way.
      type: object
      title: MemberLeftRequest
      description: Tell DSS this member is no longer in the partner's system.
      example:
        external_ref: ywc-user-123
        reason: membership_lapsed
        user_id: aQ0mtT8kRgVv7C3o1p2q3r4s5t6u
    MemberLeftResponse:
      properties:
        user_id:
          type: string
          title: User Id
        ended:
          type: boolean
          title: Ended
          description: True when an open billed seat was closed.
        downgraded:
          type: boolean
          title: Downgraded
          description: True when sponsored Crew Premium was dropped (false if already
            self-pay).
      type: object
      required:
      - user_id
      - ended
      - downgraded
      title: MemberLeftResponse
      description: Confirmation that DSS processed the leave (idempotent).
      example:
        downgraded: true
        ended: true
        user_id: aQ0mtT8kRgVv7C3o1p2q3r4s5t6u
    Membership:
      properties:
        tier:
          type: string
          title: Tier
          description: '``"premium"`` or ``"free"``.'
        source:
          anyOf:
          - type: string
          - type: 'null'
          title: Source
          description: 'How Premium is paid for: ``self`` (the member''s own subscription),
            ``sponsored`` (a partner pays for the seat), or ``vessel`` (the member''s
            yacht covers the crew). ``null`` when the member is Free.'
        since:
          anyOf:
          - type: string
          - type: 'null'
          title: Since
          description: Date Premium began, ``YYYY-MM-DD``. ``null`` when the member
            is Free.
        expires_at:
          anyOf:
          - type: string
            format: date-time
          - type: 'null'
          title: Expires At
          description: When a sponsored seat's term ends. Present only when YOU sponsor
            this member; ``null`` otherwise.
        sponsored_by_you:
          type: boolean
          title: Sponsored By You
          description: 'True when the sponsoring partner is you. Members sponsored
            by a different partner report ``source: "sponsored"`` but never name that
            partner.'
          default: false
      type: object
      required:
      - tier
      title: Membership
      description: "The crew member's current DSS membership.\n\nLets a partner tell\
        \ whether a member already holds Premium \u2014 without it, a\nmirrored \"\
        upgrade\" prompt stays visible even to members the partner is\nsponsoring.\
        \ Computed by the DSS dashboard, so vessel-covered Premium counts;\nreading\
        \ the stored subscription tier alone would report those members Free."
    ReconcileRequest:
      properties:
        active_refs:
          items:
            type: string
          type: array
          title: Active Refs
          description: Partner's authoritative list of active member emails / external
            refs.
        apply:
          type: boolean
          title: Apply
          description: False = dry run (default). True = actually cancel the diff.
          default: false
        max_cancel:
          anyOf:
          - type: integer
            minimum: 0.0
          - type: 'null'
          title: Max Cancel
          description: Hard ceiling on cancellations; exceeding it trips the threshold
            guard.
        override_threshold:
          type: boolean
          title: Override Threshold
          description: Apply even when the mass-cancel threshold guard trips.
          default: false
      type: object
      required:
      - active_refs
      title: ReconcileRequest
      description: 'Reconcile DSS seats against the partner''s authoritative active-member
        list.


        ``active_refs`` is the partner''s own list of currently-active member

        identifiers (each matched against a seat''s ``email`` AND ``external_ref``).

        An **empty** list is rejected (400) so a bad/empty export can never be read

        as "cancel everyone". By default this is a DRY RUN (``apply=false``): the
        diff

        is returned and nothing is cancelled. A mass-cancel that exceeds ``max_cancel``

        (or the default safety fraction) is refused unless ``override_threshold``
        is

        true.'
      example:
        active_refs:
        - jane@example.com
        - ywc-beta-0042
        apply: false
        max_cancel: 25
        override_threshold: false
    ReconcileResponse:
      properties:
        active_seats:
          type: integer
          title: Active Seats
        to_cancel:
          items:
            $ref: '#/components/schemas/ReconcileToCancelItem'
          type: array
          title: To Cancel
        missing_refs:
          items:
            type: string
          type: array
          title: Missing Refs
        applied:
          type: boolean
          title: Applied
        cancelled_count:
          type: integer
          title: Cancelled Count
        threshold_exceeded:
          type: boolean
          title: Threshold Exceeded
      type: object
      required:
      - active_seats
      - to_cancel
      - missing_refs
      - applied
      - cancelled_count
      - threshold_exceeded
      title: ReconcileResponse
      description: The reconcile diff plus what (if anything) was applied.
      example:
        active_seats: 42
        applied: false
        cancelled_count: 0
        missing_refs:
        - newjoiner@example.com
        threshold_exceeded: false
        to_cancel:
        - email: gone@example.com
          sponsorship_id: 665f00000000000000000042
    ReconcileToCancelItem:
      properties:
        sponsorship_id:
          type: string
          title: Sponsorship Id
        email:
          type: string
          title: Email
      type: object
      required:
      - sponsorship_id
      - email
      title: ReconcileToCancelItem
      description: One seat the partner no longer lists (a cancel candidate).
    SeaTimeRecentResponse:
      properties:
        user_id:
          type: string
          title: User Id
        months:
          items:
            $ref: '#/components/schemas/_RecentMonth'
          type: array
          title: Months
          description: Most-recent month first; max 12.
        record_updated_at:
          type: string
          format: date-time
          title: Record Updated At
      type: object
      required:
      - user_id
      - months
      - record_updated_at
      title: SeaTimeRecentResponse
      description: Rolling 12-month trend of declared sea time.
      example:
        months:
        - days_at_sea: 18
          month: 2026-05
          nautical_miles: 1240
        - days_at_sea: 22
          month: 2026-04
          nautical_miles: 1480
        record_updated_at: '2026-05-22T14:21:00Z'
        user_id: 65a1f0e2c3b4d5e6f7a8b9c0
    SeaTimeResponse:
      properties:
        user_id:
          type: string
          title: User Id
        totals:
          $ref: '#/components/schemas/_DeclaredTotals'
        verified:
          $ref: '#/components/schemas/_VerifiedTotals'
        by_role:
          items:
            $ref: '#/components/schemas/_RoleBreakdown'
          type: array
          title: By Role
        record_updated_at:
          type: string
          format: date-time
          title: Record Updated At
      type: object
      required:
      - user_id
      - totals
      - verified
      - by_role
      - record_updated_at
      title: SeaTimeResponse
      description: Aggregate sea-time totals for the authenticated user.
      example:
        by_role:
        - days: 320
          role: Deckhand
        - days: 480
          role: Bosun
        - days: 1042
          role: Mate
        record_updated_at: '2026-05-22T14:21:00Z'
        totals:
          days_at_sea: 1842
          nautical_miles: 87423
          vessels_served: 14
          years_active: 6.2
        user_id: 65a1f0e2c3b4d5e6f7a8b9c0
        verified:
          days_at_sea: 1680
          nautical_miles: 79215
    SponsorshipItem:
      properties:
        sponsorship_id:
          type: string
          title: Sponsorship Id
        email:
          type: string
          title: Email
        crew_name:
          anyOf:
          - type: string
          - type: 'null'
          title: Crew Name
        status:
          type: string
          title: Status
        user_id:
          anyOf:
          - type: string
          - type: 'null'
          title: User Id
          description: "The member's DSS user id \u2014 the same id delivered in ``user.sea_time.updated``\
            \ webhooks. Null until the seat is active."
        external_ref:
          anyOf:
          - type: string
          - type: 'null'
          title: External Ref
        invited_at:
          anyOf:
          - type: string
            format: date-time
          - type: 'null'
          title: Invited At
        activated_at:
          anyOf:
          - type: string
            format: date-time
          - type: 'null'
          title: Activated At
        expires_at:
          anyOf:
          - type: string
            format: date-time
          - type: 'null'
          title: Expires At
      type: object
      required:
      - sponsorship_id
      - email
      - status
      title: SponsorshipItem
      description: "Public projection of one sponsored seat \u2014 no token hash,\
        \ no Stripe ids."
      example:
        activated_at: '2026-07-04T10:30:00Z'
        crew_name: Jane Doe
        email: jane@example.com
        expires_at: '2027-07-04T10:30:00Z'
        external_ref: ywc-beta-0042
        invited_at: '2026-07-03T09:00:00Z'
        sponsorship_id: 665f00000000000000000042
        status: active
    SponsorshipList:
      properties:
        items:
          items:
            $ref: '#/components/schemas/SponsorshipItem'
          type: array
          title: Items
        next_cursor:
          anyOf:
          - type: string
          - type: 'null'
          title: Next Cursor
      type: object
      required:
      - items
      title: SponsorshipList
      description: A page of sponsorships plus the opaque cursor for the next page.
      example:
        items:
        - activated_at: '2026-07-04T10:30:00Z'
          crew_name: Jane Doe
          email: jane@example.com
          expires_at: '2027-07-04T10:30:00Z'
          external_ref: ywc-beta-0042
          invited_at: '2026-07-03T09:00:00Z'
          sponsorship_id: 665f00000000000000000042
          status: active
    SsoTicketRequest:
      properties:
        user_id:
          type: string
          minLength: 1
          title: User Id
          description: DSS user id from Connect (Firebase uid).
      type: object
      required:
      - user_id
      title: SsoTicketRequest
      description: Ask DSS for a one-time login URL for an already-Connected member.
      example:
        user_id: aQ0mtT8kRgVv7C3o1p2q3r4s5t6u
    SsoTicketResponse:
      properties:
        login_url:
          type: string
          title: Login Url
        expires_in:
          type: integer
          title: Expires In
          description: Seconds until the ticket stops working.
      type: object
      required:
      - login_url
      - expires_in
      title: SsoTicketResponse
      description: Redirect the member's browser to ``login_url`` immediately.
      example:
        expires_in: 60
        login_url: https://app.digitalseaservice.com/auth/partner?ticket=abc
    TestResponse:
      properties:
        event_id:
          type: string
          title: Event Id
        delivery_id:
          type: string
          title: Delivery Id
        target_url:
          type: string
          title: Target Url
      type: object
      required:
      - event_id
      - delivery_id
      - target_url
      title: TestResponse
      description: Result of firing a ``webhook.test`` event.
      example:
        delivery_id: dlv_77f1bb31c0c64e6db4f5e8a9c2d3e4f5
        event_id: evt_3f4a9c8e2b1d4f5a8c9e0d1f2a3b4c5d
        target_url: https://yachtworkerscouncil.com/integrations/dss/webhook
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
            - type: string
            - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
        input:
          title: Input
        ctx:
          type: object
          title: Context
      type: object
      required:
      - loc
      - msg
      - type
      title: ValidationError
    VesselsResponse:
      properties:
        user_id:
          type: string
          title: User Id
        vessels:
          items:
            $ref: '#/components/schemas/_VesselPeriod'
          type: array
          title: Vessels
        next_cursor:
          anyOf:
          - type: string
          - type: 'null'
          title: Next Cursor
          description: Opaque cursor for the next page. ``null`` when the page is
            the last one.
        record_updated_at:
          type: string
          format: date-time
          title: Record Updated At
          description: "Most-recently-updated crew-list record for the user. Same\
            \ value across every page of a stable scan \u2014 drives the page-stable\
            \ ``ETag``."
      type: object
      required:
      - user_id
      - vessels
      - record_updated_at
      title: VesselsResponse
      description: Page of vessel periods for the authenticated user.
      example:
        next_cursor: eyJsYXN0X2lkIjoiMDFIVjlYS...
        record_updated_at: '2026-05-22T14:21:00Z'
        user_id: 65a1f0e2c3b4d5e6f7a8b9c0
        vessels:
        - days: 99
          id: 69961ffbbd7419b471faab60
          imo: '1012725'
          name: M/V Endeavour
          nautical_miles: 4280
          period_end: '2025-04-20T00:00:00Z'
          period_start: '2025-01-12T00:00:00Z'
          role: Chief Officer
    _DeclaredTotals:
      properties:
        days_at_sea:
          type: integer
          minimum: 0.0
          title: Days At Sea
          description: Total days the crew has logged at sea.
        nautical_miles:
          type: integer
          minimum: 0.0
          title: Nautical Miles
          description: Total nautical miles travelled.
        vessels_served:
          type: integer
          minimum: 0.0
          title: Vessels Served
          description: Distinct vessels served.
        years_active:
          type: number
          minimum: 0.0
          title: Years Active
          description: Earliest-to-latest span in years.
      type: object
      required:
      - days_at_sea
      - nautical_miles
      - vessels_served
      - years_active
      title: _DeclaredTotals
      description: Crew-declared aggregate sea time.
      example:
        days_at_sea: 1842
        nautical_miles: 87423
        vessels_served: 14
        years_active: 6.2
    _RecentMonth:
      properties:
        month:
          type: string
          pattern: ^\d{4}-(0[1-9]|1[0-2])$
          title: Month
          description: ISO year-month.
        days_at_sea:
          type: integer
          minimum: 0.0
          title: Days At Sea
        nautical_miles:
          type: integer
          minimum: 0.0
          title: Nautical Miles
      type: object
      required:
      - month
      - days_at_sea
      - nautical_miles
      title: _RecentMonth
      description: One month's contribution to the rolling 12-month trend.
      example:
        days_at_sea: 18
        month: 2026-05
        nautical_miles: 1240
    _RoleBreakdown:
      properties:
        role:
          type: string
          title: Role
        days:
          type: integer
          minimum: 0.0
          title: Days
      type: object
      required:
      - role
      - days
      title: _RoleBreakdown
      description: Days at sea served in a single role.
      example:
        days: 480
        role: Bosun
    _VerifiedTotals:
      properties:
        days_at_sea:
          type: integer
          minimum: 0.0
          title: Days At Sea
        nautical_miles:
          type: integer
          minimum: 0.0
          title: Nautical Miles
      type: object
      required:
      - days_at_sea
      - nautical_miles
      title: _VerifiedTotals
      description: Independently confirmed subset of the declared totals.
      example:
        days_at_sea: 1680
        nautical_miles: 79215
    _VesselPeriod:
      properties:
        id:
          type: string
          title: Id
          description: Stable identifier for this vessel period (crew-list id).
        name:
          type: string
          title: Name
        imo:
          anyOf:
          - type: string
          - type: 'null'
          title: Imo
          description: IMO number as a string when DSS holds it, else ``null``.
        role:
          anyOf:
          - type: string
          - type: 'null'
          title: Role
          description: Rank/position served in this period.
        period_start:
          type: string
          format: date-time
          title: Period Start
        period_end:
          anyOf:
          - type: string
            format: date-time
          - type: 'null'
          title: Period End
          description: '``null`` while still aboard.'
        days:
          type: integer
          minimum: 0.0
          title: Days
          description: Qualifying sea-service days logged in the period.
        nautical_miles:
          type: integer
          minimum: 0.0
          title: Nautical Miles
      type: object
      required:
      - id
      - name
      - period_start
      - days
      - nautical_miles
      title: _VesselPeriod
      description: A single period the crew member served on a vessel.
      example:
        days: 99
        id: 69961ffbbd7419b471faab60
        imo: '1012725'
        name: M/V Endeavour
        nautical_miles: 4280
        period_end: '2025-04-20T00:00:00Z'
        period_start: '2025-01-12T00:00:00Z'
        role: Chief Officer
    ProblemDetails:
      type: object
      description: RFC 7807 Problem Details. Returned with the `application/problem+json`
        content type on every resource (`/v1/*`) error.
      properties:
        type:
          type: string
          format: uri
          description: Stable URL identifying the error type; resolves to its docs
            page.
        title:
          type: string
          description: Short, human-readable summary of the error type.
        status:
          type: integer
          description: HTTP status code.
        detail:
          type: string
          description: Explanation specific to this occurrence.
        instance:
          type: string
          description: Request correlation id; matches the `X-Request-Id` header.
      required:
      - type
      - title
      - status
      - detail
      - instance
    ValidationProblemDetails:
      description: Problem Details for a 422, carrying a per-field `errors` array.
      allOf:
      - $ref: '#/components/schemas/ProblemDetails'
      - type: object
        properties:
          errors:
            type: array
            description: One entry per field that failed validation.
            items:
              type: object
              additionalProperties: true
    UserInfo:
      type: object
      description: OIDC-style basic profile returned by `/oauth/userinfo`. Profile
        fields are present only when the token carries the `profile:read` scope.
      properties:
        sub:
          type: string
          description: Stable DSS user identifier (subject).
        name:
          type:
          - string
          - 'null'
        role:
          type:
          - string
          - 'null'
        country:
          type:
          - string
          - 'null'
        picture:
          type:
          - string
          - 'null'
          description: Profile photo URL.
      required:
      - sub
  securitySchemes:
    oauth2:
      type: oauth2
      description: OAuth 2.0 Authorization Code flow with PKCE. See the integration
        guide at docs.digitalseaservice.com for the full step-by-step.
      flows:
        authorizationCode:
          authorizationUrl: https://api.digitalseaservice.com/oauth/authorize
          tokenUrl: https://api.digitalseaservice.com/oauth/token
          refreshUrl: https://api.digitalseaservice.com/oauth/token
          scopes:
            profile:read: Read the crew member's name, role, country, and photo URL.
            seatime:read: Read aggregate sea-time totals and the 12-month trend.
            vessels:read: Read the crew member's vessel history.
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Opaque
    basicAuth:
      type: http
      scheme: basic
      description: HTTP Basic for `/v1/partners/*`. Username is the partner `client_id`;
        password is the `client_secret`.
servers:
- url: https://api.digitalseaservice.com
  description: Production
- url: https://api.dev.digitalseaservice.com
  description: Sandbox (dev)
