Documentation
¶
Overview ¶
Package pipedrive contains the typed HTTP client for the Pipedrive REST API. The client has no MCP imports; it is reusable independently of the transport.
Index ¶
- Constants
- Variables
- func BaseURL(domain string) string
- func ItemInt64(item map[string]any, key string) int64
- func ItemString(item map[string]any, key string) string
- func WebURL(domain string, kind WebURLKind, id int64) string
- type APIError
- type Activity
- type ActivityAttendee
- type ActivityLocation
- type ActivityParticipant
- type AdditionalData
- type Address
- type Client
- func (c *Client) CreateActivity(ctx context.Context, req CreateActivityRequest) (*Activity, error)
- func (c *Client) CreateDeal(ctx context.Context, req CreateDealRequest) (*Deal, error)
- func (c *Client) CreateNote(ctx context.Context, req CreateNoteRequest) (*Note, error)
- func (c *Client) CreateOrganization(ctx context.Context, req CreateOrganizationRequest) (*Organization, error)
- func (c *Client) CreatePerson(ctx context.Context, req CreatePersonRequest) (*Person, error)
- func (c *Client) DeleteNote(ctx context.Context, id int64) error
- func (c *Client) GetActivity(ctx context.Context, id int64, opts GetActivityOptions) (*Activity, error)
- func (c *Client) GetDeal(ctx context.Context, id int64) (*Deal, error)
- func (c *Client) GetNote(ctx context.Context, id int64) (*Note, error)
- func (c *Client) GetOrganization(ctx context.Context, id int64) (*Organization, error)
- func (c *Client) GetPerson(ctx context.Context, id int64) (*Person, error)
- func (c *Client) ItemSearch(ctx context.Context, opts SearchOptions) ([]SearchHit, string, error)
- func (c *Client) ListActivities(ctx context.Context, opts ListActivitiesOptions) ([]Activity, string, error)
- func (c *Client) ListDealFields(ctx context.Context) ([]Field, error)
- func (c *Client) ListDeals(ctx context.Context, opts ListDealsOptions) ([]Deal, string, error)
- func (c *Client) ListNotes(ctx context.Context, opts ListNotesOptions) ([]Note, *V1Pagination, error)
- func (c *Client) ListOrganizationFields(ctx context.Context) ([]Field, error)
- func (c *Client) ListOrganizations(ctx context.Context, opts ListOrganizationsOptions) ([]Organization, string, error)
- func (c *Client) ListPersonFields(ctx context.Context) ([]Field, error)
- func (c *Client) ListPersons(ctx context.Context, opts ListPersonsOptions) ([]Person, string, error)
- func (c *Client) ListPipelines(ctx context.Context) ([]Pipeline, error)
- func (c *Client) ListStages(ctx context.Context, pipelineID int64) ([]Stage, error)
- func (c *Client) ProbeAuth(ctx context.Context) error
- func (c *Client) ReloadDealFields(ctx context.Context) (int, error)
- func (c *Client) ReloadOrganizationFields(ctx context.Context) (int, error)
- func (c *Client) ReloadPersonFields(ctx context.Context) (int, error)
- func (c *Client) ResolveDealCustomFields(ctx context.Context, raw map[string]any) map[string]any
- func (c *Client) ResolveOrganizationCustomFields(ctx context.Context, raw map[string]any) map[string]any
- func (c *Client) ResolvePersonCustomFields(ctx context.Context, raw map[string]any) map[string]any
- func (c *Client) WarmDealFields(ctx context.Context)
- func (c *Client) WarmOrganizationFields(ctx context.Context)
- func (c *Client) WarmPersonFields(ctx context.Context)
- type ContactPoint
- type CreateActivityRequest
- type CreateDealRequest
- type CreateNoteRequest
- type CreateOrganizationRequest
- type CreatePersonRequest
- type Deal
- type Field
- type FieldCache
- type GetActivityOptions
- type ItemType
- type ListActivitiesOptions
- type ListDealsOptions
- type ListNotesOptions
- type ListOrganizationsOptions
- type ListPersonsOptions
- type Note
- type Options
- type Organization
- type Person
- type Pipeline
- type SearchHit
- type SearchOptions
- type Stage
- type V1Pagination
- type WebURLKind
Constants ¶
const DefaultActivityType = "task"
DefaultActivityType is what Pipedrive's POST /activities falls back to when the body omits `type`. Tracked as a constant so the GoDoc and the LLM-facing tool description don't drift if upstream ever changes the default.
Variables ¶
var ( ErrForbiddenPermission = errors.New("pipedrive: forbidden (permission)") ErrForbiddenBusinessRule = errors.New("pipedrive: forbidden (business rule)") ErrNotFound = errors.New("pipedrive: not found") ErrRateLimited = errors.New("pipedrive: rate limited") ErrServerError = errors.New("pipedrive: server error") ErrValidation = errors.New("pipedrive: validation") )
Sentinel error classes. Tools branch on these via errors.Is.
Functions ¶
func BaseURL ¶
BaseURL returns the v2 base URL for a Pipedrive workspace subdomain. The path suffix is stripped by hostOf in New, so the per-call API version (v2 by default; v1 for the notes carve-out) is composed in exec — see the apiVersion type below.
func ItemInt64 ¶
ItemInt64 reads a numeric field from a SearchHit's Item map, tolerating both the float64 form Go's stdlib JSON decoder produces for arbitrary numbers and any direct int64 / int forms a future caller might pass through. Returns 0 for missing or non-numeric values; callers that need to distinguish "missing" from "zero" should check key existence themselves.
func ItemString ¶
ItemString reads a string field from a SearchHit's Item map. Returns "" for missing or non-string values.
Types ¶
type APIError ¶
type APIError struct {
Class error // one of the sentinels
Status int // HTTP status from Pipedrive
Message string // upstream `error` field, or a short summary
Endpoint string // request path, useful for the 403 heuristic
}
APIError carries the wire-level detail. Wrap one of the sentinels above so callers can branch by class with errors.Is and still inspect the detail with errors.As.
type Activity ¶
type Activity struct {
ID int64 `json:"id"`
Subject string `json:"subject"`
Type string `json:"type"` // activity-type key (e.g. "call", "email", "meeting", "task"); free-form per workspace
OwnerID int64 `json:"owner_id"`
DealID int64 `json:"deal_id,omitempty"`
PersonID int64 `json:"person_id,omitempty"`
OrgID int64 `json:"org_id,omitempty"`
LeadID string `json:"lead_id,omitempty"`
ProjectID int64 `json:"project_id,omitempty"`
DueDate string `json:"due_date,omitempty"`
DueTime string `json:"due_time,omitempty"`
Duration string `json:"duration,omitempty"`
Busy bool `json:"busy"`
Done bool `json:"done"`
MarkedAsDoneTime string `json:"marked_as_done_time,omitempty"`
Location *ActivityLocation `json:"location,omitempty"`
Participants []ActivityParticipant `json:"participants,omitempty"`
Attendees []ActivityAttendee `json:"attendees,omitempty"`
ConferenceMeetingClient string `json:"conference_meeting_client,omitempty"`
ConferenceMeetingURL string `json:"conference_meeting_url,omitempty"`
ConferenceMeetingID string `json:"conference_meeting_id,omitempty"`
PublicDescription string `json:"public_description,omitempty"`
Note string `json:"note,omitempty"`
AddTime string `json:"add_time"`
UpdateTime string `json:"update_time"`
}
Activity is a Pipedrive activity record (subset). Pipedrive v2 dropped the v1 `_flag` suffixes — `busy_flag` → `busy`, `done_flag` → `done`. Activities do NOT carry a `custom_fields` block on v2; the v2 schema omits it entirely. Times use Pipedrive's date/time formats (DueDate is YYYY-MM-DD, DueTime / Duration are HH:MM, AddTime / UpdateTime are the v2 RFC3339-with-space). Strings are surfaced raw to the LLM so it can pattern-match without timezone surprises.
type ActivityAttendee ¶
type ActivityAttendee struct {
Email string `json:"email,omitempty"`
Name string `json:"name,omitempty"`
Status string `json:"status,omitempty"` // accepted | declined | tentative | needsAction (Google-style)
IsOrganizer bool `json:"is_organizer,omitempty"`
PersonID int64 `json:"person_id,omitempty"`
UserID int64 `json:"user_id,omitempty"`
}
ActivityAttendee is one row in an Activity's attendees array. Attendees are calendar-style invitees (email, name, RSVP status). PersonID is non-zero when Pipedrive matched the email to an existing person; UserID is non-zero when it matched a Pipedrive user instead. Both can be zero for an external attendee.
type ActivityLocation ¶
type ActivityLocation struct {
Value string `json:"value,omitempty"`
Country string `json:"country,omitempty"`
Locality string `json:"locality,omitempty"`
PostalCode string `json:"postal_code,omitempty"`
}
ActivityLocation is Pipedrive v2's structured location for an activity (the place a meeting is held, etc.). Street-level components from the upstream schema (route, street_number, sublocality, admin_area_level_*) are intentionally omitted — humans care about "city/country, postal" which is what the LLM-facing output surfaces.
type ActivityParticipant ¶
ActivityParticipant is one row in an Activity's participants array. Pipedrive marks one participant as primary (the contact the activity is principally tied to); the rest are co-participants. Distinct from Attendees, which are calendar invitees.
type AdditionalData ¶
type AdditionalData struct {
NextCursor string `json:"next_cursor,omitempty"`
V1Pagination *V1Pagination `json:"pagination,omitempty"`
}
AdditionalData is the paging envelope returned alongside `data` on list endpoints.
V2 uses NextCursor — opaque token; empty string means "last page". V1 (notes carve-out only) uses V1Pagination, an offset-style {start, limit, next_start} block. The tool layer encodes v1's next_start as an opaque cursor string so the LLM-facing surface stays uniform across resources.
type Address ¶
type Address struct {
Value string `json:"value,omitempty"`
Country string `json:"country,omitempty"`
Locality string `json:"locality,omitempty"`
PostalCode string `json:"postal_code,omitempty"`
}
Address is Pipedrive v2's structured address record. Returned by /organizations/{id} as the org's primary address (Pipedrive's data model puts the org-level address there; persons inherit nothing at the typed-struct level). Address-typed *custom* fields on any resource decode into raw map entries via custom_fields, not into this type. /api/v2/itemSearch returns a different, flat string-only shape — the search-side code does not decode into this type. Street-level components (route, street_number, sublocality, admin areas) are not surfaced today; add them back if a tool starts needing them.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a thin wrapper around net/http for the Pipedrive REST API. Safe for concurrent use across goroutines. There is one instance per process; the MCP server hands a pointer to every tool's Register() function.
The per-resource field caches (dealFields, …) live on the Client because their lifetime matches the process and they reuse the Client's transport. Callers go through the typed accessor methods (ResolveDealCustomFields, WarmDealFields, ReloadDealFields) rather than the unexported field.
func New ¶
New constructs a Client with sensible defaults for retry policy.
The HTTP client is configured to refuse redirects. Pipedrive's v2 JSON API does not redirect during normal operation, and Go's default redirect-follower forwards custom request headers (including the `x-api-token` we attach below) to the redirect target verbatim. Returning ErrUseLastResponse short-circuits the follow so a stray 3xx surfaces as an "unexpected status" error rather than silently leaking the token to whatever host the Location header named.
func (*Client) CreateActivity ¶ added in v0.2.0
CreateActivity posts a new activity via /api/v2/activities. Returns the created activity as Pipedrive echoes it (full record with id, structured Location parsed server-side, and resolved type).
func (*Client) CreateDeal ¶ added in v0.2.0
CreateDeal posts a new deal via /api/v2/deals. Returns the created deal as Pipedrive echoes it (full record with id, defaults resolved, and custom_fields nested as usual).
func (*Client) CreateNote ¶
CreateNote posts a new note via /api/v1/notes. Returns the created note (Pipedrive echoes the full record on success).
func (*Client) CreateOrganization ¶ added in v0.2.0
func (c *Client) CreateOrganization(ctx context.Context, req CreateOrganizationRequest) (*Organization, error)
CreateOrganization posts a new organization via /api/v2/organizations. Returns the created org as Pipedrive echoes it (full record with id, structured Address parsed server-side, and custom_fields).
func (*Client) CreatePerson ¶ added in v0.2.0
CreatePerson posts a new person via /api/v2/persons. Returns the created person as Pipedrive echoes it (full record with id and custom_fields nested as usual).
func (*Client) DeleteNote ¶
DeleteNote removes a note via DELETE /api/v1/notes/{id}. v1 deletes are SOFT — the record persists with active_flag=false and is still readable via GetNote, but list_notes filters it out by default.
func (*Client) GetActivity ¶
func (c *Client) GetActivity(ctx context.Context, id int64, opts GetActivityOptions) (*Activity, error)
GetActivity fetches a single activity by ID.
func (*Client) GetDeal ¶
GetDeal fetches a single deal by ID. custom_fields are nested under the deal's `custom_fields` object per Pipedrive v2 — caller resolves hash keys to names via the per-Client FieldCache.
func (*Client) GetNote ¶
GetNote fetches a single note by ID via /api/v1/notes/{id}. v2 has no equivalent endpoint; see types.go for the carve-out rationale.
func (*Client) GetOrganization ¶
GetOrganization fetches a single organization by ID. custom_fields are nested under the org's `custom_fields` object per Pipedrive v2 — caller resolves hash keys to names via the per-Client FieldCache.
func (*Client) GetPerson ¶
GetPerson fetches a single person by ID. custom_fields are nested under the person's `custom_fields` object per Pipedrive v2 — caller resolves hash keys to names via the per-Client FieldCache.
func (*Client) ItemSearch ¶
ItemSearch issues a free-text search across the requested item types. NextCursor is the authoritative "more results upstream" signal; an empty string means the page is the last. Tool callers must surface that to the LLM (see internal/tools/search.go).
func (*Client) ListActivities ¶
func (c *Client) ListActivities(ctx context.Context, opts ListActivitiesOptions) ([]Activity, string, error)
ListActivities issues a /activities list with the given filters. Returns the page plus the next cursor (empty string = end of results). Cursor pagination is opaque; callers pass whatever NextCursor was returned on the prior page.
func (*Client) ListDealFields ¶
ListDealFields returns the field metadata for deals. Used by the auth probe (limit=1 form is in probe.go) and by the per-Client deal field cache.
func (*Client) ListDeals ¶
ListDeals issues a /deals list with the given filters. Returns the page plus the next cursor (empty string = end of results). Cursor pagination is opaque; callers pass whatever NextCursor was returned on the prior page.
func (*Client) ListNotes ¶
func (c *Client) ListNotes(ctx context.Context, opts ListNotesOptions) ([]Note, *V1Pagination, error)
ListNotes issues a /api/v1/notes list with the given filters. Returns the page plus the v1 pagination block (or nil if the response carries no pagination, e.g. on a 0-row response).
func (*Client) ListOrganizationFields ¶
ListOrganizationFields returns the field metadata for organizations.
func (*Client) ListOrganizations ¶
func (c *Client) ListOrganizations(ctx context.Context, opts ListOrganizationsOptions) ([]Organization, string, error)
ListOrganizations issues a /organizations list with the given filters. Returns the page plus the next cursor (empty string = end).
func (*Client) ListPersonFields ¶
ListPersonFields returns the field metadata for persons. Used by the per-Client person field cache.
func (*Client) ListPersons ¶
func (c *Client) ListPersons(ctx context.Context, opts ListPersonsOptions) ([]Person, string, error)
ListPersons issues a /persons list with the given filters. Returns the page plus the next cursor (empty string = end of results).
func (*Client) ListPipelines ¶
ListPipelines returns every pipeline the API token's user can see. Pipedrive workspaces typically have a small handful (≤ 20), so no pagination wrapper is needed at this scale; if a workspace ever exceeds the response cap, we'll add cursor support here.
func (*Client) ListStages ¶
ListStages returns the stages, optionally filtered to one pipeline. Pass pipelineID = 0 to return stages across every pipeline.
func (*Client) ProbeAuth ¶
ProbeAuth confirms the API token is valid for the workspace by hitting GET /api/v2/dealFields?limit=1. That endpoint is chosen because it is confirmed to exist on Pipedrive API v2 (/api/v2/users does not), every workspace has at least one deal field, and the payload is tiny.
func (*Client) ReloadDealFields ¶
ReloadDealFields clears and re-fetches the deal-field cache, returning the count of fields now cached. Used by the refresh_field_cache tool to pick up custom-field renames or additions without restarting the server. Reload is followed by an eager Load so the next get_deal/list_deals call sees fresh data without paying the round-trip itself.
func (*Client) ReloadOrganizationFields ¶
ReloadOrganizationFields clears and re-fetches the organization-field cache, returning the count of fields now cached. See ReloadDealFields for the rationale.
func (*Client) ReloadPersonFields ¶
ReloadPersonFields clears and re-fetches the person-field cache, returning the count of fields now cached. See ReloadDealFields for the rationale.
func (*Client) ResolveDealCustomFields ¶
ResolveDealCustomFields returns a copy of raw with hash keys replaced by their human-readable names. Sole entry point tool packages need; the underlying cache is unexported.
func (*Client) ResolveOrganizationCustomFields ¶
func (c *Client) ResolveOrganizationCustomFields(ctx context.Context, raw map[string]any) map[string]any
ResolveOrganizationCustomFields delegates to the per-Client org field cache.
func (*Client) ResolvePersonCustomFields ¶
ResolvePersonCustomFields returns a copy of raw with hash keys replaced by their human-readable names. Sole entry point tool packages need; the underlying cache is unexported.
func (*Client) WarmDealFields ¶
WarmDealFields eagerly triggers the deal-field cache load so the first user-facing get_deal/list_deals call doesn't pay the /dealFields round-trip on the critical path. Errors are silently swallowed: a failed warm-up just means the first real call pays the latency, exactly as the lazy path would.
func (*Client) WarmOrganizationFields ¶
WarmOrganizationFields eagerly triggers the cache load.
func (*Client) WarmPersonFields ¶
WarmPersonFields eagerly triggers the cache load.
type ContactPoint ¶
type ContactPoint struct {
Value string `json:"value"`
Primary bool `json:"primary"`
Label string `json:"label,omitempty"`
}
ContactPoint is one row in a Person's emails / phones array. Pipedrive returns these as arrays of {value, primary, label} objects rather than flat strings so a single record can carry multiple addresses.
type CreateActivityRequest ¶ added in v0.2.0
type CreateActivityRequest struct {
Subject string `json:"subject"`
Type string `json:"type,omitempty"`
DueDate string `json:"due_date,omitempty"` // YYYY-MM-DD
DueTime string `json:"due_time,omitempty"` // HH:MM
Duration string `json:"duration,omitempty"` // HH:MM
DealID int64 `json:"deal_id,omitempty"`
PersonID int64 `json:"person_id,omitempty"`
OrgID int64 `json:"org_id,omitempty"`
LeadID string `json:"lead_id,omitempty"` // UUID
OwnerID int64 `json:"owner_id,omitempty"`
Note string `json:"note,omitempty"` // private; HTML allowed
PublicDescription string `json:"public_description,omitempty"` // shared with attendees
Location string `json:"location,omitempty"` // single-line; server-parsed
Participants []ActivityParticipant `json:"participants,omitempty"`
Done bool `json:"done,omitempty"`
Busy bool `json:"busy,omitempty"`
}
CreateActivityRequest is the JSON body for POST /api/v2/activities. v2 requires `subject`. Type defaults to DefaultActivityType upstream when omitted; the tool layer encourages the LLM to set it explicitly. Validating Type against the workspace's activityTypes enum is intentionally deferred: it would need a cache + probe, and Pipedrive's 400 on invalid type already surfaces cleanly as [validation] via the standard error mapping.
Location is a single-line string on input — Pipedrive parses it server-side into the structured ActivityLocation response (same pattern as POST /organizations).
Done and Busy use Go's zero-value-is-false default; sending false is equivalent to omitting (json:omitempty). For the rare "create already-marked-done" path the LLM sets Done=true.
type CreateDealRequest ¶ added in v0.2.0
type CreateDealRequest struct {
Title string `json:"title"`
Value float64 `json:"value,omitempty"`
Currency string `json:"currency,omitempty"`
PipelineID int64 `json:"pipeline_id,omitempty"`
StageID int64 `json:"stage_id,omitempty"`
OwnerID int64 `json:"owner_id,omitempty"`
PersonID int64 `json:"person_id,omitempty"`
OrgID int64 `json:"org_id,omitempty"`
ExpectedCloseDate string `json:"expected_close_date,omitempty"` // YYYY-MM-DD
Probability *int `json:"probability,omitempty"` // 0-100; nil = use stage default
}
CreateDealRequest is the JSON body for POST /api/v2/deals. v2 requires `title` only; everything else has Pipedrive-side defaults (currency = workspace default, value = 0, owner_id = the API token's user, status = open, stage_id = first stage of the default pipeline, ...). The tool layer enforces title non-empty client-side so a typo surfaces as [validation] instead of an upstream 400.
Custom fields are intentionally omitted from this v0 — writing them needs the inverse name→hash resolver on FieldCache, which is a separate slice. Callers wanting to set custom fields today can edit the deal in the Pipedrive UI after creation.
type CreateNoteRequest ¶
type CreateNoteRequest struct {
Content string `json:"content"`
DealID int64 `json:"deal_id,omitempty"`
PersonID int64 `json:"person_id,omitempty"`
OrgID int64 `json:"org_id,omitempty"`
LeadID string `json:"lead_id,omitempty"`
ProjectID int64 `json:"project_id,omitempty"`
}
CreateNoteRequest is the JSON body for POST /api/v1/notes. Pipedrive requires Content + at least one anchor (DealID / PersonID / OrgID / LeadID); the tool layer enforces this client-side so a typo surfaces as [validation] instead of an upstream 400.
type CreateOrganizationRequest ¶ added in v0.2.0
type CreateOrganizationRequest struct {
Name string `json:"name"`
OwnerID int64 `json:"owner_id,omitempty"`
Address string `json:"address,omitempty"` // single-line; server-parsed
}
CreateOrganizationRequest is the JSON body for POST /api/v2/organizations. v2 requires `name`. Address is a single-line string on input — Pipedrive parses it into the structured response shape (Address.Country / Locality / PostalCode) server-side.
type CreatePersonRequest ¶ added in v0.2.0
type CreatePersonRequest struct {
Name string `json:"name"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Emails []ContactPoint `json:"emails,omitempty"`
Phones []ContactPoint `json:"phones,omitempty"`
OrgID int64 `json:"org_id,omitempty"`
OwnerID int64 `json:"owner_id,omitempty"`
}
CreatePersonRequest is the JSON body for POST /api/v2/persons. v2 requires `name`; first_name+last_name are an alternative the API can derive `name` from but this struct surfaces both for callers that already have the parts.
Emails and phones are each a list of {value, primary, label}. Multiple `primary: true` entries are silently coerced by Pipedrive — last one wins.
type Deal ¶
type Deal struct {
ID int64 `json:"id"`
Title string `json:"title"`
Value float64 `json:"value"`
Currency string `json:"currency"`
Status string `json:"status"` // open | won | lost | deleted
StageID int64 `json:"stage_id"`
PipelineID int64 `json:"pipeline_id"`
OwnerID int64 `json:"owner_id"`
PersonID int64 `json:"person_id"`
OrgID int64 `json:"org_id"`
ExpectedCloseDate string `json:"expected_close_date"`
WonTime string `json:"won_time,omitempty"`
LostTime string `json:"lost_time,omitempty"`
LostReason string `json:"lost_reason,omitempty"`
AddTime string `json:"add_time"`
UpdateTime string `json:"update_time"`
Probability *int `json:"probability,omitempty"`
CustomFields map[string]any `json:"custom_fields,omitempty"`
}
Deal is a Pipedrive deal record (subset). Custom fields are nested under custom_fields per the v2 API; the LLM-facing rendering in internal/tools/ resolves the 40-char hash keys into names via the per-Client deal field cache.
Times use Pipedrive's RFC3339-ish format with space (e.g. "2026-04-26 10:00:00"). They are surfaced to the LLM as raw strings so it can pattern-match without timezone surprises; callers that need time.Time should parse explicitly.
type Field ¶
type Field struct {
Key string `json:"field_code"` // 40-char hash for custom fields; plain identifier for built-ins ("id", "title", ...)
Name string `json:"field_name"` // human-readable label
}
Field is a Pipedrive field-metadata record (the subset the field caches need: hash-key → human-name resolution). Returned by /dealFields, /personFields, /organizationFields. v2 renamed `key` → `field_code` and `name` → `field_name` from v1; this struct is v2-shaped.
type FieldCache ¶
type FieldCache struct {
// contains filtered or unexported fields
}
FieldCache lazy-loads Pipedrive field metadata for one resource type and resolves the 40-char custom-field hash keys back to their human-readable names. Loaded behind a per-entry sync.Once: concurrent first-callers see one fetch, all subsequent callers observe the same error if the fetch failed. Reload() swaps the entry so the next access refetches, while in-flight callers safely complete on their own entry — never on a Reload-corrupted Once.
func NewFieldCache ¶
func NewFieldCache(fetch func(context.Context) ([]Field, error)) *FieldCache
NewFieldCache wraps fetch; fetch is invoked at most once per Reload cycle and must return a stable snapshot of the resource's fields.
func (*FieldCache) Count ¶
func (fc *FieldCache) Count() int
Count returns the number of fields currently cached. Returns 0 if the cache hasn't been loaded or the load failed. Used by the refresh_field_cache tool to surface a per-resource sanity check the LLM (and operator) can read after triggering a reload.
Count never triggers once.Do — calling once.Do(no-op) here would race with a concurrent first Load and silently seal the once, causing the real fetch to be skipped. Instead it reads the loaded flag (set inside once.Do *after* byKey is written), which provides the happens-before edge to byKey for free.
func (*FieldCache) Load ¶
func (fc *FieldCache) Load(ctx context.Context) error
Load triggers the underlying fetch on first call. Every subsequent caller sees the same error so the LLM-facing tool can decide whether to fall back to raw output.
func (*FieldCache) Reload ¶
func (fc *FieldCache) Reload()
Reload clears the cache so the next access refetches. Concurrent callers in flight at the moment of Reload finish their load on the outgoing entry (its once.Do is unaffected by this swap), and their already-acquired entry pointer continues to surface the old data. The next batch of callers sees a fresh entry and a fresh fetch.
func (*FieldCache) Resolve ¶
Resolve returns a copy of raw with hash keys replaced by names. Unknown keys pass through verbatim so the LLM never silently loses data when the cache lags behind a freshly-created field; on cache load failure, raw is returned untouched.
Reads work off the entry snapshot rather than fc.cur so a Reload mid-call doesn't risk a torn read. Each entry's byKey is set under once.Do, which provides the happens-before for safe concurrent reads.
type GetActivityOptions ¶
type GetActivityOptions struct {
IncludeAttendees bool
}
GetActivityOptions controls the per-request shape of GetActivity. IncludeAttendees toggles `include_fields=attendees`. Future opt-ins (additional `include_fields` values Pipedrive may add) extend this struct without breaking the call signature.
type ItemType ¶
type ItemType string
ItemType is the closed set of item types Pipedrive's /itemSearch endpoint accepts. The constants are reused by the tools package for input validation so the enum has a single source of truth.
type ListActivitiesOptions ¶
type ListActivitiesOptions struct {
OwnerID int64
DealID int64
LeadID string
PersonID int64
OrgID int64
Done *bool
UpdatedSince string // RFC3339, e.g. 2026-01-01T00:00:00Z
UpdatedUntil string // RFC3339
SortBy string // id | update_time | add_time | due_date
SortDirection string // asc | desc
IncludeAttendees bool
Limit int
Cursor string
}
ListActivitiesOptions filters a /activities list call. Zero values mean "no filter on this dimension".
v2 dropped the v1 `type` and `due_date` query params (filtering by activity-type or absolute due-date is not supported on the list endpoint — callers can sort by due_date instead). v2 list params are drawn from Pipedrive's official spec: filter_id, owner_id, deal_id, lead_id, person_id, org_id, done, updated_since, updated_until, sort_by, sort_direction, include_fields, limit, cursor.
Done is a tri-state: nil = no filter (the default), &true = only completed, &false = only open. The tool layer maps a human-friendly `status: open|done|all` enum to this field.
IncludeAttendees toggles `include_fields=attendees`; off by default, set to true to populate the Attendees slice on each activity. Pipedrive returns attendees only when explicitly requested.
type ListDealsOptions ¶
type ListDealsOptions struct {
Status string // open | won | lost | deleted
PipelineID int64
StageID int64
OwnerID int64
PersonID int64
OrgID int64
UpdatedSince string // RFC3339
UpdatedUntil string // RFC3339
SortBy string // id | update_time | add_time
SortDirection string // asc | desc
Limit int
Cursor string // opaque pagination token from a previous response
}
ListDealsOptions filters a /deals list call. Zero values mean "no filter on this dimension". Limit is clamped to [1, 500] by Pipedrive; the tool layer applies its own (smaller) cap before calling here.
Pipedrive v2 returns custom_fields nested in every deal record by default — there is no opt-in query parameter, and supplying `include_fields=custom_fields` is rejected with a 400.
type ListNotesOptions ¶
type ListNotesOptions struct {
UserID int64
DealID int64
PersonID int64
OrgID int64
LeadID string // UUID
ProjectID int64
StartDate string // YYYY-MM-DD
EndDate string // YYYY-MM-DD
UpdatedSince string // RFC3339
Sort string // "<field> asc|desc, ..." — v1 accepts either case; tools/effectiveSort emits lowercase
Start int
Limit int
}
ListNotesOptions filters a /api/v1/notes list call. v1 uses offset-style pagination (Start + Limit) — the tool layer encodes the int next_start as an opaque cursor string so the LLM-facing surface stays uniform with the v2 resources.
v1 supports many more filter dimensions than v2 list endpoints (start_date / end_date / pinned_to_X_flag) — wire only what the LLM-facing tools actually expose to keep the surface narrow.
type ListOrganizationsOptions ¶
type ListOrganizationsOptions struct {
OwnerID int64
UpdatedSince string // RFC3339
UpdatedUntil string // RFC3339
SortBy string // id | update_time | add_time
SortDirection string // asc | desc
Limit int
Cursor string
}
ListOrganizationsOptions filters a /organizations list call. v2's /organizations does not accept owner-of-deals or person-count filters; the available list-time dimensions are owner + update window. Zero values mean "no filter on this dimension".
type ListPersonsOptions ¶
type ListPersonsOptions struct {
OwnerID int64
OrgID int64
UpdatedSince string // RFC3339
UpdatedUntil string // RFC3339
SortBy string // id | update_time | add_time
SortDirection string // asc | desc
Limit int
Cursor string
}
ListPersonsOptions filters a /persons list call. Zero values mean "no filter on this dimension". v2 supports more params (filter_id, ids, deal_id, include_fields, custom_fields) — wire only what the LLM-facing tools actually expose to keep the surface narrow.
type Note ¶
type Note struct {
ID int64 `json:"id"`
Content string `json:"content"`
UserID int64 `json:"user_id"`
LastUpdateUserID *int64 `json:"last_update_user_id,omitempty"`
DealID *int64 `json:"deal_id,omitempty"`
PersonID *int64 `json:"person_id,omitempty"`
OrgID *int64 `json:"org_id,omitempty"`
LeadID string `json:"lead_id,omitempty"`
ProjectID *int64 `json:"project_id,omitempty"`
AddTime string `json:"add_time"`
UpdateTime string `json:"update_time"`
ActiveFlag bool `json:"active_flag"`
PinnedToDealFlag bool `json:"pinned_to_deal_flag,omitempty"`
PinnedToPersonFlag bool `json:"pinned_to_person_flag,omitempty"`
PinnedToOrganizationFlag bool `json:"pinned_to_organization_flag,omitempty"`
PinnedToLeadFlag bool `json:"pinned_to_lead_flag,omitempty"`
PinnedToProjectFlag bool `json:"pinned_to_project_flag,omitempty"`
}
Note is a Pipedrive v1 note record. v2 has no /notes endpoint (Pipedrive officially recommends staying on v1 for notes per developer-community thread, 2025-05). The carve-out is documented in CLAUDE.md hard rule #1.
AddTime / UpdateTime use v1's `YYYY-MM-DD HH:MM:SS` format (UTC, space-separated — distinct from v2's RFC3339).
Foreign keys are pointer-typed because v1 returns null when the note isn't anchored to that entity. LeadID is a UUID string; the rest are int IDs. The pinned-to-* fields come back as actual JSON booleans on v1 (verified live, 2026-04 — earlier external docs claimed 0/1 ints; the docs were wrong).
type Options ¶
Options configures a new Client. BaseURL is the v2 base (e.g. "https://acme.pipedrive.com/api/v2"); only the scheme+host portion is retained — the per-request API path is composed in `do`.
type Organization ¶
type Organization struct {
ID int64 `json:"id"`
Name string `json:"name"`
Address *Address `json:"address,omitempty"`
OwnerID int64 `json:"owner_id"`
PeopleCount int `json:"people_count,omitempty"`
AddTime string `json:"add_time"`
UpdateTime string `json:"update_time"`
CustomFields map[string]any `json:"custom_fields,omitempty"`
}
Organization is a Pipedrive organization record (subset). Custom fields nest under custom_fields per the v2 API.
type Person ¶
type Person struct {
ID int64 `json:"id"`
Name string `json:"name"`
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
Emails []ContactPoint `json:"emails,omitempty"`
Phones []ContactPoint `json:"phones,omitempty"`
OrgID int64 `json:"org_id"`
OwnerID int64 `json:"owner_id"`
AddTime string `json:"add_time"`
UpdateTime string `json:"update_time"`
CustomFields map[string]any `json:"custom_fields,omitempty"`
}
Person is a Pipedrive person record (subset). Custom fields are nested under custom_fields per the v2 API; the LLM-facing rendering in internal/tools/ resolves the 40-char hash keys into names via the per-Client person field cache.
type Pipeline ¶
type Pipeline struct {
ID int64 `json:"id"`
Name string `json:"name"`
OrderNr int `json:"order_nr"`
Active bool `json:"active"`
}
Pipeline is a Pipedrive pipeline (a deal flow grouping). Subset of the /api/v2/pipelines response that we surface to LLM clients.
type SearchHit ¶
SearchHit is one record returned by /api/v2/itemSearch. The pipedrive layer keeps the per-type record as a raw map so the tools layer can decide how to render it for the LLM (deal `title` vs person `name`, strip-list of internal fields, etc.) without leaking presentation concerns into the HTTP layer.
type SearchOptions ¶
type SearchOptions struct {
Term string
ItemTypes []string
ExactMatch bool
Limit int
Cursor string
}
SearchOptions filters an /itemSearch call. Term is required and must be at least 2 characters (1 if ExactMatch). Empty ItemTypes means "search all of deal | person | organization | product | file | lead". Limit is clamped by the tool layer.
type Stage ¶
type Stage struct {
ID int64 `json:"id"`
Name string `json:"name"`
OrderNr int `json:"order_nr"`
Active bool `json:"active_flag"`
PipelineID int64 `json:"pipeline_id"`
DealProbability int `json:"deal_probability"`
}
Stage is a Pipedrive stage within a pipeline. Subset of /api/v2/stages.
Note: Active maps to the upstream `active_flag` field, while Pipeline uses `active`. This is Pipedrive's API, not a copy-paste error — confirmed against /api/v2/stages and /api/v2/pipelines responses.
type V1Pagination ¶
type V1Pagination struct {
Start int `json:"start"`
Limit int `json:"limit"`
MoreItemsInCollection bool `json:"more_items_in_collection"`
NextStart int `json:"next_start,omitempty"`
}
V1Pagination is Pipedrive v1's offset-style paging envelope. MoreItemsInCollection signals whether NextStart is meaningful; when false, the caller is on the last page.
type WebURLKind ¶
type WebURLKind string
WebURLKind identifies which Pipedrive web-UI route to build a URL for.
const ( WebURLPipeline WebURLKind = "pipeline" WebURLDeal WebURLKind = "deal" WebURLPerson WebURLKind = "person" WebURLOrganization WebURLKind = "organization" WebURLActivity WebURLKind = "activity" )
WebURL kinds covering the resource types the LLM-facing tools surface.