Accrawl API

Integrate your app with Accrawl

Use Connect with Accrawl to let a user approve access to specific connections and data. Your app receives a scoped token; it never receives the user’s Accrawl password or financial-institution credentials.

Register your app

Register your app

Organisation administrators register and manage their organisation’s apps in the Accrawl dashboard. Registration returns a client ID and, for a confidential app, a client secret that is shown once.

Register or manage apps

Confidential client

A server-side web app or service that can protect a secret.

Uses a client ID, a client secret, and PKCE.

Public client

A native or installed app that cannot protect a secret.

Uses a client ID and PKCE. Public clients do not receive a client secret.

Never put a confidential client secret in browser or mobile code.

Accrawl API

Authorization Code with PKCE

These steps assume a confidential client with a backend. A public client with no backend keeps the verifier and state in secure app storage, and calls the token endpoint directly without a client secret.

  1. Create a fresh PKCE verifier, its S256 challenge, and a random state value. Store the verifier and state with the authorisation attempt.

  2. Send the user to Accrawl’s authorisation page. They sign in, review the requested access, choose which connections to share, and approve or deny.

  3. At your callback URL, fail the request if the response carries an error parameter, or if the returned state doesn’t match the value you stored. Only then exchange the authorization code—along with the PKCE verifier you generated for this request—for tokens. The authorization code can be redeemed just once.

  4. Store both tokens encrypted at rest. Send the access token as a Bearer credential, and replace both stored tokens after every successful refresh.

Start the OAuth request

Generate state and PKCE values for every attempt. The example uses Node.js built-in modules. Adapt session, request, and response to your web framework.

import { createHash, randomBytes } from 'node:crypto';

const baseUrl = 'https://accrawl.com';
const clientId = process.env.ACCRAWL_CLIENT_ID;
const redirectUri = 'https://your-app.example/callback';
if (!clientId) throw new Error('ACCRAWL_CLIENT_ID is not configured');

const verifier = randomBytes(32).toString('base64url');
const challenge = createHash('sha256')
  .update(verifier)
  .digest('base64url');
const state = randomBytes(16).toString('base64url');

session.accrawlOAuth = { state, verifier };

const authorizeUrl = new URL('/oauth/authorize', baseUrl);
authorizeUrl.search = new URLSearchParams({
  response_type: 'code',
  client_id: clientId,
  redirect_uri: redirectUri,
  scope: 'read:data',
  state,
  code_challenge: challenge,
  code_challenge_method: 'S256',
}).toString();

response.redirect(authorizeUrl.toString());

Handle the callback

Exchange the code from a trusted backend, or directly from a public client that has no backend. Omit client_secret for a public client.

const callbackUrl = new URL(request.url);
const pending = session.accrawlOAuth;
const returnedState = callbackUrl.searchParams.get('state');
const code = callbackUrl.searchParams.get('code');
const oauthError = callbackUrl.searchParams.get('error');

if (oauthError) throw new Error('Accrawl authorisation was not completed');
if (!pending || !code || returnedState !== pending.state) {
  throw new Error('Invalid Accrawl OAuth callback');
}
delete session.accrawlOAuth;

const form = new URLSearchParams({
  grant_type: 'authorization_code',
  code,
  redirect_uri: redirectUri,
  client_id: clientId,
  code_verifier: pending.verifier,
});
if (process.env.ACCRAWL_CLIENT_SECRET) {
  form.set('client_secret', process.env.ACCRAWL_CLIENT_SECRET);
}

const tokenResponse = await fetch('https://accrawl.com/oauth/token', {
  method: 'POST',
  headers: { 'content-type': 'application/x-www-form-urlencoded' },
  body: form,
});
if (!tokenResponse.ok) {
  throw new Error(`Accrawl token exchange failed (${tokenResponse.status})`);
}
const tokens = await tokenResponse.json();

Data API

Read approved data

Send the access token as a Bearer credential. Start by listing the connections the user approved. Each one names its institution, so you never have to show an internal identifier. Then use a connection ID for the connection-scoped routes.

const apiResponse = await fetch(
  'https://accrawl.com/api/v1/connections',
  { headers: { authorization: `Bearer ${tokens.access_token}` } },
);
if (!apiResponse.ok) {
  throw new Error(`Accrawl API request failed (${apiResponse.status})`);
}
const connections = await apiResponse.json();

What the API does, and what it doesn’t

The API reads account data Accrawl has already collected. Every route is a GET. Your app cannot start a refresh, watch one in progress, or submit a one-time passcode — those stay with the person whose accounts these are, and happen only in Accrawl itself. Connections refresh on their own schedule, so check how current the data is before you show it: lastSyncedAt on a connection, asOf on a balance. Show your users that date rather than implying the data is live.

Scopes

ScopeAllows your app to
read:dataRead approved connections, accounts, balances, transactions, and holdings.

read:data is the only scope, because reading is all the API does. Every token is also restricted to the connections the user approved.

Data API

MethodRoutePurpose
GET/api/v1/connectionsList the connections the user approved, each with its institution’s name, type, and logo for display.
GET/api/v1/connections/:id/accountsList accounts and balances for an approved connection.
GET/api/v1/connections/:id/transactionsList transactions, optionally filtered by booking date.
GET/api/v1/connections/:id/transactions/syncRead incremental transaction changes since a cursor. Omit the cursor on the first call.
GET/api/v1/connections/:id/holdingsList holdings and their referenced securities.

Token lifecycle

Refresh or disconnect

A refresh returns a new access token and a new single-use refresh token without extending the user’s consent. Send the current refresh token once, then replace both stored tokens atomically before using either replacement.

When the user disconnects your app, revoke the current refresh token. This revokes the full grant, including its access tokens.

Refresh

Implement saveTokensAtomically as one database transaction that replaces both stored tokens.

function clientForm(fields) {
  const form = new URLSearchParams({ ...fields, client_id: clientId });
  if (process.env.ACCRAWL_CLIENT_SECRET) {
    form.set('client_secret', process.env.ACCRAWL_CLIENT_SECRET);
  }
  return form;
}

const refreshResponse = await fetch('https://accrawl.com/oauth/token', {
  method: 'POST',
  headers: { 'content-type': 'application/x-www-form-urlencoded' },
  body: clientForm({
    grant_type: 'refresh_token',
    refresh_token: storedRefreshToken,
  }),
});
if (!refreshResponse.ok) throw new Error('Accrawl token refresh failed');
const replacementTokens = await refreshResponse.json();

await saveTokensAtomically(replacementTokens);

Disconnect

const revokeResponse = await fetch('https://accrawl.com/oauth/revoke', {
  method: 'POST',
  headers: { 'content-type': 'application/x-www-form-urlencoded' },
  body: clientForm({
    token: storedRefreshToken,
    token_type_hint: 'refresh_token',
  }),
});
if (!revokeResponse.ok) throw new Error('Accrawl token revocation failed');

Token lifecycle

Access and refresh tokens expire together at the end of the same authorisation window, about 90 days after the user’s approval. Refreshing rotates both tokens within that window; it does not extend the user’s consent, so the user must reconnect when the window ends.

Security requirements

Security requirements

  • Use an exact registered redirect URI and HTTPS outside local development.
  • Generate a new state and PKCE verifier for every authorisation attempt. Bind both to the initiating user session and use each only once.
  • Keep client secrets, access tokens, refresh tokens, authorisation codes, and PKCE verifiers out of URLs, logs, analytics, and browser storage.
  • Treat refresh-token reuse as a security incident. Accrawl revokes the grant when replay is detected.
  • Treat 401 as an invalid or expired access token. Attempt one refresh only if you still hold the current refresh token. If the refresh fails, discard both tokens and ask the user to reconnect. Treat 403 as a missing scope or connection grant, not as a signal to retry.

OAuth errors

The callback can contain error=access_denied when the user cancels or denies access. Token endpoints return standard OAuth error fields. Show the user a safe recovery message, and keep credentials and raw error responses out of the interface.