{"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"}}],"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` — bearer token missing, malformed, expired, or revoked.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"`insufficient_scope` — the token lacks the required scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"`rate_limit_exceeded` — per-client request budget exhausted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"`not_found` — no matching record for the authenticated user.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"`internal_error` — 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` — bearer token missing, malformed, expired, or revoked.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"`insufficient_scope` — the token lacks the required scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"`rate_limit_exceeded` — per-client request budget exhausted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"`not_found` — no matching record for the authenticated user.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"`internal_error` — 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` — bearer token missing, malformed, expired, or revoked.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"`insufficient_scope` — the token lacks the required scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"`rate_limit_exceeded` — per-client request budget exhausted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"`not_found` — no matching record for the authenticated user.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"`internal_error` — 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` — request validation failed; see the `errors` array.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ValidationProblemDetails"}}}},"401":{"description":"`invalid_token` — bearer token missing, malformed, expired, or revoked.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"`insufficient_scope` — the token lacks the required scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"`rate_limit_exceeded` — per-client request budget exhausted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"400":{"description":"`invalid_request` — a query parameter (e.g. cursor) was malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"`internal_error` — 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` — bearer token missing, malformed, expired, or revoked.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"`partner_suspended` — the calling partner has been suspended.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"`not_found` — the partner record backing the token was not found.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"`no_webhook_configured` — no webhook URL or signing secret is registered.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"500":{"description":"`internal_error` — an unexpected error occurred; reference `instance`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}},"security":[{"bearerAuth":[]},{"oauth2":[]}]}}},"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"},"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 — 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 — 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"}},"type":"object","required":["user_id","name","record_updated_at"],"title":"MeResponse","description":"The authenticated user's profile.","example":{"name":"Alex Crew","record_updated_at":"2026-05-26T09:14:00Z","role":"Chief Officer","user_id":"sFmjAD7I8bQexmTiTbZZQwq10X43"}},"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}}},"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 — 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":"http://localhost:8080/oauth/authorize","tokenUrl":"http://localhost:8080/oauth/token","refreshUrl":"http://localhost:8080/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"}}},"servers":[{"url":"https://api.digitalseaservice.com","description":"Production"},{"url":"https://api.dev.digitalseaservice.com","description":"Sandbox (dev)"}]}