Uploads API

Send files to Valise and link them to records.

Uploads represent any file attached to a Valise record: an image on an artwork or presentation, or a document on a sale, consignment, or expense. The API currently accepts artwork images. Files reach Valise in three steps: create an upload, send the file, and link the upload to a record. Every request except sending the file needs an API key, as described in the API overview.

How uploads work

  1. Create the upload with POST /v0/uploads. The response is an upload with status: "pending" and an uploadFile.
  2. Send the file to uploadFile.url with the uploadFile.method and uploadFile.headers, with the raw file as the request body. The response is the upload, now ready.
  3. Link the upload to its record. For artwork images that’s the add an image endpoint, which takes the upload’s id.

Link an upload within an hour of creating it. Uploads with no links are removed after that, along with their files.

Here’s a JS example of uploading an image and attaching it to an artwork, where file is a File or Blob:

const headers = {
  Authorization: "Bearer <API_KEY>",
  "Content-Type": "application/json",
}
 
// 1. Create the upload
const { data: upload } = await fetch("https://api.valise.works/v0/uploads", {
  method: "POST",
  headers,
  body: JSON.stringify({
    kind: "artworkImage",
    filename: file.name,
    contentType: file.type,
    size: file.size,
  }),
}).then((res) => res.json())
 
// 2. Send the file
await fetch(upload.uploadFile.url, {
  method: upload.uploadFile.method,
  headers: upload.uploadFile.headers,
  body: file,
})
 
// 3. Link it to the artwork
await fetch(`https://api.valise.works/v0/artworks/${artworkId}/images`, {
  method: "POST",
  headers,
  body: JSON.stringify({ uploadId: upload.id }),
})

Create an upload

Create an upload for a file. Requires a key with read and write permission.

POST /v0/uploads
FieldTypeDescription
kindstringWhat the file is for. artworkImage is the only kind today, and accepts only images. Required.
filenamestringThe file’s name, as it should appear in Valise. Required.
contentTypestringThe file’s MIME type, if you know it. It’s passed back in the uploadFile headers.
sizenumberThe file’s size in bytes, if you know it. A file over your plan’s limits is refused here, before you send it.
curl https://api.valise.works/v0/uploads
  --method POST
  --header "Authorization: Bearer <API_KEY>"
  --header "Content-Type: application/json"
  --data '{"kind": "artworkImage", "filename": "my-artwork.jpg", "contentType": "image/jpeg", "size": 4183212}'

On success, the API returns a 201 status code with the upload and where to send its file:

{
  "data": {
    "id": "d2qv94c5i88kavrvigcg",
    "status": "pending",
    "filename": "my-artwork.jpg",
    "createdAt": "2024-09-06T11:17:15.150Z",
    "uploadFile": {
      "url": "https://api.valise.works/v0/upload?id=d2qv94c5i88kavrvigcg&kind=artworkImage&expires=1725621435&signature=...",
      "method": "PUT",
      "headers": { "Content-Type": "image/jpeg" },
      "expiresAt": "2024-09-06T11:27:15.150Z"
    }
  }
}

The upload object

FieldTypeDescription
idstringThe upload’s ID. Pass it when linking the upload to a record. An artwork image’s id is its upload’s ID.
statusstringpending until the file arrives, then ready.
filenamestringThe file’s name.
createdAtstringWhen the upload was created.
uploadFileobjectThe request that sends the file. Only present on the response that created the upload.

Where to send the file

FieldTypeDescription
urlstringThe URL to send the file to.
methodstringThe HTTP method to use. Always PUT.
headersobjectHeaders to send with the request, verbatim.
expiresAtstringWhen the URL stops accepting the file. Ten minutes after the upload was created.

The URL is signed over the upload’s ID, its kind, and its expiry, and it stops working if any of them change. Use it exactly as given, and don’t send your API key with it.

If the URL expires before you’ve sent the file, create the upload again to get a new one.

Sending the file

PUT <uploadFile.url>

The request body is the raw file, not a form. A Content-Length header is required, so a file over your plan’s limits is refused before it’s sent. Most HTTP clients set it for you when the body is a file.

curl "https://api.valise.works/v0/upload?id=d2qv94c5i88kavrvigcg&kind=artworkImage&expires=1789045092&signature=..."
  --upload-file my-artwork.jpg
  --header "Content-Type: image/jpeg"

On success, the API returns a 200 status code with the upload, now ready. Link it to its record to see the image’s dimensions and URL:

{
  "data": {
    "id": "d2qv94c5i88kavrvigcg",
    "status": "ready",
    "filename": "my-artwork.jpg",
    "createdAt": "2024-09-06T11:17:15.150Z"
  }
}

Each URL accepts one file, and sending another returns a 409. If a file is refused, the upload stays pending, so you can fix the file and send it to the same URL while it’s still valid.

Limits and errors

Files are checked against your plan’s limits twice: when you create the upload, if you include the file’s size, and again when the file arrives. A file larger than your plan allows, or one that would exceed your storage, returns a 413, and the message says which limit was hit.

StatusMeaning
400The file is empty, isn’t a type Valise accepts, doesn’t match the upload’s kind, or can’t be read.
403The upload URL has expired or was changed.
404The upload was removed before the file arrived.
409The upload already has its file.
411The request has no Content-Length header.
413The file is larger than your plan allows, or would exceed your storage.

Images are limited to 150 megapixels.

Have more questions?

We're here to help. If you didn't find what you were looking for, or have more questions feel free to reach out.

Contact Us