> ## 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.

# Upload File

> Upload a file to the specified storage provider

<Note>
  This endpoint requires authentication and the `uploadFiles` permission.
  The maximum file size is 10MB.
</Note>

### Path Parameters

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

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

### Request Headers

<ParamField header="Content-Type" type="string" required>
  The MIME type of the file being uploaded
</ParamField>

<ParamField header="Content-Disposition" type="string" required>
  Must include the filename parameter, e.g. `attachment; filename="example.jpg"`
</ParamField>

### Request Body

The request body should contain the raw file data.

### Response

<ResponseField name="id" type="string">
  File's unique identifier (UUID)
</ResponseField>

<ResponseField name="originalName" type="string">
  Original filename
</ResponseField>

<ResponseField name="url" type="string">
  URL to access the file
</ResponseField>

<ResponseField name="key" type="string">
  Storage key/path of the file
</ResponseField>

<ResponseField name="contentType" type="string">
  MIME type of the file
</ResponseField>

<ResponseField name="size" type="integer">
  File size in bytes
</ResponseField>

<ResponseField name="provider" type="string">
  Storage provider used (s3 or local)
</ResponseField>

<ResponseField name="providerMetadata" type="object" required={false}>
  Provider-specific metadata
</ResponseField>

<ResponseField name="uploadedBy" type="string">
  UUID of the user who uploaded the file
</ResponseField>

<ResponseField name="createdAt" type="string">
  Upload timestamp in ISO 8601 format
</ResponseField>

<ResponseField name="updatedAt" type="string">
  Last update timestamp in ISO 8601 format
</ResponseField>

### 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 missing filename
  * 401 Unauthorized: Missing or invalid access token
  * 403 Forbidden: Missing required permission
  * 413 Payload Too Large: File exceeds size limit
</Note>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST "https://api.tktchurch.com/v1/files/provider/s3/upload" \
    -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
    -H "Content-Type: image/jpeg" \
    -H "Content-Disposition: attachment; filename=\"example.jpg\"" \
    --data-binary "@/path/to/example.jpg"
  ```

  ```javascript JavaScript theme={null}
  const uploadFile = async (accessToken, provider, file) => {
    const formData = new FormData();
    formData.append('file', file);

    const response = await fetch(
      `https://api.tktchurch.com/v1/files/provider/${provider}/upload`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Content-Disposition': `attachment; filename="${file.name}"`
        },
        body: file
      }
    );

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

    return response.json();
  };

  // Usage
  try {
    const file = new File(['...'], 'example.jpg', { type: 'image/jpeg' });
    const result = await uploadFile(
      'eyJhbGciOiJIUzI1NiIs...',
      's3',
      file
    );
    console.log('Uploaded file:', result);
  } catch (error) {
    console.error('Error:', error.message);
  }
  ```

  ```swift Swift theme={null}
  func uploadFile(
      accessToken: String,
      provider: String,
      fileURL: URL
  ) async throws -> File {
      let filename = fileURL.lastPathComponent
      let data = try Data(contentsOf: fileURL)
      let contentType = UTType(filenameExtension: fileURL.pathExtension)?.preferredMIMEType ?? "application/octet-stream"
      
      var urlRequest = URLRequest(url: URL(string: "https://api.tktchurch.com/v1/files/provider/\(provider)/upload")!)
      urlRequest.httpMethod = "POST"
      urlRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
      urlRequest.setValue(contentType, forHTTPHeaderField: "Content-Type")
      urlRequest.setValue("attachment; filename=\"\(filename)\"", forHTTPHeaderField: "Content-Disposition")
      urlRequest.httpBody = data
      
      let (responseData, 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 try JSONDecoder().decode(File.self, from: responseData)
  }

  // Usage
  do {
      let fileURL = URL(fileURLWithPath: "/path/to/example.jpg")
      let result = try await uploadFile(
          accessToken: "eyJhbGciOiJIUzI1NiIs...",
          provider: "s3",
          fileURL: fileURL
      )
      print("Uploaded file:", result)
  } catch {
      print("Error:", error)
  }
  ```

  ```kotlin Kotlin theme={null}
  suspend fun uploadFile(
      accessToken: String,
      provider: String,
      file: File
  ): FileResponse {
      val requestBody = RequestBody.create(
          MediaType.parse(getMimeType(file) ?: "application/octet-stream"),
          file.readBytes()
      )

      val request = Request.Builder()
          .url("https://api.tktchurch.com/v1/files/provider/$provider/upload")
          .post(requestBody)
          .header("Authorization", "Bearer $accessToken")
          .header("Content-Type", getMimeType(file) ?: "application/octet-stream")
          .header("Content-Disposition", "attachment; filename=\"${file.name}\"")
          .build()

      return withContext(Dispatchers.IO) {
          client.newCall(request).execute().use { response ->
              if (!response.isSuccessful) {
                  throw IOException("Unexpected response ${response.code}")
              }
              
              val body = response.body?.string() ?: throw IOException("Empty response")
              Json.decodeFromString<FileResponse>(body)
          }
      }
  }

  private fun getMimeType(file: File): String? {
      return URLConnection.guessContentTypeFromName(file.name)
  }

  // Usage
  try {
      val file = File("/path/to/example.jpg")
      val result = uploadFile(
          "eyJhbGciOiJIUzI1NiIs...",
          "s3",
          file
      )
      println("Uploaded file: $result")
  } catch (e: Exception) {
      println("Error: ${e.message}")
  }
  ```

  ```typescript React Native theme={null}
  interface FileResponse {
    id: string;
    originalName: string;
    url: string;
    key: string;
    contentType: string;
    size: number;
    provider: 's3' | 'local';
    providerMetadata?: Record<string, string>;
    uploadedBy: string;
    createdAt: string;
    updatedAt: string;
  }

  const uploadFile = async (
    accessToken: string,
    provider: 's3' | 'local',
    file: File
  ): Promise<FileResponse> => {
    try {
      const response = await axios.post<FileResponse>(
        `https://api.tktchurch.com/v1/files/provider/${provider}/upload`,
        file,
        {
          headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': file.type,
            'Content-Disposition': `attachment; filename="${file.name}"`
          },
        }
      );
      return response.data;
    } catch (error) {
      if (axios.isAxiosError(error)) {
        throw new Error(error.response?.data?.reason || 'Failed to upload file');
      }
      throw error;
    }
  };

  // Usage
  try {
    const file = new File(['...'], 'example.jpg', { type: 'image/jpeg' });
    const result = await uploadFile(
      'eyJhbGciOiJIUzI1NiIs...',
      's3',
      file
    );
    console.log('Uploaded file:', result);
  } catch (error) {
    console.error('Error:', error.message);
  }
  ```
</RequestExample>

<ResponseExample>
  ```json 200 Success theme={null}
  {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "originalName": "example.jpg",
    "url": "https://storage.example.com/uploads/abc123.jpg",
    "key": "uploads/abc123.jpg",
    "contentType": "image/jpeg",
    "size": 1048576,
    "provider": "s3",
    "providerMetadata": {
      "bucket": "my-bucket",
      "region": "us-east-1"
    },
    "uploadedBy": "123e4567-e89b-12d3-a456-426614174001",
    "createdAt": "2024-01-20T15:00:00Z",
    "updatedAt": "2024-01-20T15:00:00Z"
  }
  ```

  ```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: uploadFiles"
    }
  }
  ```

  ```json 413 Payload Too Large theme={null}
  {
    "error": {
      "status": 413,
      "reason": "File size exceeds limit of 10MB"
    }
  }
  ```
</ResponseExample>
