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

# Connect an integration

> Connect a third-party service to ModuleX with OAuth or your own key, then call its tool from a workflow. A step-by-step guide for connecting GitHub, Slack, and 175 other integrations.

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>;
};

ModuleX connects to 175 services — GitHub, Slack, Gmail, Notion, Salesforce, and many more — so your workflows can act on them: open an issue, send a message, read a row, create a contact. This guide walks you through it end to end: connect a service once, then call one of its actions from a [tool node](/workflow-builder/nodes/tool) in a workflow.

You connect a service **once**, for your whole organization. After that, every workflow and the [Assistant](/assistant/overview) can use it. For the bigger picture of what integrations are, start with the [integrations overview](/integrations/overview).

<MediaEmbed id="MX-MEDIA-4450" type="app_video" caption={"End-to-end walkthrough: connecting GitHub with OAuth, then calling a GitHub action from a tool node in a workflow."} />

## Before you start

A few things make this go smoothly.

<CardGroup cols={3}>
  <Card title="An account in an organization" icon="building">
    Integrations are connected per organization, not per person. Make sure you are working in the right one — the organization switcher is in the app header.
  </Card>

  <Card title="The owner or admin role" icon="shield-check">
    Only an **owner** or an **admin** can connect and manage credentials for an organization. If you do not see the connect controls, ask an owner or admin to connect the service. See [roles & permissions](/security/roles-permissions).
  </Card>

  <Card title="An account on the service" icon="key">
    For most services you sign in with your existing account during the connect step. Some services need an API key instead — you can paste one in.
  </Card>
</CardGroup>

<Info>
  You do not have to connect everything yourself. ModuleX offers managed keys for many services — see [Three ways to connect](#three-ways-to-connect) below — so you can try a tool before bringing your own account.
</Info>

## How connecting works

Connecting a service stores a **credential**: an encrypted record that links your organization to that service. You create it once; ModuleX encrypts it, and uses it automatically every time a workflow or the Assistant calls one of that service's tools. You never paste a secret into a workflow.

Behind the scenes there are a few canonical terms worth knowing.

<AccordionGroup>
  <Accordion title="Integration vs. tool">
    An **integration** is the connection to one service, for example GitHub. Each integration exposes many **tools** — the individual actions you can call, such as `github.create_issue` or `github.list_repositories`. ModuleX has 175 integrations and 600+ tools in total. Browse them in the [catalog](/integrations/catalog).
  </Accordion>

  <Accordion title="Credential">
    A **credential** is the encrypted, organization-scoped record that proves your identity to a service. One integration can hold more than one credential — for example a production GitHub and a test GitHub — and you mark one as the **default**.
  </Accordion>

  <Accordion title="Default credential">
    When a tool runs and you have not picked a specific credential, ModuleX uses the one you marked as **default** for that integration. Set or change the default any time from [managing credentials](/integrations/managing-credentials).
  </Accordion>
</AccordionGroup>

## Three ways to connect

A service tells ModuleX which methods it supports. Most integrations offer one or more of these three. The connect screen shows you only the options that apply.

<CardGroup cols={3}>
  <Card title="OAuth (recommended)" icon="lock-keyhole">
    Sign in to the service and grant access — no copying secrets. This is the smoothest path and is covered step by step below. The technical details live in [integration authentication](/integrations/authentication).
  </Card>

  <Card title="Your own key (BYOK)" icon="key">
    Paste an API key or token you already have from the service. Usage is billed directly by the provider, with no ModuleX markup.
  </Card>

  <Card title="ModuleX-managed key" icon="sparkles">
    For supported services, use a key ModuleX provides — nothing to set up. Managed usage is metered in [credits](/billing/credits).
  </Card>
</CardGroup>

## Connect a service with OAuth

OAuth lets you connect by signing in to the service, instead of handling secrets yourself. The example below uses GitHub; the steps are the same for any service that offers OAuth.

<Steps>
  <Step title="Open the integrations catalog">
    In the app, go to your organization settings and open **Browse integrations**. Find the service — search by name or filter by category — and open it. See the [integrations overview](/integrations/overview) for a tour of the catalog.

    <MediaEmbed id="MX-MEDIA-4451" type="screenshot" caption={"The Browse integrations catalog with GitHub highlighted."} />
  </Step>

  <Step title="Start the connection">
    Choose **Add credential**, then pick **OAuth** as the method. Give the credential a clear name — for example `Production GitHub` — so you can tell it apart later if you connect more than one account. Turning on **Make default** lets workflows use it automatically.
  </Step>

  <Step title="Sign in and grant access">
    ModuleX sends you to the service's own sign-in and consent screen. Review the access it asks for, then approve. This grant uses OAuth2 with PKCE, an industry-standard secure flow — you stay on the service's site to sign in, and ModuleX never sees your password.

    <MediaEmbed id="MX-MEDIA-4452" type="screenshot" caption={"A third-party OAuth consent screen (for example GitHub) shown during the connect flow."} />
  </Step>

  <Step title="Return to ModuleX">
    After you approve, the service sends you back to ModuleX and the credential is saved automatically. You will see a success message, and the integration now shows as connected. That is it — the service is ready to use across your workflows and the Assistant.
  </Step>
</Steps>

<Check>
  The integration appears under your connected credentials with a name, the OAuth method, and a default badge if you set one. You are ready to [use the tool in a workflow](#use-the-tool-in-a-workflow).
</Check>

### Connect with your own key instead

If a service does not offer OAuth, or you would rather bring your own account, choose **API key** (or **bearer token**) on the same connect screen and paste the value from the service. ModuleX checks the key before saving so you catch a typo early. The secret is encrypted at rest and never shown again in full — only a masked preview.

For the full list of methods a service can offer and how each is stored and resolved, see [integration authentication](/integrations/authentication).

## Use the tool in a workflow

With the service connected, you can call its actions from a workflow. The unit that does this is the [tool node](/workflow-builder/nodes/tool) — it runs one action, with parameters you control, and writes the result into your run so later steps can use it.

<Steps>
  <Step title="Add a tool node">
    Open a workflow in the [workflow builder](/workflow-builder/overview) and add a **tool node** to the canvas.
  </Step>

  <Step title="Pick the integration and action">
    In the node's detail panel, pick the connected integration (for example GitHub) and then the action you want (for example `create_issue`). ModuleX shows the credential it will use — the default for that integration unless you choose another.
  </Step>

  <Step title="Fill in the parameters">
    Enter the action's inputs — for `create_issue`, that is the repository, the title, and the body. You can type fixed values, or pull a value from an earlier step using a `{{node_id.field}}` reference. See [variables & references](/workflow-builder/variables-and-references) for the reference syntax.
  </Step>

  <Step title="Run and check the result">
    Run the workflow. The tool node calls the service, and the action's result is written into run state under the node's own id, ready for the next step. For how runs stream and how failures surface, see [running workflows](/workflow-builder/execution/running).
  </Step>
</Steps>

<Tip>
  Want a step where the AI decides which tools to call, rather than a fixed action? Use the [agent node](/workflow-builder/nodes/agent), or let the [Assistant](/assistant/overview) use your connected tools directly in chat — no workflow required.
</Tip>

## Connect a service from code

You can also create a credential through the API. Authenticate every request with your API key as `Authorization: Bearer mx_live_…` plus the `X-Organization-ID` header for your organization. The example below creates an API-key credential for a service; the response masks the secret.

<Note>
  OAuth connections involve a browser sign-in step, so they are made in the app, not from a single API call. Use the app's connect flow for OAuth, and the API for key-based credentials.
</Note>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.modulex.dev/credentials \
    -H "Authorization: Bearer mx_live_yourkey" \
    -H "X-Organization-ID: 8f2b1c4e-1a2b-4c3d-9e8f-7a6b5c4d3e2f" \
    -H "Content-Type: application/json" \
    -d '{
          "integration_name": "tavily",
          "display_name": "Production Tavily",
          "make_default": true,
          "auth_data": { "api_key": "tvly-your-service-key" }
        }'
  ```

  ```python Python theme={null}
  import httpx

  resp = httpx.post(
      "https://api.modulex.dev/credentials",
      headers={
          "Authorization": "Bearer mx_live_yourkey",
          "X-Organization-ID": "8f2b1c4e-1a2b-4c3d-9e8f-7a6b5c4d3e2f",
      },
      json={
          "integration_name": "tavily",
          "display_name": "Production Tavily",
          "make_default": True,
          "auth_data": {"api_key": "tvly-your-service-key"},
      },
  )
  resp.raise_for_status()
  print(resp.json())
  ```

  ```javascript JavaScript theme={null}
  const resp = await fetch("https://api.modulex.dev/credentials", {
    method: "POST",
    headers: {
      Authorization: "Bearer mx_live_yourkey",
      "X-Organization-ID": "8f2b1c4e-1a2b-4c3d-9e8f-7a6b5c4d3e2f",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      integration_name: "tavily",
      display_name: "Production Tavily",
      make_default: true,
      auth_data: { api_key: "tvly-your-service-key" },
    }),
  });
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
  console.log(await resp.json());
  ```
</CodeGroup>

<Warning>
  Creating and managing credentials requires the **owner** or **admin** role. A request from a non-admin member is rejected. ModuleX never reads the organization from the request body — it always comes from the `X-Organization-ID` header.
</Warning>

For the full credential API — listing, setting a default, testing, and deleting — see [managing credentials](/integrations/managing-credentials).

## Keep a connection working

A connection lasts until the service's access expires or is revoked. For OAuth connections, ModuleX refreshes access automatically in the background as tokens near expiry, so most connections keep working without any action from you.

<Warning>
  If an OAuth connection stops working — for example because access was revoked on the service — **reconnect it** rather than looking for a manual "refresh" button. Connect the service again with OAuth, and set the new credential as the default. A manual one-click OAuth refresh in the app is a [known limitation](/reference/known-limitations) and should not be relied on.
</Warning>

To rotate a key-based credential, connect the service again with the new key, mark it as the default, and delete the old one. See [managing credentials](/integrations/managing-credentials).

## What it costs

How a connected service is billed depends on which method you used to connect it.

<CardGroup cols={2}>
  <Card title="Your own key (BYOK)" icon="key">
    Usage is billed directly by the service provider, with no ModuleX markup. It is not metered in ModuleX credits.
  </Card>

  <Card title="ModuleX-managed key" icon="sparkles">
    Tool calls made through a ModuleX-managed key are metered in [credits](/billing/credits). When your plan's allowance runs out, calls are subject to the usage gate — see [usage gating & limits](/billing/usage-gating).
  </Card>
</CardGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="I do not see the connect or Add credential controls">
    Connecting integrations requires the **owner** or **admin** role for the organization. Switch to the correct organization in the header, or ask an owner or admin to connect the service. See [roles & permissions](/security/roles-permissions).
  </Accordion>

  <Accordion title="The OAuth window returned an error">
    Start the connection again from the catalog. The sign-in step is time-limited, so if you waited too long on the consent screen, the attempt expires and you simply retry. If the service reports that access was denied, approve the requested permissions when prompted.
  </Accordion>

  <Accordion title="My API key was rejected">
    Double-check you copied the full key from the service, with no extra spaces, and that it has the permissions the action needs. ModuleX tests a key before saving, so a rejection here means the service did not accept it.
  </Accordion>

  <Accordion title="The tool node does not list my integration">
    Confirm the credential was saved in the **same organization** you are building the workflow in, and that the integration shows as connected. If you connected more than one account, check which credential is set as the default.
  </Accordion>

  <Accordion title="My run was blocked with a limit or credit error">
    Tools that use a ModuleX-managed key consume [credits](/billing/credits). If your plan's allowance is exhausted, the run is stopped by the usage gate. See [usage gating & limits](/billing/usage-gating) and [errors & troubleshooting](/help/errors-troubleshooting).
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Integrations overview" icon="plug" href="/integrations/overview">
    What integrations are, how the catalog is organized, and how to browse 175 services.
  </Card>

  <Card title="Tool node" icon="wrench" href="/workflow-builder/nodes/tool">
    The full reference for calling one integration action from a workflow.
  </Card>

  <Card title="Managing credentials" icon="id-card" href="/integrations/managing-credentials">
    List, set defaults for, test, and delete the credentials your organization has connected.
  </Card>

  <Card title="Integration authentication" icon="lock-keyhole" href="/integrations/authentication">
    The connect methods a service can offer and how each secret is stored and resolved.
  </Card>
</CardGroup>
