# Requests and responses

This page is for developers writing code against VBAPI. It explains which HTTP methods to use, what each status code means, how to read the response envelope and batch results, and how to tell when queued work has finished.

This changes production data
VBAPI clients connect to production, so the write calls on this page create or change real records in the database you name in `vbasoftware-database`. Try the steps in a non-production database first if VBA has given you one, use clearly fake values while you test, and keep the "Check it worked" calls, which only read data. "Try it" in the API reference also calls production.

The examples on this page create two sample counties (`DOCS-CO1` and `DOCS-CO2`) and delete them again at the end.

## The basics

Every call goes to `https://vbapi.vbasoftware.com/vbasoftware` followed by the path from the [API reference](/openapi/vbasoftware/vbasoftware.api), over HTTPS, with JSON request and response bodies. Field names in bodies use the database column names in a fixed style (`county_Code`, `first_Name`); see [IDs, keys, and field names](/developer-portal/concepts/ids-and-conventions).

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


The samples use a small shared client that sets these headers, retries, and re-authenticates for you. [Authentication and tokens](/developer-portal/concepts/authentication) and [Errors and troubleshooting](/developer-portal/concepts/errors) show how it works.

There are two kinds of operations:

- **Data-access operations** read and write one table: create, get, list, update, delete, and batch create-or-update. Most of the API is this kind.
- **Advanced operations** run VBASoftware business logic, for example adjudicating a claim or adding an enrollment. The spec marks them with `x-advanced: true`, and their reference tags usually start with "Adv.".


## Methods

| Method | Used for | Success status | Safe to repeat? |
|  --- | --- | --- | --- |
| `GET` | Read one record or a page of records | `200` | Yes. |
| `POST` | Create a record, or run an advanced operation | `200` for creates; `200` or `202` for advanced operations | No. Repeating a create can make a duplicate or fail because the key already exists. |
| `PUT /<resource>/{key}` | Replace one record | `200` | Yes. The same body gives the same result. |
| `PUT /<resource>-batch` | Create or update many records in one call | `207` | Yes for items that carry their own key: an item that exists is updated, and one that does not is created. |
| `DELETE` | Delete one record | `204`, no body | Yes. Deleting a record that is already gone also returns `204`. |


`PATCH` is not supported. No operation in the API uses it; use `PUT`.

### Create

Creates return `200 OK` with the created record in `data`. They do not return `201 Created`, so do not test for `201`.

C#
```csharp
            var created = await api.PostAsync("/counties", new { county_Code = "DOCS-CO1", county_Name = "Docs Sample County" });
            Console.WriteLine($"{(int)created.Status} {created.Data?["county_Code"]}"); // 200, not 201
```

Python
```python
        created = api.post("/counties", {"county_Code": "DOCS-CO1", "county_Name": "Docs Sample County"})
        print(created.status, created.data["county_Code"])  # 200, not 201
```

TypeScript
```typescript
    const created = await api.post("/counties", { county_Code: "DOCS-CO1", county_Name: "Docs Sample County" });
    console.log(created.status, created.data.county_Code); // 200, not 201
```

This calls [`POST /counties`](/openapi/vbasoftware/vbasoftware.api/county/createcounty). The body has the one required field, `county_Code`, plus a name.

### Get

[`GET /counties/{countyCode}`](/openapi/vbasoftware/vbasoftware.api/county/getcounty) returns one record in `data`. If no record has that key, the API returns `404`.

C#
```csharp
            var county = (await api.GetAsync("/counties/DOCS-CO1")).Data!.AsObject();
            Console.WriteLine(county["county_Name"]);
```

Python
```python
        county = api.get("/counties/DOCS-CO1").data
        print(county["county_Name"])
```

TypeScript
```typescript
    const county = (await api.get("/counties/DOCS-CO1")).data;
    console.log(county.county_Name);
```

List operations return an array in `data` and paging details in the `X-Pagination` header; see [Pagination and sorting](/developer-portal/concepts/pagination-and-sorting).

### Update with PUT

`PUT` replaces the record with the body you send. The key fields in the body must match the key in the path. Send the complete record: read it, change the fields you need, and send the whole record back, as this example does with [`PUT /counties/{countyCode}`](/openapi/vbasoftware/vbasoftware.api/county/updatecounty). Do not send only the changed fields, because fields you leave out are not kept from the stored record.

C#
```csharp
            // PUT replaces the record: start from the full record you read, change fields, send it all back.
            county["county_Name"] = "Docs Sample County (renamed)";
            var updated = await api.PutAsync("/counties/DOCS-CO1", county);
            Console.WriteLine($"{(int)updated.Status} {updated.Data?["county_Name"]}");
```

Python
```python
        # PUT replaces the record: start from the full record you read, change fields, send it all back.
        county["county_Name"] = "Docs Sample County (renamed)"
        updated = api.put("/counties/DOCS-CO1", county)
        print(updated.status, updated.data["county_Name"])
```

TypeScript
```typescript
    // PUT replaces the record: start from the full record you read, change fields, send it all back.
    county.county_Name = "Docs Sample County (renamed)";
    const updated = await api.put("/counties/DOCS-CO1", county);
    console.log(updated.status, updated.data.county_Name);
```

### Delete

[`DELETE /counties/{countyCode}`](/openapi/vbasoftware/vbasoftware.api/county/deletecounty) returns `204 No Content` with an empty body. Do not try to parse JSON from it.

C#
```csharp
            foreach (var code in new[] { "DOCS-CO1", "DOCS-CO2" })
            {
                try
                {
                    var deleted = await api.DeleteAsync($"/counties/{code}");
                    Console.WriteLine($"{code} {(int)deleted.Status}"); // 204 No Content, no body
                }
                catch (VbapiException ex)
                {
                    Console.WriteLine($"{code} {(int)ex.Status} {ex.ActivityId}");
                }
            }
```

Python
```python
        for code in ("DOCS-CO1", "DOCS-CO2"):
            try:
                deleted = api.delete(f"/counties/{code}")
                print(code, deleted.status)  # 204 No Content, no body
            except VbapiError as error:
                print(code, error.status, error.activity_id)
```

TypeScript
```typescript
    for (const code of ["DOCS-CO1", "DOCS-CO2"]) {
      try {
        const deleted = await api.delete(`/counties/${code}`);
        console.log(code, deleted.status); // 204 No Content, no body
      } catch (error) {
        if (!(error instanceof VbapiError)) throw error;
        console.log(code, error.status, error.activityId);
      }
    }
```

A delete can be refused when other records depend on the one you are deleting. For example, the database refuses to delete a subscriber that still has claims or authorizations, and the API returns the reason in `error.detail`.

## Batch create or update (207 Multi-Status)

Most resources have a batch operation, `PUT /<resource>-batch`, that creates or updates many records in one call, for example [`PUT /counties-batch`](/openapi/vbasoftware/vbasoftware.api/county/updatebatchcounty) or [`PUT /subscribers/{subscriberID}/members-batch`](/openapi/vbasoftware/vbasoftware.api/members/updatebatchmembers). The body is an array of full records. For each item, the API updates the record if one with that key exists and creates it otherwise.

The response status is `207 Multi-Status`, and `data` holds one result per item. **A `207` does not mean every item succeeded**: one item can fail while the others are saved, so check every item.

| Field | Meaning |
|  --- | --- |
| `id` | The record's key as a list of `key`/`value` pairs. The key names use the database column style, for example `County_Code`. |
| `status` | The result for this item: `201` (created), `200` (updated), or an error status such as `400`. |
| `body` | `Created`, `Updated`, or the error message. |


Example response (illustrative; field values are fake):

```json
{
  "data": [
    { "id": [ { "key": "County_Code", "value": "DOCS-CO1" } ], "status": 200, "body": "Updated" },
    { "id": [ { "key": "County_Code", "value": "DOCS-CO2" } ], "status": 201, "body": "Created" }
  ],
  "error": null,
  "debug": { "activityID": "00000000-0000-0000-0000-000000000000" }
}
```

Read the results item by item and resend only the items that failed:

C#
```csharp
            var batch = await api.PutAsync("/counties-batch", new[]
            {
                new { county_Code = "DOCS-CO1", county_Name = "Docs Sample County One" }, // exists: updated
                new { county_Code = "DOCS-CO2", county_Name = "Docs Sample County Two" }, // new: created
            });
            Console.WriteLine((int)batch.Status); // 207 Multi-Status
            var failed = 0;
            foreach (var item in batch.Data?.AsArray() ?? new JsonArray())
            {
                var key = string.Join(", ", item!["id"]!.AsArray().Select(pair => $"{pair!["key"]}={pair["value"]}"));
                var status = item["status"]!.GetValue<int>();
                Console.WriteLine($"{key}: {status} {item["body"]}"); // for example 200 Updated, 201 Created
                if (status >= 400)
                    failed++;
            }
            if (failed > 0)
                Console.WriteLine($"{failed} item(s) failed; fix and resend only those.");
```

Python
```python
        batch = api.put("/counties-batch", [
            {"county_Code": "DOCS-CO1", "county_Name": "Docs Sample County One"},  # exists: updated
            {"county_Code": "DOCS-CO2", "county_Name": "Docs Sample County Two"},  # new: created
        ])
        print(batch.status)  # 207 Multi-Status
        failed = []
        for item in batch.data:
            key = {pair["key"]: pair["value"] for pair in item["id"]}
            print(key, item["status"], item["body"])  # for example 200 Updated, 201 Created
            if item["status"] >= 400:
                failed.append((key, item["body"]))
        if failed:
            print(f"{len(failed)} item(s) failed; fix and resend only those.")
```

TypeScript
```typescript
    const batch = await api.put<MultiCodeResponse[]>("/counties-batch", [
      { county_Code: "DOCS-CO1", county_Name: "Docs Sample County One" }, // exists: updated
      { county_Code: "DOCS-CO2", county_Name: "Docs Sample County Two" }, // new: created
    ]);
    console.log(batch.status); // 207 Multi-Status
    const failed = batch.data.filter((item) => item.status >= 400);
    for (const item of batch.data) {
      const key = Object.fromEntries(item.id.map((pair) => [pair.key, pair.value]));
      console.log(key, item.status, item.body); // for example 200 Updated, 201 Created
    }
    if (failed.length > 0) console.log(`${failed.length} item(s) failed; fix and resend only those.`);
```

Inside a batch result, a created item reports `201` even though a single create call returns `200`.

## Status codes

| Status | When you see it |
|  --- | --- |
| `200 OK` | A create, get, list, or update succeeded, or an advanced operation finished and returned its result. |
| `202 Accepted` | Work was queued and runs in the background. The body is empty or a short message, not the usual envelope. See [Queued work](#queued-work). |
| `204 No Content` | A delete succeeded. No body. |
| `207 Multi-Status` | A batch `PUT` ran. Check each item's `status`. |
| `400 Bad Request` | The request was rejected: a missing required field or wrong type in the body (title `Invalid Model State`), an invalid query parameter such as a bad `sortBy`, data the database refused (title `Invalid Data Sent`), or a Search `pageCount` over 750. |
| `401 Unauthorized` | Login failed, or the ID token is missing, expired, or not sent as `Bearer`. |
| `403 Forbidden` | The API key is missing or wrong. The gateway returns `{"message":"Forbidden"}`, not the envelope. |
| `404 Not Found` | No record has the key in the path. The detail names the table and key, for example `County not found for conditions: County_Code equals DOCS-CO9`. |
| `422 Unprocessable Entity` | Search request validation failed, for example a selected field that is not in the SearchConfig (title `Invalid Entity`). |
| `429 Too Many Requests` | More than 25 requests per second sustained, or 50 in a burst, on your API key. See [Rate limits and performance](/developer-portal/concepts/rate-limits-and-performance). |
| `500 Internal Server Error` | An unexpected error. Keep the `debug.activityID` and contact support. |


[Errors and troubleshooting](/developer-portal/concepts/errors) lists causes and fixes for each error status, and which ones to retry.

## The response envelope

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

## Queued work

Some advanced operations do their work in the background and return `202 Accepted` straight away. Others wait and return the result.

| Operation | Returns | How you know it finished |
|  --- | --- | --- |
| [`POST /claim-batches/{batchNumber}/claims/{batchClaim}/adjudicate`](/openapi/vbasoftware/vbasoftware.api/claim-adjudication/adjudicateclaim) | `200` with the adjudicated claim batch entry and its service lines. The claim stays in the claim batch. | When the response arrives. |
| [`POST /claim-batches/{batchNumber}/claims/{batchClaim}/adjudicate-no-response`](/openapi/vbasoftware/vbasoftware.api/claim-adjudication/adjudicateclaimnoresponse) | `202`. Adjudication runs in the background. | Read the claim batch entry again with [`GET /claim-batches/{batchNumber}/claims/{batchClaim}`](/openapi/vbasoftware/vbasoftware.api/claim-batch/getclaimbatch). |
| [`POST /claim-batches/{batchNumber}/claims/{batchClaim}/process`](/openapi/vbasoftware/vbasoftware.api/claim-adjudication/processclaim) | `200` when the claim was adjudicated and moved out of the claim batch into the claim table. `202` when it was not moved, for example because it pended; add `includeDetails=true` to get the claim batch entry back in that case. | When the response arrives. |
| [`POST /claim-queues/{claimQueueKey}/adjudicate`](/openapi/vbasoftware/vbasoftware.api/claim-adjudication/adjudicateclaimqueue) and [`POST /claim-queues/{claimQueueKey}/process`](/openapi/vbasoftware/vbasoftware.api/claim-adjudication/processclaimqueue) | `202`. Every claim in the queue is worked in the background. | [`GET /claim-queues/{claimQueue_Key}`](/openapi/vbasoftware/vbasoftware.api/claim-queues/getclaimqueue) shows `queue_Status` (`QUEUED` while claims are being worked, `COMPLETE` when all are done), and [`GET /claim-queues/{claimQueueKey}/details`](/openapi/vbasoftware/vbasoftware.api/claim-queue-details/listclaimqueuedetail) shows each claim's `queue_Result`. |
| [`POST /subscribers/{subscriberID}/add-enrollment`](/openapi/vbasoftware/vbasoftware.api/adv.-enrollment/subscriberaddenrollment) | `202`. | Read the enrollments with [`GET /subscribers/{subscriberID}/enrollments`](/openapi/vbasoftware/vbasoftware.api/subscriber-enrollments/listsubenrollment). |


A `202` only means the request was accepted. To confirm the work finished, read the records it changes, as in the last column. When you check repeatedly, wait between checks and keep within the [rate limits](/developer-portal/concepts/rate-limits-and-performance).

## Related

- [Data model](/developer-portal/concepts/data-model): what the main records are and how their keys join.
- [IDs, keys, and field names](/developer-portal/concepts/ids-and-conventions): composite keys, required flags, and dates.
- [Errors and troubleshooting](/developer-portal/concepts/errors): error bodies, retries, and support.