Metadata-Version: 2.4
Name: tidefold-client
Version: 0.0.228
Summary: Python client for starting and following tidefold workflow runs
Author: tidefold
License-Expression: Apache-2.0
License-File: LICENSE
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Classifier: Typing :: Typed
Requires-Dist: httpx>=0.27.0,<1.0.0
Requires-Python: >=3.13, <4.0
Description-Content-Type: text/markdown

# tidefold-client

A small Python client for starting workflow runs on a tidefold deployment,
waiting for them, and reading what they produced.

```sh
pip install "tidefold-client==<your platform version>"
```

Install the version that matches your deployment's platform release. The
client warns when it talks to a server on a different release. There is no
compatibility promise across versions.

## Quick start

```python
import asyncio

from tidefold_client import AsyncClient


async def main() -> None:
    async with AsyncClient.from_env() as client:  # TIDEFOLD_API_URL, TIDEFOLD_API_TOKEN
        run = await client.workflows.start(
            "invoice_review",
            inputs={"reference": "INV-1042"},
            files={"document": "invoice.pdf"},
            subject="INV-1042",
        )
        await run.wait(timeout=1800)
        print((await run.results())["marked_results"])


asyncio.run(main())
```

`TIDEFOLD_API_URL` is the API root: `https://<your host>/api` on a
deployment, `http://localhost:8000` on a local stack. There is no default.

## The API token

An administrator creates tokens under **Settings → API tokens**. A token is
shown once; keep it in a secret store and pass it through the environment.
Grant only the scopes your script needs:

| Scope | Needed for |
| --- | --- |
| `workflow_read` | `workflows.list()`, `workflows.get_id()`, `workflows.start()` by name |
| `run_create` | `workflows.start()` |
| `run_file_write` | `files.upload()`, `files.upload_bytes()`, `workflows.start()` with `files` |
| `run_read` | `runs.get()`, `runs.wait()`, `runs.results()`, `runs.usage()`, `files.download()`, `files.download_bytes()` |
| `run_cancel` | `runs.cancel()` |
| `inbox_read` | `runs.requests()` |

"Own data only" visibility suits scripts: the token then sees only the runs
it started. A local stack running without auth takes no token.

## The subclients

The client has one subclient per resource, all sharing one session. One
client may be shared by concurrent tasks. `async with` closes it, and so
does `await client.aclose()`. `http=` takes your own `httpx.AsyncClient`,
which the client never closes.

**`client.workflows`**

- `start(name, *, inputs, files, file_ids, subject, correlation_id)`
  uploads each file in `files` and starts a run on the workflow's latest
  published release (`channel="draft"` runs the current draft). It
  returns a `Run` at once. `workflow_id=` starts by id instead of name.
- `file_ids` takes files uploaded earlier, by id, in the same shape as
  `files`: one id or a list per input. A start with `file_ids` only
  uploads nothing, so uploading and starting can be separate steps. An
  input goes in `files` or `file_ids`, not both.
- `list()` returns the workflows the token can see; `get_id(name)` resolves
  a name to its stable id.

**`client.runs`**, each taking a run id

- `get()` reads the run as it stands.
- `wait()` polls until the run ends. It returns the run on `COMPLETED` and
  raises `RunFailed`, `RunCancelled` or `WaitTimeout`. A server restarting
  during a deploy is waited out. Cancelling the waiting task, like a
  timeout, leaves the run going on the server.
- `results()` returns `results` per step and `marked_results`, the outputs
  the workflow declares. `usage()`, `requests()` and `cancel()` do what
  their names say.

**`client.files`**

- `upload_bytes(name, content, *, mime_type=None)` declares the file,
  uploads it to the signed URL (never with the token) and finalizes it;
  it returns the file's record, whose `id` goes into `file_ids`. Without
  `mime_type`, the type is guessed from `name`.
- `upload(path)` does the same for a file on disk.
- `download(file_id, path)` streams a file, such as a produced document,
  to disk; `download_bytes(file_id)` returns its content.

A `Run` is `client.runs` with the id filled in: `await run.wait()`,
`await run.results()` and so on. `client.run(run_id)` gives one for a run
started earlier.

**Starting is not idempotent.** The client never retries `start()`, and
calling it twice starts two runs, even with the same `correlation_id`.

## Errors

Every exception derives from `TidefoldError`.

| Exception | Meaning |
| --- | --- |
| `AuthError` | 401: token missing, wrong, expired or revoked |
| `PermissionDenied` | 403: the token lacks the scope named in the message |
| `NotFound` | 404: no such run or file; for a workflow, also a category the token cannot see |
| `Conflict` | 409: workflow never published, or results read while the run is still running |
| `InvalidRequest` | 400, 413, 422: a missing input, an oversized or unsupported file |
| `UploadError` | a file did not upload; no run was started |
| `RunFailed`, `RunCancelled`, `WaitTimeout` | raised by `wait()` |
| `NetworkError` | the server could not be reached after retries |

## License

Apache-2.0.
