> ## Documentation Index
> Fetch the complete documentation index at: https://docs.afauth.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart: accept AFAuth on your service

> Mount the discovery document, verify a signed request, and start accepting agents.

You're going to make a service accept AFAuth-signed requests. After this, any AI agent in the world can sign up to your service with its own keypair — no portal to maintain, no API keys to provision.

## Install

<Tabs>
  <Tab title="pnpm">
    ```bash theme={null}
    pnpm add @afauthhq/agent     # client / agent
    pnpm add @afauthhq/server    # service handlers + Verifier
    pnpm add @afauthhq/worker    # Cloudflare Workers bindings
    pnpm add @afauthhq/core      # primitives shared by the above
    ```
  </Tab>

  <Tab title="npm">
    ```bash theme={null}
    npm i @afauthhq/agent
    npm i @afauthhq/server
    npm i @afauthhq/worker
    npm i @afauthhq/core
    ```
  </Tab>

  <Tab title="yarn">
    ```bash theme={null}
    yarn add @afauthhq/agent
    yarn add @afauthhq/server
    yarn add @afauthhq/worker
    yarn add @afauthhq/core
    ```
  </Tab>
</Tabs>

## Configure a Server

`defineService` is the opinionated convenience factory. It returns a
[`Server`](/sdk/typescript/server/overview) — the same class that wraps
[`Verifier`](/concepts/signing) and exposes endpoint handlers for
discovery, signup, account introspection, owner invitation, claim
completion, and key rotation — but with two spam-resistance switches
flipped ON by default:

1. Discovery advertises `billing.unclaimed_mode: "attested_only"` (§9.2).
2. The attestor defaults to `trustAttestor()` (AFAP-0006 §10).

The net effect: un-attested implicit signups are rejected at the wire,
and all of a human's agents — which carry the same per-service pseudonym
`sub_h` (§10.4) — are **grouped onto one account** (§10.4.4: one account,
many devices). Bucket your free-tier quota, rate limits, and bans on the
account and a human's whole fleet shares one bucket, no matter how many
keypairs they spin up — while their legitimate PC and phone agents both
keep working.

<Note>
  Because this default rejects un-attested signups, an agent reaches your
  service by linking to a human at [trust.afauth.org](https://trust.afauth.org)
  and presenting an `AFAuth-Attestation` JWT — the [agent
  quickstart](/quickstart-agent#link-your-agent-to-a-human) shows that step
  (the `afauth signup` CLI auto-mints it). To accept un-linked agents while
  you ramp, set `attestation: "optional"` below.
</Note>

```typescript theme={null}
import {
  consoleEmailHandler,
  defineService,
  MemoryAccountStore,
  MemoryNonceStore,
  MemoryRevocationList,
} from "@afauthhq/server";

const server = defineService({
  baseUrl: "https://api.example.com",
  serviceDid: "did:web:api.example.com",

  // Storage. Memory stores are for development only — swap for D1 /
  // KV / Postgres / Redis in production. See @afauthhq/worker for the
  // Cloudflare-native durable stores.
  nonceStore: new MemoryNonceStore(),
  accounts: new MemoryAccountStore(),
  revocationList: new MemoryRevocationList(),

  // The §7.2 owner-invitation ceremony. consoleEmailHandler logs the
  // magic link to stderr — replace with your real email transport.
  recipients: { email: consoleEmailHandler },

  // §7.2: redirect_url is rejected unless its host is in this list.
  redirectAllowList: ["yourapp.com"],

  // attestation: "required" is the default. Override with:
  //   "optional" — verify when present, accept un-linked agents
  //                while you ramp (migration path)
  //   "off"      — read-only / paid-only services where a human
  //                signal is overkill
});
```

The synthesized discovery doc derives the canonical §4.1 endpoints from
`baseUrl`: `/afauth/v1/accounts`, `/afauth/v1/accounts/me/owner-invitation`,
`/afauth/v1/accounts/me/keys/rotate`, and `/afauth/v1/claim` (plus the
`/claim` claim page). These match the paths `@afauthhq/agent` signs, so a
default agent interoperates out of the box. To customize paths, advertise
additional `accepted_attestors`, or declare `limits`, pass a `discovery`
override — it merges on top:

```typescript theme={null}
defineService({
  /* ... */
  discovery: {
    endpoints: { accounts: "/afauth/v1/accounts", /* ...other paths */ },
    billing: { accepted_attestors: ["afauth-trust", "stripe-projects"] },
    limits: { unclaimed_rate_limit_per_hour: 100 },
  },
});
```

For multi-attestor setups, custom `HmacAttestor`, or fully custom
discovery, drop to `new Server({...})` directly — see [`@afauthhq/server`
overview](/sdk/typescript/server/overview).

## Route incoming requests to the handlers

```typescript theme={null}
export default {
  async fetch(req: Request): Promise<Response> {
    const url = new URL(req.url);

    if (url.pathname === "/.well-known/afauth") {
      return server.handleDiscovery(req);
    }
    if (url.pathname === "/afauth/v1/accounts/me") {
      // Implicit signup: the first signed request from an unknown agent DID
      // creates the account here (there is no separate create endpoint).
      return server.handleAccountIntrospection(req);
    }
    if (url.pathname === "/afauth/v1/accounts/me/owner-invitation") {
      return server.handleOwnerInvitation(req);
    }
    if (url.pathname === "/afauth/v1/accounts/me/keys/rotate") {
      return server.handleKeyRotation(req);
    }
    // /afauth/v1/claim/<token> — wire to server.handleClaimCompletion(req,
    // session), building an OwnerSession from your claim page's auth layer.
    // See the worker example: https://github.com/AFAuthHQ/typescript-sdk/blob/main/examples/worker/src/index.ts

    return new Response("not found", { status: 404 });
  },
};
```

## Verify it works

Hit the discovery endpoint:

```bash theme={null}
curl https://api.example.com/.well-known/afauth
```

For deeper conformance, run the spec's Appendix C vectors against your `Verifier` in your own test suite — `@afauthhq/server`'s test suite already does this, so cloning that pattern is the fastest path. The canonical vectors live at [`AFAuthHQ/spec/vectors`](https://github.com/AFAuthHQ/spec/tree/main/vectors).

## Production checklist

* Swap `Memory*` stores for durable backends. `@afauthhq/worker` ships `D1AccountStore` (multi-agent accounts; atomic device-grouping via the `(iss, sub_h)` index), `KvNonceStore`, `KvRevocationList`. See [Deploy to Cloudflare Workers](/guides/deploy-cloudflare-worker).
* Replace `consoleEmailHandler` with a real email transport.
* Host the claim page — the SDK doesn't route it for you because it's service UI. Reference: [`examples/worker/src/index.ts`](https://github.com/AFAuthHQ/typescript-sdk/blob/main/examples/worker/src/index.ts).
* Decide your [billing mode](/concepts/attestation). The `defineService` default is `attested_only` — set `attestation: "off"` to opt out, or `"optional"` for a migration path.

## Next steps

* [The ceremony](/concepts/ceremony) — what your service is actually enforcing, and why.
* [Error envelope](/concepts/error-envelope) — the shape your service must return.
* [`@afauthhq/server` overview](/sdk/typescript/server/overview) — full API surface.
