Reference / Uploads
create_file_upload
Start uploading a CSV or Excel file. Returns a one-time URL and the exact curl command, so the file never passes through the conversation.
Starts an upload of a CSV or Excel (.xlsx) file into a workspace, for a
dashboard built from a spreadsheet rather than from a warehouse. The call does
not take the file's contents. It returns a one-time upload URL and the exact
curl command to run in the agent's own shell, so the file goes from the user's
disk straight to Dashies and never passes through the conversation.
A client with no shell cannot run the command, so it cannot upload.
What an upload can be used for
Uploading a file works as described here, and so does AUTHORING against one: once
an upload is ready, pass the workspace's uploaded-file source as connection
and the upload id as upload to
introspect_schema,
explore_data and
validate_cube_sql. PUBLISHING a
dashboard from an uploaded file also works: name that same connection in the
spec's source.connection and the upload id in source.upload, and the dashboard
reads that one file and refreshes from it on its schedule.
get_file_upload reports where an upload
stands, and returns the catalog once the file is converted. An upload can still
stop once the file arrives, with a failure saying conversion is not available for
that workspace, which is final for that upload: uploading the same file again will
not change it.
Behaviour hints for clients: not read-only, not idempotent, not open-world.
Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
filename | string | no | The file's name, for example sales.csv. At most 255 characters, with no control characters. Required for an upload and refused-as-missing without it; omit it only when passing reconvert_of, which takes the original's name. It is not merely a label: a CSV's table is named from it, so converting the same file under a different name would rename the tables. |
bytes | integer | no | The file's exact size in bytes. Worth passing: a file over the limit is refused before anything is sent, and an upload whose body is a different size is refused before it is stored. |
reconvert_of | string | no | Convert an upload you already made again, with overrides, instead of uploading a file. Nothing is uploaded, the same stored bytes are read again, and the original upload is untouched. The answer has no url and no curl: it returns a new upload id to poll with get_file_upload. |
overrides | object | no | How to read the file differently, for a re-convert only - passed on its own it is refused, because a first upload has no census to check an override against. Keys: types (an object keyed by the column's header, valued with a type spelled exactly as the catalog spells one - BIGINT, DECIMAL(12,2), VARCHAR, DATE), dateformat, timestampformat, nullstr, thousands, delim, quote, header, skip, names, sheets, range, encoding. Each is validated against the census this conversion takes, never against what the first conversion concluded: your overrides change how the file is read, so a fresh reading can legitimately disagree with the catalog you chose the override from. An override the data does not support fails that conversion and names your value rather than silently dropping data. |
workspace | string | no | Workspace slug, only for a legacy connection authorized for no workspace. |
filename reads no in the Required column, and the reason is worth a sentence rather than an
asterisk: this tool takes two shapes. An upload needs a filename and is refused by name without one;
a re-convert takes the original's name from the ledger and must not re-declare it. A schema-level
required list cannot say "required on one of two shapes", so the list is empty and the tool does
the refusing.
Returns
Upload 0b6f0c1e-4c1d-4f55-9a57-2a1f0f7d9e21 created for "sales.csv" in the "acme-co" workspace.
Run this command exactly as printed, replacing <path> with the path to the file. Its ticket works once and expires at 2026-09-17T18:15:00Z:
curl --fail-with-body -sS -T "<path>" -H "authorization: Bearer <ticket>" -H "content-type: application/octet-stream" "https://dashies.ai/api/uploads/0b6f0c1e-4c1d-4f55-9a57-2a1f0f7d9e21"
The file goes straight to Dashies: do not read it into this conversation. The largest file accepted is 95000000 bytes. When the command succeeds, call get_file_upload with upload "0b6f0c1e-4c1d-4f55-9a57-2a1f0f7d9e21" and wait_seconds 45, and call it again until the status is ready or failed.
BEGIN_JSON
{"mode":"upload","upload":"0b6f0c1e-...","connection":"5d2c...","url":"https://dashies.ai/api/uploads/0b6f0c1e-...","expires_at":"2026-09-17T18:15:00Z","max_bytes":95000000,"curl":"curl --fail-with-body ..."}
END_JSON
mode says which of two shapes the answer is. A re-convert answers mode: "reconvert"
and has no url and no curl, because nothing is uploaded:
Converting upload 11111111-... again as 0b6f0c1e-..., with the overrides you named.
- The same stored bytes are read again. Nothing is uploaded and the original upload is untouched.
- Status: converting. Call get_file_upload with 0b6f0c1e-... and wait_seconds until it is ready or failed.
BEGIN_JSON
{"mode":"reconvert","upload":"0b6f0c1e-...","connection":"5d2c...","reconvert_of":"11111111-...","status":"converting","max_bytes":95000000}
END_JSON
The <ticket> in a real response is a one-time secret. It is stored only as a
hash, it works once, and it expires at expires_at.
Running the command
The command PUTs the file to the URL. -T makes curl send the file with a
content-length, which the upload requires, and --fail-with-body makes a
refusal exit non-zero while still printing the sentence explaining it.
| Answer | Meaning |
|---|---|
200 | The file arrived. The body says whether its conversion started; follow it with get_file_upload. |
404 | No upload is waiting at this address for this ticket: it was already used, it expired, or it never existed. Every one of those gets the same answer. Create a new upload. |
411 | The request carried no content-length. Run the command as printed. |
413 | The file is over the limit, or it is not the size the upload was created for. |
400 | The file sent was empty. Check the path. |
503 | Dashies could not take the file just now. The body says whether to run the same command again or create a new upload. |
Limits
- The largest file accepted is 95,000,000 bytes (95 MB). Split larger data into several files.
- A ticket works once and expires; the response gives the exact time.
- A workspace can have only a few uploads in progress at once; the refusal names the number.
- Calls are rate-limited per user.
Errors
Refusals arrive as tool errors, each with a sentence naming what to do:
- a file over the limit, or a missing or invalid
filename, before anything is created; You hold a view-only role in this workspace, so you cannot upload files to it.when the caller has a viewer seat;A paid plan is required to upload a file.on a workspace with no paid plan or trial;- a sentence naming the data-connection limit when the workspace has no connection slot left for its uploaded files;
- a sentence naming how many uploads are already in progress.
Scope
Which workspace the file belongs to comes from how this MCP connection was
authorized. A workspace's uploads share one data connection, labelled
Uploaded files, which the first upload creates; it uses one of the workspace's
connection slots.