Uploading Files
Understand the different methods for uploading files to Vulgate, including single and multipart uploads.
Upload modes
Ingesting files is a two-step process. First, you upload the files, and then start the ingestion process.
Vulgate supports two upload modes: single and multipart.
Single upload
To upload a file at once, follow these two steps:
- Obtain a signed URL for the file upload.
- Make a PUT request to the signed URL with the file content.
Obtaining a signed URL
To obtain a signed URL for the file upload, use the following endpoint:
GET /api/uploads
The following parameters are supported as URL search params:
| Parameter | Type | Default | Description |
|---|---|---|---|
name | string | Required | The name of the file |
type | string | Required | The content type of the file |
For example:
GET /api/uploads?name=example.pdf&type=application/pdf
The response schema is as follows:
{
"url": "https://your-signed-url.com",
"method": "PUT",
"headers": {
"content-type": "application/pdf",
"x-amz-meta-name": "example.pdf"
}
}
Making a PUT request
To upload the file, make a PUT request to the signed URL with the file content. The request should include the headers specified in the response object.
For example:
PUT your-signed-url.com HTTP/1.1
Content-Type: application/pdf
X-Amz-Meta-Name: example.pdf
<file content>
Multipart upload
For large files, upload the file in parts using an S3-style multipart flow. The overall sequence is:
- Initiate the multipart upload to obtain an
uploadIdand an objectkey. - For each part, request a signed URL, then PUT the chunk to that URL and keep the
ETagreturned in the response headers. - Complete the upload by sending the ordered list of part numbers and their ETags.
Each part must be at least 5 MB, except the last part. Choose a part size, split the file into sequential parts numbered from 1, and upload them with the steps below. Use List parts to resume an interrupted upload and Abort to cancel one.
Initiating a multipart upload
POST /api/uploads
Send the file metadata in the request body:
{
"file": {
"name": "example.pdf",
"type": "application/pdf",
"size": 73400320,
"meta": { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479" }
}
}
| Field | Type | Default | Description |
|---|---|---|---|
file.name | string | Required | The name of the file |
file.type | string | Required | The content type of the file |
file.size | number | — | The file size in bytes |
file.meta.id | string (UUID) | — | Optional client-supplied UUID. When valid it becomes the file’s id; otherwise one is generated. |
The response returns the uploadId and key used by every subsequent request, plus the id you later pass to POST /api/jobs:
{
"uploadId": "2~aBcD1234efGh5678",
"key": "user_files/file_f47ac10b-58cc-4372-a567-0e02b2c3d479",
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
Uploading a part
For each part, first request a signed URL:
POST /api/uploads/{uploadId}/parts/{partNumber}
| Parameter | Type | Default | Description |
|---|---|---|---|
uploadId | string | Required | The uploadId from the initiate step (path segment) |
partNumber | number | Required | The 1-based index of this part (path segment) |
{ "key": "user_files/file_f47ac10b-58cc-4372-a567-0e02b2c3d479" }
The response contains a presigned PUT URL, valid for one hour:
{ "url": "https://your-signed-url.com" }
Then PUT the chunk directly to that URL and record the ETag from the response headers — you must supply it when completing the upload. Repeat for every part.
Completing the upload
POST /api/uploads/{uploadId}/complete
Send the object key and the parts in ascending PartNumber order. Use the exact ETag value returned by each part’s PUT response (S3 returns it as a quoted string):
{
"key": "user_files/file_f47ac10b-58cc-4372-a567-0e02b2c3d479",
"parts": [
{ "PartNumber": 1, "ETag": "\"d8e8fca2dc0f896fd7cb4cb0031ba249\"" },
{ "PartNumber": 2, "ETag": "\"a420c5f7b1c0e9d3f8a1b2c3d4e5f607\"" }
]
}
The response describes the assembled object:
{
"Location": "https://.../user_files/file_f47ac10b-58cc-4372-a567-0e02b2c3d479",
"Bucket": "vulgate",
"Key": "user_files/file_f47ac10b-58cc-4372-a567-0e02b2c3d479",
"ETag": "\"f8c2e1d0b9a8765432100fedcba98765-2\""
}
Listing uploaded parts
POST /api/uploads/{uploadId}/parts
To resume an interrupted upload, list the parts already stored, then upload only the missing ones:
{ "key": "user_files/file_f47ac10b-58cc-4372-a567-0e02b2c3d479" }
[
{
"PartNumber": 1,
"ETag": "\"d8e8fca2dc0f896fd7cb4cb0031ba249\"",
"Size": 5242880,
"LastModified": "2025-01-01T00:00:00.000Z"
}
]
Aborting a multipart upload
DELETE /api/uploads/{uploadId}
Cancel a multipart upload and discard any uploaded parts:
{ "key": "user_files/file_f47ac10b-58cc-4372-a567-0e02b2c3d479" }
Full example
Step 1 — Initiate the multipart upload:
curl -X POST "https://vulgate.ai/api/uploads" \
-H "Authorization: Bearer $VULGATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"file": { "name": "large.pdf", "type": "application/pdf", "size": 73400320 }
}'
Step 2 — For each part, get a signed URL and upload the chunk:
# Request a signed URL for part 1
curl -X POST "https://vulgate.ai/api/uploads/<upload-id>/parts/1" \
-H "Authorization: Bearer $VULGATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "key": "<object-key>" }'
# Upload the chunk to the signed URL; -D - prints the ETag response header
curl -X PUT "<signed-url>" \
--data-binary @part-1.bin \
-D -
Step 3 — Complete the upload with the collected ETags:
curl -X POST "https://vulgate.ai/api/uploads/<upload-id>/complete" \
-H "Authorization: Bearer $VULGATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"key": "<object-key>",
"parts": [
{ "PartNumber": 1, "ETag": "<etag-from-part-1>" },
{ "PartNumber": 2, "ETag": "<etag-from-part-2>" }
]
}'
Once the upload is complete, pass the id from step 1 to POST /api/jobs to create a document and start ingestion.