Metadata-Version: 2.5
Name: plainrouter
Version: 0.10.0
Summary: Python SDK and command-line interface for the signed Plainrouter OpenAPI contract, supporting Meta conversion tracking and governed ad changes.
Project-URL: Homepage, https://plainrouter.com
Project-URL: Documentation, https://plainrouter.com/docs/sdk/python
Project-URL: Repository, https://github.com/plainrouter/sdk
Author: Plainrouter
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: conversions-api,facebook-ads,mcp,meta-ads,openapi,plainrouter,sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.11
Requires-Dist: attrs>=22.2.0
Requires-Dist: httpx<0.29.0,>=0.23.1
Description-Content-Type: text/markdown

# Plainrouter Python SDK and CLI

Generated from Plainrouter's signed OpenAPI contract. The package is in 0.x and its API may change between minor releases. No support guarantees are made yet.

Official project: [plainrouter.com](https://plainrouter.com) · [documentation](https://plainrouter.com/docs/sdk/python) · [source](https://github.com/plainrouter/sdk/tree/main/packages/python)

Report Python SDK issues in the [Plainrouter SDK issue tracker](https://github.com/plainrouter/sdk/issues).

## Install

```bash
python -m pip install plainrouter
```

Python 3.11 or newer is required.

For command-line use, install the same package in an isolated environment:

```bash
pipx install plainrouter
plainrouter --help
```

You can also run the CLI from an active Python environment with
`python -m plainrouter`. The PyPI CLI provides the same command tree, config
path, environment variables, JSON output, and safety prompts as
[`@plainrouter/cli`](https://www.npmjs.com/package/@plainrouter/cli).

## Use

```python
from plainrouter import create_client, list_events

client = create_client("your-signal-tracker-secret")
response = list_events.sync(client=client)
```

Async operations are available through each operation module:

```python
from plainrouter import create_client, get_event

client = create_client("your-signal-tracker-secret")
response = await get_event.asyncio(event="event-id", client=client)
```

The default API base is `https://plainrouter.com/api/v1` and the default request timeout is 30 seconds. Pass `base_url` or an `httpx.Timeout` to `create_client` to override either setting. Credentials are supplied by the caller and are never embedded in the SDK.

Copy a failed plan to a fresh draft using a plan-writer bearer token:

```python
import os
from plainrouter import create_client, launch_plans_copy

client = create_client(os.environ["PLAINROUTER_TOKEN"])
response = launch_plans_copy.sync_detailed(1, "failed-plan-id", client=client)
```

The same operation provides `asyncio` and `asyncio_detailed` entry points. The
public `plainrouter.launch_plans_copy.PlanCopyResponse` type alias describes the
parsed result union, including `ProposalUnavailable` and `WorkspaceLockTimeout`.
The detailed entry points return `Response[PlanCopyResponse]`; `sync` and
`asyncio` return `PlanCopyResponse | None`.

## Retryable proposal errors

Pass the detailed response or raw HTTPX response to the helper, including for
async calls and endpoints with no documented 503. It recognizes only 503 error
envelopes with a string message and either `workspace_lock_timeout` or
`proposal_not_recorded`; other bodies return `False`.

```python
import json
from plainrouter import is_retryable_proposal_error

retryable = is_retryable_proposal_error(response.status_code, response)
error_code = json.loads(response.content)["error"]["code"] if retryable else None
retry_after = response.headers.get("Retry-After") if retryable else None
```

This example also distinguishes lock timeout (`error_code ==
"workspace_lock_timeout"`) without relying on `response.parsed`. If generated
parsing raises before returning a detailed response, capture the raw response
with an HTTPX response hook and call `response.read()` there; in an async hook,
await `response.aread()`. Run the same example on that captured response. Use
the public copy facade rather than calling generated copy directly.

No automatic retry is added. Honor `Retry-After` when present (delta seconds or
an HTTP date) when scheduling a retry.

Licensed under Apache-2.0.

Use `create_client()` or the hand-written `plainrouter.AuthenticatedClient` / `plainrouter.Client` for JSON negotiation and redirect protection. Headers default to `Accept: application/json`; override with `client.with_headers({"Accept": "..."})` or constructor `headers`. A 3xx raises `httpx.HTTPStatusError` before parsing or following, even with `follow_redirects=True`; inspect `error.response.status_code` and `error.response.headers["Location"]`. The separate generated namespace provides raw clients without this policy.

Header names merge case-insensitively, including repeated `with_headers` calls
and clients derived after first use. In-place updates to a live client also merge
names case-insensitively: if a dict names Accept or Authorization in multiple
letter cases, the last spelling in caller order wins and sends exactly one line.
On a token client, the client `with_headers` returns always sends the SDK's own
token as Authorization; the live client and an injected client send the caller's
last spelling.

Injected HTTPX clients receive JSON Accept
when it is absent or exactly `*/*` (HTTPX's automatic default is indistinguishable
from an explicit wildcard); any other configured value is retained. To request
an explicit wildcard after injection, use `client.with_headers({"Accept": "*/*"})`.

`set_httpx_client` and `set_async_httpx_client` set Accept on the HTTPX client
object you pass in, so other code sharing that object sees the change.
