Skip to content

Authentication and tokens

This page is for developers building an integration that calls VBAPI for longer than a single script run. It explains the login flow, the challenges login can return, how to send and reuse the ID token, and how to keep credentials safe.

The flow

VBAPI uses two steps. You log in once with your username and password and receive tokens. You then send the ID token as a Bearer token on every other call until it expires.

VBAPIYour integrationVBAPIYour integrationalt[NEW_PASSWORD_REQUIRED][Multi-factor code required]alt[Tokens returned][Challenge returned]loop[Until expiresIn runs out]POST /user-authenticationBasic username:password, x-api-key,client ID, client code, database, User-Agent1data.authenticationResult(idToken, accessToken, refreshToken, expiresIn)2data.challengeName and data.session3POST /change-temporary-password(userID, newPassword, session)4POST /mfa-submit-code(username, mfaCode, session)5authenticationResult, or another challenge6Any call with Authorization: Bearer idToken7Response8Log in again (POST /user-authentication)or POST /user-refresh-tokens9
VBAPIYour integrationVBAPIYour integrationalt[NEW_PASSWORD_REQUIRED][Multi-factor code required]alt[Tokens returned][Challenge returned]loop[Until expiresIn runs out]POST /user-authenticationBasic username:password, x-api-key,client ID, client code, database, User-Agent1data.authenticationResult(idToken, accessToken, refreshToken, expiresIn)2data.challengeName and data.session3POST /change-temporary-password(userID, newPassword, session)4POST /mfa-submit-code(username, mfaCode, session)5authenticationResult, or another challenge6Any call with Authorization: Bearer idToken7Response8Log in again (POST /user-authentication)or POST /user-refresh-tokens9

Log in

POST /user-authentication has no body. It reads your username and password from a Basic Authorization header and needs these headers:

HeaderValue
x-api-keyYour API key.
AuthorizationBasic <base64 of username:password>.
vbasoftware-client-idYour client ID.
vbasoftware-client-codeYour client code.
vbasoftware-databaseThe database to log in to, for example YOUR_DATABASE.
vbasoftware-machine-idOptional. An identifier for the calling machine.
User-AgentRecommended: your integration name, for example YourCompany-Integration/1.0.

The shared client used by the samples logs in like this:

    public async Task AuthenticateAsync()
    {
        using var request = new HttpRequestMessage(HttpMethod.Post, $"{_baseUrl}/user-authentication");
        var basic = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{_username}:{_password}"));
        request.Headers.Authorization = new AuthenticationHeaderValue("Basic", basic);
        request.Headers.Add("x-api-key", _apiKey);
        request.Headers.Add("vbasoftware-client-id", _clientId);
        request.Headers.Add("vbasoftware-client-code", _clientCode);
        request.Headers.Add("vbasoftware-database", _database);
        request.Headers.UserAgent.ParseAdd(_userAgent);

        using var response = await _http.SendAsync(request);
        var body = await ReadJsonAsync(response);
        if (response.StatusCode != HttpStatusCode.OK)
            throw new VbapiException(response.StatusCode, body?["error"], body?["debug"]?["activityID"]?.GetValue<string>());

        var login = body!["data"]!;
        var challenge = login["challengeName"]?.GetValue<string>();
        if (!string.IsNullOrEmpty(challenge))
            throw new VbapiChallengeException(challenge, login["session"]?.GetValue<string>());

        var result = login["authenticationResult"]!;
        _idToken = result["idToken"]!.GetValue<string>();
        var expiresIn = result["expiresIn"]?.GetValue<int>() ?? 3600; // seconds
        _expiresAt = DateTimeOffset.UtcNow.AddSeconds(expiresIn - 60);  // refresh one minute early
    }
    

A successful login returns the tokens in data.authenticationResult. Example response (illustrative; the token values are fake and shortened, and expiresIn varies, so read it from each response):

{
  "data": {
    "challengeName": null,
    "session": null,
    "authenticationResult": {
      "accessToken": "eyJraWQiOiJET0NTIn0.fake-access-token",
      "idToken": "eyJraWQiOiJET0NTIn0.fake-id-token",
      "refreshToken": "fake-refresh-token",
      "expiresIn": 3600
    }
  },
  "error": null,
  "debug": { "activityID": "00000000-0000-0000-0000-000000000000" }
}

Send the idToken, not the accessToken, as Authorization: Bearer <idToken> on every other call, together with x-api-key, vbasoftware-database, and a descriptive User-Agent.

Handle login challenges

Instead of tokens, login can return a challengeName and a session, with authenticationResult empty. Your code must check for this before it reads the token. The session value ties the next call to this login attempt, so pass it back unchanged.

ChallengeWhat it meansNext call
NEW_PASSWORD_REQUIREDThe user still has a temporary password.POST /change-temporary-password with userID, newPassword, and session.
A multi-factor challengeThe user must enter a code from their authenticator.POST /mfa-submit-code with username, mfaCode, and session. It returns 202 with the same response shape as a login.

Both calls send vbasoftware-client-id, vbasoftware-client-code, and vbasoftware-database, like login. Their responses have the same shape as a login response, so they can carry tokens or another challenge; check challengeName again.

Example body for POST /change-temporary-password (illustrative; values are fake):

{
  "userID": "DOCS-APIUSER",
  "newPassword": "a-new-strong-password",
  "session": "session-value-from-the-login-response"
}

An unattended integration cannot answer a challenge by itself. The shared client stops with a clear message when login returns one. Complete the challenge once, by hand or with a small one-off script, and then run the integration again.

Reuse the token until it expires

expiresIn is the token's lifetime in seconds. Do not hard-code a lifetime; read expiresIn from each login response.

  • Log in once and reuse the ID token for every call until shortly before it expires. Do not log in for each request.
  • Share one client (and so one token) across your process rather than logging in from every worker.
  • Renew the token a little early. The shared client renews one minute before expiresIn runs out.
  • If a call returns 401 anyway, log in again once and retry the call. The shared client does this; see Errors and troubleshooting for the code.

The shared client keeps the token and its expiry time in memory and checks them before every call, as shown in the login code above.

Refresh tokens

Login also returns a refreshToken. POST /user-refresh-tokens exchanges it for new tokens. It takes the expired ID token in the expired-id-token header and the refresh token in the refresh-token header, plus vbasoftware-client-id, vbasoftware-client-code, and vbasoftware-database, and returns the same shape as a login. The samples log in again instead, which needs nothing beyond the credentials you already have.

Password resets

If a user forgets their password, POST /forgot-password with the userID sends the user a confirmation code. POST /confirm-forgot-password then sets the new password, with userID, password, and confirmationCode. Both send vbasoftware-client-id, vbasoftware-client-code, and vbasoftware-database. For an API service user, it is usually simpler to ask whoever issued your credentials.

Common authentication issues

What you seeLikely causeFix
401 from POST /user-authenticationWrong username or password, or the wrong client ID, client code, or database for that user.Check each value with whoever issued your credentials.
challengeName: NEW_PASSWORD_REQUIRED instead of tokensThe user still has a temporary password.Set a permanent password with POST /change-temporary-password, then log in again.
401 on a data callThe ID token is missing, expired, or not sent as Bearer.Log in again, and send Authorization: Bearer <idToken>.
403 with {"message":"Forbidden"} on every callThe x-api-key header is missing or wrong.Send your API key in x-api-key on every request, login included.
429More than 25 requests per second (burst 50) on your API key.Slow down and retry with backoff. The samples do this for you.
Login or data calls fail for a database you expect to useThe vbasoftware-database value does not match a database your user can reach.Use the database name you were given, exactly as issued.

Keep credentials safe

  • Load the API key, client ID, client code, username, and password from a secrets manager or environment variables, never from source code.
  • Keep tokens in memory. Do not write them to disk or share them between systems.
  • Never log passwords, tokens, the API key, or full Authorization headers. Log the debug.activityID instead.
  • Rotate the password and API key according to your security policy, and straight away if they may have been exposed.
Contacting support

Send VBA support:

  • the debug.activityID from the response
  • the date and time of the call, with time zone
  • the method and path, for example POST /claim-batches
  • the full error object from the response
  • the request body, with passwords, tokens, and personal data removed

Never send your password, API key, or tokens. See Support and escalation.