> ## 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.

# Errors & troubleshooting — help

> What the common ModuleX error codes mean — 401, 402, 403, 404, 429, and 500 — and how to fix them, plus how to debug a failed node or tool.

export const MediaEmbed = ({id, type = 'screenshot', caption = '', ext, ratio = '16 / 9'}) => {
  const isVideo = type === 'video' || type === 'app_video';
  const resolvedExt = ext || (isVideo ? 'mp4' : type === 'screenshot' ? 'webp' : 'svg');
  const src = 'https://media.modulex.dev/' + id + '.' + resolvedExt;
  const [status, setStatus] = useState('loading');
  const [isDev, setIsDev] = useState(false);
  const [inView, setInView] = useState(false);
  const boxRef = useRef(null);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const h = window.location.hostname;
    setIsDev(h === 'localhost' || h === '127.0.0.1' || h.endsWith('.mintlify.app'));
  }, []);
  useEffect(() => {
    if (inView) return;
    if (typeof IntersectionObserver === 'undefined') {
      setInView(true);
      return;
    }
    const el = boxRef.current;
    if (!el) return;
    const io = new IntersectionObserver(entries => {
      if (entries.some(e => e.isIntersecting)) {
        setInView(true);
        io.disconnect();
      }
    }, {
      rootMargin: '300px'
    });
    io.observe(el);
    return () => io.disconnect();
  }, [inView]);
  if (status === 'missing') {
    if (!isDev) return null;
    return <div style={{
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'center',
      gap: '0.4rem',
      padding: '1rem 1.25rem',
      margin: '1.25rem 0',
      width: '100%',
      aspectRatio: ratio,
      boxSizing: 'border-box',
      border: '1px dashed rgba(128,128,128,0.45)',
      borderRadius: '0.75rem',
      background: 'rgba(128,128,128,0.06)',
      color: 'currentColor',
      fontSize: '0.85rem',
      lineHeight: 1.45
    }}>
        <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: '0.5rem',
      opacity: 0.75
    }}>
          <span aria-hidden="true">🎬</span>
          <code style={{
      fontSize: '0.75rem'
    }}>{id}</code>
          <span style={{
      fontSize: '0.65rem',
      textTransform: 'uppercase',
      letterSpacing: '0.04em',
      padding: '0.1rem 0.4rem',
      borderRadius: '0.4rem',
      background: 'rgba(128,128,128,0.18)'
    }}>
            {type}
          </span>
        </div>
        <div style={{
      opacity: 0.9
    }}>{caption || 'Media not uploaded yet.'}</div>
        <div style={{
      fontSize: '0.7rem',
      opacity: 0.5
    }}>
          Upload to R2 as <code>{id}.{resolvedExt}</code> — preview only, hidden in production.
        </div>
      </div>;
  }
  const mediaStyle = {
    display: status === 'loaded' ? 'block' : 'none',
    width: '100%',
    height: 'auto',
    borderRadius: '0.75rem'
  };
  const media = isVideo ? <video src={inView ? src : undefined} autoPlay loop muted playsInline preload="metadata" onLoadedData={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} /> : <img src={inView ? src : undefined} alt={caption} onLoad={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} />;
  return <figure style={{
    margin: '1.25rem 0'
  }}>
      <div ref={boxRef} style={status === 'loaded' ? {
    width: '100%'
  } : {
    width: '100%',
    aspectRatio: ratio,
    borderRadius: '0.75rem',
    background: 'rgba(128,128,128,0.06)'
  }}>
        {media}
      </div>
      {status === 'loaded' && caption ? <figcaption style={{
    marginTop: '0.5rem',
    textAlign: 'center',
    fontSize: '0.85rem',
    opacity: 0.7
  }}>
          {caption}
        </figcaption> : null}
    </figure>;
};

Most errors fall into a handful of codes, and each one has a clear fix. This page explains what the common codes mean in plain terms and what to do about them. For the full technical reference, follow the link in each answer.

<CardGroup cols={2}>
  <Card title="Errors & status codes" icon="list-checks" href="/api-reference/errors">
    The complete reference: every status code and the response shapes behind it.
  </Card>

  <Card title="Usage gating & limits" icon="shield-halved" href="/billing/usage-gating">
    The admission gate and the `402` / `403` / `429` responses it returns.
  </Card>
</CardGroup>

## Errors & troubleshooting

<AccordionGroup>
  <Accordion title="What do the common error codes mean?">
    Here is the short version:

    * `401` — you are not signed in, or your API key or token is invalid.
    * `402` — your organization is out of credits (payment required).
    * `403` — you do not have permission, or you are calling the wrong organization.
    * `404` — the thing you asked for does not exist, or it is not in your organization.
    * `429` — too many requests, or a usage limit was hit.
    * `500` — something went wrong on our side.

    For the precise meaning and response body of each code, see [errors & status codes](/api-reference/errors).
  </Accordion>

  <Accordion title="Why do I get a 401?">
    Your API key or token is missing or invalid. API keys start with `mx_live_` and go in the `Authorization: Bearer` header, alongside the `X-Organization-ID` header. Check the value is correct and not expired — see [authentication](/api-reference/authentication).
  </Accordion>

  <Accordion title="Why do I get a 403?">
    Either you do not have the right role for that action, or your key is scoped to a different organization than the one you are calling. Building or running with Composer, the Assistant, and schedules needs an owner or admin role; see [roles & permissions](/security/roles-permissions). A `403` on a run, Composer, Assistant, or managed-knowledge request can also mean a plan limit was reached — see [usage gating & limits](/billing/usage-gating).
  </Accordion>

  <Accordion title="Why do I get a 402?">
    Your organization is out of credits, so the request was blocked with a "payment required" response. Top up your [wallet](/billing/wallet), turn on overage, or upgrade your plan in [plans & pricing](/billing/plans). For what counts as managed usage, see [credits & metering](/billing/credits).
  </Accordion>

  <Accordion title="Why do I get a 429?">
    You have hit a rate limit, or a usage limit on a run. Wait for the number of seconds given in the `Retry-After` header on the response, then retry. See [rate limiting](/api-reference/rate-limiting).
  </Accordion>

  <Accordion title="A tool or node failed — how do I debug it?">
    Open the run in your run history and look at the failed node's error message. The usual causes are a missing or expired credential, bad input passed into the node, or the external service returning its own error. To reconnect a credential, see [credentials & integrations](/help/credentials-integrations); for retry behavior in a workflow, see [error handling & retries](/workflow-builder/error-handling-retries).

    <MediaEmbed id="MX-MEDIA-4590" type="screenshot" caption={"the run history view with a failed node expanded to show its error message"} />
  </Accordion>
</AccordionGroup>

## A note on 402, 403, and 429

These three codes mean different things depending on where they come from, so it helps to know which is which.

<Note>
  On the **run, Composer, Assistant, and managed-knowledge** surfaces, a `402`, `403`, or `429` from a usage limit returns a flat response that tells you exactly which limit was hit — for example `{code, layer, key, current, limit, reason}`. Plain create/read/update/delete and settings requests instead return the simpler `{detail}` message shape. The full breakdown of these response shapes is in [errors & status codes](/api-reference/errors).
</Note>

## Debug a failed request from code

When you call ModuleX from the API or an SDK, every request uses `Authorization: Bearer mx_live_…` together with the `X-Organization-ID` header. If a request fails, read the HTTP status code and the response body — the SDKs raise typed errors you can catch so you can branch on the status and the included fields.

<CodeGroup>
  ```bash cURL theme={null}
  # Add -i to see the status line and headers (such as Retry-After on a 429)
  curl -i -X POST https://api.modulex.dev/workflows/run \
    -H "Authorization: Bearer mx_live_your_api_key" \
    -H "X-Organization-ID: org_your_org_id" \
    -H "Content-Type: application/json" \
    -d '{ "workflow_id": "wf_29ab83c1", "input": { "topic": "release notes" } }'
  ```

  ```python Python theme={null}
  from modulex import Modulex
  from modulex import RateLimitError, BillingError, AuthenticationError, ModulexError

  client = Modulex(
      api_key="mx_live_your_api_key",
      organization_id="org_your_org_id",
  )

  try:
      run = await client.executions.run(
          workflow_id="wf_29ab83c1",
          input={"topic": "release notes"},
      )
  except AuthenticationError:
      print("401 — check your API key and organization id")
  except RateLimitError as err:
      print(f"429 — retry after {err.retry_after} seconds")
  except BillingError as err:
      # 402 / 403 / 429 usage limit — err.code and err.layer say which one
      print(f"Usage limit hit: {err.code} ({err.layer})")
  except ModulexError as err:
      print(f"Request failed with status {err.status_code}")
  ```

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

  const client = new Modulex({
    apiKey: "mx_live_your_api_key",
    organizationId: "org_your_org_id",
  });

  try {
    const run = await client.executions.run({
      workflowId: "wf_29ab83c1",
      input: { topic: "release notes" },
    });
  } catch (err) {
    if (err instanceof AuthenticationError) {
      console.log("401 — check your API key and organization id");
    } else if (err instanceof RateLimitError) {
      console.log(`429 — retry after ${err.retryAfter} seconds`);
    } else if (err instanceof ModulexError) {
      // 402 / 403 usage limits expose .code, .layer, and .reason on the error
      console.log(`Request failed (${err.status}): ${err.code ?? "error"}`);
    }
  }
  ```
</CodeGroup>

<Note>
  The SDK error classes differ slightly between languages — for example, the Python SDK has a dedicated billing error family that the JavaScript SDK folds into its base error. See [errors & status codes](/api-reference/errors) for the exact mapping.
</Note>

## Related help

<CardGroup cols={2}>
  <Card title="Account & access — help" icon="user-lock" href="/help/account-access">
    Signing in, organizations, and the roles behind a `403`.
  </Card>

  <Card title="Billing, plans & credits — help" icon="credit-card" href="/help/billing-credits">
    What it costs, what uses credits, and how to clear a `402`.
  </Card>

  <Card title="Credentials & integrations — help" icon="plug" href="/help/credentials-integrations">
    Reconnect an expired credential behind a failed tool or node.
  </Card>

  <Card title="Known limitations — help" icon="triangle-alert" href="/help/known-limitations">
    Behaviors to be aware of so you do not chase a non-bug.
  </Card>
</CardGroup>
