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

# Behaviors & edge cases

> Behaviors and edge cases worth knowing when you build on ModuleX — multi-node delete, refreshing OAuth, role requirements, annual checkout, installing tool dependencies, run de-duplication, and external sync — each with the recommended approach.

This page collects behaviors and edge cases worth knowing so you can build with
confidence. Each entry states what to expect and the recommended approach to take.

If you hit something that is not on this list, check
[Errors & status codes](/api-reference/errors) and the
[Errors & troubleshooting help page](/help/errors-troubleshooting) first, then
[get in touch](/help/getting-help).

<Note>
  This is the developer-facing reference. For a plain-language version aimed at app users,
  see [the help version](/help/known-limitations).
</Note>

## Summary

<CardGroup cols={2}>
  <Card title="Delete nodes one at a time" icon="trash" href="#delete-nodes-one-at-a-time">
    In the builder, remove selected nodes individually and confirm each is gone.
  </Card>

  <Card title="Refresh OAuth by reconnecting" icon="rotate-ccw" href="#refresh-an-oauth-connection-by-reconnecting">
    To restore a lapsed OAuth2 credential, disconnect and reconnect it.
  </Card>

  <Card title="Composer, Assistant & schedules need owner/admin" icon="shield-check" href="#composer-assistant-and-schedules-need-owner-or-admin">
    These surfaces require the owner or admin role.
  </Card>

  <Card title="Confirm the annual total at checkout" icon="receipt" href="#confirm-the-annual-total-at-checkout">
    The Stripe checkout total is authoritative — confirm it before subscribing annually.
  </Card>

  <Card title="Install tool dependencies directly" icon="package" href="#installing-tool-dependencies">
    Install the base package, then add each tool's required SDK.
  </Card>

  <Card title="External sync uses Socket.io" icon="radio" href="#external-sync-uses-socketio">
    Listen for the Socket.io `workflow:external-sync` event for external edits.
  </Card>

  <Card title="Idempotency-Key and runs" icon="copy" href="#idempotency-key-and-run-de-duplication">
    Each run request starts a new run — guard against duplicates in your own code.
  </Card>
</CardGroup>

## Builder & canvas

### Delete nodes one at a time

When you select several nodes on the workflow canvas and delete them in a single action,
remove them **one at a time** instead. After each deletion, confirm the node is gone
before removing the next one.

<Warning>
  Delete nodes **one at a time**, confirming each removal, rather than relying on a
  multi-select delete to remove every selected node in one step.
</Warning>

For the collaboration event reference and how external sync works, see
[Socket.io collaboration events](/realtime/socket-events) and
[Realtime co-editing & external sync](/workflow-builder/realtime-coediting).

## Credentials & OAuth

### Refresh an OAuth connection by reconnecting

To restore a lapsed OAuth2 credential — for example when its access token has expired and
cannot be renewed automatically — **disconnect the credential and reconnect it**.
Reconnecting runs the full OAuth2 flow again and issues fresh tokens.

<Warning>
  To restore an expired OAuth connection, disconnect the credential and reconnect it. This
  is the supported way to issue fresh tokens.
</Warning>

For how credentials are connected, scoped, and rotated, see
[Authentication & credentials](/integrations/authentication) and
[Managing credentials](/integrations/managing-credentials). When the Assistant needs a
credential mid-task, it asks you to reconnect — see [Using tools](/assistant/using-tools).

## Roles & permissions

### Composer, Assistant, and schedules need owner or admin

The live organization roles are **`owner`** and **`admin`**. The Composer, the Assistant,
and schedule management require owner or admin; a request from another role receives a
`403`.

<Warning>
  If a teammate cannot start the Composer, use the Assistant, or create a schedule, check
  that they are an **owner or admin** — promote them, or perform the action with an
  owner/admin account.
</Warning>

For the full permission model, see [Roles & permissions](/security/roles-permissions),
[Organizations, roles & membership](/concepts/organizations-roles), and the Assistant's
[Permissions & limits](/assistant/permissions-and-limits).

## Billing & pricing

### Confirm the annual total at checkout

When you subscribe annually, the **total shown on the Stripe checkout page is
authoritative**. Confirm it before you complete the purchase rather than assuming the
figure on a pricing table.

<Warning>
  Always confirm the total shown at Stripe checkout before subscribing annually.
</Warning>

For the plan and pricing details, see [Plans & pricing](/billing/plans). For what consumes
credits and how usage is metered, see [Credits & metering](/billing/credits).

<Note>
  Credit allowances and rate limits are unaffected. The credit allowances (Pro 5,000 /
  Max 20,000 per month) come from the authoritative plan configuration.
</Note>

## SDKs & integrations

### Installing tool dependencies

ModuleX does not publish per-tool `pip` extras. Install the base package, then add the
SDK each tool requires, based on that tool's documented requirements.

<Warning>
  Install the base package and add each tool's required dependencies directly, rather than
  relying on a per-tool extra to pull them in.
</Warning>

```bash Install theme={null}
# Install the base package (core dependencies)
pip install modulex-integrations

# Then add the SDKs the tools you use require, e.g.
pip install boto3            # for the AWS tool
```

For how integrations are discovered and loaded, see
[Installing & using integrations](/integrations/install). For the full inventory, see the
[Integration catalog](/integrations/catalog), generated from the 175 live integrations.

### Idempotency-Key and run de-duplication

Both SDKs can send an `Idempotency-Key` header, but it is **not used to de-duplicate
workflow runs** — each run request starts a new run with its own `run_id`, so sending the
same `Idempotency-Key` twice produces two distinct runs.

<Warning>
  Do not use `Idempotency-Key` to make a run request safe to retry. Guard against duplicate
  runs in your own code — for example, only retry after confirming the first request did not
  start a run.
</Warning>

The per-execution `run_id` is also re-minted on every resume, so it is not stable across a
paused-and-resumed conversation. For retry policy and error classes in the SDKs, see
[Errors & retries](/sdks/errors-retries). For run-id identity in general, see
[Workflows & runs](/concepts/workflows-and-runs).

<CodeGroup>
  ```bash cURL theme={null}
  # Each call starts its own run; Idempotency-Key does not de-duplicate runs.
  curl -X POST https://api.modulex.dev/workflows/run \
    -H "Authorization: Bearer mx_live_REDACTED" \
    -H "X-Organization-ID: org_8f2c1a9b4e7d" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: 7c1f0a2e-3b4d-4f6a-9c8e-2d1b5a6f0e3c" \
    -d '{"workflow_id": "wf_842f192a76c", "input": {"topic": "weekly digest"}}'
  ```

  ```python Python theme={null}
  import os
  from modulex import Modulex

  client = Modulex(
      api_key=os.environ["MODULEX_API_KEY"],          # mx_live_…
      organization_id=os.environ["MODULEX_ORG_ID"],    # X-Organization-ID
  )

  # Calling this twice starts two separate runs with two different run_ids.
  run = await client.workflows.run(
      workflow_id="wf_842f192a76c",
      input={"topic": "weekly digest"},
      idempotency_key="7c1f0a2e-3b4d-4f6a-9c8e-2d1b5a6f0e3c",
  )
  print(run.run_id)
  ```

  ```javascript JavaScript theme={null}
  import { Modulex } from "modulex-js";

  const client = new Modulex({
    apiKey: process.env.MODULEX_API_KEY,            // mx_live_…
    organizationId: process.env.MODULEX_ORG_ID,      // X-Organization-ID
  });

  // Calling this twice starts two separate runs with two different run_ids.
  const run = await client.workflows.run({
    workflowId: "wf_842f192a76c",
    input: { topic: "weekly digest" },
    idempotencyKey: "7c1f0a2e-3b4d-4f6a-9c8e-2d1b5a6f0e3c",
  });
  console.log(run.runId);
  ```
</CodeGroup>

## Realtime

### External sync uses Socket.io

External edits — changes made through the REST API while collaborators have the canvas
open — are delivered over the **Socket.io `workflow:external-sync` event**. That is the
live external-sync mechanism.

<Warning>
  If you are building or debugging realtime sync, listen for the Socket.io
  `workflow:external-sync` event.
</Warning>

For the complete event reference, see
[Socket.io collaboration events](/realtime/socket-events) and
[Realtime co-editing & external sync](/workflow-builder/realtime-coediting). For the
realtime model overview, see [Realtime & collaboration model](/concepts/realtime-model).

### Edit history is managed in the builder

Workflow edits are versioned, and you read or revert them through the in-builder
versioning and history controls rather than a separate programmatic API. See
[Versioning & history](/workflow-builder/versioning-history).

<Note>
  For the shared entities behind these behaviors — workflows, runs, credentials, and more —
  see the [Data model reference](/reference/data-model).
</Note>

## Quick reference

| Behavior                         | Recommended approach                          |
| -------------------------------- | --------------------------------------------- |
| Multi-node delete                | Delete nodes one at a time                    |
| Refresh OAuth                    | Disconnect and reconnect the credential       |
| Composer / Assistant / schedules | Use an owner or admin account                 |
| Annual price                     | Confirm the Stripe checkout total             |
| Tool dependencies                | Install the base package plus each tool's SDK |
| Idempotency-Key                  | Guard against duplicate runs in your own code |
| External sync                    | Listen for Socket.io `workflow:external-sync` |
| Edit history                     | Use in-builder versioning & history           |
