Metadata-Version: 2.4
Name: countersignatory
Version: 0.2.0
Summary: The going rate for a human minute. A standard-library client for the Countersignatory quote API, Spot Index and orders.
Project-URL: Homepage, https://countersignatory.com
Project-URL: Methodology, https://countersignatory.com/methodology
Author-email: Countersignatory Ltd <douglas@countersignatory.com>
License-Expression: MIT
License-File: LICENSE
Keywords: ai-agents,countersignatory,human-in-the-loop,pricing,spot-index,verification
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# countersignatory

The going rate for a human minute. A Python client for the Countersignatory quote API, Spot Index and orders. Standard library only, Python 3.9 and later.

**Most prices are indicative.** A quote is not a binding offer. The markets the index marks `live` can be bought with an order, paid for, and answered by hand by a named group; everywhere else no task is taken and no money moves.

## Quickstart

```python
import countersignatory as cs

q = cs.quote(task="judgment", tier="consensus", n=3, sla=3600, max_price=5)
print(q.unit_price, q.band, q.would_clear, q.quote_id)

cs.interest(q, would_pay=True, note="labelling 200 eval rows a week")  # this price would clear
cs.index()                                                           # the Spot Index
cs.market("countersign.certified_translation")                       # one market from it
cs.interest(market="countersign.certified_translation", would_pay=True)  # vote to open a market
```

Arguments map one-to-one onto `POST /v1/quotes`: `task` is `task_type`, `sla` is `sla_seconds`, `n` is `consensus_n`, `market` is `market_id`, and `max_price`, `jurisdiction`, `unassisted` and `criteria` keep their names. The quote comes back exactly as the API sent it: every field is an attribute, `q.derived` carries what is derived rather than measured, and `q.raw` is the whole response. Nothing is rounded.

## Buy a Check

A market whose `state` is `live` on the index can be bought. `check.general` is one screened human, $1.25 at a 24 hour deadline, answered by hand or refunded in full.

```python
import os, countersignatory as cs
from eth_account import Account  # optional: pip install "x402[evm,requests]"

o = cs.order(
    market="check.general", template="approval", sla=86400, max_price=1.25,
    input={"action": "Send the refund of $40 to customer 1182", "context": "The item arrived broken. Policy allows refunds under $50."},
)                                                               # unpaid: nothing is charged yet
paid = cs.pay_x402(o, Account.from_key(os.environ["BUYER_PRIVATE_KEY"]))  # 1.25 USDC on Base, and no more
done = cs.wait_for_answer(o["id"], timeout=3600)                # polls every 15 seconds
print(done["answer"]["value"], done["answer"]["reason"], done["answer"]["responder"]["disclosed_as"], paid["transaction"])
```

`cs.order()` never pays by itself. It returns the order with both ways to pay under `payment_options`: `["x402"]["pay_url"]` for an agent with a wallet, and `["card"]["checkout_url"]` for a person. Pay within 30 minutes, by `cs.pay_x402()` or by handing `checkout_url` to your human.

`cs.pay_x402(order, signer)` is the only call that moves money, and it runs only when you call it. It needs the [`x402`](https://pypi.org/project/x402/) package, which this package does not depend on and which needs Python 3.10 or later: `pip install "x402[evm,requests]"`. On Python 3.9, or without it, pay with `checkout_url`. The signer is an `eth_account` `LocalAccount`. The key stays in your process, and the most it will sign for is the order's own price. The buyer needs USDC on Base and no ETH, because the facilitator pays the gas. It returns `{"order", "transaction", "settlement"}`. A payment the server does not accept raises `payment_not_accepted` and the order stays unpaid. If you sign an `X-PAYMENT` header some other way, pass it to `cs.order()` as `payment=` instead.

`cs.wait_for_answer(order_id, timeout=3600, interval=15)` reads the order until it is `answered`, `refused`, `refunded` or `expired`, and returns it. Both are in seconds. If the timeout passes first it raises `wait_timeout` with the last order read as `.body`; the order keeps its own deadline. `cs.get_order(order_id)` reads it once. Neither needs a key: the order id is the credential.

### Limits while the founder answers alone

While a live market's only responders are founders, and the index says so under `fulfilment.limits`:

- `sla` must be at least 3600 seconds.
- An order placed between 23:00 and 07:00 Europe/London needs a deadline at or after 08:00 Europe/London.

An order outside those limits is a 422 with the reason, raised as an error. Nothing is charged and no order is made.

### Refused at intake

Refused with a 422, `refused_at_intake`, before any payment:

- solving CAPTCHAs or passing any bot check
- logging in to anything, or entering credentials
- contacting third parties
- medical, legal or financial advice
- anything sexual, or involving minors

A responder may also refuse after intake, which refunds you in full. Templates are `approval`, `verification` and `judgment`; [llms.txt](https://countersignatory.com/llms.txt) gives the input each one takes and a worked x402 example.

## Keys

A key is free, instant and needs no approval. The client looks for one in this order:

1. `key=` on the call.
2. `COUNTERSIGNATORY_KEY` in the environment.
3. The cached file `~/.config/countersignatory/key` (`%APPDATA%\countersignatory\key` on Windows), mode 0600.
4. In CI (`CI`, `GITHUB_ACTIONS`, `GITLAB_CI`, `BUILDKITE`, `CIRCLECI` or `JENKINS_URL` set), it stops with `KeyRequired`. It never issues a key in CI, because every ephemeral runner would mint a new one. Set `COUNTERSIGNATORY_KEY` in your CI secrets.
5. Otherwise it takes a key with `POST /v1/keys`, caches it and prints one line to stderr saying where.

`index()` and `market()` need no key.

## Errors

Nothing is retried and nothing is substituted. An error from the API is raised as `CountersignatoryError` with `.code`, `.message`, `.status` and `.body`. Quoting a market that is listed but not open raises `MarketNotOpen`, whose message carries the one line that registers interest in it. A key that is not in the shape the API issues raises `InvalidKey`.

## What it sends

Each request carries `x-countersignatory-client: python/0.2.0`, so calls from this client can be counted as a channel. That header is the only telemetry. Nothing is sent on import.

Set `COUNTERSIGNATORY_BASE_URL` to point the client somewhere other than `https://countersignatory.com`.

## Markets

<!-- prices:start -->
Indicative prices from the Spot Index, index version `seed-2026-10-04`, read when this package was built. They are seeded from third-party comparables, not live clears, and do not update with the package. Call `index()` for the current figures and `/methodology` for how each one is derived.

| Market | Tier | State | Indicative base (USD) | Band | Unit |
|---|---|---|---|---|---|
| `check.general` | check | open | $1.25 | $0.80 to $2.00 | per task |
| `consensus.3.general` | consensus | open | $4.70 | $2.60 to $5.00 | per task |
| `consensus.5.general` | consensus | open | $5.60 | $4.20 to $8.00 | per task |
| `countersign.domain_expert` | countersign | open | $18.00 | $12.00 to $30.00 | per attestation |
| `countersign.uk_professional` | countersign | open | $25.00 | $15.00 to $40.00 | per attestation |
| `countersign.accessibility_statement` | countersign | opening | $21.00 | $14.00 to $28.00 | per criterion (MED shown) |
| `countersign.certified_translation` | countersign | opening | $16.00 | $12.00 to $24.00 | per page (250 words) |
| `seal.notarisation_us` | seal | open | $45.00 | $38.00 to $60.00 | per seal |

A market in the `opening` state is listed with a reference rate but takes no quotes yet. Quoting it raises `market_not_open`; registering interest is the vote that opens it. A market the seed marks for sale is served as `live` only while a payment rail and its responders stand behind it, and as `open` otherwise, so call `index()` for the state now.
<!-- prices:end -->

Methodology: https://countersignatory.com/methodology

MIT licence. Countersignatory Ltd.
