All skills
catalystbyzoho avatar

/catalyst-sdk

@4a64353

Catalyst SDKs — initialization patterns, service access, and method reference for Node.js, Web (browser), Python, Java, Android, iOS, and Flutter. Trigger on 'SDK', 'zcatalyst-sdk-node', 'Node.js SDK', 'Web SDK', 'Python SDK', 'Java SDK', 'Android SDK', 'iOS SDK', 'Flutter SDK', or 'initialize SDK'.

Use this Skill: https://skilld.dev/gh/catalystbyzoho/agent-skills/catalyst-sdk

This session only. Nothing lands on disk.

referencessdk-nodejs.md

≈3.6k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Install: npm install zcatalyst-sdk-node

Use version 2.5.0 or later. All earlier versions are deprecated.


Initialization

const catalyst = require('zcatalyst-sdk-node');

// Advanced I/O (Express)
app.post('/api/action', async (req, res) => {
  const catalystApp = catalyst.initialize(req);
});

// Basic I/O
module.exports = async (context, basicIO) => {
  const catalystApp = catalyst.initialize(context);
  basicIO.write(JSON.stringify({ status: 'ok' }));
  context.close();
};

// Event function
module.exports = async (event, context) => {
  const catalystApp = catalyst.initialize(context);
  // event.data = event payload
  context.close();
};

// Cron function
module.exports = async (cronDetails, context) => {
  const catalystApp = catalyst.initialize(context);
  const maxMs = context.getMaxExecutionTimeMs(); // "900000" (STRING) = 15 minutes
  const remainingMs = context.getRemainingExecutionTimeMs(); // decrements as function runs
  context.close();
};

// Job function — MUST use admin scope; USER token is absent in the Job runtime
module.exports = async (jobData, context) => {
  const catalystApp = catalyst.initialize(context, { scope: 'admin' });
  const maxMs = context.getMaxExecutionTimeMs(); // "900000" (STRING) = 15 minutes
  const remainingMs = context.getRemainingExecutionTimeMs(); // decrements as function runs
  context.closeWithSuccess();
};

// Admin scope (bypass row-level permissions)
const adminApp = catalyst.initialize(req, { scope: 'admin' });

// User scope
const userApp = catalyst.initialize(req, { scope: 'user' });

Cron/Job Context APIs:

  • context.getMaxExecutionTimeMs() — Returns "900000" (STRING, not number) for both Cron and Job functions (15-minute limit)
  • context.getRemainingExecutionTimeMs() — Decrements as the function runs; ~500ms startup overhead consumed before handler starts
  • context.closeWithSuccess() / context.closeWithFailure() — Required for Cron/Job functions to signal completion

Data Store

const table = catalystApp.datastore().table('Shipments');

// Insert single row
const row = await table.insertRow({ Name: 'Alice', Email: 'alice@example.com' });
// row.ROWID is the auto-generated unique identifier

// Insert multiple rows
const rows = await table.insertRows([
  { Name: 'Bob', Email: 'bob@example.com' },
  { Name: 'Carol', Email: 'carol@example.com' }
]);

// Get single row by ROWID
const singleRow = await table.getRow('123456000000012345');

// Paginated rows (max 200 per page)
const result = await table.getPagedRows({ nextToken: null, maxRows: 100 });
const data = result.data;
const hasMore = result.more_records;
const nextToken = result.next_token;

// Update row (ROWID required)
const updated = await table.updateRow({ ROWID: '123456000000012345', Name: 'Alice Updated' });

// Delete row
await table.deleteRow('123456000000012345');

// Bulk delete (max 200 per call)
await table.deleteRows(['123456000000012345', '123456000000012346']);

ZCQL

const zcql = catalystApp.zcql();

const rows = await zcql.executeZCQLQuery("SELECT * FROM Shipments WHERE Status = 'Active'");

// INSERT/UPDATE/DELETE via ZCQL
await zcql.executeZCQLQuery("INSERT INTO Shipments (Name, Status) VALUES ('Package A', 'Pending')");
await zcql.executeZCQLQuery("UPDATE Shipments SET Status = 'Shipped' WHERE ROWID = '12345'");
await zcql.executeZCQLQuery("DELETE FROM Shipments WHERE ROWID = '12345'");

// OLAP (aggregations)
const stats = await zcql.executeOLAPQuery('SELECT Status, COUNT(ROWID) AS cnt FROM Shipments GROUP BY Status');

Cache

const segment = catalystApp.cache().segment(segmentId);

await segment.put('key', 'value');                        // default 48h TTL
await segment.put('key', 'value', 1);                     // 1 hour TTL (hours)
const value = await segment.getValue('key');               // string value
const item = await segment.get('key');                     // full cache item
await segment.update('key', 'newValue');
await segment.delete('key');                               // sets to null, doesn't remove key

Stratus (Object Storage)

const bucket = catalystApp.stratus().bucket('your-bucket-name');

// ⚠️ Bucket names are globally unique across ALL Catalyst projects

// List objects
const pagedResult = await bucket.listPagedObjects({ prefix: 'uploads/', maxKeys: 100 });
for await (const obj of bucket.listIterableObjects({ prefix: 'uploads/' })) { ... }

// HEAD (check if exists) — returns true/false boolean
const exists = await bucket.headObject('uploads/file.pdf');
const existsSafe = await bucket.headObject('uploads/file.pdf', { throwErr: false }); // false if missing, true if exists

// Download
const stream = await bucket.getObject('uploads/file.pdf');
stream.pipe(res);

// Upload
const fs = require('fs');
await bucket.putObject('uploads/file.pdf', fs.createReadStream('/path/to/file.pdf'));
// ⚠️ Default overwrite: false — 409 key_already_exists if key exists and versioning is OFF
await bucket.putObject('uploads/file.pdf', fs.createReadStream('/path'), {
  overwrite: true, ttl: 86400,
  metaData: { uploadedBy: 'automation' }
});

// Multipart (for files >= 100 MB) — methods are directly on bucket, no .multipart() wrapper
const initRes = await bucket.initiateMultipartUpload('uploads/huge.mp4');
const uploadId = initRes['upload_id'];  // snake_case, not uploadId
await bucket.uploadPart('uploads/huge.mp4', uploadId, fs.createReadStream('/path/part1'), 1);
await bucket.completeMultipartUpload('uploads/huge.mp4', uploadId);  // no parts array needed

// Pre-signed URLs — requires admin scope; positional: (key, action, options?); returns { signature: url }
// 'GET' action = download-only URL (HTTP GET); 'PUT' action = upload-only URL (HTTP PUT). Cannot cross-use.
const getResult = await bucket.generatePreSignedUrl('uploads/file.pdf', 'GET', { expiryIn: 3600 });
const getUrl = getResult.signature;
const putResult = await bucket.generatePreSignedUrl('uploads/new.pdf', 'PUT', { expiryIn: 3600 });
const putUrl = putResult.signature;

// Delete
await bucket.deleteObject('uploads/file.pdf');
await bucket.deleteObjects([{ key: 'file1.pdf' }, { key: 'file2.pdf' }]);
await bucket.deletePath('uploads/temp/');

// Rename / move
await bucket.renameObject('uploads/old-name.pdf', 'uploads/new-name.pdf');

Auth / User Management

const userManagement = catalystApp.userManagement();

const currentUser = await userManagement.getCurrentUser();  // null for collaborators
const user = await userManagement.getUserDetails(userId);
const allUsers = await userManagement.getAllUsers();
await userManagement.deleteUser(userId);

const newUser = await userManagement.registerUser({
  first_name: 'John', last_name: 'Doe',
  email_id: 'john@example.com',
  role_id: '123456000000007003'
});

Email

await catalystApp.email().sendMail({
  from_email: 'noreply@yourdomain.com',
  to_email: ['recipient@example.com'],
  cc: ['cc@example.com'],
  subject: 'Order Confirmation',
  content: '<h1>Thank you!</h1>',
  attachments: [{ name: 'invoice.pdf', content: fs.createReadStream('/path/invoice.pdf') }]
});

⚠️ Sender domain must be verified before emails are delivered. Since 1 Feb 2026, Catalyst Mail rejects sends from unverified domains — the API call succeeds (no error thrown) but the email is never delivered. Add DKIM and SPF records for your sender domain first: Console → Mail → Sender Domains → Add Domain → follow the DNS verification steps.


NoSQL

const nosql = catalystApp.nosql();
const { NoSQLItem } = require('zcatalyst-sdk-node/lib/no-sql');
const table = nosql.table('SessionStore');

// Build item with typed builder methods — no item.put(); no plain JSON
const item = new NoSQLItem()
  .addString('userId', 'user_001')   // partition key
  .addNumber('loginTime', Date.now());

// insertItems takes an object { item }, NOT an array
await table.insertItems({ item });

// fetchItem (singular) — keys is a NoSQLItem identifying the record
const fetched = await table.fetchItem({
  keys: [new NoSQLItem().addString('userId', 'user_001')]
});

const queryResult = await table.queryTable({
  partitionKey: { name: 'userId', value: 'user_001' },
  sortKey: { name: 'loginTime', operator: 'GREATERTHAN', value: 1700000000000 },
  limit: 50, ascending: true
});
// Operators: EQUALS, BETWEEN, GREATERTHAN, LESSERTHAN, GREATERTHANOREQUALTO, LESSERTHANOREQUALTO

await table.updateItems([item]);
await table.deleteItems([{ partitionKey: 'user_001', sortKey: 1700000000001 }]);

Job Scheduling

const jobScheduling = catalystApp.jobScheduling();
const cron = jobScheduling.cron();

// job_meta defines WHAT to execute — jobpool_name (or jobpool_id) lives here, not at cron level
const jobMeta = {
  job_name: 'process_orders',          // alphanumeric + underscores only, MAX 20 CHARS; hyphens rejected
  target_type: 'Function',             // must be a JOB-type function — cron/basicio/aio targets are rejected
  target_name: 'ProcessOrderFunction', // or use target_id
  jobpool_name: 'OrderPool',           // or jobpool_id
  params: { batchSize: '50' },         // optional; values are delivered to the handler as strings
  job_config: { number_of_retries: 2, retry_interval: 60 } // retry_interval in SECONDS, 60–86400 (runtime-confirmed; ms values are rejected)
};

// OneTime: fires once — time_of_execution is UNIX SECONDS as a string (runtime-confirmed).
// ⚠️ A milliseconds value is stored without error and schedules the cron for year ~58000 — it NEVER fires.
// ⚠️ A past timestamp fires immediately (~35s). OneTime crons auto-disable after firing.
await cron.createCron({
  cron_name: 'one_time_report', cron_status: true,
  cron_type: 'OneTime',
  cron_detail: { time_of_execution: String(Math.floor(Date.now() / 1000) + 3600), timezone: 'Asia/Kolkata' }, // 1h from now
  job_meta: jobMeta
});

// Periodic: repeats every N h/m/s — use cron_detail with repetition_type: 'every'
// ⚠️ NOT schedule: { every, unit } — that shape is wrong and will fail
// ⚠️ Fires IMMEDIATELY on create (and on every update), then every interval (runtime-confirmed)
await cron.createCron({
  cron_name: 'health_check', cron_status: true,
  cron_type: 'Periodic',
  cron_detail: { hour: 0, minute: 15, second: 0, repetition_type: 'every' }, // every 15 min
  job_meta: jobMeta
});

// Calendar daily: fixed time each day — use cron_detail with repetition_type: 'daily'
// ⚠️ Spelling: 'Calendar'. The SDK enum CRON_TYPE.CALENDER ("Calender") is misspelled and the
//    server rejects it with "Mandatory parameter cron_type is missing" (runtime-confirmed).
await cron.createCron({
  cron_name: 'daily_digest', cron_status: true,
  cron_type: 'Calendar',
  cron_detail: { hour: 9, minute: 0, second: 0, repetition_type: 'daily' },
  job_meta: jobMeta
});

// CronExpression — cron_expression sits at the TOP LEVEL, not inside cron_detail
await cron.createCron({
  cron_name: 'custom', cron_status: true,
  cron_type: 'CronExpression',
  cron_expression: '0 */6 * * *',
  cron_detail: { timezone: 'Asia/Kolkata' },
  job_meta: jobMeta
});

// Cron management (id or name accepted) — all runtime-confirmed from deployed function code
await cron.pauseCron(cronId);
await cron.resumeCron(cronId);
await cron.runCron(cronId);     // manual trigger; returns the submitted job's details. Works on disabled crons too.
await cron.deleteCron(cronId);
// ⚠️ SDK-created crons are cron_execution_type "dynamic" and DO NOT appear in the REST/console
//    cron LIST (pre-defined only) — persist the returned cron.id if you'll manage it later.

// Submit an immediate job
// ⚠️ Use job().submitJob({...}) — pass jobpool_name inside the payload
const job = jobScheduling.job();
const submitted = await job.submitJob({
  job_name: 'process_orders',          // alphanumeric + underscores only, ≤ 20 chars
  jobpool_name: 'OrderPool',           // or jobpool_id
  target_type: 'Function',
  target_name: 'ProcessOrderFunction', // or use target_id
  params: { batchSize: '50' },
  job_config: { number_of_retries: 2, retry_interval: 60 } // SECONDS (60–86400)
});
// submitted.job_status: 'PENDING' → 'RUNNING' → 'SUCCESS' | 'FAILURE' (uppercase at runtime).
// Each retry is a NEW job record linked via parent_job_id — the original job's record stays FAILURE.

Circuits

const circuit = catalystApp.circuit();
// Node.js SDK: 3 arguments — circuitId, executionName, inputJSON
const result = await circuit.execute(circuitId, 'execution-name', { key1: 'value1' });

Node.js vs Python SDK difference:

  • Node.js: circuit.execute(circuitId, executionName, inputJSON) — 3 arguments
  • Python: circuit.execute(circuit_id, input_json) — 2 arguments (execution name auto-generated)

executionName is a user-defined string label for this execution (used for tracking and logs).


Connections

const credentials = await catalystApp.connections().getConnectionCredentials('ZohoCRM');
// credentials.access_token = OAuth access token

Search

const results = await catalystApp.search().executeSearchQuery({
  search: 'shipping delayed',
  search_table_columns: { Shipments: ['TrackingNotes'], Orders: ['CustomerName'] }
});

Push Notifications

const webNotif = catalystApp.pushNotification().web();
await webNotif.sendNotification({ message: 'Your order shipped!', recipients: [userId1] });

const mobileNotif = catalystApp.pushNotification().mobile(appId);
await mobileNotif.sendAndroidNotification({ message: 'Update available', recipients: [userId] });
await mobileNotif.sendIOSNotification({ message: 'Update available', recipients: [userId], badge_count: 1 });

Common Errors

Error Cause Fix
getCurrentUser() throws in admin scope getCurrentUser() requires user credentials; admin scope has none Switch to user scope: catalyst.initialize(req) before calling getCurrentUser()
Timeout calculations fail context.getMaxExecutionTimeMs() returns STRING "900000", not number Use parseInt(context.getMaxExecutionTimeMs()) for arithmetic

Source: SKILL.md on GitHub

No alerts16d3 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides reference documentation, initialization patterns, and code examples for Zoho Catalyst SDKs across multiple platforms. It is purely informational and contains no executable code or security risks.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

Signed by skilld at 4a64353. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 weeks ago.

Activeupdated 3 weeks ago
metadata
{
  "version": "2.0.3"
}

README badge

README badge for catalystbyzoho/agent-skills/catalyst-sdk