Skip to content

Quickstart

This quickstart runs a small Go program against the sandbox. It signs a staff member in, lists the organizations they belong to, and prints each one’s balances.

You need:

  • Go 1.27 or later.
  • curl and jq for the curl equivalents below.
  • A sandbox user with an authenticator app set up. Ask your Ele contact for sandbox access.
  • The user must be an owner or admin of at least one organization. Members cannot read balances.
  • The example Go module, set up as shown in Example code. It holds a small shared HTTP helper and one directory per example.

From the examples directory, set the user’s credentials and run the program. read -rs asks for the password without echoing it or saving it in your shell history. Use a fresh code from the authenticator app:

Terminal window
export ELE_EMAIL=you@example.com
read -rs ELE_PASSWORD && export ELE_PASSWORD
ELE_TOTP_CODE=123456 go run ./quickstart

The program prints each organization’s name and ID, then one line per currency with the available balance:

Acme Ltd (0190f1c2-…)
USD 1250.00
EUR 80.00
quickstart/main.go
// Quickstart: sign in, list your organizations, print their balances.
//
// ELE_EMAIL=… ELE_PASSWORD=… ELE_TOTP_CODE=123456 go run ./quickstart
package main
import (
"context"
"errors"
"fmt"
"log"
"net/url"
"os"
"github.com/elephants-labs/ele/developers/examples/internal/ele"
)
// signInResult covers the sign-in and MFA responses. Status says which
// fields are set.
type signInResult struct {
Status string `json:"status"`
Token string `json:"token"`
ChallengeToken string `json:"challenge_token"`
}
func main() {
ctx := context.Background()
c := ele.FromEnv()
var signIn signInResult
must(c.Do(ctx, "POST", "/api/v1/auth/sign-in",
map[string]string{"email": os.Getenv("ELE_EMAIL"), "password": os.Getenv("ELE_PASSWORD")}, &signIn))
switch signIn.Status {
case "mfa_required":
must(c.Do(ctx, "POST", "/api/v1/auth/mfa",
map[string]string{"challenge_token": signIn.ChallengeToken, "code": os.Getenv("ELE_TOTP_CODE")}, &signIn))
case "totp_setup_required":
log.Fatal("this account must finish authenticator (TOTP) setup before it can sign in; see the sign-in guide")
}
if signIn.Status != "authenticated" {
log.Fatalf("sign-in ended in status %q", signIn.Status)
}
c.Token = signIn.Token
type organization struct {
ID string `json:"id"`
Name string `json:"name"`
}
// The list is paginated: follow next_cursor until has_more is false.
var all []organization
path := "/api/v1/organizations"
for {
var page struct {
Data []organization `json:"data"`
HasMore bool `json:"has_more"`
NextCursor string `json:"next_cursor"`
}
must(c.Do(ctx, "GET", path, nil, &page))
all = append(all, page.Data...)
if !page.HasMore {
break
}
path = "/api/v1/organizations?cursor=" + url.QueryEscape(page.NextCursor)
}
for _, org := range all {
var balance struct {
Balances []struct {
Currency string `json:"currency"`
Available string `json:"available"`
} `json:"balances"`
}
err := c.Do(ctx, "GET", "/api/v1/organizations/"+url.PathEscape(org.ID)+"/balance", nil, &balance)
// Only owners and admins may read balances; skip the rest.
var apiErr *ele.APIError
if errors.As(err, &apiErr) && apiErr.Type == "forbidden" {
fmt.Fprintf(os.Stderr, "skipping %s (%s): %v\n", org.Name, org.ID, err)
continue
}
must(err)
fmt.Printf("%s (%s)\n", org.Name, org.ID)
for _, b := range balance.Balances {
fmt.Printf(" %s %s\n", b.Currency, b.Available)
}
}
}
func must(err error) {
if err != nil {
log.Fatal(err)
}
}

The curl commands below do the same steps by hand. They assume ELE_API_BASE is set, as shown in Environments.

POST /api/v1/auth/sign-in checks the email and password. It does not return a session yet if the account has an authenticator app. It returns status: "mfa_required" and a challenge_token instead.

Terminal window
jq -n '{email: env.ELE_EMAIL, password: env.ELE_PASSWORD}' |
curl -s "$ELE_API_BASE/api/v1/auth/sign-in" \
-H 'Content-Type: application/json' \
--data @-

jq reads the credentials from the environment and encodes them as JSON, so a password with quotes or backslashes still works. --data @- reads the body from standard input, so the password does not appear in the process list.

{ "status": "mfa_required", "challenge_token": "…", "methods": ["totp"] }

POST /api/v1/auth/mfa takes the challenge token and the code. On success it returns status: "authenticated" and a session token.

Terminal window
curl -s "$ELE_API_BASE/api/v1/auth/mfa" \
-H 'Content-Type: application/json' \
-d "{\"challenge_token\": \"$CHALLENGE_TOKEN\", \"code\": \"$ELE_TOTP_CODE\"}"

Sign-in can end in other statuses. Authentication explains each one.

Send the token as a bearer token. GET /api/v1/organizations returns the organizations the user belongs to, one page at a time. The program follows next_cursor until has_more is false.

Terminal window
curl -s "$ELE_API_BASE/api/v1/organizations" \
-H "Authorization: Bearer $ELE_TOKEN"
{ "data": [{ "id": "…", "name": "Acme Ltd", "role": "admin", "permissions": ["…"] }], "has_more": false, "next_cursor": null }

GET /api/v1/organizations/{organization_id}/balance returns a balances list with one entry per currency. Amounts are decimal strings, such as "1250.00". Each entry has available, pending and posted amounts.

Terminal window
curl -s "$ELE_API_BASE/api/v1/organizations/$ORGANIZATION_ID/balance" \
-H "Authorization: Bearer $ELE_TOKEN"

Only owners and admins may read balances. For an organization where the user is a member, Ele answers 403 with the error type forbidden. The program notes the skip on standard error and moves on to the next organization. To avoid the call altogether, check that the organization’s permissions list contains organization.view_balances before you ask for its balance.