Docs
BlogHomeStart building

Database

Read the schema, query with rich filters, and mutate records and relations in your project database.


The database API exposes your project's tables and records. You can read the table structure, query with filtering/sorting/pagination/aggregation and nested relations up to 6 levels deep, create, edit and delete records, and link or unlink records across a many-to-many relation. All endpoints require the api-key header and are free — reading and writing a project's own data is not metered; only agent work and infrastructure are.

#Get Database Tables Structure

GET/api/v1/vcaas/projects/:projectId/database/tables-structureFree

Retrieve all database table definitions including field names, types, and configuration.

Path parameters

ParameterTypeDescription
projectIdstringThe project ID

Response fields

FieldTypeDescription
data.tablesarrayArray of table definitions
data.tables[]._idstringTable ID
data.tables[].typestringTable name (snake_case)
data.tables[].labelstringHuman-friendly display name
data.tables[].descriptionstringTable description
data.tables[].iconstringFontAwesome icon class
data.tables[].propertiesobjectMap of property name to property definition
data.tables[].properties.{name}.idstringProperty ID
data.tables[].properties.{name}.namestringProperty field name
data.tables[].properties.{name}.propertyTypestring"string" | "number" | "date" | "options" | "file" | "long-string" | "objectReference"
data.tables[].properties.{name}.labelstringHuman-friendly field label
data.tables[].properties.{name}.descriptionstringField description
data.tables[].properties.{name}.objectReferenceobjectRelationship config (if objectReference type)
data.tables[].properties.{name}.typeExtrasobjectType-specific config (options values, date settings, etc.)

Example request

bash
curl -H "api-key: tlm_sk_your_key" \
  https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/database/tables-structure
Success · 200 OK
json
{
  "errors": null,
  "data": {
    "tables": [
      {
        "_id": "65f1a2b3c4d5e6f7",
        "type": "customers",
        "label": "Customers",
        "description": "Customer records",
        "icon": "fa-solid fa-users",
        "properties": {
          "name": {
            "id": "prop_abc123",
            "name": "name",
            "propertyType": "string",
            "label": "Full Name"
          },
          "email": {
            "id": "prop_def456",
            "name": "email",
            "propertyType": "string",
            "label": "Email",
            "typeExtras": { "string": { "type": "link" } }
          }
        }
      }
    ]
  }
}
Error · 404
json
{
  "errors": {
    "errorCode": "PROJECT_NOT_FOUND",
    "errorMessage": "Project does not exist or you don't own it"
  },
  "data": null
}

Error codes

CodeHTTPMeaning
MISSING_PROJECT_ID400projectId is required
PROJECT_NOT_FOUND404Project does not exist or you don't own it
End of Get Database Tables StructureNext endpointPOSTQuery Database

#Query Database

POST/api/v1/vcaas/projects/:projectId/database/queryFree

Query records from any table with advanced filtering, sorting, pagination, aggregations, and nested related data up to 6 levels deep.

Path parameters

ParameterTypeDescription
projectIdstringThe project ID

Body parameters

FieldTypeRequiredDescription
tableNamestringYesThe table name to query (use type from tables-structure response)
queryOptionsobjectNoAdvanced query options
queryOptions._filterobjectNoField filters (see filter operators below)
queryOptions._sortobjectNoSort by fields: { fieldName: "asc" | "desc" }
queryOptions._limitnumberNoMax records to return (default 50, max 1000)
queryOptions._offsetnumberNoRecords to skip (for pagination)
queryOptions._selectobjectNoInclude only specified fields: { fieldName: true }. Cannot use with _omit
queryOptions._omitobjectNoExclude specified fields: { fieldName: true }. Cannot use with _select
queryOptions._countbooleanNoAdds _count._total with total matching records (before pagination)
queryOptions._aggregateobjectNoAggregations: { _sum: { field: true }, _avg, _min, _max, _count }
queryOptions._groupBystring | string[]NoGroup results by field(s). Requires _aggregate
queryOptions.[property]true | objectNoExpand related data. true = all fields, object = nested queryOptions

Filter operators (use inside _filter)

  • Exact match: { "status": "active" }
  • With operator: { "fieldName": { "operator": value } }
OperatorDescriptionExample
gteGreater than or equal{ "age": { "gte": 18 } }
lteLess than or equal{ "age": { "lte": 65 } }
gtGreater than{ "price": { "gt": 0 } }
ltLess than{ "price": { "lt": 100 } }
neNot equal{ "status": { "ne": "deleted" } }
inMatches any value in array{ "status": { "in": ["active", "pending"] } }
ninMatches none in array{ "role": { "nin": ["admin", "super"] } }
regexRegex pattern (+options for flags){ "email": { "regex": "@gmail", "options": "i" } }
containsCase-insensitive contains{ "name": { "contains": "john" } }
startsWithCase-insensitive starts with{ "name": { "startsWith": "J" } }
endsWithCase-insensitive ends with{ "email": { "endsWith": ".com" } }
_orOR logic (array of conditions){ "_or": [{ "status": "active" }, { "role": "admin" }] }

Nested queries(related data)

Expand related tables by using the property name as a key in queryOptions.

  • Shorthand: { "orders": true } — expands all related orders
  • Full: { "orders": { "_filter": {...}, "_sort": {...}, "_limit": 5 } } — with options (max 300 children)
  • { "orders": { "_has": true } } — only parents with at least one matching child
  • { "orders": { "_has": "none" } } — only parents with zero matching children
  • { "orders": { "_count": true } } — include child count per parent
  • Nesting up to 6 levels deep supported

Response fields

FieldTypeDescription
data.resultsarrayArray of matching records

Example request

bash
curl -X POST \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"tableName":"customers","queryOptions":{"_filter":{"status":"active","age":{"gte":18}},"_sort":{"createdAt":"desc"},"_limit":10,"orders":{"_filter":{"total":{"gt":50}},"_limit":5}}}' \
  https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/database/query
Success · 200 OK
json
{
  "errors": null,
  "data": {
    "results": [
      {
        "_id": "65f1a2b3c4d5e6f7a8b9c0d1",
        "name": "John Doe",
        "email": "john@example.com",
        "status": "active",
        "age": 30,
        "orders": [
          { "_id": "...", "total": 120.50, "createdAt": "2026-03-09T..." }
        ],
        "createdAt": "2026-03-10T08:00:00.000Z"
      }
    ]
  }
}
Error · 400
json
{
  "errors": {
    "errorCode": "MISSING_TABLE_NAME",
    "errorMessage": "tableName is required"
  },
  "data": null
}

More filter examples

js
// OR logic
"_filter": { "_or": [{ "status": "active" }, { "role": "admin" }] }
// IN operator
"_filter": { "status": { "in": ["active", "pending"] } }
// Regex case-insensitive
"_filter": { "email": { "regex": "@gmail", "options": "i" } }
// Aggregation with groupBy
"_aggregate": { "_sum": { "total": true }, "_count": true }, "_groupBy": "status"
// Nested: only parents with children matching filter
"orders": { "_has": true, "_filter": { "total": { "gt": 100 } } }

Error codes

CodeHTTPMeaning
MISSING_PROJECT_ID400projectId is required
PROJECT_NOT_FOUND404Project does not exist or you don't own it
MISSING_TABLE_NAME400tableName is required

#Create Database Record

POST/api/v1/vcaas/projects/:projectId/database/recordsFree

Create a new record in any table. The _id field is auto-generated by the database — do not include it in the request body. The response returns the full created record including the generated _id and any default fields.

Path parameters

ParameterTypeDescription
projectIdstringThe project ID

Body parameters

FieldTypeRequiredDescription
tableNamestringYesThe table name to insert into (use type from tables-structure response)
dataobjectYesThe record properties as key-value pairs. Do NOT include _id (auto-generated)

Response fields

FieldTypeDescription
dataobjectThe full created record including the auto-generated _id and all fields
data._idstringThe auto-generated unique identifier for the new record

Example request

bash
curl -X POST \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"tableName":"customers","data":{"name":"John Doe","email":"john@example.com","status":"active","age":30}}' \
  https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/database/records
Success · 200 OK
json
{
  "errors": null,
  "data": {
    "_id": "65f1a2b3c4d5e6f7a8b9c0d1",
    "name": "John Doe",
    "email": "john@example.com",
    "status": "active",
    "age": 30,
    "createdAt": "2026-03-25T10:00:00.000Z",
    "updatedAt": "2026-03-25T10:00:00.000Z"
  }
}
Error · 400
json
{
  "errors": {
    "errorCode": "MISSING_DATA",
    "errorMessage": "data is required and must be an object"
  },
  "data": null
}

Error codes

CodeHTTPMeaning
MISSING_PROJECT_ID400projectId is required
PROJECT_NOT_FOUND404Project does not exist or you don't own it
MISSING_TABLE_NAME400tableName is required
MISSING_DATA400data is required and must be an object
TABLE_NOT_FOUND400Table doesn't exist in the project
End of Create Database RecordNext endpointPATCHEdit Database Record

#Edit Database Record

PATCH/api/v1/vcaas/projects/:projectId/database/records/:recordIdFree

Update specific fields of an existing record by its ID. Only the fields included in data will be modified — all other fields remain unchanged. The response returns the full updated record.

Path parameters

ParameterTypeDescription
projectIdstringThe project ID
recordIdstringThe _id of the record to update

Body parameters

FieldTypeRequiredDescription
tableNamestringYesThe table name (use type from tables-structure response)
dataobjectYesThe properties to update as key-value pairs. Only included fields are modified

Response fields

FieldTypeDescription
dataobjectThe full updated record with all fields (including unchanged ones)
data._idstringThe record ID

Example request

bash
curl -X PATCH \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"tableName":"customers","data":{"email":"newemail@example.com","age":31}}' \
  https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/database/records/65f1a2b3c4d5e6f7a8b9c0d1
Success · 200 OK
json
{
  "errors": null,
  "data": {
    "_id": "65f1a2b3c4d5e6f7a8b9c0d1",
    "name": "John Doe",
    "email": "newemail@example.com",
    "status": "active",
    "age": 31,
    "createdAt": "2026-03-25T10:00:00.000Z",
    "updatedAt": "2026-03-25T10:05:00.000Z"
  }
}
Error · 404
json
{
  "errors": {
    "errorCode": "PROJECT_NOT_FOUND",
    "errorMessage": "Project does not exist or you don't own it"
  },
  "data": null
}

Error codes

CodeHTTPMeaning
MISSING_PROJECT_ID400projectId is required
PROJECT_NOT_FOUND404Project does not exist or you don't own it
MISSING_RECORD_ID400recordId is required
MISSING_TABLE_NAME400tableName is required
MISSING_DATA400data is required and must be an object
TABLE_NOT_FOUND400Table doesn't exist in the project

#Delete Database Record

DELETE/api/v1/vcaas/projects/:projectId/database/records/:recordIdFree

Permanently delete one record by its ID.

`tableName` goes in the query string, not the body

A DELETE with a request body is awkward for several HTTP clients, so this endpoint takes the table as a query parameter: ?tableName=customers.

Path parameters

ParameterTypeDescription
projectIdstringThe project ID
recordIdstringThe _id of the record to delete

Query parameters

FieldTypeRequiredDescription
tableNamestringYesThe table name (use type from the tables-structure response)

Response fields

FieldTypeDescription
data.deletedbooleantrue on successful deletion
data.recordIdstringThe ID of the record that was deleted

Example request

bash
curl -X DELETE -H "api-key: tlm_sk_your_key" \
  "https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/database/records/65f1a2b3c4d5e6f7a8b9c0d1?tableName=customers"
Success · 200 OK
json
{
  "errors": null,
  "data": {
    "deleted": true,
    "recordId": "65f1a2b3c4d5e6f7a8b9c0d1"
  }
}
Error · 400
json
{
  "errors": {
    "errorCode": "MISSING_TABLE_NAME",
    "errorMessage": "tableName is required (query parameter)"
  },
  "data": null
}

Error codes

CodeHTTPMeaning
MISSING_PROJECT_ID400projectId is required
PROJECT_NOT_FOUND404Project does not exist or you don't own it
MISSING_RECORD_ID400recordId is required
MISSING_TABLE_NAME400tableName is required as a query parameter
TABLE_NOT_FOUND400Table doesn't exist in the project
End of Delete Database RecordNext endpointPOSTLink Records
POST/api/v1/vcaas/projects/:projectId/database/records/:recordId/linkFree

Link two records across a many-to-many relation.

Many-to-many only

Totalum owns the junction table for a manyToMany relation, so links are made by asking it to add a reference — never by creating a junction row yourself.

A manyToOne or oneToMany link is an ordinary field on one of the two records: set it with Edit Database Record instead. Routing one of those through this endpoint would silently do nothing.

Path parameters

ParameterTypeDescription
projectIdstringThe project ID
recordIdstringThe _id of the record on this side of the relation

Body parameters

FieldTypeRequiredDescription
tableNamestringYesThe table recordId belongs to (use type from the tables-structure response)
propertyIdstringYesThe objectReference property on tableName that holds the relation
referenceIdstringYesThe _id of the record on the other side of the relation

Response fields

FieldTypeDescription
data.linkedbooleantrue when the link exists after the call

Example request

bash
curl -X POST \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"tableName":"customers","propertyId":"tags","referenceId":"65f1a2b3c4d5e6f7a8b9c0d2"}' \
  https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/database/records/65f1a2b3c4d5e6f7a8b9c0d1/link
Success · 200 OK
json
{ "errors": null, "data": { "linked": true } }
Error · 400
json
{
  "errors": {
    "errorCode": "MISSING_PROPERTY_ID",
    "errorMessage": "propertyId is required (the tableLink field holding the relation)"
  },
  "data": null
}

Error codes

CodeHTTPMeaning
MISSING_PROJECT_ID400projectId is required
PROJECT_NOT_FOUND404Project does not exist or you don't own it
MISSING_RECORD_ID400recordId is required
MISSING_TABLE_NAME400tableName is required
MISSING_PROPERTY_ID400propertyId is required — the relation field on the table
MISSING_REFERENCE_ID400referenceId is required — the _id of the record to link
TABLE_NOT_FOUND400Table doesn't exist in the project
DELETE/api/v1/vcaas/projects/:projectId/database/records/:recordId/linkFree

Remove a many-to-many link. The two records are untouched — only the link between them goes.

Idempotent

Removing a link that is not there succeeds. You do not need to check first.

Path parameters

ParameterTypeDescription
projectIdstringThe project ID
recordIdstringThe _id of the record on this side of the relation

Body parameters

FieldTypeRequiredDescription
tableNamestringYesThe table recordId belongs to (use type from the tables-structure response)
propertyIdstringYesThe objectReference property on tableName that holds the relation
referenceIdstringYesThe _id of the record on the other side of the relation

Response fields

FieldTypeDescription
data.unlinkedbooleantrue when the link is gone after the call

Example request

bash
curl -X DELETE \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"tableName":"customers","propertyId":"tags","referenceId":"65f1a2b3c4d5e6f7a8b9c0d2"}' \
  https://api-accounts.totalum.app/api/v1/vcaas/projects/my-app/database/records/65f1a2b3c4d5e6f7a8b9c0d1/link
Success · 200 OK
json
{ "errors": null, "data": { "unlinked": true } }
Error · 404
json
{
  "errors": {
    "errorCode": "PROJECT_NOT_FOUND",
    "errorMessage": "Project does not exist or you don't own it"
  },
  "data": null
}

Error codes

CodeHTTPMeaning
MISSING_PROJECT_ID400projectId is required
PROJECT_NOT_FOUND404Project does not exist or you don't own it
MISSING_RECORD_ID400recordId is required
MISSING_TABLE_NAME400tableName is required
MISSING_PROPERTY_ID400propertyId is required — the relation field on the table
MISSING_REFERENCE_ID400referenceId is required — the _id of the record to unlink
TABLE_NOT_FOUND400Table doesn't exist in the project
End of Unlink Records Back to top