// Drop-in Go client library for the Friendship Tracker HTTP API. // // Save this file alongside your code as `friendship_client.go` (or in its // own subpackage) and use the Client type: // // import "yourproject/friendship_client" // // c := friendship_client.New("pat_...") // rows, err := c.AccountList(&friendship_client.ListOpts{Limit: 20, Sort: "-created_at"}) // // Every endpoint exposed by the HTTP API is wrapped as a // `` method on Client. List endpoints take *ListOpts; // get/update/delete endpoints take the row id as their first argument. // // Provided as-is, with no warranty. Vendor freely; modify as needed. // Targets Go 1.21+; uses only the standard library. // // DO NOT EDIT THIS FILE MANUALLY - re-download from the docs site. // Local edits will be overwritten by the once-per-day version check. package friendship_client import ( "bytes" "crypto/rand" "encoding/hex" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "os" "path/filepath" "runtime" "strconv" "strings" "sync" "time" ) // ── Identity (substituted at generation time) ─────────────────────── const ( AppSlug = "friendship" AppName = "Friendship Tracker" ModuleName = "friendship_client" ClientVersion = "0.3.13" Language = "go" defaultBase = "https://friendship-tracker.com" ) // TypesJSON is the per-type metadata baked at generation time. // Available at runtime when calling code needs to know the legal // filters / sort columns / max_limit for a model without a second // round-trip. const TypesJSON = `{"activity":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","kind","summary","description","occurred_at","location"],"update_fields":["kind","summary","description","occurred_at","location"],"allowed_filters":["data__parent_id","data__kind","status","is_archived","owned_by"],"allowed_sorts":["created_at","data__occurred_at"],"default_sort":"data__occurred_at","max_limit":200,"fields":[{"name":"kind","type":"enum","values":["meeting","call","email","message","event","other"]},{"name":"summary","type":"string","max_len":400},{"name":"location","type":"string","max_len":200},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}},{"name":"description","type":"string","max_len":4000},{"name":"occurred_at","type":"string","max_len":32}]},"contact":{"ops":["list","read","create","update","delete"],"create_fields":["name","nickname","pronouns","email","phone","secondary_email","secondary_phone","company","job_title","address_line","city","country","website","linkedin","twitter","birthday","anniversary","gender","how_we_met","food_prefs","allergies","last_contacted_at","stay_in_touch_frequency","stay_in_touch_topic","notes","tags","favorite","avatar_blob_id","color"],"update_fields":["name","nickname","pronouns","email","phone","secondary_email","secondary_phone","company","job_title","address_line","city","country","website","linkedin","twitter","birthday","anniversary","gender","how_we_met","food_prefs","allergies","last_contacted_at","stay_in_touch_frequency","stay_in_touch_topic","notes","tags","favorite","avatar_blob_id","color"],"allowed_filters":["data__name","data__email","data__company","data__city","data__country","data__favorite","data__tags","data__gender","data__stay_in_touch_frequency","status","is_archived","owned_by"],"allowed_sorts":["created_at","updated_at","data__name","data__company","data__last_contacted_at","data__birthday","data__stay_in_touch_frequency"],"default_sort":"data__name","max_limit":200,"fields":[{"name":"city","type":"string","max_len":120},{"name":"name","type":"string","max_len":200},{"name":"tags","type":"tags"},{"name":"color","type":"string","max_len":24},{"name":"email","type":"string","max_len":320},{"name":"notes","type":"string","max_len":8000},{"name":"phone","type":"string","max_len":64},{"name":"gender","type":"enum","values":["male","female","other","unspecified"]},{"name":"company","type":"string","max_len":200},{"name":"country","type":"string","max_len":120},{"name":"twitter","type":"url"},{"name":"website","type":"url"},{"name":"birthday","type":"string","max_len":32},{"name":"favorite","type":"bool"},{"name":"linkedin","type":"url"},{"name":"nickname","type":"string","max_len":120},{"name":"pronouns","type":"string","max_len":32},{"name":"allergies","type":"string","max_len":600},{"name":"job_title","type":"string","max_len":200},{"name":"food_prefs","type":"string","max_len":600},{"name":"how_we_met","type":"string","max_len":1000},{"name":"anniversary","type":"string","max_len":32},{"name":"address_line","type":"string","max_len":200},{"name":"avatar_blob_id","type":"string","max_len":64},{"name":"secondary_email","type":"string","max_len":320},{"name":"secondary_phone","type":"string","max_len":64},{"name":"last_contacted_at","type":"string","max_len":32},{"name":"stay_in_touch_topic","type":"string","max_len":400},{"name":"stay_in_touch_frequency","type":"enum","values":["never","weekly","biweekly","monthly","quarterly","yearly"]}]},"conversation":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","channel","summary","content","occurred_at","sentiment","duration_minutes"],"update_fields":["channel","summary","content","occurred_at","sentiment","duration_minutes"],"allowed_filters":["data__parent_id","data__channel","data__sentiment","status","is_archived","owned_by"],"allowed_sorts":["created_at","data__occurred_at"],"default_sort":"data__occurred_at","max_limit":200,"fields":[{"name":"channel","type":"enum","values":["call","sms","whatsapp","email","in_person","video","voice","letter","other"]},{"name":"content","type":"string","max_len":8000},{"name":"summary","type":"string","max_len":400},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}},{"name":"sentiment","type":"enum","values":["positive","neutral","negative"]},{"name":"occurred_at","type":"string","max_len":32},{"name":"duration_minutes","type":"number"}]},"custom_field":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","label","value","kind","icon"],"update_fields":["label","value","kind","icon"],"allowed_filters":["data__parent_id","data__kind","status","is_archived","owned_by"],"allowed_sorts":["created_at","data__label"],"default_sort":"created_at","max_limit":200,"fields":[{"name":"icon","type":"string","max_len":32},{"name":"kind","type":"enum","values":["text","number","date","url","bool"]},{"name":"label","type":"string","max_len":80},{"name":"value","type":"string","max_len":2000},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}}]},"gift":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","title","occasion","status","occurred_at","price","currency","url","notes"],"update_fields":["title","occasion","status","occurred_at","price","currency","url","notes"],"allowed_filters":["data__parent_id","data__status","data__occasion","status","is_archived","owned_by"],"allowed_sorts":["created_at","data__occurred_at","data__title"],"default_sort":"data__occurred_at","max_limit":200,"fields":[{"name":"url","type":"url"},{"name":"notes","type":"string","max_len":4000},{"name":"price","type":"number"},{"name":"title","type":"string","max_len":200},{"name":"status","type":"enum","values":["idea","given","received"]},{"name":"currency","type":"string","max_len":8},{"name":"occasion","type":"string","max_len":120},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}},{"name":"occurred_at","type":"string","max_len":32}]},"journal_entry":{"ops":["list","read","create","update","delete"],"create_fields":["title","body","mood","occurred_at","tags"],"update_fields":["title","body","mood","occurred_at","tags"],"allowed_filters":["data__mood","data__tags","status","is_archived","owned_by"],"allowed_sorts":["data__occurred_at","created_at","updated_at"],"default_sort":"data__occurred_at","max_limit":200,"fields":[{"name":"body","type":"string","max_len":16000},{"name":"mood","type":"enum","values":["great","good","ok","down","awful"]},{"name":"tags","type":"tags"},{"name":"title","type":"string","max_len":200},{"name":"occurred_at","type":"string","max_len":32}]},"life_event":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","kind","title","occurred_at","description","location","recurring"],"update_fields":["kind","title","occurred_at","description","location","recurring"],"allowed_filters":["data__parent_id","data__kind","data__recurring","status","is_archived","owned_by"],"allowed_sorts":["data__occurred_at","created_at"],"default_sort":"data__occurred_at","max_limit":200,"fields":[{"name":"kind","type":"enum","values":["birthday","anniversary","met","graduation","job_start","job_end","move","marriage","birth","loss","milestone","custom"]},{"name":"title","type":"string","max_len":200},{"name":"location","type":"string","max_len":200},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}},{"name":"recurring","type":"bool"},{"name":"description","type":"string","max_len":4000},{"name":"occurred_at","type":"string","max_len":32}]},"note":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","body","pinned"],"update_fields":["body","pinned"],"allowed_filters":["data__parent_id","data__pinned","status","is_archived","owned_by"],"allowed_sorts":["created_at","updated_at"],"default_sort":"created_at","max_limit":200,"fields":[{"name":"body","type":"string","max_len":8000},{"name":"pinned","type":"bool"},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}}]},"pet":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","name","species","species_other","breed","born_at","color","notes"],"update_fields":["name","species","species_other","breed","born_at","color","notes"],"allowed_filters":["data__parent_id","data__species","status","is_archived","owned_by"],"allowed_sorts":["created_at","data__name","data__born_at"],"default_sort":"data__name","max_limit":200,"fields":[{"name":"name","type":"string","max_len":80},{"name":"breed","type":"string","max_len":120},{"name":"color","type":"string","max_len":80},{"name":"notes","type":"string","max_len":2000},{"name":"born_at","type":"string","max_len":32},{"name":"species","type":"enum","values":["dog","cat","bird","fish","rabbit","hamster","guinea_pig","reptile","horse","other"]},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}},{"name":"species_other","type":"string","max_len":80}]},"relationship":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","target_id","kind","label","since","notes"],"update_fields":["kind","label","since","notes"],"allowed_filters":["data__parent_id","data__target_id","data__kind","status","is_archived","owned_by"],"allowed_sorts":["created_at","data__kind"],"default_sort":"data__kind","max_limit":200,"fields":[{"name":"kind","type":"enum","values":["partner","spouse","parent","child","sibling","friend","colleague","manager","report","mentor","mentee","other"]},{"name":"label","type":"string","max_len":80},{"name":"notes","type":"string","max_len":2000},{"name":"since","type":"string","max_len":32},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}},{"name":"target_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}}]},"reminder":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","message","due_date","completed"],"update_fields":["message","due_date","completed"],"allowed_filters":["data__parent_id","data__completed","status","is_archived","owned_by"],"allowed_sorts":["created_at","data__due_date"],"default_sort":"data__due_date","max_limit":200,"fields":[{"name":"message","type":"string","max_len":400},{"name":"due_date","type":"string","max_len":32},{"name":"completed","type":"bool"},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}}]}}` // ── Configuration ────────────────────────────────────────────────── // ListOpts mirrors the standard query parameters the list endpoints // accept. Filters carries arbitrary additional ?key=value pairs. type ListOpts struct { Limit int Offset int Sort string Q string Filters map[string]any } // Client is the per-app HTTP client. Reuse across requests; safe for // concurrent use. type Client struct { HTTPClient *http.Client BaseURL string Token string once sync.Once deviceID string sessID string } // New returns a Client wired to the host this library was generated // against. Pass a personal access token; an empty string falls back to // the XCLIENT_TOKEN environment variable. func New(token string) *Client { base := os.Getenv("XCLIENT_BASE_URL") if base == "" { base = defaultBase } if token == "" { token = os.Getenv("XCLIENT_TOKEN") } return &Client{ HTTPClient: &http.Client{Timeout: 30 * time.Second}, BaseURL: strings.TrimRight(base, "/"), Token: token, } } func (c *Client) initIDs() { c.once.Do(func() { c.deviceID = loadOrMintDeviceID() c.sessID = mintUUID() }) } // ── Identifier persistence ───────────────────────────────────────── func stateDir() string { home, err := os.UserHomeDir() if err != nil || home == "" { return "" } d := filepath.Join(home, "."+ModuleName) _ = os.MkdirAll(d, 0o700) return d } func mintUUID() string { var b [16]byte _, _ = rand.Read(b[:]) b[6] = (b[6] & 0x0f) | 0x40 b[8] = (b[8] & 0x3f) | 0x80 h := hex.EncodeToString(b[:]) return h[0:8] + "-" + h[8:12] + "-" + h[12:16] + "-" + h[16:20] + "-" + h[20:32] } func loadOrMintDeviceID() string { dir := stateDir() if dir == "" { return mintUUID() } f := filepath.Join(dir, "device.json") if raw, err := os.ReadFile(f); err == nil { var blob struct { DeviceID string `json:"device_id"` } if json.Unmarshal(raw, &blob) == nil && len(blob.DeviceID) >= 32 { return blob.DeviceID } } id := mintUUID() body, _ := json.Marshal(map[string]string{"device_id": id}) _ = os.WriteFile(f, body, 0o600) return id } // ── Telemetry toggles ────────────────────────────────────────────── func autoupdateEnabled() bool { v := strings.ToLower(os.Getenv("XCLIENT_NO_AUTOUPDATE")) return v != "1" && v != "true" && v != "yes" } // ── Editor / runtime fingerprint ─────────────────────────────────── func fingerprint() map[string]any { out := map[string]any{ "go_version": runtime.Version(), "os": runtime.GOOS, "arch": runtime.GOARCH, } out["term_program"] = os.Getenv("TERM_PROGRAM") out["editor_env"] = os.Getenv("EDITOR") out["ci"] = os.Getenv("CI") != "" || os.Getenv("GITHUB_ACTIONS") != "" out["claude_code"] = os.Getenv("CLAUDECODE") != "" || os.Getenv("CLAUDE_CODE_ENTRYPOINT") != "" out["codex"] = os.Getenv("CODEX_HOME") != "" tp := strings.ToLower(os.Getenv("TERM_PROGRAM")) out["vscode"] = tp == "vscode" && os.Getenv("CURSOR_TRACE_ID") == "" out["cursor"] = os.Getenv("CURSOR_TRACE_ID") != "" out["antigravity"] = os.Getenv("ANTIGRAVITY_TRACE_ID") != "" out["jetbrains"] = strings.Contains(tp, "jetbrains") return out } // ── HTTP transport ───────────────────────────────────────────────── // APIError wraps a non-2xx response. type APIError struct { Status int Message string Body any } func (e *APIError) Error() string { return fmt.Sprintf("HTTP %d: %s", e.Status, e.Message) } var retryableStatus = map[int]struct{}{ 408: {}, 425: {}, 429: {}, 500: {}, 502: {}, 503: {}, 504: {}, } func backoff(attempt int, retryAfterSec float64) time.Duration { if retryAfterSec >= 0 { if retryAfterSec > 60 { retryAfterSec = 60 } return time.Duration(retryAfterSec * float64(time.Second)) } delay := float64(int(1) << uint(attempt)) if delay > 60 { delay = 60 } return time.Duration(delay * float64(time.Second)) } func (c *Client) userAgent() string { return fmt.Sprintf("%s/%s (lib/%s; go/%s; %s)", ModuleName, ClientVersion, Language, runtime.Version(), runtime.GOOS) } // requestJSON fires one method+path against the API, JSON in / JSON // out. Pass nil body for read-only verbs. func (c *Client) requestJSON(method, path string, body any) (map[string]any, error) { c.maybeAutoupdate() c.initIDs() u := c.BaseURL + path var data []byte if body != nil { var err error data, err = json.Marshal(body) if err != nil { return nil, err } } const maxRetries = 3 var lastErr error for attempt := 0; attempt < maxRetries; attempt++ { var bodyReader io.Reader if data != nil { bodyReader = bytes.NewReader(data) } req, err := http.NewRequest(method, u, bodyReader) if err != nil { return nil, err } req.Header.Set("Accept", "application/json") req.Header.Set("User-Agent", c.userAgent()) req.Header.Set("X-Client-Channel", "client_"+Language) req.Header.Set("X-Client-Version", ClientVersion) req.Header.Set("X-Analytics-Device-Id", c.deviceID) req.Header.Set("X-Analytics-Session-Id", c.sessID) if c.Token != "" { req.Header.Set("Authorization", "Bearer "+c.Token) } if data != nil { req.Header.Set("Content-Type", "application/json") } resp, err := c.HTTPClient.Do(req) if err != nil { lastErr = err if attempt+1 < maxRetries { time.Sleep(backoff(attempt, -1)) continue } c.emitCallEvent(method, path, 0, false) return nil, &APIError{Status: 0, Message: err.Error()} } raw, _ := io.ReadAll(resp.Body) _ = resp.Body.Close() c.maybePersistRefresh(resp.Header) if _, retry := retryableStatus[resp.StatusCode]; retry && attempt+1 < maxRetries { ra := -1.0 if v := resp.Header.Get("Retry-After"); v != "" { if f, err2 := strconv.ParseFloat(v, 64); err2 == nil { ra = f } } time.Sleep(backoff(attempt, ra)) continue } if resp.StatusCode >= 400 { var parsed any _ = json.Unmarshal(raw, &parsed) msg := http.StatusText(resp.StatusCode) if m, ok := parsed.(map[string]any); ok { if d, ok2 := m["detail"].(string); ok2 { msg = d } else if d, ok2 := m["message"].(string); ok2 { msg = d } } c.emitCallEvent(method, path, resp.StatusCode, false) return nil, &APIError{Status: resp.StatusCode, Message: msg, Body: parsed} } c.emitCallEvent(method, path, resp.StatusCode, true) if len(raw) == 0 { return nil, nil } var out map[string]any if err := json.Unmarshal(raw, &out); err != nil { return nil, err } return out, nil } if lastErr != nil { c.emitCallEvent(method, path, 0, false) return nil, &APIError{Status: 0, Message: lastErr.Error()} } return nil, errors.New("request failed") } // requestList wraps requestJSON for list endpoints, lifting *ListOpts // into the query string. func (c *Client) requestList(path string, opts *ListOpts) (map[string]any, error) { q := url.Values{} if opts != nil { if opts.Limit > 0 { q.Set("limit", strconv.Itoa(opts.Limit)) } if opts.Offset > 0 { q.Set("offset", strconv.Itoa(opts.Offset)) } if opts.Sort != "" { q.Set("sort", opts.Sort) } if opts.Q != "" { q.Set("q", opts.Q) } for k, v := range opts.Filters { if v == nil { continue } q.Set(k, fmt.Sprint(v)) } } if encoded := q.Encode(); encoded != "" { path = path + "?" + encoded } return c.requestJSON("GET", path, nil) } func (c *Client) maybePersistRefresh(h http.Header) { if v := h.Get("x-auth-refresh-token"); v != "" { c.Token = v } } // ── Analytics ────────────────────────────────────────────────────── var metaSentOnce sync.Once func (c *Client) emitCallEvent(method, pathStr string, status int, ok bool) { go func() { defer func() { _ = recover() }() meta := map[string]any{ "channel": "client_" + Language, "client_version": ClientVersion, "module_name": ModuleName, "language": Language, "os": runtime.GOOS, "go_version": runtime.Version(), } var addEnv bool metaSentOnce.Do(func() { addEnv = true }) if addEnv { meta["env"] = fingerprint() } evt := map[string]any{ "type": "client.call", "ts_client": time.Now().Unix(), "meta": map[string]any{ "method": strings.ToUpper(method), "path": strings.SplitN(pathStr, "?", 2)[0], "status": status, "ok": ok, }, } body := map[string]any{ "device_id": c.deviceID, "session_id": c.sessID, "events": []any{evt}, "meta": meta, } raw, _ := json.Marshal(body) req, err := http.NewRequest("POST", c.BaseURL+"/xapi2/analytics/challenge", bytes.NewReader(raw)) if err != nil { return } req.Header.Set("Content-Type", "application/json") req.Header.Set("User-Agent", c.userAgent()) hc := &http.Client{Timeout: 4 * time.Second} resp, err := hc.Do(req) if err != nil { return } _, _ = io.Copy(io.Discard, resp.Body) _ = resp.Body.Close() }() } // ── Auto-update ──────────────────────────────────────────────────── var autoupdateOnce sync.Once func (c *Client) maybeAutoupdate() { autoupdateOnce.Do(func() { if !autoupdateEnabled() { return } go c.runAutoupdate() }) } func (c *Client) runAutoupdate() { defer func() { _ = recover() }() dir := stateDir() if dir == "" { return } stamp := filepath.Join(dir, "update_check.json") if raw, err := os.ReadFile(stamp); err == nil { var blob struct { CheckedAt int64 `json:"checked_at"` } if json.Unmarshal(raw, &blob) == nil { if time.Now().Unix()-blob.CheckedAt < 86400 { return } } } hc := &http.Client{Timeout: 6 * time.Second} resp, err := hc.Get(c.BaseURL + "/xapi2/clients/version") if err != nil { return } raw, _ := io.ReadAll(resp.Body) _ = resp.Body.Close() var payload struct { Version string `json:"version"` } if json.Unmarshal(raw, &payload) != nil { return } stampBody, _ := json.Marshal(map[string]any{"checked_at": time.Now().Unix()}) _ = os.WriteFile(stamp, stampBody, 0o600) if payload.Version == "" || payload.Version == ClientVersion { return } // Source replacement is intentionally a no-op in Go - the user is // running a compiled binary, the .go file on disk is just a record // of the version they vendored. Surface the new version through // the next build. } // ActivityList lists activity rows. Pass nil opts for defaults. func (c *Client) ActivityList(opts *ListOpts) (map[string]any, error) { return c.requestList("/xapi2/data/activity", opts) } // ActivityGet fetches one activity row by id. func (c *Client) ActivityGet(id string) (map[string]any, error) { return c.requestJSON("GET", "/xapi2/data/activity/"+id, nil) } // ActivityCreate creates a new activity row. func (c *Client) ActivityCreate(data map[string]any) (map[string]any, error) { return c.requestJSON("POST", "/xapi2/data/activity", data) } // ActivityUpdate patches an existing activity row. func (c *Client) ActivityUpdate(id string, data map[string]any) (map[string]any, error) { return c.requestJSON("PATCH", "/xapi2/data/activity/"+id, data) } // ActivityDelete deletes a activity row. func (c *Client) ActivityDelete(id string) error { _, err := c.requestJSON("DELETE", "/xapi2/data/activity/"+id, nil) return err } // ContactList lists contact rows. Pass nil opts for defaults. func (c *Client) ContactList(opts *ListOpts) (map[string]any, error) { return c.requestList("/xapi2/data/contact", opts) } // ContactGet fetches one contact row by id. func (c *Client) ContactGet(id string) (map[string]any, error) { return c.requestJSON("GET", "/xapi2/data/contact/"+id, nil) } // ContactCreate creates a new contact row. func (c *Client) ContactCreate(data map[string]any) (map[string]any, error) { return c.requestJSON("POST", "/xapi2/data/contact", data) } // ContactUpdate patches an existing contact row. func (c *Client) ContactUpdate(id string, data map[string]any) (map[string]any, error) { return c.requestJSON("PATCH", "/xapi2/data/contact/"+id, data) } // ContactDelete deletes a contact row. func (c *Client) ContactDelete(id string) error { _, err := c.requestJSON("DELETE", "/xapi2/data/contact/"+id, nil) return err } // ConversationList lists conversation rows. Pass nil opts for defaults. func (c *Client) ConversationList(opts *ListOpts) (map[string]any, error) { return c.requestList("/xapi2/data/conversation", opts) } // ConversationGet fetches one conversation row by id. func (c *Client) ConversationGet(id string) (map[string]any, error) { return c.requestJSON("GET", "/xapi2/data/conversation/"+id, nil) } // ConversationCreate creates a new conversation row. func (c *Client) ConversationCreate(data map[string]any) (map[string]any, error) { return c.requestJSON("POST", "/xapi2/data/conversation", data) } // ConversationUpdate patches an existing conversation row. func (c *Client) ConversationUpdate(id string, data map[string]any) (map[string]any, error) { return c.requestJSON("PATCH", "/xapi2/data/conversation/"+id, data) } // ConversationDelete deletes a conversation row. func (c *Client) ConversationDelete(id string) error { _, err := c.requestJSON("DELETE", "/xapi2/data/conversation/"+id, nil) return err } // CustomFieldList lists custom_field rows. Pass nil opts for defaults. func (c *Client) CustomFieldList(opts *ListOpts) (map[string]any, error) { return c.requestList("/xapi2/data/custom_field", opts) } // CustomFieldGet fetches one custom_field row by id. func (c *Client) CustomFieldGet(id string) (map[string]any, error) { return c.requestJSON("GET", "/xapi2/data/custom_field/"+id, nil) } // CustomFieldCreate creates a new custom_field row. func (c *Client) CustomFieldCreate(data map[string]any) (map[string]any, error) { return c.requestJSON("POST", "/xapi2/data/custom_field", data) } // CustomFieldUpdate patches an existing custom_field row. func (c *Client) CustomFieldUpdate(id string, data map[string]any) (map[string]any, error) { return c.requestJSON("PATCH", "/xapi2/data/custom_field/"+id, data) } // CustomFieldDelete deletes a custom_field row. func (c *Client) CustomFieldDelete(id string) error { _, err := c.requestJSON("DELETE", "/xapi2/data/custom_field/"+id, nil) return err } // GiftList lists gift rows. Pass nil opts for defaults. func (c *Client) GiftList(opts *ListOpts) (map[string]any, error) { return c.requestList("/xapi2/data/gift", opts) } // GiftGet fetches one gift row by id. func (c *Client) GiftGet(id string) (map[string]any, error) { return c.requestJSON("GET", "/xapi2/data/gift/"+id, nil) } // GiftCreate creates a new gift row. func (c *Client) GiftCreate(data map[string]any) (map[string]any, error) { return c.requestJSON("POST", "/xapi2/data/gift", data) } // GiftUpdate patches an existing gift row. func (c *Client) GiftUpdate(id string, data map[string]any) (map[string]any, error) { return c.requestJSON("PATCH", "/xapi2/data/gift/"+id, data) } // GiftDelete deletes a gift row. func (c *Client) GiftDelete(id string) error { _, err := c.requestJSON("DELETE", "/xapi2/data/gift/"+id, nil) return err } // JournalEntryList lists journal_entry rows. Pass nil opts for defaults. func (c *Client) JournalEntryList(opts *ListOpts) (map[string]any, error) { return c.requestList("/xapi2/data/journal_entry", opts) } // JournalEntryGet fetches one journal_entry row by id. func (c *Client) JournalEntryGet(id string) (map[string]any, error) { return c.requestJSON("GET", "/xapi2/data/journal_entry/"+id, nil) } // JournalEntryCreate creates a new journal_entry row. func (c *Client) JournalEntryCreate(data map[string]any) (map[string]any, error) { return c.requestJSON("POST", "/xapi2/data/journal_entry", data) } // JournalEntryUpdate patches an existing journal_entry row. func (c *Client) JournalEntryUpdate(id string, data map[string]any) (map[string]any, error) { return c.requestJSON("PATCH", "/xapi2/data/journal_entry/"+id, data) } // JournalEntryDelete deletes a journal_entry row. func (c *Client) JournalEntryDelete(id string) error { _, err := c.requestJSON("DELETE", "/xapi2/data/journal_entry/"+id, nil) return err } // LifeEventList lists life_event rows. Pass nil opts for defaults. func (c *Client) LifeEventList(opts *ListOpts) (map[string]any, error) { return c.requestList("/xapi2/data/life_event", opts) } // LifeEventGet fetches one life_event row by id. func (c *Client) LifeEventGet(id string) (map[string]any, error) { return c.requestJSON("GET", "/xapi2/data/life_event/"+id, nil) } // LifeEventCreate creates a new life_event row. func (c *Client) LifeEventCreate(data map[string]any) (map[string]any, error) { return c.requestJSON("POST", "/xapi2/data/life_event", data) } // LifeEventUpdate patches an existing life_event row. func (c *Client) LifeEventUpdate(id string, data map[string]any) (map[string]any, error) { return c.requestJSON("PATCH", "/xapi2/data/life_event/"+id, data) } // LifeEventDelete deletes a life_event row. func (c *Client) LifeEventDelete(id string) error { _, err := c.requestJSON("DELETE", "/xapi2/data/life_event/"+id, nil) return err } // NoteList lists note rows. Pass nil opts for defaults. func (c *Client) NoteList(opts *ListOpts) (map[string]any, error) { return c.requestList("/xapi2/data/note", opts) } // NoteGet fetches one note row by id. func (c *Client) NoteGet(id string) (map[string]any, error) { return c.requestJSON("GET", "/xapi2/data/note/"+id, nil) } // NoteCreate creates a new note row. func (c *Client) NoteCreate(data map[string]any) (map[string]any, error) { return c.requestJSON("POST", "/xapi2/data/note", data) } // NoteUpdate patches an existing note row. func (c *Client) NoteUpdate(id string, data map[string]any) (map[string]any, error) { return c.requestJSON("PATCH", "/xapi2/data/note/"+id, data) } // NoteDelete deletes a note row. func (c *Client) NoteDelete(id string) error { _, err := c.requestJSON("DELETE", "/xapi2/data/note/"+id, nil) return err } // PetList lists pet rows. Pass nil opts for defaults. func (c *Client) PetList(opts *ListOpts) (map[string]any, error) { return c.requestList("/xapi2/data/pet", opts) } // PetGet fetches one pet row by id. func (c *Client) PetGet(id string) (map[string]any, error) { return c.requestJSON("GET", "/xapi2/data/pet/"+id, nil) } // PetCreate creates a new pet row. func (c *Client) PetCreate(data map[string]any) (map[string]any, error) { return c.requestJSON("POST", "/xapi2/data/pet", data) } // PetUpdate patches an existing pet row. func (c *Client) PetUpdate(id string, data map[string]any) (map[string]any, error) { return c.requestJSON("PATCH", "/xapi2/data/pet/"+id, data) } // PetDelete deletes a pet row. func (c *Client) PetDelete(id string) error { _, err := c.requestJSON("DELETE", "/xapi2/data/pet/"+id, nil) return err } // RelationshipList lists relationship rows. Pass nil opts for defaults. func (c *Client) RelationshipList(opts *ListOpts) (map[string]any, error) { return c.requestList("/xapi2/data/relationship", opts) } // RelationshipGet fetches one relationship row by id. func (c *Client) RelationshipGet(id string) (map[string]any, error) { return c.requestJSON("GET", "/xapi2/data/relationship/"+id, nil) } // RelationshipCreate creates a new relationship row. func (c *Client) RelationshipCreate(data map[string]any) (map[string]any, error) { return c.requestJSON("POST", "/xapi2/data/relationship", data) } // RelationshipUpdate patches an existing relationship row. func (c *Client) RelationshipUpdate(id string, data map[string]any) (map[string]any, error) { return c.requestJSON("PATCH", "/xapi2/data/relationship/"+id, data) } // RelationshipDelete deletes a relationship row. func (c *Client) RelationshipDelete(id string) error { _, err := c.requestJSON("DELETE", "/xapi2/data/relationship/"+id, nil) return err } // ReminderList lists reminder rows. Pass nil opts for defaults. func (c *Client) ReminderList(opts *ListOpts) (map[string]any, error) { return c.requestList("/xapi2/data/reminder", opts) } // ReminderGet fetches one reminder row by id. func (c *Client) ReminderGet(id string) (map[string]any, error) { return c.requestJSON("GET", "/xapi2/data/reminder/"+id, nil) } // ReminderCreate creates a new reminder row. func (c *Client) ReminderCreate(data map[string]any) (map[string]any, error) { return c.requestJSON("POST", "/xapi2/data/reminder", data) } // ReminderUpdate patches an existing reminder row. func (c *Client) ReminderUpdate(id string, data map[string]any) (map[string]any, error) { return c.requestJSON("PATCH", "/xapi2/data/reminder/"+id, data) } // ReminderDelete deletes a reminder row. func (c *Client) ReminderDelete(id string) error { _, err := c.requestJSON("DELETE", "/xapi2/data/reminder/"+id, nil) return err }