# Agent signup

An agent with no human present can get a working Hunter-Seeker credential in one unauthenticated
HTTP call, run the free samples immediately, and hand a claim link to a person whenever one shows
up. The same key keeps working after they claim it, and the autonomy ceiling never moves.

<!-- hs:gen:signup -->
One unauthenticated call, then a real ranking. No signup, no email, no card.

```bash
# 1 - mint a key. No Authorization header.
#     Returns { api_key, agent_id, claim_url, mode: "test" }.
curl -sS -X POST https://hunter-seeker.io/api/v1/agents/register \
  -H 'content-type: application/json' \
  -d '{"agent_caller":"my-agent"}'

# 2 - rank a free sample with the key you just got.
curl -sS -X POST https://hunter-seeker.io/api/v1/rank_topk \
  -H "Authorization: Bearer $HS_API_KEY" \
  -H 'content-type: application/json' \
  -d '{"data":{"dataset_id":"sample:saas_churn"},"entity_column":"customer_id","outcome_column":"churned","subject_kind":"org","page":{"k":5}}'
```

Expect `usability: "actionable"`, `top_decile_lift 4.444444`, and a `model_ref`.

The key runs the FREE SAMPLE DATASETS ONLY - it consumes no quota, cannot read anyone's data,
and is safe to print in a log. Hand `claim_url` to a human whenever you like: they sign in, and
the SAME key keeps working against a workspace they own, with every `model_ref` it produced
preserved. Claiming attaches a human to the TENANT, never to the
CREDENTIAL - so a machine key still carries no human principal, and decisions about PEOPLE stay
capped at `max_autonomy: "L0"` whether it is claimed or not. That ceiling is the point.
<!-- hs:end -->

## What the key can and cannot do

A `hsk_test_` key resolves the **free sample datasets only**. The restriction is enforced before
any upload URL is minted and before any byte of any dataset is read, so the key cannot be turned
into a data-exfiltration tool or a billing tool no matter what is sent to it. That is the reason it
is safe to issue anonymously, and the reason it is safe to paste into a public README.

Concretely, an unclaimed key can:

- run `sample:saas_churn`, `sample:b2b_leads`, `sample:telco_churn` and `sample:agent_traces`
- use every free interrogation tool against a `ranking_ref` it produced
- score a single entity against a `model_ref` it produced, and receive a signed Verdict
- verify any Verdict, which needs no credential at all

It cannot register a dataset of its own, cannot read another tenant's anything, and consumes no
quota — sample runs sit before the reserve, so nothing is taken and nothing is refunded.

## The ceiling, and why claiming does not move it

`max_autonomy` is how much rope a single decision earns, on an L0–L4 ladder. A **person-level**
decision — `subject_kind: "person"` — reaches **L2** only when a human principal is present, and is
capped at **L0** when there is not one.

A machine key has no principal. Not "an empty one": none. The Verdict schema has fields for a
service principal, and this endpoint deliberately does not populate them, because
`derive_autonomy` reads principal *presence* — so minting a plausible-looking one out of a key id
would silently lift every people-affecting decision from L0 to L2 on the strength of a shared
secret.

Claiming attaches a human to the **tenant**. It does not attach one to the **credential**. So:

| | `subject_kind: "person"` | `on_behalf_of` |
|---|---|---|
| OAuth, signed-in human | `max_autonomy: "L2"` | the user |
| Machine key, unclaimed | `max_autonomy: "L0"` | `null` |
| Machine key, **after claim** | `max_autonomy: "L0"` | `null` |

If you need L2 on a decision about a person, you need an OAuth session with a real human in it.
That is not a limitation to work around; it is the thing that makes the decision attributable to
someone an auditor can ask.

## Claiming

`claim_url` is a signed link, valid for 30 days, and it is not a credential — holding it lets you
do nothing until you sign in. When a human opens it and signs in:

- the **key string is byte-identical** afterwards. No re-issue, no rotation, nothing to redeploy.
  The agent that has been running for a week is not told anything.
- the **tenant id is unchanged**. It is one `UPDATE` setting the owner, not a migration, so every
  `model_ref` the key produced stays attached by construction.
- the **key stays a test key**. Mode lives in the key string itself, not in a database column, so a
  claim physically cannot promote a key that may be sitting in a public repo.

If the person signing in already owns a workspace, the flow **asks** which one to use rather than
merging into whichever they happen to be in. Two viable targets is a question, not a default.

## Rate limits

Registration is limited per source address. If you get a `429`, read `Retry-After` — and then
reconsider: **the key you already have does not expire.** Registering repeatedly is almost always a
sign that a key is being thrown away somewhere it should be stored.

## When you should not use this

If a human is available and will be present for the decisions, use OAuth instead — an agent acting
for a signed-in person gets a real principal and can reach L2 on person-level decisions. This
endpoint exists for the case where nobody is there.
