@catalisa/panel-sdk
v0.1.5
Published
TypeScript SDK for Panel Client API - Complete type-safe client for CRM/Workflow automation
Readme
Panel SDK
TypeScript SDK for the Panel API (painel-cliente-api). Provides a fully-typed client library for all panel features including leads, workflows, WhatsApp devices, and more.
Installation
npm install @catalisa/panel-sdkQuick Start
import { PanelAPIClient } from '@catalisa/panel-sdk';
// Create client
const client = new PanelAPIClient({
baseURL: 'https://panel-api.example.com',
});
// Login with email/password
await client.login('[email protected]', 'password');
// Or use API token
const clientWithToken = new PanelAPIClient({
baseURL: 'https://panel-api.example.com',
auth: {
type: 'apiToken',
token: 'your-api-token',
},
});
// Use resources
const leads = await client.leads.list();
const workflow = await client.workflows.create({ name: 'My Workflow' });Authentication
The SDK supports two authentication methods:
JWT Authentication
// Login to get JWT token
const response = await client.login('[email protected]', 'password');
// Token is automatically set for subsequent requests
// Or set token manually
client.setAuth('your-jwt-token', 'jwt');
// Clear authentication
client.clearAuth();
// Logout (clears token and calls logout endpoint)
await client.logout();API Token Authentication
// Initialize with API token
const client = new PanelAPIClient({
baseURL: 'https://panel-api.example.com',
auth: {
type: 'apiToken',
token: 'your-api-token',
},
});
// Or set later
client.setAuth('your-api-token', 'apiToken');Resources
Authentication (client.auth)
// Login
const response = await client.auth.login('email', 'password');
// Get current user
const user = await client.auth.me();
// Forgot password
await client.auth.forgotPassword('[email protected]');
// Reset password
await client.auth.resetPassword('token', 'newPassword');
// Logout
await client.auth.logout();Users (client.users)
// Get current user profile
const me = await client.users.me();
// Update profile
await client.users.updateProfile({ name: 'New Name' });
// Update password
await client.users.updatePassword({
currentPassword: 'old',
newPassword: 'new',
});Users Admin (client.usersAdmin)
// List all users
const users = await client.usersAdmin.list();
// Get user by ID
const user = await client.usersAdmin.get('user-id');
// Create user
const newUser = await client.usersAdmin.create({
email: '[email protected]',
password: 'password',
role: 'CORRETOR',
});
// Update user
await client.usersAdmin.update('user-id', { role: 'COMERCIAL' });
// Delete user
await client.usersAdmin.delete('user-id');Leads (client.leads)
// List leads with pagination
const leads = await client.leads.list({
page: 1,
limit: 20,
status: 'NOVO',
});
// Get lead by ID
const lead = await client.leads.get('lead-id');
// Create lead
const newLead = await client.leads.create({
name: 'John Doe',
phone: '+5511999999999',
email: '[email protected]',
typeId: 'lead-type-id',
});
// Update lead
await client.leads.update('lead-id', { status: 'QUALIFICADO' });
// Delete lead
await client.leads.delete('lead-id');Lead Types (client.leadTypes)
// List lead types
const types = await client.leadTypes.list();
// Create lead type
const type = await client.leadTypes.create({
name: 'Real Estate',
color: '#FF5733',
});
// Update lead type
await client.leadTypes.update('type-id', { name: 'Updated Name' });
// Delete lead type
await client.leadTypes.delete('type-id');Chats (client.chats)
// List chats
const chats = await client.chats.list({ page: 1, limit: 20 });
// Get chat by ID
const chat = await client.chats.get('chat-id');
// Get chat messages
const messages = await client.chats.getMessages('chat-id', {
page: 1,
limit: 50,
});Workflows (client.workflows)
// List workflows
const workflows = await client.workflows.list();
// Get workflow by ID
const workflow = await client.workflows.get('workflow-id');
// Create workflow
const newWorkflow = await client.workflows.create({
name: 'Welcome Flow',
description: 'Send welcome message to new leads',
});
// Update workflow
await client.workflows.update('workflow-id', { status: 'ACTIVE' });
// Delete workflow
await client.workflows.delete('workflow-id');
// Execute workflow manually
await client.workflows.execute('workflow-id', { leadId: 'lead-id' });
// Get workflow executions
const executions = await client.workflows.getExecutions('workflow-id');
// Get workflow statistics
const stats = await client.workflows.getStats('workflow-id');Workflow Triggers (client.workflowTriggers)
import { WorkflowTriggerType } from '@catalisa/panel-sdk';
// List triggers for a workflow
const triggers = await client.workflowTriggers.listByWorkflow('workflow-id');
// Create event trigger
const trigger = await client.workflowTriggers.create({
workflowId: 'workflow-id',
type: 'event',
config: {
type: 'event',
eventType: 'lead.created',
},
});
// Create schedule trigger
const scheduleTrigger = await client.workflowTriggers.create({
workflowId: 'workflow-id',
type: 'schedule',
config: {
type: 'schedule',
cron: '0 9 * * *', // Every day at 9 AM
timezone: 'America/Sao_Paulo',
},
});
// Update trigger
await client.workflowTriggers.update('trigger-id', {
config: { type: 'event', eventType: 'lead.updated' },
});
// Delete trigger
await client.workflowTriggers.delete('trigger-id');Workflow Actions (client.workflowActions)
import { WorkflowActionType, isActionType } from '@catalisa/panel-sdk';
// List actions for a workflow
const actions = await client.workflowActions.listByWorkflow('workflow-id');
// Create send text action
const action = await client.workflowActions.create({
workflowId: 'workflow-id',
type: 'send_text',
order: 1,
config: {
type: 'send_text',
message: 'Hello {{lead.name}}!',
deviceId: 'device-id',
},
});
// Create HTTP request action
const httpAction = await client.workflowActions.create({
workflowId: 'workflow-id',
type: 'http_request',
order: 2,
config: {
type: 'http_request',
url: 'https://api.example.com/webhook',
method: 'POST',
headers: [{ key: 'Content-Type', value: 'application/json' }],
body: '{"lead": "{{lead.id}}"}',
},
});
// Type-safe action handling
const someAction = await client.workflowActions.get('action-id');
if (isActionType(someAction, 'send_text')) {
// TypeScript knows config is SendTextConfig
console.log(someAction.config.message);
}
// Reorder actions
await client.workflowActions.reorder('workflow-id', [
{ id: 'action-1', order: 1 },
{ id: 'action-2', order: 2 },
]);WhatsApp Devices (client.whatsappDevices)
// List devices
const devices = await client.whatsappDevices.list();
// Get device by ID
const device = await client.whatsappDevices.get('device-id');
// Connect device (generate QR code)
const qrResponse = await client.whatsappDevices.connect('device-id');
console.log(qrResponse.qr); // Base64 QR code
// Get QR code
const qr = await client.whatsappDevices.getQR('device-id');
// Disconnect device
await client.whatsappDevices.disconnect('device-id');
// Delete device
await client.whatsappDevices.delete('device-id');WhatsApp Events (client.whatsappEvents)
// List events
const events = await client.whatsappEvents.list({
deviceId: 'device-id',
type: 'message',
page: 1,
limit: 50,
});
// Get event by ID
const event = await client.whatsappEvents.get('event-id');Outgoing Webhooks (client.outgoingWebhooks)
// List webhooks
const webhooks = await client.outgoingWebhooks.list();
// Create webhook
const webhook = await client.outgoingWebhooks.create({
url: 'https://api.example.com/webhook',
events: ['lead.created', 'lead.updated'],
secret: 'webhook-secret',
});
// Get webhook logs
const logs = await client.outgoingWebhooks.getLogs('webhook-id', {
page: 1,
limit: 20,
});
// Test webhook
await client.outgoingWebhooks.test('webhook-id');
// Delete webhook
await client.outgoingWebhooks.delete('webhook-id');Messages (client.messages)
// List messages
const messages = await client.messages.list({
deviceId: 'device-id',
direction: 'outbound',
page: 1,
limit: 50,
});
// Get message by ID
const message = await client.messages.get('message-id');Stats (client.stats)
// Get dashboard stats
const dashboard = await client.stats.getDashboard();
// Get message stats
const messageStats = await client.stats.getMessages({
startDate: '2024-01-01',
endDate: '2024-01-31',
});
// Get lead stats
const leadStats = await client.stats.getLeads({
startDate: '2024-01-01',
endDate: '2024-01-31',
});
// Get device stats
const deviceStats = await client.stats.getDevices();Events (client.events)
// List events
const events = await client.events.list({
type: 'lead.created',
page: 1,
limit: 50,
});
// Get event by ID
const event = await client.events.get('event-id');API Tokens (client.apiTokens)
// List tokens
const tokens = await client.apiTokens.list();
// Create token
const newToken = await client.apiTokens.create({
name: 'My API Token',
});
console.log(newToken.secret); // Only shown once!
// Delete token
await client.apiTokens.delete('token-id');System Settings (client.systemSettings)
// Get settings
const settings = await client.systemSettings.get();
// Update settings
await client.systemSettings.update({
defaultLanguage: 'pt-BR',
});
// Reset to defaults
await client.systemSettings.reset();Routes (client.routes)
// List routes
const routes = await client.routes.list();
// Get route groups
const groups = await client.routes.getGroups();
// Create route
const route = await client.routes.create({
path: '/api/custom',
method: 'GET',
roles: ['ADMIN'],
});
// Update route
await client.routes.update('route-id', { roles: ['ADMIN', 'COMERCIAL'] });
// Delete route
await client.routes.delete('route-id');Health (client.health)
// Check API health
const status = await client.health.check();
console.log(status.status); // 'ok'Workflow Action Types
The SDK provides complete type safety for all 22 workflow action types:
Messaging Actions
send_text- Send text messagesend_media- Send image, video, audio, or documentsend_reaction- Send emoji reactionsend_location- Send locationsend_contact- Send contact cardsend_buttons- Send message with buttonssend_list- Send list message
Group Actions
create_group- Create WhatsApp groupupdate_group- Update group infoadd_to_group- Add participantsremove_from_group- Remove participants
HTTP Actions
http_request- Make HTTP requestwebhook- Send webhook
Control Flow Actions
delay- Wait for durationcondition- If/else branchingswitch- Switch/case branchingloop- Loop over itemswait_for_reply- Wait for user reply
Lead Actions
create_lead- Create new leadupdate_lead- Update leadassign_lead- Assign lead to userchange_lead_status- Change lead status
Workflow Actions
start_workflow- Start another workflowstop_workflow- Stop workflow executionset_variable- Set workflow variable
System Actions
log- Log messagenotify- Send notificationschedule- Schedule action
AI Actions
ai_response- Generate AI response
Webhook Actions
trigger_webhook- Trigger advanced webhookcreate_advanced_webhook- Create advanced webhook
Error Handling
import {
APIError,
AuthError,
ValidationError,
NotFoundError,
RateLimitError,
NetworkError,
} from '@catalisa/panel-sdk';
try {
await client.leads.get('invalid-id');
} catch (error) {
if (error instanceof NotFoundError) {
console.log('Lead not found');
} else if (error instanceof AuthError) {
console.log('Authentication failed');
} else if (error instanceof ValidationError) {
console.log('Validation errors:', error.message);
} else if (error instanceof RateLimitError) {
console.log('Rate limited, retry after:', error.retryAfter);
} else if (error instanceof NetworkError) {
console.log('Network error:', error.message);
} else if (error instanceof APIError) {
console.log('API error:', error.status, error.message);
}
}Advanced Usage
Custom Headers
const client = new PanelAPIClient({
baseURL: 'https://panel-api.example.com',
headers: {
'X-Custom-Header': 'value',
},
});Request/Response Interceptors
const client = new PanelAPIClient({
baseURL: 'https://panel-api.example.com',
onRequest: (config) => {
console.log('Request:', config.method, config.url);
return config;
},
onResponse: (response) => {
console.log('Response:', response.status);
return response;
},
onError: (error) => {
console.error('Error:', error.message);
},
});Custom Timeout
const client = new PanelAPIClient({
baseURL: 'https://panel-api.example.com',
timeout: 60000, // 60 seconds
});TypeScript Support
The SDK is written in TypeScript and provides full type definitions. All types are exported from the main package:
import {
Lead,
LeadStatus,
Workflow,
WorkflowAction,
WorkflowActionType,
isActionType,
getActionConfig,
} from '@catalisa/panel-sdk';
// Type guards for action types
const action: WorkflowAction = await client.workflowActions.get('id');
if (isActionType(action, 'send_text')) {
// action.config is now typed as SendTextConfig
const message = action.config.message;
}
// Get typed config from action
const config = getActionConfig(action, 'http_request');
if (config) {
// config is HttpRequestConfig
console.log(config.url, config.method);
}Testing
The SDK has two types of tests:
- Unit Tests - Uses Vitest + MSW (Mock Service Worker)
- E2E Tests - Runs against the real API
Quick Start
Run unit tests
npm testRun E2E tests (local)
# Generate test token (if needed)
cd ../painel-cliente-api
JWT_SECRET="painel-jwt-secret-change-me" \
DATABASE_URL="postgresql://postgres:postgres@localhost:5434/painel_cliente_db" \
node scripts/generate-security-test-tokens.js
# Run E2E tests
cd ../panel-sdk
TEST_TOKEN="<token>" API_URL="http://localhost:3004" npx tsx src/e2e-tests.tsE2E Tests - Environments
Local (development)
TEST_TOKEN="<token>" \
API_URL="http://localhost:3004" \
npx tsx src/e2e-tests.tsStaging
# First, generate a valid token for staging
cd ../painel-cliente-api
JWT_SECRET="<staging-jwt-secret>" \
DATABASE_URL="postgresql://panel_staging_db_user:[email protected]:5432/panel_staging_db" \
node scripts/generate-security-test-tokens.js
# Run tests
cd ../panel-sdk
TEST_TOKEN="<token>" \
API_URL="https://panel-api.wpp.bb.staging.catalisa.app" \
npx tsx src/e2e-tests.tsProduction (use with caution!)
# Be careful - tests create and delete real data!
TEST_TOKEN="<production-token>" \
API_URL="https://panel-api.wpp.bb.catalisa.app" \
npx tsx src/e2e-tests.tsDetailed Documentation
File Structure
apps/panel-sdk/
├── src/
│ ├── client.ts # Main client
│ ├── cli.ts # CLI utilities (login, output)
│ ├── e2e-tests.ts # E2E tests
│ ├── index.test.ts # Unit tests
│ ├── resources/ # API resources
│ │ ├── leads.ts
│ │ ├── workflows.ts
│ │ ├── capture-links.ts
│ │ ├── branding.ts
│ │ └── ...
│ ├── types/ # TypeScript types
│ │ ├── index.ts
│ │ ├── capture-links.ts
│ │ ├── branding.ts
│ │ └── ...
│ └── test/
│ └── mocks/
│ └── handlers.ts # MSW handlers for mocks
├── package.json
└── README.mdUnit Tests (Vitest + MSW)
Unit tests use MSW to intercept HTTP requests and return mocked data.
Run tests:
npm test # Run once
npm run test:watch # Watch mode
npm run test:coverage # With coverageAdd new mocks:
Edit src/test/mocks/handlers.ts:
// Add mock data
export const mockMyResource = {
id: 'test-123',
name: 'Test',
// ...
};
// Add handlers
http.get(`${BASE_URL}/my-resource`, () => {
return HttpResponse.json([mockMyResource]);
}),E2E Tests
E2E tests run against a real API (local, staging, or production).
Environment variables:
| Variable | Description | Example |
|----------|-------------|---------|
| TEST_TOKEN | Valid JWT | eyJhbGciOi... |
| API_URL | API URL | http://localhost:3004 |
E2E test scripts:
# Basic test
npx tsx src/e2e-tests.ts
# Hierarchy tests (master/sub-user)
MASTER_TOKEN="..." SUBUSER_TOKEN="..." npx tsx src/e2e-hierarchy-tests.ts
# Chats/messages tests
TEST_TOKEN="..." npx tsx src/e2e-chats-messages-tests.tsInteractive login (without token):
If you don't provide TEST_TOKEN, tests will start interactive login:
API_URL="http://localhost:3004" npx tsx src/e2e-tests.ts
# You will be prompted:
# Email: [email protected]
# Password: ***Generating Test Tokens
For local development:
cd ../painel-cliente-api
JWT_SECRET="painel-jwt-secret-change-me" \
DATABASE_URL="postgresql://postgres:postgres@localhost:5434/painel_cliente_db" \
node scripts/generate-security-test-tokens.jsFor staging:
cd ../painel-cliente-api
JWT_SECRET="<staging-jwt-secret>" \
DATABASE_URL="postgresql://panel_staging_db_user:[email protected]:5432/panel_staging_db" \
node scripts/generate-security-test-tokens.jsThe script generates tokens for:
ADMIN_TOKEN- Admin userMASTER_TOKEN- Master (COMERCIAL with sub-users)SUBUSER_TOKEN- Sub-user of a masterSIBLING_TOKEN- Another sub-user of the same masterOTHER_TENANT_TOKEN- User from another tenant
Test Scenarios
Capture Links
- Full CRUD
- Toggle status (activate/deactivate)
- Statistics
- Public lead submission
- assignedTo verification (leads created via capture link are always assigned to the link owner)
Branding
- Get/Upsert/Delete
- Permissions (canManage)
- Public branding (default and by userId)
Workflows
- Full CRUD
- Triggers and Actions
- Validation (required fields, JID format, etc.)
Hierarchy (Master/Sub-user)
- Data isolation
- Master sees all sub-users' data
- Sub-user sees only their own data
Troubleshooting
Error "Token expired"
Generate a new token using the generate-security-test-tokens.js script.
Connection error
Check if:
- The API is running (
docker compose up painel-cliente-api) - The URL is correct
- The ports are correct (local: 3004, staging: HTTPS)
Tests failing due to existing data
Some tests create and delete data. If they fail midway, they may leave "orphan" data. Run the seed again or clean up manually.
CI/CD
Unit tests run automatically in CI:
# .github/workflows/test.yml
- name: Run tests
run: |
cd apps/panel-sdk
npm testFor E2E tests in CI, configure secrets for staging:
- name: Run E2E tests
env:
TEST_TOKEN: ${{ secrets.STAGING_TEST_TOKEN }}
API_URL: https://panel-api.wpp.bb.staging.catalisa.app
run: |
cd apps/panel-sdk
npx tsx src/e2e-tests.tsLicense
MIT
