> ## Documentation Index
> Fetch the complete documentation index at: https://code.dcycle.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Get Presigned Upload URL

> Generate a pre-signed S3 URL to upload a large transport route file directly

# Get Presigned Upload URL

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.

## Two-Step Upload Flow

1. **Call this endpoint** to receive a `upload_url` and a `file_id`.
2. **PUT the raw file bytes** to `upload_url`. No authentication headers are needed for the S3 PUT — the signature is embedded in the URL.

Once the file lands in S3, Dcycle picks it up automatically and begins asynchronous processing (geocoding, emission factor resolution, CO2e calculation).

## Request

### Headers

<ParamField header="x-api-key" type="string" required>
  Your API key for authentication

  **Example:** `sk_live_1234567890abcdef`
</ParamField>

<ParamField header="x-organization-id" type="string" required>
  Your organization UUID.

  **Example:** `a8315ef3-dd50-43f8-b7ce-d839e68d51fa`
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Must be `application/json`.
</ParamField>

### Body (JSON)

<ParamField body="file_name" type="string" required>
  The name of the file being uploaded. Must end with `.csv`, `.xls`, or `.xlsx`.

  **Example:** `"q1_transport_routes.xlsx"`
</ParamField>

<ParamField body="transport_direction" type="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)
</ParamField>

## Response

<ResponseField name="upload_url" type="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=..."`
</ResponseField>

<ResponseField name="file_id" type="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"`
</ResponseField>

<ResponseField name="file_name" type="string">
  Sanitized version of the file name (alphanumeric characters only, extension preserved).

  **Example:** `"q1_transport_routes"`
</ResponseField>

<ResponseField name="destination_file_key" type="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"`
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable confirmation.

  **Example:** `"Upload pre-signed url generated"`
</ResponseField>

## Example

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  # 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
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  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")
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  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);
  ```
</CodeGroup>

### Successful Response (Step 1)

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "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:

1. Rows are parsed from the spreadsheet.
2. Origin and destination addresses are geocoded.
3. Emission factors are resolved and CO2e values are calculated.

Poll for completion by listing transport routes filtered by `file_id`:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
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}"
```

Route statuses move through `pending` → `active` (success) or `error` as processing completes.

## Common Errors

### 401 Unauthorized

**Cause:** Missing or invalid API key / Bearer token.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "detail": "Invalid API key",
  "code": "INVALID_API_KEY"
}
```

### 422 Unprocessable Entity

**Cause:** `file_name` has an unsupported extension, or `transport_direction` is invalid.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "detail": [
    {
      "loc": ["body", "file_name"],
      "msg": "file_name must end with .csv, .xls, or .xlsx",
      "type": "value_error"
    }
  ]
}
```

**Solution:** Ensure `file_name` ends with `.csv`, `.xls`, or `.xlsx`, and that `transport_direction` is `downstream` or `upstream`.

### S3 PUT Errors

If the S3 PUT returns a `403 Forbidden`, the presigned URL may have expired. Request a new URL and retry.

## Related Endpoints

<CardGroup cols={2}>
  <Card title="Direct File Upload" icon="arrow-up-from-bracket" href="/api-reference/transport/upload">
    Upload smaller files directly through the API (multipart/form-data)
  </Card>

  <Card title="List Transport Routes" icon="list" href="/api-reference/transport/get">
    Retrieve transport routes, filter by file\_id to track batch processing
  </Card>

  <Card title="Transport Combinations" icon="shuffle" href="/api-reference/transport/combinations">
    Explore valid transport type and method combinations before creating routes
  </Card>

  <Card title="Transport Overview" icon="truck" href="/api-reference/transport/overview">
    Learn the full Transport API data model and workflow
  </Card>
</CardGroup>
