Skip to main content

ghl_sdk/
lib.rs

1//! Unofficial async Rust SDK for the [GoHighLevel](https://www.gohighlevel.com)
2//! (HighLevel) CRM API.
3//!
4//! **Every endpoint has a typed Rust method** — 1,203 operations across 45
5//! modules, in API v2 *and* v3, each with generated request/response types, so
6//! you never have to leave the library to read HighLevel's docs.
7//!
8//! ```no_run
9//! use ghl_sdk::{contacts::CreateContact, Ghl};
10//!
11//! # async fn demo() -> Result<(), ghl_sdk::Error> {
12//! // Reads GHL_PIT_TOKEN (or GHL_ACCESS_TOKEN) from the environment.
13//! let ghl = Ghl::from_env()?;
14//!
15//! let contact = ghl.contacts().create(CreateContact {
16//!     location_id: "LOCATION_ID".into(),
17//!     email: Some("ada@example.com".into()),
18//!     first_name: Some("Ada".into()),
19//!     ..Default::default()
20//! }).await?;
21//!
22//! println!("created {}", contact.id);
23//! # Ok(()) }
24//! ```
25//!
26//! # What you get
27//!
28//! - **Auth** — Private Integration Tokens, raw OAuth access tokens, or full
29//!   OAuth 2.0 with automatic single-flight refresh and a pluggable
30//!   [`TokenStore`]. Agency→location token exchange via [`Ghl::as_location`].
31//!   See the [`auth`] module.
32//! - **Resilience** — 429s retried honoring `Retry-After`; 5xx and transport
33//!   failures retried with exponential backoff + jitter, **idempotent methods
34//!   only**, so a `POST` is never silently duplicated.
35//! - **Rate-limit awareness** — live headroom from response headers via
36//!   [`Ghl::rate_status`].
37//! - **Pagination** — GoHighLevel's cursor scheme handled for you and exposed as
38//!   [`futures_util::Stream`]s.
39//! - **Config by env var or parameter** — [`Ghl::from_env`] or
40//!   [`Ghl::builder`]; explicit parameters always win.
41//! - **Secret hygiene** — tokens live in [`secrecy`] types and are redacted from
42//!   all `Debug` output.
43//! - **Forward-compatible types** — unknown response fields are preserved in an
44//!   `extra` map instead of failing deserialization.
45//!
46//! # Generated services — every endpoint, typed
47//!
48//! Enable the cargo feature named after an API module and its whole surface
49//! appears on the client, with generated parameter and body types:
50//!
51//! ```toml
52//! ghl-sdk = { version = "0.5", features = ["invoices"] }
53//! ```
54//!
55//! ```ignore
56//! use ghl_sdk::services::invoices::ListInvoicesParams;
57//!
58//! // Required query params are constructor arguments; optional ones are setters.
59//! let params = ListInvoicesParams::new(&location_id, "location", "20", "0")
60//!     .status("draft");
61//!
62//! let page = ghl.invoices().list_invoices(&params).await?;   // typed response
63//! println!("{:?} invoices", page.total);
64//! ```
65//!
66//! Every generated method has the same predictable shape:
67//!
68//! ```text
69//! async fn <name>(&self, <path params…>, params: &XParams, body: &Dto) -> Result<Response>
70//! ```
71//!
72//! - **Path parameters** are positional `&str` arguments, in URL order.
73//! - **Query parameters** collapse into one `XParams` struct — required fields
74//!   are `new()` arguments, optional ones are chainable setters. The argument is
75//!   absent entirely when an endpoint takes no query parameters.
76//! - **Bodies** take the generated DTO from [`ghl-models`](https://docs.rs/ghl-models).
77//! - **Returns** the response type the spec names (about 3 in 4 endpoints), else
78//!   [`serde_json::Value`].
79//!
80//! ## API v3
81//!
82//! v3 is a parallel, newer surface (627 operations) reached through [`Ghl::v3`],
83//! which sends `Version: v3` for you:
84//!
85//! ```ignore
86//! let dup = ghl.v3().contacts().get_duplicate_contact(&params).await?;
87//! ```
88//!
89//! It has modules v2 lacks — `ad-publishing`, `social-planner`, `saas`,
90//! `chat-widget` — and renames three others.
91//!
92//! See [`services`] for the module list, and the
93//! [API reference](https://github.com/Shahroz/ghl-rs/blob/main/docs/api/README.md)
94//! for the Rust method behind every endpoint.
95//!
96//! ## Hand-written helpers
97//!
98//! Five modules also carry curated helpers that go beyond a 1:1 endpoint
99//! mapping — they unwrap response envelopes and turn cursor pagination into
100//! [`futures_util::Stream`]s. They live on the same services, so both styles are
101//! available together:
102//!
103//! | Module | Helpers |
104//! |---|---|
105//! | [`contacts`] | `create`, `get`, `update`, `delete`, `list` (streaming) |
106//! | [`opportunities`] | `pipelines`, `create`, `get`, `update`, `update_status`, `delete`, `search` (streaming) |
107//! | [`conversations`] | `search`, `messages`, `send_message` |
108//! | [`calendars`] | `list`, `free_slots`, `create_appointment`, `get_appointment` |
109//! | [`locations`] | `get`, `search` |
110//!
111//! ## Anything not generated
112//!
113//! [`Ghl::request_raw`] reaches any endpoint — including all of API v3 — with
114//! the same auth, retry, and rate-limit handling:
115//!
116//! ```ignore
117//! let dup = ghl.request_raw(
118//!     "GET", "/contacts/search/duplicate",
119//!     &[("locationId".into(), loc), ("email".into(), email)],
120//!     None,
121//!     Some("v3"),          // v3 endpoints need their own Version header
122//! ).await?;
123//! ```
124//!
125//! # Strict on send, lenient on receive
126//!
127//! Request types keep the spec's required fields non-`Option`, so a missing
128//! mandatory field is a compile error. Response types make everything optional:
129//! GoHighLevel sometimes omits fields its own spec marks required, and a strict
130//! response type would turn that into an unrecoverable deserialization failure.
131//!
132//! # Authentication at a glance
133//!
134//! | Situation | Use |
135//! |---|---|
136//! | Internal tool, one sub-account | [`Auth::private_integration`] (a `pit-…` token) |
137//! | You ran OAuth yourself | [`Auth::access_token`] (used as-is, never refreshed) |
138//! | Marketplace app | [`Auth::oauth`] with a [`TokenStore`] — auto-refresh |
139//! | Agency, many sub-accounts | [`Auth::oauth`] with [`UserType::Company`], then [`Ghl::as_location`] |
140//!
141//! GoHighLevel rotates the refresh token on every use, so a [`TokenStore`]
142//! implementation must persist durably — [`MemoryTokenStore`] loses the session
143//! on restart.
144//!
145//! # Webhooks
146//!
147//! With the `webhooks` feature, `webhooks::verify` checks HighLevel's
148//! RSA-SHA256 signature and `webhooks::WebhookEvent` types the envelope its 58
149//! event types share. Verify the raw bytes before parsing. See the [`webhooks`]
150//! module.
151//!
152//! # Errors
153//!
154//! [`Error`] distinguishes [`Error::Api`] (the API said no, with status and
155//! message), [`Error::RateLimited`] (retries exhausted), [`Error::Auth`],
156//! [`Error::Transport`], [`Error::Decode`], and [`Error::Config`]. GoHighLevel
157//! returns `message` as either a string or an array of strings; both normalize
158//! into [`Error::Api`]'s `message`.
159//!
160//! # Cargo features
161//!
162//! Nothing is on by default. Each API module is its own feature so you compile
163//! only the surface you use — one module is a second or two, all 45 is closer to
164//! a minute.
165//!
166//! | Feature | Effect |
167//! |---|---|
168//! | `<module>` (45 of them, e.g. `invoices`, `payments`, `products`) | That module's generated services (v2 and v3) plus its DTOs |
169//! | `full` | Every generated service. Convenient, slow to compile |
170//! | `models` | Just the [`ghl-models`](https://docs.rs/ghl-models) re-export, no services |
171//! | `webhooks` | RSA signature verification and typed events ([`webhooks`]) |
172//!
173//! # Further reading
174//!
175//! - [Usage guide](https://github.com/Shahroz/ghl-rs/blob/main/docs/GUIDE.md) —
176//!   auth decision tree, per-module cookbook, pagination, rate limits,
177//!   multi-location, v2 vs v3, troubleshooting
178//! - [Full API reference](https://github.com/Shahroz/ghl-rs/blob/main/docs/api/README.md)
179//!   — all 45 modules: every endpoint, struct, and enum value
180//! - [`ghl-mcp`](https://crates.io/crates/ghl-mcp) — MCP server built on this
181//!   SDK, exposing GoHighLevel to AI agents
182//!
183//! *Not affiliated with HighLevel Inc. "GoHighLevel" and "HighLevel" are
184//! trademarks of their respective owners.*
185
186#![warn(missing_docs)]
187#![warn(clippy::all)]
188#![cfg_attr(docsrs, feature(doc_cfg))]
189
190/// Generated data models (DTOs) for the whole GoHighLevel API, re-exported from
191/// the [`ghl-models`](https://docs.rs/ghl-models) crate.
192///
193/// Most callers don't need this directly: enabling a module feature (e.g.
194/// `invoices`) already brings in that module's generated service *and* its DTOs.
195/// Reach for `models` when you want the types without the services:
196///
197/// ```toml
198/// ghl-sdk = { version = "0.5", features = ["models"] }
199/// ghl-models = { version = "0.5", features = ["invoices", "payments"] }
200/// ```
201///
202/// Types live under `models::v2::*` and `models::v3::*`.
203#[cfg(feature = "models")]
204#[cfg_attr(docsrs, doc(cfg(feature = "models")))]
205pub use ghl_models as models;
206
207pub mod auth;
208pub mod calendars;
209mod client;
210pub mod contacts;
211pub mod conversations;
212mod error;
213pub mod locations;
214pub mod opportunities;
215pub mod services;
216
217// Module docs live in webhooks.rs; a second doc comment here would shadow the
218// intra-doc link resolution inside it.
219#[cfg(feature = "webhooks")]
220#[cfg_attr(docsrs, doc(cfg(feature = "webhooks")))]
221pub mod webhooks;
222
223pub use auth::{Auth, MemoryTokenStore, OAuthConfig, TokenSet, TokenStore, UserType};
224pub use client::{Ghl, GhlBuilder, RateStatus, API_VERSION, DEFAULT_BASE_URL};
225pub use error::{Error, Result};