A file every agent knows how to read.

auth.md is a plain markdown file at the root of your domain that tells an agent your service accepts agent authentication, which hostname to authenticate for, and what to do when you say no.


The problem it solves

An agent can verify a token perfectly well and still have no way in, because it has no way to know your service takes one. A coding agent that has never heard of your API will not find it. One that has heard of it will not know which hostname to mint a token for, and a 403 that says “no account” looks identical to every other failure it knows.

So each service invents a flow, publishes it somewhere, and hopes the agent found it. Discovery is the part with no convention attached to it, and discovery is the part that has to be right.

auth.md is that convention: a file at a URL an agent can derive from your domain alone, in a format simple enough to read without a parser.


What it is, exactly

Plain markdown, served over HTTPS at https://<your-audience>/auth.md, with no authentication and no redirect to another host. Three required sections, in this order, and nothing else: the Audience string to pass to the CLI, one entry per Endpoint, and a table of what happens when a request is rejected.

There is no front matter, no version field, and no machine-readable block. A field nothing reads is a field you maintain for nothing.


The shape of it

Abbreviated. The full worked example, and the wording that matters in the rejection table, are in the docs.

public/auth.md

# Notes API

This service accepts AgentOnboard. Verified agent requests are
authorized against the account the verified email maps to.

## Audience

`notes.com`

    aon token get notes.com

Send the token on every request as `Authorization: Bearer <token>`.

## Endpoints

### `POST /api/notes`

- Request: `{ "title": string, "body": string }`
- Success: `201`
- Required header: `Authorization: Bearer <token>`

## When a request is rejected

| Status | `code` | What to do |
|---|---|---|
| 401 | any | The token was rejected. Mint another and retry **once**. |
| 403 | `ACCOUNT_REQUIRED` | The identity is genuine but has no Notes
account. Send the human to notes.com/connect, then retry. Retrying
without that never works. |

The part that earns its bytes

That last table is the highest-value section in the file. It is the only place an agent is told the difference between “mint a new token” and “ask the human to do something”, and that difference is exactly what the 401 / 403 / 500 split carries. An agent that reads it does not need a human to interpret a 403.

Two entries in it are easy to get backwards. ACCOUNT_REQUIRED is a 403, never a 401 — a 401 tells the agent its token is bad, and the right response to a 401 is to re-authenticate, which cannot create an account. And the action is connect, not register: an account created on the spot is an account whose ownership was never demonstrated, and there is no consent step a signup performs that connecting does not.

Publish the file after the integration works, not before. An agent reading it needs no other instruction, and an agent reading it against an unprotected endpoint will use that endpoint.


What it is not

Nothing we ship fetches this file. There is no reader in the SDK, none in the CLI, and no validator anywhere — which is exactly why the document is the contract and why the format has to be exact. An agent that does not know the convention will still authenticate correctly; it will just have to be told.

And it is provisional, not a standard. We defined the format without a spec to fix it, and it may change. Until a second party publishes one and the format has been tested against something other than our own reasoning, we treat a change to it as a patch release — and we would rather say that now than have you discover it when your agents stop.


One file, and an agent knows the way in.

The spec, the canonical rejection table, and a complete worked example.