nexra

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: MIT Imports: 20 Imported by: 0

README

Nexra SDK for Go

A client for the Nexra platform API: integrations, flows, runs, connections, triggers, reconciliation, approvals and signed event webhooks. Every operation and model is generated from the published OpenAPI document into the nexraapi package; the nexra package adds sign-in headers, retries with backoff, typed errors, cursor pages, safe run starts and webhook signature checks.

  • Go 1.23 or later; no dependencies outside the standard library
  • API version 2026-10-01

Install

go get github.com/TochukwuAchebe/nexra-go@v1.0.0

Quickstart

Sign in once with the Nexra CLI (nexra auth login) to get an access token. The token acts with your own role in the workspace.

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

	nexra "github.com/TochukwuAchebe/nexra-go"
)

ctx := context.Background()
client, err := nexra.NewClient(nexra.Options{
	Token:       os.Getenv("NEXRA_TOKEN"),        // the default
	WorkspaceID: os.Getenv("NEXRA_WORKSPACE_ID"), // the default
})
if err != nil {
	log.Fatal(err)
}

integrations, response, err := client.IntegrationsAPI.ListIntegrations(ctx).Execute()
if err != nil {
	log.Fatal(nexra.ParseError(response, err))
}
for _, integration := range integrations {
	fmt.Println(integration.GetName(), integration.GetStatus())
}

// Start a run. An idempotency key is added for you, so a retry never starts a second run.
run, err := client.StartRun(ctx, "cmflow01orders", "sandbox", nexra.StartRunOptions{})
if err != nil {
	log.Fatal(err)
}
detail, response, err := client.RunsAPI.GetRun(ctx, run.GetId()).Execute()

Each area of the API is a service on the client: IntegrationsAPI, FlowsAPI, RunsAPI, ConnectionsAPI, TriggersAPI, ReconciliationAPI, ApprovalsAPI, EventWebhooksAPI, GovernanceAPI, LifecycleAPI, SchedulesAPI, FailedRecordsAPI, TemplatesAPI, CatalogueAPI, EndUsersAPI, RecordsAPI, PensionsAPI, ResearchAPI, CompanyDataAPI and MCPAPI. Models are in nexraapi.

Errors

Pass the (response, err) from Execute() to nexra.ParseError. A refusal becomes an *nexra.APIError with Status, Code, RequestID and Nexra's Message, and matches one of these with errors.Is:

Status Error
400, 422 nexra.ErrBadRequest
401 nexra.ErrAuthentication
403 nexra.ErrPermission
404 nexra.ErrNotFound
409 nexra.ErrConflict
429 nexra.ErrRateLimited (with RetryAfter)
5xx nexra.ErrServer
flow, response, err := client.FlowsAPI.GetFlow(ctx, "cmflow01orders").Execute()
if err := nexra.ParseError(response, err); errors.Is(err, nexra.ErrNotFound) {
	fmt.Println("No such flow")
} else if err != nil {
	var apiError *nexra.APIError
	if errors.As(err, &apiError) {
		log.Fatalf("%s (request %s)", apiError.Message, apiError.RequestID)
	}
	log.Fatal(err)
}

client.StartRun returns *APIError already.

Retries

Requests are retried up to 3 times with exponential backoff and jitter on network errors and on 408, 425, 429, 500, 502, 503 and 504, honouring Retry-After. A POST or PATCH is only retried when Nexra did not act on it (429) or when its body has an idempotencyKey. Change this with Options{Retry: &nexra.RetryOptions{MaxRetries: 5, BaseDelay: 500 * time.Millisecond, MaxDelay: 20 * time.Second}} or turn it off with Options{DisableRetries: true}. Waits stop when the request's context ends.

Cursor pages

Most lists return the newest items as a slice. Governance lists return items and nextCursor; nexra.Paginate follows the cursor and gives a Go 1.23 iterator:

entries := nexra.Paginate(ctx, func(ctx context.Context, cursor string) (nexra.Page[nexraapi.EvidenceEntry], error) {
	request := client.GovernanceAPI.ListEvidence(ctx)
	if cursor != "" {
		request = request.Cursor(cursor)
	}
	page, response, err := request.Execute()
	if err != nil {
		return nexra.Page[nexraapi.EvidenceEntry]{}, nexra.ParseError(response, err)
	}
	return nexra.Page[nexraapi.EvidenceEntry]{Items: page.Items, NextCursor: page.GetNextCursor()}, nil
})
for entry, err := range entries {
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(entry.GetSequence(), entry.GetEventType())
}

Verify event webhooks

Nexra signs each event delivery with your endpoint's signing secret. Verify against the raw body before parsing it:

http.HandleFunc("/nexra/events", func(w http.ResponseWriter, r *http.Request) {
	raw, _ := io.ReadAll(io.LimitReader(r.Body, 1<<20))
	event, err := nexra.ConstructEvent(raw, r.Header.Get(nexra.SignatureHeader), secret, nexra.VerifyOptions{})
	if err != nil {
		http.Error(w, "bad signature", http.StatusBadRequest)
		return
	}
	// De-duplicate on event.ID, queue the work, answer quickly.
	w.WriteHeader(http.StatusNoContent)
})

Signatures older than 5 minutes are refused. Answer with a 2xx within 8 seconds. Decode the whole body into a nexraapi.Event* model (for example nexraapi.EventFlowRunFailed) for typed fields. See examples/webhook-receiver.

Send events to a webhook trigger

body := []byte(`{"orderId":"1001"}`)
headers, err := nexra.SignTriggerRequest(body, triggerSecret, "order-1001-0001", time.Now())

Versioning

This module follows semantic versioning from v1.0.0. The API it is built from is dated (2026-10-01); Nexra adds operations and optional fields without a new date, and a minor SDK release picks them up. A breaking API change gets a new dated version and a new major version (with a /v2 module path).

Reference

For maintainers

nexraapi/ is generated by openapi-generator 7.25.0 (go) from openapi/nexra-platform.json at the repository root. Everything else is written by hand. This directory is developed inside the Nexra backend repository and published by copying it to the public module repository github.com/TochukwuAchebe/nexra-go and tagging it (see docs/sdk/releasing.md).

bash scripts/sdk-codegen/generate.sh go   # after the API document changes (needs Node.js and Java 11+)
bash scripts/sdk-codegen/check.sh go      # generated code is current
go vet ./... && go test ./...
bash scripts/sdk-release/go.sh            # dry run; --publish to push and tag

Documentation

Overview

Package nexra is the Go SDK for the Nexra platform API.

Every operation in the API is generated from its OpenAPI document into the nexraapi package. This package adds what the generator does not: client construction from a token and workspace, retries with backoff, typed errors, cursor pages, safe run starts and webhook signature checks.

client, err := nexra.NewClient(nexra.Options{}) // NEXRA_TOKEN and NEXRA_WORKSPACE_ID
integrations, _, err := client.IntegrationsAPI.ListIntegrations(ctx).Execute()

Index

Examples

Constants

View Source
const APIVersion = "2026-10-01"

APIVersion is the dated version of the Nexra API this SDK was built from.

View Source
const DefaultBaseURL = "https://api.nexra.amakoragroup.com"

DefaultBaseURL is the Nexra API.

View Source
const DefaultTolerance = 5 * time.Minute

DefaultTolerance is the oldest signature timestamp accepted.

View Source
const SignatureHeader = "X-Amakora-Signature"

SignatureHeader carries Nexra's signature on event deliveries.

View Source
const Version = "1.0.0"

Version is this SDK's version.

Variables

View Source
var (
	ErrBadRequest     = errors.New("nexra: bad request")       // 400, 422
	ErrAuthentication = errors.New("nexra: not signed in")     // 401
	ErrPermission     = errors.New("nexra: not allowed")       // 403
	ErrNotFound       = errors.New("nexra: not found")         // 404
	ErrConflict       = errors.New("nexra: conflict")          // 409
	ErrRateLimited    = errors.New("nexra: too many requests") // 429
	ErrServer         = errors.New("nexra: server error")      // 5xx
)

Kinds of refusal. Test for them with errors.Is:

if errors.Is(err, nexra.ErrNotFound) { ... }
View Source
var DefaultRetryOptions = RetryOptions{MaxRetries: 3, BaseDelay: 500 * time.Millisecond, MaxDelay: 20 * time.Second}

DefaultRetryOptions are 3 retries from 500ms, capped at 20s.

View Source
var ErrNoToken = errors.New("nexra: an access token is needed: set Options.Token or NEXRA_TOKEN")

ErrNoToken is returned by NewClient when no token is given or set.

View Source
var ErrWebhookVerification = errors.New("nexra: the signature is missing, does not match, or is older than the tolerance")

ErrWebhookVerification means the signature is missing, wrong or too old.

Functions

func BackoffDelay

func BackoffDelay(attempt int, options RetryOptions, random func() float64) time.Duration

BackoffDelay is the wait before retry attempt (1-based): the upper half of an exponential ceiling, with jitter. random returns a number in [0, 1).

func Collect

func Collect[T any](items iter.Seq2[T, error], limit int) ([]T, error)

Collect gathers up to limit items (0 for all) from Paginate.

func NewIdempotencyKey

func NewIdempotencyKey(prefix string) string

NewIdempotencyKey is the prefix and 32 random hex characters.

func Paginate

func Paginate[T any](ctx context.Context, fetch func(ctx context.Context, cursor string) (Page[T], error)) iter.Seq2[T, error]

Paginate follows cursors until the last page. fetch receives the cursor ("" for the first page). Most Nexra lists return a capped array and need no paging; governance lists return items and nextCursor.

pages := nexra.Paginate(ctx, func(ctx context.Context, cursor string) (nexra.Page[nexraapi.EvidenceEntry], error) {
	request := client.GovernanceAPI.ListEvidence(ctx)
	if cursor != "" {
		request = request.Cursor(cursor)
	}
	page, response, err := request.Execute()
	if err != nil {
		return nexra.Page[nexraapi.EvidenceEntry]{}, nexra.ParseError(response, err)
	}
	return nexra.Page[nexraapi.EvidenceEntry]{Items: page.Items, NextCursor: page.GetNextCursor()}, nil
})
for entry, err := range pages { ... }
Example
package main

import (
	"context"
	"fmt"
	"log"

	nexra "github.com/TochukwuAchebe/nexra-go"
	"github.com/TochukwuAchebe/nexra-go/nexraapi"
)

func main() {
	ctx := context.Background()
	client, _ := nexra.NewClient(nexra.Options{})
	entries := nexra.Paginate(ctx, func(ctx context.Context, cursor string) (nexra.Page[nexraapi.EvidenceEntry], error) {
		request := client.GovernanceAPI.ListEvidence(ctx)
		if cursor != "" {
			request = request.Cursor(cursor)
		}
		page, response, err := request.Execute()
		if err != nil {
			return nexra.Page[nexraapi.EvidenceEntry]{}, nexra.ParseError(response, err)
		}
		return nexra.Page[nexraapi.EvidenceEntry]{Items: page.Items, NextCursor: page.GetNextCursor()}, nil
	})
	for entry, err := range entries {
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(entry.GetSequence(), entry.GetEventType())
	}
}

func ParseError

func ParseError(response *http.Response, err error) error

ParseError turns the error from a generated Execute() call into an *APIError when Nexra answered with a refusal. Other errors (network, context, decoding) are returned unchanged, and nil stays nil.

flow, response, err := client.FlowsAPI.GetFlow(ctx, id).Execute()
if err := nexra.ParseError(response, err); errors.Is(err, nexra.ErrNotFound) { ... }

func RetryAfter

func RetryAfter(value string, now time.Time) (time.Duration, bool)

RetryAfter parses a Retry-After value (seconds or an HTTP date). The second result is false when the value is missing or not valid.

func SignTriggerRequest

func SignTriggerRequest(rawBody []byte, secret, eventID string, timestamp time.Time) (map[string]string, error)

SignTriggerRequest returns the headers for a signed request to a Nexra webhook trigger (POST /api/connect/triggers/{publicId}). The signed bytes are "v1:<timestamp>:<eventID>:<raw body>". eventID must be unique per delivery: 8 to 128 of A-Z a-z 0-9 . _ : -

func SignWebhookPayload

func SignWebhookPayload(rawBody []byte, secret string, timestamp time.Time) string

SignWebhookPayload signs a body the way Nexra does. Useful for testing your handler.

func VerifyWebhookSignature

func VerifyWebhookSignature(rawBody []byte, header, secret string, options VerifyOptions) bool

VerifyWebhookSignature reports whether header is a valid, fresh signature of rawBody with secret. Nexra signs "<t>.<raw body>" with HMAC-SHA256 and sends "t=<unix seconds>,v1=<hex>". Verify the raw bytes before parsing them.

Types

type APIError

type APIError struct {
	Status    int
	Message   string
	Code      string
	RequestID string
	// RetryAfter is the Retry-After header, for 429 answers.
	RetryAfter string
	Body       []byte
}

APIError is a refusal from Nexra, with its status, message, stable code (where one exists) and request id. Quote RequestID when you contact support.

func NewAPIError

func NewAPIError(status int, header http.Header, body []byte) *APIError

NewAPIError builds an APIError from a status, headers and body in Nexra's error shape (other bodies are kept as text).

func (*APIError) Error

func (e *APIError) Error() string

func (*APIError) Is

func (e *APIError) Is(target error) bool

Is lets errors.Is match the sentinel errors.

func (*APIError) Kind

func (e *APIError) Kind() error

Kind is the sentinel error for this status, or nil.

type Client

type Client struct {
	*nexraapi.APIClient
	// BaseURL is the API address the client calls.
	BaseURL string
}

Client is the generated API client with the SDK's auth and retries installed. Use its service fields, for example client.RunsAPI.

func NewClient

func NewClient(options Options) (*Client, error)

NewClient makes a client for the Nexra API.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	nexra "github.com/TochukwuAchebe/nexra-go"
)

func main() {
	ctx := context.Background()
	client, err := nexra.NewClient(nexra.Options{}) // NEXRA_TOKEN and NEXRA_WORKSPACE_ID
	if err != nil {
		log.Fatal(err)
	}
	integrations, response, err := client.IntegrationsAPI.ListIntegrations(ctx).Execute()
	if err != nil {
		log.Fatal(nexra.ParseError(response, err))
	}
	for _, integration := range integrations {
		fmt.Println(integration.GetName(), integration.GetStatus())
	}
	_, response, err = client.FlowsAPI.GetFlow(ctx, "cmflow01orders").Execute()
	if err := nexra.ParseError(response, err); errors.Is(err, nexra.ErrNotFound) {
		fmt.Println("No such flow")
	}
}

func (*Client) StartRun

func (c *Client) StartRun(ctx context.Context, flowID, environment string, options StartRunOptions) (*nexraapi.Execution, error)

StartRun starts a run of a published flow in "sandbox" or "production" and returns the Execution. Refusals come back as *APIError.

type Event

type Event struct {
	ID          string          `json:"id"`
	Type        string          `json:"type"`
	Version     string          `json:"version"`
	CreatedAt   time.Time       `json:"createdAt"`
	Mode        string          `json:"mode"`
	WorkspaceID string          `json:"workspaceId"`
	Data        json.RawMessage `json:"data"`
}

Event is a Nexra event envelope. Data holds the event's own fields; decode it into the matching nexraapi.Event* model when you need them typed.

func ConstructEvent

func ConstructEvent(rawBody []byte, header, secret string, options VerifyOptions) (*Event, error)

ConstructEvent verifies the signature and returns the parsed event, or an error that matches ErrWebhookVerification.

type Options

type Options struct {
	// Token is a Nexra access token (sent as Authorization: Bearer).
	// Defaults to the NEXRA_TOKEN environment variable.
	Token string
	// TokenSource returns a current token for each request, for tokens you
	// refresh yourself. It takes precedence over Token.
	TokenSource func() (string, error)
	// WorkspaceID is the workspace to act in (X-Workspace-Id).
	// Defaults to NEXRA_WORKSPACE_ID.
	WorkspaceID string
	// BaseURL defaults to NEXRA_API_URL, then DefaultBaseURL.
	BaseURL string
	// Retry sets backoff. Nil uses DefaultRetryOptions.
	Retry *RetryOptions
	// DisableRetries turns retries off.
	DisableRetries bool
	// HTTPClient is used for requests. Its Transport is wrapped with auth
	// and retries. Defaults to a client with a 30 second timeout.
	HTTPClient *http.Client
	// Headers are added to every request.
	Headers map[string]string
}

Options configure a Client. Every field is optional.

type Page

type Page[T any] struct {
	Items      []T
	NextCursor string
}

Page is one page of a cursor-paged list. NextCursor is empty on the last page.

type RetryOptions

type RetryOptions struct {
	// MaxRetries after the first attempt. 0 turns retries off.
	MaxRetries int
	// BaseDelay is the first backoff.
	BaseDelay time.Duration
	// MaxDelay caps a single wait.
	MaxDelay time.Duration
}

RetryOptions set how failed requests are retried.

A request is retried when the network fails or the answer is 408, 425, 429, 500, 502, 503 or 504. To keep retries safe, a request that changes data (POST or PATCH) is only retried when Nexra did not act on it (429) or when its JSON body carries an idempotencyKey, which makes a repeat return the first result. Retry-After is honoured.

type RetryTransport

type RetryTransport struct {
	Base    http.RoundTripper
	Options RetryOptions
	// Sleep waits between attempts. Defaults to a timer that stops when the
	// request's context ends.
	Sleep func(request *http.Request, wait time.Duration) error
}

RetryTransport is an http.RoundTripper that applies Nexra's retry rules.

func (*RetryTransport) RoundTrip

func (t *RetryTransport) RoundTrip(request *http.Request) (*http.Response, error)

RoundTrip sends the request, retrying where it is safe.

type StartRunOptions

type StartRunOptions struct {
	// IdempotencyKey defaults to a fresh key, so a retry never starts a second run.
	IdempotencyKey string
	// CorrelationID defaults to a fresh id.
	CorrelationID    string
	TriggerReference string
	InputID          string
	EndUserID        string
	ExternalUserID   string
}

StartRunOptions are the optional fields of a run start.

type VerifyOptions

type VerifyOptions struct {
	Tolerance time.Duration
	Now       time.Time
}

VerifyOptions change signature checks. The zero value uses DefaultTolerance and the current time.

Directories

Path Synopsis
examples
quickstart command
Lists integrations and, given a flow id, starts a sandbox run and reads it back.
Lists integrations and, given a flow id, starts a sandbox run and reads it back.
webhook-receiver command
Receives Nexra events on :8080/nexra/events and verifies each signature.
Receives Nexra events on :8080/nexra/events and verifies each signature.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL