Calco

Go

The Go connector uses only the standard library (net/http). Render methods return []byte so you can write the result straight to disk or an HTTP response.

Add the connector

There's no published module to go get (yet) — the client is a single, dependency-free Go file. Copy client.go into a package in your module (for example a calco/ directory) and import that package. The examples below alias it as dc:

go
import dc "yourmodule/calco"

Quick start

go
package main

import (
    "log"
    "os"
    "time"

    dc "yourmodule/calco"
)

func main() {
    client := dc.NewClient("http://localhost:8080", &dc.Options{
        APIKey:  "dc_live_...",
        Timeout: 5 * time.Minute,
    })

    pdf, err := client.Convert("report.xlsx", "pdf", nil)
    if err != nil {
        log.Fatal(err)
    }
    os.WriteFile("report.pdf", pdf, 0644)
}

NewClient(baseURL, *Options) accepts nil for defaults. The APIKey is sent as Authorization: Bearer <key> on every request.

Converting a file

go
pdf, err := client.Convert("report.xlsx", "pdf", &dc.ConvertOptions{
    DataJSON: `{"company": "Acme"}`,
    Sheet:    "Summary",
    Title:    "Q4 Report",
})

Extract the placeholder map with Scaffold:

go
scaffold, err := client.Scaffold("spreadsheet.xlsx")
fmt.Println(string(scaffold))

Managing templates

go
// Create
result, err := client.CreateTemplate("template.xlsx", "Invoice", "finance", "billing,monthly")

// List
templates, err := client.ListTemplates()

// Update metadata (empty string = unchanged)
_, err = client.UpdateTemplate("42", "Invoice v2", "accounting", "")

// Add a new version
_, err = client.CreateTemplateVersion("42", "template_v2.xlsx")

// Delete
_, err = client.DeleteTemplate("42")

Rendering a stored template

go
html, err := client.RenderTemplateVersion("42", "2", "html", &dc.RenderOptions{
    DataJSON: `{"customer": "Jane"}`,
    Title:    "Invoice #1001",
})
os.WriteFile("invoice.html", html, 0644)

Batch rendering

Pass a JSON array of records and receive a ZIP archive:

go
records := `[{"name":"Alice","amount":100},{"name":"Bob","amount":200}]`

zip, err := client.BatchRender(records, "42", "1", "pdf", &dc.BatchRenderOptions{
    TitleTemplate: "invoice_{{name}}",
    Sheet:         "Sheet1",
})
os.WriteFile("invoices.zip", zip, 0644)

By-hash workflow

Every stored version exposes a content hash (sha256 of the file). Address a version directly by hash:

go
meta, _ := client.GetTemplateByHash("3c9f...d1ab")
pdf, _  := client.RenderByHash("3c9f...d1ab", "pdf", &dc.RenderOptions{
    DataJSON: `{"customer": "Jane"}`,
})
xlsx, _ := client.DownloadByHash("3c9f...d1ab", "xlsx", nil)

Error handling

API errors are returned as *calco.Error with the HTTP status code and body. Use errors.As to distinguish them from transport errors:

go
result, err := client.Convert("missing.xlsx", "pdf", nil)
if err != nil {
    var apiErr *dc.Error
    if errors.As(err, &apiErr) {
        fmt.Printf("Status: %d\nBody: %s\n", apiErr.StatusCode, apiErr.Body)
    } else {
        fmt.Printf("Transport error: %v\n", err)
    }
}

Framework integration

Gin

go
client := dc.NewClient("http://localhost:8080", nil)

r := gin.Default()
r.POST("/export", func(c *gin.Context) {
    file, err := c.FormFile("file")
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "file required"})
        return
    }
    tmp := "/tmp/upload-" + file.Filename
    if err := c.SaveUploadedFile(file, tmp); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    defer os.Remove(tmp)

    format := c.DefaultPostForm("format", "pdf")
    result, err := client.Convert(tmp, format, nil)
    if err != nil {
        c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
        return
    }
    c.Data(http.StatusOK, "application/octet-stream", result)
})
r.Run(":8080")

Echo

go
e.POST("/export", func(c echo.Context) error {
    file, err := c.FormFile("file")
    if err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, "file required")
    }
    src, _ := file.Open()
    defer src.Close()

    tmp, _ := os.CreateTemp("", "upload-*.xlsx")
    defer os.Remove(tmp.Name())
    io.Copy(tmp, src)
    tmp.Close()

    result, err := client.Convert(tmp.Name(), "pdf", nil)
    if err != nil {
        return echo.NewHTTPError(http.StatusBadGateway, err.Error())
    }
    return c.Blob(http.StatusOK, "application/octet-stream", result)
})

Fiber

go
app.Post("/export", func(c *fiber.Ctx) error {
    file, err := c.FormFile("file")
    if err != nil {
        return c.Status(400).JSON(fiber.Map{"error": "file required"})
    }
    tmp := "/tmp/upload-" + file.Filename
    if err := c.SaveFile(file, tmp); err != nil {
        return c.Status(500).JSON(fiber.Map{"error": err.Error()})
    }
    defer os.Remove(tmp)

    result, err := client.Convert(tmp, c.FormValue("format", "pdf"), nil)
    if err != nil {
        return c.Status(502).JSON(fiber.Map{"error": err.Error()})
    }
    c.Set("Content-Type", "application/octet-stream")
    return c.Send(result)
})

Configuration options

FieldDefaultDescription
APIKey""Bearer token for authentication
Timeout120sHTTP client timeout
HTTPClientnilOptional pre-configured *http.Client