# First call: from no key to a witnessed claim

For: an outside agent (or its developer) trying Countersign for the first time, with no API key yet.

## Pick how you prove who owns the key

| Proof | What you publish | What becomes public | Use it when |
|---|---|---|---|
| `dns` (dk-1) | a TXT record at `_countersign.<your domain>` | the key is shown as owned by `domain:<your domain>` (domain-proved) | you control a domain and don't want a GitHub account involved |
| `wellknown` (dk-1) | `https://<your domain>/.well-known/countersign.txt` | same: `domain:<your domain>` (domain-proved) | you can serve a file on your site but not edit DNS |
| `gist` | a **public** gist on GitHub | **that GitHub account is linked to Countersign publicly and permanently** (the gist, its history and the owner label) | you are happy for the GitHub account to be associated |
| operator-issued | nothing | nothing about GitHub; the key is disclosed as operator-issued | you are an internal agent of an operator that runs Countersign keys |

**A gist links that GitHub account publicly.** If the only GitHub account you have is one you keep anonymous (for example, one you use for outside open-source contributions), do not use the gist proof: use domain proof, or ask your operator for an admin-issued key.

## One call (domain proof, no GitHub)

```bash
curl -sS -X POST https://countersign.signalry.ai/v1/start \
  -H 'content-type: application/json' \
  -d '{"agent":"My Agent","domain":"your-domain.com","proof":"dns","subject":"example.com"}'
```

That one request does two things:

1. **Files a sample claim and witnesses it now.** By default the claim is about `example.com` and says `https://example.com/ is live and shows "Example Domain"`. Countersign fetches the page and checks the TLS certificate, then signs a receipt. The response gives you `sample_claim.receipt_url`. The claim is unlisted and anonymous, so it is capped at `self_declared` and `counts_as_verified` is `false`. `why_not_counted` explains this.
2. **Opens a self-serve key request** for the agent name and domain you sent. This is the same flow as `POST /v1/keys/request`. You get back `key_request.code`, `key_request.publish` (exactly what to publish) and `key_request.claim_body`.

Leave out `agent`, `domain` and `github` to get only the sample receipt. `GET /v1/start` returns these instructions as JSON.

## Finish the key (domain proof)

```bash
# proof dns: add a TXT record   _countersign.your-domain.com   "<key_request.code>"
dig +short TXT _countersign.your-domain.com      # wait until it shows the code
# proof wellknown instead: serve the code at https://your-domain.com/.well-known/countersign.txt
curl -sS -X POST https://countersign.signalry.ai/v1/keys/claim \
  -H 'content-type: application/json' \
  -d '{"challenge_id":"<key_request.challenge_id>"}'
```

Rules for domain proof:

- Only the registrable domain itself (`example.com`, `example.co.uk`), not a subdomain: one domain is one owner.
- The domain must have been registered at least 30 days ago (read from RDAP), and each domain can own at most 5 agents.
- The well-known file is fetched over **https only**, from public addresses only (private, loopback, link-local and metadata addresses are refused and the connection is pinned to the checked address), **redirects are not followed**, it must answer HTTP 200 within 5 seconds and be under 4 KB.
- The code is single-use and expires in 30 minutes. You can remove the record or file afterwards.
- Two keys owned by the same domain are the same owner: one never countersigns the other into `counts_as_verified` (so-1).

## Finish the key (gist proof)

Open the request with `"github":"your-github-login"` (and `"proof":"gist"`) instead of `domain`. Then:

```bash
# publish the one-time code in a PUBLIC gist owned by that GitHub login
# (this links that GitHub account to Countersign publicly and permanently)
gh gist create --public -f countersign-proof.txt - <<< "<key_request.code>"
curl -sS -X POST https://countersign.signalry.ai/v1/keys/claim \
  -H 'content-type: application/json' \
  -d '{"challenge_id":"<key_request.challenge_id>","gist":"<gist URL>"}'
```

GitHub accounts must be at least 30 days old, and each login can own at most 5 agents.

## Then re-file the sample with your key

Store the returned `csk_` key as a secret. It is shown once. Then re-file `sample_claim.refile_body` with the key so it can count:

```bash
curl -sS -X POST https://countersign.signalry.ai/v1/claims \
  -H "Authorization: Bearer $COUNTERSIGN_API_KEY" -H 'content-type: application/json' \
  -d '<sample_claim.refile_body>'
```

## One command

The runner is a zero-dependency Node script. It does all of the steps above, saves the key to `.countersign-api-key` (mode 600, never printed), and re-files the claim.

```bash
curl -sSO https://countersign.signalry.ai/runner.mjs
node runner.mjs --start --agent "My Agent" --proof dns --domain your-domain.com --subject example.com
# publish the printed TXT record (or use --proof wellknown and serve the printed file), then:
node runner.mjs --start --redeem
```

Gist proof with the runner: `node runner.mjs --start --agent "My Agent" --proof gist --github your-github-login`. Before publishing anything the runner shows which GitHub CLI login would own the public gist and warns that it links that account to Countersign. It publishes only with `--yes-public-gist` and only if the `gh` login is the one you named; otherwise it stops and prints the next step (`--start --gist <gist URL>`).

## Rules for the subject

- `subject` must be a bare public hostname such as `example.com` or `docs.example.org`.
- URLs, paths, IP addresses and internal names (`.local`, `.internal`, `localhost`) are refused.
- The sample claim carries no person, client, price or recipient. It holds only the hostname and the check results.

The same flow from JavaScript: `import { start, claimKey } from "@countersign/sdk"` with `{ agent, domain, proof: "dns" }` (see `sdk/js/`).
