Go SDK
Official Go client library for RelayAPI. Post to 22 platforms with idiomatic, fully-typed Go.
Official Go SDK for RelayAPI. Post to 22 platforms, manage accounts, upload media, and track analytics.
The Go client is generated and released separately from the RelayAPI monorepo. Check the relay-go repository for the surface and version in the release you install.
Setup
Install
go get -u github.com/relayapi-dev/relay-goRequires Go 1.22 or later.
Authenticate
import (
relaygo "github.com/relayapi-dev/relay-go"
"github.com/relayapi-dev/relay-go/option"
)
client := relaygo.NewClient() // reads RELAY_API_KEY from environmentOr pass the key directly:
client := relaygo.NewClient(
option.WithAPIKey("rlay_live_..."),
)Get your API key from relayapi.dev under Settings > API Keys.
Quick Examples
Request fields are wrapped with relaygo.F(...) so the SDK can distinguish between an unset field and a zero value.
Create and Publish a Post
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
Content: relaygo.F("Hello from the Go SDK!"),
Targets: relaygo.F([]string{"twitter", "linkedin"}),
ScheduledAt: relaygo.F("now"),
})
if err != nil {
panic(err.Error())
}
fmt.Printf("Post created: %s — status: %s\n", post.ID, post.Status)Schedule a Post
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
Content: relaygo.F("This will go out tomorrow morning"),
Targets: relaygo.F([]string{"twitter", "linkedin"}),
ScheduledAt: relaygo.F("2025-01-15T09:00:00Z"),
Timezone: relaygo.F("America/New_York"),
})Save as Draft
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
Content: relaygo.F("Work in progress..."),
Targets: relaygo.F([]string{"instagram"}),
ScheduledAt: relaygo.F("draft"),
})List Connected Accounts
accounts, err := client.Accounts.List(context.TODO(), relaygo.AccountListParams{})
if err != nil {
panic(err.Error())
}
for _, acc := range accounts.Data {
fmt.Printf("%s: %s (%s)\n", acc.Platform, acc.DisplayName, acc.ID)
}Upload Media
import (
"context"
"net/http"
"net/url"
"os"
"strings"
)
// Get a presigned upload URL
presign, err := client.Media.GetPresignURL(context.TODO(), relaygo.MediaGetPresignURLParams{
Filename: relaygo.F("photo.jpg"),
ContentType: relaygo.F("image/jpeg"),
})
if err != nil {
panic(err.Error())
}
// PUT the bytes once using both headers required by the presign contract
file, err := os.Open("photo.jpg")
if err != nil {
panic(err.Error())
}
defer file.Close()
request, err := http.NewRequestWithContext(context.TODO(), http.MethodPut, presign.UploadURL, file)
if err != nil {
panic(err.Error())
}
request.Header.Set("Content-Type", "image/jpeg")
request.Header.Set("If-None-Match", "*")
upload, err := http.DefaultClient.Do(request)
if err != nil {
panic(err.Error())
}
defer upload.Body.Close()
if upload.StatusCode < 200 || upload.StatusCode >= 300 {
panic(upload.Status)
}
// Confirm the object so the pending upload becomes ready
mediaURL, err := url.Parse(presign.URL)
if err != nil {
panic(err.Error())
}
storageKey := strings.TrimPrefix(mediaURL.Path, "/")
confirmed, err := client.Media.Confirm(context.TODO(), relaygo.MediaConfirmParams{
StorageKey: relaygo.F(storageKey),
})
if err != nil {
panic(err.Error())
}
if confirmed.ID != presign.ID {
panic("confirmed the wrong media intent")
}
// Posts take a media object with the canonical URL, not the media ID
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
Content: relaygo.F("Check out this photo!"),
Targets: relaygo.F([]string{"instagram"}),
Media: relaygo.F([]relaygo.PostNewParamsMedia{{
URL: relaygo.F(presign.URL),
Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage),
}}),
ScheduledAt: relaygo.F("now"),
})Cross-Post with Per-Platform Content
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
Content: relaygo.F("Default content for all platforms"),
Targets: relaygo.F([]string{"twitter", "linkedin", "bluesky"}),
TargetOptions: relaygo.F(map[string]map[string]interface{}{
"twitter": {"content": "Short version for Twitter"},
"linkedin": {"content": "Longer professional version for LinkedIn"},
}),
ScheduledAt: relaygo.F("now"),
})Get Post Analytics
analytics, err := client.Analytics.Get(context.TODO(), relaygo.AnalyticsGetParams{
PostID: relaygo.F("post_abc123"),
})
fmt.Printf("%+v\n", analytics)Check Account Health
// All accounts
health, err := client.Accounts.Health.List(context.TODO())
// A single account by ID
acc, err := client.Accounts.Health.Get(context.TODO(), "acc_twitter_123")Error Handling
When the API returns a non-success status code, the error can be inspected as a *relaygo.Error:
_, err := client.Posts.List(context.TODO(), relaygo.PostListParams{})
if err != nil {
var apierr *relaygo.Error
if errors.As(err, &apierr) {
println(string(apierr.DumpRequest(true)))
println(string(apierr.DumpResponse(true)))
}
panic(err.Error())
}Configuration
Set your API key via the RELAY_API_KEY environment variable, or pass option.WithAPIKey(...) / option.WithBaseURL(...) to NewClient.
Timeouts
Pass a context with a deadline to bound any request:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
client.Posts.List(ctx, relaygo.PostListParams{})Links
Found something wrong? Help us improve this page.