- API clients create a file object and get credentials for the upload. The response contains authentication information for the next step.
- API clients then upload the file directly to Amazon S3.
- API clients then confirm the upload with a final request.
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
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);
}
}
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)