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
- Create the upload with
POST /v0/uploads. The response is an upload withstatus: "pending"and anuploadFile. - Send the file to
uploadFile.urlwith theuploadFile.methodanduploadFile.headers, with 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. 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| Field | Type | Description |
|---|---|---|
kind | string | What the file is for. artworkImage is the only kind today, and accepts only images. 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 passed back in the uploadFile headers. |
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=1725621435&signature=...",
"method": "PUT",
"headers": { "Content-Type": "image/jpeg" },
"expiresAt": "2024-09-06T11:27:15.150Z"
}
}
}The upload object
| Field | Type | Description |
|---|---|---|
id | string | The upload’s ID. Pass it when linking the upload to a record. An artwork image’s id is its upload’s ID. |
status | string | pending until the file arrives, then ready. |
filename | string | The file’s name. |
createdAt | string | When the upload was created. |
uploadFile | object | The request that sends the file. Only present on the response that created the upload. |
Where to send the file
| 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. |
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 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.
| Status | Meaning |
|---|---|
400 | The file is empty, isn’t a type Valise accepts, doesn’t match the upload’s kind, 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. |
Images are limited to 150 megapixels.