Go client library for Mastodon API with complete coverage of v1 and v2 endpoints
Complete instance metadata, configuration, and statistics operations.
Instance endpoints provide information about Mastodon servers including configuration, statistics, activity, and federated peers.
type Instance struct {
URI string
Title string
Description string
EMail string
Version string
Thumbnail string
URLs map[string]string
Stats *InstanceStats
Languages []string
ContactAccount *Account
Configuration *InstanceConfig
}Instance holds information for a Mastodon instance.
Fields:
URI - Instance domain (e.g., "mastodon.social")Title - Instance nameDescription - Instance description (HTML)EMail - Admin contact emailVersion - Mastodon version stringThumbnail - Instance thumbnail image URLURLs - Streaming API URLs and other endpointsStats - Instance statisticsLanguages - Supported language codesContactAccount - Contact account for adminConfiguration - Instance configuration limitsMethod:
func (c *Instance) GetConfig() *InstanceConfigReturns instance configuration.
type InstanceConfig struct {
Accounts *InstanceConfigMap
Statuses *InstanceConfigMap
MediaAttachments map[string]interface{}
Polls *InstanceConfigMap
}InstanceConfig holds configuration limits accessible to clients.
Fields:
Accounts - Account-related limitsStatuses - Status-related limitsMediaAttachments - Media file size and type limitsPolls - Poll-related limitstype InstanceConfigMap map[string]interface{}InstanceConfigMap is a type alias for configuration maps.
Configuration examples:
Accounts: max_featured_tags, max_pinned_statusesStatuses: max_characters, max_media_attachmentsMediaAttachments: supported_mime_types, image_size_limit, video_size_limitPolls: max_options, max_characters_per_option, min_expiration, max_expirationtype InstanceStats struct {
UserCount int64
StatusCount int64
DomainCount int64
}InstanceStats holds statistics for a Mastodon instance.
Fields:
UserCount - Total registered usersStatusCount - Total statuses postedDomainCount - Number of known instances (federated peers)type WeeklyActivity struct {
Week Unixtime
Statuses int64
Logins int64
Registrations int64
}WeeklyActivity holds weekly activity statistics.
Fields:
Week - Week timestampStatuses - Number of statuses posted that weekLogins - Number of logins that weekRegistrations - Number of new registrations that weekfunc (c *Client) GetInstance(ctx context.Context) (*Instance, error)Returns information about the instance.
Parameters:
ctx - Context for cancellation/timeoutReturns: Instance information or error
Example:
instance, err := client.GetInstance(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Instance: %s\n", instance.Title)
fmt.Printf("URI: %s\n", instance.URI)
fmt.Printf("Version: %s\n", instance.Version)
fmt.Printf("Description: %s\n", instance.Description)
if instance.Stats != nil {
fmt.Printf("\nStatistics:\n")
fmt.Printf(" Users: %d\n", instance.Stats.UserCount)
fmt.Printf(" Statuses: %d\n", instance.Stats.StatusCount)
fmt.Printf(" Federated instances: %d\n", instance.Stats.DomainCount)
}
if instance.ContactAccount != nil {
fmt.Printf("\nAdmin: @%s\n", instance.ContactAccount.Acct)
}func (c *Client) GetInstanceActivity(ctx context.Context) ([]*WeeklyActivity, error)Returns instance activity statistics by week.
Parameters:
ctx - Context for cancellation/timeoutReturns: Slice of WeeklyActivity objects or error
Example:
activities, err := client.GetInstanceActivity(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println("Weekly Activity:")
for _, activity := range activities {
fmt.Printf("Week of %s:\n", time.Time(activity.Week).Format("2006-01-02"))
fmt.Printf(" Statuses: %d\n", activity.Statuses)
fmt.Printf(" Logins: %d\n", activity.Logins)
fmt.Printf(" New users: %d\n", activity.Registrations)
}func (c *Client) GetInstancePeers(ctx context.Context) ([]string, error)Returns instance peers (other federated instances this instance knows about).
Parameters:
ctx - Context for cancellation/timeoutReturns: Slice of peer domain strings or error
Example:
peers, err := client.GetInstancePeers(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Known instances: %d\n", len(peers))
for i, peer := range peers {
if i < 10 { // Show first 10
fmt.Printf(" %s\n", peer)
}
}
if len(peers) > 10 {
fmt.Printf(" ... and %d more\n", len(peers)-10)
}func displayInstanceInfo(client *mastodon.Client) error {
ctx := context.Background()
instance, err := client.GetInstance(ctx)
if err != nil {
return err
}
fmt.Println("╔" + strings.Repeat("═", 60) + "╗")
fmt.Printf("║ %-58s ║\n", instance.Title)
fmt.Println("╠" + strings.Repeat("═", 60) + "╣")
fmt.Printf("║ URI: %-47s ║\n", instance.URI)
fmt.Printf("║ Version: %-47s ║\n", instance.Version)
if instance.Stats != nil {
fmt.Println("╠" + strings.Repeat("═", 60) + "╣")
fmt.Printf("║ Users: %-47d ║\n", instance.Stats.UserCount)
fmt.Printf("║ Posts: %-47d ║\n", instance.Stats.StatusCount)
fmt.Printf("║ Federation: %-47d ║\n", instance.Stats.DomainCount)
}
if len(instance.Languages) > 0 {
fmt.Println("╠" + strings.Repeat("═", 60) + "╣")
langs := strings.Join(instance.Languages, ", ")
fmt.Printf("║ Languages: %-47s ║\n", langs)
}
fmt.Println("╚" + strings.Repeat("═", 60) + "╝")
return nil
}func checkInstanceLimits(client *mastodon.Client) error {
ctx := context.Background()
instance, err := client.GetInstance(ctx)
if err != nil {
return err
}
if instance.Configuration == nil {
fmt.Println("Configuration not available")
return nil
}
fmt.Println("Instance Limits:")
// Status limits
if instance.Configuration.Statuses != nil {
if maxChars, ok := (*instance.Configuration.Statuses)["max_characters"]; ok {
fmt.Printf(" Max status length: %v characters\n", maxChars)
}
if maxMedia, ok := (*instance.Configuration.Statuses)["max_media_attachments"]; ok {
fmt.Printf(" Max media per post: %v\n", maxMedia)
}
}
// Media limits
if instance.Configuration.MediaAttachments != nil {
if imgLimit, ok := instance.Configuration.MediaAttachments["image_size_limit"]; ok {
fmt.Printf(" Max image size: %v bytes\n", imgLimit)
}
if vidLimit, ok := instance.Configuration.MediaAttachments["video_size_limit"]; ok {
fmt.Printf(" Max video size: %v bytes\n", vidLimit)
}
}
// Poll limits
if instance.Configuration.Polls != nil {
if maxOpts, ok := (*instance.Configuration.Polls)["max_options"]; ok {
fmt.Printf(" Max poll options: %v\n", maxOpts)
}
if maxExp, ok := (*instance.Configuration.Polls)["max_expiration"]; ok {
fmt.Printf(" Max poll duration: %v seconds\n", maxExp)
}
}
return nil
}func analyzeInstanceActivity(client *mastodon.Client) error {
ctx := context.Background()
activities, err := client.GetInstanceActivity(ctx)
if err != nil {
return err
}
if len(activities) == 0 {
fmt.Println("No activity data available")
return nil
}
fmt.Println("Instance Activity Analysis")
fmt.Println(strings.Repeat("=", 50))
// Calculate totals and averages
var totalStatuses, totalLogins, totalRegistrations int64
for _, activity := range activities {
totalStatuses += activity.Statuses
totalLogins += activity.Logins
totalRegistrations += activity.Registrations
}
weeks := len(activities)
fmt.Printf("\nLast %d weeks:\n", weeks)
fmt.Printf(" Total statuses: %d (avg %.1f/week)\n",
totalStatuses, float64(totalStatuses)/float64(weeks))
fmt.Printf(" Total logins: %d (avg %.1f/week)\n",
totalLogins, float64(totalLogins)/float64(weeks))
fmt.Printf(" Total registrations: %d (avg %.1f/week)\n",
totalRegistrations, float64(totalRegistrations)/float64(weeks))
// Find most active week
maxStatuses := int64(0)
var mostActiveWeek time.Time
for _, activity := range activities {
if activity.Statuses > maxStatuses {
maxStatuses = activity.Statuses
mostActiveWeek = time.Time(activity.Week)
}
}
fmt.Printf("\nMost active week: %s (%d statuses)\n",
mostActiveWeek.Format("2006-01-02"), maxStatuses)
// Calculate growth trend
if len(activities) >= 4 {
recentWeeks := activities[:2]
olderWeeks := activities[len(activities)-2:]
recentAvg := (recentWeeks[0].Statuses + recentWeeks[1].Statuses) / 2
olderAvg := (olderWeeks[0].Statuses + olderWeeks[1].Statuses) / 2
if recentAvg > olderAvg {
growth := float64(recentAvg-olderAvg) / float64(olderAvg) * 100
fmt.Printf("\nGrowth trend: +%.1f%% (increasing activity)\n", growth)
} else if recentAvg < olderAvg {
decline := float64(olderAvg-recentAvg) / float64(olderAvg) * 100
fmt.Printf("\nGrowth trend: -%.1f%% (declining activity)\n", decline)
} else {
fmt.Println("\nGrowth trend: Stable")
}
}
return nil
}func analyzeFederation(client *mastodon.Client) error {
ctx := context.Background()
peers, err := client.GetInstancePeers(ctx)
if err != nil {
return err
}
fmt.Printf("Federation Analysis\n")
fmt.Println(strings.Repeat("=", 50))
fmt.Printf("Total federated instances: %d\n\n", len(peers))
// Analyze by TLD
tldCount := make(map[string]int)
for _, peer := range peers {
parts := strings.Split(peer, ".")
if len(parts) >= 2 {
tld := parts[len(parts)-1]
tldCount[tld]++
}
}
// Sort by count
type tldInfo struct {
tld string
count int
}
var tlds []tldInfo
for tld, count := range tldCount {
tlds = append(tlds, tldInfo{tld, count})
}
sort.Slice(tlds, func(i, j int) bool {
return tlds[i].count > tlds[j].count
})
fmt.Println("Top TLDs:")
for i := 0; i < 10 && i < len(tlds); i++ {
percentage := float64(tlds[i].count) / float64(len(peers)) * 100
fmt.Printf(" .%-10s %4d instances (%.1f%%)\n",
tlds[i].tld, tlds[i].count, percentage)
}
return nil
}type InstanceInfo struct {
URI string
Users int64
Statuses int64
Version string
}
func compareInstances(client1, client2 *mastodon.Client) error {
ctx := context.Background()
inst1, err := client1.GetInstance(ctx)
if err != nil {
return err
}
inst2, err := client2.GetInstance(ctx)
if err != nil {
return err
}
fmt.Println("Instance Comparison")
fmt.Println(strings.Repeat("=", 70))
fmt.Printf("%-25s %-20s %-20s\n", "", inst1.URI, inst2.URI)
fmt.Println(strings.Repeat("-", 70))
if inst1.Stats != nil && inst2.Stats != nil {
fmt.Printf("%-25s %-20d %-20d\n",
"Users", inst1.Stats.UserCount, inst2.Stats.UserCount)
fmt.Printf("%-25s %-20d %-20d\n",
"Statuses", inst1.Stats.StatusCount, inst2.Stats.StatusCount)
fmt.Printf("%-25s %-20d %-20d\n",
"Federation", inst1.Stats.DomainCount, inst2.Stats.DomainCount)
}
fmt.Printf("%-25s %-20s %-20s\n", "Version", inst1.Version, inst2.Version)
return nil
}func checkInstanceHealth(client *mastodon.Client) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
startTime := time.Now()
instance, err := client.GetInstance(ctx)
responseTime := time.Since(startTime)
fmt.Println("Instance Health Check")
fmt.Println(strings.Repeat("=", 50))
if err != nil {
fmt.Printf("❌ Instance unreachable: %v\n", err)
return err
}
fmt.Printf("✓ Instance reachable\n")
fmt.Printf(" Response time: %v\n", responseTime)
fmt.Printf(" Version: %s\n", instance.Version)
if instance.Stats != nil {
fmt.Printf(" Active users: %d\n", instance.Stats.UserCount)
fmt.Printf(" Total posts: %d\n", instance.Stats.StatusCount)
}
// Check activity
activities, err := client.GetInstanceActivity(ctx)
if err == nil && len(activities) > 0 {
recentActivity := activities[0]
if recentActivity.Statuses > 0 {
fmt.Printf("✓ Recent activity detected\n")
fmt.Printf(" Last week: %d posts, %d logins\n",
recentActivity.Statuses, recentActivity.Logins)
} else {
fmt.Printf("⚠ Low recent activity\n")
}
}
// Performance rating
if responseTime < 500*time.Millisecond {
fmt.Println("\n✓ Performance: Excellent")
} else if responseTime < 2*time.Second {
fmt.Println("\n✓ Performance: Good")
} else {
fmt.Println("\n⚠ Performance: Slow")
}
return nil
}func extractConfiguration(client *mastodon.Client) (map[string]interface{}, error) {
ctx := context.Background()
instance, err := client.GetInstance(ctx)
if err != nil {
return nil, err
}
config := make(map[string]interface{})
if instance.Configuration != nil {
if instance.Configuration.Statuses != nil {
config["status_max_chars"] = (*instance.Configuration.Statuses)["max_characters"]
config["status_max_media"] = (*instance.Configuration.Statuses)["max_media_attachments"]
}
if instance.Configuration.MediaAttachments != nil {
config["image_size_limit"] = instance.Configuration.MediaAttachments["image_size_limit"]
config["video_size_limit"] = instance.Configuration.MediaAttachments["video_size_limit"]
}
if instance.Configuration.Polls != nil {
config["poll_max_options"] = (*instance.Configuration.Polls)["max_options"]
config["poll_max_duration"] = (*instance.Configuration.Polls)["max_expiration"]
}
}
return config, nil
}Instance data changes infrequently:
type InstanceCache struct {
instance *mastodon.Instance
fetchedAt time.Time
ttl time.Duration
}
func (ic *InstanceCache) Get(client *mastodon.Client, ctx context.Context) (*mastodon.Instance, error) {
if time.Since(ic.fetchedAt) < ic.ttl && ic.instance != nil {
return ic.instance, nil
}
instance, err := client.GetInstance(ctx)
if err != nil {
return nil, err
}
ic.instance = instance
ic.fetchedAt = time.Now()
return instance, nil
}Verify limits before attempting operations:
config, _ := extractConfiguration(client)
if maxChars, ok := config["status_max_chars"].(float64); ok {
if len(statusText) > int(maxChars) {
return fmt.Errorf("status too long: max %d chars", int(maxChars))
}
}Not all instances provide full configuration:
if instance.Configuration == nil {
// Use sensible defaults
maxChars = 500
} else {
// Extract from config
}Regularly check instance availability:
ticker := time.NewTicker(5 * time.Minute)
for range ticker.C {
checkInstanceHealth(client)
}Instance endpoints count toward rate limits:
// Cache results
cachedInstance, _ := getCachedInstance()
// Don't poll too frequently
time.Sleep(time.Minute)See also:
Install with Tessl CLI
npx tessl i tessl/golang-go-mastodon