custfind

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Nov 5, 2026 License: MIT Imports: 19 Imported by: 0

README

custfind-go

Official Go client for the Custfind API: start lead searches, read scored leads, write and send emails, move deals and receive webhooks. Standard library only; Go 1.23+.

go get github.com/parevo/custfind-go
import custfind "github.com/parevo/custfind-go"

cf, err := custfind.NewClient() // reads CUSTFIND_API_KEY; or custfind.WithAPIKey(...)

created, err := cf.Searches.Create(ctx, custfind.CreateSearchParams{
	Sector:             "Dental clinic",
	Location:           "Kadıköy, Istanbul",
	Limit:              25,        // caps the credits this search reserves
	ExternalCustomerID: "acme-42", // optional: bill your own customer
})

_, err = cf.Searches.WaitForCompletion(ctx, created.JobID, custfind.WaitOptions{})
for lead, err := range cf.Searches.AllLeads(ctx, created.JobID, custfind.LeadListParams{MinScore: custfind.Ptr(70)}) {
	if err != nil {
		return err
	}
	fmt.Println(lead.Name, lead.Email, *lead.Score)
}

Safe retries

Every write is sent with an Idempotency-Key that stays the same across retries. Network errors, 429 (respecting Retry-After), 5xx and 409 idempotency_in_progress are retried up to 3 times (WithMaxRetries); a timed-out search is never started twice. Pass your own key to make a retry across process restarts safe too:

cf.Searches.Create(ctx, params, custfind.WithIdempotencyKey("search-"+orderID))

Every call takes a context.Context; cancelling it stops the call and its retries.

Errors

Failed calls return *custfind.Error with StatusCode and the API's Code:

var apiErr *custfind.Error
if errors.As(err, &apiErr) && apiErr.Code == custfind.CodeCustomerLimitReached { … }
if custfind.IsPaymentRequired(err) { … } // no credits, customer cap, or plan without API
if custfind.IsNotFound(err) { … }

Webhooks

http.HandleFunc("/webhooks/custfind", func(w http.ResponseWriter, r *http.Request) {
	payload, _ := io.ReadAll(r.Body)
	event, err := custfind.ConstructEvent(payload, r.Header.Get(custfind.SignatureHeader), os.Getenv("CUSTFIND_WEBHOOK_SECRET"))
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	switch event.Type {
	case custfind.EventSearchCompleted:
		var d custfind.SearchEventData
		_ = event.Decode(&d)
	case custfind.EventLeadCreated:
		var d custfind.LeadCreatedData
		_ = event.Decode(&d)
	}
	w.WriteHeader(http.StatusOK)
})

Deliveries can repeat; use event.ID to handle each event once.

Resources

Searches, Leads, Outreach, Pipelines, Deals, Webhooks, Customers, SenderAccounts, plus Me, Balance and Usage. See the API reference.

Contributing

This repository is a read-only mirror of sdks/go in Custfind's main repository, updated on every release. Issues are welcome here; pull requests are read and ported by hand, then closed, since the next release overwrites this repository.

Developing

This module is outside the repo's go.work, so it builds on its own:

GOWORK=off go test ./...

Documentation

Overview

Package custfind is the official Go client for the Custfind API: start lead searches, read scored leads, write and send emails, move deals and receive webhooks.

cf, err := custfind.NewClient() // reads CUSTFIND_API_KEY
created, err := cf.Searches.Create(ctx, custfind.CreateSearchParams{
	Sector: "Dental clinic", Location: "Kadıköy, Istanbul", Limit: 25,
})

Every write is sent with an Idempotency-Key that stays the same across its retries, so a retried search never starts twice or reserves credits twice.

Example
package main

import (
	"context"
	"fmt"
	"log"

	custfind "github.com/parevo/custfind-go"
)

func main() {
	ctx := context.Background()
	cf, err := custfind.NewClient() // reads CUSTFIND_API_KEY
	if err != nil {
		log.Fatal(err)
	}
	created, err := cf.Searches.Create(ctx, custfind.CreateSearchParams{
		Sector:             "Dental clinic",
		Location:           "Kadıköy, Istanbul",
		Limit:              25,        // caps the credits this search reserves
		ExternalCustomerID: "acme-42", // optional: bill your own customer
	})
	if err != nil {
		log.Fatal(err)
	}
	if _, err := cf.Searches.WaitForCompletion(ctx, created.JobID, custfind.WaitOptions{}); err != nil {
		log.Fatal(err)
	}
	for lead, err := range cf.Searches.AllLeads(ctx, created.JobID, custfind.LeadListParams{}) {
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(lead.Name, lead.Email)
	}
}

Index

Examples

Constants

View Source
const (
	CodeMissingAPIKey          = "missing_api_key"
	CodeInvalidAPIKey          = "invalid_api_key"
	CodeAPIRequiresPaidPlan    = "api_requires_paid_plan"
	CodeInsufficientScope      = "insufficient_scope"
	CodeCustomerLimitReached   = "customer_limit_reached"
	CodeRateLimited            = "rate_limited"
	CodeIdempotencyKeyReused   = "idempotency_key_reused"
	CodeIdempotencyInProgress  = "idempotency_in_progress"
	CodeInvalidIdempotencyKey  = "invalid_idempotency_key"
	CodeAIUnavailable          = "ai_unavailable"
	CodeResponseTooLargeReplay = "response_too_large_to_replay"
)

Error codes the API returns in {"error": "<code>"}.

View Source
const (
	ToneProfessional = "professional"
	ToneShort        = "short"
	ToneCasual       = "casual"
)

Rewrite tones.

View Source
const (
	EventSearchCompleted  = "search.completed"
	EventSearchFailed     = "search.failed"
	EventSearchCancelled  = "search.cancelled"
	EventOutreachSent     = "outreach.sent"
	EventOutreachReplied  = "outreach.replied"
	EventDealStageChanged = "deal.stage_changed"
	EventLeadCreated      = "lead.created"
	// EventWebhookTest is sent only by Webhooks.SendTest.
	EventWebhookTest = "webhook.test"
)

Webhook event types.

View Source
const (
	DefaultBaseURL = "https://api.custfind.com"
)
View Source
const DefaultWebhookTolerance = 5 * time.Minute

DefaultWebhookTolerance is how old a signed timestamp ConstructEvent accepts.

View Source
const SignatureHeader = "Custfind-Signature"

SignatureHeader is the header every webhook delivery is signed in: "t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">".

View Source
const Version = "1.0.0"

Version is the SDK version, sent in the User-Agent.

Variables

View Source
var (
	ErrWebhookMalformedSignature = errors.New("custfind: malformed Custfind-Signature header")
	ErrWebhookTimestamp          = errors.New("custfind: webhook timestamp outside tolerance")
	ErrWebhookSignatureMismatch  = errors.New("custfind: webhook signature mismatch")
)

Webhook verification errors.

Functions

func HasCode

func HasCode(err error, code string) bool

HasCode reports whether err is an API error with the given code.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err is a 404 from the API.

func IsPaymentRequired

func IsPaymentRequired(err error) bool

IsPaymentRequired reports whether err is a 402: no credits left, a customer's monthly cap reached, or a plan without API access.

func Ptr

func Ptr[T any](v T) *T

Ptr returns a pointer to v, for optional fields: custfind.Ptr(5000).

func VerifyWebhookSignature

func VerifyWebhookSignature(payload []byte, header, secret string, tolerance time.Duration) error

VerifyWebhookSignature checks a Custfind-Signature header against the raw request body and your endpoint's whsec_ secret. tolerance 0 means DefaultWebhookTolerance.

Types

type Balance

type Balance struct {
	Plan               string         `json:"plan"`
	SubscriptionStatus string         `json:"subscription_status"`
	PeriodEnd          *time.Time     `json:"period_end,omitempty"`
	Credits            map[string]int `json:"credits"`
	MonthlyCredits     map[string]int `json:"monthly_credits"`
	FairUseSoftReached bool           `json:"fair_use_soft_reached"`
	FairUseHardReached bool           `json:"fair_use_hard_reached"`
}

Balance is the plan and what is left to spend. Credits are remaining meter balances ("lead", "ai_edit", "call_minute"); MonthlyCredits what the plan grants each month.

type Call

type Call struct {
	ID              string    `json:"id"`
	LeadID          string    `json:"lead_id"`
	Status          string    `json:"status"`
	RecordingURL    string    `json:"recording_url,omitempty"`
	Transcript      string    `json:"transcript,omitempty"`
	Summary         string    `json:"summary,omitempty"`
	DurationSeconds int       `json:"duration_seconds"`
	MinutesCharged  int       `json:"minutes_charged,omitempty"`
	ErrorMessage    string    `json:"error_message,omitempty"`
	CreatedAt       time.Time `json:"created_at"`
	UpdatedAt       time.Time `json:"updated_at"`
}

type Client

type Client struct {
	Searches       *SearchesService
	Leads          *LeadsService
	Outreach       *OutreachService
	Pipelines      *PipelinesService
	Deals          *DealsService
	Webhooks       *WebhooksService
	Customers      *CustomersService
	SenderAccounts *SenderAccountsService
	// contains filtered or unexported fields
}

Client talks to the Custfind /v1 API. It is safe for concurrent use.

func NewClient

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

NewClient returns a client. Without WithAPIKey it reads CUSTFIND_API_KEY.

func (*Client) Balance

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

Balance returns the plan and remaining credits.

func (*Client) Me

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

Me returns the workspace and key behind the current key.

func (*Client) Usage

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

Usage returns what API searches spent, optionally grouped by customer or key.

type CreateDealParams

type CreateDealParams struct {
	LeadID string `json:"lead_id"`
	Title  string `json:"title,omitempty"`
	// StageID defaults to the first stage of the default pipeline.
	StageID        string `json:"stage_id,omitempty"`
	ValueCents     *int64 `json:"value_cents,omitempty"`
	Currency       string `json:"currency,omitempty"`
	AssignedUserID string `json:"assigned_user_id,omitempty"`
}

type CreateSearchParams

type CreateSearchParams struct {
	Sector   string `json:"sector"`
	Location string `json:"location,omitempty"`
	// Limit is the most leads to collect; 0 means the plan's maximum.
	Limit int `json:"limit,omitempty"`
	// SearchMode is "point" (default) or "country".
	SearchMode  string   `json:"search_mode,omitempty"`
	CountryCode string   `json:"country_code,omitempty"`
	Latitude    *float64 `json:"latitude,omitempty"`
	Longitude   *float64 `json:"longitude,omitempty"`
	RadiusKm    *float64 `json:"radius_km,omitempty"`
	Language    string   `json:"language,omitempty"`
	// SellerProfileID picks the offer the leads are pitched with; empty
	// uses the workspace default.
	SellerProfileID string `json:"seller_profile_id,omitempty"`
	// ExternalCustomerID is your own id for the customer this search is
	// for; usage and monthly caps are tracked per customer.
	ExternalCustomerID string `json:"external_customer_id,omitempty"`
}

CreateSearchParams starts a search. Set Limit to what you need: it caps the credits the search reserves.

type CreateSenderAccountParams

type CreateSenderAccountParams struct {
	Name     string `json:"name"`
	Email    string `json:"email"`
	SMTPHost string `json:"smtp_host"`
	SMTPPort int    `json:"smtp_port"`
	SMTPUser string `json:"smtp_user"`
	SMTPPass string `json:"smtp_pass"`
	// ExternalCustomerID reserves the mailbox for one of your customers:
	// that customer's leads are then emailed only from its own mailboxes.
	ExternalCustomerID string `json:"external_customer_id,omitempty"`
}

CreateSenderAccountParams connects a mailbox. The password is write-only and tested against the server before the account is saved.

type CreateWebhookParams

type CreateWebhookParams struct {
	URL         string   `json:"url"`
	Events      []string `json:"events"`
	Description string   `json:"description,omitempty"`
}

type Customer

type Customer struct {
	ExternalCustomerID string    `json:"external_customer_id"`
	MonthlyCreditLimit *int      `json:"monthly_credit_limit"`
	SpentThisMonth     int       `json:"spent_this_month"`
	Remaining          *int      `json:"remaining"`
	PeriodStart        time.Time `json:"period_start"`
}

Customer is one of your customers (external_customer_id) and its monthly credit cap. MonthlyCreditLimit and Remaining are nil when uncapped.

type CustomersService

type CustomersService struct {
	// contains filtered or unexported fields
}

CustomersService manages monthly credit caps for your own customers (the external_customer_id you pass when starting searches).

func (*CustomersService) Get

func (s *CustomersService) Get(ctx context.Context, externalCustomerID string) (*Customer, error)

func (*CustomersService) List

func (s *CustomersService) List(ctx context.Context) ([]Customer, error)

func (*CustomersService) RemoveLimit

func (s *CustomersService) RemoveLimit(ctx context.Context, externalCustomerID string, opts ...RequestOption) error

func (*CustomersService) SetLimit

func (s *CustomersService) SetLimit(ctx context.Context, externalCustomerID string, monthlyCredits int, opts ...RequestOption) (*Customer, error)

SetLimit caps what the customer's searches may spend each month.

type Deal

type Deal struct {
	ID             string     `json:"id"`
	LeadID         string     `json:"lead_id"`
	PipelineID     string     `json:"pipeline_id"`
	StageID        string     `json:"stage_id"`
	Title          string     `json:"title"`
	ValueCents     *int64     `json:"value_cents,omitempty"`
	Currency       string     `json:"currency"`
	AssignedUserID *string    `json:"assigned_user_id,omitempty"`
	AssignedName   string     `json:"assigned_name,omitempty"`
	ClosedAt       *time.Time `json:"closed_at,omitempty"`
	CreatedAt      time.Time  `json:"created_at"`
	UpdatedAt      time.Time  `json:"updated_at"`
	LeadName       string     `json:"lead_name,omitempty"`
	LeadEmail      string     `json:"lead_email,omitempty"`
	LeadScore      *int       `json:"lead_score,omitempty"`
}

type DealStageChangedData

type DealStageChangedData struct {
	ID              string  `json:"id"`
	LeadID          string  `json:"lead_id"`
	PipelineID      string  `json:"pipeline_id"`
	StageID         string  `json:"stage_id"`
	StageName       string  `json:"stage_name"`
	IsWon           bool    `json:"is_won"`
	IsLost          bool    `json:"is_lost"`
	PreviousStageID *string `json:"previous_stage_id"`
}

DealStageChangedData is the data of deal.stage_changed. PreviousStageID is nil when the deal was just created.

type DealsService

type DealsService struct {
	// contains filtered or unexported fields
}

func (*DealsService) Create

func (s *DealsService) Create(ctx context.Context, params CreateDealParams, opts ...RequestOption) (*Deal, error)

func (*DealsService) Update

func (s *DealsService) Update(ctx context.Context, id string, params UpdateDealParams, opts ...RequestOption) (*Deal, error)

Update changes the fields that are set, e.g. moves a deal to another stage.

type Error

type Error struct {
	// StatusCode is the HTTP status, or 0 when there was no response.
	StatusCode int
	// Code is the API's error value, e.g. "customer_limit_reached", or
	// "http_<status>" when the body had none.
	Code string
	// Message holds extra detail when the API gave some.
	Message string
	// RetryAfter is the server's Retry-After, if it sent one.
	RetryAfter time.Duration
	// contains filtered or unexported fields
}

Error is an error response from the API, or a request that never got one (StatusCode 0, Code "network_error").

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

type Lead

type Lead struct {
	ID        string   `json:"id"`
	ProjectID string   `json:"project_id"`
	Name      string   `json:"name"`
	Phone     string   `json:"phone,omitempty"`
	Website   string   `json:"website,omitempty"`
	Email     string   `json:"email,omitempty"`
	Address   string   `json:"address,omitempty"`
	Category  string   `json:"category,omitempty"`
	Rating    *float64 `json:"rating,omitempty"`
	Score     *int     `json:"score,omitempty"`
	// ContactLocked is true when the plan's contact-reveal allowance hides
	// this lead's email and phone.
	ContactLocked     bool       `json:"contact_locked,omitempty"`
	Source            string     `json:"source,omitempty"`
	RegionCode        string     `json:"region_code,omitempty"`
	RegionName        string     `json:"region_name,omitempty"`
	Language          string     `json:"language,omitempty"`
	CreatedAt         *time.Time `json:"created_at,omitempty"`
	UnsubscribedAt    *time.Time `json:"unsubscribed_at,omitempty"`
	SellerProfileID   string     `json:"seller_profile_id,omitempty"`
	SellerProfileName string     `json:"seller_profile_name,omitempty"`
	DealID            string     `json:"deal_id,omitempty"`
	DealStageID       string     `json:"deal_stage_id,omitempty"`
	DealStageName     string     `json:"deal_stage_name,omitempty"`
	AssignedUserID    string     `json:"assigned_user_id,omitempty"`
	AssignedName      string     `json:"assigned_name,omitempty"`
}

type LeadCRM

type LeadCRM struct {
	Deal     *Deal             `json:"deal,omitempty"`
	Notes    []Note            `json:"notes"`
	Tasks    []Task            `json:"tasks"`
	Tags     []Tag             `json:"tags"`
	Outreach []OutreachMessage `json:"outreach"`
	Meetings []Meeting         `json:"meetings"`
}

LeadCRM is everything the CRM holds about a lead.

type LeadCreatedData

type LeadCreatedData struct {
	ID                 string   `json:"id"`
	SearchID           *string  `json:"search_id"`
	ExternalCustomerID *string  `json:"external_customer_id"`
	Name               string   `json:"name"`
	Phone              *string  `json:"phone"`
	Website            *string  `json:"website"`
	Address            *string  `json:"address"`
	Category           *string  `json:"category"`
	Rating             *float64 `json:"rating"`
	RegionName         *string  `json:"region_name"`
	Language           *string  `json:"language"`
	Source             string   `json:"source"`
}

LeadCreatedData is the data of lead.created.

type LeadDetail

type LeadDetail struct {
	Lead       Lead              `json:"lead"`
	Enrichment *LeadEnrichment   `json:"enrichment,omitempty"`
	Outreach   []OutreachMessage `json:"outreach,omitempty"`
	Calls      []Call            `json:"calls,omitempty"`
}

LeadDetail is a lead with its enrichment, emails and calls.

type LeadEnrichment

type LeadEnrichment struct {
	ID              string         `json:"id"`
	LeadID          string         `json:"lead_id"`
	Email           string         `json:"email,omitempty"`
	EmailStatus     string         `json:"email_status"`
	EmailVerifiedAt *time.Time     `json:"email_verified_at,omitempty"`
	Socials         []string       `json:"socials"`
	Metadata        map[string]any `json:"metadata"`
	CreatedAt       time.Time      `json:"created_at"`
}

type LeadListParams

type LeadListParams struct {
	Page     int `url:"page"`
	PageSize int `url:"page_size"` // at most 100
	// Q searches name, email, phone and website.
	Q        string `url:"q"`
	JobID    string `url:"job_id"`
	Category string `url:"category"`
	// Contact is "email", "phone", "both", "website" or "none".
	Contact   string   `url:"contact"`
	MinScore  *int     `url:"min_score"`
	MaxScore  *int     `url:"max_score"`
	MinRating *float64 `url:"min_rating"`
	MaxRating *float64 `url:"max_rating"`
	Region    string   `url:"region"`
	Language  string   `url:"language"`
	// Source is "scrape", "manual", "import", "linkedin" or "manual_import".
	Source string `url:"source"`
	// OutreachStatus is "none", "pending", "sent", "opened" or "failed".
	OutreachStatus string `url:"outreach_status"`
	// Unsubscribed is "yes" or "no".
	Unsubscribed string `url:"unsubscribed"`
	StageID      string `url:"stage_id"`
	TagID        string `url:"tag_id"`
	// Sort is e.g. "score_desc", "rating_desc", "created_asc", "name".
	// Empty means newest first.
	Sort string `url:"sort"`
}

LeadListParams filters GET /v1/leads. Zero values are left out.

type LeadsService

type LeadsService struct {
	// contains filtered or unexported fields
}

LeadsService reads leads and acts on one.

func (*LeadsService) AddNote

func (s *LeadsService) AddNote(ctx context.Context, id, body string, opts ...RequestOption) (*Note, error)

func (*LeadsService) All

func (s *LeadsService) All(ctx context.Context, params LeadListParams) iter.Seq2[Lead, error]

All yields every lead matching params, page by page.

func (*LeadsService) CRM

func (s *LeadsService) CRM(ctx context.Context, id string) (*LeadCRM, error)

CRM returns the lead's deal, notes, tasks, tags, emails and meetings.

func (*LeadsService) Get

func (s *LeadsService) Get(ctx context.Context, id string) (*LeadDetail, error)

Get returns a lead with its enrichment, emails and calls.

func (*LeadsService) List

func (s *LeadsService) List(ctx context.Context, params LeadListParams) (*Page[Lead], error)

func (*LeadsService) Outreach

func (s *LeadsService) Outreach(ctx context.Context, id string) ([]OutreachMessage, error)

Outreach returns the lead's emails.

func (*LeadsService) WriteDraft

func (s *LeadsService) WriteDraft(ctx context.Context, id string, regenerate bool, opts ...RequestOption) ([]OutreachMessage, error)

WriteDraft writes the lead's email draft if it has none. With regenerate it rewrites the draft, which spends one AI edit.

type Me

type Me struct {
	OrganizationID string `json:"organization_id"`
	APIKeyID       string `json:"api_key_id"`
}

Me is the workspace and key behind the current API key.

type Meeting

type Meeting struct {
	ID            string    `json:"id"`
	LeadID        string    `json:"lead_id"`
	Title         string    `json:"title"`
	Description   string    `json:"description"`
	StartsAt      time.Time `json:"starts_at"`
	EndsAt        time.Time `json:"ends_at"`
	MeetLink      string    `json:"meet_link,omitempty"`
	AttendeeEmail string    `json:"attendee_email,omitempty"`
	CreatedAt     time.Time `json:"created_at"`
}

type Note

type Note struct {
	ID         string    `json:"id"`
	LeadID     string    `json:"lead_id"`
	AuthorName string    `json:"author_name,omitempty"`
	Body       string    `json:"body"`
	CreatedAt  time.Time `json:"created_at"`
	UpdatedAt  time.Time `json:"updated_at"`
}

type Option

type Option func(*Client)

Option configures a Client.

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the workspace key (cf_live_…). Defaults to CUSTFIND_API_KEY.

func WithBaseURL

func WithBaseURL(u string) Option

WithBaseURL points the client at another API host.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient sets the http.Client used for requests. Its Timeout is the per-attempt timeout.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets how often a request is retried after a network error, 429, 5xx or an idempotent write still in progress. Default 3.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the per-attempt timeout. Default 30 s.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent prepends your own product to the User-Agent.

type OutreachListParams

type OutreachListParams struct {
	Page     int            `url:"page"`
	PageSize int            `url:"page_size"`
	JobID    string         `url:"job_id"`
	Status   OutreachStatus `url:"status"`
	Channel  string         `url:"channel"`
	Q        string         `url:"q"`
	// Sort is "created_at_asc", "sent_at_desc", "sent_at_asc",
	// "lead_name_asc", "lead_name_desc" or "engagement_score_desc".
	// Empty means newest first.
	Sort string `url:"sort"`
}

type OutreachMessage

type OutreachMessage struct {
	ID                  string     `json:"id"`
	LeadID              string     `json:"lead_id"`
	LeadName            string     `json:"lead_name,omitempty"`
	LeadEmail           string     `json:"lead_email,omitempty"`
	LeadEmailStatus     string     `json:"lead_email_status,omitempty"`
	LeadEmailSuppressed bool       `json:"lead_email_suppressed,omitempty"`
	Channel             string     `json:"channel"`
	EmailSubject        string     `json:"email_subject,omitempty"`
	Message             string     `json:"message"`
	ScheduledAt         *time.Time `json:"scheduled_at,omitempty"`
	SentAt              *time.Time `json:"sent_at,omitempty"`
	SenderAccountID     *string    `json:"sender_account_id,omitempty"`
	ErrorMessage        *string    `json:"error_message,omitempty"`
	OpenedAt            *time.Time `json:"opened_at,omitempty"`
	OpenCount           int        `json:"open_count,omitempty"`
	LastOpenedAt        *time.Time `json:"last_opened_at,omitempty"`
	ClickCount          int        `json:"click_count,omitempty"`
	FirstClickedAt      *time.Time `json:"first_clicked_at,omitempty"`
	LastClickedAt       *time.Time `json:"last_clicked_at,omitempty"`
	EngagementScore     int        `json:"engagement_score,omitempty"`
	StepPosition        *int       `json:"step_position,omitempty"`
	SequenceStepName    string     `json:"sequence_step_name,omitempty"`
	SequenceTotalSteps  int        `json:"sequence_total_steps,omitempty"`
	// QualityIssues lists the automatic checks an AI draft failed; such a
	// draft needs a human look before it is sent.
	QualityIssues []string  `json:"quality_issues,omitempty"`
	PromptVersion string    `json:"prompt_version,omitempty"`
	CreatedAt     time.Time `json:"created_at"`
}

type OutreachPage

type OutreachPage struct {
	Page[OutreachMessage]
	Stats OutreachStats `json:"stats"`
}

type OutreachRepliedData

type OutreachRepliedData struct {
	ID                string    `json:"id"`
	LeadID            *string   `json:"lead_id"`
	OutreachMessageID *string   `json:"outreach_message_id"`
	ThreadID          string    `json:"thread_id"`
	FromEmail         string    `json:"from_email"`
	FromName          *string   `json:"from_name"`
	Subject           *string   `json:"subject"`
	Text              *string   `json:"text"` // the first 2000 characters
	ReceivedAt        time.Time `json:"received_at"`
}

OutreachRepliedData is the data of outreach.replied.

type OutreachSentData

type OutreachSentData struct {
	ID           string    `json:"id"`
	LeadID       string    `json:"lead_id"`
	Channel      string    `json:"channel"`
	EmailSubject *string   `json:"email_subject"`
	SentAt       time.Time `json:"sent_at"`
}

OutreachSentData is the data of outreach.sent.

type OutreachService

type OutreachService struct {
	// contains filtered or unexported fields
}

OutreachService edits and sends emails.

func (*OutreachService) Get

func (*OutreachService) List

func (*OutreachService) Rewrite

Rewrite rewrites the email with AI by tone or instruction. It spends one AI edit, refunded if the rewrite fails.

func (*OutreachService) Send

func (s *OutreachService) Send(ctx context.Context, id string, scheduledAt time.Time, opts ...RequestOption) (*OutreachMessage, error)

Send queues the email now, or for scheduledAt when it is not zero.

func (*OutreachService) Update

type OutreachStats

type OutreachStats struct {
	Total          int `json:"total"`
	Draft          int `json:"draft"`
	NeedsReview    int `json:"needs_review"`
	Scheduled      int `json:"scheduled"`
	Sent           int `json:"sent"`
	Failed         int `json:"failed"`
	Opened         int `json:"opened"`
	OpenRate       int `json:"open_rate"`
	AvgEngagement  int `json:"avg_engagement"`
	EngagementRate int `json:"engagement_rate"`
}

OutreachStats counts the emails matching an outreach list's filter.

type OutreachStatus

type OutreachStatus string

OutreachStatus filters emails by state.

const (
	OutreachDraft     OutreachStatus = "draft"
	OutreachScheduled OutreachStatus = "scheduled"
	OutreachSent      OutreachStatus = "sent"
	OutreachFailed    OutreachStatus = "failed"
)

type Page

type Page[T any] struct {
	Items    []T `json:"items"`
	Total    int `json:"total"`
	Page     int `json:"page"`
	PageSize int `json:"page_size"`
}

Page is one page of a list.

type Pipeline

type Pipeline struct {
	ID        string    `json:"id"`
	Name      string    `json:"name"`
	IsDefault bool      `json:"is_default"`
	CreatedAt time.Time `json:"created_at"`
	Stages    []Stage   `json:"stages"`
}

Pipeline is a deal pipeline with its stages in order.

type PipelinesService

type PipelinesService struct {
	// contains filtered or unexported fields
}

func (*PipelinesService) List

func (s *PipelinesService) List(ctx context.Context) ([]Pipeline, error)

List returns the pipelines with their stages in order.

type RequestOption

type RequestOption func(*requestConfig)

RequestOption changes a single call.

func WithIdempotencyKey

func WithIdempotencyKey(key string) RequestOption

WithIdempotencyKey sends your own Idempotency-Key with a write instead of a generated one, which makes a retry across process restarts safe too. The API remembers a key for 24 hours.

type RewriteOutreachParams

type RewriteOutreachParams struct {
	Tone        string `json:"tone,omitempty"`
	Instruction string `json:"instruction,omitempty"`
}

RewriteOutreachParams needs a Tone, an Instruction or both.

type Search struct {
	ID                 string       `json:"id"`
	OrganizationID     string       `json:"organization_id"`
	ProjectID          string       `json:"project_id"`
	CreatedBy          string       `json:"created_by"`
	Sector             string       `json:"sector"`
	Location           string       `json:"location"`
	Limit              int          `json:"limit"`
	Latitude           *float64     `json:"latitude,omitempty"`
	Longitude          *float64     `json:"longitude,omitempty"`
	RadiusKm           *float64     `json:"radius_km,omitempty"`
	SearchMode         string       `json:"search_mode,omitempty"`
	CountryCode        string       `json:"country_code,omitempty"`
	Status             SearchStatus `json:"status"`
	Progress           int          `json:"progress"`
	LeadsFound         int          `json:"leads_found"`
	LeadsProcessed     int          `json:"leads_processed"`
	CreditsReserved    int          `json:"credits_reserved,omitempty"`
	CreditsConsumed    int          `json:"credits_consumed,omitempty"`
	CreditsRefunded    int          `json:"credits_refunded,omitempty"`
	ErrorMessage       string       `json:"error_message,omitempty"`
	Source             string       `json:"source,omitempty"`
	SellerProfileID    string       `json:"seller_profile_id,omitempty"`
	APIKeyID           string       `json:"api_key_id,omitempty"`
	ExternalCustomerID string       `json:"external_customer_id,omitempty"`
	CreatedAt          time.Time    `json:"created_at"`
	UpdatedAt          time.Time    `json:"updated_at"`
}

type SearchCreated

type SearchCreated struct {
	ProjectID string       `json:"project_id"`
	JobID     string       `json:"job_id"`
	Status    SearchStatus `json:"status"`
}

type SearchEventData

type SearchEventData struct {
	ID                 string       `json:"id"`
	Status             SearchStatus `json:"status"`
	Sector             string       `json:"sector"`
	Location           string       `json:"location"`
	LeadsFound         int          `json:"leads_found"`
	CreditsReserved    int          `json:"credits_reserved"`
	CreditsConsumed    int          `json:"credits_consumed"`
	Source             string       `json:"source"`
	APIKeyID           *string      `json:"api_key_id"`
	ExternalCustomerID *string      `json:"external_customer_id"`
	ErrorMessage       *string      `json:"error_message"`
}

SearchEventData is the data of search.completed, search.failed and search.cancelled.

type SearchStatus

type SearchStatus string

SearchStatus is where a search is in its life.

const (
	SearchQueued     SearchStatus = "queued"
	SearchScraping   SearchStatus = "scraping"
	SearchProcessing SearchStatus = "processing"
	SearchCompleted  SearchStatus = "completed"
	SearchFailed     SearchStatus = "failed"
	SearchCancelled  SearchStatus = "cancelled"
)

func (SearchStatus) Done

func (s SearchStatus) Done() bool

Done reports whether the search has stopped (completed, failed or cancelled).

type SearchesService

type SearchesService struct {
	// contains filtered or unexported fields
}

SearchesService starts and follows lead searches.

func (*SearchesService) AllLeads

func (s *SearchesService) AllLeads(ctx context.Context, id string, params LeadListParams) iter.Seq2[Lead, error]

AllLeads yields every lead of a search, page by page. Iterate a finished search: while one runs, new leads can shift the pages.

func (*SearchesService) Cancel

func (s *SearchesService) Cancel(ctx context.Context, id string, opts ...RequestOption) (*Search, error)

Cancel stops a running search; unused credits are refunded.

func (*SearchesService) Create

Create starts a search. Set Limit to what you need: it caps the credits the search reserves.

func (*SearchesService) Get

func (s *SearchesService) Get(ctx context.Context, id string) (*Search, error)

func (*SearchesService) Leads

func (s *SearchesService) Leads(ctx context.Context, id string, params LeadListParams) (*Page[Lead], error)

Leads returns one page of a search's leads. params.JobID is ignored.

func (*SearchesService) List

func (s *SearchesService) List(ctx context.Context, limit int) ([]Search, error)

List returns the latest searches, newest first; limit is at most 100 (0 means 20).

func (*SearchesService) WaitForCompletion

func (s *SearchesService) WaitForCompletion(ctx context.Context, id string, opts WaitOptions) (*Search, error)

WaitForCompletion polls until the search completes, fails or is cancelled, and returns it. Bound the wait with ctx.

type SenderAccount

type SenderAccount struct {
	ID                 string     `json:"id"`
	Name               string     `json:"name"`
	Email              string     `json:"email"`
	SMTPHost           string     `json:"smtp_host"`
	SMTPPort           int        `json:"smtp_port"`
	SMTPUser           string     `json:"smtp_user"`
	DailyLimit         int        `json:"daily_limit"`
	ExternalCustomerID string     `json:"external_customer_id,omitempty"`
	IMAPSyncStatus     string     `json:"imap_sync_status,omitempty"`
	IMAPSyncedAt       *time.Time `json:"imap_synced_at,omitempty"`
	CreatedAt          time.Time  `json:"created_at"`
}

type SenderAccountsService

type SenderAccountsService struct {
	// contains filtered or unexported fields
}

SenderAccountsService manages the mailboxes emails go out from.

func (*SenderAccountsService) Create

Create connects a mailbox; the SMTP login is tested before it is saved.

func (*SenderAccountsService) Delete

func (s *SenderAccountsService) Delete(ctx context.Context, id string, opts ...RequestOption) error

func (*SenderAccountsService) List

func (s *SenderAccountsService) List(ctx context.Context, externalCustomerID string) ([]SenderAccount, error)

List returns the mailboxes; a non-empty externalCustomerID returns only that customer's.

type Stage

type Stage struct {
	ID         string    `json:"id"`
	PipelineID string    `json:"pipeline_id"`
	Name       string    `json:"name"`
	Position   int       `json:"position"`
	Color      string    `json:"color"`
	IsWon      bool      `json:"is_won"`
	IsLost     bool      `json:"is_lost"`
	CreatedAt  time.Time `json:"created_at"`
}

type Tag

type Tag struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Color string `json:"color"`
}

type Task

type Task struct {
	ID           string     `json:"id"`
	LeadID       string     `json:"lead_id"`
	DealID       *string    `json:"deal_id,omitempty"`
	AssignedName string     `json:"assigned_name,omitempty"`
	Title        string     `json:"title"`
	DueAt        *time.Time `json:"due_at,omitempty"`
	CompletedAt  *time.Time `json:"completed_at,omitempty"`
	CreatedAt    time.Time  `json:"created_at"`
}

type UpdateDealParams

type UpdateDealParams struct {
	Title          *string `json:"title,omitempty"`
	PipelineID     *string `json:"pipeline_id,omitempty"`
	StageID        *string `json:"stage_id,omitempty"`
	ValueCents     *int64  `json:"value_cents,omitempty"`
	Currency       *string `json:"currency,omitempty"`
	AssignedUserID *string `json:"assigned_user_id,omitempty"`
}

UpdateDealParams changes only the fields that are set.

type UpdateOutreachParams

type UpdateOutreachParams struct {
	Message      string  `json:"message"`
	EmailSubject *string `json:"email_subject,omitempty"`
}

type UpdateWebhookParams

type UpdateWebhookParams struct {
	URL         *string  `json:"url,omitempty"`
	Description *string  `json:"description,omitempty"`
	Events      []string `json:"events,omitempty"`
	Enabled     *bool    `json:"enabled,omitempty"`
}

UpdateWebhookParams changes only the fields that are set.

type Usage

type Usage struct {
	From    time.Time  `json:"from"`
	To      time.Time  `json:"to"`
	GroupBy string     `json:"group_by"`
	Rows    []UsageRow `json:"rows"`
	Total   UsageRow   `json:"total"`
}

type UsageParams

type UsageParams struct {
	From string `url:"from"` // YYYY-MM-DD or RFC 3339
	To   string `url:"to"`
	// GroupBy is "customer", "api_key" or "none".
	GroupBy            string `url:"group_by"`
	ExternalCustomerID string `url:"external_customer_id"`
}

type UsageRow

type UsageRow struct {
	Group       string `json:"group"`
	Searches    int    `json:"searches"`
	LeadsFound  int    `json:"leads_found"`
	CreditsUsed int    `json:"credits_used"`
	CreditsHeld int    `json:"credits_held"`
}

type WaitOptions

type WaitOptions struct {
	// Interval between polls. Default 3 s.
	Interval time.Duration
}

WaitOptions tune WaitForCompletion.

type WebhookDelivery

type WebhookDelivery struct {
	ID             string     `json:"id"`
	EventID        string     `json:"event_id"`
	EventType      string     `json:"event_type"`
	Status         string     `json:"status"` // pending, delivered or failed
	Attempts       int        `json:"attempts"`
	LastStatusCode *int       `json:"last_status_code,omitempty"`
	LastError      string     `json:"last_error,omitempty"`
	NextAttemptAt  *time.Time `json:"next_attempt_at,omitempty"`
	DeliveredAt    *time.Time `json:"delivered_at,omitempty"`
	CreatedAt      time.Time  `json:"created_at"`
}

type WebhookEndpoint

type WebhookEndpoint struct {
	ID                  string     `json:"id"`
	URL                 string     `json:"url"`
	Description         string     `json:"description"`
	Events              []string   `json:"events"`
	CreatedAt           time.Time  `json:"created_at"`
	DisabledAt          *time.Time `json:"disabled_at,omitempty"`
	ConsecutiveFailures int        `json:"consecutive_failures"`
}

type WebhookEndpointCreated

type WebhookEndpointCreated struct {
	Endpoint WebhookEndpoint `json:"endpoint"`
	Secret   string          `json:"secret"`
}

WebhookEndpointCreated carries the signing secret, shown only once.

type WebhookEvent

type WebhookEvent struct {
	ID             string          `json:"id"`
	Type           string          `json:"type"`
	CreatedAt      time.Time       `json:"created_at"`
	OrganizationID string          `json:"organization_id"`
	Data           json.RawMessage `json:"data"`
}

WebhookEvent is the body of a webhook delivery. Decode Data with Event.Decode into the struct for its Type (SearchEventData, …).

func ConstructEvent

func ConstructEvent(payload []byte, header, secret string) (*WebhookEvent, error)

ConstructEvent verifies a webhook delivery and returns its event. Pass the raw body exactly as received, before any JSON parsing:

payload, _ := io.ReadAll(r.Body)
event, err := custfind.ConstructEvent(payload, r.Header.Get(custfind.SignatureHeader), secret)
if err != nil { w.WriteHeader(400); return }
if event.Type == custfind.EventSearchCompleted {
	var d custfind.SearchEventData
	_ = event.Decode(&d)
}

Deliveries can repeat; use event.ID to handle each event once.

func (*WebhookEvent) Decode

func (e *WebhookEvent) Decode(v any) error

Decode unmarshals the event's data into v.

type WebhooksService

type WebhooksService struct {
	// contains filtered or unexported fields
}

func (*WebhooksService) Create

Create registers an endpoint. The returned Secret is shown only now; keep it to verify deliveries with ConstructEvent.

func (*WebhooksService) Delete

func (s *WebhooksService) Delete(ctx context.Context, id string, opts ...RequestOption) error

func (*WebhooksService) Deliveries

func (s *WebhooksService) Deliveries(ctx context.Context, id string, limit int) ([]WebhookDelivery, error)

Deliveries returns the endpoint's latest deliveries (limit 0 = default).

func (*WebhooksService) List

func (*WebhooksService) SendTest

func (s *WebhooksService) SendTest(ctx context.Context, id string, opts ...RequestOption) (*WebhookDelivery, error)

SendTest queues a webhook.test delivery to the endpoint.

func (*WebhooksService) Update

Jump to

Keyboard shortcuts

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