Metadata-Version: 2.5
Name: ghl-api-extended
Version: 0.1.0
Summary: Typed GHL client covering GoHighLevel's internal search-v2 endpoints (contacts, opportunities, appointments) — filter the way the GHL UI does, with date-range fetch helpers and its own OAuth token store.
Project-URL: Homepage, https://github.com/Mubashir-19/ghl-api-extended-py
Project-URL: Repository, https://github.com/Mubashir-19/ghl-api-extended-py
Project-URL: Issues, https://github.com/Mubashir-19/ghl-api-extended-py/issues
Author: Mubashir
License: Apache-2.0
License-File: LICENSE
Keywords: api,crm,ghl,gohighlevel,highlevel,sdk
Requires-Python: >=3.10
Requires-Dist: python-dotenv>=1.0
Requires-Dist: requests>=2.31
Description-Content-Type: text/markdown

# ghl-api-extended (Python)

A GHL client covering the internal search-v2 endpoints GHL's own UI uses for
Contacts, Opportunities, and Appointments/Calendar filtering — plus a
self-contained OAuth token store, so it works standalone without any other
project's auth.

This is the Python port of [ghl-api-extended](https://github.com/Mubashir-19/ghl-api-extended)
(the Node/TypeScript package). One difference: the JS package is a drop-in
subclass of the official `@gohighlevel/api-client` SDK, so it inherits every
official route (`.contacts`, `.opportunities`, `.calendars`, ...) for free.
**No official GHL SDK exists for Python**, so `HighLevel` here only covers
auth + these search-v2 endpoints — for any other GHL route, call
`ghl.request(method, url, ...)` directly (see `http.HighLevelClient.request`).

These search-v2 endpoints aren't in GHL's public API docs; the field/operator
behavior in `docs/filters-reference.md` and the per-endpoint docs was
reverse-engineered by probing a live account. Check those before filtering on
anything not already covered by the `fetch_*_by_date_range` helpers below.

## Install

```bash
pip install ghl-api-extended
```

## Usage

```python
from ghl_api_extended import HighLevel

ghl = HighLevel(location_access_token=location_access_token)  # however you already obtain it

# Filter + auto-paginate the way the GHL UI's Contacts tab does.
contacts = ghl.fetch_contacts_by_date_range(ContactsByDateRangeParams(
    location_id=location_id,
    start_date="2026-01-01",
    end_date="2026-01-31",
    filters=[Filter(field="tags", operator="contains", value=["confirmed"])],
))
```

```python
from ghl_api_extended import OpportunitiesByDateRangeParams, AppointmentsByDateRangeParams, Filter

opportunities = ghl.fetch_opportunities_by_date_range(OpportunitiesByDateRangeParams(
    location_id=location_id,
    date_field="last_stage_change_date",  # default: date_added
    start_date="2026-01-01",
    end_date="2026-01-31",
    filters=[Filter(field="pipeline_id", operator="eq", value=[pipeline_id])],
))

appointments = ghl.fetch_appointments_by_date_range(AppointmentsByDateRangeParams(
    location_id=location_id,
    start_date="2026-01-01",  # ranges over startTime by default
    end_date="2026-01-31",
    filters=[Filter(field="appoinmentStatus", operator="eq", value="confirmed")],
))
```

The `fetch_*_by_date_range` methods auto-paginate to exhaustion, same as
scrolling a filtered list in the GHL UI — no separate page-loop needed. Each
`*ByDateRangeParams` dataclass takes `max_results`/`max_pages`/`page_limit` if
you want to bound that. For a single page, or full control over
sort/pagination/aggregations, use `ghl.search_contacts(...)` /
`ghl.search_opportunities(...)` / `ghl.search_appointments(...)` directly.

The same behavior is also available as standalone functions
(`search_contacts(client, location_id, ...)`,
`fetch_contacts_by_date_range(client, params)`, ...) that take any object with
a compatible `.request(method, url, ...)` method — useful if you're not using
the `HighLevel` class directly.

## No existing auth? Use the built-in OAuth flow

Copy `.env.example` to `.env`, fill in your GHL marketplace app's
`GHL_CLIENT_ID` / `GHL_CLIENT_SECRET` / `GHL_REDIRECT_URI`, then:

```bash
ghl-authorize
```

This opens the GHL OAuth consent screen, catches the redirect on a local
server, and saves the company session to `.tokens.json` (gitignored). Location
tokens are minted and cached automatically as you use them.

```python
from ghl_api_extended import find_most_recent_company_id, get_authorized_location_client

company_id = find_most_recent_company_id()
ghl = get_authorized_location_client(company_id, location_id)  # a HighLevel instance
```

## Filters

`filters` is a list of leaf `Filter(field, operator, value)` or
`FilterGroup(group="AND" | "OR", filters=[...])`, nestable arbitrarily.
Multiple entries at the top level are implicitly ANDed together — see
`docs/filters-reference.md` for the full field/operator map per endpoint,
including the traps (opportunities filter fields are snake_case and don't all
mechanically match the response's camelCase names; contacts/appointments
custom fields need a `customFields.<key>` dot-path — use
`contact_custom_field(key, operator, value)` / `opportunity_custom_field(...)`).

Invalid fields are rejected client-side before the network call
(`GhlFilterFieldError`); operator/value-shape errors from GHL itself are
classified into `GhlFilterOperatorError` / `GhlFilterValueError` so you can
branch on them instead of parsing message strings.

## License

Apache-2.0
