Skip to main content

Direct API Integration

It's possible to implement your own Connect UI using the Vezgo Accounts API. But the process is complicated and thus not recommended. Only supported in certain Vezgo subscription plans.

Connect new account

  1. POST /accounts with the credentials. If everything is valid, Vezgo should return 202 Accepted with the account object. A new interactive sync should start and account.status should be syncing at this point.

  2. GET /accounts/:id/poll?v={account.v} to poll for an update and act according to account.status

    • syncing: the account is still syncing but there has been some progress. If account.authorized is true, this means the sync has gotten past the login/authentication process, and is fetching new data. Your application may choose to stop polling at this point if you don't need to show the balances to your user right away.
    • ok: The sync has been successful.
    • error: There's been an error with the authentication process, usually because of invalid credentials or a verification step. You should see "status": "error", "error.name": "LoginFailedError|SecurityQuestionError" in the account object.
      • LoginFailedError: Invalid credentials. In this case you would need to update the credentials by calling PUT /accounts/:id with the new credentials. Similar to 1), you would receive a 202 Accepted status and "status": "syncing". Then go back to polling.
      • SecurityQuestionError: Verification is required (mostly for exchanges with username/password authentication). There should be a security question or 2FA request inside account.error. The question would be in account.error.message, e.g. "error.message": "Your one-time security code will be sent to number ending with XXX". Your application would send the answer (verification code in this case) by calling PUT /accounts/:id with { "security_answer": "thecode" } in the request body. Then go back to polling. If you wait too long to send the answer, then the answer or the interactive session might expire and you will need to start over by triggering a new sync.
    • retry: there has been an unexpected problem and the sync has been rescheduled in the next hour. It's advised to report to us the account id so we can investigate the problem. This usually happens when we are unable to connect to the exchange or the data service.
    • If a 404 is returned, it means the credentials is invalid and the account has been automatically deleted from the Vezgo side. This is equivalent to LoginFailedError but should happen only for new connections that have never synced, to minimize the number of abandoned connections. In this case, go back to 1) to create a new connection.

How polling works (the v parameter)

Every account object returned by the API contains a numeric v field — a change counter that increases each time the account is updated during a sync. The poll endpoint is a long poll built around it: GET /accounts/:id/poll?v=N means "I have seen version N — respond when something newer exists." You never calculate v yourself; you echo back the value from the last account object you received.

  1. Take v from the last account object you received — e.g. the 202 Accepted body of POST /accounts, PUT /accounts/:id or POST /accounts/:id/sync.
  2. Call GET /accounts/:id/poll?v={thatValue} and handle the response:
    • 200 — the account changed. The body is the updated account with a higher v. Check status; if it is still syncing, poll again with the new v.
    • 304 — no change within ~28 seconds. Poll again with the same v.
  3. Stop when a 200 response shows status of ok or error (see the list above for handling errors).
caution

Always pass v. If it is omitted, the server has no reference version to compare against and falls back to the account's current version — so once the sync settles, every poll waits ~28 seconds and returns a 503, indefinitely. Persistent 503 responses from the poll endpoint almost always mean the v parameter is missing from the request.

const api = axios.create({
baseURL: "https://api.vezgo.com/v1",
timeout: 35000, // the poll holds the request up to ~28s — keep the client timeout above that
validateStatus: (s) => (s >= 200 && s < 300) || s === 304,
});

async function syncAndWait(accountId) {
// 1. Trigger a sync — the 202 body is the account object
const { data: account } = await api.post(`/accounts/${accountId}/sync`, { interactive: true });
let v = account.v;

// 2. Long-poll until the sync finishes
for (;;) {
const res = await api.get(`/accounts/${accountId}/poll`, { params: { v } });

if (res.status === 304) continue; // no change in ~28s — poll again with the SAME v

const updated = res.data; // 200 — adopt the new version
v = updated.v;

if (updated.status === "ok" || updated.status === "error") {
return updated; // done — inspect updated.error when status is "error"
}
// still "syncing" — loop and poll again with the new v
}
}

It might take from seconds to a couple of minutes to reach a final account.status depending on the provider and the account. For full syncs (especially transactions) we recommend webhooks over a long polling loop: a complete sync can take a while, and the webhook fires when the account is fully synced.

info

The polling process should be similar for when you connect a new account, sync or fix an existing connection

Reconnect/fix a connection

Sometimes a connection might fail to sync because of changed/revoked credentials or 2FA verifications. In that case, when you fetch the connection by doing GET /accounts/:id, you should see "status": "error" and the error type inside account.error.name

LoginFailedError:

  1. Update the credentials by calling PUT /accounts/:id with the new credentials. A 202 Accepted should be returned and a new interactive sync should start. It's advised to have this triggered by the user, so they are around to handle any 2FA verification if needed.
  2. Poll for update and act accordingly.

SecurityQuestionError:

You might see the question under account.error.message and be tempted to send an answer. However most of the time when you see this error, the 2FA session has already expired. You can confirm that by checking account.status_details.date to see the date when the error was returned. If it's past 5 minutes since the error was set, then it's likely the session has expired. Some providers have even shorter 2FA sessions (under 60s).

Even if it looks like the session is still active, it might not be possible to send an answer because the sync was likely started in non-interactive mode (i.e. daily syncs). So most of the time you would need to first trigger a new interactive sync:

  1. Call POST /accounts/:id/sync with { force: true, interactive: true } in the POST body.

    • Passing interactive enables interactive mode, which means the sync would accept 2FA answers. It's advised to have this triggered by the user, so they are around to handle any 2FA verification if needed.
    • Passing force allows starting a new sync even though an existing sync is already in progress.
  2. Poll for update and act accordingly. If you get a new SecurityQuestionError, send the answer and poll again.

Sync a connection

  1. Trigger a sync by calling POST /accounts/:id/sync with { interactive: true } in the POST body.

    • Passing interactive enables interactive mode, which means the sync would accept 2FA answers. It's advised to have this triggered by the user, so they are around to handle any 2FA verification if needed.
  2. Poll for update and act accordingly.

Connecting with oAuth providers

OAuth providers (e.g. Coinbase) require a specific workflow to connect. Some important notes:

  1. The oAuth process must be opened as Vezgo (using the oAuth Client Id provided under provider.client_id).
  2. At the end of the process, the oAuth result (code or errors) is always returned to the Vezgo OAuth Redirection service at https://api.wealthica.com/oauth/redirect. This redirection service will take care of returning the oAuth result back to your app. You will need to register your Redirect URI prior to the integration.

Detailed integration instructions

The process must be initiated for providers with "auth_type": "oauth" (from the /providers endpoint). The information needed to initiate the process is provided in the provider data, for example:

{
"name": "Coinbase",
"auth_type": "oauth",
"client_id": "theclientid",
"authorize_url": "https://www.coinbase.com/oauth/authorize?account=all",
"available_scopes": [{ "name": "wallet:user:read", ... }]
}

Upon registering your Redirect URI with Vezgo, you will be provided a sub string corresponding with the Redirect URI. You can register multilple Redirect URIs (for dev, staging and production environments), each with its own sub. For example:


// STEP 1: Build oAuth URL

const OAUTH_REDIRECT_URI = "https://api.wealthica.com/oauth/redirect";
const OAUTH_SUB = 'myappdev';

// If you have different receiving paths for different flows (e.g. connect vs reconnect), you can
// provide a path. It will be concatenated with your registered URI.
// E.g. https://dev.myapp.com/connect, https://dev.myapp.com/reconnect/someaccountid
const OAUTH_PATH = '/connect';

// Prepare oAuth state.
// The Vezgo OAuth Redirection service will redirect based on your `sub` and (optional) `path`.
// You can also pass your client state info here.
const oAuthState = {
sub: OAUTH_SUB,
path: OAUTH_PATH,
...additionalStateProps,
};
const state = encodeBase64(oAuthState);

// Build the oAuth Authorize URL
const url = new URL(provider.authorize_url);
url.searchParams.append("client_id", provider.client_id);
url.searchParams.append("response_type", "code");
url.searchParams.append("state", state);
url.searchParams.append("redirect_uri", OAUTH_REDIRECT_URI);
if (provider.available_scopes) url.searchParams.append("scope", provider.available_scopes.map((scope) => scope.name).join(" "));

// STEP 2: Start the oAuth process
// User will be presented with the provider's oAuth page (Coinbase in this example).
// E.g. https://www.coinbase.com/oauth/authorize?account=all&client_id=theclientid...
openURL(url.href);


// STEP 3: Handling oAuth result
// After user completes the process, the oAuth result will be passed to your Redirect URI.
// E.g. https://dev.myapp.com/connect?code=somecode&state=thestate...

// Your app will then be responsible to send the code to the Vezgo API. Either:

// Send a POST to `/accounts` endpoint to create a new connection
createAccount({
name: "Coinbase",
provider: "coinbase",
credentials: {
code: 'somecode'
}
});

// Or send a PUT to `/accounts/:id` to update the existing connection
updateAccount({
id: 'existingconnectionid',
credentials: {
code: 'somecode'
}
})