Skip to content

Example code

The Go examples on this site belong to one small Go module. It uses only the standard library. This page shows the two files every example builds against. Each example’s own main.go is shown in full on the page that explains it.

Create a directory with this layout:

  • Directoryexamples/
    • go.mod
    • Directoryinternal/
      • Directoryele/
        • client.go
    • Directoryidempotent-payment/
      • main.go
    • Directorypagination/
      • main.go
    • Directoryquickstart/
      • main.go
    • Directorysignin/
      • main.go
    • Directorystepup/
      • main.go

Copy go.mod and client.go from below. Copy each main.go from its page:

Run each example from the examples directory, for example go run ./quickstart. You do not need to download anything: the module path in go.mod is only a name, and the examples import the helper by that name.

go.mod
module github.com/elephants-labs/ele/developers/examples
go 1.27

client.go is a small helper, not an SDK. It reads ELE_API_BASE and ELE_TOKEN from the environment, sends JSON requests with the bearer token, and turns any error response into an APIError. Branch on its Type field.

internal/ele/client.go
// Package ele is the small HTTP helper the examples share. It is not an SDK.
package ele
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
// Client sends JSON requests to the Ele API. Token, when set, is sent as a
// bearer token.
type Client struct {
Base string
Token string
HTTP *http.Client
}
// FromEnv reads ELE_API_BASE (default: the sandbox) and ELE_TOKEN.
func FromEnv() *Client {
base := os.Getenv("ELE_API_BASE")
if base == "" {
base = "https://staging-api.elefin.net"
}
return &Client{Base: base, Token: os.Getenv("ELE_TOKEN"), HTTP: &http.Client{Timeout: 30 * time.Second}}
}
// APIError is Ele's error envelope. Branch on Type, never on Message or Status.
//
// Details holds field-level validation errors on a 422. StepUp holds the
// step-up challenge; it is nil when the response has none, but branch on
// Type == "step_up_required", not on StepUp.
//
// A response that is not an Ele error (a proxy's HTML page, say) has an
// empty Type, and Message carries the HTTP status text and the start of
// the body.
type APIError struct {
Status int
Type string `json:"type"`
Message string `json:"message"`
RequestID string `json:"request_id"`
Details json.RawMessage `json:"details"`
StepUp json.RawMessage `json:"step_up"`
}
func (e *APIError) Error() string {
s := fmt.Sprint(e.Status)
if e.Type != "" {
s += " " + e.Type
}
s += ": " + e.Message
if e.RequestID != "" {
s += " (request " + e.RequestID + ")"
}
return s
}
// Option changes a request before it is sent.
type Option func(*http.Request)
// WithIdempotencyKey makes a mutation safe to retry with the same key.
func WithIdempotencyKey(key string) Option {
return func(r *http.Request) { r.Header.Set("Idempotency-Key", key) }
}
// Do sends a request to Base+path. A non-nil body is sent as JSON. On a 2xx
// response the JSON body is decoded into out, unless out is nil or the
// status is 204. Any status of 400 or above returns an *APIError.
func (c *Client) Do(ctx context.Context, method, path string, body, out any, opts ...Option) error {
var reader *bytes.Reader
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return err
}
reader = bytes.NewReader(b)
} else {
reader = bytes.NewReader(nil)
}
req, err := http.NewRequestWithContext(ctx, method, c.Base+path, reader)
if err != nil {
return err
}
req.Header.Set("Accept", "application/json")
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
if c.Token != "" {
req.Header.Set("Authorization", "Bearer "+c.Token)
}
for _, opt := range opts {
opt(req)
}
resp, err := c.HTTP.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return readError(resp)
}
if out == nil || resp.StatusCode == http.StatusNoContent {
return nil
}
return json.NewDecoder(resp.Body).Decode(out)
}
func readError(resp *http.Response) error {
raw, err := io.ReadAll(io.LimitReader(resp.Body, 64<<10))
if err != nil {
return fmt.Errorf("%d %s: reading body: %w", resp.StatusCode, http.StatusText(resp.StatusCode), err)
}
var env struct {
Error APIError `json:"error"`
}
if json.Unmarshal(raw, &env) != nil || env.Error.Type == "" {
return &APIError{Status: resp.StatusCode, Message: describe(resp.StatusCode, raw)}
}
e := env.Error
e.Status = resp.StatusCode
e.Details = withoutNull(e.Details)
e.StepUp = withoutNull(e.StepUp)
return &e
}
// describe summarises a response that is not an Ele error envelope.
func describe(status int, raw []byte) string {
text := http.StatusText(status)
if text == "" {
text = "HTTP error"
}
snippet := strings.Join(strings.Fields(string(raw)), " ")
if r := []rune(snippet); len(r) > 200 {
snippet = string(r[:200]) + "…"
}
if snippet == "" {
return text
}
return text + ": " + snippet
}
// withoutNull turns a JSON null into nil, so "absent" has one spelling.
func withoutNull(v json.RawMessage) json.RawMessage {
if string(bytes.TrimSpace(v)) == "null" {
return nil
}
return v
}