# Python SDK

The Arche Python SDK wraps the same public routes documented in the
[API reference](https://docs.arche.fi/reference) with typed models, pagination helpers, and
structured errors. It is the recommended integration path for production
workloads.

## Install

```bash
pip install arche-sdk
```

## Create a client

The client is a context manager. It holds a connection pool, so create one and
reuse it rather than constructing a client per request.

```python
import os

from arche_sdk import ArcheClient

with ArcheClient(api_key=os.environ["ARCHE_API_KEY"]) as client:
    company = client.companies.get("0000320193")
    print(company.entity_name)
```

- `api_key` (str | None): Sent as the `X-Api-Key` header. Create one in the
  [developer portal](https://app.arche.fi/keys).
- `base_url` (str): Defaults to `https://api.arche.fi`.
- `timeout_seconds` (float): Per-request timeout. Defaults to `30.0`.
- `http_client` (httpx.Client | None): Supply your own transport when you need custom retries, proxies, or
  connection limits.

> **Note:** Pass exactly one credential. Supplying both `api_key` and
> `bearer_token` raises an error rather than silently picking one.

## Resources

The client exposes four resource groups:

- `client.companies` (resource): Resolve tickers to canonical issuers, read company profiles, and check
  reporting completeness.
- `client.filings` (resource): List filings for a company, read one filing, and list the statements it
  contains.
- `client.statements` (resource): Statement versions, restatement timelines and deltas, reconciliation,
  data-quality overlays, derived metrics, and point-in-time financials.
- `client.narratives` (resource): Narrative disclosures attached to filings.

## The quickstart flow in Python

This mirrors the [Quickstart](https://docs.arche.fi/quickstart) step for step.

```python
import os

from arche_sdk import ArcheClient

with ArcheClient(api_key=os.environ["ARCHE_API_KEY"]) as client:
    # Step 2 - resolve a ticker to a canonical CIK
    resolved = client.companies.resolve("MSFT")

    # Step 3 - list statement versions for that issuer
    statements = client.statements.list(
        resolved.cik,
        statement_type="INCOME_STATEMENT",
        page=1,
        page_size=10,
    )
    print(statements.total, "statement versions")

    # Step 4 - ask what was knowable on a historical date
    point_in_time = client.statements.get_as_of_financials(
        "2024-03-31",
        "0000320193",
    )
    print(point_in_time.snapshot_hash)
```

Derived metrics come from the same resource:

```python
series = client.statements.get_derived_metrics_time_series(
    ciks=["0000320193"],
    statement_type="INCOME_STATEMENT",
    metrics=["GROSS_MARGIN"],
    frequency="annual",
)
print(len(series.points))
```

## Pagination

Collection methods return a typed `Page` with `page`, `page_size`, `total`,
`items`, and a `has_next` property.

```python
page = client.statements.list("0000320193", statement_type="INCOME_STATEMENT")

while True:
    for version in page.items:
        print(version.fiscal_year, version.fiscal_period)
    if not page.has_next:
        break
    page = client.statements.list(
        "0000320193",
        statement_type="INCOME_STATEMENT",
        page=page.page + 1,
    )
```

For straightforward full scans, `iter_all` handles the loop and yields items
across page boundaries:

```python
for version in client.statements.iter_all("0000320193", statement_type="INCOME_STATEMENT"):
    print(version.fiscal_year, version.fiscal_period)
```

See [Pagination](https://docs.arche.fi/pagination) for the underlying wire contract and
[Rate limits](https://docs.arche.fi/rate-limits) for guidance on how aggressively to iterate.

## Request IDs

The SDK sends an `X-Request-ID` header on every request, generating a UUID when
you do not supply one. Pass your own to correlate an API call with a job or task
in your own system:

```python
from arche_sdk import RequestOptions

company = client.companies.get(
    "0000320193",
    options=RequestOptions(request_id="nightly-refresh-2026-03-01"),
)
```

`RequestOptions` also accepts `headers` and `timeout_seconds` for per-request
overrides. See [Request IDs](https://docs.arche.fi/troubleshooting/request-ids) for how these values
are used when escalating a failure.

## Error handling

Failures raise typed exceptions. `APIError` is the base for any error the API
reported, and the subclasses map to HTTP status classes: `AuthenticationError`,
`PermissionDeniedError`, `NotFoundError`, `ValidationError`, `ConflictError`,
`RateLimitError`, and `ServerError`. Transport failures raise `ConnectionError`
or `TimeoutError` instead.

```python
from arche_sdk import ArcheClient, APIError, RateLimitError

try:
    with ArcheClient(api_key=os.environ["ARCHE_API_KEY"]) as client:
        client.companies.get("0000000000")
except RateLimitError:
    ...  # back off, then retry
except APIError as exc:
    print(exc.status_code, exc.code, exc.trace_id, exc.request_id)
```

Every `APIError` carries both identifiers: `request_id` is the value the SDK
sent, and `trace_id` is the one Arche returned in the
[error envelope](https://docs.arche.fi/errors). Log both.

## Next steps

- [Overview](https://docs.arche.fi/): Start here for the product model, first request path, and key entry points.
- [Quickstart](https://docs.arche.fi/quickstart): Authenticate, resolve a company, retrieve statement versions, and run an as_of query.
- [Authentication](https://docs.arche.fi/authentication): Send API keys correctly and handle authentication failures.
