Skip to content

Go SDK

A comprehensive Go client library for the Splinterpic API. Supports Go 1.19+ with full type safety and idiomatic error handling.

Terminal window
go get github.com/your-org/splinterpic-go
package splinterpic
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
)
// Client represents a Splinterpic API client
type Client struct {
BaseURL string
DefaultModel string
HTTPClient *http.Client
}
// Config holds client configuration
type Config struct {
BaseURL string
DefaultModel string
Timeout time.Duration
}
// GenerateRequest represents an image generation request
type GenerateRequest struct {
Prompt string `json:"prompt"`
Model string `json:"model,omitempty"`
TemplateID *string `json:"template_id,omitempty"`
CollectionID *string `json:"collection_id,omitempty"`
}
// Image represents a generated image
type Image struct {
ID string `json:"id"`
Prompt string `json:"prompt"`
Model string `json:"model"`
R2Key string `json:"r2_key"`
R2URL string `json:"r2_url"`
CreatedAt time.Time `json:"created_at"`
UserID string `json:"user_id"`
Cost float64 `json:"cost"`
TemplateID *string `json:"template_id,omitempty"`
CollectionID *string `json:"collection_id,omitempty"`
}
// ImagesResponse represents a list of images
type ImagesResponse struct {
Images []Image `json:"images"`
Total int `json:"total"`
}
// Model represents an AI model
type Model struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
CostPerImage float64 `json:"cost_per_image"`
IsRecommended bool `json:"is_recommended"`
}
// ModelsResponse represents available models
type ModelsResponse struct {
Models []Model `json:"models"`
}
// Collection represents an image collection
type Collection struct {
ID string `json:"id"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
CreatedAt time.Time `json:"created_at"`
UserID string `json:"user_id"`
}
// CollectionsResponse represents a list of collections
type CollectionsResponse struct {
Collections []Collection `json:"collections"`
}
// Error types
type Error struct {
StatusCode int
Message string
}
func (e *Error) Error() string {
return fmt.Sprintf("splinterpic: %s (status %d)", e.Message, e.StatusCode)
}
// NewClient creates a new Splinterpic client
func NewClient(config Config) *Client {
if config.Timeout == 0 {
config.Timeout = 30 * time.Second
}
if config.DefaultModel == "" {
config.DefaultModel = "@cf/black-forest-labs/flux-1-schnell"
}
return &Client{
BaseURL: config.BaseURL,
DefaultModel: config.DefaultModel,
HTTPClient: &http.Client{
Timeout: config.Timeout,
},
}
}
// Generate creates a new AI-generated image
func (c *Client) Generate(ctx context.Context, req GenerateRequest) (*Image, error) {
if req.Model == "" {
req.Model = c.DefaultModel
}
var image Image
err := c.request(ctx, "POST", "/api/generate", req, &image)
if err != nil {
return nil, err
}
return &image, nil
}
// Images retrieves a list of images
func (c *Client) Images(ctx context.Context, filters map[string]string) (*ImagesResponse, error) {
var response ImagesResponse
err := c.requestWithQuery(ctx, "GET", "/api/images", filters, &response)
if err != nil {
return nil, err
}
return &response, nil
}
// Image retrieves a single image by ID
func (c *Client) Image(ctx context.Context, imageID string) (*Image, error) {
var image Image
path := fmt.Sprintf("/api/images/%s", imageID)
err := c.request(ctx, "GET", path, nil, &image)
if err != nil {
return nil, err
}
return &image, nil
}
// DeleteImage deletes an image by ID
func (c *Client) DeleteImage(ctx context.Context, imageID string) error {
path := fmt.Sprintf("/api/images/%s", imageID)
return c.request(ctx, "DELETE", path, nil, nil)
}
// Models retrieves available AI models
func (c *Client) Models(ctx context.Context) (*ModelsResponse, error) {
var response ModelsResponse
err := c.request(ctx, "GET", "/api/models", nil, &response)
if err != nil {
return nil, err
}
return &response, nil
}
// CreateCollection creates a new image collection
func (c *Client) CreateCollection(ctx context.Context, name string, description *string) (*Collection, error) {
req := map[string]interface{}{
"name": name,
}
if description != nil {
req["description"] = *description
}
var collection Collection
err := c.request(ctx, "POST", "/api/collections", req, &collection)
if err != nil {
return nil, err
}
return &collection, nil
}
// Collections retrieves all collections
func (c *Client) Collections(ctx context.Context) (*CollectionsResponse, error) {
var response CollectionsResponse
err := c.request(ctx, "GET", "/api/collections", nil, &response)
if err != nil {
return nil, err
}
return &response, nil
}
// request makes an HTTP request to the API
func (c *Client) request(ctx context.Context, method, path string, body interface{}, result interface{}) error {
return c.requestWithQuery(ctx, method, path, nil, result)
}
// requestWithQuery makes an HTTP request with query parameters
func (c *Client) requestWithQuery(ctx context.Context, method, path string, query map[string]string, result interface{}) error {
u, err := url.Parse(c.BaseURL + path)
if err != nil {
return fmt.Errorf("invalid URL: %w", err)
}
if query != nil {
q := u.Query()
for k, v := range query {
q.Set(k, v)
}
u.RawQuery = q.Encode()
}
var reqBody io.Reader
if body != nil {
jsonData, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("failed to marshal request: %w", err)
}
reqBody = bytes.NewBuffer(jsonData)
}
req, err := http.NewRequestWithContext(ctx, method, u.String(), reqBody)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.HTTPClient.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode >= 400 {
var apiError struct {
Error string `json:"error"`
}
json.Unmarshal(respBody, &apiError)
return &Error{
StatusCode: resp.StatusCode,
Message: apiError.Error,
}
}
if result != nil && resp.StatusCode != http.StatusNoContent {
if err := json.Unmarshal(respBody, result); err != nil {
return fmt.Errorf("failed to unmarshal response: %w", err)
}
}
return nil
}
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/your-org/splinterpic-go"
)
func main() {
// Initialize client
client := splinterpic.NewClient(splinterpic.Config{
BaseURL: "https://splinterpic-worker.your-name.workers.dev",
DefaultModel: "@cf/black-forest-labs/flux-1-schnell",
Timeout: 30 * time.Second,
})
ctx := context.Background()
// Generate an image
image, err := client.Generate(ctx, splinterpic.GenerateRequest{
Prompt: "A serene mountain landscape at sunset",
})
if err != nil {
log.Fatalf("Failed to generate image: %v", err)
}
fmt.Printf("Image generated successfully!\n")
fmt.Printf("ID: %s\n", image.ID)
fmt.Printf("URL: %s\n", image.R2URL)
fmt.Printf("Cost: $%.4f\n", image.Cost)
}
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"os"
"time"
"github.com/your-org/splinterpic-go"
)
type Server struct {
client *splinterpic.Client
}
func NewServer() *Server {
client := splinterpic.NewClient(splinterpic.Config{
BaseURL: os.Getenv("SPLINTERPIC_BASE_URL"),
Timeout: 30 * time.Second,
})
return &Server{client: client}
}
func (s *Server) handleGenerate(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req splinterpic.GenerateRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
defer cancel()
image, err := s.client.Generate(ctx, req)
if err != nil {
log.Printf("Generation error: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"image": image,
})
}
func (s *Server) handleImages(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
filters := make(map[string]string)
if limit := r.URL.Query().Get("limit"); limit != "" {
filters["limit"] = limit
}
if offset := r.URL.Query().Get("offset"); offset != "" {
filters["offset"] = offset
}
images, err := s.client.Images(ctx, filters)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(images)
}
func main() {
server := NewServer()
http.HandleFunc("/api/generate", server.handleGenerate)
http.HandleFunc("/api/images", server.handleImages)
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
package main
import (
"context"
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
"github.com/your-org/splinterpic-go"
)
func main() {
client := splinterpic.NewClient(splinterpic.Config{
BaseURL: os.Getenv("SPLINTERPIC_BASE_URL"),
Timeout: 30 * time.Second,
})
r := gin.Default()
// Generate image endpoint
r.POST("/api/generate", func(c *gin.Context) {
var req splinterpic.GenerateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
image, err := client.Generate(ctx, req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"image": image,
})
})
// List images endpoint
r.GET("/api/images", func(c *gin.Context) {
filters := map[string]string{
"limit": c.DefaultQuery("limit", "20"),
"offset": c.DefaultQuery("offset", "0"),
}
images, err := client.Images(c.Request.Context(), filters)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, images)
})
// Get single image
r.GET("/api/images/:id", func(c *gin.Context) {
imageID := c.Param("id")
image, err := client.Image(c.Request.Context(), imageID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Image not found"})
return
}
c.JSON(http.StatusOK, image)
})
r.Run(":8080")
}
package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/your-org/splinterpic-go"
)
type BatchResult struct {
Image *splinterpic.Image
Error error
}
func GenerateBatch(client *splinterpic.Client, prompts []string, maxConcurrent int) []BatchResult {
ctx := context.Background()
results := make([]BatchResult, len(prompts))
// Semaphore to limit concurrent requests
sem := make(chan struct{}, maxConcurrent)
var wg sync.WaitGroup
for i, prompt := range prompts {
wg.Add(1)
go func(index int, p string) {
defer wg.Done()
sem <- struct{}{} // Acquire
defer func() { <-sem }() // Release
image, err := client.Generate(ctx, splinterpic.GenerateRequest{
Prompt: p,
})
results[index] = BatchResult{
Image: image,
Error: err,
}
}(i, prompt)
}
wg.Wait()
return results
}
func main() {
client := splinterpic.NewClient(splinterpic.Config{
BaseURL: "https://splinterpic-worker.your-name.workers.dev",
})
prompts := []string{
"Mountain landscape at sunset",
"Ocean waves on beach",
"City skyline at night",
"Forest path in autumn",
}
results := GenerateBatch(client, prompts, 3)
for i, result := range results {
if result.Error != nil {
fmt.Printf("Prompt %d failed: %v\n", i, result.Error)
} else {
fmt.Printf("Prompt %d generated: %s\n", i, result.Image.ID)
}
}
}
package main
import (
"context"
"fmt"
"time"
"github.com/your-org/splinterpic-go"
)
type RetryConfig struct {
MaxRetries int
InitialBackoff time.Duration
MaxBackoff time.Duration
}
func GenerateWithRetry(client *splinterpic.Client, req splinterpic.GenerateRequest, config RetryConfig) (*splinterpic.Image, error) {
ctx := context.Background()
backoff := config.InitialBackoff
for attempt := 0; attempt <= config.MaxRetries; attempt++ {
image, err := client.Generate(ctx, req)
if err == nil {
return image, nil
}
// Check if error is retryable
if apiErr, ok := err.(*splinterpic.Error); ok {
if apiErr.StatusCode == 429 || apiErr.StatusCode >= 500 {
// Rate limit or server error - retry
if attempt < config.MaxRetries {
fmt.Printf("Attempt %d failed, retrying in %v...\n", attempt+1, backoff)
time.Sleep(backoff)
// Exponential backoff
backoff *= 2
if backoff > config.MaxBackoff {
backoff = config.MaxBackoff
}
continue
}
}
}
return nil, err
}
return nil, fmt.Errorf("max retries exceeded")
}
func main() {
client := splinterpic.NewClient(splinterpic.Config{
BaseURL: "https://splinterpic-worker.your-name.workers.dev",
})
image, err := GenerateWithRetry(client, splinterpic.GenerateRequest{
Prompt: "Test image",
}, RetryConfig{
MaxRetries: 3,
InitialBackoff: 2 * time.Second,
MaxBackoff: 30 * time.Second,
})
if err != nil {
fmt.Printf("Failed after retries: %v\n", err)
return
}
fmt.Printf("Generated: %s\n", image.ID)
}
package main
import (
"context"
"fmt"
"sync"
"github.com/your-org/splinterpic-go"
)
type Job struct {
Prompt string
Result chan<- BatchResult
}
type WorkerPool struct {
client *splinterpic.Client
jobs chan Job
numWorkers int
wg sync.WaitGroup
}
func NewWorkerPool(client *splinterpic.Client, numWorkers int) *WorkerPool {
return &WorkerPool{
client: client,
jobs: make(chan Job, numWorkers*2),
numWorkers: numWorkers,
}
}
func (p *WorkerPool) Start(ctx context.Context) {
for i := 0; i < p.numWorkers; i++ {
p.wg.Add(1)
go p.worker(ctx, i)
}
}
func (p *WorkerPool) worker(ctx context.Context, id int) {
defer p.wg.Done()
for job := range p.jobs {
image, err := p.client.Generate(ctx, splinterpic.GenerateRequest{
Prompt: job.Prompt,
})
job.Result <- BatchResult{
Image: image,
Error: err,
}
}
}
func (p *WorkerPool) Submit(job Job) {
p.jobs <- job
}
func (p *WorkerPool) Stop() {
close(p.jobs)
p.wg.Wait()
}
func main() {
client := splinterpic.NewClient(splinterpic.Config{
BaseURL: "https://splinterpic-worker.your-name.workers.dev",
})
pool := NewWorkerPool(client, 5)
ctx := context.Background()
pool.Start(ctx)
prompts := []string{
"Mountain at sunset",
"Ocean waves",
"City at night",
}
results := make(chan BatchResult, len(prompts))
for _, prompt := range prompts {
pool.Submit(Job{
Prompt: prompt,
Result: results,
})
}
pool.Stop()
close(results)
for result := range results {
if result.Error != nil {
fmt.Printf("Error: %v\n", result.Error)
} else {
fmt.Printf("Generated: %s\n", result.Image.ID)
}
}
}
package main
import (
"context"
"fmt"
"time"
"github.com/your-org/splinterpic-go"
)
func GenerateWithTimeout(client *splinterpic.Client, prompt string, timeout time.Duration) (*splinterpic.Image, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
image, err := client.Generate(ctx, splinterpic.GenerateRequest{
Prompt: prompt,
})
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
return nil, fmt.Errorf("request timed out after %v", timeout)
}
return nil, err
}
return image, nil
}
func main() {
client := splinterpic.NewClient(splinterpic.Config{
BaseURL: "https://splinterpic-worker.your-name.workers.dev",
Timeout: 60 * time.Second, // Overall client timeout
})
// Generate with custom timeout
image, err := GenerateWithTimeout(client, "Test image", 15*time.Second)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Generated: %s\n", image.ID)
}
package splinterpic_test
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/your-org/splinterpic-go"
)
func TestGenerate(t *testing.T) {
// Mock server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/generate" {
t.Errorf("Expected path /api/generate, got %s", r.URL.Path)
}
if r.Method != http.MethodPost {
t.Errorf("Expected POST method, got %s", r.Method)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{
"id": "img_test123",
"prompt": "Test prompt",
"model": "@cf/black-forest-labs/flux-1-schnell",
"r2_url": "https://example.com/image.png",
"cost": 0.01
}`))
}))
defer server.Close()
client := splinterpic.NewClient(splinterpic.Config{
BaseURL: server.URL,
})
ctx := context.Background()
image, err := client.Generate(ctx, splinterpic.GenerateRequest{
Prompt: "Test prompt",
})
if err != nil {
t.Fatalf("Generate failed: %v", err)
}
if image.ID != "img_test123" {
t.Errorf("Expected ID img_test123, got %s", image.ID)
}
if image.Prompt != "Test prompt" {
t.Errorf("Expected prompt 'Test prompt', got %s", image.Prompt)
}
}
func TestErrorHandling(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error": "Invalid prompt"}`))
}))
defer server.Close()
client := splinterpic.NewClient(splinterpic.Config{
BaseURL: server.URL,
})
ctx := context.Background()
_, err := client.Generate(ctx, splinterpic.GenerateRequest{
Prompt: "",
})
if err == nil {
t.Fatal("Expected error, got nil")
}
apiErr, ok := err.(*splinterpic.Error)
if !ok {
t.Fatalf("Expected *splinterpic.Error, got %T", err)
}
if apiErr.StatusCode != 400 {
t.Errorf("Expected status code 400, got %d", apiErr.StatusCode)
}
}
// Good - allows cancellation and timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
image, err := client.Generate(ctx, req)
// Bad - no timeout or cancellation
image, err := client.Generate(context.Background(), req)
image, err := client.Generate(ctx, req)
if err != nil {
if apiErr, ok := err.(*splinterpic.Error); ok {
switch apiErr.StatusCode {
case 429:
// Rate limited - wait and retry
time.Sleep(5 * time.Second)
// retry logic...
case 402:
// Quota exceeded - notify admin
log.Printf("Quota exceeded: %v", apiErr)
default:
log.Printf("API error: %v", apiErr)
}
}
return err
}
import "os"
client := splinterpic.NewClient(splinterpic.Config{
BaseURL: os.Getenv("SPLINTERPIC_BASE_URL"),
Timeout: 30 * time.Second,
})
func main() {
client := splinterpic.NewClient(splinterpic.Config{
BaseURL: os.Getenv("SPLINTERPIC_BASE_URL"),
})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Listen for interrupt signal
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigChan
log.Println("Shutting down gracefully...")
cancel()
}()
// Use ctx for all operations
image, err := client.Generate(ctx, splinterpic.GenerateRequest{
Prompt: "Test",
})
}