This page is for developers who have their VBAPI credentials and want to make a first authenticated call. In about five minutes you log in with POST /user-authentication and list five groups with GET /groups.
Both calls only read data, so the script is safe to run against your production database.
VBA issues all of these to your organization. If you do not have them yet, see Credentials and environments; the five minutes start once they arrive.
| You need | Used for |
|---|---|
| API key | The x-api-key header on every call |
| Client ID and client code | The vbasoftware-client-id and vbasoftware-client-code headers when you log in |
| Database name | The vbasoftware-database header on every call (this page writes it as YOUR_DATABASE) |
| Username and password | Basic authentication when you log in |
You also need one of: .NET 10 (or .NET 8 with a console project), Python 3.10 or later with requests, or Node 20 or later.
The samples read your credentials from environment variables, so nothing secret is written into code. Set them in the terminal you run the samples from:
$env:VBAPI_BASE_URL = "https://vbapi.vbasoftware.com/vbasoftware"
$env:VBAPI_API_KEY = "your-api-key"
$env:VBAPI_CLIENT_ID = "your-client-id"
$env:VBAPI_CLIENT_CODE = "your-client-code"
$env:VBAPI_DATABASE = "YOUR_DATABASE"
$env:VBAPI_USERNAME = "your-api-username"
$env:VBAPI_PASSWORD = "your-api-password"
$env:VBAPI_USER_AGENT = "YourCompany-Integration/1.0"In production code, load these from your secrets manager instead.
Each script reads these values in its first block. The scripts send a fixed User-Agent of YourCompany-Integration/1.0; change it to name your integration.
var baseUrl = Environment.GetEnvironmentVariable("VBAPI_BASE_URL") ?? "https://vbapi.vbasoftware.com/vbasoftware";
var apiKey = Env("VBAPI_API_KEY");
var database = Env("VBAPI_DATABASE");
const string userAgent = "YourCompany-Integration/1.0";
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("x-api-key", apiKey);
http.DefaultRequestHeaders.Add("vbasoftware-database", database);
http.DefaultRequestHeaders.UserAgent.ParseAdd(userAgent);POST /user-authentication takes your username and password as Basic authentication, plus your API key, client ID, client code, and database. It returns tokens in data.authenticationResult.
var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Env("VBAPI_USERNAME")}:{Env("VBAPI_PASSWORD")}"));
using var login = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl}/user-authentication");
login.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials);
login.Headers.Add("vbasoftware-client-id", Env("VBAPI_CLIENT_ID"));
login.Headers.Add("vbasoftware-client-code", Env("VBAPI_CLIENT_CODE"));
using var loginResponse = await http.SendAsync(login);
loginResponse.EnsureSuccessStatusCode();
using var loginJson = JsonDocument.Parse(await loginResponse.Content.ReadAsStringAsync());
var loginData = loginJson.RootElement.GetProperty("data");
if (loginData.TryGetProperty("challengeName", out var challenge) && challenge.ValueKind == JsonValueKind.String)
{
// For example NEW_PASSWORD_REQUIRED: call POST /change-temporary-password with the session value.
throw new InvalidOperationException($"Login needs an extra step: {challenge.GetString()}");
}
var idToken = loginData.GetProperty("authenticationResult").GetProperty("idToken").GetString();The idToken is what you send as Authorization: Bearer <idToken> on every other call. expiresIn is its lifetime in seconds; when it runs out, log in again. Authentication and tokens covers token handling in long-running integrations.
Instead of tokens, the response can contain a challengeName and a session, with authenticationResult empty. The usual challenge is NEW_PASSWORD_REQUIRED, which means the user still has a temporary password. The scripts stop and print the challenge name. Set a permanent password with POST /change-temporary-password, sending userID, newPassword, and the session value, then run the script again.
The first call lists groups: GET /groups?page=1&pageSize=5 returns the first page of five. The script prints the paging header and each group's ID and name.
using var request = new HttpRequestMessage(HttpMethod.Get, $"{baseUrl}/groups?page=1&pageSize=5");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", idToken);
using var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();
if (response.Headers.TryGetValues("X-Pagination", out var paging))
{
Console.WriteLine($"Paging: {string.Join("", paging)}");
}
using var body = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
foreach (var group in body.RootElement.GetProperty("data").EnumerateArray())
{
Console.WriteLine($"{group.GetProperty("group_ID").GetString()} - {group.GetProperty("name").GetString()}");
}Copy the complete file for your language and run it in the terminal where you set the environment variables.
Run with dotnet run Quickstart.cs on .NET 10. On .NET 8, create a console app (dotnet new console), paste the file into Program.cs, and run dotnet run.
// VBAPI quick start: authenticate, then list the first five groups.
// .NET 10: dotnet run Quickstart.cs (.NET 8: paste into a console app's Program.cs)
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Step 1: configuration
var baseUrl = Environment.GetEnvironmentVariable("VBAPI_BASE_URL") ?? "https://vbapi.vbasoftware.com/vbasoftware";
var apiKey = Env("VBAPI_API_KEY");
var database = Env("VBAPI_DATABASE");
const string userAgent = "YourCompany-Integration/1.0";
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("x-api-key", apiKey);
http.DefaultRequestHeaders.Add("vbasoftware-database", database);
http.DefaultRequestHeaders.UserAgent.ParseAdd(userAgent);
// End of step 1
// Step 2: log in and get an ID token
var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Env("VBAPI_USERNAME")}:{Env("VBAPI_PASSWORD")}"));
using var login = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl}/user-authentication");
login.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials);
login.Headers.Add("vbasoftware-client-id", Env("VBAPI_CLIENT_ID"));
login.Headers.Add("vbasoftware-client-code", Env("VBAPI_CLIENT_CODE"));
using var loginResponse = await http.SendAsync(login);
loginResponse.EnsureSuccessStatusCode();
using var loginJson = JsonDocument.Parse(await loginResponse.Content.ReadAsStringAsync());
var loginData = loginJson.RootElement.GetProperty("data");
if (loginData.TryGetProperty("challengeName", out var challenge) && challenge.ValueKind == JsonValueKind.String)
{
// For example NEW_PASSWORD_REQUIRED: call POST /change-temporary-password with the session value.
throw new InvalidOperationException($"Login needs an extra step: {challenge.GetString()}");
}
var idToken = loginData.GetProperty("authenticationResult").GetProperty("idToken").GetString();
// End of step 2
// Step 3: first call, list five groups
using var request = new HttpRequestMessage(HttpMethod.Get, $"{baseUrl}/groups?page=1&pageSize=5");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", idToken);
using var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();
if (response.Headers.TryGetValues("X-Pagination", out var paging))
{
Console.WriteLine($"Paging: {string.Join("", paging)}");
}
using var body = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
foreach (var group in body.RootElement.GetProperty("data").EnumerateArray())
{
Console.WriteLine($"{group.GetProperty("group_ID").GetString()} - {group.GetProperty("name").GetString()}");
}
// End of step 3
static string Env(string name) =>
Environment.GetEnvironmentVariable(name) ?? throw new InvalidOperationException($"Set {name}");The output looks like this (illustrative; field values are fake):
Paging: {"TotalCount":1,"TotalPages":1,"CurrentPage":1,"PageSize":5}
DOCS-GRP1 - Sample Group OneExample response (illustrative; field values are fake). Real responses include every Groups field; most are left out here.
{
"data": [
{
"group_ID": "DOCS-GRP1",
"name": "Sample Group One",
"admin_COBRA": false,
"advice_To_Pay": false,
"apply_Ex_Code": false,
"auth_Not_Reqd_Override": false,
"coPay_Max_Per_Date_Provider": false,
"default_Process_by_Disability_Date": false,
"default_Roll_Up_Waiting_Period": false,
"disable_COBRA_Auto_Select": false,
"duplicate_Claim_Override": false,
"individual_Billing": false,
"individual_Cycle": false,
"individual_Dates": false,
"open_Closed_Enrollment": false,
"paperless": false,
"prepaid_Group": false,
"prorate_Deduction": false,
"self_Admin": false,
"set_Billing_Due_Date": false,
"entry_Date": "2030-01-01T00:00:00",
"entry_User": "DOCS-USER",
"update_Date": "2030-01-01T00:00:00",
"update_User": "DOCS-USER"
}
],
"error": null,
"debug": {
"activityID": "00000000-0000-0000-0000-000000000000"
}
}JSON responses are wrapped in the same envelope:
{
"data": { },
"error": null,
"debug": { "activityID": "00000000-0000-0000-0000-000000000000" }
}dataholds the result: one object for a get, create, or update, and an array for a list.errorisnullon success. On failure it is a problem-details object withtype,title,status,detail, andinstance.debug.activityIDidentifies the request in VBA's logs. Successful responses spell itactivityID; error responses spell itActivityID, so read the property without regard to case (the sample clients do). Log it with every failure and include it when you contact support.
Not every response has an envelope. 204 No Content (deletes) has no body, some 202 Accepted responses for queued work return only a short confirmation message, and the API gateway answers 401 with {"message":"Unauthorized"} and 403 with {"message":"Forbidden"}.
List calls also return an X-Pagination response header with TotalCount, TotalPages, CurrentPage, and PageSize. Pages start at 1, and pageSize defaults to 100. See Pagination and sorting.
The scripts set these headers for you:
Every API call sends these headers:
| Header | Value |
|---|---|
x-api-key | Your API key. |
Authorization | Bearer <idToken>, using the ID token from POST /user-authentication. |
vbasoftware-database | The database to work in, for example YOUR_DATABASE. |
User-Agent | Recommended. A name for your integration, for example YourCompany-Integration/1.0. VBAPI accepts requests without one today, but a descriptive value lets VBA support find your calls in the logs. |
Content-Type | application/json when the request has a body. |
Logging in (POST /user-authentication) sends a different set:
| Header | Value |
|---|---|
x-api-key | Your API key. |
Authorization | Basic <base64 of username:password>. |
vbasoftware-client-id | Your client ID. |
vbasoftware-client-code | Your client code. |
vbasoftware-database | The database to log in to. |
vbasoftware-machine-id | Optional. An identifier for the calling machine. |
User-Agent | Recommended: your integration name. |
To check your credentials without installing anything, run this one line in bash (Git Bash or WSL on Windows) after setting the environment variables. It logs in once and prints the HTTP status and whether tokens ("authenticationResult":{) or a challenge ("challengeName":"NEW_PASSWORD_REQUIRED") came back. It never prints the tokens, so the output is safe to paste into a support request.
curl -s -X POST "${VBAPI_BASE_URL:-https://vbapi.vbasoftware.com/vbasoftware}/user-authentication" -u "$VBAPI_USERNAME:$VBAPI_PASSWORD" -H "x-api-key: $VBAPI_API_KEY" -H "vbasoftware-client-id: $VBAPI_CLIENT_ID" -H "vbasoftware-client-code: $VBAPI_CLIENT_CODE" -H "vbasoftware-database: $VBAPI_DATABASE" -A "${VBAPI_USER_AGENT:-YourCompany-Integration/1.0}" -w '\nHTTP %{http_code}\n' | grep -oE '"(authenticationResult|challengeName)": *("[A-Za-z_]+"|null|[{])|^HTTP [0-9]+$'| What you see | Likely cause | Fix |
|---|---|---|
401 from POST /user-authentication | Wrong 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 tokens | The user still has a temporary password. | Set a permanent password with POST /change-temporary-password, then log in again. |
401 on a data call | The ID token is missing, expired, or not sent as Bearer. | Log in again, and send Authorization: Bearer <idToken>. |
403 with {"message":"Forbidden"} on every call | The x-api-key header is missing or wrong. | Send your API key in x-api-key on every request, login included. |
429 | More 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 use | The vbasoftware-database value does not match a database your user can reach. | Use the database name you were given, exactly as issued. |
Send VBA support:
- the
debug.activityIDfrom the response - the date and time of the call, with time zone
- the method and path, for example
POST /claim-batches - the full
errorobject 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.
- Data model: how groups, subscribers, members, and claims relate.
- Requests and responses: methods, status codes, and the envelope.
- Onboard a new employer group and Enroll employees and dependents: the first two playbooks.
- Try it in the browser: call operations from the API reference without writing code.