# Quick start

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`](/openapi/vbasoftware/vbasoftware.api/user/userauthentication) and list five groups with [`GET /groups`](/openapi/vbasoftware/vbasoftware.api/groups/listgroups).

Both calls only read data, so the script is safe to run against your production database.

## What you need

VBA issues all of these to your organization. If you do not have them yet, see [Credentials and environments](/developer-portal/get-started/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.

## 1. Set your environment variables

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:

PowerShell
```powershell
$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"
```

bash
```bash
export VBAPI_BASE_URL="https://vbapi.vbasoftware.com/vbasoftware"
export VBAPI_API_KEY="your-api-key"
export VBAPI_CLIENT_ID="your-client-id"
export VBAPI_CLIENT_CODE="your-client-code"
export VBAPI_DATABASE="YOUR_DATABASE"
export VBAPI_USERNAME="your-api-username"
export VBAPI_PASSWORD="your-api-password"
export 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.

C#
```csharp
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);
```

Python
```python
BASE_URL = os.environ.get("VBAPI_BASE_URL", "https://vbapi.vbasoftware.com/vbasoftware")
API_KEY = os.environ["VBAPI_API_KEY"]
DATABASE = os.environ["VBAPI_DATABASE"]
USER_AGENT = "YourCompany-Integration/1.0"
```

TypeScript
```typescript
const BASE_URL = process.env.VBAPI_BASE_URL ?? "https://vbapi.vbasoftware.com/vbasoftware";
const API_KEY = process.env.VBAPI_API_KEY!;
const DATABASE = process.env.VBAPI_DATABASE!;
const USER_AGENT = "YourCompany-Integration/1.0";
```

## 2. Log in

`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`.

C#
```csharp
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();
```

Python
```python
def get_id_token() -> str:
    credentials = f"{os.environ['VBAPI_USERNAME']}:{os.environ['VBAPI_PASSWORD']}"
    response = requests.post(
        f"{BASE_URL}/user-authentication",
        headers={
            "x-api-key": API_KEY,
            "Authorization": "Basic " + base64.b64encode(credentials.encode()).decode(),
            "vbasoftware-client-id": os.environ["VBAPI_CLIENT_ID"],
            "vbasoftware-client-code": os.environ["VBAPI_CLIENT_CODE"],
            "vbasoftware-database": DATABASE,
            "User-Agent": USER_AGENT,
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()["data"]
    if data.get("challengeName"):
        # For example NEW_PASSWORD_REQUIRED: call POST /change-temporary-password with data["session"].
        sys.exit(f"Login needs an extra step: {data['challengeName']}")
    return data["authenticationResult"]["idToken"]
```

TypeScript
```typescript
async function getIdToken(): Promise<string> {
  const credentials = Buffer.from(
    `${process.env.VBAPI_USERNAME}:${process.env.VBAPI_PASSWORD}`,
  ).toString("base64");
  const response = await axios.post(`${BASE_URL}/user-authentication`, null, {
    headers: {
      "x-api-key": API_KEY,
      Authorization: `Basic ${credentials}`,
      "vbasoftware-client-id": process.env.VBAPI_CLIENT_ID!,
      "vbasoftware-client-code": process.env.VBAPI_CLIENT_CODE!,
      "vbasoftware-database": DATABASE,
      "User-Agent": USER_AGENT,
    },
  });
  const data = response.data.data;
  if (data.challengeName) {
    // For example NEW_PASSWORD_REQUIRED: call POST /change-temporary-password with data.session.
    throw new Error(`Login needs an extra step: ${data.challengeName}`);
  }
  return data.authenticationResult.idToken;
}
```

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](/developer-portal/concepts/authentication) covers token handling in long-running integrations.

### If login asks for an extra step

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`](/openapi/vbasoftware/vbasoftware.api/user/userchangetemppassword), sending `userID`, `newPassword`, and the `session` value, then run the script again.

## 3. Make your first call

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.

C#
```csharp
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()}");
}
```

Python
```python
def list_groups(id_token: str) -> None:
    response = requests.get(
        f"{BASE_URL}/groups",
        params={"page": 1, "pageSize": 5},
        headers={
            "x-api-key": API_KEY,
            "Authorization": f"Bearer {id_token}",
            "vbasoftware-database": DATABASE,
            "User-Agent": USER_AGENT,
        },
        timeout=30,
    )
    response.raise_for_status()
    body = response.json()
    print("Paging:", response.headers.get("X-Pagination"))
    for group in body["data"]:
        print(group["group_ID"], "-", group["name"])
```

TypeScript
```typescript
async function listGroups(idToken: string): Promise<void> {
  const response = await axios.get(`${BASE_URL}/groups`, {
    params: { page: 1, pageSize: 5 },
    headers: {
      "x-api-key": API_KEY,
      Authorization: `Bearer ${idToken}`,
      "vbasoftware-database": DATABASE,
      "User-Agent": USER_AGENT,
    },
  });
  console.log("Paging:", response.headers["x-pagination"]);
  for (const group of response.data.data) {
    console.log(`${group.group_ID} - ${group.name}`);
  }
}
```

## 4. Run the whole script

Copy the complete file for your language and run it in the terminal where you set the environment variables.

C#
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`.

```csharp
// 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}");
```

Python
Run `pip install requests`, then `python quickstart.py`.

```python
# VBAPI quick start: authenticate, then list the first five groups.
# Requires Python 3.10+ and `pip install requests`. Run: python quickstart.py
import base64
import os
import sys

import requests

# Step 1: configuration
BASE_URL = os.environ.get("VBAPI_BASE_URL", "https://vbapi.vbasoftware.com/vbasoftware")
API_KEY = os.environ["VBAPI_API_KEY"]
DATABASE = os.environ["VBAPI_DATABASE"]
USER_AGENT = "YourCompany-Integration/1.0"
# End of step 1


# Step 2: log in and get an ID token
def get_id_token() -> str:
    credentials = f"{os.environ['VBAPI_USERNAME']}:{os.environ['VBAPI_PASSWORD']}"
    response = requests.post(
        f"{BASE_URL}/user-authentication",
        headers={
            "x-api-key": API_KEY,
            "Authorization": "Basic " + base64.b64encode(credentials.encode()).decode(),
            "vbasoftware-client-id": os.environ["VBAPI_CLIENT_ID"],
            "vbasoftware-client-code": os.environ["VBAPI_CLIENT_CODE"],
            "vbasoftware-database": DATABASE,
            "User-Agent": USER_AGENT,
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()["data"]
    if data.get("challengeName"):
        # For example NEW_PASSWORD_REQUIRED: call POST /change-temporary-password with data["session"].
        sys.exit(f"Login needs an extra step: {data['challengeName']}")
    return data["authenticationResult"]["idToken"]
# End of step 2


# Step 3: first call, list five groups
def list_groups(id_token: str) -> None:
    response = requests.get(
        f"{BASE_URL}/groups",
        params={"page": 1, "pageSize": 5},
        headers={
            "x-api-key": API_KEY,
            "Authorization": f"Bearer {id_token}",
            "vbasoftware-database": DATABASE,
            "User-Agent": USER_AGENT,
        },
        timeout=30,
    )
    response.raise_for_status()
    body = response.json()
    print("Paging:", response.headers.get("X-Pagination"))
    for group in body["data"]:
        print(group["group_ID"], "-", group["name"])
# End of step 3


if __name__ == "__main__":
    list_groups(get_id_token())
```

TypeScript
In an empty folder, run `npm init -y` and `npm install axios`. Then run `npx tsx quickstart.ts`.

```typescript
// VBAPI quick start: authenticate, then list the first five groups.
// Requires Node 20+ and `npm install axios`. Run: npx tsx quickstart.ts
import axios from "axios";

// Step 1: configuration
const BASE_URL = process.env.VBAPI_BASE_URL ?? "https://vbapi.vbasoftware.com/vbasoftware";
const API_KEY = process.env.VBAPI_API_KEY!;
const DATABASE = process.env.VBAPI_DATABASE!;
const USER_AGENT = "YourCompany-Integration/1.0";
// End of step 1

// Step 2: log in and get an ID token
async function getIdToken(): Promise<string> {
  const credentials = Buffer.from(
    `${process.env.VBAPI_USERNAME}:${process.env.VBAPI_PASSWORD}`,
  ).toString("base64");
  const response = await axios.post(`${BASE_URL}/user-authentication`, null, {
    headers: {
      "x-api-key": API_KEY,
      Authorization: `Basic ${credentials}`,
      "vbasoftware-client-id": process.env.VBAPI_CLIENT_ID!,
      "vbasoftware-client-code": process.env.VBAPI_CLIENT_CODE!,
      "vbasoftware-database": DATABASE,
      "User-Agent": USER_AGENT,
    },
  });
  const data = response.data.data;
  if (data.challengeName) {
    // For example NEW_PASSWORD_REQUIRED: call POST /change-temporary-password with data.session.
    throw new Error(`Login needs an extra step: ${data.challengeName}`);
  }
  return data.authenticationResult.idToken;
}
// End of step 2

// Step 3: first call, list five groups
async function listGroups(idToken: string): Promise<void> {
  const response = await axios.get(`${BASE_URL}/groups`, {
    params: { page: 1, pageSize: 5 },
    headers: {
      "x-api-key": API_KEY,
      Authorization: `Bearer ${idToken}`,
      "vbasoftware-database": DATABASE,
      "User-Agent": USER_AGENT,
    },
  });
  console.log("Paging:", response.headers["x-pagination"]);
  for (const group of response.data.data) {
    console.log(`${group.group_ID} - ${group.name}`);
  }
}
// End of step 3

getIdToken()
  .then(listGroups)
  .catch((error) => {
    console.error(error.response?.data ?? error.message);
    process.exit(1);
  });
```

The output looks like this (illustrative; field values are fake):

```text
Paging: {"TotalCount":1,"TotalPages":1,"CurrentPage":1,"PageSize":5}
DOCS-GRP1 - Sample Group One
```

## What you just saw

Example response (illustrative; field values are fake). Real responses include every `Groups` field; most are left out here.

```json
{
  "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:

```json
{
  "data": { },
  "error": null,
  "debug": { "activityID": "00000000-0000-0000-0000-000000000000" }
}
```

- `data` holds the result: one object for a get, create, or update, and an array for a list.
- `error` is `null` on success. On failure it is a problem-details object with `type`, `title`, `status`, `detail`, and `instance`.
- `debug.activityID` identifies the request in VBA's logs. Successful responses spell it `activityID`; error responses spell it `ActivityID`, 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](/developer-portal/concepts/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. |


## Check your credentials with curl (optional)

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.

```bash
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]+$'
```

## Something went wrong?

| 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`](/openapi/vbasoftware/vbasoftware.api/user/userchangetemppassword), 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. |


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](/developer-portal/help/support).

## Next steps

- [Data model](/developer-portal/concepts/data-model): how groups, subscribers, members, and claims relate.
- [Requests and responses](/developer-portal/concepts/requests-and-responses): methods, status codes, and the envelope.
- [Onboard a new employer group](/developer-portal/playbooks/onboard-employer-group) and [Enroll employees and dependents](/developer-portal/playbooks/enroll-employees): the first two playbooks.
- [Try it in the browser](/developer-portal/get-started/try-it): call operations from the API reference without writing code.