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_c.h"
#include <stdio.h>
#include <stdlib.h>
int main() {
bosbase_client* pb = bosbase_client_new("http://127.0.0.1:8090", "en-US");
// Basic list with pagination
char* records_json = NULL;
if (bosbase_collection_get_list(pb, "posts", 1, 50, NULL, NULL, NULL,
NULL, "{}", "{}", &records_json, NULL) == 0) {
printf("Records: %s\n", records_json);
bosbase_free_string(records_json);
}
bosbase_client_free(pb);
return 0;
}
Advanced List with Filtering and Sorting
// Filter and sort
char* result_json = NULL;
if (bosbase_collection_get_list(pb, "posts", 1, 50,
"created >= \"2022-01-01 00:00:00\" && status = \"published\"",
"-created,title", // DESC by created, ASC by title
"author,categories", NULL, "{}", "{}", &result_json, NULL) == 0) {
printf("Filtered records: %s\n", result_json);
bosbase_free_string(result_json);
}
// Filter with operators
char* result2_json = NULL;
if (bosbase_collection_get_list(pb, "posts", 1, 50,
"title ~ \"javascript\" && views > 100",
"-views", NULL, NULL, "{}", "{}", &result2_json, NULL) == 0) {
printf("Search results: %s\n", result2_json);
bosbase_free_string(result2_json);
}
Get Full List
Fetch all records at once (useful for small collections):
// Get all records with batch size
char* all_posts_json = NULL;
if (bosbase_collection_get_full_list(pb, "posts", 200,
"status = \"published\"", "-created", NULL, NULL,
"{}", "{}", &all_posts_json, NULL) == 0) {
printf("All posts: %s\n", all_posts_json);
bosbase_free_string(all_posts_json);
}
View Record
Retrieve a single record by ID:
// Basic retrieval
char* record_json = NULL;
if (bosbase_collection_get_one(pb, "posts", "RECORD_ID",
NULL, NULL, "{}", "{}", &record_json, NULL) == 0) {
printf("Record: %s\n", record_json);
bosbase_free_string(record_json);
}
// With expanded relations
char* record_expanded_json = NULL;
if (bosbase_collection_get_one(pb, "posts", "RECORD_ID",
"author,categories,tags", NULL, "{}", "{}",
&record_expanded_json, NULL) == 0) {
printf("Record with relations: %s\n", record_expanded_json);
bosbase_free_string(record_expanded_json);
}
// Nested expand
char* nested_json = NULL;
if (bosbase_collection_get_one(pb, "comments", "COMMENT_ID",
"post.author,user", NULL, "{}", "{}", &nested_json, NULL) == 0) {
printf("Nested relations: %s\n", nested_json);
bosbase_free_string(nested_json);
}
// Field selection (via query_json)
const char* query = "{\"fields\":\"id,title,content,author.name\"}";
char* selected_json = NULL;
if (bosbase_collection_get_one(pb, "posts", "RECORD_ID",
NULL, NULL, query, "{}", &selected_json, NULL) == 0) {
printf("Selected fields: %s\n", selected_json);
bosbase_free_string(selected_json);
}
Create Record
Create a new record:
// Simple create
const char* body = "{\"title\":\"My First Post\","
"\"content\":\"Lorem ipsum...\","
"\"status\":\"draft\"}";
char* created_json = NULL;
if (bosbase_collection_create(pb, "posts", body, NULL, 0,
NULL, NULL, "{}", "{}", &created_json, NULL) == 0) {
printf("Created: %s\n", created_json);
bosbase_free_string(created_json);
}
// Create with relations
const char* body_with_relations =
"{\"title\":\"My Post\","
"\"author\":\"AUTHOR_ID\","
"\"categories\":[\"cat1\",\"cat2\"]}";
char* relation_json = NULL;
if (bosbase_collection_create(pb, "posts", body_with_relations, NULL, 0,
NULL, NULL, "{}", "{}", &relation_json, NULL) == 0) {
printf("Created with relations: %s\n", relation_json);
bosbase_free_string(relation_json);
}
// Create with file upload
bosbase_file_attachment files[1] = {
{
.field = "image",
.filename = "photo.jpg",
.content_type = "image/jpeg",
.data = image_data,
.data_len = image_size
}
};
const char* body_with_file = "{\"title\":\"My Post\"}";
char* file_json = NULL;
if (bosbase_collection_create(pb, "posts", body_with_file, files, 1,
NULL, NULL, "{}", "{}", &file_json, NULL) == 0) {
printf("Created with file: %s\n", file_json);
bosbase_free_string(file_json);
}
// Create with expand to get related data immediately
const char* query_expand = "{\"expand\":\"author\"}";
char* expanded_json = NULL;
if (bosbase_collection_create(pb, "posts", body_with_relations, NULL, 0,
"author", NULL, query_expand, "{}", &expanded_json, NULL) == 0) {
printf("Created with expand: %s\n", expanded_json);
bosbase_free_string(expanded_json);
}
Update Record
Update an existing record:
// Simple update
const char* update_body =
"{\"title\":\"Updated Title\",\"status\":\"published\"}";
char* updated_json = NULL;
if (bosbase_collection_update(pb, "posts", "RECORD_ID", update_body,
NULL, 0, NULL, NULL, "{}", "{}", &updated_json, NULL) == 0) {
printf("Updated: %s\n", updated_json);
bosbase_free_string(updated_json);
}
// Update with relations using modifiers
const char* relation_update =
"{\"categories+\":\"NEW_CATEGORY_ID\",\"tags-\":\"OLD_TAG_ID\"}";
char* mod_json = NULL;
if (bosbase_collection_update(pb, "posts", "RECORD_ID", relation_update,
NULL, 0, NULL, NULL, "{}", "{}", &mod_json, NULL) == 0) {
printf("Updated relations: %s\n", mod_json);
bosbase_free_string(mod_json);
}
// Update with file upload
bosbase_file_attachment new_file[1] = {
{
.field = "image",
.filename = "new_photo.jpg",
.content_type = "image/jpeg",
.data = new_image_data,
.data_len = new_image_size
}
};
const char* file_update = "{\"title\":\"Updated Title\"}";
char* file_update_json = NULL;
if (bosbase_collection_update(pb, "posts", "RECORD_ID", file_update,
new_file, 1, NULL, NULL, "{}", "{}", &file_update_json, NULL) == 0) {
printf("Updated with file: %s\n", file_update_json);
bosbase_free_string(file_update_json);
}
Delete Record
Delete a record:
// Simple delete
bosbase_error* err = NULL;
if (bosbase_collection_delete(pb, "posts", "RECORD_ID",
NULL, "{}", "{}", &err) != 0) {
fprintf(stderr, "Delete failed: %s\n", err->message);
bosbase_free_error(err);
} else {
printf("Record deleted successfully\n");
}
Filter Syntax
The filter parameter supports a powerful query syntax:
Comparison Operators
// Equal
const char* filter = "status = \"published\"";
// Not equal
const char* filter2 = "status != \"draft\"";
// Greater than / Less than
const char* filter3 = "views > 100";
const char* filter4 = "created < \"2023-01-01\"";
// Greater/Less than or equal
const char* filter5 = "age >= 18";
const char* filter6 = "price <= 99.99";
String Operators
// Contains (like)
const char* filter = "title ~ \"javascript\"";
// Equivalent to: title LIKE "%javascript%"
// Not contains
const char* filter2 = "title !~ \"deprecated\"";
// Exact match (case-sensitive)
const char* filter3 = "email = \"user@example.com\"";
Array Operators (for multiple relations/files)
// Any of / At least one
const char* filter = "tags.id ?= \"TAG_ID\""; // Any tag matches
const char* filter2 = "tags.name ?~ \"important\""; // Any tag name contains "important"
// All must match
const char* filter3 = "tags.id = \"TAG_ID\" && tags.id = \"TAG_ID2\"";
Logical Operators
// AND
const char* filter = "status = \"published\" && views > 100";
// OR
const char* filter2 = "status = \"published\" || status = \"featured\"";
// Parentheses for grouping
const char* filter3 = "(status = \"published\" || featured = true) && views > 50";
Special Identifiers
// Request context (only in API rules, not client filters)
// @request.auth.id, @request.query.*, etc.
// Collection joins
const char* filter = "@collection.users.email = \"test@example.com\"";
// Record fields
const char* filter2 = "author.id = @request.auth.id";
Sorting
Sort records using the sort parameter:
// Single field (ASC)
const char* sort = "created";
// Single field (DESC)
const char* sort2 = "-created";
// Multiple fields
const char* sort3 = "-created,title"; // DESC by created, then ASC by title
// Supported fields
const char* sort4 = "@random"; // Random order
const char* sort5 = "@rowid"; // Internal row ID
const char* sort6 = "id"; // Record ID
const char* sort7 = "fieldName"; // Any collection field
// Relation field sorting
const char* sort8 = "author.name"; // Sort by related author's name
Field Selection
Control which fields are returned via query_json:
// Specific fields
const char* query = "{\"fields\":\"id,title,content\"}";
// All fields at level
const char* query2 = "{\"fields\":\"*\"}";
// Nested field selection
const char* query3 = "{\"fields\":\"*,author.name,author.email\"}";
// Excerpt modifier for text fields
const char* query4 = "{\"fields\":\"*,content:excerpt(200,true)\"}";
// Returns first 200 characters with ellipsis if truncated
Expanding Relations
Expand related records without additional API calls:
// Single relation
const char* expand = "author";
// Multiple relations
const char* expand2 = "author,categories,tags";
// Nested relations (up to 6 levels)
const char* expand3 = "author.profile,categories.tags";
// Back-relations
const char* expand4 = "comments_via_post.user";
See Relations Documentation for detailed information.
Pagination Options
// Skip total count (faster queries) - via query_json
const char* query = "{\"skipTotal\":true}";
char* result_json = NULL;
if (bosbase_collection_get_list(pb, "posts", 1, 50,
"status = \"published\"", NULL, NULL, NULL,
query, "{}", &result_json, NULL) == 0) {
// totalItems and totalPages will be -1
printf("Results: %s\n", result_json);
bosbase_free_string(result_json);
}
// Get Full List with batch processing
char* all_json = NULL;
if (bosbase_collection_get_full_list(pb, "posts", 200,
NULL, "-created", NULL, NULL, "{}", "{}", &all_json, NULL) == 0) {
// Processes in batches of 200 to avoid memory issues
printf("All records: %s\n", all_json);
bosbase_free_string(all_json);
}
Batch Operations
Execute multiple operations in a single transaction using bosbase_send:
// Create batch request body
const char* batch_body =
"{"
"\"requests\":["
"{\"method\":\"POST\",\"url\":\"/api/collections/posts/records\","
"\"body\":{\"title\":\"Post 1\",\"author\":\"AUTHOR_ID\"}},"
"{\"method\":\"POST\",\"url\":\"/api/collections/posts/records\","
"\"body\":{\"title\":\"Post 2\",\"author\":\"AUTHOR_ID\"}},"
"{\"method\":\"PATCH\",\"url\":\"/api/collections/tags/records/TAG_ID\","
"\"body\":{\"name\":\"Updated Tag\"}},"
"{\"method\":\"DELETE\",\"url\":\"/api/collections/categories/records/CAT_ID\"}"
"]"
"}";
char* batch_result = NULL;
bosbase_error* err = NULL;
if (bosbase_send(pb, "/api/batch", "POST", batch_body,
"{}", "{}", 30000, NULL, 0, &batch_result, &err) == 0) {
printf("Batch results: %s\n", batch_result);
bosbase_free_string(batch_result);
} else {
fprintf(stderr, "Batch failed: %s\n", err->message);
bosbase_free_error(err);
}
Note: Batch operations must be enabled in Dashboard > Settings > Application.
Complete Examples
Example 1: Blog Post Search with Filters
void search_posts(bosbase_client* pb, const char* query,
const char* category_id, int min_views) {
char filter[512];
snprintf(filter, sizeof(filter),
"title ~ \"%s\" || content ~ \"%s\"", query, query);
if (category_id) {
char temp[256];
snprintf(temp, sizeof(temp), " && categories.id ?= \"%s\"", category_id);
strncat(filter, temp, sizeof(filter) - strlen(filter) - 1);
}
if (min_views > 0) {
char temp[128];
snprintf(temp, sizeof(temp), " && views >= %d", min_views);
strncat(filter, temp, sizeof(filter) - strlen(filter) - 1);
}
char* result_json = NULL;
if (bosbase_collection_get_list(pb, "posts", 1, 20,
filter, "-created", "author,categories", NULL,
"{}", "{}", &result_json, NULL) == 0) {
printf("Search results: %s\n", result_json);
bosbase_free_string(result_json);
}
}
Example 2: Advanced Filtering
// Complex filter example
const char* complex_filter =
"(status = \"published\" || featured = true) && "
"created >= \"2023-01-01\" && "
"(tags.id ?= \"important\" || categories.id = \"news\") && "
"views > 100 && "
"author.email != \"\"";
const char* query = "{\"fields\":\"*,content:excerpt(300),author.name,author.email\"}";
char* result_json = NULL;
if (bosbase_collection_get_list(pb, "posts", 1, 50,
complex_filter, "-views,created",
"author.profile,tags,categories", NULL,
query, "{}", &result_json, NULL) == 0) {
printf("Filtered results: %s\n", result_json);
bosbase_free_string(result_json);
}
Example 3: Pagination Helper
void get_all_records_paginated(bosbase_client* pb,
const char* collection_name,
const char* filter,
const char* sort) {
int page = 1;
int has_more = 1;
while (has_more) {
const char* query = "{\"skipTotal\":true}";
char* result_json = NULL;
if (bosbase_collection_get_list(pb, collection_name, page, 500,
filter, sort, NULL, NULL, query, "{}",
&result_json, NULL) == 0) {
// Parse JSON to check if there are more records
// (In practice, use a JSON library)
printf("Page %d: %s\n", page, result_json);
// Check if we got 500 records (likely more available)
// has_more = (number_of_items == 500);
bosbase_free_string(result_json);
page++;
} else {
has_more = 0;
}
}
}
Error Handling
char* result = NULL;
bosbase_error* err = NULL;
if (bosbase_collection_create(pb, "posts", body, NULL, 0,
NULL, NULL, "{}", "{}", &result, &err) != 0) {
if (err) {
if (err->status == 400) {
fprintf(stderr, "Validation error: %s\n", err->message);
} else if (err->status == 403) {
fprintf(stderr, "Permission denied\n");
} else if (err->status == 404) {
fprintf(stderr, "Collection or record not found\n");
} else {
fprintf(stderr, "Error %d: %s\n", err->status, err->message);
}
bosbase_free_error(err);
}
return 1;
}
// Use result...
bosbase_free_string(result);
Best Practices
- Use Pagination: Always use pagination for large datasets
- Skip Total When Possible: Use
skipTotal: truein query_json for better performance - 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
- Memory Management: Always free strings returned by SDK functions
- Error Handling: Always check return values and handle 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