skills/integrate-anything/SKILL.md
Connect to any external app and perform actions on it. Use when the user wants to interact with external services like Slack, Linear, HubSpot, Salesforce, Jira, GitHub, Google Sheets, or any other app — send messages, create tasks, sync data, manage contacts, or perform any API operation.
npx skillsauth add membranehq/agent-skills integrate-anythingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Connect to any external app and perform actions on it. Uses the Membrane CLI.
Always route through Membrane. Don't hit vendor APIs directly. Membrane proxies every request through an authenticated connection and injects the right auth headers — including transparent credential refresh.
Never handle external-app credentials yourself. OAuth tokens, API keys, refresh tokens — Membrane stores and manages them server-side. Pass a connectionKey (or connectionId) and the tools call the vendor on your behalf.
The rest of the skill is these three steps in detail.
npx @membranehq/cli login --tenant
--tenant gets a tenant-scoped token (workspace + customer) so you don't need to pass --workspaceKey and --tenantKey on every subsequent command.
The command opens a browser. In headless environments it prints an authorization URL — ask the user to open it, complete the flow, and paste the code back; finish with:
npx @membranehq/cli login complete <code>
Credentials are stored at ~/.membrane/credentials.json. All later commands pick them up automatically.
If npx is awkward, install globally: npm i -g @membranehq/cli@latest and use plain membrane …. Add --json to any command for machine-readable output.
npx @membranehq/cli connection list --json
Each connection carries id, key, integrationKey, state. Scan for the target app and branch on state:
READY → use it. Skip to Step 2.
CLIENT_ACTION_REQUIRED (disconnected, needs re-auth) → reconnect the existing connection, do NOT create a new one:
npx @membranehq/cli connect --connectionId <id>
Creating a fresh connection while the old one is CLIENT_ACTION_REQUIRED leaves orphaned records and breaks anything that referenced the old connectionKey. Always reconnect.
Multiple matches (e.g. slack-work and slack-personal) → ask the user which to use. Don't guess.
No match → create a new one (Step 1b).
By URL or domain — shortest path:
npx @membranehq/cli connection ensure "https://slack.com" --json
# also accepts a bare domain: "slack.com"
The URL is normalized to a domain and matched against known apps. If no app is found, one is created and a connector is built automatically.
To set a stable, human-readable key for later lookup (especially for multi-account setups like slack-work + slack-personal), set it after creation:
npx @membranehq/cli connection patch <id> --data '{"connectionKey":"slack-work"}'
For the explicit multi-connection case (creating a second connection to an app you already have connected), use connect:
npx @membranehq/cli connect --integrationKey slack \
--connectionKey slack-personal --allowMultipleConnections
READYAfter 1a's reconnect or 1b's create, read state and follow the state machine:
READY — done. Move to Step 2.
BUILDING — Membrane's builder agent is working. Wait:
npx @membranehq/cli connection get <id> --wait --json
--wait long-polls (up to --timeout seconds, default 30).
CLIENT_ACTION_REQUIRED — the user or agent must do something. The clientAction object describes what:
clientAction.type — "connect" (auth flow) or "provide-input" (extra fields needed).clientAction.agentInstructions (optional) — follow these verbatim if present. They tell the agent how to drive the provider side of the flow programmatically. Don't shortcut to "paste this URL" — the instructions exist because the agent is expected to handle it.clientAction.uiUrl (optional) — a Membrane-hosted page where the user completes the action manually. Show this only when agentInstructions tells you to, or when no agentInstructions are present.clientAction.description — human-readable summary.When the action requires writing data back to the connection (e.g. captured OAuth credentials, custom params):
npx @membranehq/cli connection patch <id> --data '{"connectorParameters":{...},"input":{...}}'
After the user completes their step, poll with connection get <id> --wait --json until state changes.
CONFIGURATION_ERROR / SETUP_FAILED — surface the error field to the user. These are terminal — don't retry blindly.
The fastest path to a real response is act with an inline dispatch. No "create action → wait → run" ceremony required.
act accepts exactly one of four dispatch styles:
| Dispatch | When to use |
|---|---|
| --api '<json>' | First call after a fresh connection, and any one-off HTTP request. Membrane handles auth + base URL. |
| --code '<js>' | You need a small piece of logic (loop, transform, multi-step). |
| --key <key> | You've previously saved this call as a reusable action. |
| --id <id> | Same as --key but by id (use only when the action has no key). |
api (recommended for the first call after a fresh connection, and for one-off calls)Use this as the default for the very first call against a new connection. It's the fastest way to confirm the connection works and to give the user a real response — no build step, no BUILDING state, no waiting.
Pass an HTTP spec; Membrane proxies it through the connection's auth layer and base URL:
npx @membranehq/cli act --connectionKey slack-work \
--api '{"method":"POST","path":"/api/chat.postMessage","body":{"channel":"#general","text":"Hello"}}' \
--json
Spec shape: { method, path, body?, headers?, query? }. The connector's base URL is prepended automatically. Auth is injected automatically.
Only escalate to a saved action (Step 3) if the user is going to run the same call repeatedly — saving has real value for repeat use, but adds latency and failure modes that are wasteful for first-call activation.
code (when you need logic, not just an HTTP call)npx @membranehq/cli act --connectionKey hubspot \
--code 'module.exports = async ({ input, membrane }) => {
const all = []
let after
do {
const page = await membrane.api({ method: "GET", path: "/crm/v3/objects/contacts", query: { limit: 100, after } })
all.push(...page.results)
after = page.paging?.next?.after
} while (after)
return { count: all.length }
}' \
--input '{}' --json
The function receives { input, membrane, connection, integration }. Use membrane.api({ method, path, ... }) to make authenticated calls inside the function. Whatever you return becomes the response output.
If the user is going to run the same call repeatedly, save it once and call it by key:
npx @membranehq/cli act --key send-channel-message --connectionKey slack-work \
--input '{"channel":"#general","text":"Hello"}' --json
See Step 3 below for how to create a saved action.
If you don't already know whether one exists:
# Ranked by semantic match against an intent
npx @membranehq/cli action list --connectionKey slack-work --intent "send a message" --limit 10 --json
# Catalog actions for one app (browse without a connection)
npx @membranehq/cli external-app list --search slack --json # → externalAppId
npx @membranehq/cli action list --externalAppId <id> --json
Each result carries id, key, name, description, inputSchema, outputSchema. Read the inputSchema before running — it's authoritative.
If nothing matches, fall back to inline api or code (above), or create a saved action (Step 3).
When you find yourself about to make the same act --api call a second time, save it. Future calls become act --key <key> instead of the full inline spec.
Two ways:
By intent — describe what you want; Membrane builds the config and validates it:
npx @membranehq/cli action create "send a message in a channel" --connectionKey slack-work --json
The action returns in state: BUILDING. Wait for it:
npx @membranehq/cli action get <id> --wait --json
By explicit spec — supply type + config directly. Common when lifting a tested inline api call into a saved action:
npx @membranehq/cli action create \
--key send-channel-message \
--type api-request-to-external-app \
--config '{"request":{"method":"POST","path":"/api/chat.postMessage"}}' \
--integrationKey slack --json
Scope follows which fields you set:
connectionKey / connectionId → connection-level (tied to one connection)integrationKey / integrationId (no connection) → integration-level (shared across every connection on that integration)Update / delete:
npx @membranehq/cli action update <id-or-key> --data '<json-merge>'
npx @membranehq/cli action delete <id-or-key>
Ask the user before saving — they may want the action named, described, or kept inline.
Read the response body — never branch on HTTP status alone. Three error paths:
Your CLI session is invalid or expired. Run membrane login --tenant again.
The vendor's auth no longer works (token revoked, OAuth expired, credentials rotated). Read the connection state:
npx @membranehq/cli connection get <id-or-key> --json
If state is CLIENT_ACTION_REQUIRED, reconnect the existing connection (don't create a new one):
npx @membranehq/cli connect --connectionId <id>
After re-auth, retry the original act call.
Every act response carries actionRunId, on success AND on error. Pull the full log:
npx @membranehq/cli action-run-log get <actionRunId> --details --json
You get the mapped input, output, errors, plus the raw HTTP exchange with the external app.
All commands support --json. Add --workspaceKey <key> and --tenantKey <key> to override project defaults.
npx @membranehq/cli connection ensure <appUrl> [--name <n>] [--json] # Find or create by URL
npx @membranehq/cli connection list [--json]
npx @membranehq/cli connection get <id-or-key> [--wait] [--timeout <n>] [--json]
npx @membranehq/cli connection patch <id> --data '<json>' [--json]
npx @membranehq/cli connect --connectionId <id> # Reconnect existing
npx @membranehq/cli connect --integrationKey <k> [--connectionKey <k>] [--allowMultipleConnections]
npx @membranehq/cli act --connectionKey <k> --api '<json>' [--input <json>] [--json] # Inline HTTP
npx @membranehq/cli act --connectionKey <k> --code '<js>' [--input <json>] [--json] # Inline JS
npx @membranehq/cli act --connectionKey <k> --key <k> [--input <json>] [--json] # Reusable
npx @membranehq/cli act --connectionKey <k> --id <id> [--input <json>] [--json] # Reusable by id
npx @membranehq/cli action list [--connectionKey <k>] [--externalAppId <id>] [--intent <t>] [--limit <n>] [--json]
npx @membranehq/cli action create <intent> --connectionKey <k> [--json] # Build by intent
npx @membranehq/cli action create --key <k> --type <t> --config '<json>' --integrationKey <k> [--json] # Explicit spec
npx @membranehq/cli action get <id-or-key> [--wait] [--timeout <n>] [--json]
npx @membranehq/cli action update <id-or-key> --data '<json>' # Merge
npx @membranehq/cli action delete <id-or-key>
npx @membranehq/cli action-run-log get <actionRunId> [--details] [--json] # Diagnostics for any /act call
npx @membranehq/cli external-app list --search <query> --json
npx @membranehq/cli search <query> [--elementType <type>] [--limit <n>] [--json]
If the CLI is not available, call the API directly.
Base URL: https://api.getmembrane.com
Auth header: Authorization: Bearer $MEMBRANE_TOKEN
Get the token from the Membrane dashboard.
| CLI Command | API Equivalent |
|---|---|
| connection ensure "<url>" --json | POST /connections/ensure with {"appUrl": "<url>"} |
| connection list --json | GET /connections |
| connection get <id> --wait --json | GET /connections/:id?wait=true |
| connection patch <id> --data <json> | PATCH /connections/:id with <json> |
| connect --connectionId <id> | POST /connections/:id/reconnect |
| act --connectionKey <k> --api <json> | POST /act with {"connectionKey":"<k>","api":<json>} |
| act --connectionKey <k> --code <js> | POST /act with {"connectionKey":"<k>","code":"<js>"} |
| act --connectionKey <k> --key <ak> | POST /act with {"connectionKey":"<k>","key":"<ak>","input":<json>} |
| action list --connectionKey <k> --intent <t> | GET /actions?connectionKey=<k>&intent=<t> |
| action create <intent> --connectionKey <k> | POST /actions with {"intent":"<t>","connectionKey":"<k>"} |
| action get <id> --wait | GET /actions/:id?wait=true |
| action-run-log get <actionRunId> --details | GET /action-run-logs/:id?details=true |
All requests go to the Membrane API. No other external services are contacted directly by this skill.
| Endpoint | Data Sent |
|---|---|
| https://api.getmembrane.com/* | Auth credentials, connection parameters, action inputs, agent prompts |
~/.membrane/ with restricted file permissions.By using this skill, data is sent to Membrane. Only install if you trust Membrane with access to your connected apps.
tools
Use this skill when writing code that reads, writes, syncs, or reacts to data in an external app. Applies to SaaS products, internal tools, scripts, batch jobs, and CLIs. The skill uses Membrane as the integration engine — it handles OAuth and credential lifecycle (authentication, token refresh, reconnect), exposes vendor operations through a uniform interface, delivers events via webhooks, generates connectors on demand for apps not yet in the workspace, and captures every action run and raw API exchange in structured logs. Works against any external app.
tools
Build apps that integrate with external services via Membrane. Use when the user wants to add integrations to their product — let their customers connect to Slack, HubSpot, Salesforce, GitHub, Google Sheets, Jira, or any other app, execute actions, sync data, or handle webhooks. Covers backend token generation, frontend connection UI, running actions, data collections, and AI agent tooling.
development
Maintainer-only workflow for handling GitHub Secret Scanning alerts on OpenClaw. Use when Codex needs to triage, redact, clean up, and resolve secret leakage found in issue comments, issue bodies, PR comments, or other GitHub content.
development
Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts.