references/sdk-java.md
> **Supported Java versions:** 8, 11, 17
> **Docs:** https://docs.catalyst.zoho.com/en/sdk/java/v1/overview/
## Maven Setup
```xml
<!-- Repository -->
<repository>
<id>zoho-dl</id>
<url>https://maven.zohodl.com</url>
</repository>
<!-- Dependency -->
<dependency>
<groupId>com.zc</groupId>
<artifactId>zcatalyst-sdk</artifactId>
<version>1.15.0</version>
</dependency>
```
---
## Initialization
```java
// Default (uses request context)
ZCProject.initProject();
// Admin scope
ZCProject adminProject = ZCProject.initProject("admin", ZCUserScope.ADMIN);
// User scope
ZCProject userProject = ZCProject.initProject("user", ZCUserScope.USER);
```
---
## Data Store
```java
ZCObject object = ZCObject.getInstance();
ZCTable table = object.getTable("TableName");
// Insert single row
ZCRowObject row = ZCRowObject.getInstance();
row.set("column_name", "value");
row.set("numeric_column", 123);
ZCRowObject insertedRow = table.insertRow(row);
long rowId = insertedRow.getRowId();
// Insert multiple rows
List<ZCRowObject> rows = new ArrayList<>();
ZCRowObject row1 = ZCRowObject.getInstance();
row1.set("Name", "Alice");
rows.add(row1);
List<ZCRowObject> insertedRows = table.insertRows(rows);
// Get single row
ZCRowObject row = table.getRow(rowId);
String value = row.get("column_name").toString();
// Get all rows (paginated)
List<ZCRowObject> allRows = table.getRows();
ZCRowPagedResponse pagedResponse = table.getPagedRows();
List<ZCRowObject> currentPage = pagedResponse.getCurrentPageData();
boolean hasNext = pagedResponse.hasNextPage();
ZCRowPagedResponse nextPage = pagedResponse.getNextPage();
// Update row (ROWID required)
ZCRowObject updateRow = ZCRowObject.getInstance();
updateRow.set("ROWID", rowId);
updateRow.set("column_name", "updated_value");
ZCRowObject updatedRow = table.updateRow(updateRow);
// Delete row
table.deleteRow(rowId);
```
---
## ZCQL
```java
ZCQL zcql = ZCQL.getInstance();
// Basic query
List<ZCRowObject> results = zcql.executeQuery("SELECT * FROM TableName WHERE column = 'value'");
// V2 query
List<ZCRowObject> results = zcql.executeQuery("SELECT * FROM TableName", true);
// OLAP query
List<ZCRowObject> stats = zcql.executeQuery("SELECT COUNT(*) FROM TableName", true, true);
```
---
## Cache
```java
ZCCache cache = ZCCache.getInstance();
ZCSegment segment = cache.getSegment(segmentId);
// Put with expiry (milliseconds)
ZCCacheObject cacheObject = segment.put("cacheKey", "cacheValue", 3600000L);
// Get
ZCCacheObject cacheObject = segment.get("cacheKey");
String value = cacheObject.getValue();
// Update
ZCCacheObject updated = segment.update("cacheKey", "newValue", 7200000L);
// Delete
segment.delete("cacheKey");
```
---
## Logging in Java Functions
```java
import java.util.logging.Logger;
private static final Logger LOGGER = Logger.getLogger(MyFunction.class.getName());
LOGGER.info("Processing request"); // INFO
LOGGER.warning("Potential issue"); // WARNING
LOGGER.severe("Critical error"); // ERROR
LOGGER.fine("Debug details"); // DEBUG
// Structured logging
LOGGER.info("{\"action\":\"createUser\",\"userId\":\"12345\"}");
```
---
## SmartBrowz — PDF & Screenshot (Java)
```java
import com.zc.component.smartbrowz.ZCSmartBrowz;
import com.zc.component.smartbrowz.ZCSmartBrowzConvertDetails;
import com.zc.component.smartbrowz.ZCSmartBrowzPDFOptions;
import com.zc.component.smartbrowz.ZCSmartBrowzNavigationOptions;
// Initialize SmartBrowz (static getInstance, not catalystApp)
ZCSmartBrowz smartBrowz = ZCSmartBrowz.getInstance();
// Convert HTML to PDF
ZCSmartBrowzConvertDetails convertDetails = ZCSmartBrowzConvertDetails.getInstance();
ZCSmartBrowzPDFOptions pdfOptions = ZCSmartBrowzPDFOptions.getInstance();
pdfOptions.setFormat("A4");
pdfOptions.setPrintBackground(true);
ZCSmartBrowzNavigationOptions navigationOptions = new ZCSmartBrowzNavigationOptions();
navigationOptions.setWaitUntil("domcontentloaded");
navigationOptions.setTimeout(30000);
convertDetails.setHtml("<html><body><h1>Hello</h1></body></html>");
convertDetails.setPdfDetails(pdfOptions);
convertDetails.setNavigationDetails(navigationOptions);
InputStream outputStream = smartBrowz.convertToPdf(convertDetails);
// Generate from template
ZCSmartBrowzTemplateOptions templateOptions = ZCSmartBrowzTemplateOptions.getInstance();
templateOptions.setTemplateId(2075000000021001L);
templateOptions.setOutputType(ZC_CONVERT_OUTPUT_TYPE.PDF);
templateOptions.setPdfDetails(pdfOptions);
templateOptions.setNavigationDetails(navigationOptions);
InputStream templateOutput = smartBrowz.generateFromTemplate(templateOptions);
```
### Browser Logic Function (Java — Selenium pre-initialized)
```java
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.openqa.selenium.chrome.ChromeDriver;
import com.catalyst.browserlogic.SeleniumHandler;
import org.json.simple.JSONObject;
public class BrowserLogicExample implements SeleniumHandler {
@Override
public void runner(HttpServletRequest request, HttpServletResponse response,
ChromeDriver driver) throws Exception {
JSONObject responseData = new JSONObject();
driver.get("https://www.example.com");
responseData.put("message", "Title: " + driver.getTitle());
response.setContentType("application/json");
response.getWriter().write(responseData.toString());
response.setStatus(200);
}
}
```
> `ChromeDriver driver` is injected by SmartBrowz — do not connect to the browser manually.
---
## APM — Application Performance Monitoring
APM is available for Java functions (and Node.js). Python is NOT supported.
**What it shows:**
- Invocation count, success/failure breakdown
- P50/P95/P99 response times
- Top 100 slowest executions with full traces
- Breakdown of SDK calls (DataStore, Cache, etc.)
## Common Errors
| Error | Cause | Fix |
|-------|-------|-----|
| `NullPointerException` on `ZCatalystApp.getInstance()` | SDK not initialized before use | Call `ZCatalystApp.initializeApp(context)` in `Application.onCreate()` before any SDK call |
| `UnauthorizedException` in Job/Cron function | SDK initialized without admin scope | Use `ZCatalystApp.initializeApp(context, ZCatalystApp.RequestScope.ADMIN)` for background functions |
| `ClassNotFoundException` for Catalyst classes | Dependency not included in `pom.xml` / `build.gradle` | Add `zcatalyst-sdk-java` dependency; ensure JAR is in the function's `lib/` directory for deployed functions |
| DataStore query returns empty result set | Table name or column name case mismatch | Table and column names in ZCQL are case-sensitive; verify in Console → Data Store |
references/sdk-mobile.md
## Android SDK (Kotlin) v3
### Setup (Gradle)
```groovy
// project-level build.gradle
allprojects {
repositories {
maven { url "https://maven.zohodl.com" }
}
}
// app-level build.gradle
dependencies {
implementation 'com.zoho.catalyst:catalyst-android-sdk:3.+'
}
```
**AndroidManifest.xml:**
```xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
```
**strings.xml:**
```xml
<string name="url_scheme">zc-YOUR_PROJECT_ID</string>
```
Place `AppConfigurationData.plist` (downloaded from console) in `app/src/main/assets/`.
### Init & Auth
```kotlin
ZCatalystApp.init(context, ZCatalystEnvironment.DEVELOPMENT)
val app = ZCatalystApp.getInstance()
// Sign Up
app.signup(firstName, lastName, email, object : ZCatalystCallback<Void> {
override fun onSuccess(result: Void?) { }
override fun onFailure(exception: ZCatalystException) { }
})
// Login
app.login(activity, object : ZCatalystCallback<Void> {
override fun onSuccess(result: Void?) { }
override fun onFailure(exception: ZCatalystException) { }
})
// Logout
app.logout(object : ZCatalystCallback<Void> { ... })
// Get Current User
app.getCurrentUser(object : ZCatalystCallback<ZCatalystUser> {
override fun onSuccess(user: ZCatalystUser) {
val email = user.emailId
val name = user.firstName
}
override fun onFailure(exception: ZCatalystException) { }
})
val signedIn = app.isUserSignedIn()
```
### Data Store
```kotlin
val dataStore = ZCatalystApp.getInstance().getDataStoreInstance()
val table = dataStore.getTableInstance("Users")
// Create rows
val row = ZCatalystRow()
row.setColumnValue("Name", "Alice")
table.createRows(listOf(row), object : ZCatalystCallback<List<ZCatalystRow>> {
override fun onSuccess(rows: List<ZCatalystRow>) { }
override fun onFailure(exception: ZCatalystException) { }
})
// Get rows
table.getRows(object : ZCatalystCallback<List<ZCatalystRow>> {
override fun onSuccess(rows: List<ZCatalystRow>) {
for (row in rows) { val name = row.getColumnValue("Name") }
}
override fun onFailure(exception: ZCatalystException) { }
})
// Update rows
row.setColumnValue("ROWID", "12345")
row.setColumnValue("Age", 31)
table.updateRows(listOf(row), object : ZCatalystCallback<List<ZCatalystRow>> { ... })
// Delete row
table.deleteRow("12345", object : ZCatalystCallback<Void> { ... })
```
---
## iOS SDK (Swift) v2
### Setup (CocoaPods)
```ruby
pod 'ZCatalyst', :git => 'https://github.com/nicetomeetyou/ZCatalyst.git', :tag => '2.2.2'
```
**Info.plist:**
```xml
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array><string>zc-YOUR_PROJECT_ID</string></array>
</dict>
</array>
```
### Init & Handle Redirects
```swift
import ZCatalyst
// AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions ...) -> Bool {
ZCatalystApp.shared.initSDK()
return true
}
// Handle login redirects (AppDelegate)
func application(_ app: UIApplication, open url: URL, options: ...) -> Bool {
return ZCatalystApp.shared.handleLoginRedirection(for: url)
}
// SceneDelegate (iOS 13+)
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url {
ZCatalystApp.shared.handleLoginRedirection(for: url)
}
}
```
### Auth
```swift
// Sign Up
ZCatalystApp.shared.signup(firstName: "Alice", lastName: "Smith", email: "alice@example.com") { result in
switch result {
case .success: print("Signup successful")
case .failure(let error): print("Error: \(error)")
}
}
// Login
ZCatalystApp.shared.login(presentingViewController: self) { result in ... }
// Logout
ZCatalystApp.shared.logout { result in ... }
// Get Current User
ZCatalystApp.shared.getCurrentUser { result in
switch result {
case .success(let user): print(user.emailId, user.firstName)
case .failure(let error): print("Error: \(error)")
}
}
let signedIn = ZCatalystApp.shared.isUserSignedIn()
```
### Data Store
```swift
let table = ZCatalystApp.shared.getDataStoreInstance().getTableInstance(name: "Users")
// Get rows
table.getRows { result in
switch result {
case .success(let rows):
for row in rows { let name = row.getValue(forColumn: "Name") }
case .failure(let error): print("Error: \(error)")
}
}
// Create row
var row = ZCatalystRow()
row.setColumnValue("Name", forColumn: "Name")
table.createRow(row) { result in ... }
// Delete row
table.deleteRow(id: "12345") { result in ... }
```
---
## Flutter SDK (Dart) v2
### Setup
```yaml
dependencies:
zcatalyst_sdk: ^2.2.1
```
```dart
import 'package:zcatalyst_sdk/zcatalyst_sdk.dart';
await ZCatalystApp.init();
// Or with custom config:
await ZCatalystApp.init(
config: SDKConfigs(
projectId: "YOUR_PROJECT_ID",
environment: Environment.development,
),
);
```
### Auth
```dart
final app = ZCatalystApp.getInstance();
// Sign Up
final (success, error) = await app.signup(
firstName: "Alice", lastName: "Smith", email: "alice@example.com",
);
await app.login();
await app.logout();
final isLoggedIn = await app.isUserLoggedIn();
final user = await app.getCurrentUser();
print(user.emailId, user.firstName);
```
### Data Store
```dart
final table = ZCatalystApp.getInstance().getDataStoreInstance().getTableInstance("Users");
final rows = await table.getRows();
final row = await table.getRow("12345");
final newRow = await table.createRow({"Name": "Alice", "Age": 30});
final newRows = await table.createRows([{"Name": "Alice"}, {"Name": "Bob"}]);
final updatedRow = await table.updateRow({"ROWID": "12345", "Age": 31});
await table.deleteRow("12345");
```
### ZCQL Query Builder (Flutter)
```dart
final zcql = ZCatalystApp.getInstance().getZCQLInstance();
// Simple query
final results = await zcql.executeQuery("SELECT * FROM Users");
// Type-safe Query Builder
final query = ZCatalystQueryBuilder()
.select(["Name", "Age", "Email"])
.from("Users")
.where("Age", ">", 25)
.and("Name", "LIKE", "%Alice%")
.orderBy("Name", ascending: true)
.limit(50)
.build();
final results = await zcql.executeQuery(query);
// JOIN query
final joinQuery = ZCatalystQueryBuilder()
.select(["Users.Name", "Orders.Total"])
.from("Users")
.innerJoin("Orders", "Users.ROWID", "Orders.UserId")
.build();
```
### Functions (Flutter)
```dart
final functions = ZCatalystApp.getInstance().getFunctionsInstance();
final getResult = await functions.executeGET("functionName", queryParams: {"key": "value"});
final postResult = await functions.executePOST("functionName", body: {"key": "value"});
final putResult = await functions.executePUT("functionName", body: {"key": "value"});
final deleteResult = await functions.executeDELETE("functionName", queryParams: {"id": "123"});
```
### File Store (Deprecated) & Stratus (Flutter)
> **⚠️ File Store is deprecated.** Not available for accounts created after August 27, 2025. Use Stratus for new projects.
```dart
// File Store — migration reference only, do not use for new projects
final folder = ZCatalystApp.getInstance().getFileStoreInstance().getFolderInstance("folderId");
final uploadedFile = await folder.uploadFile(file);
await folder.downloadFile("fileId", savePath: "/path/file.txt", onProgress: (received, total) { });
await folder.deleteFile("fileId");
// Stratus
final bucket = ZCatalystApp.getInstance().getStratusInstance().bucket("bucket-name");
final obj = await bucket.getObject("path/to/file.txt");
await bucket.uploadObject("path/to/file.txt", file);
await bucket.deleteObjects(["file1.txt", "file2.txt"]);
await bucket.deletePath("path/to/folder/");
```
### Error Handling (Flutter)
```dart
try {
final rows = await table.getRows();
} on ZCatalystException catch (e) {
print("Error code: ${e.code}");
print("Error message: ${e.message}");
print("HTTP status: ${e.httpStatusCode}");
}
```
## Common Errors
| Error | Cause | Fix |
|-------|-------|-----|
| Login screen not returning to app (Android) | OAuth redirect URI not registered | Add the Catalyst redirect URI to your Android manifest `<intent-filter>` |
| `ZCatalystException: Unauthorized` on DataStore | SDK initialized without admin scope in background task | Use admin-scoped init for WorkManager / background jobs |
| iOS `openURL` not called after login | Missing `application(_:open:options:)` implementation | Implement the URL handler in `AppDelegate` to complete the OAuth callback |
| Flutter `ZCatalystApp.getInstance()` returns null | `await ZCatalystApp.initializeApp(...)` not awaited before use | Ensure `initializeApp` completes before any SDK call (use `await` in `main()`) |
| Row update silently fails on Android | Row ID passed as `String` instead of `Long` | Cast row ID to `Long` before passing to `updateRow()` |
references/sdk-nodejs.md
Install: `npm install zcatalyst-sdk-node`
> Use version 2.5.0 or later. All earlier versions are deprecated.
---
## Initialization
```javascript
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
```javascript
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
```javascript
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
```javascript
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)
```javascript
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
```javascript
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
```javascript
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
```javascript
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
```javascript
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; hyphens rejected
target_type: 'Function',
target_name: 'ProcessOrderFunction', // or use target_id
jobpool_name: 'OrderPool', // or jobpool_id
params: { batchSize: 50 }, // optional
job_config: { number_of_retries: 2, retry_interval: 15 * 60 * 1000 } // number, NOT String()
};
// OneTime: fires once — time_of_execution is UNIX timestamp in ms, passed as a string
await cron.createCron({
cron_name: 'one_time_report', cron_status: true,
cron_type: 'OneTime',
cron_detail: { time_of_execution: Date.now() + (60 * 60 * 1000) + '' }, // 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
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'
// ⚠️ NOT schedule: { time, timezone, days_of_week } — that shape is wrong and will fail
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
await cron.createCron({
cron_name: 'custom', cron_status: true,
cron_type: 'CronExpression',
cron_detail: { cron_expression: '0 */6 * * *' },
job_meta: jobMeta
});
// Cron management
await cron.pauseCron(cronId);
await cron.resumeCron(cronId);
await cron.runCron(cronId); // manual trigger
await cron.deleteCron(cronId);
// Submit an immediate job
// ⚠️ Use job().submitJob({...}) — pass jobpool_name inside the payload
// retry_interval is a number (ms), NOT String()
const job = jobScheduling.job();
await job.submitJob({
job_name: 'process_orders', // alphanumeric + underscores only
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: 15 * 60 * 1000 } // number, NOT String()
});
```
---
## Circuits
```javascript
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
```javascript
const credentials = await catalystApp.connections().getConnectionCredentials('ZohoCRM');
// credentials.access_token = OAuth access token
```
---
## Search
```javascript
const results = await catalystApp.search().executeSearchQuery({
search: 'shipping delayed',
search_table_columns: { Shipments: ['TrackingNotes'], Orders: ['CustomerName'] }
});
```
---
## Push Notifications
```javascript
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 |
```
references/sdk-python.md
Install: `pip install zcatalyst-sdk`
Requires **Python 3.9+**.
---
## Initialization
```python
import zcatalyst_sdk
# Advanced I/O (Flask)
catalyst_app = zcatalyst_sdk.initialize(req=request)
# Basic I/O
catalyst_app = zcatalyst_sdk.initialize(req=context)
# Event / Cron functions
catalyst_app = zcatalyst_sdk.initialize(req=context)
# Job functions — MUST use admin scope; USER token is absent in the Job runtime
catalyst_app = zcatalyst_sdk.initialize(req=context, scope='admin')
# Admin scope (any function type)
admin_app = zcatalyst_sdk.initialize(req=request, scope='admin')
```
---
## Data Store
```python
table = catalyst_app.datastore().table("TableName")
# Insert single row
row = table.insert_row({"Name": "Alice", "Email": "alice@example.com"})
# Insert multiple rows
rows = table.insert_rows([
{"Name": "Bob", "Email": "bob@example.com"},
{"Name": "Carol", "Email": "carol@example.com"}
])
# Get single row by ROWID
row = table.get_row(row_id)
# Get paged rows
result = table.get_paged_rows(next_token="token", max_rows=200)
rows = result["data"]
has_more = result["more_records"]
next_token = result["next_token"]
# Update row (ROWID required)
updated_row = table.update_row({"ROWID": "123456000000012345", "Name": "Alice Updated"})
# Delete row
table.delete_row(row_id)
```
---
## ZCQL
```python
zcql_service = catalyst_app.zcql()
rows = zcql_service.execute_query("SELECT * FROM TableName WHERE Name = 'Alice'")
result = zcql_service.execute_olap_query("SELECT COUNT(ROWID) FROM TableName GROUP BY Status")
```
---
## Cache
```python
segment = catalyst_app.cache().segment(segment_id)
segment.put("my_key", "my_value", expiry=3600000) # expiry in ms
value = segment.get("my_key")
segment.update("my_key", "new_value", expiry=7200000)
segment.delete("my_key") # sets to null, doesn't truly delete
```
---
## Stratus
```python
stratus_service = catalyst_app.stratus()
bucket = stratus_service.bucket(bucket_name)
buckets = stratus_service.list_buckets()
details = bucket.get_details()
objects = bucket.list_objects(prefix="folder/", max_keys=100)
with open("/path/to/file.txt", "rb") as f:
bucket.upload_object("folder/file.txt", f, content_type="text/plain")
content = bucket.download_object("folder/file.txt")
bucket.delete_object("folder/file.txt")
bucket.rename_object("folder/old_name.txt", "folder/new_name.txt")
```
---
## Auth
```python
auth_service = catalyst_app.authentication()
# Register user
result = auth_service.register_user(
{"platform_type": "web", "zaid": "your_zaid"},
{"first_name": "Alice", "last_name": "Smith", "email_id": "alice@example.com"}
)
# Get current user details
user = auth_service.get_user_details()
# Delete user
auth_service.delete_user(user_id)
```
---
## Email
```python
catalyst_app.email().send_mail({
"from_email": "noreply@yourdomain.com",
"to_email": ["recipient@example.com"],
"cc": ["cc@example.com"],
"subject": "Hello from Catalyst",
"content": "<h1>Welcome!</h1>",
"html_mode": True
})
```
---
## Search
```python
result = catalyst_app.search().execute_search_query(
"search term",
search_config={"search_table_columns": {"TableName": ["Col1", "Col2"]}}
)
```
---
## Connections
```python
credentials = catalyst_app.connections().get_connection_credentials({
"connection_name": "my_connection"
})
# credentials["access_token"] = OAuth token
```
---
## Circuits
```python
result = catalyst_app.circuit().execute(circuit_id, {"key1": "value1"})
```
---
## NoSQL
```python
nosql_service = catalyst_app.nosql()
table = nosql_service.table("NoSQLTableName")
table.insertItems([{"pk": "partition1", "sk": "sort1", "data": "value1"}])
items = table.fetchItems([{"pk": "partition1", "sk": "sort1"}])
results = table.queryTable({"pk": "partition1", "query": {"condition": "sk BEGINS_WITH 'sort'", "limit": 10}})
table.updateItems([{"pk": "partition1", "sk": "sort1", "update_expression": "SET data = :val", "expression_values": {":val": "updated"}}])
table.deleteItems([{"pk": "partition1", "sk": "sort1"}])
```
---
## Job Scheduling
```python
pool = catalyst_app.job_scheduling().pool(pool_id)
cron = pool.create_cron({
"cron_name": "daily_report",
"target_function": "generate_report",
"cron_type": "calendar",
"cron_expression": "0 9 * * *",
"params": {"report_type": "daily_summary"}
})
```
---
## Push Notifications
```python
push_service = catalyst_app.pushnotification()
push_service.sendNotification({
"subject": "New Update",
"message": "A new feature has been released.",
"recipients": ["user_id_1", "user_id_2"]
})
```
---
## Zia Services
```python
zia_service = catalyst_app.zia()
with open("document.png", "rb") as f:
ocr_result = zia_service.extractOpticalCharacters(f, {"language": "eng", "model_type": "OCR"})
sentiment = zia_service.getSentimentAnalysis(["I love this!", "Terrible experience."])
entities = zia_service.getNamedEntityRecognition(["Zoho Corporation is in Chennai, India."])
keywords = zia_service.getKeywordExtraction(["Catalyst is a serverless platform."])
analytics = zia_service.getAllTextAnalytics(["Zoho Catalyst makes development easy."])
with open("image.jpg", "rb") as f:
moderation = zia_service.moderateImage(f)
faces = zia_service.detectFaces(f)
objects = zia_service.recognizeObjects(f)
with open("barcode.png", "rb") as f:
barcode = zia_service.scanBarcode(f)
```
---
## SmartBrowz
```python
smart_browz = catalyst_app.smart_browz()
pdf = smart_browz.convert_to_pdf({
"url": "https://example.com",
"pdf_options": {"format": "A4", "print_background": True},
"navigation_options": {"wait_until": "networkidle0", "timeout": 30000}
})
screenshot = smart_browz.take_screenshot({
"url": "https://example.com",
"screenshot_options": {"full_page": True, "type": "png"},
"navigation_options": {"wait_until": "networkidle2", "timeout": 60000}
})
output = smart_browz.generate_from_template(
"153000000009001", # template_id
template_data={"name": "Alice", "amount": "$100"},
output_options={"output_type": "pdf"}
)
```
> ⚠️ APM (Application Performance Monitoring) is NOT available for Python functions. Use logs only for Python performance monitoring.
---
## Common Errors
| Error | Cause | Fix |
|-------|-------|-----|
| DataStore methods hang silently in Job functions | `zcatalyst_sdk` Table methods (`get_paged_rows`, `delete_rows`, `insert_rows`, etc.) use `CredentialUser.USER` internally. Job functions have no USER token — every call makes an unauthenticated request, waits 60 s per attempt, raises no exception, and silently burns toward the 15-minute timeout | Initialize with `scope='admin'`: `zcatalyst_sdk.initialize(req=context, scope='admin')` |
references/sdk-web.md
## Setup
```html
<script src="https://static.zohocdn.com/catalyst/sdk/js/4.6.1/catalystWebSDK.js"></script>
<script src="/__catalyst/sdk/init.js"></script>
```
Load `init.js` **after** `catalystWebSDK.js`. `init.js` auto-initializes the SDK.
### client-package.json
| Framework | Location |
|-----------|---------|
| Vite / React / Vue | `public/client-package.json` |
| Next.js | `public/client-package.json` |
| Angular | `src/assets/client-package.json` |
| Legacy Web Client | `client/client-package.json` |
```json
{
"name": "my-app",
"version": "1.0.0",
"homepage": "/",
"login_redirect": "/"
}
```
> ⚠️ Do NOT place in project root — it won't be copied to build output.
> Use `/` for Slate apps; `/app/index.html` is for legacy Web Client Hosting only.
### Version Compatibility
| Feature | Min SDK |
|---------|---------|
| Core SDK | v4.0.0 |
| `changePassword()` | v4.3.0 |
| `isUserAuthenticated()` | v4.5.0 |
| `generateAuthToken()` | v4.6.1 |
---
## Authentication
### Type 1: Hosted Login (Redirect-Based)
**Prerequisite:** Console → Authentication → Login → enable Hosted Authentication
```javascript
// Check auth + get user details
catalyst.auth.isUserAuthenticated()
.then(result => {
// result.content = full user object
console.log(result.content.email_id, result.content.first_name);
showApp(result.content);
})
.catch(() => {
// SDK does NOT auto-redirect — you must do this
window.location.href = '/__catalyst/auth/login';
});
```
> ⚠️ `catalyst.auth.getCurrentUser()` does NOT exist. Use `isUserAuthenticated()`.
### Type 2: Embedded Login (iFrame)
```javascript
catalyst.auth.signIn("login-div", { login_redirect: "/" });
catalyst.auth.signUp("signup-div");
catalyst.auth.forgotPassword("forgot-div");
catalyst.auth.changePassword("change-pwd-div"); // v4.3.0+
```
### isUserAuthenticated() (v4.5.0+)
```javascript
try {
const result = await catalyst.auth.isUserAuthenticated();
// ⚠️ Returns full USER OBJECT on success (not a boolean)
console.log(result.content.email_id); // "user@example.com"
console.log(result.content.user_id); // "10103000000115057"
console.log(result.content.first_name); // "John"
} catch (err) {
// Rejects with 401 when NOT authenticated
window.location.href = '/__catalyst/auth/login';
}
```
### Sign Out
```javascript
// ⚠️ REQUIRED: pass redirect URL — crashing without it
catalyst.auth.signOut(window.location.origin);
// For legacy Web Client:
// catalyst.auth.signOut(window.location.origin + '/app/index.html');
// ⚠️ Does NOT return a promise — do NOT await it
// ⚠️ constructSignOutUrl() does NOT exist
```
### generateAuthToken() (v4.6.1+)
For cross-domain calls (Slate → Functions or AppSail):
> ⚠️ `generateAuthToken()` requires an active session. Without one it makes two HTTP calls (Catalyst backend + Zoho IAM) before rejecting with a cryptic IAM error — not a clear "not authenticated" message. Always call `isUserAuthenticated()` first:
```javascript
try {
await catalyst.auth.isUserAuthenticated(); // fast 401 if not signed in
const tokenRes = await catalyst.auth.generateAuthToken();
const token = tokenRes.access_token; // NOT tokenRes.content.token
} catch (err) {
// redirect to login
window.location.href = '/__catalyst/auth/login';
}
```
---
## Calling Functions from Slate (Cross-Domain)
> Slate (`*.onslate.com`) and Functions (`*.catalystserverless.com`) are different domains.
> **Relative paths DO NOT work from Slate.** `/server/func/execute` resolves to Slate's domain.
```javascript
const FUNCTION_URL = 'https://{project-domain}.development.catalystserverless.com/server/{func_name}/execute';
async function callFunction(path, method = 'GET', body = null) {
const tokenRes = await window.catalyst.auth.generateAuthToken();
const token = tokenRes.access_token; // NOT .content.token
const options = {
method,
headers: {
'Authorization': token, // Raw token, no prefix
'Content-Type': 'application/json'
}
};
if (body && method !== 'GET') options.body = JSON.stringify(body);
const url = path.startsWith('http') ? path : `${FUNCTION_URL}${path}`;
const res = await fetch(url, options);
return res.json();
}
```
**Required setup:** Console → Authentication → Whitelisting → Authorized Domains → add your Slate domain → enable CORS toggle.
> ⚠️ Do NOT add CORS headers in function code for production origins — the Catalyst gateway injects them automatically. Adding both causes duplicate headers which browsers reject.
**CORS for localhost only (in Express function):**
```javascript
app.use((req, res, next) => {
const origin = req.headers.origin || '';
if (/^http:\/\/localhost(:\d+)?$/.test(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') return res.status(204).end();
}
next();
});
```
---
## Data Store
```javascript
const table = catalyst.table.tableId('TableName');
const allRows = await table.getAll();
const pagedRows = await table.getPagedRows({ nextToken: null, maxRows: 100 });
const columns = await table.getColumns();
const newRow = await table.addRow({ column1: 'value1', column2: 'value2' });
const updated = await table.updateRow({ ROWID: '12345', column1: 'new_value' });
await table.delete('12345');
await table.deleteRows(['12345', '12346']);
```
---
## ZCQL
```javascript
const zcql = catalyst.ZCatalystQL;
const result = await zcql.executeQuery("SELECT * FROM Users WHERE age > 25");
console.log(result.content);
// V2 features
catalyst.ZCatalystQL.setCatalystEnv("V2");
```
---
## Stratus
```javascript
const bucket = catalyst.stratus.bucket("bucket-name");
const head = await bucket.headObject("path/to/file.txt");
const obj = await bucket.getObject("path/to/file.txt", { signedUrl: true });
const file = document.getElementById("file-input").files[0];
await bucket.putObject("path/to/file.txt", file);
await bucket.uploadObject("path/large.zip", file, { partSize: 5 * 1024 * 1024 });
await bucket.deleteObject("path/to/file.txt");
```
---
> **⚠️ Deprecated — migration reference only.** File Store is not available to accounts created after August 27, 2025. For new projects, use **Stratus** (see the Stratus section above). The code below is retained only to help migrate existing integrations.
## File Store (Deprecated)
```javascript
const fileStore = catalyst.file;
const folders = await fileStore.getAllFolder();
const folder = fileStore.folderId("folderId");
const fileInput = document.getElementById("file-input");
const uploaded = await folder.uploadFile(fileInput.files[0]);
const downloadLink = await folder.getDownloadLink("fileId");
await folder.delete("fileId");
```
---
## Search
```javascript
const results = await catalyst.search.executeSearchQuery("search term");
console.log(results.content);
```
---
## Push Notifications
```javascript
await catalyst.push.sendNotification({
message: "Hello!",
recipients: ["user@example.com"]
});
```
---
## Functions
```javascript
const result = await catalyst.function.execute("functionName", {
key1: "value1", key2: "value2"
});
console.log(result.content);
```
---
## Environment Variables
```javascript
const value = await catalyst.env.getValue("MY_ENV_VAR");
const allVars = await catalyst.env.getAll();
```
---
## Common Errors
| Symptom | Cause | Fix |
|---------|-------|-----|
| `api_domain` is empty | `init.js` not loaded or wrong order | Load `catalystWebSDK.js` first, then `init.js` |
| `isUserAuthenticated` always fails locally | SDK < v4.5.0 | Upgrade SDK |
| `generateAuthToken is not a function` | SDK < v4.6.1 | Upgrade to v4.6.1+ |
| Duplicate CORS headers / preflight fails | Express `cors()` + Authorized Domains both set header | Remove Express CORS for prod origins; only set for localhost |
| `signOut()` crashes | Called without redirect URL | `catalyst.auth.signOut(window.location.origin)` |
| `getCurrentUser is not a function` | Method doesn't exist | Use `catalyst.auth.isUserAuthenticated()` |
| Embedded iFrame won't load | Div ID mismatch or CSP | Verify div id, check CSP allows Zoho iFrame origins |
| `/__catalyst/auth/login` returns 404 | Hosted Auth not enabled | Console → Authentication → Login → enable Hosted Auth |
| `isUserAuthenticated` rejects but nothing happens | SDK doesn't auto-redirect | Add `window.location.href = '/__catalyst/auth/login'` in catch |
| `Unexpected token '<'` in fetch response | Relative path used from Slate | Use full URL with `generateAuthToken()` |
SKILL.md
---
name: catalyst-sdk
description: "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'."
metadata:
version: "2.0.2"
---
## How It Works
1. **Identify the platform** — Node.js, Web (browser), Python, Java, Android, iOS, or Flutter.
2. **Load the matching reference file** — Each platform has its own SDK reference with initialization pattern and service methods.
3. **Initialization first** — Always show the SDK init call before any service-specific code.
4. **Platform quirks** — Web SDK uses browser auth (no service account); Node.js SDK uses server-side init; Mobile SDKs require the Catalyst project ID in the config.
## Triggers
Use this skill for: "SDK", `zcatalyst-sdk-node`, `zcatalyst-sdk`, "Node.js SDK", "Web SDK", "Python SDK", "Java SDK", "Android SDK", "iOS SDK", "Flutter SDK", `sdk-web`, `sdk-mobile`, `catalyst.initialize`, `catalystApp`, "initialize SDK", or any platform-specific SDK question.
## References
| Reference | Load when the query is about… |
|-----------|-------------------------------|
| `references/sdk-nodejs.md` | Node.js SDK — DataStore, ZCQL, Cache, Stratus (multipart, signed URLs), Auth, Email, NoSQL, Job Scheduling, Circuits, Connections, Search, Push Notifications |
| `references/sdk-web.md` | Web SDK v4 — browser-side auth (hosted/embedded login), generateAuthToken, isUserAuthenticated, signOut, DataStore, Stratus, cross-domain Slate→function pattern |
| `references/sdk-python.md` | Python SDK — DataStore, ZCQL, Cache, Stratus, Auth, Email, Search, Connections, NoSQL, Push Notifications, Job Scheduling, Circuits, Zia, SmartBrowz |
| `references/sdk-java.md` | Java SDK — Maven setup, DataStore, ZCQL, Cache, SmartBrowz (Selenium, PDF) |
| `references/sdk-mobile.md` | Android (Kotlin), iOS (Swift), Flutter (Dart) — auth, DataStore, ZCQL, Stratus, Push Notifications, Search |