# auth.md — GolfCore agent access

GolfCore is an on-course golf GPS and club operating system at
`https://www.golfcore.app`. This document tells software agents how to obtain a
session and reach the course maps without a human account.

There is no OAuth authorization server. Do not look for
`/.well-known/oauth-protected-resource` or `/.well-known/oauth-authorization-server`;
they are not published, because no authorization server issues a token this API
accepts. GolfCore is an OpenID Connect relying party, not an OAuth resource
server: signing in with Google exchanges a Google **ID token** for a GolfCore
session cookie, and a Google access token sent as `Authorization: Bearer` is
rejected. Everything an agent needs in order to sign in is in this file.

These discovery documents *are* published here: `/.well-known/api-catalog`
(RFC 9727), `/.well-known/mcp.json` and `/.well-known/mcp/server-card.json`,
`/.well-known/agent-card.json`, and `/.well-known/agent-skills/index.json`.
Anything else under `/.well-known/` returns 404.

## Audience

Agents that want to read golf course information: hole layouts, traced course
vectors, green contour maps, wind, and live GPS positioning. Club operations —
tee sheets, caddie scheduling, member rosters, messaging — require a real club
membership and are not reachable this way.

## Registration: anonymous guest

One request. It creates an anonymous account and returns a session cookie. No
email, no phone, no password, no human step.

```http
POST https://www.golfcore.app/api/v1/session/guest
Content-Type: application/json
Accept: application/json

{"golf_course_slug": "the-institute"}
```

`Accept: application/json` is required. It is what exempts the request from CSRF
verification; without it the request is rejected as forgery.

The response is `302` to `/` with `Set-Cookie: _golfcore_session=...`. Keep that
cookie and send it on every later request. The account is created with the
`guest` role and is flagged anonymous. Sign out at `/api/v1/session` with DELETE.

## Identifying yourself

Indexing and training crawlers are refused here with `403 automated_client`.
Assistants acting for a person are not: `ChatGPT-User`, `Claude-User`,
`Perplexity-User` and their siblings are recognised by user agent and pass
without any extra step.

Any other automated client sends a header naming itself:

```http
X-GolfCore-Agent: acme-caddie/1.4 (+https://acme.example/contact)
```

The header is the whole requirement. Nothing validates its contents, but it is
recorded against the account, and it is what separates a client that read this
document from a crawler that walked the sitemap.

## Registration: a real account

Send a name and the account is not anonymous — it gets a profile, a roster
entry, and the messaging and posting the app hides from guests. Every field is
optional and may be sent with or without `golf_course_slug`.

```http
POST https://www.golfcore.app/api/v1/session/guest
Content-Type: application/json
Accept: application/json
X-GolfCore-Agent: acme-caddie/1.4 (+https://acme.example/contact)

{"golf_course_slug": "the-institute",
 "first_name": "Dana", "last_name": "Reyes",
 "email": "dana@example.com", "phone": "+1 415 555 0147"}
```

Send only what the person gave you. A `first_name` alone is enough to clear the
anonymous flag; an email or phone alone is not, and leaves a guest account that
happens to be reachable.

Phone numbers are parsed against `phone_country` when the number is not already
in `+E.164` form. The account is created with SMS notifications off; the person
turns them on themselves in the app.

If the email or phone already belongs to an account, the request is refused with
`409 contact_registered` and nothing is created. Do not treat that as a login —
an existing account is reached only by its owner, through the magic link at
`/api/v1/session/email` or the SMS code at `/api/v1/session/phone`.

## Choosing a home course

`golf_course_slug` is optional but you almost always want it. It is the only
thing that picks the course.

- **With a slug** the account's current course is set to it immediately, and the
  app opens on that course's maps.
- **Without a slug** the account is created with *no* current course. Nothing is
  guessed from location or defaults, and the app opens a course picker instead of
  a map. An agent that skips the slug sees no course data.

Only active courses resolve. An unknown or inactive slug leaves the course unset
rather than failing the sign-in.

Change course later with the same slug vocabulary:

```http
POST https://www.golfcore.app/api/v1/users/{user_id}/switch_club
Content-Type: application/json
Accept: application/json

{"golf_course_slug": "cordevalle-golf-course"}
```

`golf_course_id` is accepted in place of `golf_course_slug`, and
`course_layout_id` selects a routing at a club with more than one.

## Finding a slug

Course slugs are published as URLs on the marketing site, one page per course:

- `https://www.golfcore.org/courses/{slug}`
- `https://www.golfcore.org/sitemap-index.xml`
- `https://www.golfcore.org/sitemap-courses-pilot.xml`

## Reading the maps

Once the session holds a course, the app renders GPS, hole layouts, green
contours and wind at `https://www.golfcore.app/gps`.

The underlying course documents are public objects on the CDN and need no
session at all:

- `https://cdn.golfcore.org/public/static/{slug}_traced_vectors_document.json`
- `https://cdn.golfcore.org/public/static/{slug}_green_topo_document.json`
- `https://cdn.golfcore.org/public/static/{slug}_trees_document.json`

They are gzip-encoded JSON. Not every course has every document.

## What a guest cannot do

Anonymous accounts are readers. The social surfaces reject them with `403`:
posting to or commenting on the club feed, direct messages, conversations, and
the clubhouse. Club administration is closed to them entirely.

An anonymous account stops being anonymous the moment a real name is set on it
at `https://www.golfcore.app/profile`. That is the upgrade path; there is no
separate credential exchange.

## Machine-readable summary

```json
{
  "agent_auth": {
    "skill": "https://isitagentready.com/.well-known/agent-skills/content-signals/SKILL.md",
    "register_uri": "https://www.golfcore.app/api/v1/session/guest",
    "identity_types_supported": ["anonymous"],
    "anonymous": {
      "credential_types_supported": ["session_cookie"],
      "claim_uri": "https://www.golfcore.app/profile"
    },
    "methods": [
      {
        "name": "guest",
        "type": "anonymous",
        "register_uri": "https://www.golfcore.app/api/v1/session/guest",
        "http_method": "POST",
        "request_content_type": "application/json",
        "required_headers": { "Accept": "application/json" },
        "parameters": {
          "golf_course_slug": {
            "type": "string",
            "required": false,
            "description": "Slug of the home course. Omitting it leaves the account with no course."
          }
        },
        "credential_type": "session_cookie",
        "credential_name": "_golfcore_session",
        "credential_location": "cookie",
        "success_status": 302
      }
    ]
  }
}
```

## Contact

support@golfcore.org — GolfCore LLC, California. Company information is at
`https://www.golfcore.org/` and `https://www.golfcore.app/llms.txt`.
