
Make Api Shell Connection Workflow
- 193 installs
- 75 repo stars
- Updated July 21, 2026
- integromat/make-skills
Build a reusable Make API-call shell scenario to retrieve data from email, CRM, and ticketing SaaS systems by resolving the right app and connection.
About
Builds or reuses a Make API-call shell scenario as a generic transport for retrieving data from email, CRM, ticketing, and similar SaaS systems. A developer uses it to discover the right Make app module, resolve a connection, and run a reusable retrieval shell.
- Builds a reusable shell scenario (StartSubscenario + app API-call module + ReturnData) as a SaaS retrieval transport
- Resolves or requests the right Make connection and matches apps locally against the ~4k-app catalog
Make Api Shell Connection Workflow by the numbers
- 193 all-time installs (skills.sh)
- +12 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #597 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/integromat/make-skills --skill make-api-shell-connection-workflowAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 193 |
|---|---|
| repo stars | ★ 75 |
| Last updated | July 21, 2026 |
| Repository | integromat/make-skills ↗ |
What it does
Build a reusable Make API-call shell scenario to retrieve data from email, CRM, and ticketing SaaS systems by resolving the right app and connection.
Files
Make API Shell + Connection Workflow
Use this skill for one specific workflow family:
- discover the correct Make app and app-specific API-call module
- reuse or build a reusable shell scenario with StartSubscenario, one app API-call module, and ReturnData
- reuse an existing suitable connection or create the connection request needed by that shell
- patch the shell with the selected connection once authorization is complete
- run the scenario and use it as a generic SaaS retrieval transport for email, CRM, tickets, and similar systems
This skill is primarily about provisioning and shell construction. Treat business retrieval as a second phase that starts only after the connection is ready and the shell has been validated against current workspace metadata.
The generic shell described here is an API transport wrapper, not business logic. It should behave like a reusable API endpoint for any SaaS app that Make can front, including email, CRM, ticketing, support, marketing, or task systems.
Known Make module id: the Make Code module is "module": "code:ExecuteCode".
Quick routing
Read the file that matches the current task:
| Task | Reference |
|---|---|
| Discover the app, module, and connection type layers | Discovery and Shells |
| Create, inspect, or resolve a credential request | Connection Requests |
| Choose and execute the post-connection retrieval path | Retrieval Execution |
| Repair provider authorization or scope failures after a shell run | Retrieval Execution |
| Sanitize examples and prepare a public shareable version | Sanitization and Sharing |
| Start from a generic blueprint template | Example shell blueprint |
| Provider has no Make app: generic HTTP shell with noAuth / API key / Basic auth / OAuth 2.0 | HTTP Fallback Shells |
Fresh-agent operating sequence
When a fresh agent gets a request such as "get my unread emails", "pull my open CRM leads", or "fetch my Jira tickets", the default operating sequence is:
1. Resolve the provider anchor.
- email: Gmail vs Outlook vs other
- CRM: HubSpot vs Salesforce vs other
- ticketing: Jira vs Zendesk vs Linear vs other
2. Resolve the target account, mailbox, workspace, project, or queue if the request is ambiguous. 3. Resolve the active Make zone, organization, and team. 4. Resolve the provider app deterministically. GET /api/v2/imt/apps ignores search, limit, and scoredSearch and always returns the full ~4k-app catalog, so never rely on server-side search. Match locally instead: search a curated top-apps catalog first (in helper environments, search_apps(query=...) does this); only when nothing matches there, fetch the full catalog once and filter it client-side, case-insensitively, over name and label. All query words score; without an exact phrase match the first query word must hit for a candidate to count, and the first ranked result is the recommended app.
- Only Make-verified apps and the organization's own custom/SDK apps may be auto-selected. Community apps (
communityApp-*) are excluded from matching; custom apps (catalog prefixapp#*) are allowed. Authoritative check:GET /api/v2/imt/apps-metalists all verified and custom apps available to the organization (custom-app names appear there without theapp#prefix). Builtin utility pseudo-apps (builtin,util,gateway,regexp) are not SaaS providers and must never surface as search results. If the provider only exists as a community app, treat it as "no app" and build a generic HTTP fallback shell instead — see HTTP Fallback Shells.
5. Inspect the chosen app with GET /api/v2/imt/apps/{appName}/{version} and record the exact API-call module slug and both connection type layers. 6. Reuse an existing suitable connection only after verifying details and liveness: GET /api/v2/connections/{connectionId} plus POST /api/v2/connections/{connectionId}/test. 7. Reuse an existing shell only when reusing an existing suitable connection. If a new connection must be created, create a new shell for that new connection instead of patching an old shell onto a newly authorized account. 8. Only if no suitable connection exists, create a credential request. 9. After the connection decision is settled, create or patch the shell according to the reuse rule above and verify that the shell can run. 10. Run the narrowest possible retrieval request first through the API-call shell. 11. Expand into list/search -> detail -> normalization only after the first shell run proves the path works. 12. If no Make app exists for the provider at all, build a generic HTTP fallback shell with http:MakeRequest instead of giving up — see HTTP Fallback Shells.
The agent should not jump straight from "user wants SaaS data" to "create a new connection" or "call a direct SDK" without walking this sequence.
Input-resolution gates
Before provisioning or retrieval, explicitly resolve these inputs:
- provider anchor
- target account identity if multiple accounts or mailboxes are possible
- intended credential-request recipient if it may differ from the current token owner
- Make zone, organization, and team
If one of those items is missing and cannot be discovered safely, stop and ask only for that exact missing item.
Core rules
1. Never guess the API-call module name. Discover it from current Make metadata. 2. Treat example apps such as Gmail, Outlook, or HubSpot as illustrations, not as universal defaults. 3. Keep the two type layers separate:
- scenario/module connection parameter type
- connection listing or credential request type
4. Prefer reuse before creation:
- existing suitable connection before new credential request
- existing shell scenario only when reusing an existing suitable connection
- a newly authorized connection must get a newly created shell
5. Do not route business retrieval to native Make search/list/get modules. For this workflow family, always retrieve through the Make app's API-call shell. 6. Ask for confirmation before writing into an existing live scenario or replacing a connection mapping. 7. Keep public examples sanitized. Do not include real names, user IDs, team IDs, organization IDs, tenant-specific hosts, or claims that a single private workspace proves a universal rule. 8. Use a clean base URL variable in examples. For public examples, default to https://us1.make.com and keep placeholders generic. Do not mention we.make.com in public examples unless the current user explicitly provides or requests that zone. Valid zones can be eu1, eu2, us1, us2, or we, and if the user provides a custom zone or BASE_URL, accept it. 9. Separate four phases explicitly:
- provider and app resolution
- connection provisioning
- shell provisioning
- retrieval execution and output normalization
10. Do not assume the generic three-module blueprint is automatically activatable for every app. Before activation, compare the middle module metadata with a real current blueprint or module export for the same app and version in the active workspace. 11. For the generic API shell contract that uses scenario-service:ReturnData with ExpectDataAny, the final mapper must return the app module response body as data: {{MIDDLE_API_MODULE_ID.body}}. 12. In the generic example blueprint in this skill the middle API-call module id is 3, so the example mapper is data: {{3.body}}. If the real blueprint uses different module ids, use the real middle module id instead. For example, a Make UI export may use StartSubscenario 2, API-call module 5, and ReturnData 4, so the correct mapper is data: {{5.body}}. 13. Never replace that shell-contract default with {{MIDDLE_API_MODULE_ID}} or {{MIDDLE_API_MODULE_ID.data}} just because the full bundle looks tempting. The shell is meant to return the API response body, not the entire Make module bundle. 14. Still inspect a real execution bundle for validation, but use that to confirm that body contains the intended payload or error object — not to redefine the generic shell contract. 15. Resolve the active workspace zone before any team-scoped call. GET /api/v2/users/me and GET /api/v2/imt/apps/... can succeed on multiple zones, while GET /api/v2/connections?teamId=... or scenario endpoints on the wrong zone can fail with 403 Permission denied. 16. For the REST /api/v2/connections endpoint, filter with type=... or type[]=.... Do not assume query parameters such as accountName=... are honored just because an MCP tool uses accountName terminology. 17. Do not ask the user to paste raw OAuth secrets, API keys, or passwords into chat. Use a credential request whenever a new connection must be created. Pick the path by recipient: for the current Make user, the self-service endpoints (actions/create, actions/create-by-credentials) work on a wide range of plans; only requesting credentials from a different person (requests/v2) is the Enterprise/Partner feature gated by license.credentialRequests (see Connection Requests, "Choose the request path by recipient first"). When no request path works, guide the user through creating the connection in the scenario editor instead. In all flows state the exact credential paste format (Bearer prefix or raw key) — never assume the user knows it. 18. If the user request is ambiguous, resolve the concrete provider and account first; if it is already explicit, do not ask again. 19. If the Module 2 request method is PUT, PATCH, or DELETE, warn explicitly before execution. Treat those methods as mutating live SaaS operations, not passive retrieval. 20. Do not assume StartSubscenario.metadata.interface is enough for scenario runs. After creating or updating an on-demand shell, explicitly set the scenario-level interface with /api/v2/scenarios/{scenarioId}/interface and verify it before the first run. 21. Treat /api/v2/scenarios/{scenarioId}/run as the standard execution path for this shell family. Pass the business payload under data with keys that match the scenario interface exactly, and prefer responsive: true for validation runs. 22. Expose query parameters as a first-class shell input named qs. Use qs for provider API query parameters such as Gmail search options, Drive fields, Drive q, Graph $select, pagination, or supportsAllDrives. If a caller provides a query string in path, normalize it into qs before running the shell. 23. When moving blueprints between Make UI exports and Make scenario create/update APIs, normalize shape deliberately: UI/export artifacts may store the flow at subflows[0].flow, while scenario create/update payloads require a top-level flow. Do not send a raw UI export to create/update without normalization. 24. Shell reuse is app-specific, not just provider-family-specific. A shell built around one app module should not be repointed to another app module just because both belong to the same vendor suite. 25. Before reusing any existing connection or a shell that points to an existing connection, call Make's connection verification API: POST /api/v2/connections/{connectionId}/test. A response with verified: true is the liveness proof. A stale Credential Request status does not override a verified connection. Liveness is not proof that the provider will authorize every path, method, or scope; provider authorization is proven only by a successful shell run for the intended operation. On provider auth or scope errors, use the Authorization Repair Playbook. 26. When resuming after user authorization, reuse the saved Credential Request requestId; inspect the request, then list and verify connections. Do not create a new Credential Request while an active request for the same app/account is still being resolved. 27. Treat a future expire timestamp as valid. Treat only a past expiry, revoked connection, failed verification, or provider auth error as invalid.
App binding and connection-family matrix
The shell pattern is generic, but each actual shell is bound to one discovered Make app module.
Do not treat “Google” or “Microsoft” as a single interchangeable connection family. In Make, app families often split by product surface.
Common examples:
| Business surface | Example API-call module | Scenario/module connection parameter type | Common connection listing or request type |
|---|---|---|---|
| Gmail | google-email:makeAnApiCall | account:google-email | google-email or workspace-specific variants such as google-restricted |
| Google Calendar / Sheets / Drive style apps | app-specific Google module discovered from metadata | commonly account:google | commonly google |
| Outlook / Microsoft mail | microsoft-email:makeApiCall | account:azure | azure |
Rules that follow from this:
- Reuse a shell only when the discovered app, module slug, version, and connection family still match.
- Reuse a connection only when the account identity and scopes still fit the requested operation.
- If a new connection is authorized for a different account or connection family, create a new shell for it instead of silently repointing an old shell.
When in doubt, inspect the exact app metadata and existing connection detail instead of inferring compatibility from the vendor name.
Standard shell shape
The reusable shell has exactly three modules: 1. scenario-service:StartSubscenario 2. one app-specific Make API-call module discovered from metadata 3. scenario-service:ReturnData
Expose these shell inputs through StartSubscenario:
pathmethodheaderqsbody
Use the discovered middle module as the only app-specific part of the shell.
Generic shell contract
This shell is a generic API endpoint wrapper.
It receives:
pathmethodheaderqsbody
It forwards those values into the app-specific Make API-call module.
Default retrieval should use GET. Treat PUT, PATCH, and DELETE as write/destructive methods and require explicit user confirmation before running them.
It returns exactly one thing:
- the response body from the app-specific Make API-call module
Therefore the shell contract is:
{
"data": "{{MIDDLE_API_MODULE_ID.body}}"
}In this skill's generic example blueprint, MIDDLE_API_MODULE_ID is 3, so the example value is {{3.body}}. In a real Make export, always inspect the module ids and use the actual API-call module id.
That contract is generic across SaaS providers. It applies whether the middle module fronts Gmail, Outlook, HubSpot, Jira, or another provider-specific Make API-call module.
The shell should not try to return:
- the whole Make bundle
{{3}} - a guessed nested field such as
{{3.data}} - transport metadata mixed together with the body
The shell is transport only. Business interpretation happens later.
Two-phase operating model
Phase A: provisioning
Complete these steps first: 1. identify the provider and exact Make app 2. discover the exact app version and module slug 3. determine both connection type layers 4. look for an existing suitable connection for the correct account identity and scope 5. verify candidate connections with POST /api/v2/connections/{connectionId}/test 6. look for an existing shell scenario that already fits the contract for that verified connection 7. create or resolve the credential request only if reuse failed 8. create a new shell if a new connection was created, or patch an existing shell only when reusing an existing connection 9. verify that the shell runs with the chosen connection
Deliverable at the end of Phase A:
- a connection-ready API-call shell scenario
Phase B: retrieval
Only after Phase A succeeds: 1. configure the retrieval call through the API-call shell 2. run a narrow validation query or lookup 3. inspect the real output bundle shape 4. keep ReturnData fixed as the generic shell contract and update only downstream normalization if needed 5. rerun and verify the user-facing payload
Do not treat a successful credential request as proof that the retrieval stage is already solved.
Important:
- the generic three-module API shell remains the only retrieval transport in this workflow family
- keep the shell contract fixed as
data: {{MIDDLE_API_MODULE_ID.body}}whereMIDDLE_API_MODULE_IDis the actual app API-call module id in the blueprint - do not switch to native retrieval/search/list modules as a fallback or optimization
- if authorization fails because an existing connection is expired or invalid, do not try to re-auth that connection in place; go through the credential-request path and then create a new shell for the new connection
Interface-and-run rule
For on-demand shells, treat interface provisioning as a separate deployment step: 1. create or update the scenario blueprint 2. explicitly set /api/v2/scenarios/{scenarioId}/interface 3. verify the interface shape before the first run 4. only then call /api/v2/scenarios/{scenarioId}/run
Use a run payload shaped like:
{
"data": {
"path": "...",
"method": "GET",
"header": [],
"qs": [],
"body": null
},
"responsive": true
}The key names under data must match the scenario interface exactly. If the interface was never explicitly set, run can reject the call even when the StartSubscenario module itself contains interface metadata.
Body-handling compatibility rule
Keep the generic shell contract stable, but do not assume every provider module tolerates an empty or null body the same way.
Observed-safe pattern:
- write-capable shells can expose and map
body - read-heavy shells may need to omit the Module 2
bodymapper entirely when the provider module serializes empty payloads badly onGETorDELETE
If a provider-specific Make API-call module fails only when body is present-but-empty, prefer one of these patterns:
- a read shell without a
bodymapper - a write shell with a
bodymapper - two separate shells when the provider behavior differs between read and write paths
Do not change ReturnData for this. This is a Module 2 request-shape compatibility issue, not a shell-output-contract issue.
Response behavior
When using this skill:
- first summarize the discovered app, version, exact API-call module name, and both connection type layers
- explicitly state how the provider was resolved: user statement, existing Make artifacts, or Make app-catalog lookup
- explicitly say whether the shell is being reused or newly created
- explicitly say whether the connection is being reused or newly requested
- explicitly state which phase you are in: provisioning or retrieval
- when retrieval begins, state the API-call plan: list/search path, any follow-up detail paths, and normalization plan
- if a new connection was created, explicitly state that a new shell was created for it
- if the request started as a business ask such as email, CRM, or tickets, state the business target and the exact API path pattern you chose
- explicitly label any assumptions
- keep write-operation prompts brief and concrete
- if Module 2 is about to run a
PUT,PATCH, orDELETE, stop and warn before execution - if activation fails or
ReturnDatalooks wrong, stop calling the flow complete and report the exact failing phase - if sharing publicly, rewrite examples with placeholders and neutral labels before finalizing
Related skills
make-scenario-buildingfor broader scenario architecture beyond this shell patternmake-module-configuringfor detailed module configuration, mapping, webhooks, keys, and data storesmake-mcp-referencefor Make MCP connection methods, scopes, and timeout behavior
Connection Requests
This file covers how to request authorization for the shell, inspect the result, and patch the selected connection into the scenario.
Authentication format
Use a Make API token in the header:
Authorization: Token YOUR_API_KEYChoose the request path by recipient first
Resolve WHO the credential is for before picking an endpoint. There are two separate flows with different gating:
| Recipient | Endpoint | Gating |
|---|---|---|
| The current Make user (default) | POST /api/v2/credential-requests/actions/create — self-service, no provider field, one connection or key object per call, returns publicUri | Available to a wide range of plans; not gated by the Enterprise credential-requests license |
| A colleague, employee, or service-account owner — only when the task explicitly says so | POST /api/v2/credential-requests/requests/v2 — requires provider (providerMakeUserId or invite by name/email) | Enterprise/Partner feature; check the plan gate below first |
Unless the task explicitly states that the credential must come from another person or a shared/service account, use the self-service flow. The external request flow exists for delegating authorization to someone else; that delegation is the heavily gated feature.
The guided connection flow (below) remains the last fallback for BOTH paths: when a request endpoint fails with a policy denial, create the shell without a credential and walk the user through adding the connection in the UI.
Plan gate: external credential requests are an Enterprise/Partner feature
The gate applies to the external request flow (requests/v2). Free/Core/Pro organizations cannot request credentials from other people; the self-service path above is the route that works on a wider range of plans. Check the capability BEFORE creating an external request — the authoritative source is the organization license flag, not the plan name:
curl -sS "${BASE_URL}/api/v2/organizations/${ORG_ID}" \
-H "authorization: Token $API_KEY" | jq '.organization.license.credentialRequests'(In helper environments: get_organization_capabilities().)
When the flag is false (or the create call fails with a policy denial), do NOT give up and do NOT ask the user to paste secrets into chat. Switch to the guided connection flow instead:
1. Create (or reuse) the shell scenario first, without a working credential. 2. Hand the user the scenario editor URL (https://<zone>.make.com/<teamId>/scenarios/<scenarioId>/edit) and a click path: open the middle module -> click "Add" next to the connection/keychain field -> fill the fields -> Save. 3. State the exact paste format for the credential fields (see "Credential paste formats" below) — this is mandatory, users cannot know provider-specific shapes such as Bearer prefixes. 4. Ask the user to reply "done" when finished, then verify the connection with POST /connections/{id}/test or a narrow shell test run before any real retrieval.
In helper environments connection_setup_guide(scenario_id, module_label=..., provider=...) generates this guide.
Credential paste formats (state these verbatim to the user)
Always include the exact expected format in the credential-request description AND in chat. Known shapes:
| Provider / type | What to paste |
|---|---|
| HTTP app, API Key Auth keychain | Fields: Key, Placement, Name. Provider expects Authorization: Bearer <key> -> Key = Bearer <key> (literal prefix + space), Placement = header, Name = Authorization. Provider uses a plain key header (e.g. X-API-Key) -> Key = raw key, Name = header name. |
| Daytona (HTTP app keychain) | Key = Bearer <daytona-key>, Placement = header, Name = Authorization |
| e2b app connection | raw API Key starting with e2b_ — no Bearer prefix, no quotes, and NOT the sk_e2b_... Access Token (app sends X-API-Key itself; any value not starting with e2b_ fails with 401: authorization header is malformed) |
| Basic auth keychain | username/password exactly as issued, no encoding |
When the provider is unknown, say explicitly which of the two API-key conventions applies after checking the provider's API docs.
If a pasted value keeps failing after UI edits (verified live with the e2b connection — multiple UI re-saves did not fix a malformed key), write it programmatically instead: GET /connections/{id}/editable-data-schema lists the writable fields, then POST /connections/{id}/set-data (e.g. {"apiKey": "..."}) updates the connection guaranteed whitespace-free. Verify with a real module run, not just /connections/{id}/test — the test endpoint can report verified: true for basic-type connections even when the stored secret is wrong.
Decision ladder
Prefer the most current supported path first, then fall back only when needed.
0. Before creating anything, list existing connections for the target app in the active team and reuse one if it already satisfies the workflow. 1. Decide the recipient (see "Choose the request path by recipient first"). 2. For the current Make user (default), use the self-service path:
- for API-call shells:
POST /api/v2/credential-requests/actions/create-by-credentialswith an explicit connectiontypeandscopearray (see the scope rule for universal API-call modules below) - for regular modules that declare their own scopes:
POST /api/v2/credential-requests/actions/createwith the app/module selection, or the equivalent MCP credential-request tool
3. Only for an explicitly external recipient: check the plan gate above (on credentialRequests: false use the guided connection flow), then:
POST /api/v2/credential-requests/requests/v2
4. Use older legacy request paths only when the workspace clearly still depends on them.
Important branching rule:
- if the workspace returns a policy or permission denial such as
403 Permission deniedor a message indicating credential requests are not enabled for the target user/workspace, stop retrying request endpoints and switch to the guided connection flow above - do not keep retrying equivalent credential-request endpoints when the failure is clearly policy-based rather than endpoint-shape-based
- only fall back to another endpoint when the evidence suggests API-version mismatch, route availability, or request-shape incompatibility
- if authorization fails because an existing connection is expired, revoked, or otherwise invalid, do not try to re-auth that connection in place; use the credential-request path to create a fresh connection
Preflight: reuse before create
Before opening a new credential request, verify all of the following:
- correct zone
- correct organization and team
- the provider has already been proven in the Make app catalog for this organization/team
- existing connections for the target app in that team
- whether one of those connections is already suitable for the requested account and scope
Use a two-tier proof model:
- A connection is a reuse candidate when the app or connection family matches the discovered module, the account identity matches the requested target,
POST /api/v2/connections/{connectionId}/testreturnsverified: true, and the required scope fit is known or can be checked. - A connection is proven for retrieval only after a real
POST /api/v2/scenarios/{scenarioId}/runsucceeds through a shell bound to that connection for the intended path, method, query, and body.
A proven run validates that operation. It does not prove unrelated future provider paths or write methods.
No-duplicate request rule:
- before initial provisioning, search existing requests for the same app, account target, recipient, and required scope or credential shape
- if an active request already exists for the same fresh authorization incident, return its
publicUriinstead of creating another duplicate link - a stale
pendingrequest status never overrides a verified connection, and a completed request is not proof that the resulting connection is usable until the connection and a real shell run are verified - after a live shell run proves an existing connection has provider auth, permission, or insufficient-scope failure, do not repair that old OAuth connection in place; create or return a fresh Make Credential Request/new connection link for the missing authorization
REST example:
curl -sS \
-H "authorization: Token $API_KEY" \
-H 'accept: application/json' \
-H 'user-agent: Mozilla/5.0' \
"${BASE_URL}/api/v2/connections?teamId=${TEAM_ID}&type[]=azure"For REST calls, prefer the type filter. Do not rely on accountName=... query parameters to filter the response.
Do not treat a type match alone as enough to reuse the connection. Also confirm:
- the connection family matches the discovered module's expected connection family
- the account identity matches the requested mailbox, tenant, workspace, or user
- the scope set is sufficient for the intended API path and method
- the connection is not known to be expired, revoked, or otherwise invalid
If Make MCP or another supported surface exposes connection detail, inspect it before reuse. Useful checks include the visible account label and scope count.
Connection verification before reuse
Before reusing a connection, or before trusting an existing shell that already points at a connection, verify the connection through Make itself:
1. Get connection detail:
GET /api/v2/connections/{connectionId}
2. Test the saved credentials:
POST /api/v2/connections/{connectionId}/test
3. When scope IDs are available and scope fit matters, check scope explicitly:
POST /api/v2/connections/{connectionId}/scoped
Treat {"verified": true} from /test as the liveness proof. Treat verified: false, provider auth errors, revoked credentials, or a past expire value as not reusable. Liveness is necessary for reuse, but it is not provider authorization proof for every path or scope; the first successful shell run proves only the tested operation.
Important nuance:
- a future
expiretimestamp means the connection is still usable - Credential Request detail can lag or remain
pendingeven after the UI shows a credential as authorized - when
/connections/{connectionId}/testreturnsverified: true, that verified connection wins over stale request-detail status
Do not create a second Credential Request for the same app/account just because an old request detail still says pending during initial provisioning. Reuse the saved requestId, inspect it, list matching connections again, verify candidate connections, and continue if a verified connection is found. This preflight rule does not override a live shell-run authorization failure: when a run proves the old connection lacks the needed provider permission, create or return a fresh request link for a new connection.
Recipient and account-identity gate
Before creating a new credential request, resolve two separate questions:
1. Who should complete the authorization flow? 2. Which provider account should the resulting connection point at?
Do not assume the current Make token owner is automatically the right credential-request recipient if the task is for another human or shared account.
Do not assume the first matching connection is correct when multiple connections exist for the same app. Compare at least:
- connection type
- account metadata such as email, domain, tenant, or UID when available
- scenario usage if the shell is expected to reuse a known existing scenario
- scope fit for the requested operation
If the intended recipient or target account identity is unclear, stop and ask for that exact missing item before creating a new request.
New-connection rule
If a new credential request results in a newly authorized connection, create a new shell for that new connection.
The same rule applies when an old connection exists but its authorization is expired or invalid.
Do not automatically patch a pre-existing shell to point at the newly created connection unless the user explicitly wants that exact shell replaced.
Reason:
- it keeps shell ownership and account identity clear
- it avoids silently repointing an existing reusable scenario from one account to another
- it generalizes across email, CRM, ticketing, and other SaaS providers
It also prevents a different class of mismatch: same vendor suite, wrong connection family. Example: a provider's mail app and calendar app may both authenticate through the same vendor, while the discovered Make modules still require different app bindings or different connection families.
External-recipient V2 request style
Use this style only when the credential must come from a different person or a service-account owner (see the recipient decision above). Why this style for that case:
- you specify the app and module context directly
- Make can derive required credential types more reliably
- the request is less dependent on hardcoded connection-type assumptions
Example body with placeholders:
{
"name": "Outlook API shell connection",
"teamId": TEAM_ID,
"description": "Authorize Outlook for the generic API shell scenario.",
"credentials": [
{
"appName": "microsoft-email",
"appModules": ["makeApiCall"],
"appVersion": 2,
"nameOverride": "outlook-api-shell"
}
],
"provider": { "providerMakeUserId": MAKE_USER_ID }
}provider is required by POST /credential-requests/requests/v2: either {"providerMakeUserId": <existing Make user id>} or {"name": "...", "email": "..."} to invite a new user. A current Make user id for the active account is visible as authorId in scenario run logs and via GET /api/v2/users/me. Requests without provider are rejected with a payload-shape error.
appModules entries are module IDs (for example ["makeApiCall"], or ["*"] for all modules with credentials) — not appName/moduleName pairs. appVersion matters: request the version that carries the universal API-call module (see the version sweep rule in discovery-and-shells.md).
appVersion is app-specific — never copy it from another app's example; two apps, even from the same vendor, can sit on different current versions. Take it from apps_recommend output, which returns the current appVersion per app, or sweep versions with app_modules_list. Module validation on credential requests only checks the module list of the requested version, so an API-call module that lives in a different version produces a misleading "module does not exist" error.
Use this external path only for a different recipient, and only when the workspace can infer the needed connection family from the discovered app/module context; when an explicit connection type or scope must be encoded, hand the recipient a create-by-credentials-shaped request instead.
Scope rule for universal API-call modules
Module-derived credential requests inherit OAuth scopes from the modules named in appModules. Universal API-call modules declare no provider scopes themselves, so a request derived only from the API-call module yields a connection that authenticates but carries only baseline identity scopes — every provider API call through the shell then fails with an insufficient-scope error.
["*"] module derivation is not a safe substitute here either: it can resolve to a different connection family of the same vendor than the one the API-call module requires.
For API-call shells, request the connection with create-by-credentials and an explicit scope array. Reserve module-derived requests for regular modules that declare their own scopes.
Insufficient-scope failures on existing connections
A structurally compatible connection can still fail at run time with 403 Request had insufficient authentication scopes or a similar provider permission error. Provider wording varies, but the meaning is the same: the connection exists and authenticates, yet it was authorized without the scope or permission the intended call needs.
Handling rule:
- treat insufficient-scope as "no suitable connection exists" in the decision
ladder, even though the connection tests as valid
- follow the Authorization Repair Playbook
- create one credential request for the target app/module, or use
create-by-credentials when the exact connection type and scope must be encoded
- after authorization, bind the shell to the new connection; do not expect
the old connection to gain scopes in place
Self-service create-by-credentials style
This is the standard self-service choice for API-call shells: it is not gated like the external request flow, and it encodes the connection type and scope explicitly instead of deriving them from modules.
Example body with placeholders:
{
"name": "Provider API shell connection",
"description": "Authorize the provider account for the generic API shell scenario.",
"teamId": TEAM_ID,
"connections": [
{
"type": "PROVIDER_CONNECTION_TYPE",
"description": "Readonly provider connection for the API shell example.",
"scope": ["PROVIDER_READ_SCOPE"],
"nameOverride": "provider-api-shell"
}
]
}This fallback is often the safer generic choice when you already know the exact connection family and scope requirement and want the request to encode them directly.
Practical rule:
- for the current user, prefer
create-by-credentialswith explicit type and scope (API-call shells) oractions/createwith module selection (regular modules) - use the external V2 request style only when the credential must come from a different recipient
- for vendor suites with multiple connection families, verify whether the discovered module expects an app-specific family (such as a mail-specific connection) or a broader vendor-wide family before encoding the type
Inspect authorization state
After the user opens the public authorization URL and completes consent, inspect the request:
GET /api/v2/credential-requests/requests/{requestId}/detail
Confirm:
- request status
- credential state
- resulting credential or connection identifier
Also confirm whether the resulting connection is usable in the target scenario or module family. Authorization success alone does not prove that retrieval execution is correctly configured. A credential request appearing in a list, reporting completed, or returning a connection identifier is still only request-state evidence; it is not authorization proof for the intended provider operation.
After inspecting the request detail, list connections again and match the resulting connection back to the target identity before patching the scenario.
Then verify the matched connection:
GET /api/v2/connections/{connectionId}for visible detailsPOST /api/v2/connections/{connectionId}/testfor credential livenessPOST /api/v2/connections/{connectionId}/scopedif the required scope IDs are known and scope fit is still uncertain
Only patch or create a shell after the target connection is verified. If verification fails, treat the credential as not ready and go back to the same Credential Request or create a new request only when the old request can no longer satisfy the app/account requirement.
Patch the scenario after authorization
Once the chosen connection exists: 1. inspect the current blueprint 2. inject the confirmed connection value in the correct module field or restore structure 3. update the scenario 4. activate it if needed 5. run a verification execution
If a reusable shell scenario already exists, prefer patching that shell only when reusing an existing suitable connection. If the connection is newly created, create a new shell for that new connection.
What to record before patching
Always record these values first:
- scenario ID
- target module ID in the blueprint
- exact connection field or restore path to update
- selected connection ID
- both connection type layers for the app
If the request was created for a different recipient than the current token owner, also record:
- intended recipient identity
- recipient Make user ID if known
- any workspace or feature limitations discovered during request creation
Safe user-facing write prompt
Use a brief confirmation prompt before patching an existing scenario:
You asked me to patch the Make shell with the authorized connection.
Risk: this can overwrite the current connection mapping and stop the scenario if the wrong connection is inserted.
Example: if the shell expects an Outlook connection and I patch a different credential, the API-call module can fail until corrected.
Reply with YES to proceed, or tell me what to change first.Public sharing rule
If this workflow is being published or contributed to a shared repository:
- replace real team IDs, organization IDs, user IDs, connection IDs, and workspace-specific names with placeholders
- use neutral labels such as
provider-api-shellinstead of personal labels - avoid phrases such as
verified liveorworked in tenant X - describe fallbacks as compatibility options, not as tenant-specific facts
Discovery and Shells
This file covers how to discover the correct Make app and module, distinguish connection type layers, and build the reusable shell blueprint.
It does not guarantee that the first generic shell draft is directly activatable. Treat the generic blueprint as a starting template that must be reconciled with current app-specific metadata.
Goal
Build one reusable scenario pattern for many apps:
scenario-service:StartSubscenario- one app-specific
Make an API Callstyle module in the middle scenario-service:ReturnData
The middle module is the only app-specific part.
This goal is about shell provisioning, not about proving the final business retrieval output. Retrieval strategy and output normalization come after the shell is connection-ready.
The shell described here is generic for any SaaS provider that exposes an app-specific Make API-call module. It is a reusable API transport scenario, not a business-specific scenario.
Source of truth
Use current Make metadata as the source of truth. Preferred evidence sources: 1. current IMT app metadata 2. current module metadata from Make MCP or Make APIs 3. current connection listing behavior in the active workspace
Local notes, old blueprints, and example apps are only hints.
Provider-to-app resolution
When the user asks for business data such as emails, CRM records, or tickets, do not start with shell creation.
Resolve the app in this order: 1. explicit user statement about the provider or system 2. existing Make artifacts that unambiguously prove the provider and account 3. Make app-catalog lookup for the provider candidate 4. if still ambiguous, ask only for the missing provider or account identity
Examples:
Get my emails from user@example.com on Gmailalready gives both the provider and the account targetGet my emails from todaydoes not; resolve provider and account firstGet my open leadsrequires provider resolution such as HubSpot vs Salesforce before any shell work
Do not invent a provider from the business object alone.
Standard shell contract
Module 1: StartSubscenario
Use:
scenario-service:StartSubscenario
Expose these inputs:
pathmethodheaderqsbody
Important:
- treat this as module-level intent, not as proof that the scenario-level interface is deployed
- after creating or updating the scenario, explicitly set
/api/v2/scenarios/{scenarioId}/interface - verify the deployed interface before the first
/runcall
Module 2: app-specific API-call module
Examples only:
- Gmail:
google-email:makeAnApiCall - Outlook:
microsoft-email:makeApiCall - HubSpot:
hubspotcrm:MakeAPICall
Typical mapper:
{
"url": "{{2.path}}",
"method": "{{2.method}}",
"headers": "{{2.header}}",
"qs": "{{2.qs}}",
"body": "{{2.body}}"
}Default retrieval should use GET. Treat PUT, PATCH, and DELETE as write/destructive methods and require explicit user confirmation before running them.
Connection binding is part of the blueprint, not an afterthought
The middle module must carry the connection in its parameters block:
{
"parameters": { "__IMTCONN__": CONNECTION_ID }
}A shell created without __IMTCONN__ can pass blueprint validation, patch its interface, and even activate — and then fail at run time with a provider auth error such as Slack's not_authed. Verify the binding right after create or update by re-reading the blueprint, and bind the connection in the same write that creates the module, not in a later repair step.
Blueprint validation, interface deployment, activation, and connection liveness are setup proofs. Provider authorization for a specific operation is proven only by a successful shell run through the bound connection.
Activation gate
POST /scenarios/{scenarioId}/run on a created-but-inactive scenario fails with IM325: Scenario is not activated. Activation (POST /scenarios/{scenarioId}/start) is a separate mandatory step after create/patch/verify and before the first run.
Body-mapper compatibility rule
The generic shell exposes body, but some provider modules do not behave well when body is present and empty on read-style calls.
Use this decision rule:
- default shell template: include the
bodymapper - if the provider module serializes empty/null bodies badly on
GETorDELETE, remove the Module 2bodymapper for the read shell - keep or reintroduce the
bodymapper for write shells that need request payloads
This is a provider-module compatibility choice, not a reason to change the generic shell output contract.
Confirmed example to remember, but not to universalize: google-calendar:makeApiCall v5 has been observed to work better when a read/delete shell omits the body mapper entirely.
Module 3: ReturnData
Use:
scenario-service:ReturnData
Typical mapper:
{
"data": "{{MIDDLE_API_MODULE_ID.body}}"
}For the generic API shell contract, {{MIDDLE_API_MODULE_ID.body}} is not just a heuristic. It is the intended transport contract.
In this skill's generic example blueprint, the middle API-call module id is 3, so the example mapper is {{3.body}}. In a real blueprint, do not hardcode 3: inspect the actual middle module id and map ReturnData to that module's body. A Make UI export may use ids such as StartSubscenario 2, API-call module 5, and ReturnData 4; in that case the correct mapper is {{5.body}}.
Do not replace it with {{MIDDLE_API_MODULE_ID}} or {{MIDDLE_API_MODULE_ID.data}} inside this generic shell pattern.
Use bundle inspection only to confirm that body contains the expected payload or error object. Do not use bundle inspection to redefine the generic shell contract.
App-action shell fallback
Use this only when the version sweep proves that no app version exposes a universal API-call module.
Keep the same three-module frame, but the middle module is an app-specific action module with its own real parameter mapping instead of the HTTP transport mapper:
scenario-service:StartSubscenariowith the standard interface- one app action module, for example
google-calendar:ActionGetEventsv4 scenario-service:ReturnData
Rules that differ from the generic shell:
1. Map the module's real parameters, not url/method/headers. Feed variable inputs from the standard interface's qs object as {{2.qs.<field>}}. Required module parameters must be mapped or set as literals or the run fails with BundleValidationError: Missing value of required parameter '<name>' (confirmed example: ActionGetEvents requires singleEvents). 2. ReturnData.data maps the module's actual output field, which is usually not body. ActionGetEvents returns its events under array, so the mapper is {{5.array}} when the middle module id is 5. 3. __IMTCONN__ binding is mandatory, exactly as for the generic shell. 4. Write safety cannot use the HTTP method, because the action itself decides mutation. Treat module names matching Get/List/Search/Watch/Download/Read/ Fetch as read-style; require explicit confirmation for everything else (Create/Update/Delete/Send/...). 5. Pass run inputs as a plain qs object so named fields resolve in the mapper. Do not convert qs to key/value pair lists for action shells; that format is only correct for universal API-call modules.
An app-action shell is bound to one module and one parameter shape. It is reusable for that one operation, not a generic transport. Name it after the operation, and prefer the generic shell whenever a universal module exists.
Activation readiness rule
Do not assume a minimal middle-module block is valid just because the slug and mapper are correct.
Before activation, compare the generated middle module with current evidence from the same app and version in the active workspace, such as:
- a current scenario blueprint that already uses the module
- current module metadata from Make
- a current exported module block from the same app/version
Specifically verify whether the module requires app-specific metadata structures such as:
expectmetadata.restore.expect- connection restore blocks
- parameter restore hints
If activation returns a generic validation error such as Scenario contains errors, inspect the live blueprint and reconcile the metadata structure before retrying.
Important discovery rule
The API-call module name is not standardized across apps. Common variants include:
makeAnApiCallmakeApiCallMakeAPICallMakeAnAPICallActionMakeAnApiCall
Never guess the exact name or casing.
Sweep app versions before concluding a module does not exist
The universal API-call module may exist only in some app versions, and the version your discovery returns first is not necessarily the one that has it.
Confirmed example: google-calendar v4 exposes only app-specific action modules (ActionGetEvents, ActionCreateEvent, ...) and has no universal API-call module at all, while google-calendar v5 exposes makeApiCall ("Make an API Call"). Slack exposes MakeAPICall in v4.
Before falling back to app-specific action modules: 1. enumerate the app's available versions 2. query the module catalog per version: GET /api/v2/imt/apps/{appName}/{version}/modules-with-credentials 3. search each version's module list case-insensitively for an API-call module 4. prefer the version that has the universal module, even if it is not the version an existing scenario or first lookup returned
Only when no version has a universal API-call module, use the app-action shell fallback described below.
Module base URL and path prefix
Universal API-call modules prepend an app-specific base URL to the url input. Passing a full provider path can double a prefix and produce a provider-side 404 with a visibly duplicated segment.
Confirmed example: google-calendar:makeApiCall v5 has a base URL ending in /calendar, so the correct shell path for an events read is /v3/calendars/primary/events — not /calendar/v3/calendars/primary/events, which fails with a Google 404 for /calendar/calendar/v3/....
When a shell run returns 404 and the echoed URL shows a repeated segment, strip the app prefix from path and retry before touching the blueprint.
API surfaces
IMT app discovery
List apps:
GET /api/v2/imt/apps?organizationId=ORG_ID&teamId=TEAM_ID
Warning: this endpoint has no working server-side search. search, query, limit, and scoredSearch are silently ignored and the response is always the full catalog (about 4k apps, alphabetical). Filter client-side over name and label, case-insensitively, and prefer a curated top-apps catalog before fetching the full list at all. Do not feed the unfiltered response to a model context.
The catalog mixes verified, community, and custom apps. Auto-selection rules:
- Community apps (name prefix
communityApp-*) are never auto-selected.
When the requested provider exists only as a community app, treat it as not available and build a generic HTTP fallback shell instead.
- Custom/SDK apps of the own organization (catalog name prefix
app#*)
are selectable. The authoritative source is GET /api/v2/imt/apps-meta?organizationId=ORG_ID ("all verified and custom apps"); custom-app names appear there without the app# prefix.
- Builtin utility pseudo-apps —
builtin(Flow Control),util(Tools),
gateway (Webhooks), regexp (Text parser) — are not SaaS providers and must be excluded from search results entirely.
Get one app in detail:
GET /api/v2/imt/apps/{appName}/{version}
Use the app-catalog endpoint to prove that the provider exists in Make for the active organization/team context before provisioning a shell.
Scenario APIs
Create scenario:
POST /api/v2/scenarios?confirmed=true
Update scenario:
PATCH /api/v2/scenarios/{scenarioId}?confirmed=true
Activate scenario:
POST /api/v2/scenarios/{scenarioId}/start
Run scenario:
POST /api/v2/scenarios/{scenarioId}/run
Inspect interface:
GET /api/v2/scenarios/{scenarioId}/interface
Set interface:
PATCH /api/v2/scenarios/{scenarioId}/interface
Inspect blueprint:
GET /api/v2/scenarios/{scenarioId}/blueprint
Inspect a failed run:
GET /api/v2/scenarios/{scenarioId}/logs/{executionId}
The run-log response carries the structured error (error.name, error.message, for example BundleValidationError with the missing parameter), which is far more specific than the run endpoint's status. The full REST surface is documented at {BASE_URL}/api/v2/openapi.json.
For on-demand API shells, do not stop after scenario create or update. Explicitly patch the scenario-level interface, then verify it:
{
"input": [
{ "name": "path", "type": "text", "required": false, "label": "Path" },
{ "name": "method", "type": "text", "required": false, "label": "Method" },
{ "name": "header", "type": "any", "required": false, "label": "Header" },
{ "name": "qs", "type": "any", "required": false, "label": "Query String" },
{ "name": "body", "type": "any", "required": false, "label": "Body" }
]
}Reason: StartSubscenario.metadata.interface documents the shell shape, but it does not reliably deploy the scenario-level run interface by itself. Treat PATCH /interface as mandatory for reusable on-demand shells.
Query-string discipline
Treat query parameters as a separate shell input named qs.
Use qs for provider API query parameters such as:
- Google Drive
q,fields,supportsAllDrives,uploadType, oralt - Gmail search/list options
- Microsoft Graph
$select,$filter,$top, or pagination tokens - SaaS-specific page, limit, cursor, or projection fields
If a caller gives a path that already includes a query string, split the path before running the shell:
path: the API path without?queryqs: the query parameters as key/value entries
This keeps delete/read/update calls deterministic. For example, Google Drive file delete is a DELETE /drive/v3/files/{fileId} call with query parameters such as supportsAllDrives=true; those belong in qs, not in an ad hoc URL string.
Blueprint shape normalization
Make blueprints appear in two common shapes:
- Create/update API payloads require a top-level
flow. - UI exports and some stored artifacts may carry the scenario steps under
subflows[0].flow.
Normalize before writing:
- when creating or patching a scenario, send a payload with top-level
flow - when exporting or storing a human-readable artifact,
subflows[0].flowis acceptable
Do not send a raw UI export to POST /api/v2/scenarios or PATCH /api/v2/scenarios/{scenarioId} without converting subflows[0].flow to top-level flow.
Connection APIs
List candidate connections:
GET /api/v2/connections?teamId=TEAM_ID&type[]=CONNECTION_TYPE
Inspect connection details:
GET /api/v2/connections/{connectionId}
Verify connection liveness:
POST /api/v2/connections/{connectionId}/test
Verify whether required scopes are present, when scope IDs are known:
POST /api/v2/connections/{connectionId}/scoped
Use /test before reusing a connection or a shell that already points at one. A response with verified: true is the Make-side proof that the saved provider credentials are still live. It is not proof that every provider path, method, or scope is authorized. If a Credential Request detail is stale or still says pending, a verified connection still wins.
Base URL and zone
Do not treat a successful user-scoped endpoint as proof that the workspace zone is correct.
Observed practical behavior:
GET /api/v2/users/mecan return200on multiple zonesGET /api/v2/imt/apps/...can also return200on multiple zones- team-scoped endpoints such as
GET /api/v2/connections?teamId=...can still fail with403 Permission deniedon the wrong zone
Therefore resolve the zone before team-scoped work.
Preferred order: 1. infer the probable zone from the user's dashboard URL if provided 2. list organizations on that zone 3. list teams for the matching organization 4. confirm with a team-scoped read such as GET /api/v2/connections?teamId=TEAM_ID
Ask the user which Make zone or base URL applies only if it is still not recoverable from the environment or their provided links. For generic examples, define:
BASE_URL="https://us1.make.com"Then use that variable consistently in examples. Replace it with the actual zone only when the user provides or confirms it.
Resolve organizations and teams
List organizations first:
curl -sS \
-H "authorization: Token $API_KEY" \
-H 'accept: application/json' \
-H 'user-agent: Mozilla/5.0' \
"${BASE_URL}/api/v2/organizations"Then list teams for the organization that owns the target workspace:
curl -sS \
-H "authorization: Token $API_KEY" \
-H 'accept: application/json' \
-H 'user-agent: Mozilla/5.0' \
"${BASE_URL}/api/v2/teams?organizationId=${ORG_ID}"Finally confirm the zone with a team-scoped call:
curl -sS \
-H "authorization: Token $API_KEY" \
-H 'accept: application/json' \
-H 'user-agent: Mozilla/5.0' \
"${BASE_URL}/api/v2/connections?teamId=${TEAM_ID}"If that final call returns 403 Permission denied, treat the zone as wrong or the team as inaccessible and stop guessing.
Discover the app and module
Step 1: list candidate apps
curl -sS \
-H "authorization: Token $API_KEY" \
-H 'accept: application/json' \
"${BASE_URL}/api/v2/imt/apps?organizationId=${ORG_ID}&teamId=${TEAM_ID}" \
| jq '[.apps[] | select((.name + " " + .label) | ascii_downcase | contains("outlook"))]'The endpoint returns the entire catalog regardless of query parameters (search/limit/scoredSearch are ignored), so the filtering must happen client-side as shown. Example: searching "outlook" this way surfaces microsoft-email ("Microsoft 365 Email (Outlook)").
Step 2: find apps exposing API-call modules
Search module names case-insensitively for strings such as:
makeapicallmakeanapicall
Step 3: inspect one app in detail
Example:
curl -sS \
-H "authorization: Token $API_KEY" \
-H 'accept: application/json' \
"${BASE_URL}/api/v2/imt/apps/hubspotcrm/2"Confirm:
- exact app name
- app version
- exact module slug and casing
- any module-specific parameter shape that differs from the standard mapper
Two different type layers
Do not mix these concepts.
1. Scenario or module connection parameter type
Used in blueprint metadata or module restore data. Examples:
account:google-emailaccount:azure
2. Connection listing or credential request type
Used when listing connections or creating fallback credential requests. Examples:
google-emailazure
Document both values explicitly before building or patching the shell.
Existing-connection preflight
Before creating any credential request, check whether the workspace already has a usable connection for the app.
Do not stop at “same vendor”. Check structural compatibility:
- same app family and connection family required by the discovered module
- same target account identity when available
- same or broader scope set than the requested operation needs
- no evidence that the connection is expired, revoked, or otherwise invalid
If the tooling exposes detailed connection metadata, inspect it before testing reuse. Useful evidence includes:
- connection type
- account or accountName
- scope list or scope count
- last validation or health indicators when available
Common examples:
- Gmail-style module:
google-email:makeAnApiCallusually expectsaccount:google-email; do not assume a genericgoogleconnection is interchangeable - Google workspace app modules such as Calendar/Sheets/Drive often use
account:google; do not assume a Gmail-specific connection is interchangeable - Microsoft mail modules commonly use
account:azure
For the REST API, filter /api/v2/connections with type or type[]:
curl -sS \
-H "authorization: Token $API_KEY" \
-H 'accept: application/json' \
-H 'user-agent: Mozilla/5.0' \
"${BASE_URL}/api/v2/connections?teamId=${TEAM_ID}&type[]=google-email"Notes:
type=google-emailandtype[]=google-emailare both accepted by the REST endpoint- do not assume
accountName=google-emailwill filter correctly in REST just because Make MCP tooling usesaccountNameterminology - only create a credential request when no suitable existing connection is available for the target app and scope
Existing-shell preflight
Before creating a new shell scenario, check whether the active team already has one that matches the generic shell contract.
Look for a scenario that has all of the following:
scenario-service:StartSubscenario- the exact app-specific API-call module for the resolved app and version
scenario-service:ReturnData- on-demand execution or another explicit reusable shell shape
Prefer reusing a shell when:
- the module slug and app version still match current metadata
- the shell is still bound to the same app family and connection family
- the scenario interface still exposes
path,method,header,qs, andbody - the shell is already linked to a suitable connection or can be patched safely
Do not reuse a shell for a newly created connection. When the connection is new, create a new shell dedicated to that connection.
Do not treat “same SaaS vendor” as enough for shell reuse. A Gmail shell, a Google Calendar shell, and a Google Sheets shell may all live under Google but still require different Make apps, different module slugs, and different connection families.
Only create a new shell when:
- no matching shell exists
- the existing shell uses the wrong app, wrong module slug, or wrong contract
- the existing shell cannot be patched safely without breaking a live flow
- a new connection has just been created for a new account or authorization context
- the old connection exists but authorization failed because it is expired or invalid
Record these values before deciding to reuse or create:
- scenario ID
- scenario name
- app name and version
- middle-module slug
- whether the current
ReturnDatamapper still returns the actual middle API-call module body, such as{{3.body}}in the generic example or{{5.body}}in a blueprint whose middle module id is5
Practical workflow
1. Identify the target app. 2. Prove that the target provider exists in the Make app catalog for the active organization/team. 3. Discover the exact app name, version, and API-call module slug. 4. Determine both connection type layers. 5. Check whether a suitable connection already exists. 6. Check whether a matching shell scenario already exists for that existing connection. 7. Create or resolve the credential request only if a suitable connection does not already exist. 8. Generate the three-module shell blueprint when no reusable shell exists or when a new connection has just been created. 9. Reconcile the middle-module metadata against a real current module blueprint for the same app/version. 10. Create the scenario when required by the shell-reuse rule. 11. Patch the selected existing connection only when reusing an existing shell. 12. Explicitly set the scenario-level interface. 13. Verify the interface. 14. Activate and run the scenario.
Shell output vs. retrieval output
Keep these separate:
- Shell output contract: a transport shape for passing data through
ReturnData - Retrieval output contract: the user-facing payload for messages, records, issues, or tickets
For this generic shell pattern, the shell output contract is fixed:
{
"data": "{{MIDDLE_API_MODULE_ID.body}}"
}In the generic example blueprint, MIDDLE_API_MODULE_ID is 3, so the example mapping is {{3.body}}.
The shell may activate successfully while still returning an unusable payload for the business question. That is a retrieval/output-normalization problem, not a shell-contract problem.
Safety gate for write operations
Before any operation that changes a live scenario, ask for explicit confirmation. Keep it short and concrete:
You asked me to update an existing Make scenario.
Risk: this can replace a module mapper or connection value and break a live flow until it is repaired.
Example: changing the API-call module connection could stop the shell from authenticating until the correct connection is restored.
Reply with YES to proceed, or tell me what to change first.{
"name": "Generic API Shell",
"flow": [
{
"id": 2,
"module": "scenario-service:StartSubscenario",
"version": 2,
"parameters": {},
"mapper": {},
"metadata": {
"designer": { "x": 0, "y": 0 },
"restore": {},
"interface": [
{ "name": "path", "type": "text", "required": false, "multiline": false },
{ "name": "body", "type": "any", "required": false },
{ "name": "header", "type": "any", "required": false },
{ "name": "qs", "type": "any", "required": false },
{ "name": "method", "type": "text", "required": false, "multiline": false }
]
}
},
{
"id": 3,
"module": "APP_NAME:API_CALL_MODULE_NAME",
"version": 1,
"parameters": {},
"filter": null,
"mapper": {
"url": "{{2.path}}",
"method": "{{2.method}}",
"headers": "{{2.header}}",
"qs": "{{2.qs}}",
"body": "{{2.body}}"
},
"metadata": {
"designer": { "x": 300, "y": 0 },
"restore": {
"expectUserSelection": true,
"notes": "Replace module name, version, and connection metadata with values discovered from the active workspace. Reconcile any app-specific expect or restore structures against a real current blueprint before activation."
}
}
},
{
"id": 4,
"module": "scenario-service:ReturnData",
"version": 1,
"parameters": {},
"mapper": {
"data": "{{3.body}}"
},
"metadata": {
"designer": { "x": 600, "y": 0 },
"restore": {}
}
}
],
"metadata": {
"version": 1,
"notes": "Template only. Replace placeholders with discovered app and connection data before deployment. This file is a provisioning starter, not proof that the scenario is activatable. After create or update, explicitly patch the scenario-level interface before the first run. Query-string parameters belong in qs, not in ad hoc path concatenation. For the generic shell contract, keep ReturnData mapped to the actual middle API-call module body. In this template the middle module id is 3, so ReturnData is {{3.body}}; if a real export uses a different middle module id, update the mapper accordingly. Validate runs to confirm body contains the intended payload or error object. If a provider module mishandles empty bodies on GET or DELETE, keep this shell contract but consider a read shell without the Module 2 body mapper and a separate write shell with it. If this blueprint is stored as subflows[0].flow, normalize it back to top-level flow before Make scenario create/update calls."
}
}
{
"name": "HTTP Fallback Shell - noAuth",
"flow": [
{
"id": 2,
"module": "scenario-service:StartSubscenario",
"version": 2,
"parameters": {},
"mapper": {},
"metadata": {
"designer": {
"x": 0,
"y": 0
},
"restore": {},
"interface": [
{
"name": "url",
"type": "text",
"label": "URL",
"required": true,
"multiline": false
},
{
"name": "method",
"type": "text",
"label": "Method",
"required": true,
"multiline": false
},
{
"name": "headers",
"spec": [
{
"name": "name",
"type": "text",
"label": "Name"
},
{
"name": "value",
"type": "text",
"label": "Value"
}
],
"type": "array",
"label": "Headers",
"required": false
},
{
"name": "qs",
"spec": [
{
"name": "name",
"type": "text",
"label": "Name"
},
{
"name": "value",
"type": "text",
"label": "Value"
}
],
"type": "array",
"label": "Query Parameters",
"required": false
},
{
"name": "body",
"type": "text",
"label": "Body",
"required": false,
"multiline": true
}
]
}
},
{
"id": 3,
"module": "http:MakeRequest",
"version": 4,
"parameters": {
"tlsType": "",
"proxyKeychain": "",
"authenticationType": "noAuth"
},
"filter": null,
"mapper": {
"url": "{{2.url}}",
"method": "{{lower(2.method)}}",
"headers": "{{2.headers}}",
"contentType": "{{if(length(2.body) > 0; \"json\")}}",
"inputMethod": "jsonString",
"shareCookies": false,
"parseResponse": false,
"allowRedirects": true,
"queryParameters": "{{2.qs}}",
"stopOnHttpError": false,
"jsonStringBodyContent": "{{2.body}}",
"requestCompressedContent": true
},
"metadata": {
"designer": {
"x": 300,
"y": 0
}
}
},
{
"id": 1,
"module": "scenario-service:ReturnData",
"version": 2,
"parameters": {},
"mapper": {
"data": "{{3.data}}",
"headers": "{{3.headers}}",
"statusCode": "{{3.statusCode}}"
},
"metadata": {
"designer": {
"x": 600,
"y": 0
},
"expect": [
{
"name": "data",
"type": "any",
"label": ""
},
{
"name": "statusCode",
"type": "any",
"label": ""
},
{
"name": "headers",
"type": "any",
"label": ""
}
]
}
}
],
"metadata": {
"version": 1
}
}
HTTP Fallback Shells
Use this reference when the target provider has no dedicated Make app (or no usable API-call module). Instead of giving up or asking for a custom app, build a generic HTTP shell: the same three-module on-demand scenario family as the API-call shells, but with http:MakeRequest (HTTP app, version 4) in the middle. The shell takes url, method, headers, qs, and body as inputs and returns data, statusCode, and headers.
All four authentication variants below were verified live against the Make REST API (scenario creation, activation, and execution).
Module anatomy
http:MakeRequest v4 selects authentication through the module parameters (not the mapper):
authenticationType | Extra required parameter | Credential storage | Verified |
|---|---|---|---|
noAuth | none | none — pass auth headers/query through the shell inputs | end-to-end |
apiKey | apiKeyKeychain: key id | Keys API, type apikeyauth | end-to-end (header injected) |
basicAuth | basicAuthKeychain: key id | Keys API, type basicauth | end-to-end (authenticated: true) |
oAuth | oAuthAccount: connection id | oauth2 connection ("HTTP OAuth 2.0") | creation verified; attachment requires an authorized connection |
With noAuth, place the authentication wherever the target API expects it by passing it through the shell inputs: an Authorization header (or any custom header) in headers, a token in qs, or credentials inside the JSON body. This keeps the shell generic, but the secret then transits the caller — prefer an apiKey/basicAuth keychain whenever the value is an actual secret, so it stays inside Make.
Credential storage (Keys API)
Keychains live under the Keys API, not under connections:
- List key types:
GET /api/v2/keys/types(relevant:apikeyauth,basicauth) - Create API-key keychain:
curl -sS -X POST "${BASE_URL}/api/v2/keys" \
-H "authorization: Token $API_KEY" -H 'content-type: application/json' \
--data-binary '{
"teamId": TEAM_ID,
"name": "Acme API Key",
"typeName": "apikeyauth",
"parameters": {"key": "<secret>", "placement": "header", "name": "X-API-Key"}
}'placement is header or qs; parameters.name is the header/query parameter name. For Basic auth use "typeName": "basicauth" with {"authUser": "...", "authPass": "..."}. The response returns the key id — that id goes into the module parameter (apiKeyKeychain / basicAuthKeychain). Delete test keys with DELETE /api/v2/keys/{id}?teamId=.
OAuth 2.0 connections
The oAuth variant references an account:oauth2 connection ("HTTP OAuth 2.0"). It can be created via the API:
curl -sS -X POST "${BASE_URL}/api/v2/connections?teamId=${TEAM_ID}" \
-H "authorization: Token $API_KEY" -H 'content-type: application/json' \
--data-binary '{
"teamId": TEAM_ID,
"accountType": "oauth2",
"accountName": "Acme OAuth2",
"flowType": "authorizationCode",
"scopeSeparator": " ",
"tokenPlacement": "header",
"tokenName": "access_token",
"clientId": "...",
"clientSecret": "...",
"authorizeUri": "https://provider/oauth/authorize",
"tokenUri": "https://provider/oauth/token",
"scope": []
}'Constraints verified live:
- Valid
flowTypeoptions areauthorizationCodeandimplicitonly. There
is no client-credentials flow on this connection type.
- The connection is created unauthorized (
uid: null). The OAuth consent
itself is always interactive, but both completion paths below are fully drivable via the REST API.
- Creating a scenario that references an unauthorized oauth2 connection fails
with IM304 "Connection not found 'http:<id>'". Authorize the connection first, then create or patch the shell.
GET /connections/{id}/editable-data-schemaon an oauth2 connection lists
clientId, clientSecret, scope, scopeSeparator, tokenPlacement, tokenName, additionalAuthorizeParams, and similar config fields for POST /connections/{id}/set-data. Tokens (accessToken/refreshToken) are not injectable — there is no way to skip the consent.
Completion path A: Credential Request (preferred)
The credential-request system fronts every http:MakeRequest auth variant. Discover the requestable module ids first:
curl -sS "${BASE_URL}/api/v2/credential-requests/apps/http/4/modules-with-credentials?teamId=${TEAM_ID}&organizationId=${ORG_ID}" \
-H "authorization: Token $API_KEY"Verified ids: MakeRequest:authenticationType:apiKey (keychain:apikeyauth), MakeRequest:authenticationType:basicAuth (keychain:basicauth), MakeRequest:authenticationType:oAuth (account:oauth2), plus mutual-TLS and proxy keychain variants. This means API keys and Basic credentials can also be collected through a credential request instead of asking the user to paste secrets into chat.
Create the request (verified live; note appModules must be an array — a plain string fails with Expected array):
curl -sS -X POST "${BASE_URL}/api/v2/credential-requests/requests/v2" \
-H "authorization: Token $API_KEY" -H 'content-type: application/json' \
--data-binary '{
"teamId": TEAM_ID,
"name": "Authorize Acme via HTTP OAuth 2.0",
"credentials": [{"appName": "http", "appModules": ["MakeRequest:authenticationType:oAuth"]}],
"provider": {"providerMakeUserId": USER_ID}
}'The response contains a publicUri (credential-request inbox link). The end user opens it, enters the OAuth client configuration, and completes the consent; the derived oauth2 connection then appears authorized and its id goes into oAuthAccount. Track progress with GET /credential-requests/requests/{requestId}/detail (credential state moves from pending).
Completion path B: API-created connection + authorize link
For a connection created directly via POST /connections (as above), fetch the authorization link:
curl -sS -o /dev/null -w "%{redirect_url}" \
"${BASE_URL}/api/v2/oauth/auth/{connectionId}" \
-H "authorization: Token $API_KEY"Verified live: this returns 302 to a https://www.make.com/oauth/init?... URL that wraps the provider authorize call. Hand that URL (or the /api/v2/oauth/auth/{connectionId} link itself, opened while logged into Make) to the user to complete the consent. An optional scope query parameter adds scopes. The provider OAuth client must whitelist Make's callback https://www.integromat.com/oauth/cb/oauth2 as redirect URI. Afterwards confirm with POST /connections/{connectionId}/test (verified: true) before attaching the connection to the shell.
Blueprint
See examples/http-fallback-shell-blueprint.json for the full flow (StartSubscenario -> http:MakeRequest -> ReturnData). The middle module for the keychain variants differs only in parameters, e.g.:
{"tlsType": "", "proxyKeychain": "", "authenticationType": "apiKey", "apiKeyKeychain": 182303}Creation rules that differ from app API-call shells:
- The blueprint JSON must contain a top-level `name` — without it,
POST /api/v2/scenarios?confirmed=true fails with a 500 (code 23502, database not-null violation), not a helpful validation error.
- Create with
"scheduling": "{\"type\":\"on-demand\"}", then PATCH the
scenario interface (/scenarios/{id}/interface) with inputs url/method/headers/qs/body and outputs data/statusCode/headers.
- Activate before running:
POST /api/v2/scenarios/{id}/start. Running a
non-activated scenario fails with IM325 "Scenario is not activated".
Run contract
POST /api/v2/scenarios/{id}/run with responsive: true:
{
"data": {
"url": "https://api.example.com/v1/items",
"method": "GET",
"headers": [{"name": "X-Trace", "value": "shell"}],
"qs": [{"name": "limit", "value": "10"}],
"body": "{}"
},
"responsive": true
}headersandqsare arrays of{name, value}collections — this matches
the http:MakeRequest v4 expect spec exactly.
methodis lowercased by the mapper ({{lower(2.method)}}); valid values
are get, head, post, put, patch, delete, options.
- The mapper sets
contentTypeconditionally:
{{if(length(2.body) > 0; "json")}}. Leave body empty for GET — CDNs such as CloudFront in front of provider APIs (verified with Daytona) reject GET requests that carry a body with an opaque edge 403. With a static contentType: json the shell always sends a body, and an empty string then fails the run with BundleValidationError — the conditional avoids both failure modes. When a body is provided it must be valid JSON text.
stopOnHttpError: falseplus the returnedstatusCodelets the caller
handle provider errors; parseResponse: false returns the raw body text in data.
The non-GET write-confirmation rule for API-call shells applies unchanged: warn and get explicit user confirmation before POST/PUT/PATCH/DELETE through an HTTP fallback shell.
Retrieval Execution
This file covers what happens after connection provisioning succeeds.
Provisioning success is not the same thing as retrieval success. Treat retrieval as a separate phase with its own strategy choice, validation run, and output normalization.
Goal
Given a connection-ready Make API-call shell:
- configure the right API path pattern for the business request
- run a narrow validation request through the shell
- inspect the real output bundle
- normalize the result for the user-facing caller
Retrieval transport rule
For this workflow family, the Make API-call shell is always the retrieval transport.
Do not switch to provider-native Make search/list/get modules for the first retrieval step or for follow-up enrichment.
If the business request needs multiple steps, perform all of them through repeated runs of the API-call shell.
Execution workflow
1. Confirm the provider and the exact Make app version again. 2. Resolve the business target precisely: mailbox, inbox, account, pipeline, board, queue, or project. 3. Choose the API endpoint pattern that best matches the business request. 4. Run the narrowest possible validation call first through the shell. 5. Inspect the real output bundle from that run. 6. Keep scenario-service:ReturnData fixed to the generic shell contract and adjust only downstream normalization. 7. Re-run and verify the final payload.
Generic shell run contract
When using the generic three-module shell, run it with a payload shaped like this:
{
"data": {
"path": "...",
"method": "GET",
"header": [],
"qs": [],
"body": null
},
"responsive": true
}This is the default execution contract for the shell across providers.
The concrete path changes by provider, but the scenario-run payload shape stays the same.
Use qs for query-string parameters. Do not hide provider options inside a concatenated URL when the shell supports qs; normalize path?x=1&y=2 into path plus qs before the run.
The provider module's base URL may already include a service segment. This applies to any provider: when a 404 error output echoes a doubled path segment (/<segment>/<segment>/...), the path value repeated a segment the module base already carries — remove the duplicated prefix from path and retry. Confirm the effective base by checking the provider module documentation or a single probe call before composing further paths.
Important deployment precondition:
- do not assume the
StartSubscenariomodule metadata alone made this callable - explicitly set the scenario-level input interface first
- verify
/api/v2/scenarios/{scenarioId}/interfacebefore the first run
The keys under data must match the deployed interface exactly. For reusable shells, the standard execution path is: 1. PATCH /api/v2/scenarios/{scenarioId}/interface 2. GET /api/v2/scenarios/{scenarioId}/interface 3. POST /api/v2/scenarios/{scenarioId}/run
Use responsive: true for validation runs and for normal interactive retrieval whenever the response size is still manageable.
Default retrieval should use GET. Treat PUT, PATCH, and DELETE as write/destructive methods and require explicit user confirmation before running them.
Large payloads, timeouts, and extraction path
Two separate limits matter here:
- Make scenario execution limits and provider API limits during the run
- post-run inspection limits when reading full execution detail
Practical guidance: 1. Start with the narrowest possible list/search call. 2. Prefer responsive: true so the shell returns the business payload directly when feasible. 3. Read the returned payload from outputs.data first instead of forcing an execution-detail round trip. 4. Add provider-side narrowing such as limit, maxResults, pageSize, fields, updatedSince, or a search query before trying to inspect giant payloads. 5. Split list/search from detail enrichment instead of fetching everything in one run.
Do not treat a timeout or a heavy executions_get-detail response as evidence that the shell contract is wrong. First narrow the retrieval and reduce payload size.
Rate limiting and write safety
Even when scenarios_run itself is exempt from an organization's general request-rate bucket, provider APIs behind Module 2 still enforce their own limits.
Generic operating rules:
- rate-limit write methods more aggressively than reads
- use backoff and replay for 429-style provider errors
- keep batch sizes modest on detail-enrichment loops
- separate read-heavy and write-heavy workloads when the provider module behaves differently
Treat provider documentation and live error responses as the actual rate-limit source of truth. Even generous read limits are easy to exceed with naive fan-out.
If a provider module shows empty-body serialization issues on GET or DELETE, split the shell design:
- read/delete shell without a Module 2
bodymapper - write shell with a Module 2
bodymapper
That split is about request-shape compatibility and rate safety, not about changing ReturnData.
Common retrieval pattern for SaaS data
For most business retrieval tasks, use this pattern through the API-call shell:
1. Run a narrow list/search call first. 2. Collect stable identifiers from that first result. 3. Run follow-up detail calls only for the shortlisted records. 4. Normalize the detail payload into a user-facing summary.
This keeps the first execution cheap, proves that the shell works, and avoids over-fetching.
Email pattern
Use: 1. list or search messages/threads with a narrow filter 2. fetch message detail only for the returned IDs or thread IDs 3. normalize sender, subject, date, labels, snippet, and whether a reply seems needed
CRM pattern
Use: 1. search or list records with a narrow filter such as owner, stage, or updated-after 2. fetch detail only for the returned record IDs 3. normalize owner, company/contact, stage, last activity, next action, and urgency
Ticketing pattern
Use: 1. search or list issues or tickets with a narrow filter such as assignee, state, queue, or updated-after 2. fetch detail only for the shortlisted IDs 3. normalize requester, status, SLA or priority, latest comment, and next action
Chat/messaging pattern
Chat providers usually require a container identifier before any message read. Confirmed example: Slack's /conversations.history rejects calls without channel (missing required field: channel).
Use: 1. list conversations first (/conversations.list with types and a small limit) 2. pick the target container by recency or by the user's naming 3. fetch history only for that container id, with a small limit 4. normalize channel name, sender, timestamp, and text; note that bot or attachment-only messages can have empty text fields and still be valid
Suggested normalization contract
For user-facing summaries, normalize the provider payload into a stable business shape whenever practical:
{
"id": "provider-specific-id",
"title": "subject or record title",
"actor": "sender, requester, owner, or customer",
"status": "state or stage",
"updatedAt": "timestamp",
"summary": "snippet or compact summary",
"recommendedAction": "reply | inspect | ignore",
"reason": "why that action is recommended"
}The shell still returns raw body. This normalization happens after retrieval, not inside the shell contract.
Output-mapping rule
Do not mix the generic shell contract with retrieval-specific normalization.
A. Generic API shell contract
For the three-module generic API transport shell:
{
"data": "{{MIDDLE_API_MODULE_ID.body}}"
}That is the contract. It should stay stable across providers.
In the generic example blueprint, MIDDLE_API_MODULE_ID is 3, so the example mapping is {{3.body}}. In real Make exports, inspect the module ids and map to the actual middle API-call module. For example, a flow with StartSubscenario id 2, API-call module id 5, and ReturnData id 4 must use {{5.body}}.
Do not switch that generic contract to:
{{MIDDLE_API_MODULE_ID}}{{MIDDLE_API_MODULE_ID.data}}- another guessed nested field
B. Retrieval-specific normalization
Only after the body has been returned through the generic shell may you decide how to interpret the business payload:
- messages
- records
- issues
- tickets
- errors
If data: null, a bare number, or another unusable shape appears, first ask: 1. did the generic shell still return {{MIDDLE_API_MODULE_ID.body}} for the actual middle module id? 2. did the API path, method, headers, query parameters, or body match the provider requirement? 3. is the downstream interpreter reading the body correctly?
If the failure is actually an authorization failure from an expired or invalid connection, stop retrieval debugging and go back to the credential-request path instead of trying to re-auth the old connection in place.
Do not redefine the generic shell contract to compensate for a retrieval problem.
Failure interpretation
Keep failure diagnosis phase-specific:
- connection request failure: provisioning problem
- scenario activation failure: shell-provisioning problem
- empty or unusable payload from a successful run: retrieval or output-normalization problem
If activation fails with a generic validation error, go back to shell metadata. If the run succeeds but the payload is wrong, stay in Make and fix the API-call plan or downstream normalization before considering fallback.
Private debug bundle
For hard SaaS retrieval failures, collect one private troubleshooting bundle before changing strategy:
- original user request and resolved business target
- agent step log with timestamps and phase labels
- Make zone, organization, team, app name, app version, and API-call module slug
- scenario ID plus the relevant
GET /api/v2/scenarios/{scenarioId}/blueprintexcerpt showing the middle module and__IMTCONN__ - bound connection ID, connection detail summary,
/connections/{connectionId}/testresult, and/connections/{connectionId}/scopedresult when scope IDs are known - credential request IDs, recipient, app or connection type, scope or credential shape, and current status
- failing shell run payload summary (
path,method,qs,bodyshape) and the exact provider or Make error - execution-log reference from
GET /api/v2/scenarios/{scenarioId}/logs/{executionId}when an execution ID exists - final proof result:
success,auth_request_pending,missing_scope,wrong_connection,path_error, orunknown
This bundle is for private debugging. Before sharing publicly, sanitize it according to Sanitization and Sharing.
Authorization Repair Playbook
Use this sequence for provider authentication, authorization, insufficient-scope, wrong-account, or shell-bound-to-old-connection failures after a shell run:
1. Preserve the failing request payload exactly: path, method, header, qs, and body shape. 2. Read the shell blueprint with GET /api/v2/scenarios/{scenarioId}/blueprint and extract the middle module app, version, module slug, and bound __IMTCONN__. 3. If the shell has no bound connection, the wrong app/module, or the wrong connection family, treat it as shell provisioning or binding drift before changing API paths. 4. Inspect the bound connection with GET /api/v2/connections/{connectionId} and test liveness with POST /api/v2/connections/{connectionId}/test. 5. If the connection is not live, expired, revoked, or belongs to the wrong account/workspace, treat it as no suitable connection. Return to the credential-request path. 6. If the connection is live but the provider returns auth, permission, or scope errors, do not try to repair the old OAuth connection in place. Derive the minimal missing permission from the selected endpoint, provider documentation, or live error. When scope IDs are known, check with POST /api/v2/connections/{connectionId}/scoped only as evidence for the new request shape. 7. Create or return a fresh Make Credential Request/new connection for this auth failure. If an already-created fresh pending request for the same app, recipient, account target, and required scope exists, return its publicUri instead of creating another duplicate during the same incident. Otherwise create exactly one new request. 8. The user-facing answer must include the exact request publicUri auth link. If Make does not return a URL, state the exact publicUriUnavailableReason and the request status; do not collapse this into a vague "reauthorize" answer. For scope-based providers, include explicit connection type and explicit scope. For API-key, Basic, or other non-scope credential families, follow the credential paste-format rules instead of inventing scopes. 9. After authorization, list connections again, match the resulting connection to the target identity, test it, and run /scoped when scope IDs are known. 10. Bind according to the shell ownership rule: a newly authorized connection gets a newly created shell; patch an existing shell only when reusing a connection for the same automation and after the required write confirmation. 11. Rerun the same request payload (path, method, header, qs, body) through the correctly bound shell. This proves that operation only; repeat scope/path validation for materially different operations.
Do not keep guessing provider paths while the evidence points to connection identity, liveness, or scope. Path repair and authorization repair are separate loops.
Generic debugging matrix
Scenario exists but /run returns no useful output:
- check the output interface
- check
scenario-service:ReturnDatamapping if used - verify
responsive: truebehavior and response shape - verify the output keys the downstream reader expects
400 or 422 from /run:
- compare submitted
datakeys and types against/api/v2/scenarios/{scenarioId}/interface - verify required inputs and defaults
Empty business data:
- check the retrieval query or filter
- check the target account/workspace identity
- check the provider API endpoint and permissions
Authentication or authorization error:
- stop path guessing and use the Authorization Repair Playbook
Wrong account/workspace data:
- treat this as a connection identity mismatch and use the Authorization Repair Playbook
Scope or permission error:
- use the Authorization Repair Playbook; do not treat connection liveness as scope proof
Existing shell points to an old connection:
- use the Authorization Repair Playbook and preserve the new-connection/new-shell rule
Definition of Done
Do not call retrieval complete just because the scenario exists. Done means:
- target provider confirmed
- target account/workspace/mailbox/tenant confirmed
- retrieval target and operation confirmed
- connection identity and scope verified
- connection liveness verified by Make's connection test API
- Credential Request completed if needed
- resulting connection ID extracted and recorded
- real Make scenario exists with
scenario-service:StartSubscenario, the app-specific API-call module, andscenario-service:ReturnData - scenario-level input/output interface patched and verified
- blueprint shows the correct app module and connection ID
/runwithresponsive: truereturns real output data- the first real run proves the intended path, method, query, and body through the bound shell
- retrieval returns records from the intended account/workspace
- downstream normalization/reporting works if requested
- schedule points to the final validated configuration if scheduling was requested
Sanitization and Sharing
Use this checklist before installing a skill globally or contributing it to a shared repository.
Remove tenant-specific data
Do not publish any of the following:
- real user names
- personal labels in
nameOverride - team IDs
- organization IDs
- user IDs such as provider IDs
- workspace-specific hosts such as a private Make workspace hostname
- absolute local file paths from a personal machine
Replace them with placeholders such as:
TEAM_IDORG_IDREQUEST_IDSCENARIO_IDBASE_URLCONNECTION_ID
Rewrite example labels
Bad:
personal-outlook-api-shellgmail-shell-debugtenant-scoped API shell execution
Better:
provider-api-shellapi-shell-debuggeneric API shell execution
Rewrite evidence language
Bad:
verified liveworked in tenant Xconfirmed from private reverse-engineering
Better:
example observed during developmentpractical fallbackcurrent workflow recommendationworkspace-specific behavior can vary
Public documentation style
Prefer wording like this:
Use current Make metadata as the source of truth.Examples are illustrative and should be validated in the active workspace.If the preferred endpoint is unavailable, try the compatibility fallback.
Avoid wording like this:
This exact request shape always works.These values are confirmed globally.This private workspace proves the rule for every tenant.
Final pre-publish scan
Search for and remove or replace:
- personal names
- email addresses
- numeric IDs copied from private environments
- workspace-specific hosts; use
https://us1.make.comas the public example base URL providerMakeUserId- absolute home-directory paths copied from a personal machine
- phrases such as
verified live,working example, orfor debuggingif they imply private validation or tenant-specific state
Repository fit
For public repositories:
- keep
SKILL.mdconcise - move detailed operational guidance into sibling markdown files
- keep templates generic
- use examples that teach the shape of the workflow without exposing private data