Metadata-Version: 2.5
Name: goliath-data
Version: 0.1.0
Summary: Official Python SDK for the Goliath developer API (goliathdata.com)
Project-URL: Homepage, https://goliathdata.com/developers
Project-URL: Documentation, https://goliathdata.com/developers
Author-email: Goliath Data <support@goliathdata.com>
License-Expression: MIT
License-File: LICENSE
Keywords: api,crm,goliath,real estate,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: anyio<5,>=3.5
Requires-Dist: httpx<1,>=0.23
Requires-Dist: typing-extensions>=4.7
Provides-Extra: dev
Requires-Dist: mypy==2.4.1; (python_version >= '3.10') and extra == 'dev'
Requires-Dist: pytest==8.4.2; (python_version < '3.10') and extra == 'dev'
Requires-Dist: pytest==9.2.0; (python_version >= '3.10') and extra == 'dev'
Description-Content-Type: text/markdown

# goliath-data

Official Python SDK for the [Goliath](https://goliathdata.com) developer API.

The developer API is a persisted-operation gateway: one HTTP endpoint (`POST /api/v1/call`), a fixed catalog of named operations, and `Authorization: Bearer gsk_...` keys you create in the app under **Settings → API keys**. This SDK wraps it with one typed method per operation, namespaced by domain, plus transport, retries, typed errors and idempotency handled for you — sync and async.

Full API documentation lives at [goliathdata.com/developers](https://goliathdata.com/developers).

## Install

```bash
pip install goliath-data
```

Python 3.9 or later. Depends on `httpx`, `anyio` and `typing_extensions`. Server-side only: your API key must never ship in a browser or mobile app.

## Quickstart

```python
import uuid

from goliath import GoliathClient

# api_key defaults to the GOLIATH_API_KEY environment variable;
# base_url to https://server.goliathdata.com (or GOLIATH_BASE_URL).
client = GoliathClient()

# Typed queries — one method per operation, namespaced by domain.
deals = client.deals.find_deals(limit=25)

# Idempotent writes — pass an idempotency key so a retry can never double-fire.
client.contacts.create_contact(
    input={
        "sellerFirstName": "Jane",
        "sellerLastName": "Doe",
        "addPhoneNumbers": [{"phoneNumber": "+13235550100", "verificationStatus": "UNVERIFIED"}],
    },
    idempotency_key=str(uuid.uuid4()),
)

# Untyped escape hatch for operations newer than this SDK release.
client.execute("someBrandNewOperation", {"id": "..."})
```

Async is the same surface, awaited:

```python
from goliath import AsyncGoliathClient

async with AsyncGoliathClient() as client:
    deals = await client.deals.find_deals(limit=25)
```

Every call runs as the key's owning user, so your real organization permissions apply.

### Names

Methods and their arguments are snake_case (`find_deals(title_contains=...)`); the dicts you pass in and get back keep the API's own camelCase keys (`{"sellerFirstName": ...}`), exactly as the API reference shows them. Results are plain `dict`s typed as `TypedDict`s (`from goliath.types import FindDealsResult`), so your editor and type checker know every field.

An optional argument you leave out is not sent at all; passing `None` sends `null` — which for an update means "clear this field".

## Configuration

| Argument | Default | Notes |
|---|---|---|
| `api_key` | `GOLIATH_API_KEY` | A `gsk_...` key from **Settings → API keys**. |
| `base_url` | `GOLIATH_BASE_URL`, else `https://server.goliathdata.com` | The API host. |
| `timeout` | `60.0` | Seconds, per attempt — a retried call can take longer overall. Each method also takes `timeout=`. |
| `max_retries` | `2` | Retry budget for the policy below. |
| `http_client` | a new `httpx.Client` / `httpx.AsyncClient` | Bring your own for proxies, custom TLS, or a mock transport in tests. |

Use the client as a context manager (`with GoliathClient() as client:`) or call `close()` to release its connections.

## Errors

Everything raises — no error-shaped return values:

| Class | When |
|---|---|
| `GoliathAuthenticationError` (401) | Missing, invalid or revoked key |
| `GoliathPaymentRequiredError` (402) | The organization has no usable plan |
| `GoliathPermissionError` (403) | Missing scope, send consent not attested, or a resource outside your organization |
| `GoliathBadRequestError` (400) | Bad body, unknown operation or variable, raw GraphQL, idempotency key on a read |
| `GoliathIdempotencyConflictError` (409) | Same idempotency key still in flight — retry with the same key |
| `GoliathRateLimitError` (429) | Rate limited; carries `retry_after_seconds` |
| `GoliathConcurrencyLimitError` (429) | Too many in-flight requests on this key |
| `GoliathServerError` (5xx) | Gateway-side failure |
| `GoliathOperationError` | The operation ran and returned `errors` (HTTP 200); carries every error and any partial `data` |
| `GoliathNetworkError` / `GoliathTimeoutError` | No usable HTTP response at all |

All derive from `GoliathError`; the HTTP ones from `GoliathApiError` (`.status`, `.code`). Branch on a code without string literals: `GatewayErrorCode` holds the gateway's codes (`UNAUTHORIZED`, `RATE_LIMITED`, …) and `OperationErrorCode` the codes an operation's `errors` carry (`BAD_USER_INPUT`, …).

## Retries (built in)

- Both 429s are pre-execution rejections — retried for any operation, honoring `Retry-After` exactly.
- Network errors, timeouts and 5xx retry only when a re-send cannot double-fire a side effect: queries, or mutations sent with an `idempotency_key`.
- A mutation without an idempotency key is **never** re-sent.
- Exponential backoff with full jitter (0.5 s base, 8 s cap).

## Idempotency

Every mutation takes `idempotency_key=` — always pass one on writes, since the client never re-sends an unkeyed mutation. Reads don't take it at all (a type error, matching the gateway's 400). Replays surface through `on_meta=`, which receives a `ResponseMeta(status, replayed, retries)` — `replayed is True` on a replay — including replays whose stored body carries errors.

## Ask Goliath

Any `WRITE` key can talk to its owner's Ask Goliath, the agent inside the app, through `client.ask_goliath.*`. Each call holds at most 25 seconds; `ask_goliath()` sends a message and polls until the reply is done:

```python
from goliath import ask_goliath

turn = ask_goliath(client, "Which deals stalled this week?")
# turn["status"]: COMPLETED, WAITING_FOR_CALLER (answer turn["questions"] with
# client.ask_goliath.answer_agent_questions), FAILED or CANCELLED
print(turn["reply"])
ask_goliath(client, "Draft a follow-up for the first one.", conversation_id=turn["conversationId"])
```

`ask_goliath_async` is the same for `AsyncGoliathClient`. It acts as the key's owner, under that person's trust settings, and the chat shows in their chat list labelled with the key's name.

## Discovering operations

- Every method's docstring carries the operation's description, required scope and arguments.
- [goliathdata.com/developers](https://goliathdata.com/developers) — the API reference, with Python examples.
- Live: `GET /api/v1/operations` lists the catalog with per-key authorization (including operations added after this release), and `GET /api/v1/help` is the whole API as one markdown document.

Operations newer than your installed release still work through `execute()`; pick up their types with the next upgrade.

## License

MIT
