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

# Download File

> Download a file by redirecting to a signed URL

<Note>
  This endpoint requires authentication and the `viewFiles` permission.
  The endpoint returns a 302 redirect to a signed URL that expires in 5 minutes.
</Note>

### Path Parameters

<ParamField path="provider" type="string" required>
  The storage provider. One of:

  * `s3`: Amazon S3 storage
  * `local`: Local file storage
</ParamField>

<ParamField path="key" type="string" required>
  The storage key/path of the file
</ParamField>

### Response

Returns HTTP 302 Found with a Location header containing a signed URL for downloading the file.

### Error Responses

<ResponseField name="error" type="object">
  Error details when the request fails

  <Expandable title="Error Object">
    <ResponseField name="status" type="integer">
      HTTP status code
    </ResponseField>

    <ResponseField name="reason" type="string">
      Error message explaining why the request failed
    </ResponseField>
  </Expandable>
</ResponseField>

<Note>
  Common error cases:

  * 400 Bad Request: Invalid provider or key
  * 401 Unauthorized: Missing or invalid access token
  * 403 Forbidden: Missing required permission
  * 404 Not Found: File not found
</Note>

<RequestExample>
  ```bash cURL theme={null}
  curl -L -X GET "https://api.tktchurch.com/v1/files/provider/s3/download/uploads/abc123.jpg" \
    -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
  ```

  ```javascript JavaScript theme={null}
  const downloadFile = async (accessToken, provider, key) => {
    const response = await fetch(
      `https://api.tktchurch.com/v1/files/provider/${provider}/download/${key}`,
      {
        headers: {
          'Authorization': `Bearer ${accessToken}`
        },
        redirect: 'follow'
      }
    );

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.reason || 'Failed to download file');
    }

    return response.blob();
  };

  // Usage
  try {
    const blob = await downloadFile(
      'eyJhbGciOiJIUzI1NiIs...',
      's3',
      'uploads/abc123.jpg'
    );
    
    // Create download link
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'example.jpg';
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
  } catch (error) {
    console.error('Error:', error.message);
  }
  ```

  ```swift Swift theme={null}
  func downloadFile(
      accessToken: String,
      provider: String,
      key: String
  ) async throws -> Data {
      var urlRequest = URLRequest(url: URL(string: "https://api.tktchurch.com/v1/files/provider/\(provider)/download/\(key)")!)
      urlRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
      
      let (data, response) = try await URLSession.shared.data(for: urlRequest)
      
      guard let httpResponse = response as? HTTPURLResponse else {
          throw URLError(.badServerResponse)
      }
      
      if httpResponse.statusCode != 200 {
          throw URLError(.badServerResponse)
      }
      
      return data
  }

  // Usage
  do {
      let data = try await downloadFile(
          accessToken: "eyJhbGciOiJIUzI1NiIs...",
          provider: "s3",
          key: "uploads/abc123.jpg"
      )
      
      // Save file
      let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
      let filePath = documentsPath.appendingPathComponent("example.jpg")
      try data.write(to: filePath)
      print("File downloaded to:", filePath)
  } catch {
      print("Error:", error)
  }
  ```

  ```kotlin Kotlin theme={null}
  suspend fun downloadFile(
      accessToken: String,
      provider: String,
      key: String
  ): ByteArray {
      val request = Request.Builder()
          .url("https://api.tktchurch.com/v1/files/provider/$provider/download/$key")
          .get()
          .header("Authorization", "Bearer $accessToken")
          .build()

      return withContext(Dispatchers.IO) {
          client.newCall(request).execute().use { response ->
              if (!response.isSuccessful) {
                  throw IOException("Unexpected response ${response.code}")
              }
              
              response.body?.bytes() ?: throw IOException("Empty response")
          }
      }
  }

  // Usage
  try {
      val bytes = downloadFile(
          "eyJhbGciOiJIUzI1NiIs...",
          "s3",
          "uploads/abc123.jpg"
      )
      
      // Save file
      File("example.jpg").writeBytes(bytes)
      println("File downloaded successfully")
  } catch (e: Exception) {
      println("Error: ${e.message}")
  }
  ```

  ```typescript React Native theme={null}
  const downloadFile = async (
    accessToken: string,
    provider: 's3' | 'local',
    key: string
  ): Promise<Blob> => {
    try {
      const response = await axios.get(
        `https://api.tktchurch.com/v1/files/provider/${provider}/download/${key}`,
        {
          headers: {
            'Authorization': `Bearer ${accessToken}`
          },
          responseType: 'blob',
          maxRedirects: 5
        }
      );
      return response.data;
    } catch (error) {
      if (axios.isAxiosError(error)) {
        throw new Error(error.response?.data?.reason || 'Failed to download file');
      }
      throw error;
    }
  };

  // Usage
  try {
    const blob = await downloadFile(
      'eyJhbGciOiJIUzI1NiIs...',
      's3',
      'uploads/abc123.jpg'
    );
    
    // Save file using react-native-fs
    const path = `${RNFS.DocumentDirectoryPath}/example.jpg`;
    await RNFS.writeFile(path, blob, 'base64');
    console.log('File downloaded to:', path);
  } catch (error) {
    console.error('Error:', error.message);
  }
  ```
</RequestExample>

<ResponseExample>
  ```http 302 Found theme={null}
  HTTP/1.1 302 Found
  Location: https://storage.example.com/uploads/abc123.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...
  ```

  ```json 400 Bad Request theme={null}
  {
    "error": {
      "status": 400,
      "reason": "Invalid provider specified"
    }
  }
  ```

  ```json 401 Unauthorized theme={null}
  {
    "error": {
      "status": 401,
      "reason": "Invalid or expired access token"
    }
  }
  ```

  ```json 403 Forbidden theme={null}
  {
    "error": {
      "status": 403,
      "reason": "Missing required permission: viewFiles"
    }
  }
  ```

  ```json 404 Not Found theme={null}
  {
    "error": {
      "status": 404,
      "reason": "File not found"
    }
  }
  ```
</ResponseExample>
