Help us shape the future of Go! Take the 2026 Go Developer Survey

trueup

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 27, 2026 License: MIT Imports: 15 Imported by: 0

README

TrueUp for Go

Go Reference

The official client for the TrueUp API. Send TrueUp two ledgers (a supplier statement and your receiving log, your books and the bank feed, invoices and payments) and it pairs every row, then tells you what's only on one side, what was counted twice and where the numbers disagree.

Go 1.21+. No dependencies beyond the standard library.

Install

go get github.com/merchantprotocol/trueup-go

Quickstart

Create an API key in the TrueUp dashboard (API keys), then set TRUEUP_API_KEY:

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/merchantprotocol/trueup-go"
)

func main() {
	client, err := trueup.NewClient() // reads TRUEUP_API_KEY
	if err != nil {
		log.Fatal(err)
	}
	// left: the side that bills or claims; right: the other side
	result, err := client.Reconcile(context.Background(), trueup.File("statement.csv"), trueup.File("receiving.csv"), nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(result.Headline)
	// 7 of 8 rows of statement.csv paired with receiving.csv; 1 only in statement.csv, ...
	for _, f := range result.Findings {
		fmt.Println(f.Kind, f.Subject, f.Detail)
	}
	// qty_mismatch statement.csv:row 5 Qty 24 vs qty_received 20; ...
	// phantom statement.csv:row 6 no match on the other side
}

Reconcile

A table is a file, file contents, or rows:

trueup.File("books.csv")
trueup.Content("books.csv", csvBytes)
trueup.Rows("invoices", []trueup.Row{{"Invoice #": "INV-10101", "Date": "2026-06-09", "Total": "$2,999.31"}})

CSV, TSV, JSON and JSON Lines are read, and date and number formats are detected. Nothing about the columns is configured.

Not sure which file is which? client.ReconcileFiles(ctx, []trueup.Table{trueup.File("a.csv"), trueup.File("b.csv")}, nil) picks the pair and the sides.

Reuse what was learned by passing an earlier result's weights, and answer the questions TrueUp wasn't sure about:

march, _ := client.Reconcile(ctx, trueup.File("march-statement.csv"), trueup.File("march-receiving.csv"), nil)
april, _ := client.Reconcile(ctx, trueup.File("april-statement.csv"), trueup.File("april-receiving.csv"),
	&trueup.ReconcileOptions{Weights: march.Details.Weights})

client.Reconcile(ctx, trueup.File("statement.csv"), trueup.File("receiving.csv"), &trueup.ReconcileOptions{
	Answers: &trueup.Answers{
		Same:      [][2]string{{"statement.csv:row 12", "receiving.csv:row 11"}},
		Different: [][2]string{{"statement.csv:row 3", "receiving.csv:row 9"}},
	},
})

Each call to Reconcile or ReconcileFiles counts as one analysis on your plan.

Findings

Kind Meaning
phantom Only on the left: billed or recorded, never matched
unbilled Only on the right: received or paid, never billed
duplicate, received_duplicate A copy of a row that's already paired
qty_mismatch, price_change, amount_mismatch Paired rows whose numbers disagree
unsure_pair A likely pair a person should confirm

Account and usage

acct, _ := client.Account(ctx)
usage, _ := client.Usage(ctx)
plans, _ := client.Plans(ctx)

Errors

API errors are *trueup.Error with Status, Code (the API's error code) and Message. Match kinds with errors.Is:

Sentinel When
trueup.ErrAuthentication 401: missing, unknown or revoked key
trueup.ErrInvalidRequest 400, 413, 415, 422: the request or the files need fixing (unsupported_file, not_reconcilable, ...)
trueup.ErrRateLimited 429 rate_limited: retried automatically; RetryAfter
trueup.ErrQuotaExceeded 429 quota_exceeded: the plan's monthly allowance is used up
trueup.ErrServer 5xx: retried automatically
trueup.ErrConnection the API couldn't be reached
if errors.Is(err, trueup.ErrQuotaExceeded) {
	fmt.Println("upgrade the plan")
}

Configuration

trueup.NewClient(
	trueup.WithAPIKey("tu_live_..."),       // default: TRUEUP_API_KEY
	trueup.WithBaseURL("https://..."),      // default: TRUEUP_BASE_URL, then the hosted API
	trueup.WithHTTPClient(&http.Client{Timeout: 5 * time.Minute}),
	trueup.WithMaxRetries(2),               // rate limits, 5xx and dropped connections
)

Development

The tests run in Docker against the live API:

export TRUEUP_API_KEY=tu_live_...   # a key for a test team (each run uses 2 analyses)
just test                            # or: docker compose run --rm test

License

MIT

Documentation

Overview

Package trueup is the official client for the TrueUp API.

Send TrueUp two ledgers (a supplier statement and your receiving log, your books and the bank feed, invoices and payments) and it pairs every row, then tells you what's only on one side, what was counted twice and where the numbers disagree.

client, err := trueup.NewClient() // reads TRUEUP_API_KEY
result, err := client.Reconcile(ctx, trueup.File("statement.csv"), trueup.File("receiving.csv"), nil)
for _, f := range result.Findings {
	fmt.Println(f.Kind, f.Subject, f.Detail)
}

Index

Constants

View Source
const DefaultBaseURL = "https://trueup-cloud.merchantprotocol.workers.dev"

DefaultBaseURL is the hosted TrueUp API.

View Source
const Version = "0.1.0"

Version of this SDK.

Variables

View Source
var (
	ErrAuthentication = errors.New("authentication")  // 401: missing, unknown or revoked API key
	ErrInvalidRequest = errors.New("invalid request") // 400, 413, 415, 422: the request or the files need fixing
	ErrNotFound       = errors.New("not found")       // 404, 405
	ErrRateLimited    = errors.New("rate limited")    // 429 rate_limited
	ErrQuotaExceeded  = errors.New("quota exceeded")  // 429 quota_exceeded: retrying won't help
	ErrServer         = errors.New("server error")    // 5xx
	ErrConnection     = errors.New("connection")      // the API couldn't be reached
)

Kinds of error, for errors.Is.

Functions

This section is empty.

Types

type Account

type Account struct {
	Team struct {
		ID   string `json:"id"`
		Name string `json:"name"`
	} `json:"team"`
	Plan *struct {
		Slug string `json:"slug"`
		Name string `json:"name"`
	} `json:"plan"`
	Key struct {
		ID     string `json:"id"`
		Name   string `json:"name"`
		Prefix string `json:"prefix"`
	} `json:"key"`
}

Account is the team, plan and key behind an API key.

type Answers

type Answers struct {
	Same      [][2]string `json:"same,omitempty"`
	Different [][2]string `json:"different,omitempty"`
}

Answers are decisions a person made about pairs: [left row, right row], e.g. {"statement.csv:row 3", "receiving.csv:row 4"}.

type Client

type Client struct {
	BaseURL    string
	HTTPClient *http.Client
	MaxRetries int
	// contains filtered or unexported fields
}

Client talks to the TrueUp API. It is safe for concurrent use.

func NewClient

func NewClient(opts ...Option) (*Client, error)

NewClient returns a client. It fails if there is no API key.

func (*Client) Account

func (c *Client) Account(ctx context.Context) (*Account, error)

Account returns the team, plan and key behind the client's API key.

func (*Client) Plans

func (c *Client) Plans(ctx context.Context) ([]Plan, error)

Plans returns the plans a team can be on.

func (*Client) Reconcile

func (c *Client) Reconcile(ctx context.Context, left, right Table, opts *ReconcileOptions) (*ReconcileResult, error)

Reconcile reconciles two tables. left is the side that bills or claims (a statement, your books), right the other side (receiving log, bank feed). opts may be nil. Counts as one analysis.

func (*Client) ReconcileFiles

func (c *Client) ReconcileFiles(ctx context.Context, files []Table, opts *ReconcileOptions) (*ReconcileResult, error)

ReconcileFiles sends two or more files; TrueUp picks the pair to reconcile and which side is which. opts may be nil. One analysis.

func (*Client) Usage

func (c *Client) Usage(ctx context.Context) (*Usage, error)

Usage returns this month's usage for the key's team.

type Details

type Details struct {
	Model   map[string]any  `json:"model"`
	Weights json.RawMessage `json:"weights"`
	Pairs   []Pair          `json:"pairs"`
}

Details are what the run learned and every pair it made.

type Error

type Error struct {
	Status     int
	Code       string
	Message    string
	RetryAfter time.Duration
	Body       []byte
	// contains filtered or unexported fields
}

Error is any error the API returned, or a failure to reach it. Code is the API's error code; branch on it (or use errors.Is with the sentinel kinds below).

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap lets errors.Is(err, trueup.ErrQuotaExceeded) and friends work.

type Finding

type Finding struct {
	// Kind: phantom, unbilled, duplicate, received_duplicate, qty_mismatch, price_change, amount_mismatch, unsure_pair.
	Kind    string `json:"kind"`
	Label   string `json:"label"`
	Subject string `json:"subject"`
	Detail  string `json:"detail"`
	// Confidence is how likely the pairing is right; nil for rows with no pair.
	Confidence *float64       `json:"confidence"`
	Status     string         `json:"status"`
	Amount     *float64       `json:"amount"`
	Provenance []string       `json:"provenance"`
	Data       map[string]any `json:"data"`
}

Finding is one thing TrueUp found.

type Option

type Option func(*Client)

Option configures a Client.

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the API key (default: TRUEUP_API_KEY).

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL sets the API address (default: TRUEUP_BASE_URL, then the hosted API).

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient sets the HTTP client (default: 300 s timeout; big ledgers take a while).

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets retries for rate limits, server errors and dropped connections (default 2).

type Pair

type Pair struct {
	Left       string  `json:"left"`
	Right      string  `json:"right"`
	Confidence float64 `json:"confidence"`
}

Pair is one pairing of a left row with a right row.

type Plan

type Plan struct {
	Slug        string `json:"slug"`
	Name        string `json:"name"`
	Description string `json:"description"`
	PriceCents  int    `json:"price_cents"`
	Interval    string `json:"interval"`
	Purchasable bool   `json:"purchasable"`
	Limits      []struct {
		Metric               string `json:"metric"`
		Included             int    `json:"included"`
		HardCap              bool   `json:"hard_cap"`
		OverageMicrosPerUnit *int   `json:"overage_micros_per_unit"`
	} `json:"limits"`
}

Plan is a plan a team can be on.

type ReconcileOptions

type ReconcileOptions struct {
	// Weights is Details.Weights from an earlier result: apply what was learned then instead of learning again.
	Weights json.RawMessage
	Answers *Answers
}

ReconcileOptions are optional settings for a reconcile call.

type ReconcileResult

type ReconcileResult struct {
	Analysis string             `json:"analysis"`
	Title    string             `json:"title"`
	Headline string             `json:"headline"`
	Stats    map[string]float64 `json:"stats"`
	Findings []Finding          `json:"findings"`
	Details  Details            `json:"details"`
	Inputs   []string           `json:"inputs"`
	Engine   string             `json:"engine"`
}

ReconcileResult is the answer to a reconcile call.

type Row

type Row map[string]any

Row is one row of a table: column name -> value (string, number, bool or nil).

type Table

type Table struct {
	Name string
	// contains filtered or unexported fields
}

Table is one table to reconcile: a file, file contents, or rows. Name is how findings refer to its rows ("statement.csv:row 5"). Build one with File, Content or Rows.

func Content

func Content(name string, content []byte) Table

Content is a table from file contents; name's extension says the format (.csv, .tsv, .json, .jsonl).

func File

func File(path string) Table

File is a table read from a file on disk when the request is sent.

func Rows

func Rows(name string, rows []Row) Table

Rows is a table from rows.

type Usage

type Usage struct {
	Period   string        `json:"period"`
	ResetsAt string        `json:"resets_at"`
	Metrics  []UsageMetric `json:"metrics"`
}

Usage is this month's usage for a team.

type UsageMetric

type UsageMetric struct {
	Metric    string `json:"metric"`
	Label     string `json:"label"`
	Used      int    `json:"used"`
	Included  int    `json:"included"`
	Remaining int    `json:"remaining"`
	HardCap   bool   `json:"hard_cap"`
	Overage   int    `json:"overage"`
}

UsageMetric is this month's use of one metric.

Jump to

Keyboard shortcuts

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