Metadata-Version: 2.4
Name: ysaere
Version: 0.1.0
Summary: Official Python SDK for Ysaere — traceable swarm intelligence with provenance on every claim.
Project-URL: Homepage, https://ysaere.com/builders/
Project-URL: Source, https://github.com/ysaere/sdk-python
Project-URL: Issues, https://github.com/ysaere/sdk-python/issues
Project-URL: API keys, https://app.ysaere.com/developer/keys
Project-URL: OpenAPI spec, https://api.ysaere.com/v1/openapi.json
Project-URL: Trust & provenance, https://ysaere.com/trust/
Author-email: "Ysaere, Inc." <hello@ysaere.com>
License-Expression: MIT
License-File: LICENSE
Keywords: agents,ai,due-diligence,intelligence,llm,mcp,provenance,ysaere
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
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: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1.0,>=0.24
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == 'dev'
Description-Content-Type: text/markdown

# ysaere

Official Python SDK for **[Ysaere](https://ysaere.com)** — multi-agent research
swarms that return a report *and* the evidence chain behind it.

Every completed run carries a Trust Receipt: a hash chain over each agent's
inputs and outputs, signed with both Ed25519 and ML-DSA-65 (FIPS 204,
post-quantum) and checkable by anyone against the public keys at
`https://api.ysaere.com/.well-known/trust-keys` — without trusting our answer.

```bash
pip install ysaere
```

## Six lines

```python
from ysaere import Ysaere

y = Ysaere()                                        # reads YSAERE_API_KEY
report = y.run_and_wait("dd-report", "Acme Corp")   # submit, then poll
print(report.text)                                  # markdown
print(report.sources)                               # what it actually read
print(report.content_hashes)                        # section -> v2:sha512:...
```

Get a key at <https://app.ysaere.com/developer/keys>. New accounts start with 150
free credits — enough to run Angel Intelligence (50) and Angel Deep (90) before
spending anything.

## Two shapes of call

**Swarm runs** take minutes and return a `run_id` you poll:

```python
run = y.company_intelligence("Acme Corp")   # 202, queued
print(run.credits_consumed, run.balance_remaining)
report = y.wait(run)                        # blocks until terminal
```

**Sync micro-SKUs** answer in one response and return a plain dict — there is no
`run_id` and nothing to poll:

```python
y.classify("Acme Corp")                                   # 10 credits
y.quick_brief("Acme Corp", focus="key risks")             # 20
y.sourced_brief("Acme Corp", sources=["web", "vault"])    # 40, with citations
y.vault_search("cap table")                               #  5, your own files
```

`run()` refuses a sync endpoint outright rather than handing back an empty `Run`.

## Catalog

| Method | Endpoint | Credits |
|---|---|---|
| `vault_search` | `vault-search` | 5 |
| `classify` | `classify` | 10 |
| `quick_brief` | `quick-brief` | 20 |
| `retail_insights` | `retail-insights` | 25 |
| `sourced_brief` | `sourced-brief` | 40 |
| `angel_intelligence` | `angel-report` | 50 |
| `security_assessment` | `security-scan` | 75 |
| `retail_insights_deep` | `retail-insights-deep` | 75 |
| `angel_deep` | `angel-deep-report` | 90 |
| `company_intelligence` | `ci-report` | 100 |
| `market_intelligence` | `market-report` | 150 |
| `due_diligence` | `dd-report` | 200 |

This is the same surface `@ysaere/sdk` and `@ysaere/cli` expose — one product
definition across all three clients. The REST API serves more than this; reach
anything else through `y.request(...)`.

Pricing is answerable offline — the catalog ships with the package:

```python
from ysaere import credits_for

credits_for("dd-report")      # 200
credits_for("dd")             # 200  — short alias
credits_for("due_diligence")  # 200  — MCP tool name
```

`y.estimate("dd-report")` asks the server instead. Neither debits anything.

## Errors you can act on

```python
from ysaere import InsufficientCreditsError, X402PaymentRequiredError

try:
    report = y.run_and_wait("dd-report", "Acme Corp")
except InsufficientCreditsError as e:
    print(e.required, e.balance, e.topup_url)   # enough to recover automatically
except X402PaymentRequiredError as e:
    print(e.pay_to, e.max_amount_required)      # pay on-rail with your own signer
```

The SDK never signs an x402 challenge for you. Ysaere does not custody keys and
neither does this client: you get the challenge, you decide, you retry with your
own `X-PAYMENT` header via `y.request(..., extra_headers={...})`.

Also raised: `AuthenticationError` (401), `PermissionError_` (403),
`NotFoundError` (404), `RateLimitError` (429), `NotImplementedYetError` (501 —
published but not yet wired, never retried), `ServerError` (5xx),
`ValidationError` (400), `TimeoutError_`, and `UsageError` for a call that was
wrong before it left the process. `PaymentRequiredError` is the base of both
402 shapes, if you want to catch them together.

## Waiting

`wait()` defaults to a 1,800-second budget. That number is measured, not
advertised: Company Intelligence runs about 1,185 seconds and Market
Intelligence about 2,119. If the budget expires you get `TimeoutError_` carrying
the `run_id` — the run keeps going and nothing is lost:

```python
try:
    report = y.wait(run, timeout=300)
except TimeoutError_ as e:
    report = y.get_report(e.run_id)   # later, from anywhere
```

Or skip polling entirely:

```python
y.create_webhook("https://you.example/hook", events=["report.completed"])
```

Deliveries are HMAC-signed in `X-Ysaere-Signature`, with `X-Ysaere-Event` and
`X-Ysaere-Delivery` alongside for idempotent handling.

## Retries, and where they stop

Replayable requests retry `429/500/502/503/504` with exponential backoff,
honouring `Retry-After`. Swarm submits carry an auto-generated `Idempotency-Key`,
so a retried submit replays the original acceptance instead of debiting twice.
Pass your own with `idempotency_key=`.

Sync micro-SKUs are deliberately **not** retried on 5xx or on a network timeout.
They debit on success and the server does not dedupe them, so a replay could
charge twice for work that already happened. A 429 — a rejection before any work
— still retries.

`501` is never retried. A published-but-unwired endpoint answers the same way
forever.

## Async

```python
import asyncio
from ysaere import AsyncYsaere

async def main():
    async with AsyncYsaere() as y:
        run = await y.due_diligence("Acme Corp")
        report = await y.wait(run)
        print(report.text)

asyncio.run(main())
```

Identical surface, every method awaitable.

## Traceability

```python
report.provenance["verified"]    # chain resolved
report.provenance["agents"]      # which agents contributed
report.content_hashes            # section_key -> v2:sha512:...
report.sources                   # every source reference

y.provenance(run.id)             # the full chain
y.trust_receipt(run.id)          # the signed receipt
y.verify(signature)              # public — resolves with no key at all
```

Citations to your uploaded documents carry a chunk index and a content hash of
the exact excerpt the model read, not a page number. A page number says roughly
where to look; a content hash proves exactly what was read, and cannot be
invented.

Signing proves **integrity, not correctness** — that an output came from the
stated process over the stated sources and has not been altered since.

## Nothing is unreachable

Any `/v1` path is one call away, with the same auth, retry and error mapping:

```python
y.request("GET", "/marketplace/agents")
y.request("POST", "/intel/ci-report", json={"target": "Acme"},
          idempotency_key="my-own-key")
```

## Configuration

| | |
|---|---|
| `YSAERE_API_KEY` | API key. Or pass `api_key=`. |
| `YSAERE_BASE_URL` | Defaults to `https://api.ysaere.com/v1`. |

```python
Ysaere(api_key=..., base_url=..., timeout=60.0, max_retries=3)
```

Keys come in two kinds. A test key (`ysa_test_*`) runs in sandbox and debits
nothing; a production key (`ysa_prod_*`) does the real work. A key is optional at
construction — `verify()` and `pricing()` are public — and an authenticated call
without one raises `UsageError` immediately rather than spending a round-trip to
earn a 401.

Requires Python 3.9+. The only dependency is `httpx`.

## Also available

TypeScript SDK `@ysaere/sdk` · CLI `@ysaere/cli` · MCP server at
`https://mcp.ysaere.com/mcp` · OpenAPI at
<https://api.ysaere.com/v1/openapi.json>

---

MIT © Ysaere, Inc.
