Official Ruby SDK for the OctaSpace API.
- Resource-oriented API —
client.nodes.list,client.services.session(uuid).stop - Keep-alive mode — persistent connections via
faraday-net_http_persistent+connection_pool - URL rotation / failover — round-robin across multiple endpoints with per-URL cooldown
- Retry with exponential backoff + jitter — configurable retries on transient failures
- Typed error hierarchy — 12 exception classes mapped from HTTP status codes
on_request/on_responsehooks — for logging, tracing, APM- Rails integration — Railtie, shared client, graceful shutdown at_exit
- Playground app —
bin/playgroundwith live diagnostics page - Ruby ≥ 3.2, Rails 7.1 / 7.2 / 8.0
# Gemfile
gem "octaspace"
# Optional — required for keep_alive: true
gem "faraday-net_http_persistent"
gem "connection_pool"bundle installrequire "octaspace"
# Public endpoints (no API key required)
client = OctaSpace::Client.new
client.network.info # GET /network
# Authenticated endpoints
client = OctaSpace::Client.new(api_key: ENV["OCTA_API_KEY"])
# Accounts
client.accounts.profile # GET /accounts
client.accounts.balance # GET /accounts/balance
# Nodes
client.nodes.list # GET /nodes
client.nodes.find(123) # GET /nodes/:id
client.nodes.reboot(123) # GET /nodes/:id/reboot
client.nodes.update_prices(123, gpu_hour: 0.5) # PATCH /nodes/:id/prices
# Sessions (list)
client.sessions.list # GET /sessions
# Session proxy — operations on a specific session
session = client.services.session("uuid-here")
session.info # GET /services/:uuid/info
session.logs # GET /services/:uuid/logs
session.stop(score: 5) # POST /services/:uuid/stop
# Services
client.services.mr.list # GET /services/mr
client.services.mr.create(
node_id: 1,
disk_size: 10,
image: "ubuntu:24.04",
app: "249b4cb3-3db1-4c06-98a4-772ba88cd81c"
) # POST /services/mr
client.services.vpn.list # GET /services/vpn
client.services.vpn.create(node_id: 1, subkind: "wg") # POST /services/vpn
client.services.render.list # GET /services/render
client.services.render.create(node_id: 1, disk_size: 100) # POST /services/render
# Apps
client.apps.list # GET /apps
# Network
client.network.info # GET /network
# Idle Jobs
client.idle_jobs.find(node_id: 69, job_id: 42) # GET /idle_jobs/:node_id/:job_id
client.idle_jobs.logs(node_id: 69, job_id: 42) # GET /idle_jobs/:node_id/:job_id/logs# config/initializers/octaspace.rb
OctaSpace.configure do |config|
config.api_key = ENV["OCTA_API_KEY"]
config.keep_alive = true
config.pool_size = ENV.fetch("RAILS_MAX_THREADS", 5).to_i
config.logger = Rails.logger
endOctaSpace.client (called without arguments) returns a lazily-initialized shared client built from global configuration. It is safe to call it on every request:
# app/controllers/application_controller.rb
def octa_client
OctaSpace.client # returns the same instance each time — no new connections
endPass arguments to create a one-off client instead (e.g., for per-user API keys):
OctaSpace.client(api_key: current_user.octa_api_key) # new client, not cachedThe Railtie registers an at_exit hook that automatically shuts down the shared client's connection pool when the Rails process stops. No manual cleanup needed.
| Option | Default | Description |
|---|---|---|
api_key |
nil |
API key sent as Authorization header (optional for public endpoints) |
base_url |
https://api.octa.space |
Single API endpoint |
base_urls |
nil |
Multiple endpoints — enables URL rotation |
keep_alive |
false |
Persistent HTTP connections + pool |
pool_size |
5 |
Connection pool size (keep-alive mode) |
pool_timeout |
5 |
Seconds to wait for a pool slot |
idle_timeout |
60 |
Seconds before an idle connection closes |
open_timeout |
10 |
Seconds to open TCP connection |
read_timeout |
30 |
Seconds to read response |
write_timeout |
30 |
Seconds to write request body |
max_retries |
2 |
Retry attempts on transient failures |
retry_interval |
0.5 |
Base retry interval in seconds |
backoff_factor |
2.0 |
Exponential backoff multiplier |
ssl_verify |
true |
Verify SSL certificates |
on_request |
nil |
callable(req_hash) — before each request |
on_response |
nil |
callable(response) — after each response |
logger |
nil |
Ruby Logger instance |
log_level |
:info |
Log level |
user_agent |
auto | User-Agent header value |
persistent is an alias for keep_alive for compatibility with Cube internals.
Requires faraday-net_http_persistent and connection_pool in your Gemfile.
client = OctaSpace::Client.new(
api_key: ENV["OCTA_API_KEY"],
keep_alive: true,
pool_size: ENV.fetch("RAILS_MAX_THREADS", 5).to_i,
idle_timeout: 120
)
# Diagnostics
client.transport_stats
# => { mode: :persistent, pools: { "https://api.octa.space" => { size: 5, available: 4 } } }
# Explicit shutdown (optional — Railtie does this automatically in Rails)
client.shutdownclient = OctaSpace::Client.new(
api_key: ENV["OCTA_API_KEY"],
base_urls: ["https://api.octa.space", "https://api2.octa.space"]
)- Requests are distributed round-robin across healthy endpoints.
- If an endpoint raises a connection or timeout error, it enters a 30-second cooldown and traffic shifts to the remaining endpoints.
- After cooldown, the endpoint is re-admitted automatically.
OctaSpace.configure do |config|
config.on_request = ->(req) { Rails.logger.debug "→ #{req[:method].upcase} #{req[:path]}" }
config.on_response = ->(resp) { Rails.logger.debug "← #{resp.status} (#{resp.request_id})" }
endon_request receives { method:, path:, params: }.
on_response receives an OctaSpace::Response instance.
begin
client.nodes.find(999_999)
rescue OctaSpace::NotFoundError => e
puts "Not found — request_id: #{e.request_id}"
rescue OctaSpace::AuthenticationError
puts "Invalid API key"
rescue OctaSpace::RateLimitError => e
sleep e.retry_after
retry
rescue OctaSpace::ConnectionError, OctaSpace::TimeoutError => e
puts "Network error: #{e.message}"
rescue OctaSpace::Error => e
puts "API error #{e.status}: #{e.message}"
endOctaSpace::Error
├── ConfigurationError — missing gems, invalid config
├── NetworkError — no HTTP response received
│ ├── ConnectionError — TCP connection refused / failed
│ └── TimeoutError — open/read timeout
└── ApiError — HTTP response received with error status
├── AuthenticationError 401
├── PermissionError 403
├── NotFoundError 404
├── ValidationError 422
├── RateLimitError 429 → #retry_after (seconds)
└── ServerError 5xx
├── BadGatewayError 502
├── ServiceUnavailableError 503
└── GatewayTimeoutError 504
All ApiError subclasses expose:
#status— HTTP status code#request_id— value ofX-Request-Idresponse header#response— the rawOctaSpace::Responseobject
The OctaSpace::Types namespace provides immutable Data.define value objects for domain entities. They are not returned by default — resources return raw response.data (Hash/Array). Use them explicitly when you want structured objects:
response = client.nodes.find(123)
node = OctaSpace::Types::Node.new(**response.data.transform_keys(&:to_sym))
node.online? # => true / false
node.state # => "online"
node.id # => 123Available types: Node, Account, Balance, Session.
Interactive demo for manual testing against a real API key:
OCTA_API_KEY=your_key bin/playground
# → http://localhost:3000Pages:
| Route | Content |
|---|---|
/playground/account |
Profile + balance |
/playground/nodes |
Node list with state badges |
/playground/sessions |
Active sessions |
/playground/services |
Machine Rentals + VPN sessions |
/playground/diagnostics |
Transport mode, pool stats, URL rotator state (auto-refresh every 5s) |
bin/console # IRB with gem loaded
bundle exec standardrb # lint (StandardRB)
bundle exec rake test # tests onlyYou can verify the gem in a clean Ruby environment without Rails:
- Build the gem:
gem build octaspace.gemspec - Install it locally:
gem install ./octaspace-0.1.0.gem - Test in IRB:
irb
> require 'octaspace'
> client = OctaSpace::Client.new # Test public endpoints
> client.network.info
> client_auth = OctaSpace::Client.new(api_key: "token") # Test authenticated client
> client_auth.accounts.profile.data # Retrieving profile data (email, ID, etc.)bundle exec appraisal rails-7-1 rake test
bundle exec appraisal rails-7-2 rake test
bundle exec appraisal rails-8-0 rake testThe repository includes a Rails "Dummy" application for manual testing and UI prototyping. It is located in test/dummy.`.
To run the dummy app:
- Ensure you have an API key:
export OCTA_API_KEY=your_key - Run the playground script:
bin/playground(starts Puma on port 3000) - Visit
http://localhost:3000/playground/diagnostics
You can also run it manually from the directory:
cd test/dummy
bin/rails serverThe dummy app is configured to use the local version of the gem. It is not included in the published gem package.
See PUBLISHING.md for instructions on how to package and release new versions of the gem.
MIT © OctaSpace Team