πŸ“ž VoiceTel Ruby SDK

The official Ruby client for the VoiceTel REST API β€” provision numbers, place orders, validate e911, send messages, and manage your account, all with idiomatic Ruby, structured errors, and zero codegen footprint.

Version Ruby License Coverage Gem

πŸ“š Table of Contents

✨ Features

🧱 Idiomatic Ruby End-to-End

  • Resource-style API β€” client.numbers.list, client.messaging.send_message(...), just like the official SDKs in every other language.
  • snake_case everywhere, automatically camelCased on the wire so the spec's fromNumber / toNumber / messagingBrandId look like plain Ruby.
  • RBS signatures ship in sig/ β€” opt into type-checking with Steep or Sorbet, or ignore them and stay dynamic.

πŸ” Production-Grade Transport

  • Faraday 2.x with connection pooling, persistent HTTP, and pluggable adapters.
  • Automatic retry on 429 / 5xx via faraday-retry, honoring Retry-After.
  • Configurable timeouts per client.
  • Bearer auth managed for you; passwordβ†’key exchange handled by client.login.
  • Structured ApiError β€” :rate_limit, :not_found, :conflict, ... pattern-match on a Symbol, or call err.rate_limit?.

πŸ“ž Complete API Coverage

  • Numbers β€” list, get, add, remove, route, translate, CNAM, LIDB, fax, forward, SMS, messaging campaigns, port-out PIN, account moves.
  • Account β€” profile, sub-accounts, CDRs, credits, payments, MRC, registration, password recovery.
  • e911 β€” record provisioning, address validation, lookup, removal.
  • Gateways β€” list, create, update, delete, view bound numbers.
  • Messaging β€” SMS & MMS sending, message history, 10DLC brand and campaign registration, per-number messaging state.
  • Lookups β€” CNAM and LRN dips.
  • iNumbering β€” inventory search, coverage queries, number orders, port-in submissions, port-out availability checks (v2.2.10 LRN + rate-center tier fields).
  • Support β€” ticket create / read / update / delete, threaded messages, replies.
  • ACL β€” IP allowlist management with structured 409 conflict bodies.
  • Authentication β€” switch between Digest, IP-only, or hybrid modes; rotate passwords.

πŸ§ͺ Battle-Tested

  • Unit tests against a mocked HTTP layer (webmock), every resource method covered.
  • Read-only integration suite gated by env vars β€” safe for CI.
  • β‰₯85% line coverage via simplecov.

πŸš€ Installation

gem install voicetel

Or in your Gemfile:

gem "voicetel", "~> 2.2.10"

Requires Ruby 3.1 or later. Tested on 3.1, 3.2, 3.3, and 3.4.

🏁 Quickstart

require "voicetel"

client = VoiceTel::Client.new

# Exchange username + password for an API key (one-time per session)
client.(username: 1_000_000_001, password: "hunter2")

me = client..get
puts "Balance: $#{me['cash']}  |  Caller ID: #{me['callerId']}"

# List your numbers
client.numbers.list["numbers"].each do |n|
  puts "#{n['number']}  route=#{n['route']}  cnam=#{n['cnam']}  sms=#{n['smsEnabled']}"
end

Or, if you already have an API key:

client = VoiceTel::Client.new(api_key: ENV.fetch("VOICETEL_API_KEY"))

coverage = client.i_numbering.coverage(state: "NJ")
coverage["coverage"].each do |bucket|
  puts "#{bucket['npa']}-#{bucket['nxx']}: #{bucket['count']} TNs available"
end

πŸ”‘ Authentication

Every endpoint requires Authorization: Bearer <apikey> except POST /v2.2/account/api-key, which exchanges username + password for a fresh key. Client#login handles the exchange and installs the returned key on the transport.

Re-fetch the API key after any password change β€” the old one is invalidated.

Don't have credentials yet? Get them at voicetel.com/docs/api/v2.2/credentials.

client = VoiceTel::Client.new
key = client.(username: 1_000_000_001, password: "hunter2")
# `key` is the freshly minted bearer; the client already has it installed.

πŸ—ΊοΈ Resource Reference

Resource Operations Example
client.account Profile, CDR, credits, payments, MRC, signup, recovery, sub-accounts client.account.cdr(start: t1, end_at: t2)
client.acl IP allowlist (CIDR entries) client.acl.add(acl: [{ cidr: "1.2.3.0/24" }])
client.authentication SIP/HTTP auth mode + password client.authentication.update(auth_type: 1)
client.e911 Records, address validation, provisioning client.e911.validate(address1: "...", city: "...", state: "NJ", zip: "07101")
client.gateways Termination routes client.gateways.list
client.i_numbering Inventory, orders, port-ins client.i_numbering.search_inventory(npa: 201)
client.lookups CNAM & LRN dips client.lookups.lrn("2015551234", "2012548000")
client.messaging SMS/MMS, 10DLC brands & campaigns client.messaging.send_message(from_number: "...", to_number: "...", text: "...")
client.numbers All operations on TNs on the account client.numbers.assign_campaign("2015551234", campaign_id: "CABC123")
client.support Tickets, replies, attachments client.support.create(subject: "...", message: "...")

Every method that takes a body accepts a plain Ruby Hash with snake_case keys; the SDK camelCases them when serializing to JSON:

client.messaging.send_message(
  from_number: "2012548000",
  to_number:   "2015551234",
  text:        "Your code is 482917"
)
# β†’ POST /v2.2/messages { "fromNumber": "2012548000", "toNumber": "2015551234", "text": "Your code is 482917" }

client.numbers.assign_campaign("2015551234", campaign_id: "CABC123")
# β†’ PUT /v2.2/numbers/2015551234/messaging-campaign { "campaignId": "CABC123" }

Responses come back as Hash/Array structures with the API's native (camelCase) keys β€” easy to pass straight to to_json or pretty-print.

🚨 Error Handling

All HTTP errors raise VoiceTel::ApiError. The kind attribute is a Symbol; convenience predicates are exposed for readability:

Status kind Predicate
400 :bad_request err.bad_request?
401 :authentication err.authentication?
403 :permission_denied err.permission_denied?
404 :not_found err.not_found?
409 :conflict err.conflict?
429 :rate_limit err.rate_limit?
5xx :server err.server?
other :unknown err.unknown?
begin
  client.numbers.get("9999999999")
rescue VoiceTel::ApiError => e
  case e.kind
  when :not_found    then puts "Not on your account."
  when :rate_limit   then puts "Slow down β€” retry suggested."
  when :authentication then puts "Re-login: #{e.message}"
  else raise
  end
end

ApiError#body preserves the parsed response payload β€” useful on 409 ACL conflicts, where the server returns structured {added, removed, failed} detail.

⏱️ Rate Limits

These endpoints are limited to 6 requests per hour per IP:

  • account/info (client.account.get)
  • account/cdr (client.account.cdr)
  • account/recurring-charges (client.account.recurring_charges)
  • account/payments (client.account.payments)
  • account/registration (client.account.registration)
  • account/api-key (client.login)

The SDK automatically retries 429 responses with Retry-After honored, up to max_retries (default 2). To raise it:

VoiceTel::Client.new(api_key: key, max_retries: 4, timeout: 60)

πŸ› οΈ Development

git clone https://github.com/voicetel/ruby-sdk
cd ruby-sdk
bundle install

# Unit tests (fast, no network)
bundle exec rspec

# Lint
bundle exec rubocop

# Integration tests (live api.voicetel.com, read-only)
cp .env.example .env  # fill in VOICETEL_USERNAME / VOICETEL_PASSWORD
INTEGRATION=1 bundle exec rspec spec/integration

# Build the gem
gem build voicetel.gemspec

πŸ“– API Documentation

πŸ™Œ Contributors

Contributions welcome. Open an issue describing the change you want to make, or send a pull request against main.

πŸ’– Sponsors

Sponsor Contribution
VoiceTel Communications Primary development and production hosting

πŸ“„ License

This project is licensed under the MIT License β€” see the LICENSE file for details.