Go SDK
Go client for the ScrapingIsNotACrime API: package sinac, context on every call, typed structs, standard library only.
01Install
go get github.com/ScrapingIsNotACrime/sdk-go
The module has no third-party dependencies.
02Quick start
package main
import (
"context"
"errors"
"fmt"
"log"
sinac "github.com/ScrapingIsNotACrime/sdk-go"
)
func main() {
client, err := sinac.NewClient(sinac.WithAPIKey("sinac_..."))
if err != nil {
log.Fatal(err)
}
profile, err := client.Instagram.Profile(context.Background(), "nasa")
if errors.Is(err, sinac.ErrNotFound) {
fmt.Println("no such profile")
return
}
if err != nil {
log.Fatal(err)
}
fmt.Println(profile.Username, profile.Followers)
}Get a key at your dashboard. Keys start with sinac_, and new accounts get 100 free credits.
NewClient also reads SCRAPINGISNOTACRIME_API_KEY, so sinac.NewClient() with no options works when it is set.
03Configuration
client, err := sinac.NewClient(
sinac.WithAPIKey("sinac_..."),
sinac.WithBaseURL("https://api.scrapingisnotacrime.com/v1"),
sinac.WithTimeout(30*time.Second),
sinac.WithMaxRetries(2),
sinac.WithHTTPClient(nil),
)| Option | Default | Description |
|---|---|---|
WithAPIKey | SCRAPINGISNOTACRIME_API_KEY | Your API key. NewClient returns an error if none is set. Defaults to the SCRAPINGISNOTACRIME_API_KEY environment variable. |
WithBaseURL | https://api.scrapingisnotacrime.com/v1 | API base URL. Redirects are not followed, so use the final HTTPS URL. |
WithTimeout | 30 * time.Second | Per attempt, covering connect, headers and the whole body read. |
WithMaxRetries | 2 | Extra attempts for 429, 502 and network errors. 0 disables retries. |
WithHTTPClient | *http.Client | Bring your own *http.Client. It is never modified. Defaults to a new *http.Client. |
04Methods
Each platform is a service field on Client. Every method takes a context.Context first and returns a typed struct; paginated ones return *Page[T, R]. Pass nil for any params struct to use the defaults.
05Pagination
Page has Items, HasMore, NextCursor or NextPage, and Data. Range over page.All(ctx) to iterate every item, or call page.Next(ctx) page by page.
Each page fetched is one billed request, so bound your loops. Breaking out early stops fetching.
page, err := client.GitHub.Followers(ctx, "torvalds", &sinac.GitHubListParams{Limit: 100})
if err != nil {
log.Fatal(err)
}
n := 0
for user, err := range page.All(ctx) {
if err != nil {
log.Fatal(err)
}
fmt.Println(user.Username)
if n++; n >= 250 {
break // stop early; no further pages are fetched
}
}page, err := client.Bluesky.Posts(ctx, "bsky.app", &sinac.BlueskyPostsParams{Limit: 25})
// Each page fetched is one billed request, so stop after a bounded number of pages.
for fetched := 0; page != nil && err == nil && fetched < 3; fetched++ {
for _, post := range page.Items {
fmt.Println(post.Text)
}
page, err = page.Next(ctx)
}
if err != nil {
log.Fatal(err)
}06Errors
Every API, network and argument failure is a *sinac.Error with Status, Message and RequestID. Match it with errors.Is against the sentinels, or read its fields with errors.As.
| Type | Status | Retried |
|---|---|---|
ErrBadRequest | 400, or an invalid path argument | no |
ErrAuthentication | 401 | no |
ErrQuotaExceeded | 402 | no |
ErrNotFound | 404 | no |
ErrRateLimit | 429 | yes |
ErrUpstream | 502 | yes |
ErrConnection | network failure or per-attempt timeout | yes |
ErrAPI | any other status, a 2xx without the JSON envelope, or a redirect (not followed) | no |
var apiErr *sinac.Error
if errors.As(err, &apiErr) {
fmt.Println(apiErr.Status, apiErr.RequestID)
}profile, err := client.TikTok.Profile(ctx, "this-user-does-not-exist-123")
switch {
case errors.Is(err, sinac.ErrNotFound):
fmt.Println("no such profile")
case errors.Is(err, sinac.ErrQuotaExceeded):
log.Fatal(err) // includes the pricing link in Message
case errors.Is(err, sinac.ErrRateLimit):
fmt.Println("TikTok is rate limiting; already retried, try again later")
case err != nil:
log.Fatal(err)
default:
fmt.Println(profile.Username)
}07Retries
Rate limits (429), upstream failures (502) and network failures or timeouts are retried automatically, up to 2 extra attempts by default. These failures don't consume credits. The wait honors the Retry-After header; otherwise it is exponential backoff with jitter, starting at 500 ms and capped at 10 seconds.
Use WithMaxRetries(0) to disable retries.
08Context and cancellation
A cancelled or expired ctx, including while the client waits between retries, makes the call return at once with an error matching errors.Is(err, ctx.Err()). Cancellation is never retried.
A per-attempt timeout is reported as ErrConnection. Check ctx.Err() to tell it apart from your own deadline.