Data API builder (DAB) provides a RESTful web API that lets you access tables, views, and stored procedures from a connected database.
Each exposed database object is defined as an entity in the runtime configuration.
By default, DAB hosts REST endpoints at:
https://{base_url}/api/{entity}
Note
All path components and query parameters are case sensitive.
Keywords supported in Data API builder
| Concept |
REST |
Purpose |
| Projection |
$select |
Choose which fields to return |
| Filtering |
$filter |
Restrict rows by condition |
| Sorting |
$orderby |
Define the sort order |
| Page size |
$first |
Limit the items per page |
| Continuation |
$after |
Continue from the last page |
Basic structure
To call a REST API, construct a request using this pattern:
{HTTP method} https://{base_url}/{rest-path}/{entity}
Example reading all records from the book entity:
GET https://localhost:5001/api/book
The response is a JSON object with a value array. Pagination and error information appear only when applicable.
Note
By default, DAB returns up to 100 items per query unless configured otherwise (runtime.pagination.default-page-size).
GET https://localhost:5001/api/book
Success:
{
"value": [
{ "id": 1, "title": "Dune", "year": 1965, "pages": 412 },
{ "id": 2, "title": "Foundation", "year": 1951, "pages": 255 }
]
}
Success with pagination:
{
"value": [
{ "id": 1, "title": "Dune", "year": 1965, "pages": 412 },
{ "id": 2, "title": "Foundation", "year": 1951, "pages": 255 }
],
"nextLink": "https://localhost:5001/api/book?$after=WyJCb29rMiJd"
}
Error:
{
"error": {
"code": "NotFound",
"message": "Could not find item with the given key.",
"status": 404
}
}
curl -X GET "https://localhost:5001/api/book"
The following model classes deserialize DAB responses:
using System.Text.Json.Serialization;
public class DabResponse<T>
{
[JsonPropertyName("value")]
public List<T>? Value { get; set; }
[JsonPropertyName("nextLink")]
public string? NextLink { get; set; }
[JsonPropertyName("error")]
public DabError? Error { get; set; }
[JsonIgnore]
public bool IsSuccess => Error is null;
[JsonIgnore]
public bool HasNextPage => NextLink is not null;
}
public class DabError
{
[JsonPropertyName("code")]
public string Code { get; set; } = string.Empty;
[JsonPropertyName("message")]
public string Message { get; set; } = string.Empty;
[JsonPropertyName("status")]
public int Status { get; set; }
}
public class Book
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; } = string.Empty;
[JsonPropertyName("year")]
public int? Year { get; set; }
[JsonPropertyName("pages")]
public int? Pages { get; set; }
}
Call the API and deserialize the response:
public async Task<List<Book>> GetBooksAsync()
{
var response = await httpClient.GetAsync("api/book");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<DabResponse<Book>>();
if (result?.Error is not null)
{
throw new Exception($"{result.Error.Code}: {result.Error.Message}");
}
return result?.Value ?? [];
}
The following data classes model DAB responses:
from dataclasses import dataclass
import requests
@dataclass
class Book:
id: int
title: str
year: int | None = None
pages: int | None = None
@dataclass
class DabError:
code: str
message: str
status: int
@dataclass
class DabResponse:
value: list[Book] | None = None
next_link: str | None = None
error: DabError | None = None
@property
def is_success(self) -> bool:
return self.error is None
@property
def has_next_page(self) -> bool:
return self.next_link is not None
Call the API and parse the response:
def get_books(base_url: str) -> list[Book]:
response = requests.get(f"{base_url}/api/book")
response.raise_for_status()
data = response.json()
if "error" in data:
err = data["error"]
raise Exception(f"{err['code']}: {err['message']}")
return [Book(**item) for item in data.get("value", [])]
The following function calls the API:
async function getBooks(baseUrl) {
const response = await fetch(`${baseUrl}/api/book`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
if (data.error) {
throw new Error(`${data.error.code}: ${data.error.message}`);
}
return data.value ?? [];
}
Example usage:
const books = await getBooks("https://localhost:5001");
console.log(`Fetched ${books.length} books from the API.`);
Query types
Each REST entity supports both collection and single-record reads.
| Operation |
Description |
GET /api/{entity} |
Returns a list of records |
GET /api/{entity}/{primary-key-column}/{primary-key-value} |
Returns one record by primary key |
Example returning one record:
GET /api/book/id/1010
Example returning many:
GET /api/book
Filtering results
Use the $filter query parameter to restrict which records are returned.
GET /api/book?$filter=title eq 'Foundation'
This query returns all books whose title equals "Foundation."
Filters can include logical operators for more complex queries:
GET /api/book?$filter=year ge 1970 or title eq 'Dune'
For more information, see the $filter argument reference.
Sorting results
The $orderby parameter defines how records are sorted.
GET /api/book?$orderby=year desc, title asc
This returns books ordered by year descending, then by title.
For more information, see the $orderby argument reference.
Limiting results {#first-and-after}
The $first parameter limits how many records are returned in one request.
GET /api/book?$first=5
This returns the first five books, ordered by primary key by default.
You can also use $first=-1 to request the configured maximum page size.
For more information, see the $first argument reference.
Continuing results
To fetch the next page, use $after with the continuation token from the previous response.
GET /api/book?$first=5&$after={continuation-token}
The $after token identifies where the last query ended.
For more information, see the $after argument reference.
Field selection (projection)
Use $select to control which fields are included in the response.
GET /api/book?$select=id,title,price
This returns only the specified columns.
If a field is missing or not accessible, DAB returns 400 Bad Request.
For more information, see the $select argument reference.
Modifying data
The REST API also supports create, update, and delete operations depending on entity permissions.
| Method |
Action |
POST |
Create a new item |
PUT |
Replace an existing item (or create if missing) |
PATCH |
Update an existing item (or create if missing) |
DELETE |
Remove an item by primary key |
Create a new record
Use POST to create a new item.
POST https://localhost:5001/api/book
Content-Type: application/json
{
"id": 2000,
"title": "Leviathan Wakes",
"year": 2011,
"pages": 577
}
curl -X POST "https://localhost:5001/api/book" \
-H "Content-Type: application/json" \
-d '{"id": 2000, "title": "Leviathan Wakes", "year": 2011, "pages": 577}'
var book = new Book
{
Id = 2000,
Title = "Leviathan Wakes",
Year = 2011,
Pages = 577
};
var response = await httpClient.PostAsJsonAsync("api/book", book);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<DabResponse<Book>>();
if (result?.Error is not null)
{
throw new Exception($"{result.Error.Code}: {result.Error.Message}");
}
book = {"id": 2000, "title": "Leviathan Wakes", "year": 2011, "pages": 577}
response = requests.post(f"{base_url}/api/book", json=book)
response.raise_for_status()
data = response.json()
if "error" in data:
err = data["error"]
raise Exception(f"{err['code']}: {err['message']}")
const book = { id: 2000, title: "Leviathan Wakes", year: 2011, pages: 577 };
const response = await fetch(`${baseUrl}/api/book`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(book),
});
const data = await response.json();
if (data.error) {
throw new Error(`${data.error.code}: ${data.error.message}`);
}
Update an existing record
Use PATCH to update specific fields on an existing item.
PATCH https://localhost:5001/api/book/id/2000
Content-Type: application/json
{
"id": 2000,
"title": "Leviathan Wakes",
"year": 2011,
"pages": 577
}
curl -X PATCH "https://localhost:5001/api/book/id/2000" \
-H "Content-Type: application/json" \
-d '{"id": 2000, "title": "Leviathan Wakes", "year": 2011, "pages": 577}'
Tip
By default, DAB rejects fields in your request body that don't exist in the database or that belong in the URL (like primary key fields for PATCH and DELETE). This behavior requires separate C# types: one with keys for POST and one without for updates. To reuse a single type like Book for all operations, set runtime.rest.request-body-strict to false in your configuration.
var book = new Book
{
Id = 2000,
Title = "Leviathan Wakes",
Year = 2011,
Pages = 577
};
var response = await httpClient.PatchAsJsonAsync("api/book/id/2000", book);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<DabResponse<Book>>();
if (result?.Error is not null)
{
throw new Exception($"{result.Error.Code}: {result.Error.Message}");
}
book = {"id": 2000, "title": "Leviathan Wakes", "year": 2011, "pages": 577}
response = requests.patch(f"{base_url}/api/book/id/2000", json=book)
response.raise_for_status()
data = response.json()
if "error" in data:
err = data["error"]
raise Exception(f"{err['code']}: {err['message']}")
const book = { id: 2000, title: "Leviathan Wakes", year: 2011, pages: 577 };
const response = await fetch(`${baseUrl}/api/book/id/2000`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(book),
});
const data = await response.json();
if (data.error) {
throw new Error(`${data.error.code}: ${data.error.message}`);
}
Delete a record
Use DELETE to remove an item by primary key.
DELETE https://localhost:5001/api/book/id/2000
curl -X DELETE "https://localhost:5001/api/book/id/2000"
var response = await httpClient.DeleteAsync("api/book/id/2000");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<DabResponse<Book>>();
if (result?.Error is not null)
{
throw new Exception($"{result.Error.Code}: {result.Error.Message}");
}
response = requests.delete(f"{base_url}/api/book/id/2000")
response.raise_for_status()
data = response.json()
if "error" in data:
err = data["error"]
raise Exception(f"{err['code']}: {err['message']}")
const response = await fetch(`${baseUrl}/api/book/id/2000`, {
method: "DELETE",
});
const data = await response.json();
if (data.error) {
throw new Error(`${data.error.code}: ${data.error.message}`);
}