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

# Google Drive

> 64 actions available for Google Drive through StackOne. Use via Actions RPC, Toolset SDK, MCP, or A2A.

export const GettingStarted = ({connector}) => {
  const linkStyle = {
    textDecoration: 'none'
  };
  const authConfigGuides = connector?.authentication?.filter(a => a?.setupGuide) ?? [];
  const linkAccountGuides = connector?.authentication?.filter(a => a?.configGuide) ?? [];
  return <div style={{
    marginTop: '32px',
    paddingTop: '24px'
  }} className="getting-started-section">
      <div style={{
    fontSize: '20px',
    fontWeight: '600',
    marginBottom: '12px'
  }}>Getting Started</div>
      <Steps>
        <Step title="Create or Select a Project">
          Set up a new project or select an existing one. See the <a href="/guides/managing-projects" style={linkStyle}>Projects Guide</a>.
        </Step>
        <Step title="Configure the Connector">
          <>
            Enable the connector and set up auth configuration in your project. See <a href="/guides/explore-connectors" style={linkStyle}>Managing Connectors</a>.
            {authConfigGuides.length > 0 && <>
              <Columns cols={2}>
                {authConfigGuides.map(auth => <Card title="Auth Config" href={`/${auth.setupGuide}`} icon={connector.icon} horizontal>
                    {connector.name} - {auth.label}
                  </Card>)}
              </Columns> 
              </>}
          </>
        </Step>
        <Step title="Link an Account">
          <>
            Connect an account using <a href="/guides/embedding-stackone-hub" style={linkStyle}>StackOne Hub</a> or <a href="/guides/auth-link" style={linkStyle}>Auth Link</a>.
            {linkAccountGuides.length > 0 && <>
                <Columns cols={2}>
                  {linkAccountGuides.map(auth => <Card title="Link Account" href={`/${auth.configGuide}`} icon={connector.icon} horizontal>
                      {connector.name} - {auth.label}
                    </Card>)}
                </Columns>
              </>}
          </>
        </Step>
        <Step title="Use Actions">
          <>
            Invoke actions using one of the methods below:
            <ul style={{
    marginTop: '8px',
    paddingLeft: '20px'
  }}>
              <li style={{
    marginBottom: '4px'
  }}><a href="/mcp/quickstart" style={linkStyle}>MCP</a> – Model Context Protocol for AI assistants</li>
              <li style={{
    marginBottom: '4px'
  }}><a href="/a2a/quickstart" style={linkStyle}>A2A</a> – Agent-to-Agent protocol</li>
              <li style={{
    marginBottom: '4px'
  }}><a href="/agents/typescript/introduction" style={linkStyle}>AI Toolset (TypeScript)</a> – TypeScript SDK for AI agents</li>
              <li style={{
    marginBottom: '4px'
  }}><a href="/agents/python/introduction" style={linkStyle}>AI Toolset (Python)</a> – Python SDK for AI agents</li>
              <li style={{
    marginBottom: '4px'
  }}><a href="/platform/api-reference/actions/make-an-rpc-call-to-an-action" style={linkStyle}>Actions RPC</a> – Direct API calls</li>
              <li style={{
    marginBottom: '4px'
  }}><a href="/guides/playground" style={linkStyle}>Playground</a> – Test actions in the dashboard</li>
            </ul>
          </>
        </Step>
      </Steps>
    </div>;
};

export const ActionsLibrary = ({search, setSearch, filtered, sharedStyles = {}, KeyCell, ScopesCell, availableScopes, selectedScopes, setSelectedScopes}) => {
  const libraryStyles = {
    section: {
      marginTop: '24px',
      marginBottom: '16px'
    },
    sectionTitle: {
      fontSize: '18px',
      fontWeight: '600',
      marginBottom: '12px'
    },
    filterRow: {
      display: 'flex',
      gap: '12px',
      marginBottom: '12px',
      alignItems: 'stretch'
    },
    count: {
      fontSize: '12px',
      marginBottom: '8px'
    },
    tableContainer: {
      maxHeight: '500px',
      overflowY: 'auto',
      overflowX: 'auto',
      borderRadius: '8px',
      fontSize: '13px'
    },
    gridTable: {
      display: 'grid',
      minWidth: '600px',
      gridTemplateColumns: '200px 1fr'
    },
    gridTableWithScopes: {
      gridTemplateColumns: '200px 1fr 150px'
    },
    gridHeader: {
      display: 'contents'
    },
    gridHeaderCell: {
      position: 'sticky',
      top: 0,
      padding: '10px 12px',
      fontWeight: '600',
      zIndex: 1
    },
    gridRow: {
      display: 'contents'
    },
    gridCellAction: {
      padding: '10px 12px',
      fontWeight: '500'
    },
    gridCellKey: {
      padding: '10px 12px'
    },
    gridCellScopes: {
      padding: '10px 12px',
      fontSize: '12px'
    },
    gridCellDescription: {
      padding: '10px 12px'
    }
  };
  const styles = {
    ...libraryStyles,
    ...sharedStyles
  };
  const safeAvailableScopes = Array.isArray(availableScopes) ? availableScopes : [];
  const safeSelectedScopes = Array.isArray(selectedScopes) ? selectedScopes : [];
  const hasScopesColumn = safeAvailableScopes.length > 0;
  const hasScopeFilter = safeAvailableScopes.length > 0 && typeof setSelectedScopes === 'function';
  const ScopesCellComponent = ScopesCell || (() => null);
  return <div style={styles.section}>
      <div style={styles.sectionTitle}>Actions</div>
      <div style={styles.filterRow}>
        <SearchBar value={search} onChange={setSearch} placeholder="Search actions" />
        {hasScopeFilter && <FilterDropdown label="Scopes" items={safeAvailableScopes} selectedItems={safeSelectedScopes} onChange={setSelectedScopes} searchPlaceholder="Search scopes..." emptyLabel="No scopes found" />}
      </div>
      <div style={styles.count} className="actions-library-count">
        {filtered.length === 0 ? '0 actions found' : `${filtered.length} action${filtered.length !== 1 ? 's' : ''}`}
      </div>
      {}
      <div className="not-prose actions-library-table-container" style={styles.tableContainer}>
        <div style={{
    ...hasScopesColumn ? {
      ...styles.gridTable,
      ...styles.gridTableWithScopes
    } : styles.gridTable
  }}>
          {}
          <div style={styles.gridHeader}>
            <div style={styles.gridHeaderCell} className="actions-library-grid-header-cell">Action</div>
            <div style={styles.gridHeaderCell} className="actions-library-grid-header-cell">Description</div>
            {hasScopesColumn && <div style={styles.gridHeaderCell} className="actions-library-grid-header-cell">Required scopes</div>}
          </div>
          {}
          {filtered.map(a => <div key={a.id} style={styles.gridRow}>
              <div style={styles.gridCellAction} className="actions-library-grid-cell">
                <div>{a.label}</div>
                <div style={{
    marginTop: '4px'
  }}>
                  <KeyCell id={a.id} />
                </div>
              </div>
              <div style={styles.gridCellDescription} className="actions-library-grid-cell actions-library-grid-cell--description">{a.description}</div>
              {hasScopesColumn && <div style={styles.gridCellScopes} className="actions-library-grid-cell actions-library-grid-cell--scopes">
                  <ScopesCellComponent scopes={a.requiredScopes} actionId={a.id} />
                </div>}
            </div>)}
        </div>
      </div>
    </div>;
};

export const FilterDropdown = ({label, items, selectedItems, onChange, searchPlaceholder = 'Search...', emptyLabel = 'No items found', formatLabel}) => {
  const [dropdownOpen, setDropdownOpen] = React.useState(false);
  const [search, setSearch] = React.useState('');
  const [hoveredItem, setHoveredItem] = React.useState(null);
  const dropdownRef = React.useRef(null);
  const styles = {
    dropdownContainer: {
      position: 'relative'
    },
    dropdownTrigger: {
      display: 'flex',
      alignItems: 'center',
      gap: '8px',
      padding: '10px 14px',
      borderRadius: '8px',
      fontSize: '14px',
      cursor: 'pointer',
      whiteSpace: 'nowrap',
      minWidth: '160px',
      justifyContent: 'space-between'
    },
    dropdownMenu: {
      position: 'absolute',
      top: '100%',
      right: 0,
      marginTop: '4px',
      borderRadius: '8px',
      zIndex: 50,
      minWidth: '220px',
      maxHeight: '320px',
      display: 'flex',
      flexDirection: 'column'
    },
    dropdownHeader: {
      padding: '8px 12px'
    },
    selectActions: {
      display: 'flex',
      alignItems: 'center',
      gap: '8px',
      marginTop: '6px',
      fontSize: '12px'
    },
    selectActionBtn: {
      background: 'none',
      border: 'none',
      cursor: 'pointer',
      padding: '2px 4px',
      fontSize: '12px'
    },
    dropdownSearchInput: {
      width: '100%',
      padding: '8px 10px',
      borderRadius: '6px',
      fontSize: '13px',
      outline: 'none'
    },
    dropdownList: {
      overflowY: 'auto',
      maxHeight: '220px',
      padding: '4px 0'
    },
    dropdownItem: {
      display: 'flex',
      alignItems: 'center',
      gap: '10px',
      padding: '8px 12px',
      cursor: 'pointer',
      fontSize: '13px',
      transition: 'background-color 0.1s'
    },
    checkbox: {
      width: '16px',
      height: '16px',
      borderRadius: '4px',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      flexShrink: 0
    },
    checkmark: {
      color: 'white',
      fontSize: '10px',
      fontWeight: 'bold'
    },
    dropdownFooter: {
      padding: '8px 12px',
      display: 'flex',
      justifyContent: 'space-between',
      alignItems: 'center'
    },
    clearButton: {
      fontSize: '12px',
      cursor: 'pointer',
      padding: '4px 8px',
      borderRadius: '4px',
      border: 'none',
      background: 'none'
    },
    badge: {
      fontSize: '11px',
      fontWeight: '600',
      padding: '2px 6px',
      borderRadius: '10px',
      marginLeft: '4px'
    },
    chevron: {
      fontSize: '10px',
      transition: 'transform 0.15s'
    },
    noResults: {
      padding: '12px',
      textAlign: 'center',
      fontSize: '13px'
    }
  };
  const safeSelected = Array.isArray(selectedItems) ? selectedItems : [];
  const formatItemLabel = item => {
    if (typeof formatLabel === 'function') {
      return formatLabel(item);
    }
    return item;
  };
  const filteredItems = React.useMemo(() => {
    if (!search) return items;
    const searchLower = search.toLowerCase();
    return items.filter(item => {
      const labelText = formatItemLabel(item);
      return labelText.toLowerCase().includes(searchLower);
    });
  }, [items, search, formatLabel]);
  const isSelected = item => safeSelected.includes(item);
  const toggleItem = item => {
    const next = isSelected(item) ? safeSelected.filter(v => v !== item) : [...safeSelected, item];
    onChange(next);
  };
  const clearFilters = () => {
    onChange([]);
  };
  const selectAll = () => {
    onChange(items);
  };
  React.useEffect(() => {
    const handleClickOutside = e => {
      if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
        setDropdownOpen(false);
        setSearch('');
        setHoveredItem(null);
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, []);
  return <div style={styles.dropdownContainer} ref={dropdownRef}>
      <button type="button" onClick={() => setDropdownOpen(!dropdownOpen)} style={styles.dropdownTrigger} className={`filter-dropdown__trigger${safeSelected.length > 0 ? ' filter-dropdown__trigger--active' : ''}`}>
        <span>
          {label}
          {safeSelected.length > 0 && <span style={styles.badge} className="filter-dropdown__badge">{safeSelected.length}</span>}
        </span>
        <span style={{
    ...styles.chevron,
    transform: dropdownOpen ? 'rotate(180deg)' : 'rotate(0deg)'
  }} className="filter-dropdown__chevron">
          ▼
        </span>
      </button>
      {dropdownOpen && <div style={styles.dropdownMenu} className="filter-dropdown__menu">
          <div style={styles.dropdownHeader} className="filter-dropdown__header">
            <input type="text" placeholder={searchPlaceholder} value={search} onChange={e => setSearch(e.target.value)} style={styles.dropdownSearchInput} className="filter-dropdown__search" autoFocus />
            <div style={styles.selectActions}>
              <button type="button" onClick={selectAll} style={styles.selectActionBtn} className="filter-dropdown__action-btn">
                Select all
              </button>
              <span className="filter-dropdown__separator">|</span>
              <button type="button" onClick={clearFilters} style={styles.selectActionBtn} className="filter-dropdown__action-btn">
                Clear
              </button>
            </div>
          </div>
          <div style={styles.dropdownList}>
            {filteredItems.length === 0 ? <div style={styles.noResults} className="filter-dropdown__no-results">{emptyLabel}</div> : filteredItems.map(item => {
    const itemClass = ['filter-dropdown__item', hoveredItem === item ? 'filter-dropdown__item--hovered' : '', isSelected(item) ? 'filter-dropdown__item--selected' : ''].filter(Boolean).join(' ');
    const checkboxClass = `filter-dropdown__checkbox${isSelected(item) ? ' filter-dropdown__checkbox--checked' : ''}`;
    return <div key={item} onClick={() => toggleItem(item)} onMouseEnter={() => setHoveredItem(item)} onMouseLeave={() => setHoveredItem(null)} style={styles.dropdownItem} className={itemClass}>
                    <div style={styles.checkbox} className={checkboxClass}>
                      {isSelected(item) && <span style={styles.checkmark}>✓</span>}
                    </div>
                    <span>{formatItemLabel(item)}</span>
                  </div>;
  })}
          </div>
        </div>}
    </div>;
};

export const SearchBar = ({value, onChange, placeholder = 'Search...'}) => {
  const baseStyle = {
    padding: '10px 14px',
    borderRadius: '8px',
    fontSize: '14px',
    outline: 'none',
    flex: 1,
    minWidth: 0
  };
  return <input type="text" placeholder={placeholder} value={value} onChange={e => onChange(e.target.value)} style={baseStyle} className="search-bar" />;
};

export const ConnectorPage = ({connector}) => {
  const styles = {
    header: {
      display: 'flex',
      alignItems: 'center',
      gap: '12px',
      marginBottom: '24px'
    },
    tagsRow: {
      display: 'flex',
      flexWrap: 'wrap',
      gap: '6px',
      marginTop: '8px'
    },
    releaseTag: {
      display: 'inline-block',
      padding: '2px 8px',
      borderRadius: '4px',
      fontSize: '11px',
      fontWeight: '500'
    },
    categoryTag: {
      display: 'inline-block',
      padding: '2px 8px',
      borderRadius: '4px',
      fontSize: '11px',
      fontWeight: '500'
    },
    icon: {
      width: '48px',
      height: '48px',
      borderRadius: '10px',
      padding: '2px'
    },
    title: {
      fontSize: '24px',
      fontWeight: '600'
    },
    subtitle: {
      fontSize: '14px'
    },
    authDescription: {
      fontSize: '14px',
      marginTop: 0
    },
    sectionTitle: {
      fontSize: '18px',
      fontWeight: '600',
      marginBottom: '12px'
    },
    codeWrapper: {
      position: 'relative',
      display: 'inline-flex',
      alignItems: 'center',
      maxWidth: '100%',
      cursor: 'pointer',
      overflowX: 'auto',
      overflowY: 'hidden',
      msOverflowStyle: 'none',
      scrollbarWidth: 'none'
    },
    code: {
      padding: '4px 8px',
      borderRadius: '4px',
      fontSize: '11px',
      display: 'inline-block',
      whiteSpace: 'nowrap',
      transition: 'background-color 0.15s'
    },
    tooltip: {
      position: 'absolute',
      top: '100%',
      left: '50%',
      transform: 'translateX(-50%)',
      padding: '6px 10px',
      borderRadius: '6px',
      fontSize: '11px',
      whiteSpace: 'nowrap',
      marginTop: '6px',
      zIndex: 50,
      maxWidth: '300px',
      wordBreak: 'break-all'
    },
    tooltipArrow: {
      position: 'absolute',
      bottom: '100%',
      left: '50%',
      transform: 'translateX(-50%)',
      borderWidth: '5px',
      borderStyle: 'solid',
      zIndex: 50
    }
  };
  const [search, setSearch] = React.useState('');
  const [copiedId, setCopiedId] = React.useState(null);
  const [selectedScopes, setSelectedScopes] = React.useState([]);
  const availableScopes = React.useMemo(() => {
    const set = new Set();
    connector.actions.forEach(a => {
      if (Array.isArray(a.requiredScopes)) {
        a.requiredScopes.forEach(s => {
          if (s) set.add(s);
        });
      }
    });
    return Array.from(set).sort();
  }, [connector.actions]);
  const filtered = React.useMemo(() => {
    const searchLower = search.toLowerCase();
    return connector.actions.filter(a => {
      const matchesSearch = a.label.toLowerCase().includes(searchLower) || a.description.toLowerCase().includes(searchLower) || a.id.toLowerCase().includes(searchLower);
      if (!matchesSearch) return false;
      if (selectedScopes.length === 0) return true;
      const scopes = Array.isArray(a.requiredScopes) ? a.requiredScopes : [];
      if (scopes.length === 0) return false;
      return selectedScopes.some(scope => scopes.includes(scope));
    });
  }, [connector.actions, search, selectedScopes]);
  const handleImageError = e => {
    e.target.style.display = 'none';
  };
  const formatCategoryLabel = cat => {
    const acronyms = ['ai', 'ats', 'crm', 'hris', 'iam', 'lms'];
    if (acronyms.includes(cat.toLowerCase())) {
      return cat.toUpperCase();
    }
    return cat.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
  };
  const ReleaseTag = () => {
    if (!connector.releaseStage || connector.releaseStage === 'ga') return null;
    const variantClass = connector.releaseStage === 'beta' ? 'connector-page-tag-beta' : 'connector-page-tag-preview';
    return <span style={styles.releaseTag} className={variantClass}>
        {connector.releaseStage.charAt(0).toUpperCase() + connector.releaseStage.slice(1)}
      </span>;
  };
  const CategoryTags = () => {
    if (!connector.categories || connector.categories.length === 0) return null;
    return <>
        {connector.categories.map(cat => <span key={cat} style={styles.categoryTag} className="connector-page-category-tag">
            {formatCategoryLabel(cat)}
          </span>)}
      </>;
  };
  const copyToClipboard = async (text, id) => {
    try {
      await navigator.clipboard.writeText(text);
      setCopiedId(id);
      setTimeout(() => setCopiedId(null), 1500);
    } catch (err) {
      console.error('Failed to copy:', err);
    }
  };
  const CopyChip = ({text, copyId}) => {
    const [isHovered, setIsHovered] = React.useState(false);
    const isCopied = copiedId === copyId;
    if (!text) {
      return null;
    }
    const codeClassName = `connector-page-code${isCopied ? ' connector-page-code--copied' : ''}`;
    return <div style={styles.codeWrapper} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} onClick={() => copyToClipboard(text, copyId)} title="">
        {(isHovered || isCopied) && <div style={styles.tooltip} className="connector-page-tooltip">
            {isCopied ? '✓ Copied!' : 'Click to copy'}
            <div style={styles.tooltipArrow} className="connector-page-tooltip-arrow" />
          </div>}
        <code style={styles.code} className={codeClassName}>
          {text}
        </code>
      </div>;
  };
  const KeyCell = ({id}) => <CopyChip text={id} copyId={id} />;
  const ScopesCell = ({scopes, actionId}) => {
    const scopeList = Array.isArray(scopes) ? scopes.filter(Boolean) : [];
    if (scopeList.length === 0) return null;
    return <div style={{
      display: 'flex',
      flexWrap: 'wrap',
      gap: '6px'
    }}>
        {scopeList.map(scope => <CopyChip key={scope} text={scope} copyId={`scopes:${actionId}:${scope}`} />)}
      </div>;
  };
  return <div>
      {}
      <div style={styles.header}>
        <img src={connector.icon} alt={connector.name} style={styles.icon} className="connector-page-icon" onError={handleImageError} />
        <div>
          <div style={styles.title}>{connector.name}</div>
          <div style={styles.subtitle} className="connector-page-subtitle">
            {connector.actions.length} actions · {connector.authentication.length} auth method{connector.authentication.length !== 1 ? 's' : ''}
          </div>
          {connector.releaseStage && connector.releaseStage !== 'ga' || connector.categories && connector.categories.length > 0 ? <div style={styles.tagsRow}>
              <ReleaseTag />
              <CategoryTags />
            </div> : null}
        </div>
      </div>

      {}
      <div style={styles.sectionTitle}>Authentication</div>
      <Columns cols={2}>        
        {connector.authentication.length > 0 ? connector.authentication.map(auth => {
    const authLabel = auth?.label || '';
    const authDescription = auth?.description;
    const configGuide = auth?.configGuide;
    const setupGuide = auth?.setupGuide;
    const hasConfigGuide = !!configGuide;
    const hasSetupGuide = !!setupGuide;
    const hasAnyGuide = hasConfigGuide || hasSetupGuide;
    return <Card key={authLabel} title={authLabel}>
                {authDescription && <p style={{
      ...styles.authDescription,
      marginBottom: hasAnyGuide ? '8px' : 0
    }} className="connector-page-auth-description">
                    {authDescription}
                  </p>}
                {hasAnyGuide && <span style={{
      fontSize: '14px'
    }}>
                    Guides: {hasSetupGuide && <a href={`/${setupGuide}`} style={{
      textDecoration: 'none'
    }}>Auth Config</a>}
                    {hasConfigGuide && hasSetupGuide && ', '}
                    {hasConfigGuide && <a href={`/${configGuide}`} style={{
      textDecoration: 'none'
    }}>Link Account</a>}
                  </span>}
              </Card>;
  }) : <>
            Contact StackOne for authentication details.
          </>}
      </Columns>

      <ActionsLibrary search={search} setSearch={setSearch} filtered={filtered} sharedStyles={{
    sectionTitle: styles.sectionTitle
  }} KeyCell={KeyCell} ScopesCell={ScopesCell} availableScopes={availableScopes} selectedScopes={selectedScopes} setSelectedScopes={setSelectedScopes} />

      <GettingStarted connector={connector} />

      {connector.documentation?.references?.length > 0 && <>
          <div style={styles.sectionTitle}>References</div>
          <Columns cols={2}>
            {connector.documentation.references.map(ref => {
    let safeHref;
    try {
      const parsed = new URL(ref.url);
      safeHref = parsed.protocol === 'http:' || parsed.protocol === 'https:' ? ref.url : undefined;
    } catch {
      safeHref = undefined;
    }
    return <Card key={`${ref.title}-${ref.url}`} title={ref.title} href={safeHref}>
                  {ref.description}
                </Card>;
  })}
          </Columns>
        </>}

    </div>;
};

export const connector = {
  "key": "googledrive",
  "name": "Google Drive",
  "icon": "https://stackone-logos.com/api/google-drive/filled/png",
  "authentication": [{
    "label": "OAuth 2.0",
    "description": "Standard OAuth 2.0 for accessing files and folders. Best for integrations that need broad Drive access.",
    "configGuide": "connectors/googledrive/guides/link-account/oauth-2-0",
    "setupGuide": "connectors/googledrive/guides/auth-config/oauth-2-0"
  }, {
    "label": "OAuth2 - File Picker",
    "description": "Uses Google Picker UI to let users select specific files. Best when users should choose exactly which files to share.",
    "configGuide": "connectors/googledrive/guides/link-account/oauth2-file-picker",
    "setupGuide": "connectors/googledrive/guides/auth-config/oauth2-file-picker"
  }],
  "actions": [{
    "id": "googledrive_unified_list_files",
    "label": "List Unified Files",
    "description": "List unified files in Google Drive with StackOne unified filters",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_unified_get_file",
    "label": "Get Unified File",
    "description": "Get a unified file by ID with metadata mapped to StackOne unified schema",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_unified_download_file",
    "label": "Download Unified File",
    "description": "Downloads unified file content from Google Drive",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.readonly"]
  }, {
    "id": "googledrive_unified_upload_file",
    "label": "Upload Unified File",
    "description": "Upload a new file to Google Drive using the StackOne unified upload schema",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_unified_list_folders",
    "label": "List Unified Folders",
    "description": "List unified folders in Google Drive with StackOne unified filters",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_unified_get_folder",
    "label": "Get Unified Folder",
    "description": "Get a unified folder by ID with metadata mapped to StackOne unified schema",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_unified_list_drives",
    "label": "List Unified Drives",
    "description": "List unified shared drives in Google Drive",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.readonly"]
  }, {
    "id": "googledrive_unified_get_drive",
    "label": "Get Unified Drive",
    "description": "Get a unified shared drive by ID with metadata mapped to StackOne unified schema",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.readonly"]
  }, {
    "id": "googledrive_list_files",
    "label": "List Files",
    "description": "List files, folders, and other Drive items. Returns raw Google Drive API response",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_get_file",
    "label": "Get File",
    "description": "Get file, folder, or Drive item by ID",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_copy_file",
    "label": "Copy File",
    "description": "Creates a copy of a file and applies any requested updates with patch semantics",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_create_file",
    "label": "Create File",
    "description": "Creates a new file or folder with metadata only",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_delete_file",
    "label": "Delete File",
    "description": "Permanently deletes a file without moving it to the trash",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_empty_trash",
    "label": "Empty Trash",
    "description": "Permanently deletes all of the user's trashed files",
    "requiredScopes": ["https://www.googleapis.com/auth/drive"]
  }, {
    "id": "googledrive_generate_ids",
    "label": "Generate IDs",
    "description": "Generates a set of file IDs which can be provided in create or copy requests",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_update_file",
    "label": "Update File",
    "description": "Updates a file's metadata using patch semantics",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_upload_file_simple",
    "label": "Upload File (Simple)",
    "description": "Upload raw file binary content to Google Drive without metadata using simple media upload (uploadType=media). Restricted...",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_upload_file_multipart",
    "label": "Upload File (Multipart)",
    "description": "Upload a file with both binary content and metadata in a single request using multipart upload (uploadType=multipart). R...",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_upload_file_resumable",
    "label": "Upload File (Resumable)",
    "description": "Upload a file to Google Drive using resumable upload (uploadType=resumable). Required for files larger than 5 MB; also s...",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_list_drives",
    "label": "List Shared Drives",
    "description": "Lists the user's shared drives (formerly Team Drives)",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.readonly"]
  }, {
    "id": "googledrive_get_drive",
    "label": "Get Shared Drive",
    "description": "Gets a shared drive's metadata by ID",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.readonly"]
  }, {
    "id": "googledrive_create_drive",
    "label": "Create Shared Drive",
    "description": "Creates a new shared drive",
    "requiredScopes": ["https://www.googleapis.com/auth/drive"]
  }, {
    "id": "googledrive_update_drive",
    "label": "Update Shared Drive",
    "description": "Updates the metadata for a shared drive",
    "requiredScopes": ["https://www.googleapis.com/auth/drive"]
  }, {
    "id": "googledrive_delete_drive",
    "label": "Delete Shared Drive",
    "description": "Permanently deletes a shared drive for which the user is an organizer",
    "requiredScopes": ["https://www.googleapis.com/auth/drive"]
  }, {
    "id": "googledrive_hide_drive",
    "label": "Hide Shared Drive",
    "description": "Hides a shared drive from the default view",
    "requiredScopes": ["https://www.googleapis.com/auth/drive"]
  }, {
    "id": "googledrive_unhide_drive",
    "label": "Unhide Shared Drive",
    "description": "Restores a shared drive to the default view",
    "requiredScopes": ["https://www.googleapis.com/auth/drive"]
  }, {
    "id": "googledrive_list_permissions",
    "label": "List Permissions",
    "description": "Lists a file's or shared drive's permissions",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_get_permission",
    "label": "Get Permission",
    "description": "Gets a permission by ID",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_create_permission",
    "label": "Create Permission",
    "description": "Creates a new permission for a file or shared drive",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_update_permission",
    "label": "Update Permission",
    "description": "Updates a permission with patch semantics (modify role, expiration time, etc.)",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_delete_permission",
    "label": "Delete Permission",
    "description": "Deletes a permission (revokes access)",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_unified_check_permissions",
    "label": "Check Permissions",
    "description": "Check what permissions a user has on a Google Drive file, folder, or shared drive, and whether a specific action is allo...",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_list_comments",
    "label": "List Comments",
    "description": "Lists a file's comments",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.readonly"]
  }, {
    "id": "googledrive_get_comment",
    "label": "Get Comment",
    "description": "Gets a comment by ID",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.readonly"]
  }, {
    "id": "googledrive_create_comment",
    "label": "Create Comment",
    "description": "Creates a new comment on a file",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_update_comment",
    "label": "Update Comment",
    "description": "Updates a comment with patch semantics (edit content or resolve/reopen)",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_delete_comment",
    "label": "Delete Comment",
    "description": "Deletes a comment",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_list_replies",
    "label": "List Replies",
    "description": "Lists a comment's replies",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.readonly"]
  }, {
    "id": "googledrive_get_reply",
    "label": "Get Reply",
    "description": "Gets a reply by ID",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.readonly"]
  }, {
    "id": "googledrive_create_reply",
    "label": "Create Reply",
    "description": "Creates a reply to a comment",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_update_reply",
    "label": "Update Reply",
    "description": "Updates a reply with patch semantics",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_delete_reply",
    "label": "Delete Reply",
    "description": "Deletes a reply",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_list_revisions",
    "label": "List Revisions",
    "description": "Lists a file's revisions",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_get_revision",
    "label": "Get Revision",
    "description": "Gets a revision's metadata or content by ID",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_update_revision",
    "label": "Update Revision",
    "description": "Updates a revision with patch semantics",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_delete_revision",
    "label": "Delete Revision",
    "description": "Permanently deletes a file version",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_get_changes_start_page_token",
    "label": "Get Changes Start Page Token",
    "description": "Gets the starting pageToken for listing future changes",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_list_changes",
    "label": "List Changes",
    "description": "Returns a paginated list of changes.",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_list_access_proposals",
    "label": "List Access Proposals",
    "description": "Returns a paginated list of access proposals.",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_get_access_proposal",
    "label": "Get Access Proposal",
    "description": "Retrieves an access proposal by ID",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_resolve_access_proposal",
    "label": "Resolve Access Proposal",
    "description": "Approves or denies an access proposal",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.file"]
  }, {
    "id": "googledrive_get_about",
    "label": "Get About",
    "description": "Gets information about the user, the user's Drive, and system capabilities",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_unified_get_me",
    "label": "IAM Get Me",
    "description": "Return the authenticated identity, OAuth scopes, and auth type for the current Google Drive connection. Wraps Drive's ab...",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_auth_test",
    "label": "IAM Auth Test",
    "description": "Debugging helper that validates the current OAuth access token by calling Google's token introspection endpoint (GET htt..."
  }, {
    "id": "googledrive_unified_list_groups",
    "label": "IAM List Groups",
    "description": "List every Google Workspace group (mailing lists and collaboration groups) in the connected customer, mapped to the Stac...",
    "requiredScopes": ["https://www.googleapis.com/auth/admin.directory.group.readonly"]
  }, {
    "id": "googledrive_unified_get_group",
    "label": "IAM Get Group",
    "description": "Retrieve a single Google Workspace group by directory ID OR email (both forms accepted), optionally expanded with its me...",
    "requiredScopes": ["https://www.googleapis.com/auth/admin.directory.group.readonly"]
  }, {
    "id": "googledrive_unified_list_organizations",
    "label": "IAM List Organizations",
    "description": "List the verified Google Workspace domains for the connected customer, modeled as IAM organizations. Wraps Admin Directo...",
    "requiredScopes": ["https://www.googleapis.com/auth/admin.directory.domain.readonly"]
  }, {
    "id": "googledrive_unified_get_organization",
    "label": "IAM Get Organization",
    "description": "Retrieve a single Google Workspace verified domain (modeled as an IAM organization) by its literal domain name (e.g. \"ex...",
    "requiredScopes": ["https://www.googleapis.com/auth/admin.directory.domain.readonly"]
  }, {
    "id": "googledrive_unified_list_resource_types",
    "label": "IAM List Resource Types",
    "description": "Enumerate the resource_type values accepted by unified_list_resource_users and unified_check_permissions on this connect...",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_unified_list_resource_users",
    "label": "IAM List Resource Users",
    "description": "List the ACL grantees (sharing entries) on a Google Drive file, folder, or shared drive as IAM users, each annotated wit...",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_unified_list_roles",
    "label": "IAM List Roles",
    "description": "Return the six stable Drive sharing roles as IAM role TEMPLATES — owner, organizer, fileOrganizer, writer, commenter, re...",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_unified_get_role",
    "label": "IAM Get Role",
    "description": "Retrieve a single synthesized Google Drive sharing role by its stable key — owner, organizer, fileOrganizer, writer, com...",
    "requiredScopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "id": "googledrive_unified_list_users",
    "label": "IAM List Users",
    "description": "List every Google Workspace user in the connected customer's directory, mapped to the StackOne IAM unified user schema....",
    "requiredScopes": ["https://www.googleapis.com/auth/admin.directory.user.readonly"]
  }, {
    "id": "googledrive_unified_get_user",
    "label": "IAM Get User",
    "description": "Retrieve a single Google Workspace directory user by their unique directory ID OR primary email (both forms accepted), o...",
    "requiredScopes": ["https://www.googleapis.com/auth/admin.directory.user.readonly"]
  }],
  "releaseStage": "ga",
  "categories": ["documents", "iam"],
  "scopeDefinitions": [{
    "name": "https://www.googleapis.com/auth/drive.file",
    "description": "Create new Drive files, or modify existing files, that you open with an app or that the user shares with an app while using the Google Picker API or the app's file picker."
  }, {
    "name": "https://www.googleapis.com/auth/drive",
    "description": "View and manage all your Drive files.",
    "includes": ["https://www.googleapis.com/auth/drive.readonly", "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "name": "https://www.googleapis.com/auth/drive.readonly",
    "description": "View and download all your Drive files.",
    "includes": ["https://www.googleapis.com/auth/drive.metadata.readonly"]
  }, {
    "name": "https://www.googleapis.com/auth/drive.metadata.readonly",
    "description": "View metadata for files in your Drive."
  }, {
    "name": "https://www.googleapis.com/auth/drive.scripts",
    "description": "Modify your Google Apps Script scripts' behavior."
  }, {
    "name": "https://www.googleapis.com/auth/admin.directory.group.member.readonly",
    "description": "Check group membership for permission resolution. Optional — enables resolving group-level file sharing permissions."
  }, {
    "name": "https://www.googleapis.com/auth/admin.directory.user.readonly",
    "description": "Read user records from the Google Workspace Admin Directory API. Required for unified IAM list_users / get_user actions. Workspace admin grant required."
  }, {
    "name": "https://www.googleapis.com/auth/admin.directory.group.readonly",
    "description": "Read group records from the Google Workspace Admin Directory API. Required for unified IAM list_groups / get_group actions. Workspace admin grant required.",
    "includes": ["https://www.googleapis.com/auth/admin.directory.group.member.readonly"]
  }, {
    "name": "https://www.googleapis.com/auth/admin.directory.domain.readonly",
    "description": "Read verified domains from the Google Workspace Admin Directory API. Required for unified IAM list_organizations / get_organization actions. Workspace admin grant required."
  }]
};

<ConnectorPage connector={connector} />
