# GolfCore auth.md

GolfCore publishes three network surfaces. The Course Data API and the MCP
server's search tool are open to agents. The MCP server's personal distance,
caddie and subscription tools require the golfer to sign in with OAuth. Club
operations are closed to public agents.

## Course Data API — no authentication

Base URL: `https://api.golfcore.org/v1`

No API key, token or sign-up is needed. Fair-use rate protection may return
`429` with a `Retry-After` header during sustained excessive traffic.

```
curl https://api.golfcore.org/v1/courses?q=pebble+beach
curl https://api.golfcore.org/v1/courses/pebble-beach
```

Machine-readable description: <https://www.golfcore.org/openapi.json>
Documentation: <https://www.golfcore.org/developers/>

Conditions of use, in place of a credential:

- Attribute GolfCore and link the course page you drew the answer from, e.g.
  `https://www.golfcore.org/courses/pebble-beach/`.
- Send a `User-Agent` that names your agent or product and a way to reach you.
- Prefer this API to scraping the HTML pages. It is cheaper for you and for us,
  and it is the only surface whose shape we keep stable.

## MCP server — open, with OAuth for the caddie

Endpoint: `https://mcp.golfcore.org/mcp` (Model Context Protocol, Streamable HTTP)

Server card: <https://www.golfcore.org/.well-known/mcp.json>

Four tools. `search` needs no credential. `distance_to_flag`, `ask_caddie` and
`subscribe_premium` answer `401` with a `WWW-Authenticate` header naming
`https://mcp.golfcore.org/.well-known/oauth-protected-resource`, whose
authorization server is `https://www.golfcore.app`: register at `/oauth/register`,
use the authorization code flow with PKCE `S256` and scope `caddie`, and send the
token as `Authorization: Bearer`. The golfer signs in, or signs up, on GolfCore's
own page; the client never supplies their identity. `distance_to_flag` and
`ask_caddie` need GolfCore Premium.

`distance_to_flag` returns the course, the hole, and yards to today's pin and the
middle of the green. Pass `lat` and `lng` only when the client has the golfer's
precise GPS position, and never ask the golfer for coordinates. Without them
GolfCore reads their live position from the GolfCore app on
their phone; `position_source` and `position_age_s` say where it came from and how
old it is, and a golfer with no recent position is told to open the app on the
course. `course`, `hole` and `accuracy_m` are optional. `ask_caddie` takes the same
and a `question`; for a
golfer without Premium it returns the plans and their prices instead.
`subscribe_premium` takes `plan`: `day_pass` ($3 for 24 hours, one charge, no
subscription), `plus_monthly` ($10 a month) or `plus_yearly` ($100 a year), and returns a Stripe checkout URL for the golfer to pay. A
first-time subscriber pays half for the first month or year. There is no free
trial, and nothing is charged until the golfer completes checkout. Ask the golfer
which plan they want before calling it.

`search`: ask by course name, by course slug, by place, or about
GolfCore itself. It returns courses with their location and mapping coverage,
each course's per-hole card and tee ratings when you ask for them, the town,
region and country pages that count and list every course in a place, and
GolfCore's own product pages — each with the URL to cite.

An optional `scope` narrows it to any of `product`, `country`, `region`, `city`,
`courses`, `scorecards`, `slug`. Omit it to search all of them. `slug` treats the
query as a GolfCore course slug and returns that exact course, so
`["slug","scorecards"]` fetches one card. Leave `scorecards` out when you only
need to know which courses exist, because the cards are large.

Take any number — par, stroke index, yardage, course rating, slope — from a
course's `layouts`, which read the field, rather than from a page extract, which
matches text.

## The app at golfcore.app — guest sessions, and closed club operations

Base URL: `https://www.golfcore.app`

**The authoritative document for this host is
<https://www.golfcore.app/auth.md>.** Read that one; this is a summary.

An agent can get a read session without a human account. One request to
`POST /api/v1/session/guest` with `Accept: application/json` and a
`golf_course_slug` creates an anonymous `guest` account and returns a
`_golfcore_session` cookie, which then renders GPS, hole layouts, green contours
and wind at `/gps`. Slugs come from this site's course pages and sitemaps, and
the underlying course documents are public CDN objects needing no session at all.

Guests are readers. The social surfaces reject them with `403`, and club
operations — tee sheets, caddie yards, member rosters, messaging — need a real
club membership and are not reachable this way.

- Sign-in is by email and password, by club PIN, with Google, or as an anonymous
  guest. The Google path is OpenID Connect: the browser obtains a Google **ID
  token** and posts it to `POST /api/v1/session/google`, which verifies it against
  GolfCore's Google client ID and then establishes a session. An email Google
  presents that GolfCore has never seen is admitted as a `guest` account rather
  than turned away, the same standing as an anonymous guest.
- After sign-in, the app's API authenticates with a **Devise session cookie** and a
  CSRF token. A Google access token presented as `Authorization: Bearer` is
  rejected. GolfCore's own OAuth tokens are honoured in exactly two places,
  `POST https://www.golfcore.app/api/v1/agent/position`,
  `POST https://www.golfcore.app/api/v1/agent/caddie` and
  `POST https://www.golfcore.app/api/v1/agent/subscribe`, which the MCP server's
  `ask_caddie` and `subscribe_premium` call; metadata is at
  `https://www.golfcore.app/.well-known/oauth-authorization-server`.
- Access is scoped per club by the signed-in user's club membership and role.
- The one server-to-server route, `/api/v1/site/courses/:slug`, requires an
  HMAC-SHA256 signature over `GET\n{path}\n{timestamp}` in the
  `X-Golfcore-Signature` and `X-Golfcore-Timestamp` headers, keyed on a secret
  held only by GolfCore. Requests outside a 300-second window are rejected.

If you are an agent acting for a club that uses GolfCore and need programmatic
access to club operations, write to support@golfcore.org.

## Machine-readable summary

```json
{
  "agent_auth": {
    "skill": "https://www.golfcore.org/.well-known/agent-skills/index.json",
    "register_uri": "https://www.golfcore.app/api/v1/session/guest",
    "identity_types_supported": ["none", "anonymous", "oauth2"],
    "none": {
      "credential_types_supported": ["none"]
    },
    "anonymous": {
      "credential_types_supported": ["session_cookie"],
      "claim_uri": "https://www.golfcore.app/profile"
    },
    "methods": [
      {
        "name": "public",
        "type": "none",
        "description": "The Course Data API and the MCP server's search tool need no registration and no credential. Send the request.",
        "register_uri": null,
        "endpoints": ["https://api.golfcore.org/v1", "https://mcp.golfcore.org/mcp"],
        "credential_type": "none",
        "conditions": [
          "Attribute GolfCore and link the course page the answer came from.",
          "Send a User-Agent naming your product and a way to reach you."
        ]
      },
      {
        "name": "caddie",
        "type": "oauth2",
        "description": "The MCP server's distance_to_flag, ask_caddie and subscribe_premium tools, acting for a golfer who signs in to GolfCore and approves the client.",
        "protected_resource_metadata": "https://mcp.golfcore.org/.well-known/oauth-protected-resource",
        "authorization_server": "https://www.golfcore.app",
        "registration_endpoint": "https://www.golfcore.app/oauth/register",
        "grant_types": ["authorization_code", "refresh_token"],
        "code_challenge_methods": ["S256"],
        "scopes": ["caddie"],
        "credential_type": "bearer"
      },
      {
        "name": "guest",
        "type": "anonymous",
        "description": "An anonymous read session for the app at golfcore.app, for course maps, GPS, green contours and wind.",
        "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,
        "authoritative_document": "https://www.golfcore.app/auth.md"
      }
    ]
  }
}
```

## Contact

support@golfcore.org — GolfCore LLC, California.

## Invited API evaluation

The separate `/licensed/v1` pilot requires a server-side bearer key issued through
https://platform.golfcore.app after the invited owner accepts an evaluation.
Existing `/v1` and MCP authentication behavior is unchanged. See
https://www.golfcore.org/developers/pilot/ and `/licensed-openapi.json`.
