NebulaFlow enables seamless integration with external APIs through CLI nodes using tools like curl, wget, or custom scripts. This guide covers connecting to REST, GraphQL, and webhook endpoints, handling authentication, managing errors, and optimizing performance.
Key capabilities:
All API interactions are performed using CLI nodes that execute HTTP requests via curl or similar command-line tools.
interface CLINode {
type: NodeType.CLI
data: {
title: string
content: string // Command to execute (e.g., curl)
mode?: 'command' | 'script'
shell?: 'bash' | 'sh' | 'zsh' | 'pwsh' | 'cmd'
safetyLevel?: 'safe' | 'advanced'
streamOutput?: boolean
stdin?: { ... }
env?: { ... }
flags?: { ... }
}
}
Important: For API calls, set safetyLevel: 'advanced' only if you trust the API endpoint and need to bypass approval for automated workflows.
Fetch data from a REST endpoint:
{
type: NodeType.CLI,
data: {
title: 'Fetch User Data',
content: 'curl https://api.example.com/users',
streamOutput: true,
safetyLevel: 'advanced',
flags: { executionPolicyBypass: true }
}
}
Send JSON data to an API:
{
type: NodeType.CLI,
data: {
title: 'Create Resource',
content: 'curl -X POST -H "Content-Type: application/json" -d \'${1}\' https://api.example.com/resources',
stdin: {
source: 'parent-index',
parentIndex: 0
},
streamOutput: true
}
}
// PUT
{
type: NodeType.CLI,
data: {
title: 'Update Resource',
content: 'curl -X PUT -H "Content-Type: application/json" -d \'${1}\' https://api.example.com/resources/${id}',
env: {
static: {
id: '${resourceId}'
}
}
}
}
// DELETE
{
type: NodeType.CLI,
data: {
title: 'Delete Resource',
content: 'curl -X DELETE https://api.example.com/resources/${id}',
env: {
static: {
id: '${resourceId}'
}
}
}
}
Use template variables for dynamic query parameters:
{
type: NodeType.CLI,
data: {
title: 'Search API',
content: 'curl "https://api.example.com/search?q=${query}&limit=${limit}"',
env: {
static: {
query: '${searchTerm}',
limit: '10'
}
}
}
}
{
type: NodeType.CLI,
data: {
title: 'GraphQL Query',
content: 'curl -X POST -H "Content-Type: application/json" -d \'{"query": "${1}"}\' https://api.example.com/graphql',
stdin: {
source: 'parent-index',
parentIndex: 0
},
streamOutput: true
}
}
{
type: NodeType.CLI,
data: {
title: 'GraphQL with Variables',
content: 'curl -X POST -H "Content-Type: application/json" -d \'{"query": "${query}", "variables": ${variables}}\' https://api.example.com/graphql',
stdin: {
source: 'parent-index',
parentIndex: 0
}
}
}
Input example (from previous node):
{
"query": "query GetUser($id: ID!) { user(id: $id) { name email } }",
"variables": { "id": "123" }
}
{
type: NodeType.CLI,
data: {
title: 'Authenticated Request',
content: 'curl -H "X-API-Key: ${apiKey}" https://api.example.com/protected',
env: {
static: {
apiKey: '${apiKey}' // Reference Variable node or environment
}
}
}
}
{
type: NodeType.CLI,
data: {
title: 'Bearer Token Auth',
content: 'curl -H "Authorization: Bearer ${token}" https://api.example.com/protected',
env: {
static: {
token: '${bearerToken}'
}
}
}
}
{
type: NodeType.CLI,
data: {
title: 'Basic Auth',
content: 'curl -u "${username}:${password}" https://api.example.com/protected',
env: {
static: {
username: '${apiUser}',
password: '${apiPass}'
}
}
}
}
For OAuth, you may need a separate script to obtain tokens:
{
type: NodeType.CLI,
data: {
title: 'OAuth Token Fetch',
content: 'node /path/to/oauth-token.js',
mode: 'script',
env: {
static: {
CLIENT_ID: '${clientId}',
CLIENT_SECRET: '${clientSecret}'
}
}
}
}
Example Node.js script (oauth-token.js):
const axios = require('axios');
async function getToken() {
const response = await axios.post('https://auth.example.com/oauth/token', {
grant_type: 'client_credentials',
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET
});
console.log(response.data.access_token);
}
getToken();
Webhooks are typically processed via a CLI node that runs a script to handle incoming HTTP requests. Since NebulaFlow runs locally, you may need to expose a public endpoint using a tunnel service (e.g., ngrok).
Example webhook processor script:
// webhook-processor.js
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
const data = req.body;
// Process data, maybe write to a file or output to stdout
console.log(JSON.stringify(data));
res.sendStatus(200);
});
app.listen(3000, () => console.log('Webhook listener on port 3000'));
CLI Node to run the script:
{
type: NodeType.CLI,
data: {
title: 'Webhook Listener',
content: 'node /path/to/webhook-processor.js',
mode: 'script',
streamOutput: true,
safetyLevel: 'advanced',
flags: { executionPolicyBypass: true }
}
}
Note: For production, consider using a cloud function or webhook relay service.
curl returns exit code 0 even for HTTP errors (4xx, 5xx). Use --fail to make curl return non-zero on HTTP errors:
{
type: NodeType.CLI,
data: {
title: 'API with Error Handling',
content: 'curl --fail --silent --show-error https://api.example.com/data',
streamOutput: true
}
}
Implement retries using a wrapper script:
{
type: NodeType.CLI,
data: {
title: 'API with Retries',
content: 'node /path/to/retry-curl.js',
mode: 'script',
env: {
static: {
URL: 'https://api.example.com/data',
RETRIES: '3'
}
}
}
}
Retry script (retry-curl.js):
const { execSync } = require('child_process');
const url = process.env.URL;
const maxRetries = parseInt(process.env.RETRIES || '3');
for (let i = 0; i < maxRetries; i++) {
try {
const output = execSync(`curl --fail --silent ${url}`, { encoding: 'utf8' });
console.log(output);
process.exit(0);
} catch (error) {
if (i === maxRetries - 1) {
console.error('All retries failed:', error.message);
process.exit(1);
}
console.warn(`Retry ${i + 1}/${maxRetries}...`);
}
}
Add delays between requests using sleep:
{
type: NodeType.CLI,
data: {
title: 'Rate Limited API',
content: 'curl https://api.example.com/data && sleep 1',
streamOutput: true
}
}
jqInstall jq on your system, then parse JSON responses:
{
type: NodeType.CLI,
data: {
title: 'Extract Field',
content: 'curl -s https://api.example.com/data | jq .id',
streamOutput: true
}
}
{
type: NodeType.CLI,
data: {
title: 'Parse JSON with Node',
content: 'curl -s https://api.example.com/data | node /path/to/parser.js',
mode: 'command',
streamOutput: true
}
}
Parser script (parser.js):
const data = JSON.parse(require('fs').readFileSync(0, 'utf8'));
console.log(data.id);
Never hardcode secrets in workflows. Use environment variables or Variable nodes:
# Set environment variable
export API_KEY="your_api_key_here"
Reference in CLI node:
{
type: NodeType.CLI,
data: {
content: 'curl -H "X-API-Key: ${API_KEY}" https://api.example.com/data',
env: {
static: {
API_KEY: '${API_KEY}' // References environment variable
}
}
}
}
Create a Variable node with variableName: 'apiKey' and initialValue: 'your_key'. Then reference ${apiKey} in the CLI node.
Never directly interpolate user input into command strings. Use stdin for data:
// Good: Use stdin
stdin: {
source: 'parent-index',
parentIndex: 0
}
content: 'curl -X POST -H "Content-Type: application/json" -d @- https://api.example.com/data'
// Bad: Direct interpolation (vulnerable to injection)
content: `curl -X POST -d '${userInput}' https://api.example.com/data`
Always use HTTPS endpoints to prevent man-in-the-middle attacks.
For sensitive operations (e.g., deleting data), keep safetyLevel: 'safe' and needsUserApproval: true to require manual approval.
Use workflow parallelism to call multiple APIs simultaneously:
Start
├── CLI Node A (API 1)
├── CLI Node B (API 2)
├── CLI Node C (API 3)
└── Accumulator Node (collect results)
Cache API responses using Variable nodes or file system:
{
type: NodeType.CLI,
data: {
title: 'Cached API Call',
content: 'if [ -f /tmp/cache.json ]; then cat /tmp/cache.json; else curl -s https://api.example.com/data | tee /tmp/cache.json; fi',
streamOutput: true
}
}
curl creates a new connection per request. For high-frequency calls, consider using a persistent HTTP client (Node.js script with axios).
CLI (GET) → LLM (Analyze) → CLI (POST)
Example: Fetch GitHub repo data, analyze with LLM, post insights to Slack.
IF Node (check condition)
├── True: CLI (API A)
└── False: CLI (API B)
Loop Start (iterations = pages)
├── CLI (GET page ${i})
├── Accumulator (collect items)
Loop End
Cause: curl not installed or not in PATH.
Solution:
sudo apt-get install curl (Linux) or brew install curl (macOS)/usr/bin/curlCause: Self-signed certificate or missing CA bundle.
Solution:
# Ignore SSL verification (not recommended for production)
curl -k https://api.example.com/data
# Or provide CA bundle
curl --cacert /path/to/ca-bundle.crt https://api.example.com/data
Cause: API endpoint unreachable.
Solution:
Cause: Invalid API key or token.
Solution:
Cause: Too many requests.
Solution:
-v flag to curl--fail to detect HTTP errors-i flag to see response headersadvanced only for trusted APIsFor complex HTTP needs (e.g., HTTP/2, custom headers, cookies), write a Node.js script:
{
type: NodeType.CLI,
data: {
title: 'Custom HTTP Client',
content: 'node /path/to/custom-client.js',
mode: 'script',
env: {
static: {
API_URL: 'https://api.example.com',
API_KEY: '${apiKey}'
}
}
}
}
WebSocket connections require persistent processes. Consider using a separate service that bridges WebSocket to stdout.
Similar to WebSockets, subscriptions need a persistent connection. Use a script that listens to GraphQL subscription events and outputs to stdout.