Skip to content

Repository files navigation

inbio-go — official Go SDK for INBIO (in.bio)

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 startinbio.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

Install

go get github.com/getinbio/inbio-go

Shorten a URL — no account needed

The 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/x7Kp2q

Anonymous links are deleted after 30 days unless claimed via result.ClaimURL. Rate limit: 5/min per IP (100/day).

Authenticated quickstart

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
}

Links

List and iterate

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.

Create with options

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"},
})

Update, enable, disable

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 redirecting

Enable/disable from any other status returns a *inbio.StateConflictError carrying the link's Current status.

Bulk create (Business)

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 code to file

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.

Analytics

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 and tags

folders, err := client.Folders.List(ctx)
tags, err := client.Tags.List(ctx)

Account usage

usage, err := client.Account.Usage(ctx)
fmt.Println(usage.Plan)                       // "pro"
fmt.Println(usage.Usage.LinksCreated)         // 120
fmt.Println(usage.Limits.LinksPerMonth)       // 2000

Webhook verification

Verify 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.

Error handling

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, &notFound):
		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

Retries and timeouts

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 */ }),
)

Authenticating via environment variable

When no token is passed, the client reads INBIO_API_TOKEN:

export INBIO_API_TOKEN="your-api-token"
client := inbio.NewClient() // uses INBIO_API_TOKEN

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 Go SDK for INBIO (in.bio) — URL shortener, QR codes, click analytics. Zero dependencies, free keyless endpoints.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages