Go
Integrate PostPost API with Go using the standard library and best practices.
Installation
No external dependencies required. Uses Go's standard net/http package.
go mod init your-projectBasic Client
// postpost/client.go
package postpost
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
const BaseURL = "https://api.postpost.dev/api/v1"
type Client struct {
apiKey string
userId string
httpClient *http.Client
}
func NewClient(apiKey string) *Client {
return &Client{
apiKey: apiKey,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
func NewWorkspaceClient(apiKey, userId string) *Client {
return &Client{
apiKey: apiKey,
userId: userId,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
func (c *Client) request(method, endpoint string, body interface{}) ([]byte, error) {
var reqBody io.Reader
if body != nil {
jsonData, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
reqBody = bytes.NewBuffer(jsonData)
}
req, err := http.NewRequest(method, BaseURL+endpoint, reqBody)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-api-key", c.apiKey)
if c.userId != "" {
req.Header.Set("x-postpost-user-id", c.userId)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode >= 400 {
var apiErr APIError
json.Unmarshal(respBody, &apiErr)
return nil, &apiErr
}
return respBody, nil
}Types
// postpost/types.go
package postpost
import "fmt"
type APIError struct {
StatusCode int `json:"-"`
ErrorText string `json:"error,omitempty"`
Message string `json:"message,omitempty"`
}
func (e *APIError) Error() string {
msg := e.ErrorText
if msg == "" {
msg = e.Message
}
return fmt.Sprintf("API error (%d): %s", e.StatusCode, msg)
}
type PlatformConnection struct {
PlatformID string `json:"platformId"`
Platform string `json:"platform"`
Username string `json:"username"`
DisplayName string `json:"displayName"`
ProfileImage string `json:"profileImageUrl,omitempty"`
ExpiresAt string `json:"accessTokenExpiresAt,omitempty"`
}
type Post struct {
ID string `json:"_id"`
Platform string `json:"platform"`
PlatformID string `json:"platformId"`
Content string `json:"content"`
Status string `json:"status"`
PostedID string `json:"postedId,omitempty"`
PublishedURL string `json:"publishedUrl,omitempty"`
Error string `json:"error,omitempty"`
}
type PostGroup struct {
PostGroupID string `json:"postGroupId"`
Content string `json:"content"`
Status string `json:"status"`
ScheduledTime string `json:"scheduledTime,omitempty"`
Posts []Post `json:"posts"`
}
type CreatePostRequest struct {
Content string `json:"content"`
Platforms []string `json:"platforms"`
ScheduledTime string `json:"scheduledTime,omitempty"`
Status string `json:"status,omitempty"`
PlatformSettings *PlatformSettings `json:"platformSettings,omitempty"`
}
type PlatformSettings struct {
Instagram *InstagramSettings `json:"instagram,omitempty"`
TikTok *TikTokSettings `json:"tiktok,omitempty"`
Telegram *TelegramSettings `json:"telegram,omitempty"`
}
type InstagramSettings struct {
VideoType string `json:"videoType,omitempty"`
}
type TikTokSettings struct {
DisableDuet bool `json:"disableDuet,omitempty"`
DisableStitch bool `json:"disableStitch,omitempty"`
DisableComment bool `json:"disableComment,omitempty"`
}
type TelegramSettings struct {
ParseMode string `json:"parseMode,omitempty"`
DisableWebPagePreview bool `json:"disableWebPagePreview,omitempty"`
}
type CreatePostResponse struct {
Success bool `json:"success"`
PostGroupID string `json:"postGroupId"`
Posts []struct {
Platform string `json:"platform"`
PlatformID string `json:"platformId"`
Status string `json:"status"`
} `json:"posts"`
}
type UploadURLResponse struct {
UploadURL string `json:"uploadUrl"`
FileURL string `json:"fileUrl"`
MediaID string `json:"mediaId"`
}
type LinkedInStatsResponse struct {
Success bool `json:"success"`
Metrics LinkedInMetrics `json:"metrics"`
Cached bool `json:"cached"`
}
type LinkedInMetrics struct {
Impression int `json:"IMPRESSION"`
MembersReached int `json:"MEMBERS_REACHED"`
Reshare int `json:"RESHARE"`
Reaction int `json:"REACTION"`
Comment int `json:"COMMENT"`
}API Methods
// postpost/api.go
package postpost
import "encoding/json"
func (c *Client) GetConnections() ([]PlatformConnection, error) {
data, err := c.request("GET", "/platform-connections", nil)
if err != nil {
return nil, err
}
var result struct {
Connections []PlatformConnection `json:"connections"`
}
if err := json.Unmarshal(data, &result); err != nil {
return nil, err
}
return result.Connections, nil
}
func (c *Client) CreatePost(req *CreatePostRequest) (*CreatePostResponse, error) {
data, err := c.request("POST", "/create-post", req)
if err != nil {
return nil, err
}
var result CreatePostResponse
if err := json.Unmarshal(data, &result); err != nil {
return nil, err
}
return &result, nil
}
func (c *Client) GetPost(postGroupID string) (*PostGroup, error) {
data, err := c.request("GET", "/get-post/"+postGroupID, nil)
if err != nil {
return nil, err
}
var result PostGroup
if err := json.Unmarshal(data, &result); err != nil {
return nil, err
}
return &result, nil
}
func (c *Client) UpdatePost(postGroupID string, updates *CreatePostRequest) (*PostGroup, error) {
data, err := c.request("PUT", "/update-post/"+postGroupID, updates)
if err != nil {
return nil, err
}
var result PostGroup
if err := json.Unmarshal(data, &result); err != nil {
return nil, err
}
return &result, nil
}
func (c *Client) DeletePost(postGroupID string) error {
_, err := c.request("DELETE", "/delete-post/"+postGroupID, nil)
return err
}
func (c *Client) GetUploadURL(fileName, contentType, postGroupID string) (*UploadURLResponse, error) {
data, err := c.request("POST", "/get-upload-url", map[string]string{
"fileName": fileName,
"contentType": contentType,
"postGroupId": postGroupID,
})
if err != nil {
return nil, err
}
var result UploadURLResponse
if err := json.Unmarshal(data, &result); err != nil {
return nil, err
}
return &result, nil
}
func (c *Client) GetLinkedInPostStats(platformID, postedID string) (*LinkedInStatsResponse, error) {
data, err := c.request("POST", "/linkedin-post-statistics", map[string]string{
"platformId": platformID,
"postedId": postedID,
"queryTypes": "ALL",
})
if err != nil {
return nil, err
}
var result LinkedInStatsResponse
if err := json.Unmarshal(data, &result); err != nil {
return nil, err
}
return &result, nil
}Usage Examples
List Connections
package main
import (
"fmt"
"log"
"os"
"your-project/postpost"
)
func main() {
client := postpost.NewClient(os.Getenv("PUBLORA_API_KEY"))
connections, err := client.GetConnections()
if err != nil {
log.Fatalf("Failed to get connections: %v", err)
}
fmt.Printf("Found %d connected accounts:\n", len(connections))
for _, conn := range connections {
fmt.Printf(" - %s: %s (%s)\n", conn.Platform, conn.Username, conn.PlatformID)
}
}Create a Post
package main
import (
"fmt"
"log"
"os"
"time"
"your-project/postpost"
)
func main() {
client := postpost.NewClient(os.Getenv("PUBLORA_API_KEY"))
// Schedule for 1 hour from now
scheduledTime := time.Now().Add(time.Hour).UTC().Format(time.RFC3339)
resp, err := client.CreatePost(&postpost.CreatePostRequest{
Content: "Hello from Go! Building with PostPost API.",
Platforms: []string{"twitter-123456789", "linkedin-ABC123DEF"},
ScheduledTime: scheduledTime,
})
if err != nil {
log.Fatalf("Failed to create post: %v", err)
}
fmt.Printf("Post created: %s\n", resp.PostGroupID)
for _, post := range resp.Posts {
fmt.Printf(" - %s: %s\n", post.Platform, post.Status)
}
}Schedule Multiple Posts
package main
import (
"fmt"
"log"
"os"
"time"
"your-project/postpost"
)
type ScheduledPost struct {
Content string
Platforms []string
DayOffset int
}
func main() {
client := postpost.NewClient(os.Getenv("PUBLORA_API_KEY"))
posts := []ScheduledPost{
{"Monday motivation: Start strong!", []string{"twitter-123456789"}, 0},
{"Tech tip Tuesday: Version your APIs!", []string{"twitter-123456789", "linkedin-ABC123DEF"}, 1},
{"Wednesday wisdom: Ship fast, iterate faster.", []string{"twitter-123456789"}, 2},
{"Throwback Thursday: How we grew to 10K users.", []string{"linkedin-ABC123DEF"}, 3},
{"Feature Friday: Check out our new dashboard!", []string{"twitter-123456789", "linkedin-ABC123DEF"}, 4},
}
baseTime := time.Now().Truncate(24 * time.Hour).Add(9 * time.Hour) // 9 AM
for _, post := range posts {
scheduledTime := baseTime.AddDate(0, 0, post.DayOffset).Format(time.RFC3339)
resp, err := client.CreatePost(&postpost.CreatePostRequest{
Content: post.Content,
Platforms: post.Platforms,
ScheduledTime: scheduledTime,
})
if err != nil {
log.Printf("Failed to schedule post: %v", err)
continue
}
fmt.Printf("Scheduled: %s -> %s\n", post.Content[:30], resp.PostGroupID)
time.Sleep(200 * time.Millisecond) // Rate limiting
}
}Post with Instagram Reel Settings
package main
import (
"fmt"
"log"
"os"
"your-project/postpost"
)
func main() {
client := postpost.NewClient(os.Getenv("PUBLORA_API_KEY"))
resp, err := client.CreatePost(&postpost.CreatePostRequest{
Content: "Behind the scenes! #buildinpublic",
Platforms: []string{"instagram-789012345"},
PlatformSettings: &postpost.PlatformSettings{
Instagram: &postpost.InstagramSettings{
VideoType: "REELS",
},
},
})
if err != nil {
log.Fatalf("Failed to create reel: %v", err)
}
fmt.Printf("Instagram Reel scheduled: %s\n", resp.PostGroupID)
}Upload Media and Post
package main
import (
"bytes"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"your-project/postpost"
)
func getMimeType(filename string) string {
ext := filepath.Ext(filename)
mimeTypes := map[string]string{
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
".mp4": "video/mp4",
".webp": "image/webp",
}
if mime, ok := mimeTypes[ext]; ok {
return mime
}
return "application/octet-stream"
}
func main() {
client := postpost.NewClient(os.Getenv("PUBLORA_API_KEY"))
filePath := "./screenshot.png"
// Step 1: Create post first to get postGroupId
postResp, err := client.CreatePost(&postpost.CreatePostRequest{
Content: "Check out this screenshot!",
Platforms: []string{"twitter-123456789", "linkedin-ABC123DEF"},
})
if err != nil {
log.Fatalf("Failed to create post: %v", err)
}
postGroupID := postResp.PostGroupID
fmt.Printf("Post created: %s\n", postGroupID)
// Step 2: Get upload URL (media auto-attaches via postGroupId)
fileName := filepath.Base(filePath)
contentType := getMimeType(fileName)
uploadResp, err := client.GetUploadURL(fileName, contentType, postGroupID)
if err != nil {
log.Fatalf("Failed to get upload URL: %v", err)
}
// Step 3: Upload file to S3
fileData, err := os.ReadFile(filePath)
if err != nil {
log.Fatalf("Failed to read file: %v", err)
}
req, _ := http.NewRequest("PUT", uploadResp.UploadURL, bytes.NewReader(fileData))
req.Header.Set("Content-Type", contentType)
httpClient := &http.Client{}
resp, err := httpClient.Do(req)
if err != nil {
log.Fatalf("Failed to upload file: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
body, _ := io.ReadAll(resp.Body)
log.Fatalf("Upload failed: %s", body)
}
fmt.Printf("Media uploaded: %s\n", uploadResp.MediaID)
fmt.Printf("File URL: %s\n", uploadResp.FileURL)
fmt.Printf("Post with media created: %s\n", postGroupID)
}Error Handling
package main
import (
"errors"
"fmt"
"log"
"os"
"your-project/postpost"
)
func main() {
client := postpost.NewClient(os.Getenv("PUBLORA_API_KEY"))
_, err := client.CreatePost(&postpost.CreatePostRequest{
Content: "Test post",
Platforms: []string{"twitter-123456789"},
})
if err != nil {
var apiErr *postpost.APIError
if errors.As(err, &apiErr) {
switch apiErr.StatusCode {
case 401:
fmt.Println("Authentication failed. Check your API key.")
case 403:
fmt.Printf("Access denied: %s\n", apiErr.Message)
case 400:
fmt.Printf("Bad request: %s\n", apiErr.Message)
case 404:
fmt.Printf("Not found: %s\n", apiErr.Message)
case 429:
fmt.Println("Rate limited. Retry after delay.")
case 500:
fmt.Println("Server error. Retry later.")
default:
fmt.Printf("API error (%d): %s\n", apiErr.StatusCode, apiErr.Message)
}
} else {
log.Fatalf("Network error: %v", err)
}
return
}
fmt.Println("Post created successfully!")
}Monitor Post Status
package main
import (
"fmt"
"log"
"os"
"time"
"your-project/postpost"
)
func waitForPublish(client *postpost.Client, postGroupID string, maxAttempts int) (*postpost.PostGroup, error) {
for attempt := 0; attempt < maxAttempts; attempt++ {
postGroup, err := client.GetPost(postGroupID)
if err != nil {
return nil, err
}
fmt.Printf("[%d/%d] Status: %s\n", attempt+1, maxAttempts, postGroup.Status)
switch postGroup.Status {
case "published":
fmt.Println("All platforms published successfully!")
return postGroup, nil
case "partially_published":
fmt.Println("Partial success:")
for _, post := range postGroup.Posts {
if post.Status == "failed" {
fmt.Printf(" - %s: FAILED - %s\n", post.Platform, post.Error)
} else {
fmt.Printf(" - %s: %s\n", post.Platform, post.Status)
}
}
return postGroup, nil
case "failed":
fmt.Println("All platforms failed:")
for _, post := range postGroup.Posts {
fmt.Printf(" - %s: %s\n", post.Platform, post.Error)
}
return postGroup, nil
}
time.Sleep(10 * time.Second)
}
return nil, fmt.Errorf("timeout waiting for post %s to publish", postGroupID)
}
func main() {
client := postpost.NewClient(os.Getenv("PUBLORA_API_KEY"))
resp, err := client.CreatePost(&postpost.CreatePostRequest{
Content: "Publishing now!",
Platforms: []string{"twitter-123456789"},
})
if err != nil {
log.Fatalf("Failed to create post: %v", err)
}
postGroup, err := waitForPublish(client, resp.PostGroupID, 30)
if err != nil {
log.Fatalf("Failed to monitor post: %v", err)
}
fmt.Printf("Final status: %s\n", postGroup.Status)
}Concurrent Batch Operations
package main
import (
"fmt"
"os"
"sync"
"your-project/postpost"
)
func main() {
client := postpost.NewClient(os.Getenv("PUBLORA_API_KEY"))
postGroupIDs := []string{"pg_abc123", "pg_def456", "pg_ghi789", "pg_jkl012"}
var wg sync.WaitGroup
results := make(chan struct {
id string
status string
err error
}, len(postGroupIDs))
// Limit concurrency
sem := make(chan struct{}, 3)
for _, id := range postGroupIDs {
wg.Add(1)
go func(postGroupID string) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
postGroup, err := client.GetPost(postGroupID)
if err != nil {
results <- struct {
id string
status string
err error
}{postGroupID, "", err}
return
}
results <- struct {
id string
status string
err error
}{postGroupID, postGroup.Status, nil}
}(id)
}
go func() {
wg.Wait()
close(results)
}()
for result := range results {
if result.err != nil {
fmt.Printf("%s: ERROR - %v\n", result.id, result.err)
} else {
fmt.Printf("%s: %s\n", result.id, result.status)
}
}
}LinkedIn Analytics
package main
import (
"fmt"
"log"
"os"
"your-project/postpost"
)
func main() {
client := postpost.NewClient(os.Getenv("PUBLORA_API_KEY"))
platformID := "linkedin-ABC123DEF"
postedIDs := []string{
"urn:li:share:7123456789012345678",
"urn:li:share:7234567890123456789",
}
fmt.Println("=== LinkedIn Analytics Report ===")
var totalImpressions, totalEngagement int
for _, postedID := range postedIDs {
result, err := client.GetLinkedInPostStats(platformID, postedID)
if err != nil {
log.Printf("Error fetching stats for %s: %v", postedID, err)
continue
}
metrics := result.Metrics
engagement := metrics.Reaction + metrics.Comment + metrics.Reshare
totalImpressions += metrics.Impression
totalEngagement += engagement
fmt.Printf("\nPost: %s\n", postedID)
fmt.Printf(" Impressions: %d\n", metrics.Impression)
fmt.Printf(" Members Reached: %d\n", metrics.MembersReached)
fmt.Printf(" Engagement: %d (%d reactions, %d comments, %d reshares)\n",
engagement, metrics.Reaction, metrics.Comment, metrics.Reshare)
}
fmt.Println("\n=== TOTALS ===")
fmt.Printf("Total Impressions: %d\n", totalImpressions)
fmt.Printf("Total Engagement: %d\n", totalEngagement)
if totalImpressions > 0 {
fmt.Printf("Avg Engagement Rate: %.2f%%\n", float64(totalEngagement)/float64(totalImpressions)*100)
}
}PostPost — Social media API with free tier, paid plans from $2.99/account