Skip to main content

Go Examples

Complete code examples for using the TakeTheme API with Go.

Setup

Basic Client

package taketheme

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
)

const BaseURL = "https://api.taketheme.com/api/v1"

type Client struct {
apiKey string
httpClient *http.Client
}

type APIError struct {
Message string `json:"message"`
Name string `json:"name,omitempty"` // Used in 404 responses
Status int `json:"status"`
Code string `json:"code,omitempty"` // Only present on some errors like rate limits
}

func (e *APIError) Error() string {
msg := e.Message
if msg == "" {
msg = e.Name
}
return fmt.Sprintf("status %d: %s", e.Status, msg)
}

func NewClient(apiKey string) *Client {
return &Client{
apiKey: apiKey,
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, err
}
reqBody = bytes.NewBuffer(jsonData)
}

req, err := http.NewRequest(method, BaseURL+endpoint, reqBody)
if err != nil {
return nil, err
}

req.Header.Set("tt-api-key", "+c.apiKey)
req.Header.Set("Content-Type", "application/json")

resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode >= 400 {
var apiErr APIError
if err := json.Unmarshal(respBody, &apiErr); err != nil {
return nil, fmt.Errorf("status %d: %s", resp.StatusCode, string(respBody))
}
if apiErr.Status == 0 {
apiErr.Status = resp.StatusCode
}
return nil, &apiErr
}

return respBody, nil
}

func (c *Client) Get(endpoint string, params url.Values) ([]byte, error) {
if len(params) > 0 {
endpoint += "?" + params.Encode()
}
return c.request("GET", endpoint, nil)
}

func (c *Client) Post(endpoint string, body interface{}) ([]byte, error) {
return c.request("POST", endpoint, body)
}

func (c *Client) Patch(endpoint string, body interface{}) ([]byte, error) {
return c.request("PATCH", endpoint, body)
}

func (c *Client) Delete(endpoint string) error {
_, err := c.request("DELETE", endpoint, nil)
return err
}

// Initialize
func main() {
client := NewClient(os.Getenv("TAKETHEME_API_KEY"))
// Use client...
}

Products

List All Products

type Product struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Price float64 `json:"price"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
}

type ProductsResponse struct {
Data []Product `json:"data"`
Pagination Pagination `json:"pagination"`
}

type Pagination struct {
HasMore bool `json:"has_more"`
NextCursor string `json:"next_cursor,omitempty"`
PrevCursor string `json:"previous_cursor,omitempty"`
TotalCount int `json:"total_count"`
}

func (c *Client) ListProducts(limit int) (*ProductsResponse, error) {
params := url.Values{}
params.Set("limit", fmt.Sprintf("%d", limit))

body, err := c.Get("/products", params)
if err != nil {
return nil, err
}

var resp ProductsResponse
if err := json.Unmarshal(body, &resp); err != nil {
return nil, err
}

return &resp, nil
}

// Usage
func main() {
client := NewClient(os.Getenv("TAKETHEME_API_KEY"))

products, err := client.ListProducts(25)
if err != nil {
log.Fatal(err)
}

fmt.Printf("Found %d products\n", products.Pagination.TotalCount)
for _, p := range products.Data {
fmt.Printf("- %s ($%.2f)\n", p.Title, p.Price)
}
}

Get a Single Product

func (c *Client) GetProduct(id string) (*Product, error) {
body, err := c.Get("/products/"+id, nil)
if err != nil {
return nil, err
}

var resp struct {
Data Product `json:"data"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return nil, err
}

return &resp.Data, nil
}

Create a Product

type CreateProductInput struct {
Title string `json:"title"`
Description string `json:"description,omitempty"`
Price float64 `json:"price"`
Currency string `json:"currency,omitempty"`
Status string `json:"status,omitempty"`
Variants []Variant `json:"variants,omitempty"`
}

type Variant struct {
Title string `json:"title"`
SKU string `json:"sku"`
Price float64 `json:"price"`
InventoryQuantity int `json:"inventory_quantity"`
}

func (c *Client) CreateProduct(input CreateProductInput) (*Product, error) {
body, err := c.Post("/products", input)
if err != nil {
return nil, err
}

var resp struct {
Data Product `json:"data"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return nil, err
}

return &resp.Data, nil
}

// Usage
product, err := client.CreateProduct(CreateProductInput{
Title: "Premium Backpack",
Description: "Durable travel backpack",
Price: 79.99,
Currency: "USD",
Status: "active",
Variants: []Variant{
{Title: "Black", SKU: "BACKPACK-BLK", Price: 79.99, InventoryQuantity: 50},
{Title: "Navy", SKU: "BACKPACK-NVY", Price: 79.99, InventoryQuantity: 35},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created product: %s\n", product.ID)

Update a Product

type UpdateProductInput struct {
Title *string `json:"title,omitempty"`
Description *string `json:"description,omitempty"`
Price *float64 `json:"price,omitempty"`
Status *string `json:"status,omitempty"`
Tags []string `json:"tags,omitempty"`
}

func (c *Client) UpdateProduct(id string, input UpdateProductInput) (*Product, error) {
body, err := c.Patch("/products/"+id, input)
if err != nil {
return nil, err
}

var resp struct {
Data Product `json:"data"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return nil, err
}

return &resp.Data, nil
}

// Usage
newPrice := 84.99
updated, err := client.UpdateProduct("prod_abc123", UpdateProductInput{
Price: &newPrice,
Tags: []string{"featured", "travel"},
})

Delete a Product

func (c *Client) DeleteProduct(id string) error {
return c.Delete("/products/" + id)
}

Orders

Create an Order

type CreateOrderInput struct {
Customer OrderCustomer `json:"customer"`
LineItems []OrderLineItem `json:"line_items"`
ShippingAddress Address `json:"shipping_address"`
ShippingLine ShippingLine `json:"shipping_line,omitempty"`
}

type OrderCustomer struct {
Email string `json:"email"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
}

type OrderLineItem struct {
VariantID string `json:"variant_id"`
Quantity int `json:"quantity"`
Price float64 `json:"price"`
}

type Address struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Address1 string `json:"address1"`
Address2 string `json:"address2,omitempty"`
City string `json:"city"`
Province string `json:"province"`
PostalCode string `json:"postal_code"`
Country string `json:"country"`
Phone string `json:"phone,omitempty"`
}

type ShippingLine struct {
Title string `json:"title"`
Price float64 `json:"price"`
}

type Order struct {
ID string `json:"id"`
OrderNumber string `json:"order_number"`
Total float64 `json:"total"`
Status string `json:"status"`
FulfillmentStatus string `json:"fulfillment_status"`
}

func (c *Client) CreateOrder(input CreateOrderInput) (*Order, error) {
body, err := c.Post("/orders", input)
if err != nil {
return nil, err
}

var resp struct {
Data Order `json:"data"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return nil, err
}

return &resp.Data, nil
}

// Usage
order, err := client.CreateOrder(CreateOrderInput{
Customer: OrderCustomer{
Email: "customer@example.com",
FirstName: "John",
LastName: "Doe",
},
LineItems: []OrderLineItem{
{VariantID: "var_abc123", Quantity: 2, Price: 29.99},
},
ShippingAddress: Address{
FirstName: "John",
LastName: "Doe",
Address1: "123 Main Street",
City: "New York",
Province: "NY",
PostalCode: "10001",
Country: "US",
},
ShippingLine: ShippingLine{
Title: "Standard Shipping",
Price: 5.99,
},
})

Webhooks

Verify Webhook Signatures

package main

import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"io"
"net/http"
"os"
)

func verifyWebhook(payload []byte, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expected := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
payload, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read body", http.StatusBadRequest)
return
}

signature := r.Header.Get("X-TakeTheme-Signature")
secret := os.Getenv("TAKETHEME_WEBHOOK_SECRET")

if !verifyWebhook(payload, signature, secret) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}

// Process webhook...
var event map[string]interface{}
json.Unmarshal(payload, &event)

log.Printf("Received webhook: %v", event)
w.WriteHeader(http.StatusOK)
}

func main() {
http.HandleFunc("/webhooks/orders", webhookHandler)
http.ListenAndServe(":8080", nil)
}

Pagination Helper

func (c *Client) PaginateProducts(params url.Values) <-chan Product {
ch := make(chan Product)

go func() {
defer close(ch)

params.Set("limit", "100")
var cursor string

for {
if cursor != "" {
params.Set("cursor", cursor)
params.Set("direction", "next")
}

body, err := c.Get("/products", params)
if err != nil {
log.Printf("Error fetching products: %v", err)
return
}

var resp ProductsResponse
if err := json.Unmarshal(body, &resp); err != nil {
log.Printf("Error parsing response: %v", err)
return
}

for _, product := range resp.Data {
ch <- product
}

if !resp.Pagination.HasMore {
break
}
cursor = resp.Pagination.NextCursor
}
}()

return ch
}

// Usage
for product := range client.PaginateProducts(url.Values{}) {
fmt.Println(product.Title)
}

// Collect all
var allProducts []Product
for product := range client.PaginateProducts(url.Values{}) {
allProducts = append(allProducts, product)
}
fmt.Printf("Total: %d products\n", len(allProducts))

Error Handling

func handleAPIError(err error) {
if apiErr, ok := err.(*APIError); ok {
switch apiErr.Status {
case 400:
fmt.Printf("Validation error: %s\n", apiErr.Message)
case 401:
fmt.Println("Authentication failed")
case 403:
fmt.Printf("Permission denied: %s\n", apiErr.Message)
case 404:
fmt.Println("Resource not found")
case 429:
fmt.Println("Rate limited. Please slow down.")
default:
fmt.Printf("API error: %s\n", apiErr.Message)
}
} else {
fmt.Printf("Unexpected error: %v\n", err)
}
}

// Usage
product, err := client.CreateProduct(CreateProductInput{Title: ""})
if err != nil {
handleAPIError(err)
return
}

Complete Example: Product Sync

package main

import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)

type LocalProduct struct {
SKU string `json:"sku"`
Name string `json:"name"`
Price float64 `json:"price"`
Stock int `json:"stock"`
}

func syncProducts(client *Client, localProducts []LocalProduct) error {
// Build map of existing products by SKU
existingProducts := make(map[string]Product)
for product := range client.PaginateProducts(url.Values{}) {
for _, variant := range product.Variants {
existingProducts[variant.SKU] = product
}
}

log.Printf("Found %d existing products", len(existingProducts))

for _, local := range localProducts {
existing, exists := existingProducts[local.SKU]

if exists {
// Update existing product
newPrice := local.Price
_, err := client.UpdateProduct(existing.ID, UpdateProductInput{
Price: &newPrice,
})
if err != nil {
log.Printf("Failed to update %s: %v", local.SKU, err)
continue
}
log.Printf("Updated: %s", local.SKU)
} else {
// Create new product
_, err := client.CreateProduct(CreateProductInput{
Title: local.Name,
Price: local.Price,
Variants: []Variant{
{
Title: "Default",
SKU: local.SKU,
Price: local.Price,
InventoryQuantity: local.Stock,
},
},
})
if err != nil {
log.Printf("Failed to create %s: %v", local.SKU, err)
continue
}
log.Printf("Created: %s", local.SKU)
}

// Rate limit protection
time.Sleep(100 * time.Millisecond)
}

return nil
}

func main() {
client := NewClient(os.Getenv("TAKETHEME_API_KEY"))

// Load local products from JSON
data, err := os.ReadFile("products.json")
if err != nil {
log.Fatal(err)
}

var localProducts []LocalProduct
if err := json.Unmarshal(data, &localProducts); err != nil {
log.Fatal(err)
}

if err := syncProducts(client, localProducts); err != nil {
log.Fatal(err)
}

fmt.Println("Sync complete!")
}