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.

POSTmcp.portfoliodividendtracker.com/oauth/registerReady

Register your app

You get a client ID right away. No account or approval needed: investors decide which apps get access to their portfolio.

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 (HTML)
<!-- 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 reference
  1. Register 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"]
      }'
  2. 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 only read if 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(/=+$/, '');
    }
  3. Exchange the code for tokens

    PDT redirects back with code and state. Check that statematches, 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=$VERIFIER
  4. Call the API

    Send the access token as a bearer token. Every endpoint lives underhttps://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 get403 API_ACCESS_PAUSED until they renew. They don't need to connect your app again.

  5. 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