Metadata-Version: 2.5
Name: workwire
Version: 0.1.0
Summary: Official Python SDK for the Workwire API
Project-URL: Homepage, https://github.com/workwire/workwire-python
Project-URL: Changelog, https://github.com/workwire/workwire-python/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/workwire/workwire-python/issues
Author-email: Workwire <support@workwire.com>
License: MIT License
        
        Copyright (c) 2026 Workwire, Inc.
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.24
Description-Content-Type: text/markdown

# Workwire Python SDK

The official Python library for the [Workwire](https://workwire.com) API.
Create projects, compose and send Wires, and initiate partnerships from your
own systems — no HTTP calls to write, no idempotency keys to invent, no error
envelopes to parse, no 429s to handle.

## Install

```bash
pip install workwire
```

Until the first stable PyPI release you can also install straight from
GitHub:

```bash
pip install git+https://github.com/workwire/workwire-python.git
```

Python 3.10+. The only runtime dependency is `httpx`.

## Quickstart

```python
import workwire

client = workwire.Workwire()  # reads WORKWIRE_API_KEY from the environment

project = client.projects.create(name="Elm St. Townhomes")

# The partnership with your sub — create one (see below) or pick an
# established one from client.partnerships.list().
partnership = client.partnerships.create(sub_org_id="...")

wire = client.wires.create(
    project_id=project.id,
    title="Drywall — Bldg A",
    parties=[
        workwire.Party.partnered(
            partnership_id=partnership.id,
            trade="Drywall",
            scope_text="Hang, tape, and finish all interior drywall.",
            contract_value="125000.00",
        ),
    ],
)

client.wires.go_live(wire.id)  # emails any cold-invited subs

for p in client.partnerships.list().auto_paging_iter():
    print(p.id, p.status)
```

A party can also cold-invite a sub who isn't on Workwire yet — the
invitation is minted and emailed at `go_live`:

```python
workwire.Party.cold_invite(
    recipient_email="estimator@acmedrywall.com",
    recipient_org_name="Acme Drywall",
    trade="Drywall",
    scope_text="North wing.",
    contract_value="80000.00",
)
```

The counterparty's acceptance always happens between people in the Workwire
app — there is deliberately no API call for the other side of the handshake.

The quickstart is runnable as a script:
[`examples/quickstart.py`](https://github.com/workwire/workwire-python/blob/main/examples/quickstart.py)
drafts the wire above against a partnership you name, and only sends it
with `--go-live`.

## Authentication

An org admin issues an API key in the Workwire dashboard under **Settings →
API keys**; the secret (`wwk_live_…`) is shown exactly once. Put it in your
secret manager and export it:

```bash
export WORKWIRE_API_KEY="wwk_live_..."
```

`workwire.Workwire()` reads that variable, or takes `api_key=` explicitly.
The SDK never loads `.env` files itself — if you use one, load it with your
framework or `python-dotenv` before constructing the client. The client's
`repr()` and the SDK's errors never contain the key.

The base URL works the same way: an explicit `base_url=` argument wins,
otherwise the `WORKWIRE_BASE_URL` environment variable, otherwise production
(`https://api.workwire.com`). Point it at a local backend or a staging
environment without touching code:

```bash
export WORKWIRE_BASE_URL="http://localhost:8081"
```

There is no API version to configure: Workwire versions its API in the URL
path only (`/v1`), with no version header — the SDK sends no API version
and has none to pin, by design. (Pinning the `workwire` *package* version
is a separate matter — see Versioning and stability below.)

## Errors

Every API failure raises a typed exception from `workwire.errors`, carrying
the stable machine `code`, human `message`, `details`, HTTP `status`, and the
`request_id` to quote to support:

```python
from workwire import errors

try:
    client.wires.create(...)
except errors.ConflictError as e:
    if e.code == "partnership_not_established":
        print("partnership is", e.details["current_state"])
except errors.InvalidRequestError as e:
    print("fix these fields:", e.fields)
except errors.RateLimitError as e:
    print(f"over the {e.scope} budget, retry in {e.retry_after}s")
```

The hierarchy: `WorkwireError` → `APIConnectionError` (network),
`AuthenticationError` (401), `PermissionError` / `ForbiddenScopeError` (403,
with `.required_scope`), `NotFoundError` (404 — also another org's
resources; existence is never confirmed), `InvalidRequestError` (400/413/422,
with `.fields`), `ConflictError` (409, with `IdempotencyError` for the
idempotency codes), `RateLimitError` (429), `APIError` (5xx). Key on
`e.code`, never on the message text.

## Retries and idempotency

Every `POST` automatically carries an `Idempotency-Key` (a fresh UUID, or
pass `idempotency_key=` to control replay across your own process restarts).
That makes retries safe, so the SDK retries for you: connection errors,
timeouts, 429s (honoring `Retry-After`), 5xx responses, and the one
retryable 409 — `idempotency_in_flight`, where the API asks you to retry
after backoff because the original attempt is still running. All with
exponential backoff, up to `max_retries` (default 2, configurable on the
client). Every retry resends the identical bytes under the same key, which
is exactly what the server's replay detection requires. No other 4xx is
ever retried.

```python
client = workwire.Workwire(timeout=10, max_retries=3)
client.projects.create(name="X", idempotency_key="my-op-42", timeout=60)
```

## Pagination

`list()` returns one page: `.data`, `.next_cursor`, `.has_more`. Walk
everything with the iterator — it follows cursors until the server says done:

```python
for wire in client.wires.list(state="live").auto_paging_iter():
    ...
```

## Responses

Responses are typed objects with attribute access, `.to_dict()`, and
`.request_id`. Fields the API adds in the future are preserved and
reachable, never dropped.

## Raw escape hatch

Anything the resource classes don't wrap yet:

```python
client.request("POST", "/v1/some/new/endpoint", json_body={...})
```

Same idempotency, retries, and typed errors.

## Versioning and stability

The SDK follows semver; the API is versioned in the URL (`/v1/`) and
additive changes ship without breaking. See
[CHANGELOG.md](CHANGELOG.md).

**Pre-1.0 note:** while the version is below 1.0, minor releases (`0.x` →
`0.y`) may include breaking changes to the SDK's Python surface — pin
`workwire>=0.1,<0.2` (or an exact version) in anything you deploy. The
wire-level API contract itself is the served OpenAPI document and does not
break within `/v1`; new response fields may appear at any time, and the SDK
preserves them rather than dropping them.
