> ## Documentation Index
> Fetch the complete documentation index at: https://docs.noclick.com/llms.txt
> Use this file to discover all available pages before exploring further.

# External Apps

> Connect TypeScript and Python applications to NoClick workflows via WebSocket

# External Apps

Build standalone applications that interact with NoClick workflows using the TypeScript or Python SDK. Authenticate with API keys and connect via WebSocket (Socket.IO).

## Authentication

External apps authenticate with API keys. Keys are scoped to a user and optionally to a specific workflow.

### Creating an API Key

Create keys in the dashboard under **Settings** → **Developer**: name the key, optionally scope it to one workflow, and click **Create**. The key looks like `nk_live_a1b2c3d4...` and is shown once, so copy it immediately. See [API keys](/sdk/api-keys) for permissions, scoping, revocation, and the REST management endpoints.

### Key Format

| Part   | Example           | Description                  |
| ------ | ----------------- | ---------------------------- |
| Prefix | `nk_live_`        | Identifies NoClick API keys  |
| Secret | `a1b2c3d4e5f6...` | 64 hex characters (32 bytes) |

### Permissions

| Permission | Allows                                                                            |
| ---------- | --------------------------------------------------------------------------------- |
| `read`     | `nodes.getOutput`, `nodes.list`, `state.get`, `dataset.getRows`, `resources.list` |
| `execute`  | `execution.runNodesAndGetOutput`, `execution.runNodesInBackground`                |
| `write`    | `nodes.setConfig`, `state.set`, `dataset.appendRows`, `resources.upload`          |

***

## TypeScript SDK

### Installation

```bash theme={null}
npm install noclick socket.io-client
```

### Connection

```typescript theme={null}
import { init, nodes, execution, state } from 'noclick';

await init({
  apiKey: 'nk_live_...',
  workflowId: '...',  // optional — scope operations to a workflow
});
```

### Reading Data

```typescript theme={null}
// List all nodes
const allNodes = await nodes.list();

// Read a node's last output
const output = await nodes.getOutput('gmail-node-id');
console.log(output.emails);

// Read a node's config
const config = await nodes.getConfig('http-node-id');
```

### Running Nodes

```typescript theme={null}
// Fire and forget
execution.runNodesInBackground(['data-fetcher']);

// With temporary config overrides (doesn't save to the node)
execution.runNodesInBackground([
  { id: 'http-node', config: { url: 'https://api.example.com/data' } }
]);

// Run and stream output
const stream = execution.runNodesAndGetOutput(
  ['data-fetcher'],        // nodes to execute
  ['formatter', 'chart']   // nodes whose output we want
);

stream.on('output', (nodeId, data) => {
  console.log(`${nodeId} completed:`, data);
});

stream.on('done', () => {
  console.log('All targets completed');
});

// Or await all results at once
const results = await stream.all();
// { 'formatter': {...}, 'chart': {...} }
```

### State Management

```typescript theme={null}
await state.set('counter', 0);
const val = await state.get('counter');  // 0

await state.update('counter', n => (n || 0) + 1);
await state.del('counter');

const keys = await state.keys();  // ['counter', ...]
```

### Real-time Subscriptions

Subscribe to node output changes from any source (cron, webhooks, manual runs):

```typescript theme={null}
// React to new data from a cron-triggered node
execution.onNodeOutput('data-fetcher', (output) => {
  console.log('New data:', output);
});

// Track execution state
execution.onNodeState('data-fetcher', (state) => {
  console.log('State:', state);  // 'running' | 'completed' | 'error'
});
```

***

## Python SDK

### Installation

```bash theme={null}
pip install noclick
```

Or install from source:

```bash theme={null}
cd sdk/python
pip install -e .
```

### Connection

```python theme={null}
import asyncio
import noclick

async def main():
    sdk = noclick.Client(
        api_key="nk_live_...",
        workflow_id="...",
    )
    await sdk.connect()

    # ... use the SDK ...

    await sdk.disconnect()

asyncio.run(main())
```

### Reading Data

```python theme={null}
# List all nodes
nodes = await sdk.nodes.list()
for node in nodes:
    print(f"{node['id']} ({node['type']}) — {node['label']}")

# Read a node's last output
output = await sdk.nodes.get_output("gmail-node-id")
for email in output.get("emails", []):
    print(f"{email['subject']} — {email['from']}")

# Read a node's config
config = await sdk.nodes.get_config("http-node-id")
```

### Running Nodes

```python theme={null}
# Fire and forget
await sdk.execution.run_nodes_in_background(["data-fetcher"])

# Run and wait for specific output
results = await sdk.execution.run_nodes_and_get_output(
    ["data-fetcher"],       # nodes to execute
    ["formatter", "chart"], # nodes whose output we want
    timeout=30,
)
print(results["formatter"])
print(results["chart"])
```

### State Management

```python theme={null}
await sdk.state.set("counter", 0)
val = await sdk.state.get("counter")  # 0

await sdk.state.update("counter", lambda n: (n or 0) + 1)
await sdk.state.delete("counter")

keys = await sdk.state.keys()  # ["counter", ...]
```

### Credentials

```python theme={null}
# List credentials
creds = await sdk.auth.list_credentials()
for c in creds:
    print(f"{c['name']} ({c['type']})")

# Check if a credential exists
has_gmail = await sdk.auth.has_credential("google_gmail_oauth")

# Create an API key credential
cred = await sdk.auth.create_credential(
    "telegram_bot_token",
    {"token": "..."},
    name="My Bot"
)
```

### Resources (File Storage)

```python theme={null}
import aiohttp

# Upload a file
result = await sdk.resources.upload("report.pdf", "application/pdf", file_size)
async with aiohttp.ClientSession() as session:
    with open("report.pdf", "rb") as f:
        await session.put(result["upload_url"], data=f)

# Get download URL
url = await sdk.resources.get_url(result["resource_id"])

# List resources
files = await sdk.resources.list("file")
```

### Dataset CRUD

```python theme={null}
# Create a dataset
ds_id = await sdk.dataset.create("User Submissions")

# Append rows
count = await sdk.dataset.append_rows(ds_id, [
    {"name": "Alice", "score": 95},
    {"name": "Bob", "score": 87},
])

# Read rows (paginated)
page = await sdk.dataset.get_rows(ds_id, limit=100)
for row in page["rows"]:
    print(f"{row['id']}: {row['data']}")

# Update a row
await sdk.dataset.update_row(ds_id, row_id, {"score": 98})

# Delete rows
await sdk.dataset.delete_rows(ds_id, [row_id])

# List datasets
datasets = await sdk.dataset.list()
```

### Real-time Subscriptions

```python theme={null}
# Subscribe to node output changes
def on_output(node_id, output):
    print(f"New output from {node_id}")

sdk.execution.on_node_output("data-fetcher", on_output)
```

***

## Security

* **API keys are hashed** (SHA-256) in the database. Raw keys are never stored.
* **Workflow scoping**: keys can be limited to a single workflow.
* **Permission model**: `read`, `execute`, `write` permissions control what operations are allowed.
* **Key revocation**: keys can be revoked instantly.
* **Expiration**: keys can have an optional expiry date.
* **Socket.IO transport**: encrypted WebSocket connection. Same auth context as the browser frontend.
