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

# SFTP Connection

> How to connect to the Mention Me SFTP server, including authentication, folder structure, and file format requirements

export const SignInNotice = ({message = "To see code snippets pre-filled with your partner code"}) => {
  return <div data-mm-sign-in style={{
    display: "none"
  }}>
      <Info>
        {message}, <a data-mm-sign-in-link href="https://app.mention-me.com/sign-in"><strong>sign in</strong></a>.
      </Info>
    </div>;
};

export const SFTPFolders = () => {
  const [sftp, setSftp] = useState(null);
  const extractSftp = data => {
    if (!data || !data.sftp) return null;
    const {inboundFolders, outboundFolders} = data.sftp;
    if (!inboundFolders.length && !outboundFolders.length) return null;
    return {
      inboundFolders,
      outboundFolders
    };
  };
  useEffect(() => {
    if (!document.cookie.includes("__client_uat")) return;
    const existing = extractSftp(window.__mmMerchantContext);
    if (existing) {
      setSftp(existing);
      return;
    }
    const handler = e => {
      const result = extractSftp(e.detail);
      if (result) setSftp(result);
    };
    window.addEventListener("mm-merchant-ready", handler);
    return () => window.removeEventListener("mm-merchant-ready", handler);
  }, []);
  const buildTree = folders => {
    const root = new Map();
    for (const folder of folders) {
      const parts = folder.path.replace(/\/$/, "").split("/").filter(Boolean);
      let level = root;
      parts.forEach((part, i) => {
        if (!level.has(part)) level.set(part, {
          children: new Map()
        });
        const node = level.get(part);
        if (i === parts.length - 1 && folder.description) {
          node.description = folder.description;
        }
        level = node.children;
      });
    }
    return root;
  };
  const renderTree = level => [...level].map(([name, node]) => {
    const label = node.description ? name + " - " + node.description : name;
    return <Tree.Folder key={name} name={label} defaultOpen>
          {node.children.size > 0 ? renderTree(node.children) : null}
        </Tree.Folder>;
  });
  if (sftp) {
    const inbound = buildTree(sftp.inboundFolders);
    const outbound = buildTree(sftp.outboundFolders);
    return <Tree key="live">
        {inbound.size > 0 ? <Tree.Folder name="Inbound (you → Mention Me)" defaultOpen>
            {renderTree(inbound)}
          </Tree.Folder> : null}
        {outbound.size > 0 ? <Tree.Folder name="Outbound (Mention Me → you)" defaultOpen>
            {renderTree(outbound)}
          </Tree.Folder> : null}
      </Tree>;
  }
  return <Tree key="placeholder">
      <Tree.Folder name="client_account_slug" defaultOpen>
        <Tree.Folder name="merchant_slug" defaultOpen>
          <Tree.Folder name="demo" defaultOpen>
            <Tree.Folder name="folder_name" defaultOpen>
              <Tree.Folder name="data" />
            </Tree.Folder>
          </Tree.Folder>
          <Tree.Folder name="prod" defaultOpen>
            <Tree.Folder name="folder_name" defaultOpen>
              <Tree.Folder name="data" />
            </Tree.Folder>
          </Tree.Folder>
        </Tree.Folder>
      </Tree.Folder>
    </Tree>;
};

export const MerchantContext = () => {
  const PARTNER_STORAGE_KEY = "mm-docs-partner";
  const escapeHtml = str => str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
  const getStoredPartner = () => {
    try {
      const raw = localStorage.getItem(PARTNER_STORAGE_KEY);
      if (!raw) return null;
      const data = JSON.parse(raw);
      if (data && data.partnerCode) return data;
    } catch (e) {}
    return null;
  };
  const setStoredPartner = data => {
    try {
      localStorage.setItem(PARTNER_STORAGE_KEY, JSON.stringify(data));
    } catch (e) {}
  };
  const clearStoredPartner = () => {
    try {
      localStorage.removeItem(PARTNER_STORAGE_KEY);
    } catch (e) {}
  };
  const PERSONALISATION_OFF_KEY = "mm-docs-personalisation-off";
  const isPersonalisationOff = () => {
    try {
      return localStorage.getItem(PERSONALISATION_OFF_KEY) === "1";
    } catch (e) {
      return false;
    }
  };
  const setPersonalisationOff = () => {
    try {
      localStorage.setItem(PERSONALISATION_OFF_KEY, "1");
    } catch (e) {}
  };
  const clearPersonalisationOff = () => {
    try {
      localStorage.removeItem(PERSONALISATION_OFF_KEY);
    } catch (e) {}
  };
  const captureQueryParams = () => {
    const params = new URLSearchParams(window.location.search);
    const partner = params.get("partner");
    const name = params.get("name");
    if (partner) {
      const data = {
        partnerCode: partner
      };
      if (name) data.displayName = name;
      setStoredPartner(data);
      params.delete("partner");
      params.delete("name");
      const qs = params.toString();
      const clean = window.location.pathname + (qs ? "?" + qs : "") + window.location.hash;
      window.history.replaceState(null, "", clean);
      return data;
    }
    return getStoredPartner();
  };
  const isLocal = typeof window !== "undefined" && window.location.hostname === "localhost";
  const API_BASE = isLocal ? "http://localhost:3001" : "https://app.mention-me.com";
  const SIGN_IN_BASE = isLocal ? "http://localhost:3001/sign-in" : "https://app.mention-me.com/sign-in";
  const MERCHANT_WINDOW_KEY = "__mmMerchantContext";
  const publishMerchant = data => {
    window[MERCHANT_WINDOW_KEY] = data;
    window.dispatchEvent(new CustomEvent("mm-merchant-ready", {
      detail: data
    }));
  };
  const clearMerchant = () => {
    delete window[MERCHANT_WINDOW_KEY];
  };
  const BADGE_ID = "mm-partner-badge";
  const showPartnerBadge = (partnerData, source) => {
    if (document.getElementById(BADGE_ID)) return;
    const nav = document.getElementById("navigation-items");
    if (!nav || !nav.parentNode) return;
    const badge = document.createElement("div");
    badge.id = BADGE_ID;
    badge.className = "mm-partner-badge";
    const hasName = partnerData.displayName && partnerData.displayName !== partnerData.partnerCode;
    const heading = hasName ? escapeHtml(partnerData.displayName) : escapeHtml(partnerData.partnerCode);
    const code = hasName ? escapeHtml(partnerData.partnerCode) : "";
    const codeStr = code ? ' <span class="mm-partner-badge-code">' + code + '</span>' : '';
    const canSwitch = source === "clerk" && partnerData.hasMultipleWorkspaces;
    const switchBtn = canSwitch ? '<button class="mm-partner-badge-switch" title="Switch to another workspace">Switch workspace</button>' : '';
    badge.innerHTML = '<div class="mm-partner-badge-inner">' + '<span class="mm-partner-badge-content">' + '<span class="mm-partner-badge-context">Personalised for </span>' + '<span class="mm-partner-badge-name">' + heading + '</span>' + codeStr + '</span>' + switchBtn + '<button class="mm-partner-badge-reset" title="Clear personalisation">&times;</button>' + '</div>';
    nav.parentNode.insertBefore(badge, nav);
    if (canSwitch) {
      badge.querySelector(".mm-partner-badge-switch").addEventListener("click", e => {
        e.preventDefault();
        e.stopPropagation();
        window.location.href = API_BASE + "/select-org?redirect_url=" + encodeURIComponent(window.location.href);
      });
    }
    badge.querySelector(".mm-partner-badge-reset").addEventListener("click", e => {
      e.preventDefault();
      e.stopPropagation();
      if (source === "clerk") {
        setPersonalisationOff();
      }
      clearMerchant();
      clearStoredPartner();
      window.location.reload();
    });
  };
  const showOffBadge = () => {
    if (document.getElementById(BADGE_ID)) return;
    const nav = document.getElementById("navigation-items");
    if (!nav || !nav.parentNode) return;
    const badge = document.createElement("div");
    badge.id = BADGE_ID;
    badge.className = "mm-partner-badge";
    badge.innerHTML = '<div class="mm-partner-badge-inner">' + '<span class="mm-partner-badge-content">' + '<span class="mm-partner-badge-context">Personalisation off. </span>' + '<button class="mm-partner-badge-reenable" title="Personalise these docs for your account">Personalise</button>' + '</span>' + '</div>';
    nav.parentNode.insertBefore(badge, nav);
    badge.querySelector(".mm-partner-badge-reenable").addEventListener("click", e => {
      e.preventDefault();
      e.stopPropagation();
      clearPersonalisationOff();
      window.location.href = API_BASE + "/select-org?redirect_url=" + encodeURIComponent(window.location.href);
    });
  };
  const removePartnerBadge = () => {
    const el = document.getElementById(BADGE_ID);
    if (el) el.remove();
  };
  const showNotices = () => {
    document.querySelectorAll("[data-mm-sign-in]").forEach(el => {
      el.style.display = "";
    });
    const redirect = SIGN_IN_BASE + "?redirect_url=" + encodeURIComponent(window.location.href);
    document.querySelectorAll("[data-mm-sign-in-link]").forEach(a => {
      a.href = redirect;
    });
  };
  const hideNotices = () => {
    document.querySelectorAll("[data-mm-sign-in]").forEach(el => {
      el.style.display = "none";
    });
  };
  const walkCodeBlocks = replacements => {
    document.querySelectorAll("pre code").forEach(codeEl => {
      let html = codeEl.innerHTML;
      if (replacements) {
        for (const [from, to] of replacements) {
          html = html.split(from).join(escapeHtml(to));
        }
      }
      const paramStyle = "color:inherit;filter:brightness(110%);font-weight:bold;text-decoration:underline;cursor:pointer";
      html = html.replace(/(\?|&amp;)([a-z_]+)=/g, (match, prefix, name) => {
        const slug = name.replace(/_/g, "-");
        const target = document.getElementById("param-" + slug);
        if (!target) return match;
        return prefix + '<a href="#param-' + slug + '" style="' + paramStyle + '">' + name + "</a>=";
      });
      codeEl.innerHTML = html;
    });
  };
  const observeTabSwitches = callback => {
    let processing = false;
    const observer = new MutationObserver(() => {
      if (processing) return;
      processing = true;
      setTimeout(() => {
        callback();
        processing = false;
      }, 50);
    });
    setTimeout(() => {
      const tabs = document.querySelector("[role='tablist']");
      if (tabs && tabs.parentElement) {
        observer.observe(tabs.parentElement, {
          attributes: true,
          subtree: true,
          childList: true
        });
      }
    }, 500);
  };
  const pollForClerk = (onReady, onTimeout) => {
    let n = 0;
    const tick = () => {
      if (window.Clerk && window.Clerk.loaded) {
        return onReady(window.Clerk);
      }
      if (++n < 40) {
        setTimeout(tick, 250);
      } else {
        onTimeout();
      }
    };
    setTimeout(tick, 100);
  };
  const fetchMerchantFromAPI = () => {
    return fetch(API_BASE + "/api/docs/context", {
      credentials: "include",
      cache: "no-store"
    }).then(res => res.ok ? res.json() : null);
  };
  useEffect(() => {
    let cancelled = false;
    const hadFreshParam = new URLSearchParams(window.location.search).has("partner");
    const urlPartner = captureQueryParams();
    if (hadFreshParam) {
      clearPersonalisationOff();
    }
    const onPersonalised = (merchant, source) => {
      if (cancelled) return;
      const replacements = [["YOUR_PARTNER_CODE", merchant.partnerCode]];
      if (merchant.displayName) {
        replacements.push(["YOUR_TRADING_NAME", merchant.displayName]);
      }
      hideNotices();
      showPartnerBadge(merchant, source);
      walkCodeBlocks(replacements);
      observeTabSwitches(() => walkCodeBlocks(replacements));
    };
    const onUnauth = () => {
      if (cancelled) return;
      clearMerchant();
      removePartnerBadge();
      showNotices();
      walkCodeBlocks(null);
      observeTabSwitches(() => walkCodeBlocks(null));
    };
    const tryUrlFallback = () => {
      if (cancelled) return;
      if (urlPartner) {
        onPersonalised(urlPartner, "url");
      } else {
        onUnauth();
      }
    };
    if (isPersonalisationOff()) {
      if (!cancelled) {
        hideNotices();
        showOffBadge();
        walkCodeBlocks(null);
        observeTabSwitches(() => walkCodeBlocks(null));
      }
      return () => {
        cancelled = true;
      };
    }
    if (!document.cookie.includes("__client_uat")) {
      tryUrlFallback();
      return;
    }
    pollForClerk(clerk => {
      if (cancelled) return;
      if (!clerk.session) {
        tryUrlFallback();
        return;
      }
      fetchMerchantFromAPI().then(data => {
        if (data) {
          publishMerchant(data);
          onPersonalised(data, "clerk");
        } else {
          tryUrlFallback();
        }
      }).catch(() => tryUrlFallback());
    }, () => {
      if (!cancelled) tryUrlFallback();
    });
    return () => {
      cancelled = true;
    };
  }, []);
  return null;
};

export const VersionLink = ({href, children}) => {
  return <a href={href}>{children}</a>;
};

export const setupHref_0 = "/knowledge-app/settings/integrations/sftp-setup"

export const sshKeysHref_0 = "/knowledge-app/settings/integrations/ssh-keys"

export const sshKeysLabel_0 = "SSH keys & IP settings"

export const encryptionKeysHref_0 = "/knowledge-app/settings/integrations/encryption-keys"

export const encryptionKeysLabel_0 = "Encryption keys settings"

export const encryptionHref_0 = "/developer-docs/integration/mention-me/sftp-encryption"

export const dataFormatsHref_0 = "/developer-docs/integration/mention-me/sftp-data-formats"

<MerchantContext />

## Overview

Mention Me provides an SFTP server for exchanging data files. This page describes how to connect, the folder structure, and requirements for data transfer.

<Note>
  To set up SFTP, follow the <VersionLink href={setupHref_0}>SFTP Setup</VersionLink> guide.
</Note>

## Authentication

To connect to the Mention Me SFTP server, you need:

1. **One or more SSH public keys** - provided via the <VersionLink href={sshKeysHref_0}>{sshKeysLabel_0}</VersionLink>
2. **One or more IP address ranges** - must be well-defined and restricted; broad cloud provider ranges (e.g. AWS) cannot be whitelisted

## Connection Details

| Setting | Value |
| - | - |
| **Host** | `sftp.mention-me.com` |
| **Username** | Your client account slug (visible in the platform) |
| **Authentication** | SSH key |

Example connection command (Linux/Mac):

```bash theme={null}
sftp -i /path/to/private.key your-account-slug@sftp.mention-me.com
```

You must connect from an IP address range that has been authorised for your account.

## Folder Structure

<SignInNotice message="To see your SFTP folder structure" />

<SFTPFolders />

<Note>
  Test or non-production data must always be placed within the `demo` tree. Production data must only be placed within the `prod` tree. Placing test data in `prod` (or vice versa) may result in unexpected behaviour such as customers being emailed or additional commission fees being charged.
</Note>

Data to be received by Mention Me, or data extracted by Mention Me for you, must be placed within the `data` subfolder for the relevant data format.

## Data Retention

Files uploaded to the SFTP server are retained for **7 days** before being purged. Pick them up (or replace them) within that window.

## Optional File Encryption

Files transferred via SFTP can be encrypted with PGP in both directions - files you send to Mention Me, and files Mention Me delivers to you. You can encrypt inbound files with Mention Me's shared public key, or generate and manage your own dedicated keys yourself from the <VersionLink href={encryptionKeysHref_0}>{encryptionKeysLabel_0}</VersionLink>.

For full details, see <VersionLink href={encryptionHref_0}>SFTP File Encryption</VersionLink>.

## Ingest File Format

Mention Me is strict about the format of data that can be ingested. See <VersionLink href={dataFormatsHref_0}>SFTP Data Formats</VersionLink> for full documentation on accepted file formats.

## Export File Format

Discuss with your Client Success Manager which data feeds you'd like to receive from Mention Me and we can provide example files.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.