SDK
Official TypeScript/JavaScript SDK for AnyDB. Provides a type-safe way to interact with the AnyDB API to manage records, workspaces, types, views, shares, workflows, and files programmatically in Node.js and browser applications.
Installation
npm install anydb-api-sdk-ts
Workspace, type, view, semantic share, and workflow operations require SDK
version 1.4.0 or later.
Getting Your API Key
Before using the SDK or APIs, you'll need to obtain your API key:
- Log in to your AnyDB account at app.anydb.com
- Click on the user icon in the bottom left corner
- Navigate to the Integration tab in the Profile Dialog
- Copy your API key
Keep your API key secure. Never commit it to version control or share it publicly.
Documentation
Explore the full API reference here: AnyDB OpenAPI Documentation
Quick Start
import { AnyDBClient } from "anydb-api-sdk-ts";
// Initialize the client
const client = new AnyDBClient({
apiKey: "your-api-key",
userEmail: "user@example.com",
baseURL: "https://app.anydb.com/api", // Optional
});
// List teams
const teams = await client.listTeams();
console.log("Teams:", teams);
// Get a record
const record = await client.getRecord("teamid", "adbid", "adoid");
console.log("Record:", record);
Features
✅ Full TypeScript support with type definitions
✅ Record operations - CRUD operations for AnyDB records
✅ File management - Upload and download files from record cells
✅ Team & database discovery - List teams and databases
✅ Workspace & type management - Create workspaces and manage semantic types
✅ Views & sharing - Manage filtered views, team groups, record shares, and forms
✅ Workflow automation - Discover triggers and actions, create workflows, and inspect execution history
✅ Search functionality - Search records by keyword
✅ Error handling - Comprehensive error messages
✅ Debug mode - Optional request/response logging
Client Configuration
Initialize the Client
const client = new AnyDBClient({
apiKey: string, // Required: Your AnyDB API key
userEmail: string, // Required: User email for authentication
baseURL: string, // Optional: API base URL (default: https://app.anydb.com/api)
timeout: number, // Optional: Request timeout in ms (default: 30000)
debug: boolean, // Optional: Log requests and responses (default: false)
runtime: "auto" | "node" | "browser", // Optional (default: "auto")
});
Debug Mode
Enable debug logging to see all API requests and responses:
DEBUG_ANYDB=1 node your-script.js
API Reference
Team & Database Operations
List Teams
Get all teams accessible with your credentials.
const teams = await client.listTeams();
// Returns: Team[]
Response:
[
{
teamid: "507f1f77bcf86cd799439011",
name: "My Team",
description: "Team description",
},
];
List Databases for Team
Get all databases within a team.
const databases = await client.listDatabasesForTeam("teamid");
// Returns: ADB[]
Response:
[
{
adbid: "507f1f77bcf86cd799439012",
name: "My Database",
teamid: "507f1f77bcf86cd799439011",
},
];
Create a Workspace
Create a new AnyDB workspace for a team. clientRequestId should be unique for
each operation and can be used safely for retries.
const workspace = await client.createWorkspace({
teamid: "teamid",
name: "Customer Operations",
clientRequestId: crypto.randomUUID(),
});
console.log(workspace.result.adbid);
The examples below reuse a workspace scope:
const scope = { teamid: "teamid", adbid: "adbid" };
Type Operations
The SDK uses type terminology while the underlying API routes may refer to types as templates.
Discover and Inspect Types
const types = await client.listTypes(scope);
const matches = await client.discoverTypes({
...scope,
search: "project",
source: "all", // "workspace", "builtin", or "all"
limit: 20,
});
const projectType = await client.getType({
...scope,
typeName: "Project",
});
const definition = await client.getTypeDefinition({
...scope,
typeName: "Project",
source: "workspace",
});
Create a Type
Import a built-in type by name:
const created = await client.createType({
...scope,
clientRequestId: crypto.randomUUID(),
mode: "import_builtin",
builtInTemplateName: "Project",
});
You can also define a custom semantic type. Use validateOnly: true first to
validate it without persisting changes.
const validation = await client.createType({
...scope,
clientRequestId: crypto.randomUUID(),
validateOnly: true,
mode: "define",
type: {
name: "Customer Project",
description: "Tracks customer delivery projects",
fields: [
{
key: "Status",
valueType: "string",
format: "select",
options: ["Planned", "Active", "Complete"],
required: true,
layout: { position: "A1", colspan: 1, rowspan: 1 },
},
],
},
});
if (validation.validation.valid) {
// Submit the same request with validateOnly omitted to persist the type.
}
Update a Type
Type updates use the current revision to prevent conflicting changes.
await client.updateType({
...scope,
typeName: "Project",
clientRequestId: crypto.randomUUID(),
expectedRevision: "1",
changes: { description: "Customer delivery projects" },
confirmDataLoss: false,
});
Set confirmDataLoss: true only when intentionally applying a destructive
change such as removing a field.
View Operations
Create a filtered view, then retrieve, update, or delete it.
const createdView = await client.createView({
...scope,
clientRequestId: crypto.randomUUID(),
view: {
name: "Active projects",
scope: "workspace",
targets: [
{
typeName: "Project",
filters: [
{
source: "cell",
field: "Status",
operator: "eq",
value: "Active",
},
],
},
],
},
});
const views = await client.listViews(scope);
const view = await client.getView({
...scope,
viewId: createdView.result.viewId!,
});
await client.updateView({
...scope,
viewId: view.viewId,
clientRequestId: crypto.randomUUID(),
changes: { name: "Current projects" },
});
await client.deleteView({
...scope,
viewId: view.viewId,
clientRequestId: crypto.randomUUID(),
});
Use validateOnly: true with createView or updateView to validate a view
definition without persisting it.
Team Groups and Semantic Shares
Semantic shares support records and forms. Private shares can target users by email address and team groups by name.
const groups = await client.listTeamGroups(scope.teamid);
const createdShare = await client.createShare({
...scope,
clientRequestId: crypto.randomUUID(),
share: {
name: "Project reviewers",
privacy: "private",
target: { kind: "record", recordId: "adoid" },
recipients: {
emails: ["reviewer@example.com"],
groupNames: groups.length > 0 ? [groups[0].name] : [],
},
role: "viewer",
withAttachments: false,
},
});
const shares = await client.listShares(scope);
const share = await client.getShare({
...scope,
shareId: createdShare.result.shareId!,
kind: "record",
});
await client.revokeShare({
...scope,
shareId: share.shareId,
kind: share.kind,
clientRequestId: crypto.randomUUID(),
});
To share a form instead of a record, use a type name as the target:
await client.createShare({
...scope,
clientRequestId: crypto.randomUUID(),
share: {
privacy: "public",
target: {
kind: "form",
typeName: "Project",
parentRecordId: "optional-parent-adoid",
},
},
});
Use validateOnly: true with createShare to validate recipients and the
target without creating the share.
Workflow Operations
Discover Available Triggers and Actions
Inspect the catalogs before building a workflow. Catalog entries include JSON input/output schemas, availability for the current team, supported triggers, and configuration guidance.
const triggers = await client.listWorkflowTriggers(scope);
const actions = await client.listWorkflowActions(scope);
const creatableActions = actions.filter(
(action) =>
action.creatableViaAnydbCreateWorkflow &&
action.availableForCurrentTeam !== false,
);
Create a Workflow
Validate the workflow graph first, then submit the same definition without
validateOnly to persist it.
const workflowDefinition = {
name: "Notify on project creation",
description: "Emails the delivery team when a project is created",
enabled: true,
trigger: {
type: "trigger_on_record_create" as const,
config: { templateName: "Project" },
},
actions: [
{
key: "notify",
type: "action_send_email",
config: {
to: ["delivery@example.com"],
subject: "A project was created",
},
},
],
};
const validation = await client.createWorkflow({
...scope,
clientRequestId: crypto.randomUUID(),
validateOnly: true,
workflow: workflowDefinition,
});
const createdWorkflow = await client.createWorkflow({
...scope,
clientRequestId: crypto.randomUUID(),
workflow: workflowDefinition,
});
The supported trigger types are trigger_on_form_submit,
trigger_on_record_create, trigger_on_record_update,
trigger_on_schedule, and trigger_manual. Use the trigger catalog for the
configuration fields supported by each type.
List, Inspect, and Update Workflows
const workflows = await client.listWorkflows(scope);
const workflow = await client.getWorkflow({
...scope,
workflowId: createdWorkflow.result.workflowId!,
});
const history = await client.getWorkflowExecutionHistory({
...scope,
workflowId: workflow.workflowId,
});
await client.updateWorkflow({
...scope,
workflowId: workflow.workflowId,
clientRequestId: crypto.randomUUID(),
changes: {
name: "Notify the delivery team",
enabled: false,
},
});
Record Operations
List Records
List all records in a database with optional filtering and pagination.
const response = await client.listRecords(
"teamid",
"adbid",
"parentid", // Optional: Filter by parent record
"templateid", // Optional: Filter by template
"templatename", // Optional: Filter by template name
"50", // Optional: Page size (default: 50)
"lastmarker", // Optional: Pagination marker from previous response
);
console.log(response.items); // Array of records
console.log(response.total); // Total count
console.log(response.hasmore); // Has more pages
console.log(response.lastmarker); // Marker for next page
Example - Paginated List:
// Get first page
const page1 = await client.listRecords("teamid", "adbid");
// Get next page using lastmarker
if (page1.hasmore) {
const page2 = await client.listRecords(
"teamid",
"adbid",
undefined,
undefined,
undefined,
"50",
page1.lastmarker,
);
}
Get Record
Get a specific record with all its data.
const record = await client.getRecord("teamid", "adbid", "adoid");
// Returns: ADORecord
Response:
{
meta: {
adoid: "507f1f77bcf86cd799439013",
adbid: "507f1f77bcf86cd799439012",
teamid: "507f1f77bcf86cd799439011",
name: "Record Name",
description: "Record description",
createdat: "2024-01-01T00:00:00.000Z",
updatedat: "2024-01-01T00:00:00.000Z"
},
content: {
A1: {
pos: "A1",
key: "firstName",
type: "string",
value: "John",
colspan: 1,
rowspan: 1
}
}
}
Create Record
Create a new record in a database.
import { ADOCellValueType } from "anydb-api-sdk-ts";
const newRecord = await client.createRecord({
teamid: "teamid",
adbid: "adbid",
name: "New Record",
description: "Optional description",
attach: "parentAdoid", // Optional: Attach to parent record
template: "templateid", // Optional: Use a template
content: {
A1: {
pos: "A1",
key: "firstName",
type: ADOCellValueType.STRING,
value: "John",
colspan: 1,
rowspan: 1,
},
B1: {
pos: "B1",
key: "age",
type: ADOCellValueType.NUMBER,
value: 30,
colspan: 1,
rowspan: 1,
},
},
});
// Returns: ADORecord
Predefined Templates:
import { PredefinedTemplateAdoIds } from "anydb-api-sdk-ts";
// Create a folder
const folder = await client.createRecord({
teamid,
adbid,
name: "My Folder",
template: PredefinedTemplateAdoIds.FOLDER_TEMPLATE_ADOID,
});
// Create a page
const page = await client.createRecord({
teamid,
adbid,
name: "My Page",
template: PredefinedTemplateAdoIds.PAGE_TEMPLATE_ADOID,
});
// Available templates:
// - FILE_TEMPLATE_ADOID
// - FOLDER_TEMPLATE_ADOID
// - PAGE_TEMPLATE_ADOID
// - LINK_TEMPLATE_ADOID
// - VIEW_TEMPLATE_ADOID
Update Record
Update an existing record.
const updatedRecord = await client.updateRecord({
meta: {
adoid: "adoid",
adbid: "adbid",
teamid: "teamid",
name: "Updated Name",
description: "New description",
},
content: {
A1: { value: "Updated value" },
B1: { value: 25 },
},
});
// Returns: ADORecord
Remove Record
Remove or delete a record.
import { NULL_OBJECTID } from "anydb-api-sdk-ts";
// Option 1: Remove from specific parent(s) (detach)
await client.removeRecord({
adoid: "adoid",
adbid: "adbid",
teamid: "teamid",
removefromids: "parentAdoid1,parentAdoid2", // Comma-separated parent IDs
});
// Option 2: Delete completely
await client.removeRecord({
adoid: "adoid",
adbid: "adbid",
teamid: "teamid",
removefromids: NULL_OBJECTID, // Special constant for complete deletion
});
// Returns: boolean (true on success)
Search Records
Search for records by keyword.
const results = await client.searchRecords({
teamid: "teamid",
adbid: "adbid",
search: "keyword",
limit: "10",
});
// Returns: ADORecord[]
Copy Record
Copy an existing record with optional attachment handling.
// Copy with no attachments
const copiedRecord = await client.copyRecord({
adoid: "sourceRecordId",
adbid: "adbid",
teamid: "teamid",
attachmentsmode: "noattachments", // Don't copy attachments
});
// Copy and link attachments
const copiedRecord = await client.copyRecord({
adoid: "sourceRecordId",
adbid: "adbid",
teamid: "teamid",
attachmentsmode: "link", // Link to original attachments
});
// Deep copy with attachments
const copiedRecord = await client.copyRecord({
adoid: "sourceRecordId",
adbid: "adbid",
teamid: "teamid",
attachmentsmode: "duplicate", // Duplicate all attachments
});
// Copy and attach to another record (e.g., a folder)
const copiedRecord = await client.copyRecord({
adoid: "sourceRecordId",
adbid: "adbid",
teamid: "teamid",
attachto: "targetParentId", // Attach copy to this record
attachmentsmode: "duplicate",
});
// Returns: ADORecord (the copied record)
Parameters:
adoid- ID of the record to copyadbid- Database IDteamid- Team IDattachto- Optional: ID of another record to attach the copy toattachmentsmode- Optional: How to handle attachments"noattachments"- Don't copy attachments"link"- Link to the original record's attachments"duplicate"- Create new copies of all attachments
Move Record
Move a record to a different parent folder.
const movedRecord = await client.moveRecord({
adoid: "recordId",
adbid: "adbid",
teamid: "teamid",
parentid: "newParentId", // New parent record ID
});
// Returns: ADORecord (the updated record)
Parameters:
adoid- ID of the record to moveadbid- Database IDteamid- Team IDparentid- ID of the new parent record
moveRecord is a convenience method that uses updateRecord internally to change the record's parent attachment.
File Operations
Download File
Download a file or get its download URL.
// Get download URL
const { url } = await client.downloadFile({
teamid: "teamid",
adbid: "adbid",
adoid: "adoid",
cellpos: "A1",
redirect: false, // Return URL instead of redirecting
preview: false, // Download instead of preview
});
console.log("Download URL:", url);
Parameters:
teamid- Team IDadbid- Database IDadoid- Record ID (file record)cellpos- Cell position where file is storedredirect- If true, returns redirect response; if false, returns URLpreview- If true, returns preview URL; if false, returns download URL
Upload File
Upload a file in one call. This automatically creates a child file record and handles the complete upload workflow.
// Upload from file content (Buffer or string)
const fileAdoid = await client.uploadFile({
filename: "document.pdf",
fileContent: fileBuffer, // Buffer or string
teamid: "teamid",
adbid: "adbid",
adoid: "parentAdoid", // Parent record to attach file to
cellpos: "A1", // Optional, defaults to "A1"
contentType: "application/pdf", // Optional MIME type
});
// Upload from file path
const fileAdoid = await client.uploadFile({
filename: "document.pdf",
filepath: "/path/to/document.pdf",
teamid: "teamid",
adbid: "adbid",
adoid: "parentAdoid",
cellpos: "A1",
});
console.log("File uploaded with ID:", fileAdoid);
// Returns: string (ADOID of the created file record)
The uploadFile method automatically:
- Creates a new file record as a child of the specified parent
- Gets an upload URL from AnyDB
- Uploads the file content to cloud storage
- Completes the upload process
Advanced Upload (Step-by-Step)
For more control, you can use the individual upload methods:
// Step 1: Get upload URL
const uploadUrl = await client.getUploadUrl({
filename: "document.pdf",
teamid: "teamid",
adbid: "adbid",
adoid: "adoid",
filesize: "1024",
cellpos: "A1",
});
// Step 2: Upload file to URL
await client.uploadFileToUrl(uploadUrl, fileBuffer, "application/pdf");
// Step 3: Complete upload
await client.completeUpload({
filesize: "1024",
teamid: "teamid",
adbid: "adbid",
adoid: "adoid",
cellpos: "A1",
});
Understanding AnyDB Concepts
IDs
- teamid - MongoDB ObjectId identifying a team/organization
- adbid - MongoDB ObjectId identifying an AnyDB workspace/database
- adoid - MongoDB ObjectId for an ADO (AnyDB Object/Record). Similar to a row in a spreadsheet
Cell Positions
Cell positions follow a grid system similar to spreadsheets:
- Valid format:
A1toA9,B1toB9,C1toC9, etc. - Invalid:
A0,B0, etc. (0 is not a valid row number)
Record Hierarchy
Records can have parent-child relationships:
- Use
attachparameter when creating records to attach to a parent - Use
parentidparameter when listing records to filter by parent - Files are typically child records attached to their parent records
Complete Example
Here's a complete workflow example:
import { AnyDBClient, ADOCellValueType } from "anydb-api-sdk-ts";
async function main() {
// Initialize client
const client = new AnyDBClient({
apiKey: process.env.ANYDB_API_KEY!,
userEmail: process.env.ANYDB_USER_EMAIL!,
baseURL: "https://app.anydb.com/api",
});
// 1. List teams
const teams = await client.listTeams();
const teamid = teams[0].teamid;
console.log(`Using team: ${teams[0].name}`);
// 2. List databases
const databases = await client.listDatabasesForTeam(teamid);
const adbid = databases[0].adbid;
console.log(`Using database: ${databases[0].name}`);
// 3. Create a new record
const record = await client.createRecord({
teamid,
adbid,
name: "Customer Record",
content: {
A1: {
pos: "A1",
key: "companyName",
type: ADOCellValueType.STRING,
value: "Acme Corp",
colspan: 1,
rowspan: 1,
},
B1: {
pos: "B1",
key: "employees",
type: ADOCellValueType.NUMBER,
value: 50,
colspan: 1,
rowspan: 1,
},
},
});
console.log("Created record:", record.meta.adoid);
// 4. Upload a file to the record
const fileBuffer = Buffer.from("Contract details...");
const fileAdoid = await client.uploadFile({
filename: "contract.txt",
fileContent: fileBuffer,
teamid,
adbid,
adoid: record.meta.adoid,
contentType: "text/plain",
});
console.log("File uploaded:", fileAdoid);
// 5. Search for records
const searchResults = await client.searchRecords({
teamid,
adbid,
search: "Acme",
limit: "10",
});
console.log(`Found ${searchResults.length} records`);
// 6. Update the record
const updated = await client.updateRecord({
meta: {
adoid: record.meta.adoid,
adbid,
teamid,
name: "Customer Record - Updated",
},
content: {
B1: { value: 75 }, // Update employee count
},
});
console.log("Record updated");
// 7. List child records (including the uploaded file)
const children = await client.listRecords(
teamid,
adbid,
record.meta.adoid, // Filter by parent
);
console.log(`Record has ${children.items.length} children`);
}
main().catch(console.error);
Error Handling
The SDK throws descriptive errors with HTTP status codes:
try {
const record = await client.getRecord("teamid", "adbid", "invalid-adoid");
} catch (error) {
console.error(error.message);
// Example: "AnyDB API Error (404): Record not found"
}
Common Error Scenarios:
- 401 Unauthorized - Invalid API key or email
- 404 Not Found - Record, database, or team doesn't exist
- 400 Bad Request - Invalid parameters or malformed request
- 500 Server Error - Server-side error
TypeScript Support
The SDK is written in TypeScript and exports all types:
import type {
ADORecord,
Team,
ADB,
CreateRecordParams,
UpdateRecordParams,
RemoveRecordParams,
ListRecordsResponse,
SearchRecordsParams,
DownloadFileParams,
GetUploadUrlParams,
CompleteUploadParams,
CreateWorkspaceParams,
WorkspaceResourceParams,
TypeSummary,
SemanticTypeDefinition,
CreateTypeParams,
ViewDefinition,
CreateViewParams,
TeamGroup,
ShareDefinition,
CreateShareParams,
WorkflowSummary,
WorkflowDetails,
WorkflowArtifactCatalogEntry,
CreateWorkflowParams,
} from "anydb-api-sdk-ts";
Best Practices
Secure API Keys
Store API keys in environment variables:
// .env file
ANYDB_API_KEY=your-api-key-here
ANYDB_USER_EMAIL=user@example.com
// In your code
import { AnyDBClient } from "anydb-api-sdk-ts";
import * as dotenv from "dotenv";
dotenv.config();
const client = new AnyDBClient({
apiKey: process.env.ANYDB_API_KEY!,
userEmail: process.env.ANYDB_USER_EMAIL!
});
Handle Pagination
Always handle pagination when listing records:
async function getAllRecords(client, teamid, adbid) {
const allRecords = [];
let lastmarker = undefined;
let hasmore = true;
while (hasmore) {
const response = await client.listRecords(
teamid,
adbid,
undefined,
undefined,
undefined,
"100", // Fetch 100 at a time
lastmarker,
);
allRecords.push(...response.items);
lastmarker = response.lastmarker;
hasmore = response.hasmore;
}
return allRecords;
}
Use Templates
Leverage predefined templates for consistent record structure:
import { PredefinedTemplateAdoIds } from "anydb-api-sdk-ts";
// Create folder structure
const folder = await client.createRecord({
teamid,
adbid,
name: "Documents",
template: PredefinedTemplateAdoIds.FOLDER_TEMPLATE_ADOID,
});
// Create page inside folder
const page = await client.createRecord({
teamid,
adbid,
name: "Meeting Notes",
attach: folder.meta.adoid,
template: PredefinedTemplateAdoIds.PAGE_TEMPLATE_ADOID,
});
Create Reference and Lookup Cells
A reference cell links one record to another using the target record's ADOID. A lookup cell can then follow that reference and read a labeled value from the linked record.
import { ADOCellFormat, ADOCellValueType } from "anydb-api-sdk-ts";
const personRecordAdoid = "person-record-adoid";
// E5 links this new sheet to the Person record.
const referenceSheet = await client.createRecord({
teamid: "teamid",
adbid: "adbid",
name: "Person Reference Sheet",
content: {
E5: {
pos: "E5",
type: ADOCellValueType.REF,
format: ADOCellFormat.REF,
colspan: 1,
rowspan: 1,
props: {
ATTACHMENTS_TEMPLATE_NAME: {
type: "string",
value: "Sheet",
expr: "",
proptype: "CELL",
},
},
key: "My Reference",
value: "",
expr: `O@${personRecordAdoid}!F@GO!M@MINI`,
msg: "",
display: "",
comments: { comments: [] },
},
},
});
// F5 follows the reference in E5 and reads the linked Person's "Age" value.
await client.updateRecord({
meta: {
adoid: referenceSheet.meta.adoid,
adbid: "adbid",
teamid: "teamid",
},
content: {
F5: {
pos: "F5",
type: ADOCellValueType.STRING,
format: ADOCellFormat.LOOKUP,
colspan: 1,
rowspan: 1,
props: {},
key: "F5",
value: "",
expr: 'DYNREF(E5,{{Age}},"GO")',
msg: "",
display: "",
comments: { comments: [] },
},
},
});
DYNREF takes the reference cell coordinate as its first argument and the
linked object's cell label in double braces as its second argument. In this
example, it follows E5 and reads the cell labeled Age.
Resources
- npm Package: anydb-api-sdk-ts
- GitHub Repository: HumanlyInc/anydb-api-sdk-ts
- API Documentation: AnyDB API Docs
- Support: support@anydb.com
License
MIT License - See LICENSE for details.