# auth.md — Authentication for AI Agents

**Authentication is available and optional.** Every tool on this server works
anonymously. Registering an OAuth client buys you a stable identity and your own
rate-limit bucket — it does not unlock anything you cannot already do.

That is a deliberate posture, not an oversight. The public web checkout at
https://onwardticket.us/order-now is equally unauthenticated, so requiring credentials on the
agent path would add friction without closing anything — the same order can be
created through the browser form by anyone.

## Agent registration

Machine-readable, using the same key names as the agent_auth block in
https://onwardticket.us/.well-known/oauth-authorization-server — a parser and a human should
get the same answer from either.

    register_uri: https://onwardticket.us/api/oauth/register
    claim_uri: https://onwardticket.us/api/oauth/token
    revocation_uri: https://onwardticket.us/api/oauth/revoke
    supported_identity_types: oauth_client
    supported_credential_types: client_secret
    grant_types_supported: client_credentials
    auth_required: false

All three URIs answer — verified, not assumed. An earlier version of this
file advertised register_uri and revocation_uri while one returned 403 and
the other did not exist.

Open, RFC 7591 dynamic client registration. No approval step, no waiting.

| | |
| --- | --- |
| Registration endpoint | POST https://onwardticket.us/api/oauth/register |
| Token endpoint (claim) | POST https://onwardticket.us/api/oauth/token |
| Identity type | oauth_client |
| Credential type | client_secret |
| Grant type | client_credentials only |
| Discovery | https://onwardticket.us/.well-known/oauth-authorization-server |
| Keys | https://onwardticket.us/.well-known/jwks.json |

Register by POSTing {"client_name": "Your Agent", "contacts": ["you@example.com"]}
to /api/oauth/register. The response carries client_id and client_secret.
**The secret is shown exactly once** — only a hash is stored and it cannot be
recovered. A lost secret means registering again.

Claim a token by POSTing grant_type=client_credentials with your client_id and
client_secret to /api/oauth/token. You get an RS256 JWT valid for 1 hour. There
are no refresh tokens — re-mint from the same credentials. Send it as
Authorization: Bearer <token>.

## Scopes

| scope | grants |
| --- | --- |
| agent:read | list_services, quote_order, search_blogs, get_blog_post |
| agent:orders | lookup_order |
| agent:checkout | place_order |

A self-registered client receives all three. place_order creates an UNPAID order
and returns a payment link — it does not move anyone's money, which is why
registration is open.

## What honest revocation looks like here

Tokens are self-contained JWTs verified without a database read. Disabling a
client stops new tokens immediately, but **tokens already issued stay valid until
they expire — up to 1 hour.**

One further caveat, stated because a security document that overstates its
guarantees is worse than one that admits a limit: the revocation check FAILS OPEN
if our Redis is unavailable. Failing closed would lock out every agent integration
on a transient outage. Exposure is bounded to tokens explicitly revoked within the
preceding hour, and a disabled client cannot mint new ones.

## What is NOT public

/api/admin/* is staff-only, behind a first-party session JWT issued to human
operators. That is not OAuth, it is not available to agents, and it is not part of
this surface. Do not attempt to authenticate against it.

## Payment

Placing an order needs no credentials. **Paying needs a human.** place_order
returns a Stripe-hosted checkout URL; present it to your user and let them
complete payment in their own browser. Prices are always re-derived server-side —
a price supplied by a caller is ignored. Nothing is fulfilled until payment
completes.

We do not accept machine-initiated on-chain payments.
https://onwardticket.us/.well-known/x402 documents that position and declares
live_402_responses: false. No endpoint here returns an HTTP 402 challenge.

## Rate limits

60 requests/minute per IP by default; /.well-known/ is exempt. An authenticated
client is bucketed by client id rather than IP, which matters because agent
traffic arrives from a handful of provider egress addresses — unauthenticated
callers can therefore contend with each other.

## Entry points

- OpenAPI 3.1 — https://onwardticket.us/openapi.json
- API catalog (RFC 9727) — https://onwardticket.us/.well-known/api-catalog
- MCP server card — https://onwardticket.us/.well-known/mcp/server-card.json
- MCP (Streamable HTTP) — https://onwardticket.us/api/mcp
- MCP (SSE, deprecated) — https://onwardticket.us/api/mcp/sse
- A2A agent card — https://onwardticket.us/.well-known/agent-card.json
- A2A (JSON-RPC) — https://onwardticket.us/api/a2a
- Human-readable docs — https://onwardticket.us/agents
- Contact — contact@onwardticket.us
