# auth.md

You are an agent. This service supports **agentic registration** via a single real
method — `service_auth` — with an optional human-confirmed claim ceremony: discover →
register → (claim) → exchange for an access_token → call API → handle revocation.
Every step is optional — every endpoint on this platform already works with no
`Authorization` header at all. This document has only one registration method because
that is the only one this platform actually implements; it does not describe
`identity_assertion`/ID-JAG or `anonymous` registration because neither exists here.

## Step 1 — Discover

```http
GET https://agent.columbiahonda.com/.well-known/oauth-protected-resource
```

```http
GET https://agent.columbiahonda.com/.well-known/oauth-authorization-server
```

```json
{
  "revocation_endpoint": "https://agent.columbiahonda.com/oauth/revoke",
  "agent_auth": {
    "skill": "https://agent.columbiahonda.com/auth.md",
    "register_uri": "https://agent.columbiahonda.com/agent/identity",
    "identity_endpoint": "https://agent.columbiahonda.com/agent/identity",
    "claim_endpoint": "https://agent.columbiahonda.com/agent/identity/claim",
    "events_endpoint": "https://agent.columbiahonda.com/agent/event/notify",
    "identity_types_supported": [
      "service_auth"
    ],
    "credential_types": [
      "bearer_token"
    ],
    "events_supported": [
      "https://schemas.workos.com/events/agent/auth/identity/assertion/revoked"
    ]
  }
}
```

- `issuer` / `token_endpoint` / `revocation_endpoint` — standard RFC 8414 / RFC 7009 fields.
- `agent_auth.skill` — this document's URL.
- `agent_auth.register_uri` / `agent_auth.identity_endpoint` — where you POST to register (Step 3).
- `agent_auth.claim_endpoint` — where a human confirms a registration (Step 4).
- `agent_auth.events_endpoint` — where this service can be notified of a revocation event (see Revocation). You don't call this to register; it's listed for completeness.
- `agent_auth.identity_types_supported` — always `["service_auth"]` on this platform. There is nothing else to pick.
- `agent_auth.identity_assertion.assertion_types_supported` — empty. This platform has no ID-JAG or any other assertion-based flow, honestly.
- `agent_auth.credential_types` — the shape of what you'll get back: a plain bearer token, not a signed JWT (see the empty `/.well-known/jwks.json` — there are no signing keys, because none are used).

## Step 2 — Pick a method

There is only one: **service_auth**. This platform has no end-user accounts, so there
is no identity to assert and nothing an ID-JAG could bind to — `identity_assertion` is
not offered. There is also no `anonymous` registration tier with reduced scopes,
because every scope this platform grants (`read`) is already available with zero
registration at all; layering an "anonymous" tier on top of that would describe a
distinction that doesn't exist.

## Important: the client_id/client_secret is a public value, not a secret

Columbia Honda's `client_id` (also usable as `client_secret`) is:

```
sk_6Ja2Y4v3WCQxpUpQUPkraWaaWKfnAeA6
```

This is **not confidential** — it is the same value already embedded in plaintext in
Columbia Honda's website install snippet (`data-apikey="sk_6Ja2Y4v3WCQxpUpQUPkraWaaWKfnAeA6"`), visible to anyone who views
that page's source. Publishing it here again does not reduce security, because there was
never any secrecy to begin with. Treat this flow as an identification mechanism, not an
access-control boundary.

## Step 3 — Register

```http
POST https://agent.columbiahonda.com/agent/identity
Content-Type: application/json

{ "type": "service_auth", "login_hint": "you@example.com" }
```

Response (200):

```json
{
  "registration_id": "reg_...",
  "registration_type": "service_auth",
  "claim_url": "https://agent.columbiahonda.com/agent/identity/claim",
  "claim_token": "clm_...",
  "claim_token_expires": "2026-01-01T00:10:00.000Z",
  "post_claim_scopes": ["read"],
  "claim": {
    "user_code": "123456",
    "verification_uri": "https://agent.columbiahonda.com/agent/identity/claim/view?claim_token=clm_...",
    "interval": 5
  }
}
```

`login_hint` is accepted but not emailed anywhere — there is no email system behind
this endpoint, and claiming otherwise would be misleading. `claim_token` is returned
exactly once; hold it in memory for the ceremony, same as the reference convention —
do not persist it.

## Step 4 — Claim ceremony

The `claim` block borrows its shape from RFC 8628 device authorization (`user_code`,
`verification_uri`, `interval`) — the same convention the wider `agent_auth` ecosystem
uses. Surface `verification_uri` and `user_code` to a human in one message. They open
the link, see a real confirmation page (not a placeholder), and type the code:

> Open this link and enter this 6-digit code: **123456**
> https://agent.columbiahonda.com/agent/identity/claim/view?claim_token=clm_...

Then poll the token endpoint with the claim grant:

```http
POST https://agent.columbiahonda.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:onekeel:agent-auth:grant-type:claim&claim_token=clm_...
```

While waiting:

```json
{ "error": "authorization_pending", "error_description": "..." }
```

On success, a standard OAuth token response (see Step 5's shape). If the `user_code`
window closes before the human finishes, you'll get `expired_token` — start over at
Step 3; this platform does not support re-issuing a fresh code against the same
registration. Ten wrong `user_code` guesses against one `claim_token` permanently locks
that registration (`locked_out`) — start over at Step 3 rather than continuing to guess.

## Step 5 — Get the access_token

If you'd rather skip Steps 3–4 entirely, this platform's `client_id` is a public,
non-secret value — see below — so you can exchange it directly instead of registering:

```http
POST https://agent.columbiahonda.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=sk_6Ja2Y4v3WCQxpUpQUPkraWaaWKfnAeA6&client_secret=sk_6Ja2Y4v3WCQxpUpQUPkraWaaWKfnAeA6
```

Response (200), identical shape whether you got here via Step 4's claim grant or this
shortcut:

```json
{ "access_token": "sk_6Ja2Y4v3WCQxpUpQUPkraWaaWKfnAeA6", "token_type": "Bearer", "scope": "read" }
```

The `access_token` **is** the `client_id`/`client_secret` you already had — there is no
separate token, no expiry, and no refresh step, because there is nothing more to grant
than what you already presented. This is the platform's one deliberate departure from
the reference protocol: the reference expects a service-signed, expiring
`identity_assertion` exchanged for a short-lived `access_token`; this platform has
neither signing keys nor expiry, because the underlying credential is already public
(see below) and there is nothing more sensitive to protect by adding either.

## Step 6 — Use the access_token

```http
GET https://app-confluence.onekeel.ai/api/v1/columbia-honda/business
Authorization: Bearer sk_6Ja2Y4v3WCQxpUpQUPkraWaaWKfnAeA6
```

There is no refresh step (see Step 5) and no expiry to react to. A `401` from any
endpoint below indicates a service outage or misconfiguration, not a missing or
expired credential.

## Errors

| Code | Where | What to do |
| --- | --- | --- |
| `invalid_request` | `POST /agent/identity` | Body must be exactly `{"type": "service_auth", ...}` — this is the only `type` accepted. |
| `invalid_claim_token` | `POST /agent/identity/claim` | `claim_token` wrong, expired, or already claimed. Restart at Step 3. |
| `locked_out` | `POST /agent/identity/claim` | Ten wrong `user_code` guesses against this `claim_token`. Restart at Step 3 — there is no unlock. |
| `unsupported_grant_type` | `POST /oauth/token` | Only `client_credentials` and `urn:onekeel:agent-auth:grant-type:claim` are supported. |
| `invalid_client` | `POST /oauth/token` | `client_id`/`client_secret` didn't match. Re-check the value in the "Important" section above. |
| `authorization_pending` | `POST /oauth/token` (claim grant) | Human hasn't completed the ceremony yet. Honor `interval`; retry. |
| `expired_token` | `POST /oauth/token` (claim grant) | `user_code` window closed before the human finished. Restart at Step 3. |
| `unsupported_response_type` | `GET /oauth/authorize` | This platform doesn't support an authorization_code / human-redirect flow — use `client_credentials` or the claim grant instead. |

## Revocation

Two independent, real, working entry points to the same revocation — use whichever
shape your framework already sends:

```http
POST https://agent.columbiahonda.com/oauth/revoke
Content-Type: application/x-www-form-urlencoded

token=clm_...
```

```http
POST https://agent.columbiahonda.com/agent/event/notify
Content-Type: application/json

{
  "events": {
    "https://schemas.workos.com/events/agent/auth/identity/assertion/revoked": {
      "claim_token": "clm_..."
    }
  }
}
```

Both stop the given `claim_token` from working on any future poll of the claim grant.
Per RFC 7009 §2.2, `/oauth/revoke` always returns `200` whether or not the token was
real, to avoid leaking which tokens exist; `/agent/event/notify` always returns `202`.

**Neither can revoke the `client_id`/`client_secret` itself.** That value is permanent
and used elsewhere on this platform outside of OAuth (analytics tracking, the website
install snippet) — there is no key-rotation feature, so there is nothing either
revocation endpoint can do to invalidate it. If you were relying on the Step 5 shortcut
rather than the claim ceremony, neither of the above endpoints has any effect on your
access at all.

## Also reachable, with or without any of the above

- `GET https://app-confluence.onekeel.ai/api/v1/columbia-honda/business` — business info (JSON-LD, Schema.org)
- `GET https://app-confluence.onekeel.ai/api/v1/columbia-honda/vehicles` — vehicle inventory (JSON-LD)
- `GET https://app-confluence.onekeel.ai/api/v1/columbia-honda/testimonials` — reviews & testimonials
- `GET https://app-confluence.onekeel.ai/api/v1/columbia-honda/llms.txt` — LLM discovery file
- MCP tools at `https://mcp-okp.confluencelocalmarketing.com/columbia-honda/mcp` — see
  `/.well-known/mcp/server-card.json` for the full tool list

Adding `Authorization: Bearer sk_6Ja2Y4v3WCQxpUpQUPkraWaaWKfnAeA6` is accepted but never required. A `401` from any
of these indicates a service outage or misconfiguration, not a missing credential.
