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

# File Uploads

> To support file uploads of any size and type, Scholarly uses a 3-step process for uploading files.

Scholarly utilizes a novel, 3-step process for uploading files into a user's "My Documents" section which requires a few requests in sequence to successfully upload a file.

Scholarly requires this to ensure that file uploads can complete, regardless of connection speed. Scholarly has customers send files directly to Amazon S3 using a secure method.

This method is as follows:

0. [API clients create a file object and get credentials for the upload.](/api-reference/file-upload-requests/create-a-file-upload-request-initiate-direct-upload) The response contains authentication information for the next step.
1. API clients then upload the file directly to Amazon S3.
2. API clients then confirm the upload with a final request.

By uploading the file directly to S3 in Step 2, this ensures the smooth operation of the Scholarly API servers.

Here are some code snippets in different languages for handling this 3-step process.

If you have trouble with this, please reach out to your Scholarly contact and we can pair program a solution in the language you're using.

<CodeGroup>
  ```ruby scholarly_file_upload.rb theme={null}
  require "net/http"
  require "json"
  require "uri"
  require "digest"
  require "base64"

  class ScholarlyFileUploader
    BASE_URL = "https://api.scholarlysoftware.com"

    def initialize(api_token)
      @api_token = api_token
    end

    # Step 1: Initiate the direct upload
    def initiate_upload(folder_id:, file_path:, content_type: "application/octet-stream")
      file_content = File.binread(file_path)
      file_name = File.basename(file_path)
      file_size = file_content.bytesize
      checksum = Base64.strict_encode64(Digest::MD5.digest(file_content))

      uri = URI("#{BASE_URL}/api/v1/folders/#{folder_id}/file_upload_requests")

      request_body = {
        data: {
          type: "file",
          attributes: {
            name: file_name,
            size: file_size,
            checksum: checksum,
            content_type: content_type
          }
        }
      }

      response = make_request(:post, uri, request_body)

      {
        file_id: response.dig("data", "id"),
        upload_url: response.dig("meta", "direct_upload", "url"),
        upload_headers: response.dig("meta", "direct_upload", "headers"),
        blob_signed_id: response.dig("meta", "direct_upload", "blob_signed_id"),
        file_content: file_content
      }
    end

    # Step 2: Upload file directly to S3
    def upload_to_s3(upload_url:, upload_headers:, file_content:)
      uri = URI(upload_url)

      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = true

      request = Net::HTTP::Put.new(uri)
      upload_headers.each { |key, value| request[key] = value }
      request["Content-Length"] = file_content.bytesize.to_s
      request.body = file_content

      response = http.request(request)

      unless response.is_a?(Net::HTTPSuccess)
        raise "S3 upload failed: #{response.code} - #{response.body}"
      end

      true
    end

    # Step 3: Confirm the upload
    def confirm_upload(file_id:, blob_signed_id:)
      uri = URI("#{BASE_URL}/api/v1/file_upload_requests/#{file_id}/confirm")

      request_body = {blob_signed_id: blob_signed_id}

      make_request(:post, uri, request_body)
    end

    # Convenience method to upload a file in one call
    def upload_file(folder_id:, file_path:, content_type: "application/octet-stream")
      # Step 1: Initiate
      upload_info = initiate_upload(
        folder_id: folder_id,
        file_path: file_path,
        content_type: content_type
      )

      puts "Initiated upload for file ID: #{upload_info[:file_id]}"

      # Step 2: Upload to S3
      upload_to_s3(
        upload_url: upload_info[:upload_url],
        upload_headers: upload_info[:upload_headers],
        file_content: upload_info[:file_content]
      )

      puts "Uploaded to S3 successfully"

      # Step 3: Confirm
      result = confirm_upload(
        file_id: upload_info[:file_id],
        blob_signed_id: upload_info[:blob_signed_id]
      )

      puts "Upload confirmed. File state: #{result.dig("data", "attributes", "state")}"

      result
    end

    private

    def make_request(method, uri, body = nil)
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = true

      request = case method
      when :post then Net::HTTP::Post.new(uri)
      when :get then Net::HTTP::Get.new(uri)
      when :put then Net::HTTP::Put.new(uri)
      end

      request["Authorization"] = "Bearer #{@api_token}"
      request["Content-Type"] = "application/vnd.api+json"
      request["Accept"] = "application/vnd.api+json"

      request.body = body.to_json if body

      response = http.request(request)

      unless response.is_a?(Net::HTTPSuccess)
        raise "API request failed: #{response.code} - #{response.body}"
      end

      JSON.parse(response.body)
    end
  end

  # Usage example:
  if __FILE__ == $0
    api_token = ENV.fetch("SCHOLARLY_API_TOKEN")
    folder_id = "uuid-of-the-folder"
    file_path = "/path/to/your/file.md"

    uploader = ScholarlyFileUploader.new(api_token)

    begin
      result = uploader.upload_file(
        folder_id: folder_id,
        file_path: file_path,
        content_type: "text/plain"
      )

      puts "\nUpload complete!"
      puts "File ID: #{result.dig("data", "id")}"
      puts "File URL: #{result.dig("data", "attributes", "url")}"
      puts "Download link: #{result.dig("data", "links", "download")}"
    rescue => e
      puts "Error: #{e.message}"
      exit 1
    end
  end
  ```

  ```javascript scholarly_file_upload.js theme={null}
  const crypto = require('crypto');
  const fs = require('fs');
  const path = require('path');

  class ScholarlyFileUploader {
    constructor(apiToken) {
      this.apiToken = apiToken;
      this.baseUrl = 'https://api.scholarlysoftware.com';
    }

    // Step 1: Initiate the direct upload
    async initiateUpload({ folderId, filePath, contentType = 'application/octet-stream' }) {
      const fileContent = fs.readFileSync(filePath);
      const fileName = path.basename(filePath);
      const fileSize = fileContent.length;
      const checksum = crypto.createHash('md5').update(fileContent).digest('base64');

      const requestBody = {
        data: {
          type: 'file',
          attributes: {
            name: fileName,
            size: fileSize,
            checksum: checksum,
            content_type: contentType
          }
        }
      };

      const response = await this.makeRequest(
        'POST',
        `/api/v1/folders/${folderId}/file_upload_requests`,
        requestBody
      );

      return {
        fileId: response.data?.id,
        uploadUrl: response.meta?.direct_upload?.url,
        uploadHeaders: response.meta?.direct_upload?.headers,
        blobSignedId: response.meta?.direct_upload?.blob_signed_id,
        fileContent: fileContent
      };
    }

    // Step 2: Upload file directly to S3
    async uploadToS3({ uploadUrl, uploadHeaders, fileContent }) {
      const response = await fetch(uploadUrl, {
        method: 'PUT',
        headers: {
          ...uploadHeaders,
          'Content-Length': fileContent.length.toString()
        },
        body: fileContent
      });

      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(`S3 upload failed: ${response.status} - ${errorText}`);
      }

      return true;
    }

    // Step 3: Confirm the upload
    async confirmUpload({ fileId, blobSignedId }) {
      const requestBody = { blob_signed_id: blobSignedId };

      return await this.makeRequest(
        'POST',
        `/api/v1/file_upload_requests/${fileId}/confirm`,
        requestBody
      );
    }

    // Convenience method to upload a file in one call
    async uploadFile({ folderId, filePath, contentType = 'application/octet-stream' }) {
      // Step 1: Initiate
      const uploadInfo = await this.initiateUpload({
        folderId,
        filePath,
        contentType
      });

      console.log(`Initiated upload for file ID: ${uploadInfo.fileId}`);

      // Step 2: Upload to S3
      await this.uploadToS3({
        uploadUrl: uploadInfo.uploadUrl,
        uploadHeaders: uploadInfo.uploadHeaders,
        fileContent: uploadInfo.fileContent
      });

      console.log('Uploaded to S3 successfully');

      // Step 3: Confirm
      const result = await this.confirmUpload({
        fileId: uploadInfo.fileId,
        blobSignedId: uploadInfo.blobSignedId
      });

      console.log(`Upload confirmed. File state: ${result.data?.attributes?.state}`);

      return result;
    }

    async makeRequest(method, endpoint, body = null) {
      const url = `${this.baseUrl}${endpoint}`;

      const options = {
        method,
        headers: {
          'Authorization': `Bearer ${this.apiToken}`,
          'Content-Type': 'application/vnd.api+json',
          'Accept': 'application/vnd.api+json'
        }
      };

      if (body) {
        options.body = JSON.stringify(body);
      }

      const response = await fetch(url, options);

      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(`API request failed: ${response.status} - ${errorText}`);
      }

      return await response.json();
    }
  }

  async function nodeExample() {
    const apiToken = process.env.SCHOLARLY_API_TOKEN;
    const folderId = 'uuid-of-your-folder';
    const filePath = '/path/to/your/file.md';

    const uploader = new ScholarlyFileUploader(apiToken);

    try {
      const result = await uploader.uploadFile({
        folderId,
        filePath,
        contentType: 'text/plain'
      });

      console.log('\nUpload complete!');
      console.log(`File ID: ${result.data?.id}`);
      console.log(`File URL: ${result.data?.attributes?.url}`);
      console.log(`Download link: ${result.data?.links?.download}`);
    } catch (error) {
      console.error('Error:', error.message);
      process.exit(1);
    }
  }
  ```

  ```python scholarly_file_upload.py theme={null}
  import hashlib
  import base64
  import json
  import os
  from pathlib import Path
  from typing import Optional
  import requests


  class ScholarlyFileUploader:
      def __init__(self, api_token: str):
          self.api_token = api_token
          self.base_url = "https://api.scholarlysoftware.com"

      def initiate_upload(
          self,
          folder_id: str,
          file_path: str,
          content_type: str = "application/octet-stream"
      ) -> dict:
          """Step 1: Initiate the direct upload."""
          file_path = Path(file_path)
          file_content = file_path.read_bytes()
          file_name = file_path.name
          file_size = len(file_content)
          checksum = base64.b64encode(hashlib.md5(file_content).digest()).decode("utf-8")

          request_body = {
              "data": {
                  "type": "file",
                  "attributes": {
                      "name": file_name,
                      "size": file_size,
                      "checksum": checksum,
                      "content_type": content_type
                  }
              }
          }

          response = self._make_request(
              "POST",
              f"/api/v1/folders/{folder_id}/file_upload_requests",
              request_body
          )

          return {
              "file_id": response.get("data", {}).get("id"),
              "upload_url": response.get("meta", {}).get("direct_upload", {}).get("url"),
              "upload_headers": response.get("meta", {}).get("direct_upload", {}).get("headers"),
              "blob_signed_id": response.get("meta", {}).get("direct_upload", {}).get("blob_signed_id"),
              "file_content": file_content
          }

      def upload_to_s3(
          self,
          upload_url: str,
          upload_headers: dict,
          file_content: bytes
      ) -> bool:
          """Step 2: Upload file directly to S3."""
          headers = {
              **upload_headers,
              "Content-Length": str(len(file_content))
          }

          response = requests.put(upload_url, headers=headers, data=file_content)

          if not response.ok:
              raise Exception(f"S3 upload failed: {response.status_code} - {response.text}")

          return True

      def confirm_upload(self, file_id: str, blob_signed_id: str) -> dict:
          """Step 3: Confirm the upload."""
          request_body = {"blob_signed_id": blob_signed_id}

          return self._make_request(
              "POST",
              f"/api/v1/file_upload_requests/{file_id}/confirm",
              request_body
          )

      def upload_file(
          self,
          folder_id: str,
          file_path: str,
          content_type: str = "application/octet-stream"
      ) -> dict:
          """Convenience method to upload a file in one call."""
          # Step 1: Initiate
          upload_info = self.initiate_upload(
              folder_id=folder_id,
              file_path=file_path,
              content_type=content_type
          )

          print(f"Initiated upload for file ID: {upload_info['file_id']}")

          # Step 2: Upload to S3
          self.upload_to_s3(
              upload_url=upload_info["upload_url"],
              upload_headers=upload_info["upload_headers"],
              file_content=upload_info["file_content"]
          )

          print("Uploaded to S3 successfully")

          # Step 3: Confirm
          result = self.confirm_upload(
              file_id=upload_info["file_id"],
              blob_signed_id=upload_info["blob_signed_id"]
          )

          state = result.get("data", {}).get("attributes", {}).get("state")
          print(f"Upload confirmed. File state: {state}")

          return result

      def _make_request(
          self,
          method: str,
          endpoint: str,
          body: Optional[dict] = None
      ) -> dict:
          url = f"{self.base_url}{endpoint}"

          headers = {
              "Authorization": f"Bearer {self.api_token}",
              "Content-Type": "application/vnd.api+json",
              "Accept": "application/vnd.api+json"
          }

          response = requests.request(
              method=method,
              url=url,
              headers=headers,
              json=body
          )

          if not response.ok:
              raise Exception(f"API request failed: {response.status_code} - {response.text}")

          return response.json()


  # Usage example
  if __name__ == "__main__":
      api_token = os.environ.get("SCHOLARLY_API_TOKEN")
      folder_id = "uuid-of-your-folder"
      file_path = "/path/to/your/file.md"

      uploader = ScholarlyFileUploader(api_token)

      try:
          result = uploader.upload_file(
              folder_id=folder_id,
              file_path=file_path,
              content_type="text/plain"
          )

          print("\nUpload complete!")
          print(f"File ID: {result.get('data', {}).get('id')}")
          print(f"File URL: {result.get('data', {}).get('attributes', {}).get('url')}")
          print(f"Download link: {result.get('data', {}).get('links', {}).get('download')}")

      except Exception as e:
          print(f"Error: {e}")
          exit(1)

  ```
</CodeGroup>
