Requirements
Go 1.21+ — only the standard library is used (net/http, encoding/json, net/url). If you'd rather use a typed HTTP client wrapper, the openly published openapi.json drops cleanly into oapi-codegen.
Sanity-check the API first
Before wiring up Go, confirm the endpoint responds. This is the exact call the Go program below makes:
curl "https://api.provable.io/api/ints?clientSeed=my-app-user-42&count=5&min=1&max=100"
Set up a module
mkdir provable-quickstart && cd provable-quickstart
go mod init example.com/provable-quickstart
touch main.go
Generate an outcome
The shape returned by /api/ints matches this struct verbatim — only declare the fields you care about; the JSON decoder will skip the rest.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"os"
"time"
)
const base = "https://api.provable.io"
type IntOutcome struct {
Outcome []int `json:"outcome"`
ClientSeed string `json:"clientSeed"`
ServerHash string `json:"serverHash"`
Cursor int `json:"cursor"`
Nonce int `json:"nonce"`
ShortID string `json:"shortId"`
Permalink string `json:"permalink"`
}
var client = &http.Client{Timeout: 10 * time.Second}
func getInts(clientSeed string, count, min, max int) (*IntOutcome, error) {
q := url.Values{}
q.Set("clientSeed", clientSeed)
q.Set("count", fmt.Sprint(count))
q.Set("min", fmt.Sprint(min))
q.Set("max", fmt.Sprint(max))
req, err := http.NewRequest(http.MethodGet, base+"/api/ints?"+q.Encode(), nil)
if err != nil {
return nil, err
}
if key := os.Getenv("PROVABLE_API_KEY"); key != "" {
req.Header.Set("x-api-key", key) // optional, attributes usage to your account
}
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("ints: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("ints: unexpected status %s", res.Status)
}
var out IntOutcome
if err := json.NewDecoder(res.Body).Decode(&out); err != nil {
return nil, fmt.Errorf("ints: decode: %w", err)
}
return &out, nil
}
func main() {
o, err := getInts("my-app-user-42", 5, 1, 100)
if err != nil {
log.Fatal(err)
}
fmt.Println("outcome: ", o.Outcome)
fmt.Println("serverHash: ", o.ServerHash)
fmt.Println("permalink: ", o.Permalink)
}
Generate random floats
Same shape, with Outcome typed as []float64. Hit /api/floats and drop min/max from the query string.
type FloatOutcome struct {
Outcome []float64 `json:"outcome"`
ClientSeed string `json:"clientSeed"`
ServerHash string `json:"serverHash"`
Cursor int `json:"cursor"`
Nonce int `json:"nonce"`
}
Verify it later
/api/verifyServerHash can return either the literal JSON true (fast-path match against the live seed state) or a richer VerifyResult object. Decode into json.RawMessage and branch:
type VerifyResult struct {
Verified bool `json:"verified"`
}
func verify(clientSeed, serverHash string) (bool, error) {
q := url.Values{}
q.Set("clientSeed", clientSeed)
q.Set("serverHash", serverHash)
res, err := client.Get(base + "/api/verifyServerHash?" + q.Encode())
if err != nil {
return false, err
}
defer res.Body.Close()
var raw json.RawMessage
if err := json.NewDecoder(res.Body).Decode(&raw); err != nil {
return false, err
}
// Fast path: literal "true".
var b bool
if err := json.Unmarshal(raw, &b); err == nil {
return b, nil
}
// Otherwise it's the richer object.
var v VerifyResult
if err := json.Unmarshal(raw, &v); err != nil {
return false, err
}
return v.Verified, nil
}
Idiomatic error handling
- Always check
res.StatusCode. A non-2xx body is a JSON{"error":"…"}object — decode it into a small struct for a clean error message. - Set a request-level
Timeouton the sharedhttp.Clientso a stuck connection can't pin a goroutine. - Wrap errors with
fmt.Errorf("%w", err)so callers can still useerrors.Isagainst transport errors likecontext.DeadlineExceeded. - For request bodies you care about retrying (e.g.
POST /api/batch,POST /api/reveal), set theIdempotency-Keyheader — see the idempotency guide.