Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
Attachments
Upload one file at a time to a card or table-record attachment field. The upload goes through Pipefy's presigned URL flow (request URL, S3 PUT, then field update). 3 MCP tools, 2 CLI commands.
When to use
- The user says "attach this file to card X" or "upload to the documents field
on record Y".
- Pick the source by where the bytes actually are (see Trust model):
- The file is on a disk the MCP server can read →
file_path. - The file is reachable at an HTTPS URL →
file_url. - The server can't read the file (a local file on the hosted profile), or it's
larger than the 100 MiB in-tool cap → the presigned handshake (create_attachment_presigned_url; see "Uploading a file the server can't read" below), where the client does the upload.
- The agent generated bytes in a sandbox the server cannot read → write them
to a path the server can read and use file_path, or host them at a URL and use file_url.
Do not use this skill for:
- Reading or listing existing attachments. There is no list/download tool in
this skill scope. Cards and table records expose their attachments through the regular card/record fetch tools.
- Bulk uploads. One file per call; iterate at the agent layer.
Prerequisites
- A Pipefy
organization_id. Find it viaget_organizationorget_pipe. - A target
card_idortable_record_id. - The attachment field's slug (the human-readable id like
document_upload,
not the field's uuid). Find it on the card or table record fetch tools.
- Exactly one file source: a
file_paththe MCP server can read, or a
file_url the server can download.
Tools needed
| Tool (MCP) | CLI equivalent | Read-only |
|---|---|---|
upload_attachment_to_card | pipefy attachment upload --card <id> | No |
upload_attachment_to_table_record | pipefy attachment upload --record <id> | No |
create_attachment_presigned_url | pipefy attachment presign | No |
Trust model
The upload tools accept exactly one source. Which one depends on where the MCP server runs relative to the bytes:
- `file_path` is a path on the machine running the MCP server. In the local
distribution the server is a subprocess of the agent runtime with the same filesystem access as the user, so any path the user can read works. file_path is local profile only: on the hosted server (a shared HTTP service) it has no meaning and is rejected — the file is not on the server's disk.
- `file_url` is an HTTPS URL the server downloads (http only if the
deployment enables insecure URLs). It works on any profile and is the hosted-safe source. The download runs behind an SSRF guard (HTTPS and public hosts only unless the deployment opts into insecure URLs) and the same 100 MiB cap as file_path.
A common trap: an agent running against the hosted server (or a local server whose client generated the file in a separate sandbox) has no shared disk with the server, so a temp file_path the agent writes is unreadable by the server. Use file_url when the bytes are reachable at a URL; otherwise place the file where the server can read it and use file_path.
The CLI always runs locally as the user, so it exposes --file (a local path) only.
Steps
Upload a local file to a card
file_name is inferred from the source basename when omitted, so callers usually only pass the four IDs and the source.
MCP:
upload_attachment_to_card organization_id=42 card_id=1234 field_id=document_upload file_path=~/report.pdfCLI:
pipefy attachment upload --org 42 --card 1234 --field document_upload --file ~/report.pdfUpload from a URL (hosted-safe)
upload_attachment_to_card organization_id=42 card_id=1234 field_id=document_upload file_url=https://example.com/report.pdfWhen the URL has no filename in its path (e.g. .../download?id=1), pass file_name explicitly so Pipefy stores it under a real name.
Upload a local file to a table record
MCP:
upload_attachment_to_table_record organization_id=42 table_record_id=tr-555 field_id=document_upload file_path=/tmp/export.csvCLI:
pipefy attachment upload --org 42 --record tr-555 --field document_upload --file /tmp/export.csvOverriding the file name
To store the attachment under a different name than the source basename, pass file_name explicitly. It wins over the inferred basename.
upload_attachment_to_card ... file_url=https://example.com/abc123.pdf file_name=Invoice-2026.pdfUploading a file the server can't read: the presigned handshake
file_path and file_url both need the server to reach the bytes — a disk it can read, or a URL it can fetch. When neither holds (a local file on the hosted server, or bytes larger than the 100 MiB in-tool cap), use create_attachment_presigned_url so the client does the upload while the server only mints the target:
- Call
create_attachment_presigned_urlwithorganization_id+file_name
(optional content_type / content_length). It returns upload_url (the S3 PUT url), storage_path (the object key), and expires_in_seconds.
- From an environment that can reach the upload host, HTTP
PUTthe file bytes
to upload_url within expires_in_seconds (send Content-Type / Content-Length matching what you passed, if any).
- Set the attachment field to
[storage_path]viaupdate_card_field/
set_table_record_field_value. Store storage_path, never a URL — the signed download URL is minted on read.
Because the bytes are handled by the client (step 2), not passed through the tool call, this keeps the model's context clean.
Which clients can do step 2:
- Code-execution clients (Claude Code, Claude Desktop): read the file and
PUT it from your own code — the bytes never enter the conversation.
- Subagent tip: if your client can spawn subagents, run steps 1–2 inside one
so even transient handling stays out of the main conversation's context.
- claude.ai caveat: the claude.ai code-execution sandbox cannot reach the S3
upload host by default (network egress allow-list). An org owner must allow-list the upload host for the PUT to succeed there; until then, this path is blocked on claude.ai and a file_url the server can fetch is the alternative.
The CLI equivalent is pipefy attachment presign (prints upload_url / storage_path / expires_in_seconds); you run the PUT and the field update.
Success criteria
`upload_attachment_to_card` / `upload_attachment_to_table_record` (one-shot): the payload has success: true, a download_url (the signed URL Pipefy returns), the inferred or explicit content_type, and the file_size in bytes, and the attachment field on the card or record now lists the file. One call is the whole job.
`create_attachment_presigned_url` (handshake) is not done at mint: success: true here only means a target was minted — no field was touched and nothing is attached yet. Done means you completed all three steps: after the client PUTs the bytes to upload_url and you set the field to [storage_path], the attachment field lists the file. Do not treat the mint response (or its premature signed URL) as a finished upload.
Failure modes
The one-shot tools carry a step field on failure. The handshake tool can fail at mint (step=validation / presigned_url, below), but its client PUT and field update happen outside the tool — a non-2xx PUT or a failed update_card_field / set_table_record_field_value surfaces there, not as a step on the mint response. The step values:
- `step=validation`. No source, both sources, a
file_urlwith no
inferable file_name, or file_path passed on the hosted server. Recovery: pass exactly one source; on the hosted server use file_url; supply file_name when a URL has no basename.
- `step=file_read`.
file_pathdoes not exist, points to a directory, is
unreadable, has an unknown ~user prefix, or is larger than 100 MiB. The path is read on the machine running the server (which may not be the agent's own environment). Recovery: verify the path exists as a regular file the server's user can read and is under the cap, or switch to file_url.
- `step=download`. The
file_urlfailed its SSRF guard (non-HTTPS,
private/internal host), exceeded 100 MiB, timed out, returned an HTTP error, or redirected too many times. Recovery: confirm the URL is a public HTTPS address serving the file directly and under the cap.
- `step=presigned_url`. Organization id rejected, field id not an
attachment, or Pipefy refused the request. Recovery: confirm organization_id with get_organization and that the field is actually an attachment field on the target card or record.
- `step=s3_upload`. The presigned URL expired before PUT, or
content/headers did not match what was signed. Recovery: retry the tool to obtain a fresh presigned URL.
- `step=field_update`. The field rejected the new attachment list (wrong
type, missing permission). Recovery: confirm the field accepts attachments and that the caller has write access to the card or record.
If an argument has the wrong type (a coercion failure before the body runs), the payload uses the standard Pipefy invalid-arguments envelope instead: {"success": false, "error": {"code": "INVALID_ARGUMENTS", "message": "...", "details": {"errors": [...]}}}. Recovery: read error.details.errors[*].path to see which argument failed, then retry.
See also
skills/pipes-and-cards/pipefy-pipes-and-cards/SKILL.md: finding card ids and attachment field slugs.skills/database-tables/pipefy-database-tables/SKILL.md: finding table record ids and field slugs.

