Pipefy API Fallback (Tier 3 — Last Resort)
This skill activates only after Tiers 1 and 2 have failed. Call the Pipefy GraphQL API directly, bypassing the MCP server.
3-tier resolution strategy (always follow in order)
| Tier | Method | When |
|---|---|---|
| 1 | Dedicated MCP tool (create_card, get_phase_cards, get_phase_allowed_move_targets, update_pipe, etc.) |
Always try first. For card/phase seeding and inventory, see Seed pipe across phases. |
| 2 | Introspection + execute_graphql |
When no dedicated tool exists or a tool fails unexpectedly. See skills/introspection/pipefy-introspection/SKILL.md. |
| 3 | Direct HTTP via curl / httpx (this skill) | When the MCP server itself is unavailable, or execute_graphql fails with an infrastructure error. |
Do not jump to Tier 3 after a single tool failure. Follow the tiers in order.
Authentication
Two options (use whichever is available in the environment). Prefer the Service Account when both exist.
Option A — OAuth2 Client Credentials (preferred):
TOKEN=$(curl -s -X POST https://app.pipefy.com/oauth/token \
-H "Content-Type: application/json" \
-d "{\"grant_type\":\"client_credentials\",\"client_id\":\"$PIPEFY_SERVICE_ACCOUNT_CLIENT_ID\",\"client_secret\":\"$PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET\"}" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
Option B — Personal Access Token (PAT):
TOKEN="$PIPEFY_PAT" # or $PIPEFY_TOKEN
PATs are deprecated for new integrations but may still exist in the environment.
Token rules
- The
Bearerprefix is mandatory — Pipefy rejects requests without it. - Never expose
PIPEFY_SERVICE_ACCOUNT_CLIENT_ID,PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET,PIPEFY_PAT, orPIPEFY_TOKENin responses to the user or in logs. - Service Account tokens are reused while valid; only re-fetch on expiry (401).
Endpoints
| Purpose | URL |
|---|---|
| All queries and mutations | https://api.pipefy.com/graphql |
| Schema introspection only | https://app.pipefy.com/graphql |
| OAuth2 token | https://app.pipefy.com/oauth/token |
Real operations go to api.pipefy.com; introspection goes to app.pipefy.com. The MCP server and CLI route between the two automatically (both derived from PIPEFY_BASE_URL); raw-API users must distinguish them by hand.
Execute a GraphQL query
curl -s -X POST https://api.pipefy.com/graphql \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "{ me { id name } }"}' | jq .
Execute a GraphQL mutation
curl -s -X POST https://api.pipefy.com/graphql \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation CreateCard($input: CreateCardInput!) { createCard(input: $input) { card { id } } }",
"variables": {
"input": {
"pipe_id": 67890,
"title": "Fallback Card"
}
}
}' | jq .
When to use direct API vs MCP tools
| Situation | Use |
|---|---|
| MCP server running normally | MCP tools (Tier 1 or 2) |
| MCP server down / unreachable | Direct API (Tier 3) |
execute_graphql returns 500 error |
Direct API (Tier 3) |
| Testing a new mutation before MCP tool exists | execute_graphql (Tier 2) — not direct API |
Introspection via raw API
When you need to discover schema without MCP tools, call app.pipefy.com/graphql:
# All queries and mutations
curl -s -X POST https://app.pipefy.com/graphql \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"query":"{ __schema { queryType { fields { name description } } mutationType { fields { name description } } } }"}'
# Type details
curl -s -X POST https://app.pipefy.com/graphql \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"query":"{ __type(name: \"CreateCardInput\") { inputFields { name description type { name kind ofType { name kind } } } } }"}'
Error code → cause
GraphQL always returns HTTP 200, even on errors. Check the errors array, not the HTTP status code.
| Code | Likely cause | Recovery |
|---|---|---|
| UNAUTHORIZED | Token missing, expired, or Bearer omitted |
Re-fetch token (Option A) or fix the header. |
| PERMISSION_DENIED | Service Account not a member of this pipe/table | Add SA via invite_members or ask user. |
| resource_not_found | ID does not exist or SA cannot see it | Verify ID; check pipe/table membership. |
| invalid_input | Wrong argument name or type | Run introspect_type (Tier 2) to recheck the input shape. |
| INTERNAL_SERVER_ERROR | API bug or unsupported payload | Do NOT retry the same payload. Try an alternative mutation or workaround. |
| missingRequiredInputObjectAttribute | A required field is missing from the input | Compare the payload against __type(name: …InputType). |
Ambiguous write failure (success: false, empty or unclear message) |
Mutation may already have applied (side effects on the server) | Re-read before any retry — see Ambiguous write failure. |
Ambiguous write failure (re-read before retry)
Write tools and execute_graphql can report failure even when the mutation already applied. Blind retry duplicates customer data (e.g. many cards created despite success: false).
- Do not immediately re-run the same create/mutation.
- Re-read first:
get_cards/get_phase_cards_count/ returned ids /cards_counton the pipe or phase you targeted. Prefer comparing to a count or id set you recorded before the write when available. - Retry only if the re-read clearly shows the write did not land.
- If the re-read is inconclusive (no pre-write baseline, pagination truncates results, or counts cannot prove absence), stop — report the ambiguous failure and the re-read evidence to the user. Do not guess-retry.
- Hard stop — no client-side idempotency:
CreateCardInputhas noidempotency_key(onlyclientMutationId, which is not create-idempotency). Do not invent client-side keys or assume retries are safe. Re-read is the only safe path until the API adds real idempotency.
Known workarounds
Cross-pipe create_card via automation
- Do NOT use
createAutomationwithaction: create_card+field_map— returnsINTERNAL_SERVER_ERROR(confirmed API bug). - Instead, use
createCardwith thethroughConnectorsparameter. Prerequisite: a connector field withcanCreateNewConnected: truemust exist.
Pipe listing shorter than pipesCount
pipesCountis the org-wide total;organization { pipes { ... } }andsearch_pipesreturn only the pipes the calling identity is a member of. A shorter listing, or an empty one, is expected behavior and not an error. Role does not widen it: asuper_admingets the same membership-scoped result. Detail and workarounds:docs/mcp/tools/organization.md.organization { pipes(include_publics: true) }widens the listing with pipes that are public inside the org. It still normally returns fewer thanpipesCount.- Service accounts hit this most often: an SA starts as a member of nothing.
- Pipes created via API are automatically visible to the SA.
- Pipes created in the UI require the SA to be added as an admin.
- Workaround: get pipe IDs from the user once and query
pipe(id: "...")directly.
invite_members accepts unknown emails silently
- Pipefy mints a new
user_idfor typo addresses without rejecting the invite. Sanity-check email syntax before calling.
External resources (when raw API also fails)
- Pipefy developer portal:
- API reference:
- Community + changelog:
- Status page: https://status.pipefy.com
Search for the exact error message + "Pipefy GraphQL", or the mutation name + "example Pipefy API".
Escalation to the user (absolute last resort)
Only after all 3 tiers and external resources have failed:
- State exactly what was tried (MCP tool, introspection, raw API).
- Show the verbatim error response.
- Propose a concrete workaround (e.g., "create via the Pipefy UI, then continue via API with the resulting ID").
- Stop — do not loop.
Success criteria
- The operation completes without an HTTP 4xx/5xx error.
- The response contains a
datakey anderrorsis null or absent.
Failure modes
- 401 Unauthorized — token expired or
Bearerprefix omitted. Re-fetch the OAuth token (Option A). - 400 Bad Request — GraphQL syntax error. Validate the query string and escape quotes properly when embedding via shell.
- 500 / service unavailable — Pipefy API outage. Check status.pipefy.com and retry later. Do not loop.
INTERNAL_SERVER_ERRORinerrorsarray — do NOT retry the same payload; pick a different mutation path.- Ambiguous write failure — reported error with empty/unclear message after a create or other write: re-read counts/ids before retrying; never blind-retry creates (Ambiguous write failure).
Security notes
- Never log or print tokens in plain text.
- Prefer environment variables over inline credentials.
- Use
PIPEFY_TOKEN/PIPEFY_PATonly for personal/development use; use service-account credentials (PIPEFY_SERVICE_ACCOUNT_CLIENT_ID+PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET) for service accounts.
See also
- skills/introspection/pipefy-introspection/SKILL.md — Tier 2: use
execute_graphqland introspection tools through the MCP server before falling back to direct HTTP.