go-athenahealth is an athenahealth API client for Go.
$ go get github.com/eleanorhealth/go-athenahealth
package main
import (
"fmt"
"log"
"os"
"github.com/eleanorhealth/go-athenahealth/athenahealth"
)
func main() {
practiceID := "195900" // athenahealth shared sandbox practice ID.
key := "your-api-key"
secret := "your-api-secret"
client := athenahealth.NewHTTPClient(&http.Client{}, practiceID, key, secret)
p, err := client.GetPatient("1")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s %s\n", p.FirstName, p.LastName)
}
Use tokencacher.File
to cache API tokens to a file.
client := athenahealth.NewHTTPClient(&http.Client{}, practiceID, key, secret).
WithTokenCacher(tokencacher.NewFile("/tmp/athena_token.json"))
Clients can obtain the X-Request-Id sent on the request to athena from the
X-Request-Id
header on the http.Request
for use in tracing, logging, and debugging.
xRequestId := req.Header.Get(athenahealth.XRequestIDHeaderKey))
See the athena Best Practices guide for more details about X-Request-Id and other recommended practices.
All methods that perform network or filesystem IO will accept a context for idiomatic propagation.
While not yet consistently applied across this repo, required fields should be surfaced as top-level arguments in method signatures (including path parameters, query parameters, and request body fields) while optional fields should be embedded as pointer fields in an optional pointer struct. The aim is to provide a clear, consistent, and self-documenting interface for required vs. optional fields as well as to add compile-time safety to required fields.
Optional fields options struct should be the last argument in the method signature, preceded by top-level required fields.
type CreatePatientOptions struct {
Address1 *string
Address2 *string
City *string
Email *string
HomePhone *string
MiddleName *string
MobilePhone *string
Notes *string
Sex *string
SSN *string
State *string
Status *string
Zip *string
BypassPatientMatching *bool
}
type Client interface {
// ...
CreatePatient(ctx context.Context, departmentID, firstName, lastName string, dob time.Time, opts *CreatePatientOptions) (string, error)
// ...
}
samber/lo#toptr or a custom rolled to-pointer helper is recommended to improve the ergonomics of passing the optional pointer fields
func ToPtr[T any](x T) *T { return &x }
Required fields should be checked for invalid zero values to prevent unnecessary error-prone calls to athena and surface clearer errors, relying on errors.Join
to improve DX and prevent waterfalling errors.
func (h *HTTPClient) CreatePatient(ctx context.Context, departmentID, firstName, lastName string, dob time.Time, opts *CreatePatientOptions) (string, error) {
var requiredParamErrors []error
if len(departmentID) == 0 {
requiredParamErrors = append(requiredParamErrors, errors.New("department ID is required"))
}
if len(firstName) == 0 {
requiredParamErrors = append(requiredParamErrors, errors.New("first name is required"))
}
if len(lastName) == 0 {
requiredParamErrors = append(requiredParamErrors, errors.New("last name is required"))
}
if dob.IsZero() {
requiredParamErrors = append(requiredParamErrors, errors.New("date of birth is required"))
}
if len(requiredParamErrors) > 0 {
return nil, errors.Join(requiredParamErrors...)
}
// ...
}
If there are only required fields, options struct is omitted.
type Client interface {
// ...
SearchAllergies(ctx context.Context, searchVal string) ([]*Allergy, error)
// ...
}
If there are only optional fields, expect a standalone options struct.
type Client interface {
// ...
ListChangedPatients(ctx context.Context, opts *ListChangedPatientOptions) ([]*Patient, error)
// ...
}
If neither required or optional fields are present, expect neither.
type Client interface {
// ...
ListAppointmentCustomFields(context.Context) ([]*AppointmentCustomField, error)
// ...
}