file_id, size_bytes, sha256 and a row count. The next step binds that id. The file’s contents never enter the flow unless a step is asked for them explicitly.
Files arrive from three directions: a step writes one, a step fetches one out of a bucket you own, or a human sends one back — the attachments on a reply that resumes a waiting flow are stored as files automatically, and are addressable by the same file_id every step on this page takes.
How a file travels
By reference, never by value. This is the same promise record transforms make about rows, one layer out: a step is given an identifier, reads or writes the bytes as it runs, and hands back an identifier. A flow carries an id, a size and a checksum — not the file. That is what makes a file of any size workable. A file holding two records and a file holding twenty million cost the flow exactly the same, because what moves between steps is the same handful of values either way. It is also why the steps chain the way they do: one step’sfile_id binds into the next step’s input like any other step output.
There is no way to type a file’s contents into a step. That direction is closed deliberately: a step that accepted either a reference or a block of bytes would behave differently depending on which one it was handed, and the size a flow can move would silently become the size a single value can be. The reference is the only way in.Reading contents out is a different question, and it has one deliberate answer:
getdialed__files__read_text, a step whose whole job is to say “yes, this file, as text, under a stated cap”. It is a step you ask for rather than a shape any step might return, which keeps the size question in one place.Choosing a format
create_file writes one of three shapes, and format is required — there is no default to be surprised by.
A CSV’s header comes from the columns the record set declares, not from the rows that happen to be in it. A column that is empty in every record still gets its column, so the file keeps the shape the set promised.
Compression
Setcompress to gzip to store the file compressed; leave it empty for a plain file. A compressed file’s name ends .gz, which is how Snowflake recognises it automatically when it loads the file — there is nothing else to configure at that end.
Compression happens as the file is written rather than afterwards, so it costs nothing in memory however large the set is.
content_type describes the format and does not change when a file is compressed — an NDJSON file is still NDJSON. The name ending .gz is what says it is compressed, and compressed on the step’s output says so directly. size_bytes is the size as stored, so it is the compressed size for a compressed file.Labelling a file
tags is a set of names and values you choose, stored with the file: {"team": "ops"}, {"source": "nightly-sync"}, whatever you will want to recognise later. Up to 32 labels, each name up to 64 characters and each value up to 256.
Nothing searches tags yet, and they ship anyway. There is no tag filter, no file listing and no browser to filter with — a tag today is read back by
getdialed__files__get_file_metadata, and a flow can branch on it. They ship now because a file created before tagging existed would be permanently untagged: a search added later can only find what was already labelled, and no retrofit can invent a label for a file whose purpose nobody recorded at the time.filename is a label only. It does not decide where the file is stored and it is not part of the file’s address, so a name with slashes in it makes no folders and a name that repeats an earlier one overwrites nothing. Every file has its own reference, and that reference is what every later step uses.
How long a file is kept
Files are kept until you delete them. There is no default expiry and nothing quietly reclaims a file you stopped looking at. For a file you only need temporarily,expires_in_days takes one of 1, 7, 30 or 90, and the storage lifecycle removes the file when its time is up. The set is closed rather than free-form because those are the lengths the storage layer can actually enforce — offering an arbitrary number would be promising a schedule nothing implements.
Leaving expires_in_days empty is the usual choice. Delete a file with getdialed__files__delete_file when you are done with it.
Inbound reply attachments are the one exception: they are kept for 30 days. A file that arrives on a reply was not created by a step, so nothing set an expiry for it — and it arrived on a path anybody holding the reply address can reach, where unbounded storage is not a thing to offer. The horizon is fixed rather than configurable. A flow that needs a copy for longer should write one out with
create_file while the reference is still live.The steps
Seven steps ship. Four are first-party and need no credential; three reach a bucket you own and select an AWS credential.GET /catalog/actions/{action_id} publishes the complete schema for any of them.
Create a file
getdialed__files__create_file takes a sealed record set and writes it out.
It reports:
The step also records which step of which run produced the file. You do not supply that.
Read a file as text
getdialed__files__read_text takes a file_id and hands back the file’s contents. It is platform-neutral: it does not care where the bytes came from, so the same step serves a file an earlier step created, a file fetched out of your bucket, and an attachment on a reply that resumed a waiting flow.
Three limits, and each one is a refusal rather than a partial answer:
For a CSV, you usually do not want this step at all.
getdialed__records__parse_csv accepts a file_id directly and reads the file inside the step, so the contents never travel through the flow and its own 1 MB ceiling applies instead of this one. Reach for read_text when the flow needs the text — to branch on it, to put it in a message, to send it somewhere — rather than the records.
Read a file’s details
getdialed__files__get_file_metadata takes a file_id and answers with filename, size_bytes, content_type, sha256, row_count, created_at, tags, compressed and status. The contents are not read, so this costs the same for a file of any size.
Use it to decide what a flow should do next: skip an upload when a file turned out empty (size_bytes is 0), route on a label you attached when the file was created, or confirm a file is still there before a later step uses it.
status is available or deleted. row_count is empty for a file fetched out of a bucket — the platform never read that file’s contents, so it has no count to report.
Delete a file
getdialed__files__delete_file removes the file’s contents. The file’s record is kept and marked deleted, so you can still see later that the file existed and when it went away, rather than finding nothing at all — which is also why get_file_metadata still answers for a deleted file instead of failing.
Running it twice is safe: deleted says whether this run removed the file and already_deleted says it was gone before the step ran. Both are successes. A reference that names no file of yours is a failure, because reporting a successful deletion for a file that never existed would make the step’s own answer meaningless.
Deleting a file does not affect anything a previous step produced from it.
Put an object in a bucket
aws__s3__put streams a stored file into a bucket you own.
It reports
written and size_bytes. Nothing about the destination comes back — you supplied it, and repeating it would store a copy of it in the run history.
Object keys are checked before anything is sent. A key may not begin with a slash, may not contain a . or .. step, may not contain control characters, and may be at most 1024 bytes including any prefix. A refusal names the rule rather than repeating the path.
Get an object out of a bucket
aws__s3__get takes key (and optionally bucket) and returns file_id, size_bytes, content_type and sha256.
It hands back a file reference, never the object’s contents. The object is streamed into the platform’s own file storage and the next step receives an identifier for it. There is no option to get the contents back as text instead, for the reason in the note above: a step that sometimes returned a reference and sometimes returned data would behave differently depending on the object it was pointed at. The last part of the key becomes the stored file’s name.
A missing object and an unreachable one fail identically. S3 itself refuses to tell an unauthorized caller which of the two it is — that is how it stops an outsider probing a bucket for what it contains — and neither does this step. See why the IAM policy grants
s3:ListBucket for the other half of that story.Delete an object
aws__s3__delete takes key (and optionally bucket) and reports deleted.
S3 reports success for a path that was never there, and so does this step. It tells you the delete was carried out, not that something was there to delete. There is no way to learn the difference, and that is deliberate on S3’s part as well as ours — a step that reported the difference would let a flow probe a bucket for what it contains. Running it twice is safe and reports the same thing both times.
Where a file lives
Two storage locations are in play, and they are owned by different people.
The split matters for two reasons. A
file_id keeps working even if you revoke the AWS credential tomorrow, because nothing about the platform file store depends on it. And an object in your bucket is yours — GetDialed’s own permissions do not reach it, and a step only touches it with the access key you supplied for that purpose.
What is not here yet
Named plainly, because finding a gap by hitting it is worse than reading about it:- No file listing and no file browser. You can read a file you hold the reference to. There is no way to ask “what files do I have”, and nothing filters on tags yet.
- No download URL. There is no API endpoint that hands you a link to a stored file. To get a file out today,
aws__s3__putit into a bucket you own. - No file-arrival trigger. A file landing somewhere does not start a flow. Flows still start from a trigger — manual, scheduled, webhook or event.
- Only CSV comes back into records.
getdialed__records__parse_csvreads a stored CSV into a record set; a JSON or NDJSON file has no equivalent yet, and neither does a file larger than that step’s 1 MB ceiling. - No renaming, converting or editing a stored file.
filenameis fixed when the file is written. - Amazon S3 only, plus S3-compatible services. Other object stores are reached only if they speak the S3 API — see the S3-compatible endpoint setting.
- One AWS credential shape: an access-key pair. Cross-account IAM roles are not supported.
Next steps
Store a report in S3
The end-to-end walkthrough: run a report, write it as NDJSON, land it where Snowpipe picks it up.
Credentials
Create the AWS credential the S3 steps select, and the IAM policy behind it.
Record transforms
Reshape a set of records before you write it out.
Integration catalog
The full published schema for every step on this page.