Skip to main content

Overview

The Velt Node SDK exposes two independent backends:
BackendNamespaceUse case
Self-hostingsdk.selfHosting.*Store Velt data in your own MongoDB + AWS S3
REST APIsdk.api.*Call Velt’s REST APIs directly from your backend
Self-hosting backend (sdk.selfHosting.*) simplifies backend implementation by 90%. Instead of writing custom database queries and storage logic, you:
  1. Pass your DB and storage configs to the SDK
  2. Call the relevant SDK method with the raw request payload
  3. Return the resulting response directly to the client
REST API backend (sdk.api.*) provides parity with Velt’s REST APIs. 18 services, fully-typed TypeScript request objects, and raw Velt API responses. API calls do not read or write MongoDB or AWS S3.
In @veltdev/node@1.0.7, VeltSDK.initialize still validates the top-level database config. Pass your database config during initialization even when you only call sdk.api.*; the REST API methods themselves do not use it.

Installation

npm install @veltdev/node
For self-hosting, also install the optional peer dependencies you plan to use:
npm install mongodb              # required for self-hosting backend
npm install @aws-sdk/client-s3   # required only if using S3 for attachments
npm install jose@^5              # required only for the built-in JWT/JWKS verifyToken path

Requirements

  • Node.js 18+
  • TypeScript 5.x (optional — JavaScript is fully supported)
  • MongoDB 6+ (Percona Server or MongoDB Atlas) for self-hosting
  • mongodb ^6, @aws-sdk/client-s3 ^3, and jose ^5 (for verifyToken) as optional peer dependencies

Quick Start

Initialize the SDK

Self-hosting initialization (MongoDB + optional AWS):
import { VeltSDK } from '@veltdev/node';

const sdk = VeltSDK.initialize({
  database: {
    host: 'localhost:27017',
    username: 'your-username',
    password: 'your-password',
    auth_database: 'admin',
    database_name: 'velt-integration',
  },
  apiKey: 'YOUR_VELT_API_KEY',
  authToken: 'YOUR_VELT_AUTH_TOKEN'
});

Shutdown

Call await sdk.close() when your process exits to release the database connection pool.

Configuration

Environment Variables

VariableConfig key equivalentPurpose
VELT_API_KEYapiKeyVelt API key for authenticating REST API calls
VELT_AUTH_TOKENauthTokenVelt auth token for authenticating REST API calls
VELT_WORKSPACE_IDDefault workspace ID for workspace-scoped operations
VELT_WORKSPACE_AUTH_TOKENAuth token scoped to a specific workspace
AWS_ACCESS_KEY_IDAWS access key for S3 attachments
AWS_SECRET_ACCESS_KEYAWS secret key for S3 attachments
AWS_REGIONAWS region for S3
AWS_S3_BUCKET_NAMES3 bucket name for attachments
AWS_S3_ENDPOINT_URLCustom S3 endpoint (e.g., for MinIO)

Self-Hosting Configuration

Configure MongoDB connection for storing comments, reactions, and user data.
const sdk = VeltSDK.initialize({
  database: {
    host: 'localhost:27017',
    username: 'your-username',
    password: 'your-password',
    auth_database: 'admin',
    database_name: 'velt-db',

    // Optional fields:
    // type: 'mongodb',       // defaults to 'mongodb'
    // use_srv: true,         // use mongodb+srv:// (auto-detected for *.mongodb.net hosts)
  }
});

Self-Hosting Backend

Each self-hosting service is loaded asynchronously on first access via await sdk.selfHosting.getXxx(). The service is cached after the first call. The token service is the exception — it is available as a direct synchronous property via sdk.selfHosting.token.

Comments

Access via await sdk.selfHosting.getComments().

getComments

const commentsService = await sdk.selfHosting.getComments();
const result = await commentsService.getComments({
  organizationId: 'org-123',
  documentIds: ['doc-1'],
});
Response
{
  success: true,
  statusCode: 200,
  data: { /* comment annotations keyed by annotationId */ }
}

saveComments

const commentsService = await sdk.selfHosting.getComments();
const result = await commentsService.saveComments({
  metadata: { organizationId: 'org-123', documentId: 'doc-1' },
  commentAnnotation: {
    'annotation-1': {
      annotationId: 'annotation-1',
      comments: { '123456': { commentId: '123456', commentText: 'Hello' } },
      metadata: {},
    },
  },
});
Response
{ success: true, statusCode: 200, data: { saved: true } }
Resolver contract: When saveComments runs as a save-resolver handler, the inbound event may be a ResolverActions or a CommentResolverSaveEvent member (or a raw string), and the request may include targetComment?: PartialComment — the comment the action occurred on (request context only, never persisted). See SaveCommentResolverRequest.

deleteComment

const commentsService = await sdk.selfHosting.getComments();
const result = await commentsService.deleteComment({
  commentAnnotationId: 'annotation-1',
  metadata: { organizationId: 'org-123' },
});
Response
{ success: true, statusCode: 200, data: { deleted: true } }

Reactions

Access via await sdk.selfHosting.getReactions().

getReactions

const reactionsService = await sdk.selfHosting.getReactions();
const result = await reactionsService.getReactions({
  organizationId: 'org-123',
  documentIds: ['doc-1'],
});
Response
{ success: true, statusCode: 200, data: { /* reaction annotations */ } }

saveReactions

const reactionsService = await sdk.selfHosting.getReactions();
const result = await reactionsService.saveReactions({
  metadata: { organizationId: 'org-123', documentId: 'doc-1' },
  reactionAnnotation: {
    'reaction-1': { annotationId: 'reaction-1', icon: 'thumbsup', from: { userId: 'u-1' }, metadata: {} },
  },
});
Response
{ success: true, statusCode: 200, data: { saved: true } }

deleteReaction

const reactionsService = await sdk.selfHosting.getReactions();
const result = await reactionsService.deleteReaction({
  reactionAnnotationId: 'reaction-1',
  metadata: { organizationId: 'org-123' },
});
Response
{ success: true, statusCode: 200, data: { deleted: true } }

Attachments

Access via await sdk.selfHosting.getAttachments().

getAttachment

  • Fetches an attachment record by organization and attachment ID.
  • Params: positional — organizationId (string), attachmentId (number)
  • Returns: VeltSelfHostingResponse
const attachmentsService = await sdk.selfHosting.getAttachments();
const result = await attachmentsService.getAttachment('org-123', 12345);
Response
{
  success: true,
  statusCode: 200,
  data: {
    attachmentId: 12345,
    name: 'document.pdf',
    mimeType: 'application/pdf',
    size: 1024,
    url: 'https://s3.amazonaws.com/...'
  }
}

saveAttachment

const attachmentsService = await sdk.selfHosting.getAttachments();
const result = await attachmentsService.saveAttachment(
  {
    metadata: { organizationId: 'org-123', documentId: 'doc-1' },
    attachment: {
      attachmentId: 12345,
      name: 'document.pdf',
      mimeType: 'application/pdf',
      size: 1024,
    },
  },
  fileBuffer,        // optional Buffer for S3 upload
  'document.pdf',    // optional
  'application/pdf'  // optional
);
Response
{ success: true, statusCode: 200, data: { saved: true, attachmentId: 12345 } }

deleteAttachment

const attachmentsService = await sdk.selfHosting.getAttachments();
const result = await attachmentsService.deleteAttachment({
  attachmentId: 12345,
  metadata: { organizationId: 'org-123' },
});
Response
{ success: true, statusCode: 200, data: { deleted: true } }

Users

Access via await sdk.selfHosting.getUsers().

getUsers

const usersService = await sdk.selfHosting.getUsers();
const result = await usersService.getUsers({
  organizationId: 'org-123',
  userIds: ['user-1'],
});
Response
{
  success: true,
  statusCode: 200,
  data: [{ userId: 'user-1', name: 'John Doe', email: 'john@example.com' }]
}

Recorder

Access via await sdk.selfHosting.getRecorder().

getRecorderAnnotations

const recorderService = await sdk.selfHosting.getRecorder();
const result = await recorderService.getRecorderAnnotations({
  organizationId: 'org-123',
  documentIds: ['doc-1'],
});
Response
{ success: true, statusCode: 200, data: { /* recorder annotations */ } }

saveRecorderAnnotation

const recorderService = await sdk.selfHosting.getRecorder();
const result = await recorderService.saveRecorderAnnotation({
  metadata: { organizationId: 'org-123', documentId: 'doc-1' },
  recorderAnnotation: { annotationId: 'rec-1', metadata: {} },
});
Response
{ success: true, statusCode: 200, data: { saved: true } }

deleteRecorderAnnotation

const recorderService = await sdk.selfHosting.getRecorder();
const result = await recorderService.deleteRecorderAnnotation({
  recorderAnnotationId: 'rec-1',
  metadata: { organizationId: 'org-123' },
});
Response
{ success: true, statusCode: 200, data: { deleted: true } }

Notifications

Access via await sdk.selfHosting.getNotifications().

getNotifications

const notificationsService = await sdk.selfHosting.getNotifications();
const result = await notificationsService.getNotifications({
  organizationId: 'org-123',
  userId: 'user-1',
});
Response
{ success: true, statusCode: 200, data: [/* notifications */] }

saveNotifications

const notificationsService = await sdk.selfHosting.getNotifications();
await notificationsService.saveNotifications({
  metadata: { organizationId: 'org-123', documentId: 'doc-1' },
  notifications: [{ id: 'n-1', actionUser: { userId: 'user-1' }, displayBodyMessage: 'Hello' }],
});
Response
{ success: true, statusCode: 200, data: { saved: true } }

deleteNotification

const notificationsService = await sdk.selfHosting.getNotifications();
await notificationsService.deleteNotification({
  notificationId: 'n-1',
  metadata: { organizationId: 'org-123', userId: 'user-1' },
});
Response
{ success: true, statusCode: 200, data: { deleted: true } }

Activities

Access via await sdk.selfHosting.getActivities().

getActivities

const activitiesService = await sdk.selfHosting.getActivities();
const result = await activitiesService.getActivities({
  organizationId: 'org-123',
  documentId: 'doc-1',
});
Response
{ success: true, statusCode: 200, data: [/* activities */] }

saveActivities

const activitiesService = await sdk.selfHosting.getActivities();
await activitiesService.saveActivities({
  metadata: { organizationId: 'org-123', documentId: 'doc-1' },
  activities: [{ id: 'activity-1', featureType: 'comment', actionType: 'comment.add' }],
});
Response
{ success: true, statusCode: 200, data: { saved: true } }

Token

Access via the synchronous sdk.selfHosting.token property (no await).

getToken

  • Generates a Velt auth token for a user.
  • Params: positional — organizationId (string), userId (string), email (string, optional), isAdmin (boolean, optional)
  • Returns: VeltSelfHostingResponse
Note: getToken takes positional arguments — not a request object.
const tokenResult = await sdk.selfHosting.token.getToken(
  'org-123',
  'user-1',
  'john@example.com',
  false
);
const token = (tokenResult.data as { token: string }).token;
Response
{ success: true, statusCode: 200, data: { token: 'eyJhbGciOiJIUzI1NiIs...' } }

verifyToken

sdk.selfHosting.verifyToken verifies the auth credential the Velt frontend forwards to endpoint-based resolvers. It is database-free (it does not open or query MongoDB), fail-closed (every error path returns { verified: false }, never throws), and authentication only (claims are returned verbatim; it makes no authorization decision).
In @veltdev/node@1.0.7, VeltSDK.initialize still validates the top-level database config before you can access sdk.selfHosting. Include your self-hosting database config when initializing the SDK. The verifyToken method itself remains database-free after initialization.
Optional dependency: npm install jose@^5 (optional peerDependency, pinned ^5 for Node 18 — jose v6 needs the WebCrypto global absent on Node 18). If jose is missing and the built-in JWT path is used, verifyToken returns { verified: false, errorCode: 'DEPENDENCY_MISSING' }. The custom verify callback path needs no dependency.

Configuration

Configure resolverAuth on VeltSDK.initialize. Provide the built-in jwt path, a custom verify callback, or both. When both are set, the custom callback takes priority.
const sdk = VeltSDK.initialize({
  database: {
    host: 'localhost:27017',
    username: 'your-username',
    password: 'your-password',
    auth_database: 'admin',
    database_name: 'velt-db',
  },
  apiKey: 'YOUR_VELT_API_KEY',
  authToken: 'YOUR_VELT_AUTH_TOKEN',
  resolverAuth: {
    jwt: {
      algorithms: ['RS256'],        // REQUIRED — algorithm allowlist (pinning)
      jwksUrl: 'https://your-idp.example.com/.well-known/jwks.json',
      // publicKey: '-----BEGIN PUBLIC KEY-----\n...',  // OR a static PEM
      // secret: 'hs256-shared-secret',                 // OR an HMAC secret for HS*
      issuer: 'https://your-idp.example.com/',
      audience: 'your-api-audience',
      leeway: 5,                    // clock tolerance in seconds
      require: ['exp'],             // required claims
    },
    header: 'Authorization',        // default
    scheme: 'Bearer',              // default
  },
});

Usage

Call verifyToken from your endpoint-based resolver route, then branch on result.verified.
const result = await sdk.selfHosting.verifyToken({ headers: request.headers });

if (!result.verified) {
  // result.error is a generic, code-based message; result.errorCode is machine-readable
  return response.status(401).json({ error: result.error, code: result.errorCode });
}

const userId = result.claims?.sub;
Per-call options override the configured resolverAuth (the jwt block is deep-merged):
// Pass a raw token directly instead of headers
await sdk.selfHosting.verifyToken({ token: rawToken });

// Override a single jwt option for this call (deep-merged with configured jwt)
await sdk.selfHosting.verifyToken({ headers: request.headers, jwt: { leeway: 30 } });
Signature
verifyToken(
  options?: {
    headers?: Record<string, string | string[] | undefined> | Headers | Iterable<[string, string]>;
    token?: string;
  } & Partial<ResolverAuthConfig>
) => Promise<VerifyTokenResult>
  • Params: inline options object with headers, token, and per-call ResolverAuthConfig overrides. This parameter shape is not exported as a named SDK type.
  • Returns: VerifyTokenResult
The verification logic is implemented by the ResolverAuthService class (also exported from @veltdev/node) for advanced callers who need to construct it directly; sdk.selfHosting.verifyToken is the standard entry point.

Error codes

On failure, result.errorCode is one of the ResolverAuthErrorCode values (also exported as the readonly string[] const RESOLVER_AUTH_ERROR_CODES).
CodeWhen
NOT_CONFIGUREDNeither jwt nor a verify callback is configured.
MISSING_TOKENNo token found in headers, or wrong scheme.
EXPIREDJWT exp claim has passed (beyond leeway).
INVALID_SIGNATURESignature verification failed, or malformed token.
CLAIM_MISMATCHConfigured issuer/audience wrong or absent; or a required claim is missing.
ALGORITHM_NOT_ALLOWEDalg=none; algorithm outside the allowlist; mixed symmetric+asymmetric allowlist; missing allowlist.
KEY_RESOLUTION_FAILEDNo usable key; JWKS URL is not HTTPS; JWKS fetch failed or returned non-2xx; redirect downgrade detected.
DEPENDENCY_MISSINGBuilt-in JWT path used but jose is not installed.
VERIFICATION_FAILEDCustom callback threw or returned falsy; unexpected internal error (fail-closed catch-all).

Security guarantees

  • alg=none is always rejected, even if present in the allowlist.
  • Mixed symmetric+asymmetric allowlists are refused (prevents HS/RS confusion).
  • A PEM placed in jwt.secret is refused.
  • JWKS is fetched only over HTTPS — http is rejected pre-fetch, and a redirect downgrade is rejected.
  • JWKS responses are cached per URL with a 5-minute TTL (not per kid).
  • The token-header alg is checked against the allowlist before any JWKS fetch.
  • The error field is generic and code-based — it never contains the token or secret.

REST API Backend

The sdk.api.* namespace gives you parity with Velt’s REST APIs across 18 services. Each method takes a fully-typed TypeScript request object and returns the raw Velt API response. API methods need apiKey and authToken and do not use MongoDB or AWS S3, although @veltdev/node@1.0.7 still validates the top-level database config at initialization. Every sdk.api.* method requires an organizationId in its request payload. Velt enforces data isolation per-organization server-side.

Field Allowlist

The REST add/update methods on activities, commentAnnotations, and notifications accept an optional second argument, FieldFilterOptions. Pass { filterUnknownFields: true } to narrow the request to exactly the fields the corresponding Velt backend endpoint accepts. See the note at the top of each service section for the behavior summary; the per-endpoint field lists are below.
interface FieldFilterOptions {
  /** When true, narrow the request to only the fields the Velt backend endpoint
   *  accepts, silently dropping unknown keys. Fail-open. Defaults to false. */
  filterUnknownFields?: boolean;
}

Exported filter utilities

The field-allowlist module is exported from @veltdev/node so advanced callers can reuse the same logic.
// Functions
pickKnownFields<T extends object>(data: T, keys: readonly string[]): Partial<T>;
filterRequest<T extends object>(request: T, spec: FilterSpec): T;

// Types
interface FilterSpec {
  keys: readonly string[];              // allowed top-level keys at this level (everything else dropped)
  arrays?: Record<string, FilterSpec>;  // known array-of-object fields, filtered per item
  objects?: Record<string, FilterSpec>; // known single-object fields, filtered recursively
}
  • pickKnownFields(data, keys) — keeps only own-enumerable keys present in keys; values kept by reference (no recursion); non-object/array/null inputs returned unchanged.
  • filterRequest(request, spec) — applies a FilterSpec recursively; never mutates input; fail-open (returns the original request on any error).
  • Eight per-method specs are exported: ADD_ACTIVITIES_SPEC, UPDATE_ACTIVITIES_SPEC, ADD_COMMENT_ANNOTATIONS_SPEC, UPDATE_COMMENT_ANNOTATIONS_SPEC, ADD_COMMENTS_SPEC, UPDATE_COMMENTS_SPEC, ADD_NOTIFICATIONS_SPEC, UPDATE_NOTIFICATIONS_SPEC.

Allowlisted fields per endpoint

When filterUnknownFields: true, only these keys survive (unknown keys dropped):
Spec (endpoint)Top-level keysNested item filtering
ADD_ACTIVITIES_SPEC (/v2/activities/add)organizationId, documentId, activitiesactivities[]: id, featureType, actionType, actionUser, targetEntityId, isActivityResolverUsed, targetSubEntityId, changes, entityData, entityTargetData, displayMessageTemplate, displayMessageTemplateData, actionIcon
UPDATE_ACTIVITIES_SPEC (/v2/activities/update)organizationId, activitiesactivities[]: id, changes, entityData, entityTargetData, displayMessageTemplate, displayMessageTemplateData, actionIcon
ADD_COMMENT_ANNOTATIONS_SPEC (/v2/commentannotations/add)organizationId, documentId, commentAnnotations, verifyUserPermissions, createDocument, createOrganizationcommentAnnotations[]: location, annotationId, targetElement, commentData, comments, status, commentType, type, assignedTo, priority, context, visibility, lastUpdated, createdAt, pageInfo, isPageAnnotation, positionX, positionY, screenWidth, screenHeight, screenScrollHeight, screenScrollTop — its comments[]/commentData[] use the annotation comment-data set (below)
UPDATE_COMMENT_ANNOTATIONS_SPEC (/v2/commentannotations/update)organizationId, documentId, annotationIds, locationIds, userIds, updateUsers, updatedDataupdatedData: location, targetElement, from, status, assignedTo, priority, context, visibility, createdAt, lastUpdated
ADD_COMMENTS_SPEC (/v2/commentannotations/comments/add)organizationId, documentId, annotationId, commentData, comments, verifyUserPermissions, createDocument, createOrganizationcommentData[]/comments[]: comment-data set (below)
UPDATE_COMMENTS_SPEC (/v2/commentannotations/comments/update)organizationId, documentId, annotationId, commentIds, userIds, updatedDataupdatedData: commentText, commentHtml, from, createdAt, taggedUserContacts, isCommentResolverUsed, isCommentTextAvailable, lastUpdated, context, attachments
ADD_NOTIFICATIONS_SPEC (/v2/notifications/add)organizationId, documentId, notificationId, actionUser, isNotificationResolverUsed, displayHeadlineMessageTemplate, displayHeadlineMessageTemplateData, displayBodyMessage, displayBodyMessageTemplate, displayBodyMessageTemplateData, notifyUsers, notificationSourceData, location, metadata, notifyAll, createDocument, createOrganization, verifyUserPermissions, context, isCrossOrganizationEnabledthe notification is the request itself — nested open-typed dicts pass through whole
UPDATE_NOTIFICATIONS_SPEC (/v2/notifications/update)organizationId, documentId, locationId, userId, verifyUserPermissions, notificationsnotifications[]: id, actionUser, displayHeadlineMessageTemplate, displayHeadlineMessageTemplateData, displayBodyMessage, displayBodyMessageTemplate, displayBodyMessageTemplateData, notifyUsers, notificationSourceData, location, metadata, context, readByUserIds, persistReadForUsers
The comment-annotation specs above reference two comment-data key sets:
  • Annotation-level commentData/comments (ANNOTATION_COMMENT_DATA_KEYS): commentText, commentHtml, commentId, from, lastUpdated, createdAt, taggedUserContacts, isCommentResolverUsed, isCommentTextAvailable, triggerNotification, triggerActivities, agent — carries the trigger flags but not context/attachments.
  • Comments-endpoint commentData/comments (COMMENT_DATA_KEYS): commentText, commentHtml, commentId, from, lastUpdated, createdAt, taggedUserContacts, isCommentResolverUsed, isCommentTextAvailable, context, attachments, agent — carries context/attachments but not the trigger flags.
UPDATE_NOTIFICATIONS_SPEC intentionally excludes isRead/isArchived — they are absent from the backend UpdateNotificationsSchemaV2 and unsupported by /v2/notifications/update, so they are dropped when filtering is on.

Organizations

Namespace: sdk.api.organizations

addOrganizations

await sdk.api.organizations.addOrganizations({
  organizations: [{ organizationId: 'org-123', organizationName: 'My Organization' }],
});
Response
{
  result: {
    status: 'success',
    message: 'Organization(s) added successfully.',
    data: {
      'org-123': { success: true, id: '02cf91e5...', message: 'Added Successfully' }
    }
  }
}

getOrganizations

await sdk.api.organizations.getOrganizations({
  organizationIds: ['org-123'],
  pageSize: 10,
  pageToken: 'next-page-token',
});
Response
{
  result: {
    status: 'success',
    message: 'Organization(s) retrieved successfully.',
    data: [
      { id: 'org-123', organizationName: 'Your Organization Name', disabled: false }
    ],
    nextPageToken: 'pageToken'
  }
}

updateOrganizations

await sdk.api.organizations.updateOrganizations({
  organizations: [{ organizationId: 'org-123', organizationName: 'Updated Name' }],
});
Response
{ result: { status: 'success', message: 'Organization(s) updated successfully.', data: {} } }

deleteOrganizations

await sdk.api.organizations.deleteOrganizations({ organizationIds: ['org-123'] });
Response
{ result: { status: 'success', message: 'Organization(s) deleted successfully.', data: {} } }

updateOrganizationDisableState

await sdk.api.organizations.updateOrganizationDisableState({
  organizationIds: ['org-123'],
  disabled: true,
});
Response
{ result: { status: 'success', message: 'Organization(s) disable state updated.', data: {} } }

Folders

Namespace: sdk.api.folders

addFolder

await sdk.api.folders.addFolder({
  organizationId: 'org-123',
  folders: [{ folderId: 'folder-1', folderName: 'My Folder' }],
  createOrganization: true,
});
Response
{ result: { status: 'success', message: 'Folder(s) added successfully.', data: {} } }

getFolders

await sdk.api.folders.getFolders({
  organizationId: 'org-123',
  folderId: 'folder-1',
  maxDepth: 3,
});
Response
{
  result: {
    status: 'success',
    message: 'Folders retrieved successfully.',
    data: [
      {
        folderId: 'folder-1',
        folderName: 'Folder 1',
        organizationId: 'org-123',
        parentFolderId: 'root',
        createdAt: 1738695615706,
        lastUpdated: 1738696287859
      }
    ]
  }
}

updateFolder

await sdk.api.folders.updateFolder({
  organizationId: 'org-123',
  folders: [{ folderId: 'folder-1', folderName: 'Renamed' }],
});
Response
{ result: { status: 'success', message: 'Folder(s) updated successfully.', data: {} } }

deleteFolder

await sdk.api.folders.deleteFolder({ organizationId: 'org-123', folderId: 'folder-1' });
Response
{ result: { status: 'success', message: 'Folder deleted successfully.', data: {} } }

updateFolderAccess

await sdk.api.folders.updateFolderAccess({
  organizationId: 'org-123',
  folderIds: ['folder-1'],
  accessType: 'restricted',
});
Response
{ result: { status: 'success', message: 'Folder access updated successfully.', data: {} } }

Documents

Namespace: sdk.api.documents

addDocuments

await sdk.api.documents.addDocuments({
  organizationId: 'org-123',
  documents: [
    { documentId: 'doc-1', documentName: 'My Document' },
    { documentId: 'doc-2', documentName: 'Another Document' },
  ],
  createOrganization: true,
  folderId: 'folder-1',
  createFolder: true,
});
Response
{
  result: {
    status: 'success',
    message: 'Document(s) added successfully.',
    data: {
      'doc-1': { success: true, message: 'Added Successfully' },
      'doc-2': { success: true, message: 'Added Successfully' }
    }
  }
}

getDocuments

await sdk.api.documents.getDocuments({
  organizationId: 'org-123',
  documentIds: ['doc-1'],
  folderId: 'folder-1',
  pageSize: 10,
});
Response
{
  result: {
    status: 'success',
    message: 'Document(s) retrieved successfully.',
    data: [
      { documentName: 'yourDocumentName', disabled: false, accessType: 'public', id: 'yourDocumentId' }
    ],
    pageToken: 'nextPageToken'
  }
}

updateDocuments

await sdk.api.documents.updateDocuments({
  organizationId: 'org-123',
  documents: [{ documentId: 'doc-1', documentName: 'Renamed' }],
});
Response
{ result: { status: 'success', message: 'Document(s) updated successfully.', data: {} } }

deleteDocuments

await sdk.api.documents.deleteDocuments({
  organizationId: 'org-123',
  documentIds: ['doc-1', 'doc-2'],
});
Response
{ result: { status: 'success', message: 'Document(s) deleted successfully.', data: {} } }

moveDocuments

await sdk.api.documents.moveDocuments({
  organizationId: 'org-123',
  documentIds: ['doc-1', 'doc-2'],
  folderId: 'target-folder-id',
});
Response
{ result: { status: 'success', message: 'Document(s) moved successfully.', data: {} } }

updateDocumentAccess

await sdk.api.documents.updateDocumentAccess({
  organizationId: 'org-123',
  documentIds: ['doc-1', 'doc-2'],
  accessType: 'organizationPrivate',
});
Response
{ result: { status: 'success', message: 'Document access updated successfully.', data: {} } }

updateDocumentDisableState

await sdk.api.documents.updateDocumentDisableState({
  organizationId: 'org-123',
  documentIds: ['doc-1'],
  disabled: true,
});
Response
{ result: { status: 'success', message: 'Document(s) disable state updated.', data: {} } }

migrateDocuments

Note: migrateDocuments is asynchronous and returns a migrationId. Poll completion with migrateDocumentsStatus.
await sdk.api.documents.migrateDocuments({
  organizationId: 'org-123',
  documentId: 'old-doc-id',
  newDocumentId: 'new-doc-id',
});
Response
{
  result: {
    status: 'success',
    message: 'Document migration started successfully.',
    data: { migrationId: 'yourMigrationId' }
  }
}

migrateDocumentsStatus

await sdk.api.documents.migrateDocumentsStatus({
  organizationId: 'org-123',
  migrationId: 'migration-1',
});
Response
{
  result: {
    status: 'success',
    message: 'Migration status retrieved successfully.',
    data: { migrationId: 'yourMigrationId', status: 'completed' }
  }
}

getDocumentsCount

await sdk.api.documents.getDocumentsCount({ organizationId: 'org-123' });
await sdk.api.documents.getDocumentsCount({ organizationId: 'org-123', folderId: 'folder-1', excludeFolderDocs: true });
Response
{ result: { status: 'success', message: 'Documents count retrieved successfully.', data: { count: 42 } } }

Users

Namespace: sdk.api.users

addUsers

await sdk.api.users.addUsers({
  organizationId: 'org-123',
  users: [{ userId: 'user-1', name: 'John Doe', email: 'john@example.com', accessRole: 'editor' }],
  createOrganization: true,
});
Response
{ result: { status: 'success', message: 'User(s) added successfully.', data: {} } }

getUsers

await sdk.api.users.getUsers({
  organizationId: 'org-123',
  userIds: ['user-1'],
  documentId: 'doc-1',
  pageSize: 100,
  allDocuments: true,
  groupByDocumentId: true,
});
Response
{
  result: {
    status: 'success',
    message: 'User(s) retrieved successfully.',
    data: [
      { email: 'userEmail@domain.com', name: 'userName', userId: 'yourUserId' }
    ],
    nextPageToken: 'pageToken'
  }
}

updateUsers

await sdk.api.users.updateUsers({
  organizationId: 'org-123',
  users: [{ userId: 'user-1', name: 'Jane Doe', accessRole: 'viewer' }],
});
Response
{ result: { status: 'success', message: 'User(s) updated successfully.', data: {} } }

deleteUsers

await sdk.api.users.deleteUsers({ organizationId: 'org-123', userIds: ['user-1'] });
Response
{ result: { status: 'success', message: 'User(s) deleted successfully.', data: {} } }

getUsersCount

await sdk.api.users.getUsersCount({ organizationId: 'org-123', allDocuments: true });
Response
{ result: { status: 'success', message: 'Users count retrieved successfully.', data: { count: 12 } } }

getDocUsers

await sdk.api.users.getDocUsers({ organizationId: 'org-123', userIds: ['user-1'] });
Response
{
  result: {
    status: 'success',
    message: 'Document users retrieved successfully.',
    data: [{ userId: 'user-1', name: 'John Doe', email: 'john@example.com' }]
  }
}

addUserInvite

await sdk.api.users.addUserInvite({ email: 'invitee@example.com', organizationId: 'org-123', type: 'doc_user', documentId: 'doc-1' });
Response
{ result: { status: 'success', message: 'User invite created successfully.', data: {} } }

respondToUserInvite

await sdk.api.users.respondToUserInvite({ email: 'invitee@example.com', organizationId: 'org-123', type: 'doc_user', documentId: 'doc-1', status: 'ACCEPTED', user: { userId: 'user-9', name: 'New User', email: 'invitee@example.com' }, authToken: 'user-auth-token' });
Response
{ result: { status: 'success', message: 'Invite response recorded successfully.', data: {} } }

getUserInvites

await sdk.api.users.getUserInvites({ organizationId: 'org-123' });
Response
{
  result: {
    status: 'success',
    message: 'User invites retrieved successfully.',
    data: [{ email: 'invitee@example.com', type: 'doc_user', status: 'PENDING' }],
    nextPageToken: 'pageToken'
  }
}

getUserInvitations

await sdk.api.users.getUserInvitations({ organizationId: 'org-123', email: 'invitee@example.com' });
Response
{
  result: {
    status: 'success',
    message: 'User invitations retrieved successfully.',
    data: [{ organizationId: 'org-123', type: 'doc_user', status: 'PENDING' }],
    nextPageToken: 'pageToken'
  }
}

getInvitedPendingUsersCount

await sdk.api.users.getInvitedPendingUsersCount({ organizationId: 'org-123', type: 'org_user' });
Response
{ result: { status: 'success', message: 'Pending invited users count retrieved successfully.', data: { count: 3 } } }

User Groups

Namespace: sdk.api.userGroups

addUserGroups

await sdk.api.userGroups.addUserGroups({
  organizationId: 'org-123',
  organizationUserGroups: [{ groupId: 'engineering', groupName: 'Engineering' }],
  createOrganization: true,
});
Response
{ result: { status: 'success', message: 'User group(s) added successfully.', data: {} } }

addUsersToGroup

await sdk.api.userGroups.addUsersToGroup({
  organizationId: 'org-123',
  organizationUserGroupId: 'engineering',
  userIds: ['user-1', 'user-2'],
});
Response
{ result: { status: 'success', message: 'Users added to group successfully.', data: {} } }

deleteUsersFromGroup

await sdk.api.userGroups.deleteUsersFromGroup({
  organizationId: 'org-123',
  organizationUserGroupId: 'engineering',
  userIds: ['user-1'],
});
Response
{ result: { status: 'success', message: 'Users removed from group successfully.', data: {} } }

Notifications

Namespace: sdk.api.notifications
Filtering unknown fields: The add/update methods below accept an optional options?: FieldFilterOptions second argument. When { filterUnknownFields: true } is passed, unknown/custom keys are dropped before the request is sent, narrowing the payload to exactly the fields the Velt backend endpoint accepts. Open-typed objects (actionUser, context, metadata, user objects) pass through whole. Filtering is fail-open: if it errors, the original payload is sent, so a write is never blocked. UPDATE_NOTIFICATIONS_SPEC intentionally excludes isRead/isArchived — they are unsupported by /v2/notifications/update, so they are dropped when filtering is on. See Field Allowlist for the full per-endpoint field list.

addNotifications

  • Adds one or more notifications targeted to specific users.
  • Params: AddNotificationsRequest
  • Returns: VeltApiResponse
  • Accepts an optional options?: FieldFilterOptions second argument — pass { filterUnknownFields: true } to drop unknown fields before sending (see the note above).
await sdk.api.notifications.addNotifications({
  organizationId: 'org-123',
  documentId: 'doc-1',
  actionUser: { userId: 'user-1', name: 'John Doe', email: 'john@example.com' },
  displayHeadlineMessageTemplate: '{actionUser} commented on your document',
  displayBodyMessage: 'Check out the new comment',
  notifyUsers: [{ userId: 'user-2', name: 'Jane Doe' }],
  createOrganization: true,
  createDocument: true,
});
Response
{ result: { status: 'success', message: 'Notification(s) added successfully.', data: {} } }

getNotifications

await sdk.api.notifications.getNotifications({
  organizationId: 'org-123',
  userId: 'user-2',
  pageSize: 10,
  order: 'desc',
});
Response
{
  result: {
    status: 'success',
    message: 'Notification(s) retrieved successfully.',
    data: [
      {
        id: 'notificationId',
        notificationSource: 'custom',
        actionUser: { userId: 'user-1', name: 'John Doe', email: 'john@example.com' },
        displayBodyMessage: 'Check out the new comment',
        displayHeadlineMessageTemplate: '{actionUser} commented on your document',
        timestamp: 1722409519944
      }
    ],
    pageToken: 'nextPageToken'
  }
}

updateNotifications

  • Updates existing notifications (e.g., mark as read).
  • Params: UpdateNotificationsRequest
  • Returns: VeltApiResponse
  • Accepts an optional options?: FieldFilterOptions second argument — pass { filterUnknownFields: true } to drop unknown fields before sending (see the note above).
await sdk.api.notifications.updateNotifications({
  organizationId: 'org-123',
  userId: 'user-2',
  notifications: [{ notificationId: 'n-1', read: true }],
});
Response
{ result: { status: 'success', message: 'Notification(s) updated successfully.', data: {} } }

deleteNotifications

await sdk.api.notifications.deleteNotifications({
  organizationId: 'org-123',
  userId: 'user-2',
  notificationIds: ['n-1'],
});
Response
{ result: { status: 'success', message: 'Notification(s) deleted successfully.', data: {} } }

getNotificationConfig

await sdk.api.notifications.getNotificationConfig({
  organizationId: 'org-123',
  userId: 'user-1',
});
Response
{
  result: {
    status: 'success',
    message: 'User config fetched successfully.',
    data: [
      {
        config: { inbox: 'ALL', email: 'ALL' },
        metadata: { organizationId: 'org-123', apiKey: 'API_KEY', documentId: 'doc-1', userId: 'user-1' }
      }
    ]
  }
}

setNotificationConfig

await sdk.api.notifications.setNotificationConfig({
  organizationId: 'org-123',
  userIds: ['user-1', 'user-2'],
  config: { inbox: 'ALL', email: 'MINE', slack: 'NONE' },
});
Response
{ result: { status: 'success', message: 'User config set successfully.', data: {} } }

Comment Annotations

Namespace: sdk.api.commentAnnotations
Filtering unknown fields: The add/update methods below accept an optional options?: FieldFilterOptions second argument. When { filterUnknownFields: true } is passed, request entity collections are narrowed to only the fields the Velt backend endpoint accepts, dropping unknown/custom keys before the request is sent. Open-typed objects (from, context, metadata, user objects) pass through whole — their nested contents are never filtered. Filtering is fail-open: if it errors, the original payload is sent, so a write is never blocked. See Field Allowlist for the full per-endpoint field list.
// Extra fields (internalId) are dropped before the request is sent.
await sdk.api.commentAnnotations.addCommentAnnotations(
  {
    organizationId: 'org-123',
    documentId: 'doc-1',
    commentAnnotations: [
      {
        location: { id: 'section-1' },
        commentData: [{ commentText: 'Review this', from: { userId: 'user-1' } }],
        internalId: 'app-specific-field', // dropped when filtering is enabled
      },
    ],
  },
  { filterUnknownFields: true },
);

addCommentAnnotations

  • Creates comment annotations on a document.
  • Params: AddCommentAnnotationsRequest
  • Returns: VeltApiResponse
  • Accepts an optional options?: FieldFilterOptions second argument — pass { filterUnknownFields: true } to drop unknown fields before sending (see the note above).
await sdk.api.commentAnnotations.addCommentAnnotations({
  organizationId: 'org-123',
  documentId: 'doc-1',
  commentAnnotations: [{
    location: { id: 'section-1', locationName: 'Introduction' },
    commentData: [{
      commentText: 'This needs review',
      commentHtml: '<p>This needs review</p>',
      from: { userId: 'user-1', name: 'John Doe', email: 'john@example.com' },
    }],
  }],
});
Response
{ result: { status: 'success', message: 'Annotation(s) added successfully.', data: {} } }

getCommentAnnotations

await sdk.api.commentAnnotations.getCommentAnnotations({
  organizationId: 'org-123',
  documentId: 'doc-1',
  statusIds: ['OPEN'],
  pageSize: 10,
});
Response
{
  result: {
    status: 'success',
    message: 'Annotations fetched successfully.',
    data: [
      {
        type: 'comment',
        comments: [
          {
            commentId: 123456,
            commentText: 'This is a sample comment text.',
            commentHtml: '<p>This is a sample comment text.</p>',
            from: { userId: 'user123', name: 'John Doe', email: 'john.doe@example.com' },
            createdAt: 1687344600000
          }
        ],
        status: { id: 'OPEN', name: 'Open', type: 'default' }
      }
    ]
  }
}

getCommentAnnotationsCount

await sdk.api.commentAnnotations.getCommentAnnotationsCount({
  organizationId: 'org-123',
  documentIds: ['doc-1', 'doc-2'],
  userId: 'user-1',
});
Response
{
  result: {
    status: 'success',
    message: 'Comment count retrieved successfully.',
    data: {
      'doc-1': { total: 4, unread: 2 },
      'doc-2': { total: 2, unread: 0 }
    }
  }
}

updateCommentAnnotations

  • Updates fields on existing annotations (e.g., resolve them).
  • Params: UpdateCommentAnnotationsRequest
  • Returns: VeltApiResponse
  • Accepts an optional options?: FieldFilterOptions second argument — pass { filterUnknownFields: true } to drop unknown fields before sending (see the note above).
await sdk.api.commentAnnotations.updateCommentAnnotations({
  organizationId: 'org-123',
  documentId: 'doc-1',
  annotationIds: ['annotation-id-1'],
  updatedData: { status: { id: 'RESOLVED', name: 'Resolved', type: 'terminal' } },
});
Response
{ result: { status: 'success', message: 'Annotation(s) updated successfully.', data: {} } }

deleteCommentAnnotations

await sdk.api.commentAnnotations.deleteCommentAnnotations({
  organizationId: 'org-123',
  documentId: 'doc-1',
  annotationIds: ['annotation-id-1'],
});
Response
{ result: { status: 'success', message: 'Annotation(s) deleted successfully.', data: {} } }

addComments

  • Adds comments to an existing annotation.
  • Params: AddCommentsRequest
  • Returns: VeltApiResponse
  • Accepts an optional options?: FieldFilterOptions second argument — pass { filterUnknownFields: true } to drop unknown fields before sending (see the note above).
await sdk.api.commentAnnotations.addComments({
  organizationId: 'org-123',
  documentId: 'doc-1',
  annotationId: 'annotation-id-1',
  commentData: [{
    commentText: 'I agree, let me fix this',
    from: { userId: 'user-2', name: 'Jane Doe' },
  }],
});
Response
{ result: { status: 'success', message: 'Comment(s) added successfully.', data: {} } }

getComments

await sdk.api.commentAnnotations.getComments({
  organizationId: 'org-123',
  documentId: 'doc-1',
  annotationId: 'annotation-id-1',
  userIds: ['user-1'],
});
Response
{
  result: {
    status: 'success',
    message: 'Comments(s) retrieved successfully.',
    data: [
      {
        commentId: 153783,
        commentText: 'Sample Comment Text',
        commentHtml: '<div>Hello Updated 2</div>',
        from: { userId: 'yourUserId', name: 'User Name', email: 'user@example.com' },
        lastUpdated: '2024-06-20T09:53:42.258Z',
        status: 'added',
        type: 'text'
      }
    ]
  }
}

updateComments

  • Updates comments within a specific annotation.
  • Params: UpdateCommentsRequest
  • Returns: VeltApiResponse
  • Accepts an optional options?: FieldFilterOptions second argument — pass { filterUnknownFields: true } to drop unknown fields before sending (see the note above).
await sdk.api.commentAnnotations.updateComments({
  organizationId: 'org-123',
  documentId: 'doc-1',
  annotationId: 'annotation-id-1',
  commentIds: [123456],
  updatedData: { commentText: 'Updated text', commentHtml: '<p>Updated text</p>' },
});
Response
{ result: { status: 'success', message: 'Comment(s) updated successfully.', data: {} } }

deleteComments

await sdk.api.commentAnnotations.deleteComments({
  organizationId: 'org-123',
  documentId: 'doc-1',
  annotationId: 'annotation-id-1',
  commentIds: [123456],
});
Response
{ result: { status: 'success', message: 'Comment(s) deleted successfully.', data: {} } }

Activities

Namespace: sdk.api.activities
Filtering unknown fields: The add/update methods below accept an optional options?: FieldFilterOptions second argument. When { filterUnknownFields: true } is passed, request entity collections are narrowed to only the fields the Velt backend endpoint accepts, dropping unknown/custom keys before the request is sent. Open-typed objects (actionUser, entityData, context, metadata) pass through whole — their nested contents are never filtered. Filtering is fail-open: if it errors, the original payload is sent, so a write is never blocked. See Field Allowlist for the full per-endpoint field list.

addActivities

  • Logs activity events.
  • Params: AddActivitiesRequest
  • Returns: VeltApiResponse
  • Accepts an optional options?: FieldFilterOptions second argument — pass { filterUnknownFields: true } to drop unknown fields before sending (see the note above).
await sdk.api.activities.addActivities({
  organizationId: 'org-123',
  documentId: 'doc-456',
  activities: [{
    featureType: 'comment',
    actionType: 'comment.add',
    actionUser: { userId: 'user-1', name: 'John Doe', email: 'john@example.com' },
    displayMessageTemplate: '{{user}} added a comment',
  }],
});
Response
{ result: { status: 'success', message: 'Activity(s) added successfully.', data: {} } }

getActivities

await sdk.api.activities.getActivities({
  organizationId: 'org-123',
  documentId: 'doc-456',
  featureTypes: ['comment'],
  order: 'desc',
  pageSize: 50,
});
Response
{
  result: {
    status: 'success',
    message: 'Activity(s) retrieved successfully.',
    data: [
      {
        id: 'activity-001',
        featureType: 'comment',
        actionType: 'comment.add',
        actionUser: { userId: 'user-1', email: 'user@example.com', name: 'User Name' },
        targetEntityId: 'annotation-789',
        displayMessageTemplate: '{{user}} added a comment',
        metadata: { apiKey: 'yourApiKey', documentId: 'doc-456', organizationId: 'org-123' },
        timestamp: 1722409519944
      }
    ],
    pageToken: 'nextPageToken'
  }
}

updateActivities

  • Updates existing activity events.
  • Params: UpdateActivitiesRequest
  • Returns: VeltApiResponse
  • Accepts an optional options?: FieldFilterOptions second argument — pass { filterUnknownFields: true } to drop unknown fields before sending (see the note above).
await sdk.api.activities.updateActivities({
  organizationId: 'org-123',
  activities: [{ id: 'activity-001', displayMessageTemplate: '{{user}} updated the comment' }],
});
Response
{ result: { status: 'success', message: 'Activity(s) updated successfully.', data: {} } }

deleteActivities

await sdk.api.activities.deleteActivities({
  organizationId: 'org-123',
  documentId: 'doc-456',
});
Response
{ result: { status: 'success', message: 'Activity(s) deleted successfully.', data: {} } }

Access Control

Namespace: sdk.api.accessControl

addPermissions

await sdk.api.accessControl.addPermissions({
  user: { userId: 'user-1' },
  permissions: {
    resources: [
      { type: 'organization', id: 'org-123', accessRole: 'editor' },
      { type: 'document', id: 'doc-1', organizationId: 'org-123', accessRole: 'viewer', expiresAt: 1728902400 },
    ],
  },
});
Response
{ result: { status: 'success', message: 'Permissions added successfully.', data: {} } }

getPermissions

await sdk.api.accessControl.getPermissions({
  organizationId: 'org-123',
  userIds: ['user-1'],
  documentIds: ['doc-1'],
});
Response
{
  result: {
    status: 'success',
    message: 'User permissions retrieved successfully.',
    data: {
      'user-1': {
        documents: { 'doc-1': { accessRole: 'viewer', accessType: 'restricted' } },
        organization: { 'org-123': { accessRole: 'editor' } }
      }
    }
  }
}

removePermissions

await sdk.api.accessControl.removePermissions({
  userId: 'user-1',
  permissions: {
    resources: [
      { type: 'organization', id: 'org-123' },
      { type: 'document', id: 'doc-1', organizationId: 'org-123' },
    ],
  },
});
Response
{ result: { status: 'success', message: 'Permissions removed successfully.', data: {} } }

generateSignature

await sdk.api.accessControl.generateSignature({
  permissions: [{
    userId: 'user-1',
    resourceId: 'doc-1',
    type: 'document',
    hasAccess: true,
    accessRole: 'viewer',
  }],
});
Response
{
  result: {
    status: 'success',
    message: 'Signature generated successfully.',
    data: { signature: 'a1b2c3d4e5f6...' }
  }
}

generateToken

await sdk.api.accessControl.generateToken({
  userId: 'user-1',
  userProperties: { name: 'John Doe', email: 'john@example.com', isAdmin: false },
  permissions: {
    resources: [
      { type: 'organization', id: 'org-123', accessRole: 'viewer' },
      { type: 'document', id: 'doc-1', organizationId: 'org-123', accessRole: 'editor' },
    ],
  },
});
Response
{
  result: {
    status: 'success',
    message: 'Token generated successfully.',
    data: { token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' }
  }
}

CRDT

Namespace: sdk.api.crdt Supports text, map, array, and xml CRDT data types.

addCrdtData

await sdk.api.crdt.addCrdtData({
  organizationId: 'org-123',
  documentId: 'doc-1',
  editorId: 'my-collab-note',
  data: 'Hello, collaborative world!',
  type: 'text',
});
Response
{ result: { status: 'success', message: 'CRDT data added successfully.', data: {} } }

getCrdtData

await sdk.api.crdt.getCrdtData({
  organizationId: 'org-123',
  documentId: 'doc-1',
  editorId: 'my-collab-note',
});
Response
{
  result: {
    status: 'success',
    message: 'CRDT data retrieved successfully.',
    data: [
      {
        data: 'Hello, collaborative world!',
        id: 'my-collab-note',
        lastUpdate: '2025-01-20T10:30:00.000Z',
        lastUpdatedBy: 'user-123',
        sessionId: 'session-abc-456'
      }
    ]
  }
}

updateCrdtData

await sdk.api.crdt.updateCrdtData({
  organizationId: 'org-123',
  documentId: 'doc-1',
  editorId: 'my-collab-note',
  data: 'Updated collaborative content!',
  type: 'text',
});
Response
{ result: { status: 'success', message: 'CRDT data updated successfully.', data: {} } }

deleteCrdtData

await sdk.api.crdt.deleteCrdtData({ organizationId: 'org-123', documentId: 'doc-1', editorIds: ['my-collab-note'] });
await sdk.api.crdt.deleteCrdtData({ organizationId: 'org-123', documentId: 'doc-1' });
Response
{ result: { status: 'success', message: 'CRDT data deleted successfully.', data: {} } }

Presence

Namespace: sdk.api.presence

addPresence

await sdk.api.presence.addPresence({
  organizationId: 'org-123',
  documentId: 'doc-456',
  users: [
    { userId: 'user-1', name: 'John Doe', email: 'john@example.com', status: 'online' },
    { userId: 'user-2', name: 'Jane Doe', status: 'away' },
  ],
});
Response
{ result: { status: 'success', message: 'Presence added successfully.', data: {} } }

updatePresence

await sdk.api.presence.updatePresence({
  organizationId: 'org-123',
  documentId: 'doc-456',
  users: [{ userId: 'user-1', status: 'away' }],
});
Response
{ result: { status: 'success', message: 'Presence updated successfully.', data: {} } }

deletePresence

await sdk.api.presence.deletePresence({
  organizationId: 'org-123',
  documentId: 'doc-456',
  userIds: ['user-1', 'user-2'],
});
Response
{ result: { status: 'success', message: 'Presence removed successfully.', data: {} } }

Livestate

Namespace: sdk.api.livestate

broadcastEvent

await sdk.api.livestate.broadcastEvent({
  organizationId: 'org-123',
  documentId: 'doc-1',
  liveStateDataId: 'my-live-state',
  data: { status: 'active', message: 'Hello World' },
  merge: true,
});
Response
{ result: { status: 'success', message: 'Event broadcast successfully.', data: {} } }

Recordings

Namespace: sdk.api.recordings

getRecordings

await sdk.api.recordings.getRecordings({
  organizationId: 'org-123',
  documentId: 'doc-456',
  recordingIds: ['rec-1'],
  pageSize: 10,
  pageToken: 'next-page-token',
});
Response
{
  result: {
    status: 'success',
    message: 'Recorder annotations retrieved successfully.',
    data: [
      {
        type: 'recorder',
        recordingType: 'screen',
        mode: 'floating',
        metadata: { apiKey: 'YOUR_API_KEY', documentId: 'doc-456', organizationId: 'org-123' },
        recordedTime: { duration: 4204.55, display: '00:00:04' },
        displayName: 'Screen Recording 1773814490242.mp4',
        annotationId: 'ypvmVTROaNU1qP4kq7Cc',
        attachments: [{
          attachmentId: 875113,
          url: 'https://storage.googleapis.com/...',
          mimeType: 'video/mp4',
          name: 'recording_1773814490242.mp4',
          type: 'mp4',
          size: 103551
        }],
        latestVersion: 5
      }
    ],
    pageToken: 'nextPageToken'
  }
}

Rewriter

Namespace: sdk.api.rewriter Supports OpenAI, Anthropic, and Gemini models.

askAi

Note: askAi may take several seconds to respond. The SDK does not enforce a client-side timeout.
await sdk.api.rewriter.askAi({
  model: 'gpt-4o',
  prompt: 'Fix the grammar and spelling in the following text.',
  text: 'Their going to the store to by some apples.',
});
Response
{
  result: {
    success: true,
    text: 'The rewritten or processed text returned by the model.'
  }
}

GDPR

Namespace: sdk.api.gdpr

deleteAllUserData

Note: deleteAllUserData is asynchronous and returns a job ID. Poll completion with getDeleteUserDataStatus.
await sdk.api.gdpr.deleteAllUserData({
  userIds: ['user-1', 'user-2'],
  organizationIds: ['org-123'],
});
Response
{
  result: {
    status: 'success',
    message: 'User data deletion job queued.',
    data: { jobId: 'delete-job-id' }
  }
}

getAllUserData

await sdk.api.gdpr.getAllUserData({
  organizationId: 'org-123',
  userId: 'user-1',
  pageToken: 'next-page-token',
});
Response
{
  result: {
    status: 'success',
    message: 'Data fetched successfully.',
    data: {
      comments: [/* up to 100 items */],
      reactions: [/* up to 100 items */],
      recordings: [/* up to 100 items */],
      notifications: [/* up to 100 items */]
    },
    nextPageToken: 'bhdwdqwjs298e39e479ddkeuw==329'
  }
}

getDeleteUserDataStatus

await sdk.api.gdpr.getDeleteUserDataStatus({ jobId: 'job-id-from-delete-call' });
Response
{
  result: {
    status: 'success',
    message: 'Data fetched successfully.',
    data: {
      isDeleteCompleted: true,
      tasksLeft: 0,
      lastTaskCompletedTime: 1748972106739
    }
  }
}

Workspace

Namespace: sdk.api.workspace

createWorkspace

await sdk.api.workspace.createWorkspace({
  ownerEmail: 'owner@example.com',
  workspaceName: 'My Workspace',
  name: 'John Doe',
});
Response
{ result: { status: 'success', message: 'Workspace created successfully.', data: {} } }

getWorkspace

  • Retrieves details for the current workspace.
  • Params: empty object {}
  • Returns: GetWorkspaceResponse
await sdk.api.workspace.getWorkspace({});
Response
{
  result: {
    status: 'success',
    message: 'Workspace retrieved successfully.',
    data: {
      id: 'workspace_abc123',
      name: 'My Workspace',
      owner: { email: 'owner@example.com', id: 'owner_id_123', name: 'John Doe', avatar: '' },
      authToken: 'eyJhbGciOiJSUzI1NiIs...',
      apiKeyList: {
        velt_api_key_1: { apiKeyName: 'John Doe Test API Key', id: 'velt_api_key_1', type: 'testing' }
      }
    }
  }
}

createApiKey

await sdk.api.workspace.createApiKey({
  ownerEmail: 'owner@example.com',
  type: 'production',
  createAuthToken: true,
  apiKeyName: 'Production Key',
  allowedDomains: ['example.com', '*.example.com'],
});
Response
{
  result: {
    status: 'success',
    message: 'API key created successfully.',
    data: { apiKey: 'velt_api_key_2', authToken: 'eyJhbGciOi...' }
  }
}

updateApiKey

await sdk.api.workspace.updateApiKey({ apiKey: 'velt_api_key_1', apiKeyName: 'Renamed Key' });
Response
{ result: { status: 'success', message: 'API key updated successfully.', data: {} } }

getApiKeys

await sdk.api.workspace.getApiKeys({ pageSize: 50, pageToken: 'next-page-token' });
Response
{
  result: {
    status: 'success',
    message: 'API keys retrieved successfully.',
    data: [
      { id: 'velt_api_key_1', apiKeyName: 'Production Key', type: 'production' },
      { id: 'velt_api_key_2', apiKeyName: 'Testing Key', type: 'testing' }
    ],
    nextPageToken: 'eyJsYXN0SWQiOiJ2ZWx0X2FwaV9rZXlfMiJ9'
  }
}

getApiKeyMetadata

  • Gets metadata for the current API key.
  • Now posts to /v2/workspace/apikeyconfig/get (previously /v2/workspace/apikeymetadata/get); the method name and signature (GetApiKeyMetadataRequest, which is {}) are unchanged — no caller changes required.
  • Params: empty object {}
  • Returns: GetApiKeyMetadataResponse
await sdk.api.workspace.getApiKeyMetadata({});
Response
{
  result: {
    status: 'success',
    message: 'API key metadata retrieved successfully.',
    data: {
      defaultDocumentAccessType: 'public',
      planInfo: { type: 'sdk test' },
      ownerEmail: 'owner@example.com'
    }
  }
}

resetAuthToken

await sdk.api.workspace.resetAuthToken({ apiKey: 'velt_api_key_1' });
Response
{
  result: {
    status: 'success',
    message: 'Auth token reset successfully.',
    data: { authToken: 'eyJhbGciOi...' }
  }
}

getAuthTokens

await sdk.api.workspace.getAuthTokens({ apiKey: 'velt_api_key_1' });
Response
{
  result: {
    status: 'success',
    message: 'Auth tokens retrieved successfully.',
    data: {
      apiKey: 'velt_api_key_1',
      authTokens: [
        { token: 'eyJhbGciOiJSUzI1NiIs...', name: 'Default Auth Token', domains: [] }
      ]
    }
  }
}

addDomains

await sdk.api.workspace.addDomains({ domains: ['example.com', '*.firebase.com'] });
Response
{ result: { status: 'success', message: 'Domains added successfully.', data: {} } }

deleteDomains

await sdk.api.workspace.deleteDomains({ domains: ['example.com'] });
Response
{ result: { status: 'success', message: 'Domains deleted successfully.', data: {} } }

getDomains

await sdk.api.workspace.getDomains({});
Response
{
  result: {
    status: 'success',
    message: 'Allowed domains retrieved successfully.',
    data: { allowedDomains: ['localhost', '127.0.0.1', 'example.com'] }
  }
}

getEmailStatus

await sdk.api.workspace.getEmailStatus({ ownerEmail: 'owner@example.com' });
Response
{
  result: {
    status: 'success',
    message: 'Email verified and workspace reactivated',
    data: { verified: true }
  }
}
await sdk.api.workspace.sendLoginLink({
  email: 'user@example.com',
  continueUrl: 'https://app.example.com/dashboard',
});
Response
{ result: { status: 'success', message: 'Login link sent.', data: {} } }

getEmailConfig

await sdk.api.workspace.getEmailConfig({});
Response
{
  result: {
    status: 'success',
    message: 'Email configuration retrieved successfully.',
    data: {
      useEmailService: false,
      emailServiceConfig: { type: 'default', apiKey: '', fromEmail: '', fromCompany: '', commentTemplateId: '', tagTemplateId: '' }
    }
  }
}

updateEmailConfig

await sdk.api.workspace.updateEmailConfig({
  useEmailService: true,
  emailServiceConfig: { type: 'sendgrid', apiKey: 'sg-key', fromEmail: 'noreply@example.com' },
});
Response
{ result: { status: 'success', message: 'Email config updated successfully.', data: {} } }

getWebhookConfig

await sdk.api.workspace.getWebhookConfig({});
Response
{
  result: {
    status: 'success',
    message: 'Webhook configuration retrieved successfully.',
    data: {
      useWebhookService: false,
      webhookServiceConfig: { authToken: '', rawNotificationUrl: '', processedNotificationUrl: '' }
    }
  }
}

updateWebhookConfig

await sdk.api.workspace.updateWebhookConfig({
  useWebhookService: true,
  webhookServiceConfig: {
    authToken: 'webhook-secret',
    rawNotificationUrl: 'https://example.com/webhook/raw',
    processedNotificationUrl: 'https://example.com/webhook/processed',
  },
});
Response
{ result: { status: 'success', message: 'Webhook config updated successfully.', data: {} } }

getRequestedDomains

await sdk.api.workspace.getRequestedDomains({});
Response
{ result: { status: 'success', message: 'Requested domains retrieved successfully.', data: [] } }

acceptRejectAdditionalUrlRequest

await sdk.api.workspace.acceptRejectAdditionalUrlRequest({ action: 'accept', id: 'req-1', clientDocId: 'doc-1' });
Response
{ result: { status: 'success', message: 'Additional URL request updated successfully.', data: {} } }

createDomainRequest

await sdk.api.workspace.createDomainRequest({ domain: 'https://example.com', clientDocumentId: 'doc-1', organizationId: 'org-123' });
Response
{ result: { status: 'success', message: 'Domain request created successfully.', data: {} } }

copyApiKey

await sdk.api.workspace.copyApiKey({ sourceApiKey: 'velt_api_key_1', targetApiKey: 'velt_api_key_2' });
Response
{ result: { status: 'success', message: 'API key configuration copied successfully.', data: {} } }

updateApiKeyConfig

await sdk.api.workspace.updateApiKeyConfig({ requireJwtToken: true, defaultDocumentAccessType: 'organizationPrivate' });
Response
{ result: { status: 'success', message: 'API key config updated successfully.', data: {} } }

getNotificationConfig

await sdk.api.workspace.getNotificationConfig({});
Response
{ result: { status: 'success', message: 'Notification config retrieved successfully.', data: {} } }

updateNotificationConfig

await sdk.api.workspace.updateNotificationConfig({ useNotificationService: true });
Response
{ result: { status: 'success', message: 'Notification config updated successfully.', data: {} } }

getPermissionProviderConfig

await sdk.api.workspace.getPermissionProviderConfig({});
Response
{ result: { status: 'success', message: 'Permission provider config retrieved successfully.', data: {} } }

updatePermissionProviderConfig

await sdk.api.workspace.updatePermissionProviderConfig({ usePermissionProvider: true });
Response
{ result: { status: 'success', message: 'Permission provider config updated successfully.', data: {} } }

getActivityConfig

await sdk.api.workspace.getActivityConfig({});
Response
{ result: { status: 'success', message: 'Activity config retrieved successfully.', data: {} } }

updateActivityConfig

await sdk.api.workspace.updateActivityConfig({ activityServiceConfig: { isEnabled: true } });
Response
{ result: { status: 'success', message: 'Activity config updated successfully.', data: {} } }

ensureWorkspaceAuthToken

await sdk.api.workspace.ensureWorkspaceAuthToken({});
Response
{ result: { status: 'success', message: 'Workspace auth token ensured successfully.', data: { authToken: 'eyJhbGciOi...' } } }

getAdvancedWebhookConfig

await sdk.api.workspace.getAdvancedWebhookConfig({});
Response
{ result: { status: 'success', message: 'Advanced webhook config retrieved successfully.', data: {} } }

updateAdvancedWebhookConfig

await sdk.api.workspace.updateAdvancedWebhookConfig({ isEnabled: true });
Response
{ result: { status: 'success', message: 'Advanced webhook config updated successfully.', data: {} } }

getAdvancedWebhookEndpoints

await sdk.api.workspace.getAdvancedWebhookEndpoints({});
Response
{
  result: {
    status: 'success',
    message: 'Advanced webhook endpoints retrieved successfully.',
    data: [{ id: 'endpoint-1', url: 'https://example.com/webhooks/velt' }],
    nextPageToken: 'pageToken'
  }
}

createAdvancedWebhookEndpoint

await sdk.api.workspace.createAdvancedWebhookEndpoint({ url: 'https://example.com/webhooks/velt', filterTypes: ['comment.added', 'comment.resolved'] });
Response
{ result: { status: 'success', message: 'Advanced webhook endpoint created successfully.', data: { id: 'endpoint-1' } } }

updateAdvancedWebhookEndpoint

await sdk.api.workspace.updateAdvancedWebhookEndpoint({ endpointId: 'endpoint-1', url: 'https://example.com/webhooks/velt-v2' });
Response
{ result: { status: 'success', message: 'Advanced webhook endpoint updated successfully.', data: {} } }

deleteAdvancedWebhookEndpoint

await sdk.api.workspace.deleteAdvancedWebhookEndpoint({ endpointId: 'endpoint-1' });
Response
{ result: { status: 'success', message: 'Advanced webhook endpoint deleted successfully.', data: {} } }

getAdvancedWebhookEndpointSecret

await sdk.api.workspace.getAdvancedWebhookEndpointSecret({ endpointId: 'endpoint-1' });
Response
{ result: { status: 'success', message: 'Endpoint secret retrieved successfully.', data: { secret: 'whsec_...' } } }

Token

Namespace: sdk.api.token

getToken

  • Generates a Velt auth token for a user via REST.
  • Params: positional — organizationId (string), userId (string), email (string, optional), isAdmin (boolean, optional)
  • Returns: VeltApiResponse
Note: getToken takes positional arguments — not a request object.
await sdk.api.token.getToken('org-123', 'user-1', 'john@example.com', false);
Response
{
  result: {
    status: 'success',
    message: 'Token generated successfully.',
    data: { token: 'eyJhbGciOiJIUzI1NiIs...' }
  }
}

Approval Workflows

Namespace: sdk.api.approval A new service for defining approval/review workflows (graphs of agent, human, and webhook nodes) and dispatching/resolving their executions and steps. 14 methods (routes live under /v2/workflow/*). Every type is Approval-prefixed. ApprovalService is also exported directly from @veltdev/node.

createDefinition

await sdk.api.approval.createDefinition({
  definitionId: 'doc-publish-review',
  name: 'Document Publish Review',
  scope: { level: 'organization', organizationId: 'org-123' },
  nodes: [
    { nodeId: 'review', type: 'human', config: { reviewers: [{ userId: 'user-1', mandatory: true }], commentBody: 'Please review before publishing.' } },
  ],
  edges: [],
});

updateDefinition

await sdk.api.approval.updateDefinition({
  definitionId: 'doc-publish-review',
  name: 'Document Publish Review v2',
  nodes: [
    { nodeId: 'review', type: 'human', config: { reviewers: [{ userId: 'user-1', mandatory: true }], commentBody: 'Please review before publishing.' } },
  ],
  edges: [],
  ifVersion: 1,
});

deleteDefinition

await sdk.api.approval.deleteDefinition({ definitionId: 'doc-publish-review' });

getDefinition

await sdk.api.approval.getDefinition({ definitionId: 'doc-publish-review' });

listDefinitions

await sdk.api.approval.listDefinitions({});

dispatchExecution

const exec = await sdk.api.approval.dispatchExecution({ definitionId: 'doc-publish-review', organizationId: 'org-123', documentId: 'doc-1' });

cancelExecution

await sdk.api.approval.cancelExecution({ executionId: 'exec-1' });

getExecution

await sdk.api.approval.getExecution({ executionId: 'exec-1' });

getExecutionEvents

await sdk.api.approval.getExecutionEvents({ executionId: 'exec-1' });

listExecutions

await sdk.api.approval.listExecutions({});

cancelStep

await sdk.api.approval.cancelStep({ executionId: 'exec-1', stepId: 'review', actorId: 'user-1' });

resolveStep

await sdk.api.approval.resolveStep({ executionId: 'exec-1', stepId: 'review', action: 'force-approve', actorId: 'user-1' });

recordAgentResolution

await sdk.api.approval.recordAgentResolution({ executionId: 'exec-1', stepId: 'review', responseId: 'resp-1', resolution: 'resolved', actorId: 'user-1' });

recordReviewerDecision

await sdk.api.approval.recordReviewerDecision({ executionId: 'exec-1', stepId: 'review', reviewerId: 'user-1', decision: 'approve' });

Node & Graph Types

These node/graph/config interfaces are referenced by the request types above. They are not standalone data-model entries.
interface ApprovalScope { level: 'apiKey' | 'organization' | 'document'; organizationId?: string; documentId?: string; }
interface ApprovalAgentResolutionPolicy { kind: 'allResolved' | 'minResolved'; minCount?: number; } // minCount req. when 'minResolved' (1..500)
interface ApprovalAgentNodeConfig { agentId: string; urlPath: string; /* 1..500 */ crossPageExecute?: boolean; maxUrlsToProcess?: number; /* positive int, max 500 */ userContextMapping?: Record<string, string>; pollIntervalMs?: number; /* 5000..60000 */ promptOverride?: string; /* max 8000 */ inputMapping?: Record<string, string>; requireNonEmptyOutput?: boolean; agentMaxRuntimeMs?: number; /* positive int, max 24h */ blocking?: boolean; resolutionPolicy?: ApprovalAgentResolutionPolicy; /* required when blocking */ }
interface ApprovalHumanReviewer { userId: string; mandatory: boolean; }
interface ApprovalHumanNodeOnRejectLoopBack { toNodeId: string; /* 1..64 */ maxIterations?: number; /* 1..20 */ onExhausted?: { routeToNodeId?: string }; when?: string; /* max 2000 */ }
interface ApprovalHumanNodeOnReject { routeToNodeId?: string; loopBack?: ApprovalHumanNodeOnRejectLoopBack; } // exactly one
interface ApprovalHumanNodeConfig { reviewerIds?: string[]; /* mutually exclusive with reviewers; 1..50 */ reviewers?: ApprovalHumanReviewer[]; /* unique userIds; >=1 mandatory; 1..50 */ reviewerEmails?: string[]; /* max 50 */ commentBody?: string; /* max 8000 */ onReject?: ApprovalHumanNodeOnReject; }
interface ApprovalWebhookNodeConfig { url: string; /* https, allowed host, max 2000 */ method?: 'GET' | 'POST'; mode?: 'sync' | 'async'; authMode?: 'hmac' | 'token' | 'none'; authTokenHeader?: string; /* required when authMode === 'token'; max 500 */ timeoutMs?: number; /* 1000..60000 */ expectedStatusCodes?: number[]; /* each 100..599; max 20 */ bodyTemplate?: 'envelope' | 'pass-through' | 'none'; /* 'none' requires method GET */ requestHeaders?: Record<string, string>; /* reserved/x-velt-* headers rejected; values max 2000 */ }
interface ApprovalAgentNode { nodeId: string; type: 'agent'; config: ApprovalAgentNodeConfig; slaMs?: number; requireNonEmptyOutput?: boolean; }
interface ApprovalHumanNode { nodeId: string; type: 'human'; config: ApprovalHumanNodeConfig; slaMs?: number; requireNonEmptyOutput?: boolean; }
interface ApprovalWebhookNode { nodeId: string; type: 'webhook'; config: ApprovalWebhookNodeConfig; slaMs?: number; requireNonEmptyOutput?: boolean; }
type ApprovalNode = ApprovalAgentNode | ApprovalHumanNode | ApprovalWebhookNode;
interface ApprovalEdge { from: string; to: string; when?: string; } // from/to 1..64; when max 1000
type ApprovalQuorumPolicy = 'waitAll' | 'cancelOnQuorum' | 'joinOnQuorum';
interface ApprovalGroup { groupId: string; /* 1..64 */ memberNodeIds: string[]; /* 1..500 */ expectedSteps: number; /* 1..500 */ quorum: number; /* 1..500 */ onQuorumMet?: ApprovalQuorumPolicy; requiredNodeIds?: string[]; /* max 500 */ }
interface ApprovalInboundWebhookTrigger { authMode: 'hmac' | 'bearer'; secret: string; /* 16..512 */ idempotencyHeader?: string; /* 1..128 */ idempotencyBodyPath?: string; /* 1..256 */ payloadMapping?: 'pass-through' | 'wrap'; }
interface ApprovalTrigger { triggerId: string; /* 1..128 */ eventName?: string; /* max 128 */ filters?: Record<string, unknown>; inboundWebhook?: ApprovalInboundWebhookTrigger; }
interface ApprovalLoopRejectTrigger { when?: string; } // max 2000
interface ApprovalLoopExhaustionPolicy { routeToNodeId?: string; } // 1..64
interface ApprovalLoop { loopId: string; /* 1..64 */ entryNodeId: string; /* 1..64 */ bodyNodeIds: string[]; /* 1..50 */ onIterationReject?: ApprovalLoopRejectTrigger; onExhausted?: ApprovalLoopExhaustionPolicy; maxIterations: number; /* 1..20 */ }
interface ApprovalWebhookConfig { url: string; secret: string; eventTypes?: string[]; } // url https/max2000; secret 16..512; eventTypes each 1..128, max 50

Error Handling

The SDK exports five typed error classes:
ClassExtendsWhen thrown
VeltSDKErrorErrorBase class for all SDK errors
VeltDatabaseErrorVeltSDKErrorMongoDB connection or query failures
VeltValidationErrorVeltSDKErrorMissing or invalid request parameters
VeltTokenErrorVeltSDKErrorToken generation failures
VeltApiErrorVeltSDKErrorREST API call failures
import { VeltSDK, VeltDatabaseError, VeltApiError } from '@veltdev/node';

try {
  const result = await sdk.api.organizations.getOrganizations({ organizationIds: ['org-123'] });
} catch (err) {
  if (err instanceof VeltApiError) {
    console.error('API call failed:', err.message);
  } else if (err instanceof VeltDatabaseError) {
    console.error('Database error:', err.message);
  } else {
    throw err;
  }
}
Common self-hosting error codes returned in the response errorCode field:
  • INVALID_INPUT — Request data is malformed or missing required fields
  • INTERNAL_ERROR — Server-side error during processing
  • NOT_FOUND — Requested resource was not found

Data Models

Key interfaces and serializer helpers exported from @veltdev/node. All symbols are available at the package top level:
import {
  PartialCommentAnnotation, partialCommentAnnotationFromDict, partialCommentAnnotationToDict,
  PartialTargetTextRange, partialTargetTextRangeFromDict, partialTargetTextRangeToDict,
  PartialComment, partialCommentFromDict, partialCommentToDict,
  BaseMetadata, baseMetadataFromDict, baseMetadataToDict,
} from '@veltdev/node';
PartialUser and PartialAttachment referenced in the interfaces below are minimal pass-through shapes. PartialUser is { userId: string } (any additional fields the frontend sends are preserved but not strongly typed). Full user/attachment shapes are documented in the central Data Models reference.

PartialCommentAnnotation

Represents a single comment annotation thread. Used as the payload shape when reading or writing annotation data in self-hosting event handlers.
interface PartialCommentAnnotation {
  annotationId: string;
  metadata?: BaseMetadata;
  comments?: Record<string, PartialComment>;
  from?: PartialUser;              // Typed in v1.0.2; previously pass-through
  assignedTo?: PartialUser;        // Typed in v1.0.2; previously pass-through
  targetTextRange?: PartialTargetTextRange;  // Typed in v1.0.2; previously pass-through
  resolvedByUserId?: string | null; // Three-state — see resolvedByUserId Semantics below
  [key: string]: unknown;          // Unknown keys are preserved on round-trip
}
FieldTypeNotes
annotationIdstringRequired. Stable identifier for the annotation thread.
metadataBaseMetadataOptional context (document, org, API key).
commentsRecord<string, PartialComment>Keyed by commentId string.
fromPartialUserUser who created the annotation.
assignedToPartialUserUser currently assigned to the annotation.
targetTextRangePartialTargetTextRangeText selection the annotation is anchored to.
resolvedByUserIdstring | nullThree-state field — see below.
[key]unknownExtra keys pass through serialization unchanged.
Serializers:
declare function partialCommentAnnotationFromDict(data: Record<string, unknown>): PartialCommentAnnotation;
declare function partialCommentAnnotationToDict(annotation: PartialCommentAnnotation): Record<string, unknown>;

PartialTargetTextRange

The text selection an annotation is anchored to. New interface in v1.0.2.
interface PartialTargetTextRange {
  text: string;
}
FieldTypeNotes
textstringRequired. The selected text content the annotation references.
Serializers:
declare function partialTargetTextRangeFromDict(data: Record<string, unknown>): PartialTargetTextRange;
declare function partialTargetTextRangeToDict(range: PartialTargetTextRange): Record<string, unknown>;

resolvedByUserId Semantics

resolvedByUserId on PartialCommentAnnotation is a three-state field. TypeScript has no sentinel value, so the three states map to property presence and value:
StateRepresentationMeaning
AbsentProperty not set on the objectNo resolution information — do not write to the database
Explicit nullresolvedByUserId: nullAnnotation was un-resolved (cleared)
StringresolvedByUserId: "user-123"Resolved by the user with this ID
Use Object.hasOwn (or 'resolvedByUserId' in annotation) to distinguish absent from explicit null:
const annotation = partialCommentAnnotationFromDict(payload);

if (!Object.hasOwn(annotation, 'resolvedByUserId')) {
  // Field absent — skip; do not overwrite existing resolution state
} else if (annotation.resolvedByUserId === null) {
  // Explicit null — un-resolve the annotation
} else {
  // String — mark resolved by annotation.resolvedByUserId
}

PartialComment

A single comment within an annotation thread. Round-trip serializers preserve unknown keys, so custom fields added by your application are not dropped.
interface PartialComment {
  commentId: string | number;
  commentHtml?: string;
  commentText?: string;
  attachments?: Record<string, PartialAttachment>;
  from?: PartialUser;
  to?: PartialUser[];
  taggedUserContacts?: PartialTaggedUserContacts[];
  [key: string]: unknown;   // Pass-through for unknown keys
}
FieldTypeNotes
commentIdstring | numberRequired. Unique within the annotation.
commentHtmlstringRaw HTML body of the comment.
commentTextstringPlain-text body, used for search and notifications.
attachmentsRecord<string, PartialAttachment>Keyed by attachment ID.
fromPartialUserAuthor of the comment.
toPartialUser[]Explicit recipients (direct-mention replies).
taggedUserContactsPartialTaggedUserContacts[]Users @-mentioned in the comment body.
[key]unknownExtra keys pass through serialization unchanged.
Serializers (new in v1.0.2):
declare function partialCommentFromDict(data: Record<string, unknown>): PartialComment;
declare function partialCommentToDict(comment: PartialComment): Record<string, unknown>;

BaseMetadata

Contextual identifiers attached to annotations and events. Provides both Velt-internal IDs and your application’s client-facing IDs for the same resources.
interface BaseMetadata {
  apiKey?: string;
  documentId?: string;
  clientDocumentId?: string;
  organizationId?: string;
  clientOrganizationId?: string;
  folderId?: string;
  veltFolderId?: string;
  documentMetadata?: Record<string, unknown>;
  sdkVersion?: string | null;   // New in v1.0.2
}
FieldTypeNotes
apiKeystringVelt project API key.
documentIdstringVelt-internal document identifier.
clientDocumentIdstringYour application’s document identifier.
organizationIdstringVelt-internal organization identifier.
clientOrganizationIdstringYour application’s organization identifier.
folderIdstringYour application’s folder identifier.
veltFolderIdstringVelt-internal folder identifier.
documentMetadataRecord<string, unknown>Arbitrary key-value metadata attached to the document.
sdkVersionstring | nullSDK version string. null means version is known to be absent. New in v1.0.2.
Serializers (exposed on public surface in v1.0.2):
declare function baseMetadataFromDict(data: Record<string, unknown>): BaseMetadata;
declare function baseMetadataToDict(meta: BaseMetadata): Record<string, unknown>;

Distribution

The package ships both CommonJS (CJS) and ES Module (ESM) bundles with rolled-up .d.ts type definitions, making it compatible with all modern Node.js project setups.
// ESM
import { VeltSDK } from '@veltdev/node';

// CommonJS
const { VeltSDK } = require('@veltdev/node');