This walks you from “I have credentials” to “I can read a crew member’s profile” in about 15 minutes. We use the sandbox throughout. The flow is identical in production — swap the base URL.
1

Have your sandbox credentials handy

During onboarding you’ll have received:
  • client_id (e.g. ywc_a1b2c3d4)
  • client_secret
  • webhook_secret (only if you registered a webhook URL)
  • One or more registered redirect_uris
If you don’t have these yet, email us — sandbox is issued same-day.
2

Generate a PKCE pair

Every authorization request needs a fresh code_verifier / code_challenge pair. The verifier stays in your server; the challenge goes on the wire. See Authentication for samples in other languages.
CODE_VERIFIER=$(python -c 'import secrets;print(secrets.token_urlsafe(64)[:64])')
CODE_CHALLENGE=$(python -c "
import base64, hashlib, sys
v = sys.argv[1]
print(base64.urlsafe_b64encode(hashlib.sha256(v.encode()).digest()).rstrip(b'=').decode())
" "$CODE_VERIFIER")
echo "verifier=$CODE_VERIFIER"
echo "challenge=$CODE_CHALLENGE"
Store the code_verifier against the user’s session — you’ll need it in step 4.
3

Send the crew member to /oauth/authorize

Build this URL and redirect the user’s browser to it. The DSS dashboard handles sign-in and shows them a consent screen listing the scopes you requested. On approval they’re redirected back to your redirect_uri with a one-shot code and the state you passed.
https://api.dev.digitalseaservice.com/oauth/authorize?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.example.com/integrations/dss/callback
  &scope=profile:read+seatime:read+vessels:read
  &state=RANDOM_PER_REQUEST_CSRF_TOKEN
  &code_challenge=GENERATED_IN_STEP_2
  &code_challenge_method=S256
Validate state on the callback before doing anything else. If it doesn’t match the value you sent, drop the request — it’s the standard defence against CSRF on the OAuth dance.
4

Exchange the code for tokens

The user’s browser hits your redirect_uri with ?code=...&state=.... Your server exchanges the code for an access token + refresh token.
curl -sS -X POST https://api.dev.digitalseaservice.com/oauth/token \
  -d grant_type=authorization_code \
  -d code="$CODE" \
  -d redirect_uri=https://yourapp.example.com/integrations/dss/callback \
  -d client_id="$CLIENT_ID" \
  -d client_secret="$CLIENT_SECRET" \
  -d code_verifier="$CODE_VERIFIER"
Response:
{
  "access_token": "...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "...",
  "scope": "profile:read seatime:read vessels:read"
}
Store the tokens against the user record on your side. The refresh_token rotates on every use — when you refresh, swap the stored value for the new one immediately. See Authentication / refreshing.
5

Call /v1/me

You now have an access token. Try it against the simplest endpoint.
curl -sS -H "Authorization: Bearer $ACCESS_TOKEN" \
  https://api.dev.digitalseaservice.com/v1/me
Successful response:
{
  "user_id": "65a1f0e2c3b4d5e6f7a8b9c0",
  "name": "Alex Crew",
  "role": "Mate",
  "country": "GB",
  "photo_url": "https://cdn.digitalseaservice.com/u/65a1f0e2.jpg",
  "record_updated_at": "2026-05-22T14:21:00Z"
}
If you see this — you’re integrated.

What to build next

In rough order of value:
  1. Render the data in your UI. Pull /v1/me/sea-time for the dashboard totals and /v1/me/vessels for the history list. Each returns record_updated_at and supports conditional GETs — use them.
  2. Wire conditional GETs. Cache the ETag and replay it on the next request as If-None-Match. A 304 costs you nothing against your rate limit budget beyond a single round trip.
  3. Register a webhook URL so you don’t have to poll. See Webhooks.
  4. Add the disconnect path. Call POST /oauth/revoke with the user’s token when they want to disconnect from your side (revoking one token revokes the whole pair), and handle user.consent.revoked webhooks for when they disconnect from ours.

Common gotchas

Three causes account for ~all of these. The authorization code expired (60-second TTL — exchange immediately), the code was already used (codes are single-use), or the redirect_uri you passed at token exchange doesn’t exactly match the one you passed at authorize. Trailing slashes count.
Access tokens are 1-hour TTL. Refresh them with grant_type=refresh_token. The previous access + refresh pair is revoked atomically when you refresh — if you keep using the old refresh token, you’ll lose the connection and the user will have to reconsent. See Authentication / refreshing.
Your access token was issued without the scope this endpoint needs. Send the user back through the authorize flow with the missing scope requested. Refresh cannot widen scope by design.

Going to production

Once your sandbox integration is working end-to-end (auth, all four read endpoints, at least one webhook handled), email admin@digitalseaservice.com to schedule the integration review. We check your redirect URI is HTTPS, your webhook handler verifies signatures correctly, and that you’ve covered the disconnect path. Production credentials are issued same-day on approval.