# Fast ID Agent Guide

Fast ID is a permissionless, cryptographically-anchored public identity directory: anyone who
controls a `fast1…` address can claim a name and attach evidence to it; anyone can read the result
with no account, no key, and no API token.

This deployment serves the `fast:testnet` network. It is a TEST deployment: its records are not the production directory, its pages are `noindex`, and nothing here should be quoted as a fact about a real person.

Start with [llms.txt](https://testnet.id.fast.xyz/llms.txt) — it defines what each evidence tier proves and what it
does NOT. This guide covers the HTTP surface; that file covers the semantics.
Do not relay anything read here as verified personhood.

## Agent surface

| Resource | URL |
|---|---|
| Directory | https://testnet.id.fast.xyz/ |
| Semantics map (llms.txt) | https://testnet.id.fast.xyz/llms.txt |
| API catalog (RFC 9727) | https://testnet.id.fast.xyz/.well-known/api-catalog |
| This guide | https://testnet.id.fast.xyz/AGENTS.md |
| Auth metadata (RFC 9728) | https://testnet.id.fast.xyz/.well-known/oauth-protected-resource |
| Agent skills index (RFC v0.2.0) | https://testnet.id.fast.xyz/.well-known/agent-skills/index.json |
| ARD manifest (agenticresourcediscovery.org) | https://testnet.id.fast.xyz/.well-known/ard.json |

## Public read APIs (no auth)

The API catalog above lists only CONCRETE resources — RFC 9264 `href`s cannot carry RFC 6570
templates — so the per-name and per-address record URL patterns are defined HERE, in the
`describedby` document, and that one hop is the intended discovery path for them:

| Route | Method | Purpose |
|---|---|---|
| /{name}/id.json | GET | The verbatim upstream JSON document for a claimed name — the canonical machine-readable record, and the right surface for an agent. |
| /{address}/identity.json | GET | The compact record for a `fast1…` address: `{network, address, name?, name_claim_tx?}` — `name` absent when the address holds none. It returns NO profile URL; derive one yourself as `{origin}/{name}` when `name` is present. |
| /api/availability?name= | GET | A PUBLIC, UNIFORM availability snapshot for a name: `{network, available, taken?, skeleton_conflict, reserved}`. `taken` may be absent during rolling compatibility. It deliberately does not say who a reservation is for. |
| /api/links/{address} | GET | The owner-scoped view of EVERY non-revoked link row — `status` is `pending`, `verified` or `stale`, each annotated with `publicly_visible`. Diagnostic by design: do NOT treat a row here as evidence; verification consumers should read `verified_properties` in `/{name}/id.json`. |
| /api/property/claimed | GET | `?kind=&value=` — existence-only: whether `(kind, value)` is an active claim held by ANY address. TOTAL and fail-OPEN: a missing/unsupported `kind` (supported: `website`, `work`, `orcid`, `github`), an empty `value`, or an unreachable index all answer `{claimed:false}` with a 200 — so `claimed:false` is NEVER proof of availability, only `claimed:true` is load-bearing. |
| /api/report/config | GET | The report form's public configuration: report areas and the HPKE parameters + recipient keys the report CONTENT is sealed to (text, severity, diagnostics — there is no contact-details field). (The 2 MiB body ceiling on `POST /api/report` is enforced server-side, not advertised here.) |

## Agent SDK

The `@fastxyz/fastid-sdk` package gives Node 20.19 or newer agents the same non-admin capabilities as
the public site. It signs with the key supplied by its owner. There is no API token and no account
to register with Fast ID.

Install the published package from npm:

~~~sh
npm install @fastxyz/fastid-sdk
~~~

For a temporary development install from a local source checkout, build and install the repository tarball without saving a dependency:

~~~sh
(
set -eu
# Run this block from your consumer project so the final install targets it.
CONSUMER_DIR="$PWD"
SDK_TMP_DIR="$(mktemp -d /tmp/fastid-sdk-dev.XXXXXX)"
trap 'rm -rf "$SDK_TMP_DIR"' 0
SDK_SOURCE_DIR="$SDK_TMP_DIR/fast-sdk"
SDK_PACK_DIR="$SDK_TMP_DIR/pack"
mkdir "$SDK_PACK_DIR"
git clone --branch main https://github.com/fastxyz/fast-sdk.git "$SDK_SOURCE_DIR"
cd "$SDK_SOURCE_DIR"
corepack pnpm install --frozen-lockfile
corepack pnpm --filter @fastxyz/fastid-sdk... build
corepack pnpm --filter @fastxyz/fastid-sdk pack --pack-destination "$SDK_PACK_DIR"
cd "$CONSUMER_DIR"
npm install --no-save "$SDK_PACK_DIR"/fastxyz-fastid-sdk-*.tgz
)
~~~

After installing either distribution, import `IdClient` and `KeySigner` from `@fastxyz/fastid-sdk`.
The public SDK builds and runs without access to the private Fast ID repository.

| Capability | Call |
|---|---|
| Resolve a name or address | `client.resolve(identity)` · `client.identity(address)` |
| Check name availability | `client.availability(name)`; the claim pipeline performs its own fail-closed preflight before payment |
| Claim a name | `client.claimName(name)`; it settles an on-chain ExternalClaim and registers it |
| Attach a website | `client.claimWebsite(host)`; publish the returned proof file, then call `verifyAndSettle()` to settle once and poll the verified link |
| Attach GitHub or ORCID | `client.oauthClaimInstructions("github")` (use `"orcid"` for ORCID) → `client.waitForOAuthProof(...)` → `client.settleOAuthClaim(...)` |
| Attach X | `client.startXClaim(handle)`; post the returned text, verify the post, then settle only from the committed witness |
| Import a DOI or update a profile | `client.importWork(doi)` · `client.updateProfile({...})` |
| Revoke | `client.revoke(kind, value)` |
| Report a profile | `client.submitReport({...})`; the report content is sealed client-side and the serialized body is capped at 2 MiB |
| Share | `client.share.profileUrl(name)` · `client.share.idJsonUrl(name)` · `client.share.ogImageUrl(name)` |

Construct a client with
`new IdClient({ network: "fast:testnet", signer: await KeySigner.fromPrivateKey(key) })`.
Use `fast:mainnet` only when you intend to use production. Direct public HTTP reads need no key;
the combined SDK client is signer-bound because the same instance also exposes owner-authorized writes.
`resolve`, `identity`, `availability`, and `links` validate successful response bodies at
runtime. Parsed bodies with an invalid shape reject with `InvalidReadResponseError`; other malformed
bodies also reject as typed errors instead of returning values that violate their declared types.
`resolve`, `identity`, and `links` also reject shape-valid documents whose identity or network
does not match the request and the client's configured network. `resolve` additionally binds the
document's canonical `id` to the configured origin, including a testnet origin override.
Resolver and compact-identity documents require canonical Fast addresses and names, with `name`
and `name_claim_tx` either both present or both absent.
`propertyClaimed` remains the documented advisory exception and fails open to a non-load-bearing false.
OAuth methods accept only `github` or `orcid` and reject any other runtime provider before I/O.
Paid claims are permanent on-chain records in the network's fee token, so start on `fast:testnet`.
An empty authoritative fee list is Fast's fee-free bootstrap state; otherwise the SDK requires
exactly one matching default-token row and fails closed on missing, duplicate, or malformed rows.
An indeterminate submission never authorizes another payment. If registration is pending after a
settled transaction, retain `RegistrationPendingError.pending` and pass it to
`client.retryRegistration(...)`; that retry snapshots and validates the persisted record before
registration I/O and performs no signing or provider submission.
Website, OAuth, and X sessions release a cached attempt only after a failure proves that no payment
occurred. Paid, pending, and indeterminate results take precedence over later polling failures or
cancellation so callers keep the correct recovery record without another settlement.
Issued X sessions are frozen so their proof-bound handle and receipt fields cannot be rebound.
The signed X intent endpoint's exact no-receipt `{"settlement":"applied"}` response is terminal;
protocol settlement tags on an issued receipt remain gated by exact `intent-v1`.
Legacy X settlement accepts only a 200 JSON object whose sole field is `{"committed":true}`.
An issued session retains a terminal `applied` verification internally across later failed probes,
while `verifyPost` still returns each probe's current result.

GitHub and ORCID keep their existing browser OAuth leg. Open the claim-page URL in a browser,
connect the same wallet as the SDK signer on the same network, and authenticate there. The SDK
does not start OAuth headlessly or receive a callback, code, token, or cookie. It needs the known
canonical provider identity to poll the existence-only proof endpoint before settlement.
`updateProfile(...)` validates and snapshots its public input before reading the current profile, so
malformed field types and over-limit profile text perform no I/O, and caller mutation during that read
cannot alter the signed update. Avatar file contents are validated by the backend after submission.

## Writes: signature-gated by design

Claiming a name, attaching evidence, and editing a profile require an Ed25519 signature from the
address owner. A person produces it through a wallet gesture; an agent with authority over its own
key produces it through the SDK. Fast ID provides no API token, server-side credential, or implicit
delegation. `POST /api/report` also accepts anonymous attribution; its content is sealed to the
configured recipient keys before submission. `client.submitReport(...)` is the supported machine
interface: it snapshots and validates caller input, fetches the public config, and constructs that
sealed envelope without rereading caller-owned fields after asynchronous work. Without the SDK,
reporting is a HUMAN HANDOFF, not a directly callable API: the raw envelope layout shipped in the
tarball is an internal implementation detail, not a supported stable public API. An agent therefore
drafts the report with its user and hands them the form at
https://testnet.id.fast.xyz/report rather than guessing a `POST /api/report` body.

## Not an identity provider

Fast ID does not issue tokens, sessions, or assertions about users **to third parties**, and is
not an OAuth 2.0 / OpenID Connect provider — there is no authorization server here. (The site has
its own first-party sessions — the admin surface and the OAuth proof flows set cookies — but none
of them are credentials a third party can consume.) Integrating software reads public documents;
it does not "log in with Fast ID". The
RFC 9728 metadata at `/.well-known/oauth-protected-resource` advertises no authorization
servers and no scopes; the statement that these APIs are public and that no third-party token
exists is made HERE, in prose — the RFC document points back to this guide for it.

## Crawling policy

See [robots.txt](https://testnet.id.fast.xyz/robots.txt): search and agent-time reading are welcome; model training on
this content is not (`Content-Signal: search=yes, ai-input=yes, ai-train=no`).
