{
  "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).\n\nPartner-scoped: only this partner's live seats are considered. Safety rails:\nan empty ``active_refs`` is a 400; a mass-cancel over the threshold is refused\nunless ``override_threshold`` is set; nothing is cancelled unless ``apply`` is\ntrue. 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\nThe email must already have a Digital Sea Service account (normal signup).\nNew 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.\n\n``active_refs`` is the partner's own list of currently-active member\nidentifiers (each matched against a seat's ``email`` AND ``external_ref``).\nAn **empty** list is rejected (400) so a bad/empty export can never be read\nas \"cancel everyone\". By default this is a DRY RUN (``apply=false``): the diff\nis returned and nothing is cancelled. A mass-cancel that exceeds ``max_cancel``\n(or the default safety fraction) is refused unless ``override_threshold`` is\ntrue.",
        "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)"
    }
  ]
}