Docs/Files

Upload a file

Upload attachments with presigned URLs.

POSThttps://api.vaelor.chat/v1/files

Files are uploaded straight to storage with a presigned URL, so large uploads never pass through the API. An upload takes three steps: prepare the upload, PUT the bytes, then finalize. The finalized file can be attached to chat requests.

Body#

actionenumRequired

prepare, finalize or read.

namestring

The file name, 1–240 characters. Required for prepare.

mediaTypestring

MIME type, such as application/pdf. Required for prepare.

byteSizeinteger

Size in bytes, up to 20 MiB (20,971,520). Required for prepare.

iduuid

The file ID returned by prepare. Required for finalize and read.

kindenumDefault attachment

attachment or avatar. Avatars must be images of 1 MiB or less.

Uploading a file#

  1. 1

    Prepare the upload

    Shell
    curl https://api.vaelor.chat/v1/files \
      -H "Authorization: Bearer $VAELOR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"action": "prepare", "name": "q3-report.pdf", "mediaType": "application/pdf", "byteSize": 482113}'
    JSON
    {
      "id": "c0a8012e-5b7f-4c3d-a9e2-71f4d6b8e015",
      "uploadUrl": "https://uploads.vaelor.chat/c0a8012e…?X-Amz-Signature=…"
    }
  2. 2

    PUT the bytes to the upload URL

    The URL expires after 15 minutes. Send the same Content-Type you declared, and no Authorization header.

    Shell
    curl -X PUT "$UPLOAD_URL" \
      -H "Content-Type: application/pdf" \
      --data-binary @q3-report.pdf
  3. 3

    Finalize

    Shell
    curl https://api.vaelor.chat/v1/files \
      -H "Authorization: Bearer $VAELOR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"action": "finalize", "id": "c0a8012e-5b7f-4c3d-a9e2-71f4d6b8e015"}'

Response#

finalize and read return the file object.

iduuid
Unique file ID.
urlstring
A signed download URL, valid for 1 hour. Use it in chat attachments.
stableUrlstring
A permanent URL that redirects to a fresh signed URL. See Retrieve a file.
namestring
File name.
mediaTypestring
MIME type.
byteSizeinteger
Size in bytes.

Response200

JSON
{
  "id": "c0a8012e-5b7f-4c3d-a9e2-71f4d6b8e015",
  "url": "https://files.vaelor.chat/c0a8012e…?X-Amz-Expires=3600&X-Amz-Signature=…",
  "stableUrl": "https://api.vaelor.chat/v1/files/c0a8012e-5b7f-4c3d-a9e2-71f4d6b8e015",
  "name": "q3-report.pdf",
  "mediaType": "application/pdf",
  "byteSize": 482113
}

Full example#

import { readFile, stat } from 'node:fs/promises'

const headers = {
  Authorization: `Bearer ${process.env.VAELOR_API_KEY}`,
  'Content-Type': 'application/json'
}

const call = (body: object) =>
  fetch('https://api.vaelor.chat/v1/files', { method: 'POST', headers, body: JSON.stringify(body) }).then((r) =>
    r.json()
  )

const path = 'q3-report.pdf'
const { size } = await stat(path)

const { id, uploadUrl } = await call({ action: 'prepare', name: path, mediaType: 'application/pdf', byteSize: size })

await fetch(uploadUrl, { method: 'PUT', headers: { 'Content-Type': 'application/pdf' }, body: await readFile(path) })

const file = await call({ action: 'finalize', id })

Errors#

StatusWhen
400A required field is missing, the file is over 20 MiB, or an avatar isn't an image.
404finalize or read was called with an unknown ID, or the bytes were never uploaded.

Was this page helpful?

Questions? Contact support

Last updated October 20, 2026