Skip to content

Go SDK

The official Go SDK is published as go.sybilion.dev/sybilion. It ships two packages in one module:

  • sybilion at go.sybilion.dev/sybilion — the hand-written wrapper exposed to users (New, Options, direct methods, WaitForecast, pagination callbacks, ...).
  • sybilionapi at go.sybilion.dev/sybilion/api — the OpenAPI-generated low-level client and models. Exposed via c.DefaultAPI() for endpoints not yet wrapped.

This page documents the wrapper's idioms. For canonical use cases (forecasts, drivers, alerts, account, regions/categories), see the Features section — every example there has a Go tab.

Install

bash
go get go.sybilion.dev/sybilion@latest

Requires Go 1.25+.

Construct a client

go
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"go.sybilion.dev/sybilion"
)

func main() {
	// Token read from SYBILION_API_TOKEN automatically:
	c := sybilion.New(sybilion.Options{})

	// Or pass it explicitly:
	c = sybilion.New(sybilion.Options{
		Token: os.Getenv("SYBILION_API_TOKEN"),
	})

	me, err := c.Me(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(me.GetUserId(), me.GetAvailableEurCents())
}

When Options.Token is empty, New reads SYBILION_API_TOKEN from the environment. If both are empty, the client makes unauthenticated requests (only /health works).

Options fields

FieldDefaultNotes
TokenemptyBearer token. When empty, SYBILION_API_TOKEN env var is read.
BaseURLresolved at runtimeAPI origin without trailing slash. See resolution rules below.
HTTPClient&http.Client{Timeout: 60 * time.Second}Inject your own for custom transports, retries, or different timeouts.
UserAgentgenerator defaultOverride to brand outgoing requests.

Base URL resolution

Options.BaseURL is resolved in this order:

  1. Explicit Options.BaseURL.
  2. SYBILION_API_BASE_URL environment variable (constant sybilion.EnvSybilionAPIBaseURL).
  3. The compiled-in default sybilion.DefaultPublicAPIBaseURL (currently https://api.sybilion.dev).

Trailing slashes are stripped.

Wrapper methods

Client exposes a method for every endpoint:

MethodEndpoint
Me(ctx)GET /api/v1/me
SubmitForecast(ctx, req)POST /api/v1/forecasts
GetForecast(ctx, id)GET /api/v1/forecasts/{id}
GetForecastArtifact(ctx, id, name)GET /api/v1/forecasts/{id}/artifacts/{name}
WaitForecast(ctx, jobID, poll)polling helper
GetDrivers(ctx, req)POST /api/v1/drivers
GetAlerts(ctx, req)POST /api/v1/alerts
ForEachJobsPage(ctx, sort, order, limit, fn)pagination helper
ForEachUsagePage(ctx, sort, order, limit, fn)pagination helper
ListCategories(ctx)GET /api/v1/categories
ListRegions(ctx)GET /api/v1/regions

For endpoints not yet wrapped, c.DefaultAPI() returns the *sybilionapi.DefaultAPIService. Method names mirror the OpenAPI operationId: ApiV1MeGet, ApiV1ForecastsPost, ApiV1ForecastsIdGet, ApiV1RegionsGet, etc. The fluent builder returns (*Model, *http.Response, error) from Execute().

go
regions, _, err := c.DefaultAPI().ApiV1RegionsGet(ctx).Execute()

WaitForecast — poll until settled

go
import "time"

ctx := context.Background()

acc, err := c.SubmitForecast(ctx, *body)
if err != nil { log.Fatal(err) }

job, err := c.WaitForecast(ctx, acc.GetJobId(), 2*time.Second)
if err != nil { log.Fatal(err) }

fmt.Println(job.GetStatus(), job.GetEurCentsFinal())
for _, a := range job.GetArtifacts() {
	fmt.Println(a.GetName(), a.GetSize())
}

Behaviour:

  • Polls GET /api/v1/forecasts/{id} every poll interval (no jitter, no backoff).
  • Returns the response as soon as settled == true — works for completed, failed, and canceled jobs.
  • Honors ctx: cancel the context to stop polling. The wrapper returns ctx.Err().

For a hard wall-clock cap, wrap the call in context.WithTimeout:

go
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()

job, err := c.WaitForecast(ctx, acc.GetJobId(), 2*time.Second)

c.Forecasts().Wait(ctx, jobID, poll) is an alias for c.WaitForecast kept for backwards compatibility.

GetAlerts — synchronous alert detection

c.GetAlerts calls POST /api/v1/alerts and returns the result immediately — no polling required.

go
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"go.sybilion.dev/sybilion"
	api "go.sybilion.dev/sybilion/api"
)

func main() {
	c := sybilion.New(sybilion.Options{Token: os.Getenv("SYBILION_API_TOKEN")})

	meta := api.NewTimeseriesMetadata("Brent Crude Oil Price Monthly")
	meta.SetDescription("Monthly average Brent crude oil spot price in USD/barrel.")
	meta.SetKeywords([]string{"oil", "brent", "energy", "commodity"})

	filters := api.NewFilters()
	filters.SetCategories([]int32{3, 7})
	filters.SetRegions([]int32{42})
	filters.SetLimit(25)

	alerts, err := c.GetAlerts(context.Background(), sybilion.AlertsRequest{
		Metadata:        *meta,
		ContextEnriched: false,
		Filters:         filters,
		DateFrom:        "2024-01-01",
	})
	if err != nil {
		log.Fatal(err)
	}
	for _, a := range alerts {
		fmt.Println(a.Name, a.PctChange, a.Trending)
	}
}

AlertsRequest fields:

FieldTypeNotes
Metadataapi.TimeseriesMetadataRequired. Title, description, keywords describing the series.
ContextEnrichedboolRequired. Set to true if the metadata was already enriched upstream; false to let the engine enrich it.
Filters*api.FiltersOptional. Scope by category / region ids and cap item count with SetLimit.
DateFromstringOptional. Earliest date bound for alert events (YYYY-MM-DD).
DateTostringOptional. Latest date bound for alert events (YYYY-MM-DD).

Returns ([]sybilion.AlertItem, error). Each AlertItem has Name, PctChange, Trending, and News []NewsItem. See POST /api/v1/alerts for the full response shape.

Pagination callbacks

ForEachUsagePage and ForEachJobsPage walk paginated endpoints page-by-page. The callback receives each page and returns (continueNext bool, err error) — return false to stop early.

go
// Walk all usage events.
err := c.ForEachUsagePage(ctx, "created_at", "desc", 50, func(ctx context.Context, page *api.ApiV1UsageGet200Response) (bool, error) {
	for _, ev := range page.GetUsageEvents() {
		fmt.Println(ev.GetId(), ev.GetEurCentsCharged())
	}
	return true, nil // continue to the next page
})
if err != nil { log.Fatal(err) }

// Walk completed jobs only.
err = c.ForEachJobsPage(ctx, "created_at", "desc", 50, func(ctx context.Context, page *api.ApiV1JobsGet200Response) (bool, error) {
	for _, j := range page.GetJobs() {
		fmt.Println(j.GetJobId(), j.GetStatus())
	}
	return true, nil
})

Iteration stops automatically when the last page is reached, or when the callback returns false or a non-nil error.

Error handling

Wrapper methods return a plain error whose message is the error field from the API response body. If the body can't be parsed, the original *api.GenericOpenAPIError is returned as-is.

go
me, err := c.Me(ctx)
if err != nil {
	log.Fatal(err) // prints the API error message
}

When you need the HTTP status code — for example to branch on 402 vs 422 — use DefaultAPI() directly, which returns (*Model, *http.Response, error):

go
resp, httpResp, err := c.DefaultAPI().ApiV1ForecastsPost(ctx).
	ForecastRequestV1(*body).
	Execute()
if err != nil {
	log.Printf("status=%d body=%s", httpResp.StatusCode, ...)
}
_ = resp

Custom HTTP client

Inject any *http.Client to replace timeouts, transport, retries, or proxies:

go
import (
	"crypto/tls"
	"net/http"
	"os"
	"time"

	"go.sybilion.dev/sybilion"
)

httpc := &http.Client{
	Timeout: 30 * time.Second,
	Transport: &http.Transport{
		TLSClientConfig:    &tls.Config{MinVersion: tls.VersionTLS12},
		MaxIdleConns:       50,
		IdleConnTimeout:    90 * time.Second,
	},
}

c := sybilion.New(sybilion.Options{
	Token:      os.Getenv("SYBILION_API_TOKEN"),
	HTTPClient: httpc,
	UserAgent:  "my-app/1.0",
})

For per-call cancellation and timeouts, use context.WithTimeout / context.WithCancel instead of mutating the *http.Client.

Versioning

The SDK uses SemVer independently of the API server version; minor releases stay backward-compatible, breaking changes bump the major. Pin to a tag (go get go.sybilion.dev/[email protected]) for production builds.

See also

[email protected] · Slack · Discord (links in Community page & header icons)