Skip to content

Repository files navigation

Uploadcare Ruby SDK

license Build Status Uploadcare stack on StackShare

uploadcare-ruby is a framework-agnostic client for the Uploadcare Upload API and REST API.

The gem is built around:

Requirements

  • Ruby 3.3+

Compatibility

This gem is intended for plain Ruby applications and for framework integrations built on top of it.

  • Use explicit Uploadcare::Client instances when you need multiple accounts in one process.
  • Use Uploadcare.configure and Uploadcare.client when one global default client is enough.
  • Use client.api.rest and client.api.upload when you want endpoint-level parity with the official API references.

Installation

Add the gem to your Gemfile:

gem "uploadcare-ruby"

Then install:

bundle

Set credentials with environment variables:

export UPLOADCARE_PUBLIC_KEY=your_public_key
export UPLOADCARE_SECRET_KEY=your_secret_key

Design

The gem has two public layers:

Convenience layer

This is the default API you should use in applications:

  • client.files
  • client.groups
  • client.uploads
  • client.project
  • client.webhooks
  • client.file_metadata
  • client.file_tags
  • client.addons
  • client.conversions

This layer returns resources and collections, and it raises typed exceptions on failure.

Raw parity layer

This layer mirrors Uploadcare’s REST and Upload APIs:

  • client.api.rest
  • client.api.upload

This layer returns Uploadcare::Result objects so you can inspect success and failure explicitly.

That split keeps app code clean without losing full API coverage.

Quick Start

require "uploadcare"

client = Uploadcare::Client.new(
  public_key: ENV.fetch("UPLOADCARE_PUBLIC_KEY"),
  secret_key: ENV.fetch("UPLOADCARE_SECRET_KEY")
)

file = File.open("photo.jpg", "rb") do |io|
  client.files.upload(io, store: true)
end

puts file.uuid
puts file.cdn_url

You can also configure a default global client:

Uploadcare.configure do |config|
  config.public_key = ENV.fetch("UPLOADCARE_PUBLIC_KEY")
  config.secret_key = ENV.fetch("UPLOADCARE_SECRET_KEY")
end

file = File.open("photo.jpg", "rb") do |io|
  Uploadcare.files.upload(io, store: true)
end

The recommended style is explicit Uploadcare::Client instances. Global configuration is best treated as a default.

Example Usage

This is the shortest end-to-end flow for the main public API:

require "uploadcare"

client = Uploadcare::Client.new(
  public_key: ENV.fetch("UPLOADCARE_PUBLIC_KEY"),
  secret_key: ENV.fetch("UPLOADCARE_SECRET_KEY")
)

file = File.open("photo.jpg", "rb") do |io|
  client.files.upload(io, store: true)
end

group = client.groups.create(uuids: [file.uuid])

puts file.uuid
puts file.cdn_url
puts group.id
puts group.cdn_url

Configuration

Use Uploadcare.configure to set process-wide defaults:

Uploadcare.configure do |config|
  config.public_key = "public_key"
  config.secret_key = "secret_key"
  config.auth_type = "Uploadcare"
  config.use_subdomains = false
end

Or build configuration objects directly:

base_config = Uploadcare::Configuration.new(
  public_key: "public_key",
  secret_key: "secret_key"
)

client = Uploadcare::Client.new(config: base_config)

Configuration objects are copyable:

account_a = Uploadcare::Client.new(config: base_config.with(public_key: "pk-a", secret_key: "sk-a"))
account_b = Uploadcare::Client.new(config: base_config.with(public_key: "pk-b", secret_key: "sk-b"))

Common configuration options:

  • public_key
  • secret_key
  • auth_type
  • multipart_size_threshold
  • multipart_chunk_size
  • upload_threads
  • upload_timeout
  • max_upload_retries
  • sign_uploads
  • upload_signature_lifetime
  • use_subdomains
  • cdn_base_postfix
  • default_cdn_base

CDN helpers:

Uploadcare.configure do |config|
  config.use_subdomains = true
  config.cdn_base_postfix = "https://ucarecd.net/"
  config.default_cdn_base = "https://ucarecdn.com/"
end

Uploadcare.configuration.custom_cname
Uploadcare.configuration.cdn_base

Multi-Account Usage

The gem is designed to support multiple Uploadcare projects in the same process:

primary = Uploadcare::Client.new(public_key: "pk-1", secret_key: "sk-1")
secondary = Uploadcare::Client.new(public_key: "pk-2", secret_key: "sk-2")

primary_file = primary.files.find(uuid: "uuid-1")
secondary_file = secondary.files.find(uuid: "uuid-2")

You can also derive temporary variants from an existing client:

admin_client = primary.with(secret_key: "different-secret")

Resource objects retain their client context, so subsequent instance operations stay bound to the correct account.

Uploads

Smart upload

client.uploads.upload accepts:

  • an IO or file object
  • an array of IO or file objects
  • an HTTP or HTTPS URL string
file = File.open("photo.jpg", "rb") do |io|
  client.uploads.upload(io, store: true)
end

remote_file = client.uploads.upload("https://example.com/image.jpg", store: true)

Single file upload

file = File.open("photo.jpg", "rb") do |io|
  client.files.upload(
    io,
    store: true,
    metadata: { subsystem: "avatars" },
    tags: ["avatar", "profile"]
  )
end

Multiple file upload

files = [
  File.open("photo-1.jpg", "rb"),
  File.open("photo-2.jpg", "rb")
]

uploaded = client.uploads.upload(files, store: true, tags: ["gallery", "batch"])

files.each(&:close)

Upload from URL

Synchronous:

file = client.files.upload_from_url(
  "https://example.com/image.jpg",
  store: true,
  tags: ["remote", "example"]
)

Async:

job = client.uploads.upload_from_url(url: "https://example.com/image.jpg", async: true, store: true)
status = client.uploads.upload_from_url_status(token: job.fetch("token"))

When async mode is enabled, the convenience layer returns the raw status token hash because the file does not exist yet.

Polling options for synchronous URL uploads:

  • poll_interval (default: 1) initial status polling interval in seconds
  • poll_max_interval (default: 10) maximum polling interval in seconds
  • poll_timeout (default: 300) maximum total polling time in seconds

Multipart upload with progress

File.open("large-video.mp4", "rb") do |io|
  file = client.uploads.multipart_upload(
    file: io,
    store: true,
    threads: 4,
    tags: ["video", "multipart"]
  ) do |progress|
    uploaded = progress[:uploaded]
    total = progress[:total]
    puts "#{uploaded}/#{total}"
  end

  puts file.uuid
end

Signed uploads

You can enable signed uploads globally:

client = Uploadcare::Client.new(
  public_key: "public",
  secret_key: "secret",
  sign_uploads: true
)

Or pass explicit signature data per request:

File.open("photo.jpg", "rb") do |io|
  client.files.upload(io, signature: "signature", expire: 1_900_000_000)
end

Upload options

Common upload options:

  • store: true | false | "auto"
  • metadata: { key: value }
  • tags: ["tag-1", "tag_2"]
  • signature: "..."
  • expire: unix_timestamp
  • async: true for URL uploads
  • threads: and part_size: for multipart uploads

If you prefer the older top-level style, the same flows can still be written through the global client:

Uploadcare.configure do |config|
  config.public_key = ENV.fetch("UPLOADCARE_PUBLIC_KEY")
  config.secret_key = ENV.fetch("UPLOADCARE_SECRET_KEY")
end

file = File.open("photo.jpg", "rb") do |io|
  Uploadcare.files.upload(io, store: true)
end

Files

Find a file

file = client.files.find(uuid: "file-uuid")

List files

files = client.files.list(limit: 100)
files.each { |file| puts file.uuid }

List responses are Uploadcare::Collections::Paginated:

files.next_page
files.previous_page
files.all

Filters and API parameters can still be passed through:

files = client.files.list(stored: true, removed: false, limit: 100)

Search files

Search across filenames, file UUIDs, metadata, and detected MIME types:

matches = client.files.search(
  query: "invoice",
  is_image: false,
  sort: ["-datetime_uploaded"],
  limit: 20
)

puts "Found #{matches.total} files"
matches.each do |file|
  puts file.original_filename
  puts file.highlight
end

Search accepts full-text query and field-specific phrase criteria, exact matches, ranges, and tag filters. All top-level criteria are combined with AND:

matches = client.files.search(
  phrase: { original_filename: "report" },
  exact: {
    detected_mime_type: ["application/pdf"],
    "metadata[department]" => ["finance"]
  },
  datetime_uploaded: { gte: "2026-01-01T00:00:00Z" },
  size: { lte: 10 * 1024 * 1024 },
  tags: { all: ["approved"], none: ["archived"] },
  include: "appdata"
)

limit, offset, and include are sent as URL query parameters; search criteria are sent in the JSON body. Search responses are Uploadcare::Collections::FileSearchResult objects and support next_page, previous_page, and all like ordinary file lists. Subsequent pages automatically resend the original search criteria. Full-text values must be at least four characters; use exact for shorter values. A field cannot appear in both phrase and exact, and every request needs at least one search condition. limit accepts 1–100 results, while offset + limit cannot exceed 1,000. For filter-only searches, pass an explicit sort for deterministic ordering. fuzziness: true enables typo-tolerant matching but increases latency. Newly uploaded files may take a short time to appear in the search index.

Resource operations

file.store
file.delete
file.reload
file.reload(params: { include: "appdata" })

Batch operations

result = client.files.batch_store(uuids: ["uuid-1", "uuid-2"])

puts result.status
puts result.result.map(&:uuid)
puts result.problems

The same shape applies to client.files.batch_delete.

Copy operations

copied = client.files.copy_to_local(source: file.uuid, options: { store: true })
remote_url = client.files.copy_to_remote(source: file.uuid, target: "custom_storage")

Instance-level variants are also available:

copied = file.copy_to_local(options: { store: true })
remote_url = file.copy_to_remote(target: "custom_storage")

File responses expose the ordered tag list through file.tags when the field is present.

File Tags

Tags can be attached during direct, URL, batch, and multipart uploads with the tags: option. The SDK normalizes tags to lowercase, strips surrounding whitespace, discards blank tags, removes duplicates while preserving order, and validates the platform limits. Upload responses do not include tags; use client.file_tags.list or reload the file through the REST API to read them.

Read or replace the complete tag list:

tags = client.file_tags.list(uuid: file.uuid)

change = client.file_tags.replace(
  uuid: file.uuid,
  tags: ["approved", "Summer"]
)

puts change.tags
puts change.added
puts change.deleted

Mutation responses expose the resulting ordered tags, the tags actually added, and the tags actually deleted. Replacing with the same normalized set is safe and reports no additions or deletions.

Add and delete tags atomically (deletions are applied first):

change = client.file_tags.update(
  uuid: file.uuid,
  add: ["featured"],
  delete: ["draft"]
)

Passing an empty array to replace clears all tags. Tags may contain Latin letters, digits, hyphens, underscores, and dots; each tag is limited to 100 characters and each file to 50 tags.

Groups

Create a group:

group = client.groups.create(uuids: ["uuid-1", "uuid-2"])

Find and list groups:

group = client.groups.find(group_id: "group-uuid~2")
groups = client.groups.list(limit: 50)

Delete a group:

group.delete

Useful group helpers:

group.cdn_url
group.file_cdn_urls

Project

Fetch the current project:

project = client.project.current

puts project.name
puts project.pub_key
puts project.collaborators

Metadata

client.file_metadata.update(uuid: file.uuid, key: "category", value: "avatar")
client.file_metadata.show(uuid: file.uuid, key: "category")
client.file_metadata.index(uuid: file.uuid)
client.file_metadata.delete(uuid: file.uuid, key: "category")

Uploadcare::FileMetadata is also available as a resource if you need to hold metadata state locally.

Webhooks

webhook = client.webhooks.create(
  target_url: "https://example.com/uploadcare",
  event: "file.uploaded",
  is_active: true
)

client.webhooks.list
client.webhooks.update(id: webhook.id, is_active: false)
client.webhooks.delete(target_url: webhook.target_url)

Add-ons

execution = client.addons.aws_rekognition_detect_labels(uuid: file.uuid)
client.addons.aws_rekognition_detect_labels_status(request_id: execution.request_id)

scan = client.addons.uc_clamav_virus_scan(uuid: file.uuid)
client.addons.uc_clamav_virus_scan_status(request_id: scan.request_id)

background = client.addons.remove_bg(uuid: file.uuid)
client.addons.remove_bg_status(request_id: background.request_id)

These methods return Uploadcare::AddonExecution resources.

Conversions

Document conversions:

info = client.conversions.documents.info(uuid: file.uuid)
job = client.conversions.documents.convert(uuid: file.uuid, format: :pdf)
status = client.conversions.documents.status(token: job.fetch("result").first.fetch("token"))

Video conversions:

job = client.conversions.videos.convert(uuid: file.uuid, format: :webm, quality: :normal)
status = client.conversions.videos.status(token: job.result.first.fetch("token"))

Document conversion convert returns the API response hash.

Video conversion convert returns a Uploadcare::VideoConversion resource.

Secure Delivery

The gem includes signed URL generators for delivery workflows.

generator = Uploadcare::SignedUrlGenerators::AkamaiGenerator.new(
  cdn_host: "example.com",
  secret_key: "your_hex_encoded_akamai_secret"
)

signed_url = generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3")

Custom ACL and wildcard examples:

generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3", "/*")
generator.generate_url("a7d5645e-5cd7-4046-819f-a6a2933bafe3", wildcard: true)

Errors and Results

The convenience layer raises exceptions:

  • Uploadcare::Exception::RequestError
  • Uploadcare::Exception::InvalidRequestError
  • Uploadcare::Exception::NotFoundError
  • Uploadcare::Exception::UploadError
  • Uploadcare::Exception::MultipartUploadError
  • Uploadcare::Exception::UploadTimeoutError
  • Uploadcare::Exception::ThrottleError

Example:

begin
  client.files.find(uuid: "missing")
rescue Uploadcare::Exception::NotFoundError => e
  warn e.message
end

The raw API layer returns Uploadcare::Result:

result = client.api.rest.files.info(uuid: "file-uuid")

if result.success?
  puts result.success
else
  warn result.error_message
end

Request Options

Most API calls accept request_options: and pass them to the HTTP layer.

Example:

client.files.find(uuid: "file-uuid", request_options: { timeout: 10 })

Use this when you need per-request timeout control without changing the client’s default configuration.

Raw API Access

The gem exposes full endpoint-level access through client.api.

REST API:

client.api.rest.files.list(params: { limit: 10 })
client.api.rest.files.info(uuid: "file-uuid")
client.api.rest.project.show
client.api.rest.webhooks.list

Upload API:

File.open("photo.jpg", "rb") do |io|
  client.api.upload.files.direct(file: io, store: true, tags: ["photo", "example"])
end

client.api.upload.files.from_url(
  source_url: "https://example.com/image.jpg",
  async: true,
  tags: ["remote", "example"]
)
client.api.upload.groups.create(files: ["uuid-1", "uuid-2"])

Use this layer when you want exact control over the documented endpoints or when you are wrapping the gem from another library.

The raw layer is part of the public surface, but it is intentionally less promoted than client.files, client.groups, and the other convenience accessors.

Examples

Run examples with project-managed Ruby:

mise exec -- ruby api_examples/rest_api/get_project.rb
mise exec -- ruby examples/simple_upload.rb spec/fixtures/kitten.jpeg

Upgrading from v4.x

See:

About

Ruby API client that handles uploads and further operations with files by wrapping Uploadcare Upload and REST APIs.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

40 stars

Watchers

8 watching

Forks

Releases

Packages

Used by

Contributors

Languages