> ## 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

> Initialize a Vault upload, send bytes to storage, and confirm the resulting artifact.

Complete the [authentication setup](/getting-started/authentication). Use a
write-capable user key with access to the destination. Set `EFFECTIVE_API_BASE_URL`
to your deployment origin without `/api/v2` and `EFFECTIVE_API_KEY` to your key.
The examples use Bash, curl, and jq, with a local file named `form.pdf`.

## Choose the destination

* `personal://Documents`: your personal Vault folder.
* `team://Documents`: the team's Vault folder, subject to your access.
* `@mc/Documents`: an MC's Documents folder. Include `scope: "mc:<UUID or stable slug>"`.

Send `scope` only for `@mc` destinations. No MC header is needed. For
`artifact://` writes, use a vault root plus a relative path, not an ordinary
folder ID. Alternatively, provide `sessionId` instead of `targetUri` to upload
to a session folder.

Location and record ownership are separate. To create a record-owned document,
include `ownerType` and `ownerId` on each file. For filings, use the
[filing destination and ownership example](/guides/managed-filings/manage-documents#upload-a-document)
then continue at [Upload the file](#upload-the-file).

## Prepare the request

This example uploads a file without a record owner to your team's Documents folder. For a
filing document, use the linked filing-specific request instead.

```bash theme={null}
FILE_SIZE=$(wc -c < form.pdf | tr -d '[:space:]')
cat > upload-request.json <<EOF
{
  "targetUri": "team://Documents",
  "onConflict": "rename",
  "files": [{
    "path": "form.pdf",
    "size": ${FILE_SIZE},
    "mimeType": "application/pdf",
    "type": "file"
  }]
}
EOF
```

## Upload the file

1. Initialize the upload with `POST /api/v2/files/uploads`.

   ```bash theme={null}
   curl --silent --show-error --fail-with-body \
     --header "Authorization: Bearer ${EFFECTIVE_API_KEY}" \
     --header 'Content-Type: application/json' \
     --data @upload-request.json \
     --output upload-init.json \
     "${EFFECTIVE_API_BASE_URL%/}/api/v2/files/uploads"
   ```

   The response contains `targetUri` and `files[]` with `path`, `signedUrl`,
   optional `headers`, `expiresAt`, and `uploadToken`. `rename` preserves an
   existing file with the same name; it does not replace that file or its version.
   If you choose `onConflict: "fail"`, inspect `conflicts[]`: those files are
   excluded from the PUT instructions.

2. PUT the PDF bytes to the returned signed URL, using the returned headers.

   ```bash theme={null}
   SIGNED_URL=$(jq -er '.files[0].signedUrl' upload-init.json)
   PUT_HEADERS=()
   while IFS= read -r header; do
     PUT_HEADERS+=(--header "$header")
   done < <(jq -r '.files[0].headers // {} | to_entries[] | "\(.key): \(.value)"' upload-init.json)

   curl --silent --show-error --fail-with-body \
     --request PUT "${PUT_HEADERS[@]}" \
     --data-binary @form.pdf "$SIGNED_URL"
   ```

   Do not send the Effective bearer credential to storage. Continue only after
   the PUT succeeds.

3. Complete the upload with `POST /api/v2/files/uploads/complete`.

   ```bash theme={null}
   jq '{confirmations: [{uploadToken: .files[0].uploadToken}]}' \
     upload-init.json > upload-complete-request.json
   curl --silent --show-error --fail-with-body \
     --header "Authorization: Bearer ${EFFECTIVE_API_KEY}" \
     --header 'Content-Type: application/json' \
     --data @upload-complete-request.json \
     --output upload-complete.json \
     "${EFFECTIVE_API_BASE_URL%/}/api/v2/files/uploads/complete"
   ```

   Check every `results[].success`, even when HTTP status is `200`. A successful
   result contains `uri` and `artifact`. Save `artifact.id` to reference the file
   and `artifact.name` for its stored name, which may differ from the requested name.
   For this single-file example:

   ```bash theme={null}
   ARTIFACT_ID=$(jq -er '.results[0] | select(.success == true) | .artifact.id' upload-complete.json)
   ```

   A failed result contains `error.code` and `error.message`. For example,
   `NOT_FOUND` means the uploaded file was not found; `EXPIRED` means the token
   expired. Do not use a failed upload as a file reference. The token carries the owner fields
   from initialization; do not resend them or `scope` in the completion body.

Both API calls accept batches of up to 100 files and JSON bodies up to 1 MiB.
File bytes go in the PUT, not the JSON. Complete before the token expires after
one hour, and use the signed URL before its returned `expiresAt`.
Neither initialization nor completion is idempotent. After a lost response,
inspect the destination in Vault before retrying: repeating completion can
create another version. Completion schedules indexing; it does not wait for
searchable text.

Upload completion creates or updates the Vault artifact. It does not create
application records such as filing items. To add a completed file to a filing,
continue with [Add a filing item](/guides/managed-filings/manage-documents#add-a-filing-item).

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


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