Build on Portfolio Dividend Tracker
Register your app, let investors sign in with their PDT account, and read or update their portfolio through the API.
Your client ID
Registration is anonymous, so the client isn't linked to a PDT account. Store the client ID with your app's configuration. The code samples below now use it.
Public client
No client secret. PKCE with S256 protects the code exchange.
Scopes
read for portfolio data, write to create, update and delete it.
Token lifetimes
Access tokens last 1 hour, refresh tokens 30 days.
Built for agents
Point your agent at llms-full.txt: this whole flow, written for agents.
Sign in with PDT
A button investors recognise. Plain HTML with inline styles: no fonts, images or stylesheets to load, so it drops into any app.
<!-- Link to the route of your app that starts step 2. On a dark background: color: #020708; background: #fff -->
<a href="/login" style="display: inline-flex; align-items: center; gap: 10px; height: 48px; padding: 0 22px; border-radius: 16px; font: 500 16px/1 system-ui, sans-serif; color: #fff; background: #020708; text-decoration: none;">
Sign in with
<svg width="37" height="20" viewBox="0 0 37 20" fill="currentColor" role="img" aria-label="PDT" style="margin-top: 6px;"><path d="M33.996 15.907a1 1 0 01-.787 1.071l-.116.018-32 3a1 1 0 01-.302-1.974l.116-.018 32-3a1 1 0 011.089.903zM16.979 0l.116.005a1 1 0 01.9 1.09 336.33 336.33 0 00-.566 6.576l-.018.237.004.036a.994.994 0 01-.013.226l-.015.065-.118 1.624-.146 2.204-.069 1.087-2.383-.359-1.394-.224-.718-.126-.432-.082-.397-.081-.363-.082-.33-.082-.3-.084-.269-.087a4.506 4.506 0 01-.456-.182l-.19-.097c-.443-.25-.665-.55-.766-.949l-.035-.165-.013-.087-.007-.17c.048-1.207 1.469-1.978 4.02-2.617l.585-.14.621-.137.658-.133.578-.111.146-1.832c.061-.733.124-1.466.19-2.202l.206-2.216A1 1 0 0116.979 0zM9.87-.004l.284.01.133.007.248.024.226.034.104.02.194.05.173.059c.272.107.463.257.594.452l.037.06.108.187.022.214c.054.536-.261 1.055-1.039 1.635l-.247.176a8.47 8.47 0 01-.134.09l-.29.181c-.05.031-.103.062-.156.093l-.334.19-.178.096-.378.198-.201.101-.425.207-.457.214-.489.221-.521.229-.555.237-.895.372-.974.393-1.422.56-.362.139-.17 1.317-.14 1.016-.091.607-.092.562-.094.53-.098.51-.052.249-.11.497-.119.505a1 1 0 01-1.942-.476l.119-.504.055-.25.105-.503.099-.519.047-.269.093-.566.093-.613L.785 7.5l.053-.398-.04-.006a1 1 0 01-.685-.518l-.048-.106a1 1 0 01.58-1.29l.46-.176.164-1.193.088-.59.09-.546.094-.528.09-.475.135-.683L4.235.599 5.787.37l.829-.112.755-.093.464-.05.64-.06.39-.029L9.396 0l.322-.005h.152zM31.994.89a1 1 0 01-.884 1.104l-3.157.35-.046.245-.066.39-.057.394-.051.407-.046.43-.041.463-.038.506-.055.86-.114 2.124-.06.932-.044.548-.048.505-.053.473-.06.453-.034.222-.074.444-.085.453a1 1 0 01-1.962-.386l.04-.21.073-.41.063-.412.056-.422.05-.444.044-.478.041-.522.058-.89.114-2.122.038-.614.06-.817.068-.72.052-.446.058-.428.047-.297-3.77.419a1 1 0 11-.221-1.988l9-1a1 1 0 011.104.884zM15.31 9.227l-.058.013c-1.016.21-1.961.445-2.725.688-.227.073-.436.145-.624.216l-.112.044.275.071.236.057.444.098.476.097.523.096.583.1.871.14.075-1.127.036-.493zM8.018 2.124l-.269.029-.68.078-.744.095-.812.112-.879.127-1.169.179-.068.395-.098.62-.062.417 1.232-.495.807-.334.381-.16.718-.31.337-.148.63-.284.676-.321zM36 2a1 1 0 110 2 1 1 0 010-2z"/></svg>
</a>From client ID to API call
The standard authorization code flow with PKCE. Any OAuth library that supports public clients works; these are the raw requests.
API referenceRegister your app
Use the form above, or register from your own code: it's a single unauthenticated request (dynamic client registration).
Register curl -X POST https://mcp.portfoliodividendtracker.com/oauth/register \ -H "Content-Type: application/json" \ -d '{ "client_name": "My dividend dashboard", "redirect_uris": ["https://your-app.com/callback"] }'Send the investor to PDT
Create a PKCE verifier and challenge, then redirect to the authorize endpoint, for example from aSign in with PDT button. The investor signs in and approves your app. Request
read write, or onlyreadif your app doesn't change data.Authorize (browser JavaScript) const verifier = base64url(crypto.getRandomValues(new Uint8Array(32))); const challenge = base64url(new Uint8Array( await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)), )); const state = crypto.randomUUID(); // Keep verifier and state (e.g. in the session) for the callback. const url = new URL('https://auth.portfoliodividendtracker.com/oauth/authorize'); url.search = new URLSearchParams({ client_id: 'YOUR_CLIENT_ID', redirect_uri: 'https://your-app.com/callback', response_type: 'code', scope: 'read write', state, code_challenge: challenge, code_challenge_method: 'S256', }); location.assign(url); function base64url(bytes) { return btoa(String.fromCharCode(...bytes)) .replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, ''); }Exchange the code for tokens
PDT redirects back with
codeandstate. Check thatstatematches, then trade the code and your verifier for an access token and a refresh token.Token exchange curl https://auth.portfoliodividendtracker.com/oauth/token \ -d grant_type=authorization_code \ -d client_id=YOUR_CLIENT_ID \ -d redirect_uri=https://your-app.com/callback \ -d code=$CODE \ -d code_verifier=$VERIFIERCall the API
Send the access token as a bearer token. Every endpoint lives under
https://api.portfoliodividendtracker.com/v1.Portfolio value, gains and cash curl https://api.portfoliodividendtracker.com/v1/portfolio \ -H "Authorization: Bearer $ACCESS_TOKEN"API access comes with a paid PDT subscription. When an investor's subscription ends, requests get
403 API_ACCESS_PAUSEDuntil they renew. They don't need to connect your app again.Refresh before it expires
Access tokens expire after an hour. Use the refresh token to get a new pair without asking the investor again.
Refresh curl https://auth.portfoliodividendtracker.com/oauth/token \ -d grant_type=refresh_token \ -d client_id=YOUR_CLIENT_ID \ -d refresh_token=$REFRESH_TOKEN