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