Guides / Go

Go

Call the Lagoon API from Go using the standard library. Covers search, artist lookup, pagination, authentication, and error handling. No external dependencies.

Prerequisites

Go 1.21 or later.

Search by tag

Find posts by an exact tag name using the tag parameter.

package main import ( "encoding/json" "fmt" "net/http" "net/url" "strings" ) func main() { params := url.Values{} params.Set("tag", "blue_hair") params.Set("limit", "10") resp, err := http.Get("https://lagoon.io/api/v1/search?" + params.Encode()) if err != nil { fmt.Println("request failed:", err) return } defer resp.Body.Close() var data struct { OK bool `json:"ok"` Results []struct { Title string `json:"title"` ArtistHandle string `json:"artist_handle"` PlatformName string `json:"platform_name"` Tags []string `json:"tags"` } `json:"results"` } json.NewDecoder(resp.Body).Decode(&data) for _, post := range data.Results { fmt.Printf("%s by %s\n", post.Title, post.ArtistHandle) fmt.Printf(" Platform: %s\n", post.PlatformName) tags := post.Tags if len(tags) > 5 { tags = tags[:5] } fmt.Println(" Tags:", strings.Join(tags, ", ")) fmt.Println() } }

Each post in results includes id, source_url, title, posted_at, platform, platform_name, artist_handle, artist_name, deleted, and tags.

Natural language search

Pass freeform text to the nl parameter. The API extracts platform, artist, date range, tags, and sort order from your query. The response includes a filters key showing what was parsed.

params := url.Values{} params.Set("nl", "solo girl on pixiv from 2025") resp, err := http.Get("https://lagoon.io/api/v1/search?" + params.Encode()) if err != nil { log.Fatal(err) } defer resp.Body.Close() var data struct { OK bool `json:"ok"` Filters map[string]any `json:"filters"` Results []struct { Title string `json:"title"` } `json:"results"` } json.NewDecoder(resp.Body).Decode(&data) // See what the parser extracted b, _ := json.MarshalIndent(data.Filters, "", " ") fmt.Println(string(b)) // {"from":"2025-01-01","platform":"pixiv","tags":["solo"],...} for _, post := range data.Results { fmt.Println(post.Title) }

Look up an artist

Query a handle to get all linked accounts across platforms.

resp, err := http.Get("https://lagoon.io/api/v1/artist?" + url.Values{ "handle": {"hews__"}, }.Encode()) if err != nil { log.Fatal(err) } defer resp.Body.Close() var data struct { OK bool `json:"ok"` Artist struct { DisplayName string `json:"display_name"` PostCount int `json:"post_count"` Platforms []struct { PlatformName string `json:"platform_name"` Handle string `json:"handle"` ProfileURL string `json:"profile_url"` } `json:"platforms"` } `json:"artist"` } json.NewDecoder(resp.Body).Decode(&data) if data.OK { fmt.Printf("%s (%d posts)\n", data.Artist.DisplayName, data.Artist.PostCount) for _, p := range data.Artist.Platforms { fmt.Printf(" %s: %s\n", p.PlatformName, p.Handle) if p.ProfileURL != "" { fmt.Printf(" %s\n", p.ProfileURL) } } }

Authenticate with an API key

Unauthenticated requests are rate-limited to 30 per minute. To use a higher-limit tier, pass your key in the X-API-Key header.

apiKey := "lg_your_key_here" req, _ := http.NewRequest("GET", "https://lagoon.io/api/v1/search?"+url.Values{ "tag": {"blue_hair"}, "limit": {"20"}, }.Encode(), nil) req.Header.Set("X-API-Key", apiKey) resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var data struct { OK bool `json:"ok"` Results []struct { Title string `json:"title"` } `json:"results"` } json.NewDecoder(resp.Body).Decode(&data)

Paginate through results

Use limit and offset to page through results. When the response count is less than your limit, you have reached the last page.

func searchAll(params url.Values, limit int) ([]map[string]any, error) { var results []map[string]any params.Set("limit", strconv.Itoa(limit)) for offset := 0; ; offset += limit { params.Set("offset", strconv.Itoa(offset)) resp, err := http.Get("https://lagoon.io/api/v1/search?" + params.Encode()) if err != nil { return results, err } var data struct { OK bool `json:"ok"` Count int `json:"count"` Results []map[string]any `json:"results"` } json.NewDecoder(resp.Body).Decode(&data) resp.Body.Close() if !data.OK { break } results = append(results, data.Results...) if data.Count < limit { break } } return results, nil } // Fetch all blue_hair posts from Bluesky posts, err := searchAll(url.Values{ "tag": {"blue_hair"}, "platform": {"bluesky"}, }, 100) if err != nil { log.Fatal(err) } fmt.Printf("Total: %d posts\n", len(posts))

Handle errors

The API returns "ok": false with an error message on failure. Check the HTTP status code for specific error types.

func lagoonGet(endpoint string, params url.Values, apiKey string, result any) error { req, err := http.NewRequest("GET", "https://lagoon.io/api/v1/"+endpoint+"?"+params.Encode(), nil) if err != nil { return err } if apiKey != "" { req.Header.Set("X-API-Key", apiKey) } resp, err := (&http.Client{Timeout: 10 * time.Second}).Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != 200 { var errResp struct { Error string `json:"error"` } json.NewDecoder(resp.Body).Decode(&errResp) return fmt.Errorf("HTTP %d: %s", resp.StatusCode, errResp.Error) } return json.NewDecoder(resp.Body).Decode(result) } // Usage var data struct { OK bool `json:"ok"` Results []struct { Title string `json:"title"` } `json:"results"` } err := lagoonGet("search", url.Values{"tag": {"blue_hair"}, "limit": {"10"}}, "", &data) if err != nil { fmt.Println("API error:", err) return } for _, post := range data.Results { fmt.Println(post.Title) }

Full example

A standalone CLI program that looks up an artist and prints their recent posts.

// search.go - run with: go run search.go hews__ package main import ( "encoding/json" "fmt" "net/http" "net/url" "os" "strings" ) const base = "https://lagoon.io/api/v1" func apiGet(endpoint string, params url.Values, result any) error { resp, err := http.Get(base + "/" + endpoint + "?" + params.Encode()) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != 200 { var e struct { Error string `json:"error"` } json.NewDecoder(resp.Body).Decode(&e) return fmt.Errorf("HTTP %d: %s", resp.StatusCode, e.Error) } return json.NewDecoder(resp.Body).Decode(result) } func main() { if len(os.Args) < 2 { fmt.Fprintln(os.Stderr, "Usage: go run search.go <artist_handle>") os.Exit(1) } handle := os.Args[1] // Step 1: Look up the artist var artistData struct { OK bool `json:"ok"` Artist struct { DisplayName string `json:"display_name"` PostCount int `json:"post_count"` Platforms []struct { PlatformName string `json:"platform_name"` Handle string `json:"handle"` } `json:"platforms"` } `json:"artist"` } if err := apiGet("artist", url.Values{"handle": {handle}}, &artistData); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } artist := artistData.Artist fmt.Printf("%s - %d indexed posts\n", artist.DisplayName, artist.PostCount) for _, p := range artist.Platforms { fmt.Printf(" %s: %s\n", p.PlatformName, p.Handle) } fmt.Println() // Step 2: Get their recent posts var searchData struct { OK bool `json:"ok"` Results []struct { ID int `json:"id"` Title *string `json:"title"` Platform string `json:"platform_name"` PostedAt string `json:"posted_at"` URL *string `json:"source_url"` Tags []string `json:"tags"` } `json:"results"` } if err := apiGet("search", url.Values{ "artist": {handle}, "limit": {"5"}, }, &searchData); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } for _, post := range searchData.Results { title := fmt.Sprintf("Post #%d", post.ID) if post.Title != nil { title = *post.Title } fmt.Println(title) fmt.Printf(" %s | %s\n", post.Platform, post.PostedAt[:10]) var filtered []string for _, t := range post.Tags { if !strings.HasPrefix(t, "rating:") { filtered = append(filtered, t) } } if len(filtered) > 8 { filtered = filtered[:8] } fmt.Println(" Tags:", strings.Join(filtered, ", ")) if post.URL != nil { fmt.Printf(" %s\n", *post.URL) } fmt.Println() } }

Next steps