Vacation Tracker Docs
OAuth2 Authorization

Endpoints

Complete reference for every OAuth2 endpoint.

Canonical base URL:

https://auth.vacationtracker.io

The original host https://api.app.vacationtracker.io/oauth/* stays a permanent compatibility mapping onto the same routes, used by the legacy flow. Everything below works identically on both hosts; examples use the canonical URL.

GET /.well-known/oauth-authorization-server

RFC 8414 authorization server metadata. OAuth libraries with RFC 8414 support discover every other endpoint and capability from this URL automatically, with no hard-coded URLs.

Success response (200)

{
  "issuer": "https://cognito-idp.eu-central-1.amazonaws.com/<user-pool-id>",
  "authorization_endpoint": "https://auth.vacationtracker.io/oauth/authorize",
  "token_endpoint": "https://auth.vacationtracker.io/oauth/token",
  "registration_endpoint": "https://auth.vacationtracker.io/oauth/register",
  "userinfo_endpoint": "https://auth.vacationtracker.io/oauth/me",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post", "none"],
  "scopes_supported": ["openid", "email", "profile", "mcp:workspace:read", "mcp:users:read", "mcp:leaves:read", "zapier:webhooks"]
}

issuer is the Cognito user pool URL so strict MCP clients that cross-check the JWT iss claim against metadata don't reject tokens.


POST /oauth/register

Dynamic Client Registration (RFC 7591). Registers a public client without human intervention. Used by MCP clients like Claude Desktop and Cursor on first launch.

Request body

{
  "client_name": "My Desktop App",
  "redirect_uris": ["http://127.0.0.1:53126/callback"]
}
FieldRequiredDescription
client_nameYesHuman-readable name shown on the consent screen
redirect_urisYesArray of allowed redirect URIs. Loopback (http://127.0.0.1:{port}) is matched port-agnostically per RFC 8252; all other URIs must match exactly.
grant_typesNoDefaults to ["authorization_code", "refresh_token"]. Only these two are supported.
response_typesNoDefaults to ["code"].
token_endpoint_auth_methodNoDefaults to none (public client). Other values are rejected.

Success response (201)

{
  "client_id": "6a8cd456-...",
  "client_name": "My Desktop App",
  "redirect_uris": ["http://127.0.0.1:53126/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

No client_secret is returned. PKCE replaces it.

Rate limits

The endpoint is rate-limited per source IP to prevent abuse. Clients registering once on install stay well under the limit; repeated re-registration from the same IP will be throttled.

Error response (400)

{ "error": "invalid_redirect_uri", "error_description": "…" }
Error codeCause
invalid_client_metadataMissing client_name or malformed payload
invalid_redirect_uriredirect_uris missing, empty, or contains a URL that doesn't conform to the registration rules
invalid_token_endpoint_auth_methodValue other than none requested

GET /oauth/authorize

Initiates the authorization code flow. Redirect the user's browser here.

Request parameters (query string)

ParameterRequiredDescription
client_idYesYour registered OAuth client ID
redirect_uriYesMust match a registered redirect URI
stateYesOpaque CSRF token, returned unchanged on the callback
scopeNoSpace-separated scopes. Defaults to openid
response_typeNoMust be code. Defaults to code
code_challengeRequired for public clients, optional for confidentialBase64url-encoded SHA-256 hash of the verifier
code_challenge_methodRequired when code_challenge is presentMust be S256

Success response

HTTP/1.1 302 Found
Location: https://app.vacationtracker.io/oauth/authorize?session={sessionId}
Cache-Control: no-store

The user lands on the Vacation Tracker login and consent page. After consent, they're redirected back to your redirect_uri with a code and the state you sent.

Error response (400)

{
  "error": "invalid_client | invalid_request | invalid_scope | unsupported_response_type",
  "error_description": "Human-readable description"
}
Error codeCause
invalid_clientUnknown client_id
invalid_requestMissing parameter, unregistered redirect_uri, or code_challenge_method not S256
invalid_scopeRequested scope not allowed for this client
unsupported_response_typeresponse_type is not code

POST /oauth/token

Exchanges an authorization code for tokens, or refreshes an existing token.

Client authentication

Client typeMethod
Publicclient_id in body; no secret; PKCE code_verifier required
Confidential (body)client_id + client_secret as form fields
Confidential (Basic)Authorization: Basic base64(client_id:client_secret)

Confidential clients can also include code_verifier. If Step 1 sent a code_challenge, the server validates it.

Content types

  • application/x-www-form-urlencoded (standard)
  • application/json

Authorization code exchange

ParameterRequiredDescription
grant_typeYesauthorization_code
codeYesThe authorization code from the callback
redirect_uriYesMust match the URI used in the authorization request
client_idYesVia body or Basic auth
client_secretConfidential onlyVia body or Basic auth
code_verifierRequired if code_challenge was sent at authorizeOriginal PKCE verifier

Success response (200):

{
  "access_token": "eyJ...",
  "id_token": "eyJ...",
  "refresh_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Token refresh

ParameterRequiredDescription
grant_typeYesrefresh_token
refresh_tokenYesThe refresh token from a previous token response
client_idYesVia body or Basic auth
client_secretConfidential onlyVia body or Basic auth

Success response (200):

{
  "access_token": "eyJ...",
  "refresh_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600
}

The same refresh token is echoed back. No id_token is included in refresh responses. Refresh tokens are not rotated.

Error response (400)

{
  "error": "invalid_client | invalid_grant | unsupported_grant_type | server_error",
  "error_description": "Human-readable description"
}
Error codeCause
invalid_clientUnknown client_id, incorrect client_secret for a confidential client, or PKCE required but missing
invalid_grantAuthorization code is invalid, expired, or already used. redirect_uri / client_id mismatch. Refresh token invalid/expired. PKCE verifier does not match the challenge.
unsupported_grant_typeOnly authorization_code and refresh_token are supported
server_errorUnexpected server-side error

Response headers

All token responses include:

Cache-Control: no-store
Pragma: no-cache

GET /oauth/me

Returns basic profile information for the authenticated user.

Authentication

Requires a valid access token in the Authorization header:

Authorization: Bearer {access_token}

Success response (200)

{
  "id": "vt-user-123",
  "name": "Jane Doe",
  "email": "jane.doe@example.com"
}
FieldTypeDescription
idstringVacation Tracker user ID
namestringUser's full name
emailstringUser's email address

Error responses

StatusError codeCause
401unauthorizedMissing or invalid Authorization header
401invalid_tokenToken is missing required claims

Token lifetimes

TokenLifetime
Access token60 minutes
ID token60 minutes
Refresh token365 days
Authorization code10 minutes (single-use)

On this page