Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.1.0
current_version = 1.0.0
commit = True
tag = True
tag_name = v{new_version}
Expand Down
18 changes: 18 additions & 0 deletions .conductor/settings.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"$schema" = "https://conductor.build/schemas/settings.repo.schema.json"

[scripts]
setup = "python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'"
run_mode = "concurrent"

[scripts.run.verify]
command = ".venv/bin/python scripts/fetch_contract.py && .venv/bin/ruff check . && .venv/bin/mypy src && .venv/bin/pytest -q && .venv/bin/python -m build"
icon = "check"
default = true

[scripts.run.sync-contract]
command = ".venv/bin/python scripts/fetch_contract.py --latest && .venv/bin/python -m pytest -q tests/test_openapi_coverage.py"
icon = "refresh-cw"

[scripts.run.release-check]
command = ".venv/bin/ruff check . && .venv/bin/mypy src && .venv/bin/pytest -q && .venv/bin/python -m build"
icon = "package-check"
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
python-version: "3.12"
- run: python -m pip install --upgrade pip
- run: pip install ".[dev]"
- run: python scripts/fetch_contract.py
- run: ruff check .
- run: mypy src
- run: pytest -q
Expand Down
36 changes: 36 additions & 0 deletions .github/workflows/sync-contract.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Sync public OpenAPI contract

on:
workflow_dispatch:
schedule:
- cron: "23 6 * * 1"

permissions:
contents: write
pull-requests: write

jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: python -m pip install --upgrade pip
- run: pip install ".[dev]"
- run: python scripts/fetch_contract.py --latest
- run: python -m pytest -q tests/test_openapi_coverage.py
- run: ruff check .
- run: mypy src
- run: pytest -q
- run: python -m build
- uses: peter-evans/create-pull-request@v7
with:
branch: automation/openapi-contract-sync
delete-branch: true
commit-message: "chore: sync public OpenAPI contract"
title: "chore: sync public OpenAPI contract"
body: |
This automated update was generated from the public Reconify OpenAPI
manifest. Review generated API models, examples, and migration notes before merging.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ __pycache__/
.mypy_cache/
.ruff_cache/
.venv/
.contract/
dist/
build/
*.egg-info/
4 changes: 4 additions & 0 deletions .openapi-contract.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"version": "1.0.0",
"sha256": "04170b7eda8a7bacef6591a7132b9a5cbdb62963af9ca6260e1948c1ee8233c1"
}
29 changes: 29 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Reconify Python SDK agent guide

This repository publishes reconify-python, the typed synchronous and
asynchronous client for the public Reconify /v1 API.

## Contract authority

The SaaS Go API owns the contract. Fetch the pinned public artifact with
python scripts/fetch_contract.py, or update to the manifest version with
python scripts/fetch_contract.py --latest.

For local SaaS work, set RECONIFY_OPENAPI_SPEC to an explicit OpenAPI JSON
file. Do not use sibling repositories or absolute workspace paths.

The resource and transport layers are handwritten for Python ergonomics.
tests/test_openapi_coverage.py is the contract boundary and must remain
spec-driven. Keep Pydantic models aligned with the downloaded schemas.

## Supported surface

The SDK exposes metadata, events, ingestion, issues, and organization
resources. Internal /business/v1 routes and the former ledger, wallet, setup,
search, alert, and reconciliation surface are not public SDK APIs.

## Verification

Run ruff check ., mypy src, pytest -q, and python -m build before releasing.
Keep credentials out of errors and logs, preserve request IDs, and update
examples and migration notes when the public contract changes.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 1.0.0

- Rebuilt the client for the current 13-operation public monitoring API.
- Added metadata, organization, issue notes, and issue-linked event resources.
- Removed the obsolete ledger, wallet, setup, search, alert, and reconciliation
surface.
- Added pinned public OpenAPI synchronization and contract coverage checks.

## 0.1.0

- Initial typed Reconify Python SDK.
Expand Down
227 changes: 45 additions & 182 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,215 +1,78 @@
# Reconify Python SDK

Typed synchronous and asynchronous clients for sending events to the Reconify
Public API.
Typed synchronous and asynchronous clients for the public Reconify v1 API.

## Installation
## Installation and quickstart

```bash
```sh
pip install reconify-python
```

## Quickstart: send an integrity event

```python
from datetime import datetime, timezone

from reconify import Reconify
from reconify.models import IngestEventsInputBody, PublicEvent

event = PublicEvent(
source_id="source_123",
source_event_id="checkout-evt-123",
event_type="payment.succeeded",
occurred_at=datetime.now(timezone.utc),
amount_minor=1250,
currency="USD",
wallet_id="wallet_123",
)

with Reconify(api_key="rk_...") as client:
result = client.ingestion.ingest_integrity_events(
IngestEventsInputBody(events=[event])
)
print(result.accepted, result.rejected)
```

The key may also be supplied through `RECONIFY_API_KEY`. The default endpoint
is `https://api.reconifyhq.com/v1`; pass
`base_url="https://staging.example/v1"` for staging or self-hosted
deployments. `/v1` is added when it is absent.

## Sending integrity events

The SDK has two direct integrity-event endpoints:

| Purpose | Method |
| --- | --- |
| Send production integrity events | `client.ingestion.ingest_integrity_events(...)` |
| Send test-session integrity events | `client.ingestion.ingest_integrity_test_events(...)` |

Both accept an `IngestEventsInputBody` containing typed `PublicEvent` models.
Python model fields use `snake_case`; the SDK serializes them to the API’s
wire format.

### Send a batch and inspect partial results

```python
from datetime import datetime, timezone

from reconify import Reconify
from reconify.models import IngestEventsInputBody, PublicEvent

batch = IngestEventsInputBody(
events=[
PublicEvent(
source_id="source_123",
source_event_id="payment-evt-123",
event_type="payment.succeeded",
occurred_at=datetime(2026, 1, 31, 12, 0, tzinfo=timezone.utc),
amount_minor=1250,
currency="USD",
wallet_id="wallet_123",
external_reference="order-123",
metadata={"channel": "web"},
),
PublicEvent(
source_id="source_123",
source_event_id="refund-evt-456",
event_type="payment.refunded",
occurred_at=datetime(2026, 1, 31, 12, 5, tzinfo=timezone.utc),
amount_minor=-250,
currency="USD",
wallet_id="wallet_123",
external_reference="order-123",
),
]
)

with Reconify() as client:
result = client.ingestion.ingest_integrity_events(batch)

for accepted in result.accepted or []:
print("accepted", accepted.index, accepted.source_event_id)
for rejected in result.rejected or []:
print("rejected", rejected.index, rejected.code, rejected.reason)
```

`source_event_id` identifies the source event. Keep it stable when retrying the
same event. The API returns accepted and rejected rows independently, so a
batch can contain both successful and rejected events.

### Send test-session events

Test-session events use the same event models. Pass the test-session token as
`integrity_test_session`; the SDK sends it as
`X-Integrity-Test-Session`.

```python
with Reconify() as client:
result = client.ingestion.ingest_integrity_test_events(
batch,
integrity_test_session="test-session-token",
)
print(result.accepted, result.rejected)
events = client.events.list_events(limit=25)
for event in events.events:
print(event.id, event.status)
```

### Submit events to a setup test session

After a test session has been created, submit its events with the setup
endpoint. This is also a sending operation and accepts the same `PublicEvent`
models.
The API key may also come from `RECONIFY_API_KEY`. The default endpoint is
`https://api.reconifyhq.com/v1`. `RECONIFY_API_URL` or `base_url` can select a
staging or self-hosted endpoint, and URLs with or without `/v1` are accepted.

```python
from reconify import Reconify
from reconify.models import SetupSubmitSessionInputBody

with Reconify() as client:
result = client.setup.submit_test_session_events(
"session_123",
SetupSubmitSessionInputBody(events=batch.events),
integrity_test_session="test-session-token",
)
print(result.accepted, result.rejected)
```
## Public resources

## Batch limits and validation
The client exposes metadata, events, ingestion, issues, and organization. The
current public contract contains exactly 13 operations. Python methods use
`snake_case` names and typed Pydantic v2 models from `reconify.models`.

- Integrity event batches contain 1–500 events.
- Integrity event requests must not exceed 5 MiB.
- Invalid model fields and batch sizes are rejected before an HTTP request is
sent.
- Event payloads can include `amount_minor`, `currency`, `wallet_id`,
`external_reference`, `provider_reference`, `operation_id`, and `metadata`.

## Async sending

`AsyncReconify` exposes the same sending operations. Use `async with` to close
the underlying HTTP client automatically.
Sync and async clients provide cursor iterators:

```python
import asyncio

from reconify import AsyncReconify


async def send_events() -> None:
async with AsyncReconify() as client:
result = await client.ingestion.ingest_integrity_events(batch)
print(result.accepted, result.rejected)


asyncio.run(send_events())
async with AsyncReconify() as client:
async for event in client.iter_events(limit=100):
print(event.id)
```

Async operations support normal `asyncio` cancellation.

## Retries, timeouts, and failures
Every operation supports `raw=True` for `RawResponse` and per-request timeout
through the `timeout` keyword. API errors expose status, detail, code,
validation details, response headers, and request ID without including keys or
request bodies. Safe methods retry bounded `429`, `503`, and transport failures
by default; unsafe retries require `RetryConfig(retry_unsafe_methods=True)`.

Mutating requests are not retried by default. If a sending workflow can safely
replay the same stable event IDs, opt into unsafe retries explicitly.
## Contract synchronization

```python
from reconify import Reconify
from reconify.errors import ReconifyRequestError
from reconify.transport import RetryConfig

with Reconify(
request_id="trace-123",
retry=RetryConfig(max_retries=2, retry_unsafe_methods=True),
) as client:
try:
result = client.ingestion.ingest_integrity_events(batch, timeout=10)
except ReconifyRequestError as exc:
print(exc.status_code, exc.detail, exc.request_id)
```sh
python scripts/fetch_contract.py
python scripts/fetch_contract.py --latest
pytest -q tests/test_openapi_coverage.py
```

HTTP failures raise typed `ReconifyError` subclasses. Each error exposes the
status code, detail, error code, validation details, response headers, and
request ID without including credentials or request bodies.

Use `raw=True` when the sending workflow needs the status, headers, request ID,
and unparsed response body:

```python
with Reconify() as client:
response = client.ingestion.ingest_integrity_events(batch, raw=True)
print(response.status_code, response.request_id)
print(response.json())
```
The default source is the public manifest at
<https://docs.reconifyhq.com/openapi/manifest.json>. For local SaaS changes,
set `RECONIFY_OPENAPI_SPEC` to an explicit OpenAPI JSON file. The SDK never
depends on another checkout or an absolute workspace path.

The client default timeout is 30 seconds. Individual sending operations can
override it with `timeout=...`, including an `httpx.Timeout` object.
## Migration to 1.0.0

## Build and deploy
Version `1.0.0` targets the current monitoring and issue-investigation API. The
former ledger, wallet, setup, search, alert, and reconciliation methods are
removed because they are not part of the public contract. See
[UPGRADING.md](UPGRADING.md).

Build the distributable artifacts locally or in CI:
## Build and release

```bash
python -m pip install build
```sh
ruff check .
mypy src
pytest -q
python -m build
```

The resulting wheel and source archive in `dist/` are ready for publication to
an internal or public Python package registry. CI builds both artifacts after
running lint, type checking, and tests.
The release workflow publishes the built wheel to PyPI after a GitHub release.
Additive contract changes require a minor SDK release; SDK fixes require a
patch release; breaking public API changes require a new API version and SDK
major release.
Loading
Loading