> ## Documentation Index
> Fetch the complete documentation index at: https://docs.canary.effectiveai.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Upload files

> Get a signed upload URL, send the file to storage, and confirm the upload.

Follow [connection setup](/guides/index#connect-once) first. You need a write-capable user key with
access to the destination. This example uploads a local PDF named `form.pdf`.

## Choose the destination

* `personal://Documents`: your personal Documents folder.
* `team://Documents`: your team's Documents folder.
* `@mc/Documents`: the Documents folder in an authorized scope. Also send `scope: "mc:<UUID or slug>"`.

Send `scope` only for `@mc` destinations. For `artifact://` destinations, use a storage root ID plus a
relative path, not a folder ID. To upload to a chat session's folder, send `sessionId` instead of
`targetUri`.

Where a file is stored and which record owns it are set separately. To attach a file to a record,
set `ownerType` and `ownerId` on each file. For filings, use the
[filing upload example](/guides/managed-filings/manage-documents#upload-a-document), then continue at
[Upload the file](#upload-the-file).

## Prepare the request

Start an upload to your team's Documents folder. Replace `12345` with the file's size in bytes.

```http theme={null}
POST /api/v2/files/uploads
Content-Type: application/json

{
  "targetUri": "team://Documents",
  "onConflict": "rename",
  "files": [{
    "path": "form.pdf",
    "size": 12345,
    "mimeType": "application/pdf",
    "type": "file"
  }]
}
```

The response contains `targetUri` and `files[]`, each with `path`, `signedUrl`, optional `headers`,
`expiresAt`, and `uploadToken`. With `onConflict: "rename"`, an existing file with the same name is
kept and the new file gets a different name. With `"fail"`, conflicting files are listed in
`conflicts[]` and get no upload URL.

## Upload the file

PUT the raw file bytes (not JSON or multipart) to `files[0].signedUrl` with exactly the headers in
`files[0].headers`:

```http theme={null}
PUT {signedUrl}

<raw bytes of form.pdf>
```

Don't send your Effective API key to the storage host. Once the PUT succeeds, confirm the upload with
`files[0].uploadToken`:

```http theme={null}
POST /api/v2/files/uploads/complete
Content-Type: application/json

{
  "confirmations": [{ "uploadToken": "<uploadToken from initialization>" }]
}
```

Check every `results[].success`, even when the status is `200`. A successful result contains `uri`
and `artifact`. Save `artifact.id` to refer to the file, and `artifact.name`, which can differ from
the name you asked for.

A failed result contains `error.code` and `error.message`. For example, `NOT_FOUND` means the
uploaded file wasn't found, and `EXPIRED` means the token expired. Don't send `scope` or the owner
fields again; the token already carries them.

## Limits and retries

* Each call accepts up to 100 files and a JSON body up to 1 MiB.
* Use the signed URL before its `expiresAt`, and confirm within one hour.
* Neither call is idempotent. After a lost response, check the destination before retrying;
  confirming twice can create a second version.
* Search indexing runs after confirmation and isn't finished when the call returns.

Confirming an upload doesn't add it to a filing. To do that, continue with
[Add a filing item](/guides/managed-filings/manage-documents#add-a-filing-item).

See the [initialization](/api-reference/files/prepare-file-uploads) and
[completion](/api-reference/files/confirm-uploaded-files) references for full schemas.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.