Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

inbio — official Ruby SDK for INBIO (in.bio)

The official Ruby SDK for INBIO, the premium URL shortener with click analytics and customizable QR codes. Shorten links, generate styled QR codes, read analytics, and manage folders and tags from Ruby.

  • Free to startInbio.shorten and the QR API need no account and no API key
  • Zero runtime dependencies — stdlib net/http and openssl only, Ruby ≥ 3.0
  • Complete — covers the entire INBIO REST API: links CRUD, lazy enumerator pagination, bulk create, QR codes, analytics, webhook signature verification

Install

gem install inbio

Or in your Gemfile:

gem "inbio"

Shorten a URL — no account needed

require "inbio"

puts Inbio.shorten("https://example.com/very/long/url").short_url
# => "https://in.bio/abc123"

The free endpoint is keyless and rate-limited (5/min per IP). The result also carries slug, qr_url, preview_url, claim_url (turn the anonymous link into a permanent one on your account) and expires — anonymous links are deleted after 30 days unless claimed.

Authenticated quickstart

Create a token under Settings → API tokens (API access requires Pro or Business).

require "inbio"

client = Inbio::Client.new(token: "YOUR_API_TOKEN")

link = client.links.create("https://example.com/sale", slug: "spring-sale")
link.short_url    # => "https://in.bio/spring-sale"
link.total_clicks # => 0

With no token: argument the client reads ENV["INBIO_API_TOKEN"]:

client = Inbio::Client.new # uses INBIO_API_TOKEN

Links

List and iterate

list returns one page; iterate returns a lazy Enumerator that paginates through everything for you:

page = client.links.list(status: "active", per_page: 50)
page.total        # => 57
page.current_page # => 1
page.next?        # => true
page.each { |link| puts link.short_url }

# Every link, across all pages, fetched on demand:
client.links.iterate(tag: "marketing").each do |link|
  puts "#{link.slug}: #{link.total_clicks} clicks"
end

client.links.iterate(search: "sale").first(10) # stops fetching after 10

Filters: search:, folder_id:, tag:, status: (active, disabled, archived, expired, exhausted, blocked, pending_review), per_page: (1–100). list_all is an alias for iterate.

Create with options

Only the destination URL is required:

link = client.links.create(
  "https://example.com/launch",
  slug: "launch",                 # 4–64 chars [a-zA-Z0-9-_]; omitted => random
  title: "Product launch",
  description: "Fall campaign",
  notes: "Internal note",
  redirect_type: 301,             # 301, 302 (default) or 307
  folder_id: 3,
  tags: ["marketing", "fall"],    # created on the fly
  expires_at: "2026-12-31T23:59:59Z", # Pro+
  fallback_url: "https://example.com/expired", # Pro+
  click_limit: 10_000,            # Pro+
  password: "s3cret",             # Pro+
  utm: { source: "newsletter", medium: "email", campaign: "fall" }
)

Read, update, enable/disable, delete

link = client.links.get(42)

link = client.links.update(42, title: "New title", tags: ["archive"])
# Editing destination_url requires the edit-destination feature (Pro+).

client.links.disable(42) # active   -> disabled
client.links.enable(42)  # disabled -> active
# Any other transition raises Inbio::StateConflictError (error.current has the status).

client.links.delete(42)  # => nil (204); the link stops redirecting

Bulk create (Business)

Up to 100 links per call; rows fail independently:

result = client.links.bulk_create([
  { destination_url: "https://example.com/a", slug: "promo-a" },
  { destination_url: "https://example.com/b", slug: "promo-b" }
])
result.created.each { |link| puts link.short_url }
result.failed.each  { |row| warn "row #{row['index']}: #{row['error']}" }

QR code to file

qr returns raw image bytes — write them straight to disk:

File.binwrite("spring-sale.png", client.links.qr(42))
File.binwrite("spring-sale.svg", client.links.qr(42, format: "svg", size: 1024))

The QR encodes the short URL, so editing the destination never invalidates printed codes.

Analytics

stats = client.links.analytics(42, from: "2026-06-01", to: "2026-06-30")

stats.totals    # => {"clicks"=>1240, "uniques"=>981, "botClicks"=>77}
stats.series    # => [{"date"=>"2026-06-01", "clicks"=>40, "uniques"=>31}, ...]
stats.countries # top 10: [{"value"=>"US", "clicks"=>512}, ...]
stats.devices, stats.browsers, stats.referrers # same shape

Bot traffic is excluded everywhere except totals["botClicks"].

Folders and tags

client.folders.list.each { |f| puts "#{f.name} (#{f.links_count} links)" }
client.tags.list.each    { |t| puts "#{t.name} (#{t.links_count} links)" }

Account usage

usage = client.account.usage
usage.plan                        # => "pro"
usage.usage["links_created"]      # => 120
usage.limits["links_per_month"]   # => 2000

Webhook verification

in.bio signs every delivery with X-Inbio-Signature: t=<unix>,v1=<hex> where v1 = HMAC-SHA256(secret, "<t>.<raw body>"). Verify with the exact raw request body (before parsing) — the comparison is constant-time and deliveries older than tolerance (default 300s) are rejected to prevent replays.

Rails:

class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def inbio
    event = Inbio::Webhooks.verify(
      request.raw_post,
      request.headers["X-Inbio-Signature"],
      ENV.fetch("INBIO_WEBHOOK_SECRET")
    )

    case event.event
    when "link.clicked"            then Metrics.count(event.data["slug"])
    when "link.click_limit_reached" then AlertMailer.limit(event.data).deliver_later
    end

    head :ok
  rescue Inbio::SignatureVerificationError
    head :bad_request
  end
end

Rack/Sinatra:

post "/webhooks/inbio" do
  event = Inbio::Webhooks.verify(
    request.body.read,
    request.env["HTTP_X_INBIO_SIGNATURE"],
    ENV.fetch("INBIO_WEBHOOK_SECRET")
  )
  # handle event.event / event.data ...
  200
rescue Inbio::SignatureVerificationError
  400
end

Inbio::Webhooks.construct_event is an alias; client.webhooks.verify(...) works too. No HTTP is involved — it is a pure crypto helper.

Error handling

Every error is a subclass of Inbio::Error, which exposes message, status, error_type and the parsed response body.

HTTP Error Extras
401 Inbio::AuthenticationError
403 Inbio::AccessError error_type ("plan", "scope", "account"), required_scope
404 Inbio::NotFoundError also returned for links you don't own
409 Inbio::StateConflictError current — the link's current status
422 Inbio::ValidationError errors — field name → array of messages
422 (error.type=entitlement) Inbio::EntitlementError plan feature/limit hit
429 Inbio::RateLimitError retry_after (seconds)
5xx Inbio::ServerError
Inbio::SignatureVerificationError webhook verification only
begin
  client.links.create("https://example.com", slug: "x")
rescue Inbio::ValidationError => e
  e.errors # => {"slug"=>["The slug must be at least 3 characters."]}
rescue Inbio::AccessError => e
  e.required_scope # => "links:write" when a token scope is missing
rescue Inbio::RateLimitError => e
  sleep e.retry_after
  retry
end

Retries and timeouts

client = Inbio::Client.new(
  token: "...",
  base_url: "https://in.bio", # default
  timeout: 30,                # seconds, default 30
  max_retries: 2              # default 2
)

Retries apply only to idempotent GET requests (on connection errors and 5xx) and to 429 responses that carry Retry-After — with exponential backoff capped at 10 seconds. Everything else fails fast.

About INBIO

INBIO (in.bio) is a URL shortener and link-management platform: short links with custom slugs, real-time click analytics (countries, devices, browsers, referrers — bots filtered out), a QR code studio with dot styles, marker shapes and colors, folders, tags, UTM tools, and a REST API with webhooks. Free plan included.

License

MIT © InBio, Inc.

About

Official Ruby SDK for INBIO (in.bio) — URL shortener, QR codes, click analytics. Zero dependencies, free keyless endpoints.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages