API Records - C++ SDK Documentation
Overview
The Records API provides comprehensive CRUD (Create, Read, Update, Delete) operations for collection records, along with powerful search, filtering, and authentication capabilities.
Key Features:
- Paginated list and search with filtering and sorting
- Single record retrieval with expand support
- Create, update, and delete operations
- Batch operations for multiple records
- Authentication methods (password, OAuth2, OTP)
- Email verification and password reset
- Relation expansion up to 6 levels deep
- Field selection and excerpt modifiers
Backend Endpoints:
GET /api/collections/{collection}/records- List recordsGET /api/collections/{collection}/records/{id}- View recordPOST /api/collections/{collection}/records- Create recordPATCH /api/collections/{collection}/records/{id}- Update recordDELETE /api/collections/{collection}/records/{id}- Delete recordPOST /api/batch- Batch operations
CRUD Operations
List/Search Records
Returns a paginated records list with support for sorting, filtering, and expansion.
#include "bosbase/bosbase.h"
using namespace bosbase;
BosBase pb("http://127.0.0.1:8090");
// Basic list with pagination
auto result = pb.collection("posts").getList(1, 50);
std::cout << "Page: " << result["page"] << std::endl;
std::cout << "Per Page: " << result["perPage"] << std::endl;
std::cout << "Total Items: " << result["totalItems"] << std::endl;
std::cout << "Total Pages: " << result["totalPages"] << std::endl;
auto items = result["items"]; // Array of records
Advanced List with Filtering and Sorting
// Filter and sort
auto result = pb.collection("posts").getList(1, 50, false,
std::map<std::string, nlohmann::json>{},
std::map<std::string, std::string>{},
"created >= \"2022-01-01 00:00:00\" && status = \"published\"",
"-created,title", // DESC by created, ASC by title
"author,categories"
);
// Filter with operators
auto result2 = pb.collection("posts").getList(1, 50, false,
std::map<std::string, nlohmann::json>{},
std::map<std::string, std::string>{},
"title ~ \"javascript\" && views > 100",
"-views"
);
Get Full List
Fetch all records at once (useful for small collections):
// Get all records
auto allPosts = pb.collection("posts").getFullList(500,
std::map<std::string, nlohmann::json>{},
std::map<std::string, std::string>{},
"status = \"published\"",
"-created"
);
Get First Matching Record
Get only the first record that matches a filter:
auto post = pb.collection("posts").getFirstListItem(
"slug = \"my-post-slug\"",
std::map<std::string, nlohmann::json>{},
std::map<std::string, std::string>{},
"author,categories.tags"
);
View Record
Retrieve a single record by ID:
// Basic retrieval
auto record = pb.collection("posts").getOne("RECORD_ID");
// With expanded relations
auto record = pb.collection("posts").getOne("RECORD_ID",
std::map<std::string, nlohmann::json>{},
std::map<std::string, std::string>{},
"author,categories,tags"
);
// Nested expand
auto record = pb.collection("comments").getOne("COMMENT_ID",
std::map<std::string, nlohmann::json>{},
std::map<std::string, std::string>{},
"post.author,user"
);
// Field selection
auto record = pb.collection("posts").getOne("RECORD_ID",
std::map<std::string, nlohmann::json>{},
std::map<std::string, std::string>{},
std::nullopt,
"id,title,content,author.name"
);
Create Record
Create a new record:
// Simple create
auto record = pb.collection("posts").create(nlohmann::json{
{"title", "My First Post"},
{"content", "Lorem ipsum..."},
{"status", "draft"}
});
// Create with relations
auto record = pb.collection("posts").create(nlohmann::json{
{"title", "My Post"},
{"author", "AUTHOR_ID"}, // Single relation
{"categories", nlohmann::json::array({"cat1", "cat2"})} // Multiple relation
});
// Create with file upload
std::vector<FileAttachment> files;
files.push_back(FileAttachment{
"image",
"photo.jpg",
"image/jpeg",
imageData
});
auto record = pb.collection("posts").create(nlohmann::json{
{"title", "My Post"}
}, std::map<std::string, nlohmann::json>{}, files);
// Create with expand to get related data immediately
auto record = pb.collection("posts").create(nlohmann::json{
{"title", "My Post"},
{"author", "AUTHOR_ID"}
}, std::map<std::string, nlohmann::json>{}, std::vector<FileAttachment>{},
std::map<std::string, std::string>{}, "author");
Update Record
Update an existing record:
// Simple update
auto record = pb.collection("posts").update("RECORD_ID", nlohmann::json{
{"title", "Updated Title"},
{"status", "published"}
});
// Update with relations
pb.collection("posts").update("RECORD_ID", nlohmann::json{
{"categories+", "NEW_CATEGORY_ID"}, // Append
{"tags-", "OLD_TAG_ID"} // Remove
});
// Update with file upload
std::vector<FileAttachment> files;
files.push_back(FileAttachment{
"image",
"newImage.jpg",
"image/jpeg",
newImageData
});
auto record = pb.collection("posts").update("RECORD_ID",
nlohmann::json{{"title", "Updated Title"}},
std::map<std::string, nlohmann::json>{}, files);
// Update with expand
auto record = pb.collection("posts").update("RECORD_ID",
nlohmann::json{{"title", "Updated"}},
std::map<std::string, nlohmann::json>{},
std::vector<FileAttachment>{},
std::map<std::string, std::string>{},
"author,categories");
Delete Record
Delete a record:
// Simple delete
pb.collection("posts").remove("RECORD_ID");
// Note: Returns 204 No Content on success
// Throws error if record doesn't exist or permission denied
Filter Syntax
The filter parameter supports a powerful query syntax:
Comparison Operators
// Equal
filter: "status = \"published\""
// Not equal
filter: "status != \"draft\""
// Greater than / Less than
filter: "views > 100"
filter: "created < \"2023-01-01\""
// Greater/Less than or equal
filter: "age >= 18"
filter: "price <= 99.99"
String Operators
// Contains (like)
filter: "title ~ \"javascript\""
// Equivalent to: title LIKE "%javascript%"
// Not contains
filter: "title !~ \"deprecated\""
// Exact match (case-sensitive)
filter: "email = \"user@example.com\""
Array Operators (for multiple relations/files)
// Any of / At least one
filter: "tags.id ?= \"TAG_ID\"" // Any tag matches
filter: "tags.name ?~ \"important\"" // Any tag name contains "important"
// All must match
filter: "tags.id = \"TAG_ID\" && tags.id = \"TAG_ID2\""
Logical Operators
// AND
filter: "status = \"published\" && views > 100"
// OR
filter: "status = \"published\" || status = \"featured\""
// Parentheses for grouping
filter: "(status = \"published\" || featured = true) && views > 50"
Sorting
Sort records using the sort parameter:
// Single field (ASC)
sort: "created"
// Single field (DESC)
sort: "-created"
// Multiple fields
sort: "-created,title" // DESC by created, then ASC by title
// Supported fields
sort: "@random" // Random order
sort: "@rowid" // Internal row ID
sort: "id" // Record ID
sort: "fieldName" // Any collection field
// Relation field sorting
sort: "author.name" // Sort by related author's name
Field Selection
Control which fields are returned:
// Specific fields
fields: "id,title,content"
// All fields at level
fields: "*"
// Nested field selection
fields: "*,author.name,author.email"
// Excerpt modifier for text fields
fields: "*,content:excerpt(200,true)"
// Returns first 200 characters with ellipsis if truncated
Expanding Relations
Expand related records without additional API calls:
// Single relation
expand: "author"
// Multiple relations
expand: "author,categories,tags"
// Nested relations (up to 6 levels)
expand: "author.profile,categories.tags"
// Back-relations
expand: "comments_via_post.user"
See Relations Documentation for detailed information.
Pagination Options
// Skip total count (faster queries)
auto result = pb.collection("posts").getList(1, 50, true, // skip_total = true
std::map<std::string, nlohmann::json>{},
std::map<std::string, std::string>{},
"status = \"published\""
);
// totalItems and totalPages will be -1
// Get Full List with batch processing
auto allPosts = pb.collection("posts").getFullList(200,
std::map<std::string, nlohmann::json>{},
std::map<std::string, std::string>{},
std::nullopt,
"-created"
);
// Processes in batches of 200 to avoid memory issues
Batch Operations
Execute multiple operations in a single transaction:
// Create a batch
auto batch = pb.createBatch();
// Add operations
batch->collection("posts").create(nlohmann::json{
{"title", "Post 1"},
{"author", "AUTHOR_ID"}
});
batch->collection("posts").create(nlohmann::json{
{"title", "Post 2"},
{"author", "AUTHOR_ID"}
});
batch->collection("tags").update("TAG_ID", nlohmann::json{
{"name", "Updated Tag"}
});
batch->collection("categories").remove("CAT_ID");
// Upsert (create or update based on id)
batch->collection("posts").update("EXISTING_ID", nlohmann::json{
{"id", "EXISTING_ID"},
{"title", "Updated Post"}
});
// Send batch request
auto results = batch->send();
// Results is an array matching the order of operations
for (size_t i = 0; i < results.size(); ++i) {
auto result = results[i];
if (result["status"].get<int>() >= 400) {
std::cerr << "Operation " << i << " failed: " << result["body"].dump() << std::endl;
} else {
std::cout << "Operation " << i << " succeeded: " << result["body"].dump() << std::endl;
}
}
Note: Batch operations must be enabled in Dashboard > Settings > Application.
Authentication Actions
List Auth Methods
Get available authentication methods for a collection:
auto methods = pb.collection("users").listAuthMethods();
std::cout << "Password enabled: " << methods["password"]["enabled"] << std::endl;
std::cout << "OAuth2 enabled: " << methods["oauth2"]["enabled"] << std::endl;
auto providers = methods["oauth2"]["providers"]; // Array of OAuth2 providers
std::cout << "OTP enabled: " << methods["otp"]["enabled"] << std::endl;
std::cout << "MFA enabled: " << methods["mfa"]["enabled"] << std::endl;
Auth with Password
auto authData = pb.collection("users").authWithPassword(
"user@example.com", // username or email
"password123"
);
// Auth data is automatically stored in pb.authStore()
std::cout << "Is valid: " << pb.authStore()->isValid() << std::endl;
std::cout << "Token: " << pb.authStore()->token() << std::endl;
std::cout << "User ID: " << pb.authStore()->record()["id"] << std::endl;
// Access the returned data
std::cout << "Token: " << authData["token"] << std::endl;
std::cout << "Record: " << authData["record"].dump() << std::endl;
// With expand
auto authData2 = pb.collection("users").authWithPassword(
"user@example.com",
"password123",
"profile" // expand
);
Auth with OAuth2
// Step 1: Get OAuth2 URL (usually done in UI)
auto methods = pb.collection("users").listAuthMethods();
// Find provider in methods["oauth2"]["providers"]
// Step 2: After redirect, exchange code for token
auto authData = pb.collection("users").authWithOAuth2Code(
"google", // Provider name
"AUTHORIZATION_CODE", // From redirect URL
provider["codeVerifier"], // From step 1
"https://yourapp.com/callback", // Redirect URL
nlohmann::json{{"name", "John Doe"}} // Optional data for new accounts
);
Auth with OTP (One-Time Password)
// Step 1: Request OTP
auto otpRequest = pb.collection("users").requestOTP("user@example.com");
// Returns: {"otpId": "..."}
// Step 2: User enters OTP from email
// Step 3: Authenticate with OTP
auto authData = pb.collection("users").authWithOTP(
otpRequest["otpId"].get<std::string>(),
"123456" // OTP from email
);
Auth Refresh
Refresh the current auth token and get updated user data:
// Refresh auth (useful on page reload)
auto authData = pb.collection("users").authRefresh();
// Check if still valid
if (pb.authStore()->isValid()) {
std::cout << "User is authenticated" << std::endl;
} else {
std::cout << "Token expired or invalid" << std::endl;
}
Email Verification
// Request verification email
pb.collection("users").requestVerification("user@example.com");
// Confirm verification (on verification page)
pb.collection("users").confirmVerification("VERIFICATION_TOKEN");
Password Reset
// Request password reset email
pb.collection("users").requestPasswordReset("user@example.com");
// Confirm password reset (on reset page)
// Note: This invalidates all previous auth tokens
pb.collection("users").confirmPasswordReset(
"RESET_TOKEN",
"newpassword123",
"newpassword123" // Confirm
);
Email Change
// Must be authenticated first
pb.collection("users").authWithPassword("user@example.com", "password");
// Request email change
pb.collection("users").requestEmailChange("newemail@example.com");
// Confirm email change (on confirmation page)
// Note: This invalidates all previous auth tokens
pb.collection("users").confirmEmailChange(
"EMAIL_CHANGE_TOKEN",
"currentpassword"
);
Impersonate (Superuser Only)
Generate a token to authenticate as another user:
// Must be authenticated as superuser
pb.collection("_superusers").authWithPassword("admin@example.com", "password");
// Impersonate a user
auto impersonateClient = pb.collection("users").impersonate("USER_ID", 3600);
// Returns a new client instance with impersonated user's token
// Use the impersonated client
auto posts = impersonateClient.collection("posts").getFullList();
// Access the token
std::cout << "Token: " << impersonateClient.authStore()->token() << std::endl;
std::cout << "Record: " << impersonateClient.authStore()->record().dump() << std::endl;
Complete Examples
Example 1: Blog Post Search with Filters
nlohmann::json searchPosts(const std::string& query,
const std::string& categoryId,
int minViews) {
std::string filter = "title ~ \"" + query + "\" || content ~ \"" + query + "\"";
if (!categoryId.empty()) {
filter += " && categories.id ?= \"" + categoryId + "\"";
}
if (minViews > 0) {
filter += " && views >= " + std::to_string(minViews);
}
auto result = pb.collection("posts").getList(1, 20, false,
std::map<std::string, nlohmann::json>{},
std::map<std::string, std::string>{},
filter,
"-created",
"author,categories"
);
return result["items"];
}
Example 2: User Dashboard with Related Content
nlohmann::json getUserDashboard(const std::string& userId) {
// Get user's posts
auto posts = pb.collection("posts").getList(1, 10, false,
std::map<std::string, nlohmann::json>{},
std::map<std::string, std::string>{},
"author = \"" + userId + "\"",
"-created",
"categories"
);
// Get user's comments
auto comments = pb.collection("comments").getList(1, 10, false,
std::map<std::string, nlohmann::json>{},
std::map<std::string, std::string>{},
"user = \"" + userId + "\"",
"-created",
"post"
);
return nlohmann::json{
{"posts", posts["items"]},
{"comments", comments["items"]}
};
}
Example 3: Advanced Filtering
// Complex filter example
auto result = pb.collection("posts").getList(1, 50, false,
std::map<std::string, nlohmann::json>{},
std::map<std::string, std::string>{},
"(status = \"published\" || featured = true) && "
"created >= \"2023-01-01\" && "
"(tags.id ?= \"important\" || categories.id = \"news\") && "
"views > 100 && "
"author.email != \"\"",
"-views,created",
"author.profile,tags,categories",
"*,content:excerpt(300),author.name,author.email"
);
Example 4: Batch Create Posts
std::vector<nlohmann::json> createMultiplePosts(
const std::vector<nlohmann::json>& postsData) {
auto batch = pb.createBatch();
for (const auto& postData : postsData) {
batch->collection("posts").create(postData);
}
auto results = batch->send();
// Check for failures
std::vector<nlohmann::json> failures;
for (size_t i = 0; i < results.size(); ++i) {
if (results[i]["status"].get<int>() >= 400) {
failures.push_back(nlohmann::json{
{"index", i},
{"result", results[i]}
});
}
}
if (!failures.empty()) {
std::cerr << "Some posts failed to create" << std::endl;
}
std::vector<nlohmann::json> created;
for (const auto& r : results) {
created.push_back(r["body"]);
}
return created;
}
Error Handling
#include "bosbase/error.h"
#include <stdexcept>
try {
auto record = pb.collection("posts").create(nlohmann::json{
{"title", "My Post"}
});
} catch (const ClientResponseError& error) {
if (error.status() == 400) {
// Validation error
std::cerr << "Validation errors: " << error.data().dump() << std::endl;
} else if (error.status() == 403) {
// Permission denied
std::cerr << "Access denied" << std::endl;
} else if (error.status() == 404) {
// Not found
std::cerr << "Collection or record not found" << std::endl;
} else {
std::cerr << "Unexpected error: " << error.what() << std::endl;
}
}
Best Practices
- Use Pagination: Always use pagination for large datasets
- Skip Total When Possible: Use
skip_total = truefor better performance when you don’t need counts - Batch Operations: Use batch for multiple operations to reduce round trips
- Field Selection: Only request fields you need to reduce payload size
- Expand Wisely: Only expand relations you actually use
- Filter Before Sort: Apply filters before sorting for better performance
- Cache Auth Tokens: Auth tokens are automatically stored in
authStore, no need to manually cache - Handle Errors: Always handle authentication and permission errors gracefully
Related Documentation
- Collections - Collection configuration
- Relations - Working with relations
- API Rules and Filters - Filter syntax details
- Authentication - Detailed authentication guide
- Files - File uploads and handling