Skip to content

Go client

You can call the API with plain net/http, as the example code does, or generate a typed client from the spec. This page shows how to generate one with oapi-codegen v2.8.0, the generator we build and test against the spec.

The spec is published at https://developers.elephants.inc/openapi.json.

Terminal window
curl -sO https://developers.elephants.inc/openapi.json

Keep the file in your repository, so that each change to the client shows up as a change to the spec in review.

Terminal window
go get -tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.8.0
go get github.com/oapi-codegen/runtime@v1.7.0

go tool oapi-codegen then always runs that version.

Save this as oapi-codegen.yml:

oapi-codegen.yml
package: client
output: client/client.go
generate:
models: true
client: true
output-options:
# The default suffix, "Response", gives operation results names that
# collide with schemas of the same name (signIn's result and the
# SignInResponse schema, for example).
response-type-suffix: HTTPResponse

It generates the models and a client into client/client.go. The suffix keeps operation results apart from schemas with similar names: the result of signIn is SignInHTTPResponse, and its body is the SignInResponse schema.

Terminal window
mkdir -p client
go tool oapi-codegen -config oapi-codegen.yml openapi.json
go build ./...

This program lists the signed-in person’s organizations on the sandbox. Change the client import path to your own module’s:

main.go
// Command codegen proves the generated client compiles against the
// published spec: it lists your organizations on the sandbox.
//
// ELE_TOKEN=… go run .
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"github.com/elephants-labs/ele/developers/codegen/client"
)
// bearerToken sends ELE_TOKEN with every request.
func bearerToken(_ context.Context, r *http.Request) error {
r.Header.Set("Authorization", "Bearer "+os.Getenv("ELE_TOKEN"))
return nil
}
func main() {
c, err := client.NewClientWithResponses("https://staging-api.elefin.net",
client.WithRequestEditorFn(bearerToken))
if err != nil {
log.Fatal(err)
}
res, err := c.ListOrganizationsWithResponse(context.Background(), &client.ListOrganizationsParams{})
if err != nil {
log.Fatal(err)
}
if res.JSON200 == nil {
log.Fatalf("unexpected response %d: %s", res.StatusCode(), res.Body)
}
for _, org := range res.JSON200.Data {
fmt.Printf("%s (%s)\n", org.Name, org.Id)
}
}
Terminal window
ELE_TOKEN=… go run .

WithRequestEditorFn runs on every request; here it adds the bearer token. Get the token as Authentication describes.

  • Every published operation, in two forms. Client returns the raw *http.Response. ClientWithResponses, used above, also decodes the body.
  • One field per status. A …WithResponse method returns a struct such as ListOrganizationsHTTPResponse, with JSON200, JSON401 and so on. The field for the status that came back is set. StatusCode() and the raw Body are always there. The separate error return is for transport failures and for a body that does not decode.
  • Errors. Most error statuses decode to *Error. Branch on .Error.Type, as Errors explains. On step_up_required, .Error.StepUp holds the challenge. A 429 body carries only .Error.Type; the Retry-After header is parsed into Headers429.RetryAfter (it is also in HTTPResponse.Header). See Rate limits.
  • Optional and nullable fields are pointers, such as *string.
  • Dates and IDs are openapi_types.Date and openapi_types.UUID.

Versioning lets Ele add enum values, such as a new payment status, and new kinds of object, such as a new transaction type, at any time. The generated client keeps working when that happens:

  • An enum is a named string type with one constant per value the spec knew, such as PaymentStatus. A value added later decodes as its string. Give every switch on it a default case.
  • A union, such as Transaction, keeps the raw JSON of each item. Discriminator() returns its type. ValueByDiscriminator() returns the typed value, or an error for a variant your client does not know yet. A list that contains a new variant still decodes; handle that error item by item, for example by showing the transaction’s common fields or skipping it. Switch on Discriminator() before calling an AsX() accessor such as AsCardTransaction(): on any other variant, AsX() returns a partly filled value and no error.

To decode a new variant into its type, download the spec again and regenerate. Watch the changelog.

No operation declares Idempotency-Key as a parameter, so the generated methods have no field for it. Pass a request editor on each write:

idempotency.go
package main
import (
"context"
"net/http"
"github.com/elephants-labs/ele/developers/codegen/client"
)
// withIdempotencyKey sets Idempotency-Key on one call. Create the key once
// per operation and pass the same key on every retry of it.
func withIdempotencyKey(key string) client.RequestEditorFn {
return func(_ context.Context, r *http.Request) error {
r.Header.Set("Idempotency-Key", key)
return nil
}
}

Pass withIdempotencyKey(key) as the last argument of a call, such as ExecuteSwapWithResponse.

Create the key once per operation and reuse it on every retry. See Idempotency.

The client includes every operation, including the ones that let a phone answer step-up or sign a person in without a password, such as approveChallenge and enrollApprovalCredential. Do not call them from your server; see Keep a person in the loop.

ogen v1.24.0 also generates a client from this spec, but that client checks every response against the spec it was generated from, and nothing turns the check off. A new enum value fails the check, and a new union variant fails decoding, so calls start failing as soon as Ele makes an addition that Versioning allows, until you regenerate. That includes writes that succeeded. We do not recommend it for this API.