Get Presigned Upload URL
const options = {
method: 'POST',
headers: {
'x-api-key': '<x-api-key>',
'x-organization-id': '<x-organization-id>',
'Content-Type': '<content-type>'
},
body: JSON.stringify({file_name: '<string>', transport_direction: '<string>'})
};
fetch('https://api.dcycle.io/v1/transports/upload/presigned-url', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.dcycle.io/v1/transports/upload/presigned-url"
payload = {
"file_name": "<string>",
"transport_direction": "<string>"
}
headers = {
"x-api-key": "<x-api-key>",
"x-organization-id": "<x-organization-id>",
"Content-Type": "<content-type>"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)curl --request POST \
--url https://api.dcycle.io/v1/transports/upload/presigned-url \
--header 'Content-Type: <content-type>' \
--header 'x-api-key: <x-api-key>' \
--header 'x-organization-id: <x-organization-id>' \
--data '
{
"file_name": "<string>",
"transport_direction": "<string>"
}
'{
"upload_url": "<string>",
"file_id": "<string>",
"file_name": "<string>",
"destination_file_key": "<string>",
"message": "<string>"
}Get Presigned Upload URL
Generate a pre-signed S3 URL to upload a large transport route file directly
POST
/
v1
/
transports
/
upload
/
presigned-url
Get Presigned Upload URL
const options = {
method: 'POST',
headers: {
'x-api-key': '<x-api-key>',
'x-organization-id': '<x-organization-id>',
'Content-Type': '<content-type>'
},
body: JSON.stringify({file_name: '<string>', transport_direction: '<string>'})
};
fetch('https://api.dcycle.io/v1/transports/upload/presigned-url', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.dcycle.io/v1/transports/upload/presigned-url"
payload = {
"file_name": "<string>",
"transport_direction": "<string>"
}
headers = {
"x-api-key": "<x-api-key>",
"x-organization-id": "<x-organization-id>",
"Content-Type": "<content-type>"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)curl --request POST \
--url https://api.dcycle.io/v1/transports/upload/presigned-url \
--header 'Content-Type: <content-type>' \
--header 'x-api-key: <x-api-key>' \
--header 'x-organization-id: <x-organization-id>' \
--data '
{
"file_name": "<string>",
"transport_direction": "<string>"
}
'{
"upload_url": "<string>",
"file_id": "<string>",
"file_name": "<string>",
"destination_file_key": "<string>",
"message": "<string>"
}Generate a pre-signed Amazon S3 URL that lets you upload a transport route file directly from the client to S3 — bypassing the API server entirely. This is the recommended approach for files larger than a few MB.
Route statuses move through
Solution: Ensure
Two-Step Upload Flow
- Call this endpoint to receive a
upload_urland afile_id. - PUT the raw file bytes to
upload_url. No authentication headers are needed for the S3 PUT — the signature is embedded in the URL.
Request
Headers
string
required
Your API key for authenticationExample:
sk_live_1234567890abcdefstring
required
Your organization UUID.Example:
a8315ef3-dd50-43f8-b7ce-d839e68d51fastring
required
Must be
application/json.Body (JSON)
string
required
The name of the file being uploaded. Must end with
.csv, .xls, or .xlsx.Example: "q1_transport_routes.xlsx"string
required
The GHG Protocol scope 3 direction of these transport routes.
downstream— transport paid by your organization after the sale (Category 9)upstream— inbound transport paid by your organization (Category 4)
Response
string
Pre-signed S3 URL. Send the raw file bytes as the body of an HTTP
PUT request to this URL. The URL is time-limited — use it promptly after receiving it.Example: "https://dcycle-heavy-files.s3.eu-west-1.amazonaws.com/transport-and-distribution-downstream/...?X-Amz-Signature=..."string
UUID that identifies this upload batch. Use it to filter routes after processing via
GET /v1/transports?file_id=<file_id>.Example: "c4e8f2a1-5b6d-49e0-c7f9-2345678901bc"string
Sanitized version of the file name (alphanumeric characters only, extension preserved).Example:
"q1_transport_routes"string
The S3 object key where the file will be stored. Useful for audit trails or debugging.Example:
"heavy-files/transport-and-distribution-downstream/partner/org-uuid/c4e8f2a1.../user-uuid/q1_transport_routes.xlsx"string
Human-readable confirmation.Example:
"Upload pre-signed url generated"Example
# Step 1: Get the presigned URL
RESPONSE=$(curl -s -X POST "https://api.dcycle.io/v1/transports/upload/presigned-url" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}" \
-H "Content-Type: application/json" \
-d '{
"file_name": "q1_transport_routes.xlsx",
"transport_direction": "downstream"
}')
UPLOAD_URL=$(echo $RESPONSE | jq -r '.upload_url')
FILE_ID=$(echo $RESPONSE | jq -r '.file_id')
echo "File ID: $FILE_ID"
# Step 2: PUT the file directly to S3 (no auth headers needed)
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/octet-stream" \
--data-binary @q1_transport_routes.xlsx
import requests
import os
api_key = os.getenv("DCYCLE_API_KEY")
org_id = os.getenv("DCYCLE_ORG_ID")
headers = {
"x-api-key": api_key,
"x-organization-id": org_id,
"Content-Type": "application/json",
}
# Step 1: Get the presigned URL
response = requests.post(
"https://api.dcycle.io/v1/transports/upload/presigned-url",
headers=headers,
json={
"file_name": "q1_transport_routes.xlsx",
"transport_direction": "downstream",
},
)
data = response.json()
upload_url = data["upload_url"]
file_id = data["file_id"]
print(f"File ID: {file_id}")
# Step 2: PUT the raw file bytes directly to S3
with open("q1_transport_routes.xlsx", "rb") as f:
s3_response = requests.put(
upload_url,
data=f,
headers={"Content-Type": "application/octet-stream"},
)
s3_response.raise_for_status()
print("Upload complete — processing started in the background")
const axios = require('axios');
const fs = require('fs');
const apiKey = process.env.DCYCLE_API_KEY;
const orgId = process.env.DCYCLE_ORG_ID;
async function uploadTransportFile(filePath) {
// Step 1: Get the presigned URL
const presignResponse = await axios.post(
'https://api.dcycle.io/v1/transports/upload/presigned-url',
{
file_name: 'q1_transport_routes.xlsx',
transport_direction: 'downstream',
},
{
headers: {
'x-api-key': apiKey,
'x-organization-id': orgId,
'Content-Type': 'application/json',
},
}
);
const { upload_url, file_id } = presignResponse.data;
console.log(`File ID: ${file_id}`);
// Step 2: PUT the file bytes directly to S3
const fileStream = fs.createReadStream(filePath);
await axios.put(upload_url, fileStream, {
headers: { 'Content-Type': 'application/octet-stream' },
});
console.log('Upload complete — processing started in the background');
return file_id;
}
uploadTransportFile('q1_transport_routes.xlsx').catch(console.error);
Successful Response (Step 1)
{
"upload_url": "https://dcycle-heavy-files.s3.eu-west-1.amazonaws.com/heavy-files/transport-and-distribution-downstream/dcycle/a8315ef3-dd50-43f8-b7ce-d839e68d51fa/c4e8f2a1-5b6d-49e0-c7f9-2345678901bc/550e8400-e29b-41d4-a716-446655440000/q1_transport_routes.xlsx?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Signature=...",
"file_id": "c4e8f2a1-5b6d-49e0-c7f9-2345678901bc",
"file_name": "q1_transport_routes",
"destination_file_key": "heavy-files/transport-and-distribution-downstream/dcycle/a8315ef3.../c4e8f2a1.../q1_transport_routes.xlsx",
"message": "Upload pre-signed url generated"
}
Asynchronous Processing
After the S3 PUT completes, Dcycle picks up the file and processes it in the background:- Rows are parsed from the spreadsheet.
- Origin and destination addresses are geocoded.
- Emission factors are resolved and CO2e values are calculated.
file_id:
curl "https://api.dcycle.io/v1/transports?file_id=c4e8f2a1-5b6d-49e0-c7f9-2345678901bc" \
-H "x-api-key: ${DCYCLE_API_KEY}" \
-H "x-organization-id: ${DCYCLE_ORG_ID}"
pending → active (success) or error as processing completes.
Common Errors
401 Unauthorized
Cause: Missing or invalid API key / Bearer token.{
"detail": "Invalid API key",
"code": "INVALID_API_KEY"
}
422 Unprocessable Entity
Cause:file_name has an unsupported extension, or transport_direction is invalid.
{
"detail": [
{
"loc": ["body", "file_name"],
"msg": "file_name must end with .csv, .xls, or .xlsx",
"type": "value_error"
}
]
}
file_name ends with .csv, .xls, or .xlsx, and that transport_direction is downstream or upstream.
S3 PUT Errors
If the S3 PUT returns a403 Forbidden, the presigned URL may have expired. Request a new URL and retry.
Related Endpoints
Direct File Upload
Upload smaller files directly through the API (multipart/form-data)
List Transport Routes
Retrieve transport routes, filter by file_id to track batch processing
Transport Combinations
Explore valid transport type and method combinations before creating routes
Transport Overview
Learn the full Transport API data model and workflow
Was this page helpful?