The real problem
Why this is harder than it looks
Dynamo's REST API is well-structured and the documentation is thorough. A single-tenant prototype — your own firm, your own API key — is straightforward. The complexity surfaces when you try to build a product that serves multiple investment firms, each operating their own Dynamo instance.
Dynamo authenticates via bearer token — a credential that is account-scoped to a specific Dynamo environment. In a multi-tenant product, each of your customers supplies their own token and their own Dynamo base URL. You're now managing a credential vault: one (base URL + bearer token) pair per customer, with no OAuth redirect flow to standardize collection. You have to build onboarding UI to collect these, store them encrypted, and route every API call to the correct customer's instance. Get the routing wrong and you'll silently call the wrong firm's data with no helpful error.
The operational problems compound quickly. Bearer tokens in Dynamo don't expire on a fixed schedule — but they can be reset or revoked by a Dynamo administrator at any time. The dynamo_reset_api_key tool itself clears the server-side cache, meaning a key that was valid seconds ago may need re-validation on the next request. Without per-tenant revocation detection and re-credentialing flows, your agent silently returns 401s with no actionable signal. And Dynamo's entity model is highly configurable — entity schemas, property names, and available views vary by firm, so calls that work against one customer's instance may fail against another's.
Scalekit handles credential storage, per-tenant routing, and account status tracking. Your agent names a tool and passes parameters. The plumbing is not your problem.
Capabilities
What your agent can do with Dynamo Software
Once connected, your agent has 35 pre-built tools covering Dynamo's full entity model, document management, views, and workflow engine:
- Query and manage entities: retrieve, create, update, upsert, and delete records for any Dynamo entity — contacts, activities, deals, and custom objects
- Bulk operations: upsert or delete multiple entity records atomically using bulk import with configurable key matching and conflict resolution
- Search and views: run advanced filter queries using Dynamo's saved search format; query predefined views and SQL export views with sorting, pagination, and column selection
- Document management: create, update, retrieve, and list documents — both file uploads and hyperlinks — with version control and schema introspection
- Schema introspection: inspect entity schemas, properties, and permissions at runtime to adapt to each firm's custom Dynamo configuration
- Workflow automation: trigger workflow action buttons, fire custom operations, and execute scheduled workflows programmatically
Setup context
What we're building
This guide connects a deal intelligence agent to Dynamo Software — helping investment professionals query pipelines, manage contacts, and trigger workflows without leaving your product.
🤖
Example agent
Deal intelligence assistant querying pipeline entities, managing contacts, and triggering workflows on behalf of each investment professional
🔐
Auth model
Bearer Token — each firm supplies their Dynamo base URL and API token. identifier = your user ID
🏢
Dynamo instance
Each customer provides their Dynamo base URL and a bearer token generated from their Dynamo environment
Setup
1 Setup: One SDK, One credential
Install the Scalekit SDK. The only credential your application manages is the Scalekit API key — no Dynamo secrets, no customer tokens, nothing belonging to your users.
pip install scalekit-sdk-python
npm install @scalekit-sdk/node
import scalekit.client
import os
from dotenv import load_dotenv
load_dotenv()
scalekit = scalekit.client.ScalekitClient(
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"),
env_url=os.getenv("SCALEKIT_ENV_URL"),
)
actions = scalekit.actions
import { ScalekitClient } from '@scalekit-sdk/node';
import 'dotenv/config';
const scalekit = new ScalekitClient(
process.env.SCALEKIT_ENV_URL,
process.env.SCALEKIT_CLIENT_ID,
process.env.SCALEKIT_CLIENT_SECRET
);
const actions = scalekit.actions;
Already have credentials?
Connected Accounts
2 Per-User Auth: Creating connected accounts
Dynamo uses Bearer Token authentication — there is no OAuth redirect flow. Each user's connected account is provisioned with their Dynamo base URL and bearer token. The identifier is any unique string from your system.
In the Scalekit dashboard, go to Agent Auth → Connected Accounts for your Dynamo connection and click Add account. Supply the user's identifier, their Dynamo instance URL, and their bearer token. Scalekit stores the credentials encrypted and routes every API call to the correct Dynamo instance automatically.
response = actions.get_or_create_connected_account(
connection_name="dynamo",
identifier="user_dynamo_123" # your internal user ID
)
connected_account = response.connected_account
print(f"Status: {connected_account.status}")
# Status: ACTIVE — credentials were supplied at account creation
const response = await actions.getOrCreateConnectedAccount({
connectionName: "dynamo",
identifier: "user_dynamo_123" // your internal user ID
});
const connectedAccount = response.connectedAccount;
console.log(`Status: ${connectedAccount.status}`);
// Status: ACTIVE — credentials were supplied at account creation
This call is idempotent — safe to call on every session start. Returns the existing account if one already exists.
Credential handling
3 Credential management
Because Dynamo uses bearer tokens rather than OAuth, there is no user-facing authorization step. Credentials are entered once in the Scalekit dashboard (or via API) per connected account and used on every subsequent tool call.
Credential storage is automatic
Once a connected account is provisioned with a Dynamo bearer token, Scalekit stores it in its encrypted
vault and the account is immediately ACTIVE. Every tool call is routed to the correct Dynamo instance
using the stored token — your agent code never touches it. If a token is revoked or reset by a Dynamo
administrator, the account moves to REVOKED. Check account.status before critical operations and surface
a re-credentialing prompt rather than returning a generic error.
Generating a Dynamo bearer token
Bearer tokens are generated from within the Dynamo platform by a Dynamo administrator. The token authenticates
as a specific Dynamo user account and inherits that account's role-based permissions. Use a service account
with the appropriate read/write permissions for the entity types your agent will access. Note that calling
dynamo_reset_api_key clears the server-side token cache — the token itself remains valid but will be
re-validated on the next request.
Calling Dynamo
4 Calling Dynamo: What your agent writes
With the connected account active, your agent calls Dynamo actions using execute_tool. Name the tool, pass parameters. Scalekit handles credential retrieval, instance routing, and response parsing.
Retrieve entity records
Fetch all records for any Dynamo entity type with optional column selection, sorting, and pagination. Use x_columns to reduce bandwidth by returning only the fields your agent needs.
result = actions.execute_tool(
identifier="user_dynamo_123",
tool_name="dynamo_get_entity_items",
tool_input={
"entityName": "contact",
"x_columns": "_id,Name,Email,FirmName,Stage",
"x_sort": "Name asc",
"x_resolved": True
}
)
# Returns: list of contact records with specified fields
const result = await actions.executeTool({
identifier: "user_dynamo_123",
toolName: "dynamo_get_entity_items",
toolInput: {
"entityName": "contact",
"x_columns": "_id,Name,Email,FirmName,Stage",
"x_sort": "Name asc",
"x_resolved": true
}
});
// Returns: list of contact records with specified fields
Run an advanced search
Execute Dynamo's saved search format to filter entities by complex criteria. The query field accepts the JSON query string from Dynamo's advanced search panel — use the built-in "API Query" button in the Dynamo UI to copy the exact format.
result = actions.execute_tool(
identifier="user_dynamo_123",
tool_name="dynamo_search",
tool_input={
"query": "{\"rules\":[{\"field\":\"Stage\",\"op\":\"eq\",\"data\":\"Due Diligence\"}],\"condition\":\"AND\"}",
"x_columns": "_id,DealName,Stage,TargetCloseDate,AUM",
"x_sort": "TargetCloseDate asc",
"all": False
}
)
# Returns: deals in Due Diligence stage, sorted by close date
const result = await actions.executeTool({
identifier: "user_dynamo_123",
toolName: "dynamo_search",
toolInput: {
"query": "{\"rules\":[{\"field\":\"Stage\",\"op\":\"eq\",\"data\":\"Due Diligence\"}],\"condition\":\"AND\"}",
"x_columns": "_id,DealName,Stage,TargetCloseDate,AUM",
"x_sort": "TargetCloseDate asc",
"all": false
}
});
// Returns: deals in Due Diligence stage, sorted by close date
Create or update an entity record
Upsert a record using key columns for matching — if a record with the matching key values exists, it updates; otherwise it creates. Pass only the fields that should change; unspecified fields are left untouched.
result = actions.execute_tool(
identifier="user_dynamo_123",
tool_name="dynamo_entity_upsert",
tool_input={
"entityName": "activity",
"body": {
"Subject": "Follow-up call — Q2 review",
"ContactId": "abc123",
"ActivityDate": "2026-05-15",
"Notes": "Discussed fund performance and re-up timeline"
},
"x_keycolumns": "Subject,ContactId",
"x_importaction": "updateorcreate"
}
)
# Returns: { "_id": "...", "Subject": "Follow-up call — Q2 review", ... }
const result = await actions.executeTool({
identifier: "user_dynamo_123",
toolName: "dynamo_entity_upsert",
toolInput: {
"entityName": "activity",
"body": {
"Subject": "Follow-up call — Q2 review",
"ContactId": "abc123",
"ActivityDate": "2026-05-15",
"Notes": "Discussed fund performance and re-up timeline"
},
"x_keycolumns": "Subject,ContactId",
"x_importaction": "updateorcreate"
}
});
// Returns: { "_id": "...", "Subject": "Follow-up call — Q2 review", ... }
Query a saved view
Retrieve data from a predefined Dynamo view — the same views available in the Dynamo UI. Optionally layer additional filter rules on top of the view's built-in criteria.
result = actions.execute_tool(
identifier="user_dynamo_123",
tool_name="dynamo_view_post",
tool_input={
"path": "deal/active-pipeline",
"query": "{\"rules\":[{\"field\":\"AssignedTo\",\"op\":\"eq\",\"data\":\"me\"}],\"condition\":\"AND\"}",
"x_columns": "_id,DealName,Stage,Amount,AssignedTo",
"x_sort": "Amount desc"
}
)
# Returns: active pipeline items assigned to current user, sorted by deal size
const result = await actions.executeTool({
identifier: "user_dynamo_123",
toolName: "dynamo_view_post",
toolInput: {
"path": "deal/active-pipeline",
"query": "{\"rules\":[{\"field\":\"AssignedTo\",\"op\":\"eq\",\"data\":\"me\"}],\"condition\":\"AND\"}",
"x_columns": "_id,DealName,Stage,Amount,AssignedTo",
"x_sort": "Amount desc"
}
});
// Returns: active pipeline items assigned to current user, sorted by deal size
Framework wiring
5 Wiring into your agent framework
Scalekit integrates directly with LangChain. The agent decides what to look up or act on; Scalekit handles credential routing on every invocation. No token plumbing in your agent logic.
from langchain_anthropic import ChatAnthropic
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from scalekit.langchain import get_tools
dynamo_tools = get_tools(
connection_name="dynamo",
identifier="user_dynamo_123"
)
prompt = ChatPromptTemplate.from_messages([
("system", "You are a deal intelligence assistant. Use the available tools to help investment professionals query Dynamo pipelines, manage contacts, and track activity."),
MessagesPlaceholder("chat_history", optional=True),
("human", "{input}"),
MessagesPlaceholder("agent_scratchpad"),
])
agent = create_tool_calling_agent(ChatAnthropic(model="claude-sonnet-4-6"), dynamo_tools, prompt)
result = AgentExecutor(agent=agent, tools=dynamo_tools).invoke({
"input": "Find all deals in Due Diligence stage assigned to me and summarize the key details and next steps"
})
import { ChatAnthropic } from "@langchain/anthropic";
import { AgentExecutor, createToolCallingAgent } from "langchain/agents";
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
import { getTools } from "@scalekit-sdk/langchain";
const dynamoTools = getTools({
connectionName: "dynamo",
identifier: "user_dynamo_123"
});
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a deal intelligence assistant. Use the available tools to help investment professionals query Dynamo pipelines, manage contacts, and track activity."],
new MessagesPlaceholder("chat_history", true),
["human", "{input}"],
new MessagesPlaceholder("agent_scratchpad"),
]);
const agent = await createToolCallingAgent({
llm: new ChatAnthropic({ model: "claude-sonnet-4-6" }),
tools: dynamoTools,
prompt
});
const result = await AgentExecutor.fromAgentAndTools({
agent,
tools: dynamoTools
}).invoke({
input: "Find all deals in Due Diligence stage assigned to me and summarize the key details and next steps"
});
Other frameworks supported
Tool reference
All 35 Dynamo tools
Grouped by capability. Your agent calls tools by name — no API wrappers to write.
List all records for a Dynamo entity with filtering, pagination, sorting, and column selection
Retrieve a single entity record by UUID with optional column selection and display label formatting
Create or update an entity record using key column matching. Supports updateorcreate, create-only, and update-only modes
Create or update an entity using PUT semantics with key columns or ID-based upsert
dynamo_entity_update_by_id
Update or create a specific entity record by UUID and return the updated item
Permanently delete a single entity record by UUID
Return the total record count for a given entity type
List all available Dynamo entity types with optional property filtering
Create or update multiple entity records in one call using bulk import with configurable key matching and null-value handling
Delete multiple entity records atomically using bulk import
Execute an advanced filter query in Dynamo's saved search format. Copy the query string directly from Dynamo's "API Query" button
List all available views or retrieve items from a specific view by path with sorting and column selection
Retrieve items from a view with additional filter rules layered on top of the view's built-in criteria
List all available SQL export views in Dynamo
dynamo_view_sql_get_by_name
Retrieve data from a specific SQL view by name. The EXPORTSQL_ prefix is appended automatically
dynamo_view_sql_sp_execute
Execute a named SQL stored procedure in Dynamo with optional parameters. The EXPORTSQLSP_ prefix is appended automatically
Retrieve documents with filtering, sorting, and column selection. Supports both file uploads and hyperlinks
dynamo_get_document_by_id
Retrieve a single document by UUID with optional column filtering and display formatting
Create a new document as a file upload (base64-encoded content) or as a hyperlink. Supports key-column upsert
Create a new version of a document by ID. Updating the title applies to all versions, not just the current one
Create or update a document using key columns via PUT semantics
dynamo_get_documents_total
Return the total count of document entities in Dynamo
dynamo_get_document_upload_restrictions
Retrieve upload restrictions for documents including size limits, allowed file types, and validation rules
Return the schema definition of a specified entity, optionally including user permission metadata
dynamo_entity_extended_schema
Return extended schema metadata for an entity including detailed field configuration and optional permissions
Return all available properties (field list) for a specified entity type
Return a brief schema for all available entities with optional filtering, permission details, and extended metadata
dynamo_get_document_schema
Return the schema definition of the Document entity, optionally including permission metadata
dynamo_get_document_extended_schema
Return extended schema metadata for the Document entity including detailed field configuration and optional permissions
dynamo_get_document_properties
Return all available properties for the Document entity
dynamo_workflow_action_button
Trigger a workflow action button on a specific entity record by entity name, record UUID, and button property name
dynamo_workflow_custom_operation
Trigger a named custom workflow operation with optional parameters
Trigger all workflows associated with a specific schedule ID immediately, as if the scheduled time had been reached
Return the decrypted value of an encrypted property for a given entity record
Remove the user's API key from the server-side cache. The key remains valid but will be re-validated on the next request
Connector notes
Dynamo-specific behavior
Entity schemas vary by Dynamo instance
Dynamo is highly configurable — entity types, property names, and available views differ between firms.
A query that works against one customer's instance may fail against another's if field names differ.
Use dynamo_entity_properties or dynamo_entity_schema to inspect the exact property names available
before constructing queries or upserts for a given connected account.
Tags and bulk updates replace, not append
When using dynamo_bulk_upsert with skipColumnIfSourceHasNoValue set to false (the default), blank or null
values in the input will overwrite existing data. Set skipColumnIfSourceHasNoValue to true to preserve
existing values when the input item omits a field.
Use the API Query button for search queries
dynamo_search accepts Dynamo's native advanced filter format (advf). Rather than constructing this JSON
manually, use Dynamo's built-in "API Query" button in the advanced search panel to generate the exact
query string. Pass it directly as the query parameter.
Infrastructure decision
Why not build this yourself
The Dynamo API is documented. Credential storage isn't technically hard. But here's what you're actually signing up for:
PROBLEM 01
Per-customer instance routing — every API call must go to the right firm's Dynamo environment, with the right bearer token, with no shared base URL
PROBLEM 02
Bearer tokens can be reset or revoked by Dynamo administrators at any time — you need revocation detection and re-credentialing flows without the OAuth lifecycle hooks other connectors provide
PROBLEM 03
Per-user credential isolation in a multi-tenant system — one firm's Dynamo token must never route calls to another firm's instance
PROBLEM 04
Encrypted credential storage, account status tracking, and onboarding UI to collect per-firm base URLs and tokens — all before writing a single entity query
That's one connector. Your agent product will eventually need Salesforce, Gmail, Slack, HubSpot, and whatever else your customers ask for. Each has its own auth quirks and failure modes.
Scalekit maintains every connector. You maintain none of them.