Uploads API
Send files to Valise and link them to records.
Uploads are files attached to Valise records. An upload could be an image on an artwork or presentation, or a document on a sale, consignment, or expense.
Working with uploads happens across a few steps: creating an upload record, sending the file bytes, and then linking the upload to the relevant record. All requests require an API key, as described in the API overview.
Currently only artwork images are supported, though we intend to add support for all upload types over time.
How uploads work
- Create the upload with
POST /v0/uploads. The response is an upload withstatus: "pending"and anuploadFilesaying where to send the file. - Send the file with a
PUTrequest touploadFile.url, using theuploadFile.headersand the raw file as the request body. The response is the upload, nowready. - 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. After that, uploads with no links are removed along with their files.
Here’s a JS example, where file is a File or Blob and artworkId is the ID of the artwork to attach it to:
const headers = {
Authorization: "Bearer <API_KEY>",
"Content-Type": "application/json",
}
// 1. Create the upload
const created = 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,
}),
})
const { data: upload } = await created.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| Field | Type | Description |
|---|---|---|
kind | string | What the file is for. artworkImage is the only kind today, and accepts images only. Required. |
filename | string | The file’s name, as it should appear in Valise. Required. |
contentType | string | The file’s MIME type, if you know it. It’s echoed in the uploadFile headers. Valise detects the type from the file itself either way. |
size | number | The 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=1725622035&signature=...",
"method": "PUT",
"headers": { "Content-Type": "image/jpeg" },
"expiresAt": "2024-09-06T11:27:15Z"
}
}
}The upload object
| Field | Type | Description |
|---|---|---|
id | string | The upload’s ID. Pass it when linking the upload to a record. Once linked, it’s also the image’s id on the artwork. |
status | string | pending until the file arrives, then ready. |
filename | string | The file’s name. |
createdAt | string | When the upload was created. |
uploadFile | object | Where to send the file. Only present on the response that created the upload. |
An image’s dimensions and URL are reported on the artwork once the upload is linked, not on the upload.
The uploadFile object
| Field | Type | Description |
|---|---|---|
url | string | The URL to send the file to. |
method | string | The HTTP method to use. Always PUT. |
headers | object | Headers to send with the request, verbatim. Empty if you didn’t give a contentType. |
expiresAt | string | When 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 stops working if any of them change. Use it exactly as given. If the URL expires before you’ve sent the file, create the upload again to get a new one.
Send the file
PUT <uploadFile.url>The request body is the raw file, not a form. A Content-Length header is required: Valise uses it to refuse a file over your plan’s limits before reading the body. 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=1725622035&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:
{
"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.
Images are limited to a maximum of 150 megapixels. Larger images should be stored as documents.
Creating an upload uses the response codes shared by every endpoint. Sending the file has its own:
| Status | Meaning |
|---|---|
400 | The file is empty, isn’t a type the upload’s kind accepts, is over 150 megapixels, or can’t be read. |
403 | The upload URL has expired or was changed. |
404 | The upload was removed before the file arrived. |
409 | The upload already has its file. |
411 | The request has no Content-Length header. |
413 | The file is larger than your plan allows, or would exceed your storage. |