OAuth Integration Guide

Castle uses the OAuth 2.0 authorization code flow with PKCE. A partner app redirects the user to Castle to approve a set of scopes, receives an authorization code on the registered redirect URI, exchanges that code (plus the PKCE code verifier) for tokens, and then calls scoped Castle API endpoints with the resulting access token.

Endpoints

PurposeURL
Authorize (consent)https://dashboard.savewithcastle.com/oauth
API basehttps://api.savewithcastle.com
Token exchangehttps://api.savewithcastle.com/oauth/token

Scopes

Requested as a space-separated list on the authorize URL, e.g.:

cash:account:read cash:address:write
ScopeGrants
cash:account:readView the USDC deposit address associated with the account (e.g: to send loan proceeds)
cash:address:writeWhitelist a USDC address on the account so funds can be sent there (e.g. to pay off a loan)

PKCE

Castle requires PKCE on the authorization code exchange, using the S256 method:

  • Partner generates a code_verifier — a high-entropy random string (43–128 characters, URL-safe).
  • Partner derives code_challenge = BASE64URL(SHA256(code_verifier)) and sets code_challenge_method=S256.
  • code_challenge and code_challenge_method are sent on the authorize request — no client_secret required for this.
  • The original code_verifier is sent on the token exchange request, alongside the authorization code and client_secret, so Castle can validate the pairing.

Quick generation (Node.js):

const crypto = require('crypto');

const codeVerifier = crypto.randomBytes(32).toString('base64url'); // 43 chars, URL-safe
const codeChallenge = crypto
  .createHash('sha256')
  .update(codeVerifier)
  .digest('base64url');

Quick generation (Python):

import base64, hashlib, secrets

code_verifier = secrets.token_urlsafe(32)
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b'=').decode()

Flow

  1. Configure — Partner generates the PKCE code_verifier/code_challenge pair, then builds the authorize URL using client_id, redirect_uri, requested scopes, code_challenge, and code_challenge_method.
  2. Authorize — Partner redirects the user to that authorize URL to open the Castle consent screen; user reviews and approves the requested scopes.
  3. Callback — Castle redirects back to the partner's registered redirect_uri with an authorization code.
  4. Tokens — Partner exchanges the code (plus the original code_verifier and client_secret) for an access token + refresh token via POST /oauth/token. The access token expires in 30 days; the refresh token is valid for 1 year and rotates on each use.
  5. API — Partner calls scoped Castle endpoints with Authorization: Bearer {access_token}, e.g. GET /v1/cash/deposit-info or POST /v1/cash/address.

Diagram


Token lifetimes

TokenLifetime
Access token30 days
Refresh token1 year

Once the access token expires, use the refresh token to get a new one without re-running the full authorize/consent flow. Refresh tokens rotate on use — each refresh call issues a new refresh token, so the partner should store the latest one and discard the old. Once a refresh token expires (1 year from issuance if unused), the partner needs to send the user back through Authorize/Callback.

Example API calls

Fetch deposit info

GET /v1/cash/deposit-info
Authorization: Bearer {access_token}

Add a cash address

POST /v1/cash/address
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "address": "…",
  "label": "…"
}