The official Go 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 Go.
- Free to start —
inbio.Shorten()and the QR API need no account and no API key - Zero dependencies — standard library only; context-first API, functional options, range-over-func iteration (Go ≥ 1.23)
- Complete — covers the entire INBIO REST API: links CRUD, iteration, bulk create, QR codes, analytics, webhook signature verification
go get github.com/getinbio/inbio-goThe free endpoint requires no token, no account, nothing:
result, err := inbio.Shorten(ctx, "https://example.com/some/very/long/url")
fmt.Println(result.ShortURL) // https://in.bio/x7Kp2qAnonymous links are deleted after 30 days unless claimed via result.ClaimURL.
Rate limit: 5/min per IP (100/day).
Create a token under Settings → API tokens (API access requires Pro or
Business). Pass it explicitly or set INBIO_API_TOKEN:
package main
import (
"context"
"fmt"
"log"
"github.com/getinbio/inbio-go"
)
func main() {
ctx := context.Background()
client := inbio.NewClient(inbio.WithToken("your-api-token"))
link, err := client.Links.Create(ctx, inbio.CreateLinkParams{
DestinationURL: "https://example.com/sale",
Slug: "spring-sale",
Tags: []string{"marketing"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(link.ShortURL) // https://in.bio/spring-sale
}List returns one page; Iter walks every page for you:
page, err := client.Links.List(ctx, &inbio.ListOptions{
Status: "active",
PerPage: 50,
})
fmt.Println(page.Meta.Total, page.HasNext())
// Auto-pagination over every matching link:
for link, err := range client.Links.Iter(ctx, &inbio.ListOptions{Tag: "marketing"}) {
if err != nil {
log.Fatal(err)
}
fmt.Println(link.Slug, link.TotalClicks)
}ListOptions supports Search, FolderID, Tag, Status, Page, PerPage.
Only DestinationURL is required; zero-valued fields are omitted:
link, err := client.Links.Create(ctx, inbio.CreateLinkParams{
DestinationURL: "https://example.com/launch",
Title: "Launch post",
RedirectType: 301,
FolderID: 3,
Tags: []string{"launch", "blog"},
ExpiresAt: inbio.Time(time.Date(2026, 12, 31, 0, 0, 0, 0, time.UTC)),
ClickLimit: 10000,
Password: "s3cret", // Pro+
UTM: &inbio.UTM{Source: "newsletter", Campaign: "q3"},
})UpdateLinkParams uses pointers so only the fields you set are sent
(helpers: inbio.String, inbio.Int, inbio.Int64, inbio.Time):
link, err := client.Links.Update(ctx, link.ID, inbio.UpdateLinkParams{
Title: inbio.String("New title"),
Slug: inbio.String("summer-sale"),
})
link, err = client.Links.Disable(ctx, link.ID) // active -> disabled
link, err = client.Links.Enable(ctx, link.ID) // disabled -> active
err = client.Links.Delete(ctx, link.ID) // soft delete, stops redirectingEnable/disable from any other status returns a *inbio.StateConflictError
carrying the link's Current status.
Up to 100 links per request; rows fail independently:
result, err := client.Links.BulkCreate(ctx, []inbio.CreateLinkParams{
{DestinationURL: "https://example.com/1", Slug: "promo-1"},
{DestinationURL: "https://example.com/2", Slug: "promo-2"},
})
for _, f := range result.Failed {
fmt.Printf("row %d failed: %s\n", f.Index, f.Error)
}qr, err := client.Links.QR(ctx, link.ID, &inbio.QROptions{Format: "png", Size: 512})
if err != nil {
log.Fatal(err)
}
_ = os.WriteFile("qr.png", qr.Data, 0o644) // qr.ContentType == "image/png"The QR encodes the short URL, so destination edits never invalidate printed codes.
a, err := client.Links.Analytics(ctx, link.ID, &inbio.AnalyticsOptions{
From: "2026-06-01",
To: "2026-06-30",
})
fmt.Println(a.Totals.Clicks, a.Totals.Uniques)
for _, c := range a.Countries {
fmt.Println(c.Value, c.Clicks)
}folders, err := client.Folders.List(ctx)
tags, err := client.Tags.List(ctx)usage, err := client.Account.Usage(ctx)
fmt.Println(usage.Plan) // "pro"
fmt.Println(usage.Usage.LinksCreated) // 120
fmt.Println(usage.Limits.LinksPerMonth) // 2000Verify the X-Inbio-Signature header over the exact raw request body. The
comparison is constant-time and stale timestamps are rejected (default
tolerance 300s; pass a time.Duration to change it):
func handleWebhook(w http.ResponseWriter, r *http.Request) {
payload, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "read error", http.StatusBadRequest)
return
}
event, err := client.Webhooks.Verify(
payload,
r.Header.Get("X-Inbio-Signature"),
os.Getenv("INBIO_WEBHOOK_SECRET"),
0, // 0 = default 300s tolerance
)
if err != nil {
http.Error(w, "invalid signature", http.StatusBadRequest)
return
}
switch event.Event {
case "link.clicked":
link, _ := event.Link()
fmt.Println("clicked:", link.Slug)
case "link.click_limit_reached":
// ...
}
w.WriteHeader(http.StatusOK)
}Webhooks.ConstructEvent is an alias for Verify.
Every non-2xx response maps to a typed error; all of them wrap the base
*inbio.Error (fields StatusCode, Type, Message, Body), so
errors.As works at either level:
link, err := client.Links.Get(ctx, 42)
if err != nil {
var notFound *inbio.NotFoundError
var rateLimited *inbio.RateLimitError
switch {
case errors.As(err, ¬Found):
fmt.Println("no such link")
case errors.As(err, &rateLimited):
time.Sleep(time.Duration(rateLimited.RetryAfter) * time.Second)
default:
return err
}
}| Error | Status | Extra fields |
|---|---|---|
*inbio.AuthenticationError |
401 | |
*inbio.AccessError |
403 | Type (plan/scope/account), Required scope |
*inbio.NotFoundError |
404 | also returned for links you don't own |
*inbio.StateConflictError |
409 | Current status |
*inbio.ValidationError |
422 | Errors map[string][]string per field |
*inbio.EntitlementError |
422 | plan feature/limit (error.type=entitlement) |
*inbio.RateLimitError |
429 | RetryAfter seconds |
*inbio.ServerError |
5xx | |
*inbio.SignatureVerificationError |
— | webhook verification failures |
Defaults: 30s timeout, 2 retries. Retries apply only to idempotent GET
requests (on transport errors and 5xx) and to 429 responses that carry a
Retry-After header, with exponential backoff capped at 10s.
client := inbio.NewClient(
inbio.WithToken("your-api-token"),
inbio.WithBaseURL("https://in.bio"), // default
inbio.WithTimeout(10*time.Second),
inbio.WithMaxRetries(5),
inbio.WithHTTPClient(&http.Client{ /* custom transport */ }),
)When no token is passed, the client reads INBIO_API_TOKEN:
export INBIO_API_TOKEN="your-api-token"client := inbio.NewClient() // uses INBIO_API_TOKENINBIO (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.
- Website: https://in.bio
- Documentation: https://docs.in.bio
- Free shorten API (no key): https://docs.in.bio/api/free-shorten
- Free QR code API (no key): https://docs.in.bio/api/free-qr
- MCP server for AI agents: https://docs.in.bio/api/mcp
- All SDKs (JavaScript, Python, PHP, Ruby, Go): https://docs.in.bio/sdks
MIT © InBio, Inc.