Bucket

Lists, reads, stores, checks and deletes objects in a bucket you own, and signs links to them. Seven operations over anything that speaks S3 — Cloudflare R2, AWS S3, MinIO, Backblaze B2 — and the one most people come for is a temporary download link for a private file, the thing you hand a buyer without the file ever passing through HostWebhook.

Setup

One credential, of type AWS S3 / Compatible: the endpoint, the bucket name, the region, and an access key pair. Add it under Settings → Credentials, then pick it in the node.

The node is called Bucket and not R2 because the provider is an endpoint, not a kind of node. The same node talks to R2, to S3, to a MinIO you run yourself or to Backblaze without a line of difference — what changes is the URL in the credential. R2 signs with region auto; S3 wants the bucket's real region.

ℹThe endpoint is re-validated on every run, not just when the credential is saved. A hostname that resolved to the public internet on Tuesday can resolve to a private address on Thursday, and the stored credential would never notice.

Operations

The Operation dropdown decides what the node does and which fields appear under it. These are the exact values the API validates against:

ValueIn the dropdownS3 callWhat it does
presignDownloadSign download linkGET (presigned)Returns a temporary URL for a private object. Does not move the file.
putObjectUpload objectPUTStores an object. Overwrites the key if it already exists.
headObjectCheck object existsHEADSays whether the object is there, and how big, without downloading it.
deleteObjectDelete objectDELETEDeletes an object. There is no trash.
listObjectsList objectsGET ?list-type=2Lists the files — and, grouped, the folders — under a prefix. The only iterable operation: the node below runs once per file.
getObjectRead objectGETPuts the contents of an object in the payload, as text, parsed JSON or Base64. Up to 5 MB.
presignUploadSign upload linkPUT (presigned)Returns a temporary URL someone else can upload a file to, without the credential.

In the dropdown they are grouped by what they return: the two Sign operations under Links, the five that touch the bucket under Objects. There is no multipart upload — it needs resumable state and per-part retries — so an upload through the node stops at 25 MB and a read at 5 MB. For anything bigger, sign a link and let the file travel between the person and the bucket without passing through the flow.


Which one you want

Sign download link

Use it when someone outside your system has to read one private object, once, soon. A buyer who just paid; a customer who asked support for their invoice; a partner who needs today's export. The object stays private and the bucket stays closed — what you hand out is a URL that carries its own permission and stops working on a deadline.

It never touches the network — neither of the two Sign operations does: they sign and return. That makes it fast, and it means a link for an object that does not exist is produced just as happily as one for an object that does — the failure shows up when the link is clicked. If that matters, put a Check object exists in front of it.

downloadAs is worth the extra field. Left empty, the browser decides what to do with the file and usually opens the PDF in a tab. Filled in, the browser downloads it under that name. It is the difference between “here is your booklet” and “a PDF has appeared in a tab”, and the name travels inside the signature, so nobody can rewrite it on the way. Accents and other non-English letters are kept: the name is sent the standard way for them, so Safari and command-line tools save it right too.

Sign upload link

The mirror image: someone outside your system has to put a file in your bucket — a customer sending a signed contract, a supplier dropping this week's catalogue — and you don't want to give them the credential. The node returns a URL and method: "PUT"; whoever holds it uploads with a plain HTTP PUT to that one key, until it expires. The file goes straight to the bucket, so the node's 25 MB upload limit doesn't apply.

contentType is what the object will be stored as, and it is not enforced: the bucket keeps whatever arrives. Making it part of the signature would force the uploader to send that exact header or get a 403 they cannot debug.

Upload object

Use it to keep the thing you would otherwise have to reconstruct later: the raw event as it arrived, a receipt, a rendered report, a snapshot of a payload before a transform mangled it. The contents field takes templates, so an object is written as JSON and a string is written as it stands.

It overwrites. There is no “fail if it exists” flag, because S3 does not have one — a PUT to a key that is taken replaces it, quietly and successfully. If a key can collide, make the key unique (an id, a timestamp) rather than hoping.

Check object exists

Use it as a gate. The typical shape is a flow that must not promise something the bucket cannot deliver: check that the file for this product is really there before signing a link and emailing it, or before charging for it.

failIfMissing decides who handles the absence. With Yes a missing object stops the flow right there, which is what you want when there is nothing sensible to do without the file. With No the step succeeds and hands exists: false downstream, so a Filter or Conditional can branch — alert the team, fall back to a default file, carry on.

Read object

Use it when the next step needs the contents, not a link to them: a JSON settings file, a CSV to parse, an email template, the receipt an earlier run stored. Read as decides the shape. Text puts the UTF-8 in content; JSON parses it into content, and a body that does not parse fails the step; Base64 puts the bytes in contentBase64. A PDF or an image needs Base64 — read as text it arrives corrupted — and Base64 weighs a third more than the file.

Reads stop at 5 MB, and past that the step fails, with the real size and a pointer to Sign download link. It never hands down half a file: cut contents still parse, still get emailed, and nobody notices. Over 256 KB the contents flow on whole to the next node, but the run history stores them trimmed. failIfMissing works as in Check object exists above.

List objects

Use it to look inside the bucket: what arrived in uploads/ today, which files a customer has, what is left in tmp/. Prefix narrows it — empty lists the whole bucket. Group into folders (the default) returns the files at that level plus the names of the folders below it, the way a person browses; No returns every key under the prefix, however deep.

It is the only iterable operation: the node below runs once per file, and each run gets one entry — its key, size, lastModified and etag. Folders travel alongside the list and are not walked. A prefix with nothing under it gives count: 0 and the node below runs zero times, with no error — correct for an empty list, and easy to mistake for a flow that did nothing.

Nightly cleanup
Scheduled Workflow  every night at 03:00
  → Bucket "What is in tmp"    operation: listObjects
                                 prefix: tmp/
                                 groupIntoFolders: No
  → Bucket "Delete it"         operation: deleteObject      ← runs once per file
                                 key: {{payload.key}}

Maximum objects is 1,000 by default and goes up to 5,000; the provider pages at 1,000 and the node follows the pages for you. When there are more, the step still succeeds — truncated: true says so, and nextToken is where the next run carries on: put {{payload.nextToken}}, or the token itself, in Continue after. When the last run stopped short, the node page says so, with a button that fills that field for you.

A fixed prefix lists once, however many items the event carries. A prefix templated from the payload — customers/{{payload.customerId}}/ — lists once per item, and two items that name the same prefix are listed once. The listings then reach the node below merged into one — the objects together, the folders without repeats — with a parts entry per listing giving its prefix, count, truncated and nextToken.

Delete object

Use it for cleanup you own: the temporary render you just emailed, the upload a customer asked you to erase, yesterday's export. It cannot be undone, and it does not tell you whether there was anything to delete — S3 answers the same way either way.


What downstream nodes receive

The node hands its own result to whatever comes next, replacing the payload it was given. After a Sign download link:

jsonOutput payload
{
  "_meta": { "iterable": false, "count": 1 },
  "statusCode": 200,
  "operation": "presignDownload",
  "key": "booklets/sustainable-changes-1.pdf",
  "url": "https://<account>.r2.cloudflarestorage.com/booklets/…?X-Amz-Signature=…",
  "expiresIn": 172800,
  "expiresAt": "2026-09-05T12:00:00.000Z",
  "downloadAs": "Sustainable Changes 1.pdf"
}

The other object operations return the same key plus what they learned. A Sign upload link adds url, method ("PUT"), expiresIn, expiresAt and contentType. An upload adds size, contentType and etag. A check adds exists, and when the object is there also size, contentType, etag and lastModified — an ISO 8601 date, the same format the listing uses. A read adds the same, plus encoding and the contents — in content, or contentBase64 for Base64. A delete adds deleted.

ℹBecause the payload is replaced, a node below the Bucket reads {{payload.url}} for the link. Anything else it needs from the original event it has to fetch by name, with a cross-node reference — {{$("Stripe sale").payload.…}} — in a node that supports them, such as Email, Notification or Code. The Bucket's own fields do not: they see the payload they were handed and nothing else, so chain the Bucket nodes so that each one gets what it needs from the one above.

A listing is the exception, and the only iterable payload the node produces. The node below runs once per entry of objects; everything else rides along, once:

jsonOutput payload — List objects
{
  "_meta": { "iterable": true, "iterateField": "objects", "count": 2 },
  "statusCode": 200,
  "operation": "listObjects",
  "prefix": "booklets/",
  "delimiter": "/",
  "objects": [
    { "key": "booklets/one.pdf", "size": 482113, "lastModified": "2026-09-01T10:00:00.000Z", "etag": "\"9b2c…\"" },
    { "key": "booklets/two.pdf", "size": 530774, "lastModified": "2026-09-03T08:12:00.000Z", "etag": "\"41fa…\"" }
  ],
  "folders": ["booklets/drafts/"],
  "count": 2,
  "folderCount": 1,
  "truncated": false,
  "nextToken": null
}

delimiter is "/" when the listing is grouped into folders and null when it is flat. The other six operations act on a single object, so the node below receives a result and not a list — a Loop underneath has nothing to walk.


Example — selling a file

The flow this node was built for. Someone buys a PDF; they get a link that works for two days; the file never leaves your bucket and never passes through HostWebhook.

Stripe → signed link → email
Webhook  "Stripe sale"        the payment provider posts checkout.session.completed
  → Filter                     payload.type equals "checkout.session.completed"
  → Bucket "Check the file"    operation: headObject
                                 key: booklets/{{payload.data.object.metadata.sku}}.pdf
                                 failIfMissing: Yes
                               → hands down { key, exists, size, contentType, … }
  → Bucket "Sign the link"     operation: presignDownload
                                 key: {{payload.key}}        ← from the check above
                                 expiresIn: 172800           (48 hours)
                                 downloadAs: Sustainable Changes 1.pdf
                               → hands down { key, url, expiresAt, … }
  → Email Action               to:   {{$("Stripe sale").payload.data.object.customer_details.email}}
                               html: <a href="{{payload.url}}">Download your booklet</a>
                                     This link expires on {{payload.expiresAt}}.

Three details carry the whole thing. The key is written once, in the check, and the signing node picks it up as {{payload.key}} — the check hands its own key down, so the SKU is not read out of the sale twice and the two nodes cannot drift apart. The email's recipient comes from the webhook node by name, because by that point the payload is the signed link and no longer the sale. And the deadline goes in the email: a buyer who opens it on Monday should be told, not left to find out.

The check in front is optional and cheap. Without it a missing file still produces a perfectly valid link, the email still goes out, and the buyer is the one who discovers the problem.


⚠Seven days is a hard ceiling, and it is not ours. A SigV4 signature carries its own lifetime and the algorithm will not go past 604800 seconds. The node's field stops there and the executor refuses anything above it — but that check is a courtesy, and it is worth knowing what it is protecting you from. Signing for eight days elsewhere produces a perfectly well-formed URL that the provider rejects when someone uses it, not when you create it. The error arrives days later, in a stranger's browser, with nothing to connect it back to the flow that made it.
⚠The link grants access to whoever holds it. For as long as it lasts, anyone with the URL can download the object — no account, no sign-in, no check on who they are. A forwarded email works. A screenshot pasted into a support ticket works. It is a secret with an expiry date, not a public address. Pick the shortest window the reader can live with: an hour is plenty for a click straight off a checkout page, a couple of days is generous for an email that may sit unread over a weekend, and a week is a decision you should have a reason for. An upload link is the same with writing: whoever holds it can put a file at that key — replacing what is there — until it expires.

AI toolkit mode

Once the credential is picked, the node asks for a Mode. Single operation is everything above: one operation, your templates, every run. AI toolkit turns the node into seven tools that an AI Node — or an MCP client, through one of your MCP servers — calls by name, with the key and the options decided per call.

ToolDoesArguments
list_objectsLists the files under a prefix, with key, size and date — where the model starts to find a keyprefix? · group_into_folders? · max_keys? · start_after?
read_objectReturns the contents of a file (up to 5 MB) as text, JSON or base64key · as? · fail_if_missing?
get_object_infoWhether a file exists, and its size, type and date, without downloading itkey · fail_if_missing?
get_download_linkA temporary link that downloads a private file, to hand to a personkey · expires_in_seconds? · download_as?
upload_objectStores text as a file, exactly as given (an empty text is an empty file). Overwrites the key if it exists, so it counts as destructivekey · content · content_type?
create_upload_linkA temporary link someone can PUT a file to, without the credentialkey · expires_in_seconds? · content_type?
delete_objectDeletes a file. Blocked by default.key

upload_object and delete_object are the destructive ones — each can wipe out a file that was already there. An AI Node with Block destructive tools turned on refuses them, and an MCP client runs them with run_destructive_operation.

What you control

Everything below lives in the Settings card of the node's AI Toolkit section — all optional: the defaults work.

  • Where the model can work. One folder (the default) keeps the model inside the folder you write — a key or a prefix outside it, or with a .. segment, is refused before the bucket is called, and listings start there. With no folder written it sees the whole bucket. The whole bucket opens it up even if a folder is written. Which bucket is never the model's choice: the credential fixes it.
  • Characters per call sent to the model. 20,000 by default (about 5,000 tokens), anything from 1,000 to 200,000. A longer file is cut there.
  • Deleting starts blocked. A deleted file can't be brought back. Unblock delete_object under Block these tool calls if the agent needs it; block any other tool — uploads, say — the same way.

What goes back to the model

The same response as the operation, with defaults that suit a model: a listing returns up to 200 files, flat, and says how to continue (start_after set to nextToken); links last one hour unless it asks for anything from 60 seconds to 7 days; and a file that isn't there answers exists: false instead of an error, so the model can decide. File contents stop at the characters limit, and every cut is listed in a notes field. Arguments are used as written: a {{payload.x}} the model writes is not resolved, so it can't read the output of other nodes in the flow.

Testing a tool

⚠Only a tool call picks the tool. In AI toolkit mode, only an AI Node, an MCP client or a voice agent chooses which tool the node runs. Run Test, a flow or a webhook run the operation saved on the node: an operation in their payload that names another tool is refused.
✦To try a tool, call it from an AI Node or from an MCP client. Its arguments look like this:
jsonA tool call
{ "operation": "list_objects", "prefix": "clients/" }
⚠A node in toolkit mode is not part of the pipeline. Events arriving through connections made before you switched are refused — the node only runs the tool it is asked for.

Gotchas

  • Keys have no leading slash and no .. segments. booklets/one.pdf, not /booklets/one.pdf — the node rejects both rather than writing to a key you did not mean. A prefix is refused with a leading slash too, but it can be empty (the whole bucket) and end in one.
  • Upload tops out at 25 MB. What does not fit needs multipart, which this node does not do — or a Sign upload link, which sends the file straight to the bucket.
  • Read tops out at 5 MB, and fails rather than cuts. Past it, sign a download link instead. A binary file read as text arrives corrupted: read it as Base64.
  • A listing stops at Maximum objects — 1,000 unless you raise it, 5,000 at most. The step still succeeds; truncated: true and nextToken say there is more and where to carry on. Some S3 implementations say “more” without a token — then nextToken is null and the way forward is a narrower prefix.
  • An empty listing runs the node below zero times. No error, no run — check count if an empty folder should raise an alarm.
  • An upload overwrites. A PUT to an existing key replaces it and reports success.
  • A missing object can answer 403, not 404. S3 and R2 both do it when the credential cannot list the bucket, so the check treats the two the same: not there. It also means a genuinely forbidden key reads as missing. Read object does not: there a 403 stops the step, because it can just as well mean the credential lost read access — and a flow that recreates or deletes “missing” files should not act on that.
  • deleted: true does not mean something was deleted. S3 answers 204 whether the key was there or not — deletion is idempotent by design, and there is no way to tell the two apart without a check first.
  • Signing does not verify. A link — download or upload — is signed for whatever key you give it, existing or not.
  • The credential must be aws_s3. Any other type is refused when the node is created, with the reason.