Upload Files
const options = {
method: 'POST',
headers: {
'x-api-key': '<x-api-key>',
'x-organization-id': '<x-organization-id>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: '<string>',
extension: '<string>',
mime_type: '<string>',
size_kb: 123,
folder_id: '<string>',
file_ids: ['<string>'],
status: '<string>',
project_id: '<string>'
})
};
fetch('https://api.dcycle.io/v1/files/presigned-urls', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.dcycle.io/v1/files/presigned-urls"
payload = {
"name": "<string>",
"extension": "<string>",
"mime_type": "<string>",
"size_kb": 123,
"folder_id": "<string>",
"file_ids": ["<string>"],
"status": "<string>",
"project_id": "<string>"
}
headers = {
"x-api-key": "<x-api-key>",
"x-organization-id": "<x-organization-id>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)curl --request POST \
--url https://api.dcycle.io/v1/files/presigned-urls \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--header 'x-organization-id: <x-organization-id>' \
--data '
{
"name": "<string>",
"extension": "<string>",
"mime_type": "<string>",
"size_kb": 123,
"folder_id": "<string>",
"file_ids": [
"<string>"
],
"status": "<string>",
"project_id": "<string>"
}
'{
"id": "<string>",
"status": "<string>",
"url": "<string>",
"presigned_url": "<string>"
}Upload Files
Autonomous file uploads via presigned S3 URLs, with explicit upload confirmation
POST
/
v1
/
files
/
presigned-urls
Upload Files
const options = {
method: 'POST',
headers: {
'x-api-key': '<x-api-key>',
'x-organization-id': '<x-organization-id>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: '<string>',
extension: '<string>',
mime_type: '<string>',
size_kb: 123,
folder_id: '<string>',
file_ids: ['<string>'],
status: '<string>',
project_id: '<string>'
})
};
fetch('https://api.dcycle.io/v1/files/presigned-urls', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.dcycle.io/v1/files/presigned-urls"
payload = {
"name": "<string>",
"extension": "<string>",
"mime_type": "<string>",
"size_kb": 123,
"folder_id": "<string>",
"file_ids": ["<string>"],
"status": "<string>",
"project_id": "<string>"
}
headers = {
"x-api-key": "<x-api-key>",
"x-organization-id": "<x-organization-id>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)curl --request POST \
--url https://api.dcycle.io/v1/files/presigned-urls \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--header 'x-organization-id: <x-organization-id>' \
--data '
{
"name": "<string>",
"extension": "<string>",
"mime_type": "<string>",
"size_kb": 123,
"folder_id": "<string>",
"file_ids": [
"<string>"
],
"status": "<string>",
"project_id": "<string>"
}
'{
"id": "<string>",
"status": "<string>",
"url": "<string>",
"presigned_url": "<string>"
}Upload Files
Use this 3-step handshake to upload files from browsers, scripts, CI jobs, or the Dcycle CLI without streaming file bytes through the API server.1
Create presigned upload URLs
Call
POST /v1/files/presigned-urls. The backend creates pending file rows and returns one presigned S3 URL per file.2
Upload file bytes to S3
PUT the raw file bytes to each presigned URL using the same Content-Type you sent in step 1.3
Confirm the upload
Call
PATCH /v1/files/batch-update with status=uploaded. This marks the file as uploaded and emits CLASSIFY_DOCUMENT.Step 1: Create Presigned URLs
Headers
string
required
Your API key for authenticationExample:
sk_live_1234567890abcdefstring
required
Your organization UUIDExample:
a8315ef3-dd50-43f8-b7ce-d839e68d51faBody
Send an array so one request can prepare multiple uploads.string
required
File name without the extension.
string
required
File extension such as
pdf, csv, xlsx, jpg, or png.string
required
MIME type for the uploaded file.
integer
required
File size in kilobytes.
string
Optional folder UUID. Omit for root-level uploads.
Example
curl -X POST "https://api.dcycle.io/v1/files/presigned-urls" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Content-Type: application/json" \
-d '[
{
"name": "invoice_january",
"extension": "pdf",
"mime_type": "application/pdf",
"size_kb": 2048
}
]'
import os
import requests
response = requests.post(
"https://api.dcycle.io/v1/files/presigned-urls",
headers={
"x-api-key": os.environ["DCYCLE_API_KEY"],
"x-organization-id": os.environ["DCYCLE_ORG_ID"],
"Content-Type": "application/json",
},
json=[
{
"name": "invoice_january",
"extension": "pdf",
"mime_type": "application/pdf",
"size_kb": 2048,
}
],
timeout=30,
)
file_upload = response.json()[0]
print(file_upload["id"], file_upload["presigned_url"])
const axios = require('axios');
const response = await axios.post('https://api.dcycle.io/v1/files/presigned-urls', [
{
name: 'invoice_january',
extension: 'pdf',
mime_type: 'application/pdf',
size_kb: 2048,
},
], {
headers: {
'x-api-key': process.env.DCYCLE_API_KEY,
'x-organization-id': process.env.DCYCLE_ORG_ID,
'Content-Type': 'application/json',
},
});
const fileUpload = response.data[0];
console.log(fileUpload.id, fileUpload.presigned_url);
Response Fields
string
File UUID stored in PostgreSQL.
string
Starts as
pending.string
Final S3-backed file URL stored on the File record.
string
Temporary S3
PUT URL used for the direct upload.Successful Response
[
{
"id": "11111111-1111-1111-1111-111111111111",
"name": "invoice_january",
"extension": "pdf",
"mime_type": "application/pdf",
"size_kb": 2048,
"url": "https://dcycle-files.s3.eu-west-1.amazonaws.com/orgs/a8315ef3/invoice_january.pdf",
"status": "pending",
"presigned_url": "https://dcycle-files.s3.eu-west-1.amazonaws.com/orgs/a8315ef3/invoice_january.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&..."
}
]
Step 2: Upload to S3
Upload the raw file bytes to thepresigned_url returned in step 1.
curl -X PUT "${PRESIGNED_URL}" \
-H "Content-Type: application/pdf" \
--data-binary @invoice_january.pdf
with open("invoice_january.pdf", "rb") as file_handle:
upload_response = requests.put(
file_upload["presigned_url"],
headers={"Content-Type": "application/pdf"},
data=file_handle,
timeout=300,
)
upload_response.raise_for_status()
const fs = require('fs');
const axios = require('axios');
await axios.put(fileUpload.presigned_url, fs.readFileSync('invoice_january.pdf'), {
headers: { 'Content-Type': 'application/pdf' },
});
Step 3: Confirm the Upload
PATCH /v1/files/batch-update is the step that flips the file from pending to uploaded and triggers document classification.
Body
string[]
required
File IDs returned by step 1.
string
required
Use
uploaded after a successful S3 upload, or error if the upload failed.string
Optional project UUID. When present, the backend also creates
file_project links.Example
curl -X PATCH "https://api.dcycle.io/v1/files/batch-update" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Content-Type: application/json" \
-d '{
"file_ids": ["11111111-1111-1111-1111-111111111111"],
"status": "uploaded"
}'
requests.patch(
"https://api.dcycle.io/v1/files/batch-update",
headers={
"x-api-key": os.environ["DCYCLE_API_KEY"],
"x-organization-id": os.environ["DCYCLE_ORG_ID"],
"Content-Type": "application/json",
},
json={
"file_ids": [file_upload["id"]],
"status": "uploaded",
},
timeout=30,
).raise_for_status()
await axios.patch('https://api.dcycle.io/v1/files/batch-update', {
file_ids: [fileUpload.id],
status: 'uploaded',
}, {
headers: {
'x-api-key': process.env.DCYCLE_API_KEY,
'x-organization-id': process.env.DCYCLE_ORG_ID,
'Content-Type': 'application/json',
},
});
Event Trigger
Whenstatus=uploaded, the backend emits CLASSIFY_DOCUMENT with:
{
"resource": "POST",
"method": "/classify-document",
"file_id": "file-uuid",
"user_id": "user-uuid",
"organization_id": "organization-uuid"
}
Common Errors
401 Unauthorized
Cause: Missing or invalid API key{"detail": "Invalid API key for organization", "code": "INVALID_API_KEY"}
403 Forbidden
Cause: The authenticated user is not a member of the organization{"detail": "Logged User is not Member of Organization", "code": "LOGGED_USER_NOT_MEMBER"}
422 Unprocessable Entity
Cause: Missing required fields (name, extension, mime_type, size_kb)
{
"detail": [
{
"loc": ["body", 0, "name"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
Legacy Alternative
If you do not need the direct-to-S3 flow,POST /v1/files/upload still accepts a standard multipart upload through the backend.
Related Endpoints
List File Readings
Retrieve extracted readings after processing
Retry Processing
Re-queue a file for processing
Update Reading
Edit extracted content before creating records
Create Records
Convert readings into invoices or wastes
Was this page helpful?