pipedrive-go

Go SDK for the Pipedrive API with a handwritten, stable public surface.
- API v2: full support
- API v1: legacy-only (endpoints not present in v2)
Generated OpenAPI clients are internal-only; use the pipedrive/v1 and
pipedrive/v2 packages.
API versions
This SDK is v2-first. Use pipedrive/v2 for new work and only reach for
pipedrive/v1 when an endpoint is not available in v2. The v1 surface is
derived from the v1 OpenAPI spec with all v2-covered operations removed, so
endpoints migrate out of v1 automatically as v2 grows.
Endpoint tables:
- v2:
docs/endpoints-v2.md
- v1 legacy:
docs/endpoints-v1-legacy.md
Install
go get github.com/juhokoskela/pipedrive-go@latest
Go 1.25+ is required.
The repository prefers Go 1.26.5 locally via the toolchain directive in
go.mod.
To pin a specific version:
go get github.com/juhokoskela/pipedrive-go@v1.0.8
Quickstart (API token)
v2:
package main
import (
"context"
"log"
"github.com/juhokoskela/pipedrive-go/pipedrive"
v2 "github.com/juhokoskela/pipedrive-go/pipedrive/v2"
)
func main() {
client, err := v2.NewClient(pipedrive.Config{
Auth: pipedrive.APITokenAuth("YOUR_API_TOKEN"),
})
if err != nil {
log.Fatal(err)
}
pipelines, _, err := client.Pipelines.List(
context.Background(),
v2.WithPipelinesPageSize(50),
)
if err != nil {
log.Fatal(err)
}
log.Printf("pipelines=%d", len(pipelines))
}
v1 legacy:
client, err := v1.NewClient(pipedrive.Config{
Auth: pipedrive.APITokenAuth("YOUR_API_TOKEN"),
})
if err != nil {
log.Fatal(err)
}
currencies, err := client.Currencies.List(context.Background(), v1.ListCurrenciesRequest{})
if err != nil {
log.Fatal(err)
}
log.Printf("currencies=%d", len(currencies))
Cursor pagination helpers are exposed as ListPager and ForEach:
pager := client.Deals.ListPager(v2.WithDealsPageSize(100))
err := pager.ForEach(context.Background(), func(d v2.Deal) error {
log.Printf("deal id=%d title=%s", d.ID, d.Title)
return nil
})
if err != nil {
log.Fatal(err)
}
OAuth2
Use the v1 OAuth helper to build the authorize URL and exchange tokens, then
pass the access token into a v2 client.
import (
"context"
"encoding/base64"
"log"
"golang.org/x/oauth2"
"github.com/juhokoskela/pipedrive-go/pipedrive"
v1 "github.com/juhokoskela/pipedrive-go/pipedrive/v1"
v2 "github.com/juhokoskela/pipedrive-go/pipedrive/v2"
)
ctx := context.Background()
oauthClient, _ := v1.NewClient(pipedrive.Config{})
authURL, err := oauthClient.OAuth.Authorize(
ctx,
v1.WithOAuthClientID(clientID),
v1.WithOAuthRedirectURI(redirectURI),
v1.WithOAuthState("state123"),
)
if err != nil {
log.Fatal(err)
}
log.Printf("open: %s", authURL)
authHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientID+":"+clientSecret))
tokens, err := oauthClient.OAuth.GetTokens(
ctx,
v1.WithOAuthAuthorization(authHeader),
v1.WithOAuthCode(code),
v1.WithOAuthRedirectURI(redirectURI),
)
if err != nil {
log.Fatal(err)
}
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: tokens.AccessToken})
apiClient, _ := v2.NewClient(pipedrive.Config{
BaseURL: tokens.APIDomain + "/api/v2",
Auth: pipedrive.OAuth2Auth{TokenSource: ts},
})
Retries and per-request options
Retries are enabled by default for 429/502/503/504 with backoff and jitter.
Override globally with RetryPolicy, or per call with request options:
policy := pipedrive.DefaultRetryPolicy()
policy.MaxAttempts = 2
client, _ := v2.NewClient(pipedrive.Config{
Auth: pipedrive.APITokenAuth("YOUR_API_TOKEN"),
RetryPolicy: &policy,
})
deal, err := client.Deals.Get(
context.Background(),
v2.DealID(123),
v2.WithDealRequestOptions(pipedrive.WithNoRetry()),
)
Response bodies are capped at 64 MiB by default. Override globally with
Config.MaxResponseSize, per request with WithResponseSizeLimit or
WithNoResponseSizeLimit, and use client.Files.DownloadTo to stream large
v1 file downloads.
Custom HTTP and middleware
client, _ := v2.NewClient(pipedrive.Config{
Auth: pipedrive.APITokenAuth("YOUR_API_TOKEN"),
HTTPClient: customClient,
Middleware: []pipedrive.Middleware{loggingMiddleware},
UserAgent: "my-app/1.0",
})
Request options such as WithHeader and WithRequestEditor run before the
transport chain. If you set Authorization or x-api-token yourself, the
configured auth provider will not overwrite that header, and custom middleware
can also replace auth headers later in the chain.
Raw API escape hatch
import "net/http"
var out struct {
Data []v2.Pipeline `json:"data"`
}
err := client.Raw.Do(context.Background(), http.MethodGet, "/pipelines", nil, nil, &out)
Known API quirks
- Products
category is documented as a numeric option ID on write, but some
responses return a string label. The v2 SDK tolerates both; string labels are
exposed via Product.CategoryName, while numeric IDs remain in
Product.Category.
Integration checks
Opt-in integration tests (skipped by default):
PIPEDRIVE_API_TOKEN=... go test ./pipedrive -run Integration -v
PIPEDRIVE_API_TOKEN=... PIPEDRIVE_INTEGRATION_WRITE=1 go test ./pipedrive -run IntegrationV2OrganizationCreateDelete -v
Smoke CLI:
PIPEDRIVE_API_TOKEN=... go run ./cmd/smoke
Optional env overrides:
PIPEDRIVE_BASE_URL_V1
PIPEDRIVE_BASE_URL_V2
PIPEDRIVE_SMOKE_TIMEOUT
Examples
See the runnable examples in examples/:
examples/token for API token usage
examples/oauth for OAuth2 usage
Versioning
The module follows semantic versioning and does not encode Pipedrive API
versioning in the module path. The v1/v2 distinction is in the package path,
not the module version.
See RELEASING.md for release steps.
Development
make update-specs
make derive-v1-legacy
make generate
make docs
make fmt
make lint
make security
go test ./...