Build a custom capability

Choose JSON, ConnectorBuilder, custom execution, or Postman conversion to define a capability.

Package-name note: These approved examples use cliodot-flosync. Other supplied SDK material uses cliodot or @cliodot/flosync; confirm the current package before copying an import.

This guide explains how to build capabilities in all supported ways: JSON, ConnectorBuilder (SDK chaining), custom execute (local), and Postman conversion. Each method includes building, registering/pushing, and using the capability.

Prerequisites#

  • FlowSync API access with connectors.write permission
  • API credentials: apiKey and apiSecret (from user registration)
  • JWT token obtained via POST /api-core/cliodot/user/login-with-api-key

Overview#

Method Build Register/Push Use
JSON JSON file Push to server (cURL/SDK) Workflows, client.connectors.execute()
ConnectorBuilder SDK chaining Push to server Workflows, client.connectors.execute()
Custom Execute Define with execute function flosync.registerConnector() (local only) Workflows (local runs)
Postman Convert collection Push to server Workflows, client.connectors.execute()

API Base Path#

Text
https://your-api-domain.com/api-core/cliodot/connectors

Method 1: JSON Definition#

Create a JSON file matching the capability schema. Required: name, type, auth. For REST: base_url, endpoints.

Build (connectors/my-api.json):

JSON
{
 "_id": "my-api",
 "name": "My API",
 "slug": "my-api",
 "description": "Custom API connector",
 "type": "REST",
 "base_url": "https://api.example.com/v1",
 "auth": {
   "type": "bearer",
   "token": "",
   "header_name": "Authorization",
   "prefix": "Bearer "
 },
 "endpoints": [
   {
     "name": "Get Item",
     "method": "GET",
     "path": "/items/{{ pathParams.id }}",
     "pathParams": { "id": "string" }
   },
   {
     "name": "Create Item",
     "method": "POST",
     "path": "/items",
     "headers": { "Content-Type": "application/json" },
     "body_schema": { "name": "string", "value": "number" }
   }
 ],
 "meta": {
   "category": "Custom",
   "logo": "https://via.placeholder.com/64",
   "status": "active",
   "visibility_settings": { "visibility": "private", "public_access": "none" },
   "last_updated": "2025-01-27T00:00:00Z"
 }
}

Push (cURL):

Bash
curl -X POST https://your-api-domain.com/api-core/cliodot/connectors \
 -H "Authorization: Bearer YOUR_JWT" \
 -H "Content-Type: application/json" \
 -d @connectors/my-api.json

Register (SDK):

JavaScript
const { FlosyncClient } = require('cliodot-flosync');
const connectorDef = require('./connectors/my-api.json');

const client = new FlosyncClient({ baseUrl, apiKey, apiSecret });
await client.authenticate();
await client.connectors.push(connectorDef);
await client.connectors.install('my-api', { auth: { token: process.env.MY_API_TOKEN } });

Use (workflow):

JavaScript
s.connector('my-api', 'Get Item', { pathParams: { id: '123' } });
s.connector('my-api', 'Create Item', { body: { name: 'Widget', value: 99 } });

Use (direct execute):

JavaScript
const result = await client.connectors.execute('my-api', 'Get Item', { pathParams: { id: '123' } });

Method 2: ConnectorBuilder (SDK Chaining)#

Use the SDK connector() builder to define REST capabilities programmatically.

Build:

JavaScript
const { connector } = require('cliodot-flosync');

const myConnector = connector('my-api', 'My API')
 .description('Custom API connector')
 .baseUrl('https://api.example.com/v1')
 .bearer('')
 .get('Get Item', '/items/{{ pathParams.id }}', {
   pathParams: { id: 'string' },
   response_mapping: { id: 'id', name: 'name' }
 })
 .post('Create Item', '/items', {
   headers: { 'Content-Type': 'application/json' },
   body_schema: { name: 'string', value: 'number' }
 })
 .meta({
   category: 'Custom',
   logo: 'https://via.placeholder.com/64',
   status: 'active',
   visibility_settings: { visibility: 'private', public_access: 'none' }
 })
 .build();

Push and register:

JavaScript
const { FlosyncClient, connector } = require('cliodot-flosync');

const client = new FlosyncClient({ baseUrl, apiKey, apiSecret });
await client.authenticate();

const myConnector = connector('my-api', 'My API')
 .baseUrl('https://api.example.com/v1')
 .bearer('')
 .get('Get Item', '/items/{{ pathParams.id }}', { pathParams: { id: 'string' } })
 .post('Create Item', '/items', { body_schema: { name: 'string', value: 'number' } })
 .meta({ category: 'Custom', logo: '...', status: 'active', visibility_settings: { visibility: 'private', public_access: 'none' } })
 .build();

await client.connectors.push(myConnector);
await client.connectors.install('my-api', { auth: { token: process.env.MY_API_TOKEN } });

Use (workflow):

JavaScript
s.connector('my-api', 'Get Item', { pathParams: { id: '123' } });
s.connector('my-api', 'Create Item', { body: { name: 'Widget', value: 99 } });

ConnectorBuilder methods:

Method Description
baseUrl(url) Base API URL
bearer(token) Bearer token auth
apiKey(key, keyName?) API key auth
botToken(token, prefix?) Bot token (e.g. Discord Bot prefix)
customFlow(flow) Custom auth with token exchange flow (JWT bearer, etc.)
authConfig(auth) Custom auth object
get(name, path, opts?) GET endpoint
post(name, path, opts?) POST endpoint
put(name, path, opts?) PUT endpoint
patch(name, path, opts?) PATCH endpoint
delete(name, path, opts?) DELETE endpoint
endpoint(name, method, path, opts?) Generic endpoint
meta(m) Metadata (category, logo, status, visibility_settings). Adds last_updated automatically.
build() Returns capability object

Endpoint options: params, pathParams, body_schema, headers, response_mapping, description, timeout_ms, sample_responses.

Method 3: Custom Execute (Local Only)#

For full control over logic, define a capability with an execute function. These run locally only (not pushed to the server). Use flosync.registerConnector() to register them.

Build (define capability with execute):

JavaScript
const { flosync, variable: v } = require('cliodot-flosync');

const myCustomConnector = {
 _id: 'my.custom',
 type: 'system',
 name: 'My Custom Logic',
 meta: { category: 'custom' },
 auth: { type: 'none' },
 actions: [
   { id: 'validate', name: 'Validate' },
   { id: 'transform', name: 'Transform' },
 ],
 execute: async (action, options, context) => {
   const body = options.body || options;
   if (action === 'validate') {
     const valid = body.email && body.email.includes('@');
     return { valid, email: body.email };
   }
   if (action === 'transform') {
     return {
       ...body,
       normalized: body.name?.trim().toLowerCase(),
       timestamp: new Date().toISOString(),
     };
   }
   throw new Error('Unknown action: ' + action);
 },
};

Register:

JavaScript
flosync.registerConnector('my.custom', myCustomConnector);

Or with config (for capability-specific settings):

JavaScript
flosync.configure({
 connectors: {
   'my.custom': { /* optional config */ },
 },
});
flosync.registerConnector('my.custom', myCustomConnector);

Use (workflow):

JavaScript
s.connector('my.custom', 'validate', { body: { email: v.body('email') } });
s.connector('my.custom', 'transform', { body: v.stepResult('save') });

Use (direct run):

JavaScript
const result = await flosync.runConnector('my.custom', 'validate', {
 body: { email: 'user@example.com' },
});

Execute signature:

TypeScript
execute(
 action: string,
 options: { body?: any; params?: any; pathParams?: any; headers?: any },
 context: { trigger?: any; stepResults?: any; vars?: any; headers?: any; connectorConfig?: any; env?: any }
): Promise<unknown>

Return format: Return any object. The engine uses it as the step result. For { raw, mapped }, the engine uses mapped ?? raw.

Method 4: Postman Conversion#

Convert an existing Postman collection to a capability:

Bash
curl -X POST https://your-api-domain.com/api-core/cliodot/connectors/convert/postman \
 -H "Authorization: Bearer YOUR_JWT" \
 -F "collection=@/path/to/collection.postman_collection.json"

Returns a capability object. Push it via POST /connectors or client.connectors.push(). See /docs/capabilities/build#method-4-postman-conversion for details.