---
title: create_file_upload
description: 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.
updated: 2026-09-19
tools: [create_file_upload]
---

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.

:::note{title="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`](/reference/mcp-tools/introspect-schema),
[`explore_data`](/reference/mcp-tools/explore-data) and
[`validate_cube_sql`](/reference/mcp-tools/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`](/reference/mcp-tools/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`](/reference/mcp-tools/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

```text
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:

```text
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 `PUT`s 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.
