PinSeek connector API
A per-user HTTPS API for finding golf tee times and managing the user's PinSeek alerts. PinSeek provides links to course booking sites; it never makes or pays for a reservation. This page describes the API used by PinSeek's connector CLIs and the current authentication flow.
Base URL and access
Base URL: https://app.pinseek.io. JSON endpoints are under /api/v1 and require Authorization: Bearer <access_token>.
A PinSeek account is required. Tee-time search and course lookup do not require a paid membership. Creating or resuming alerts requires a Pro membership; the API returns membership_required with an account URL otherwise. Alerts are sent through the user's configured PinSeek channels.
The public pinseek-cli client ID is bundled with the local agent guide. Other client IDs are provisioned by PinSeek; for one, contact PinSeek support. The API does not accept an API key or OAuth authorization-code/PKCE login at this time.
OAuth 2.0 device authorization
PinSeek uses the OAuth 2.0 Device Authorization Grant (RFC 8628), per user. Request teetimes.read for searches and reading alerts; request rules.write for creating, changing, pausing, resuming, or deleting alerts.
POST /oauth/device/codewith form fieldsclient_idandscope(space-separated). The response suppliesdevice_code,user_code,verification_uri_complete,expires_in, andinterval.- Show the verification URL and code to the user. The user signs in to PinSeek and approves the requested access in their own browser. The code expires after 10 minutes.
- Poll
POST /oauth/tokenwith form fieldsgrant_type=urn:ietf:params:oauth:grant-type:device_code,client_id, anddevice_code, respecting the returned interval. - Refresh via
POST /oauth/tokenwithgrant_type=refresh_tokenandrefresh_token. Revoke withPOST /oauth/revokeand form fieldtoken.
Keep tokens out of URLs, logs, and chat messages. Tokens are scoped to the approving PinSeek account; access tokens expire after one hour and refresh tokens after 180 days unless revoked sooner.
Read endpoints
All endpoints below require teetimes.read, except /me, which accepts either scope.
GET /api/v1/me— account plan, active-alert limits, permitted channels, and a membership URL when applicable.GET /api/v1/courses— find courses withq,state, orlatandlng. Optionalradius_milesandlimit.GET /api/v1/teetimes— repeatcoursefor up to 10 course keys; supplydate(YYYY-MM-DD) andplayers(1–4). Optionalholes(0, 9, 18),time_start,time_end(24-hour course-local HH:MM),max_price(USD per player), andbudget_ms(1000–15000). Dates run from the course's today through eight days ahead.GET /api/v1/courses/{key}/forecast— a course's historical availability pattern.GET /api/v1/deeplink— supplycourse,date, and optionalplayersfor a course booking URL; this does not check availability.GET /api/v1/rulesandGET /api/v1/rules/{id}/matches— the user's alerts and observed matches.
Alert management
These endpoints require rules.write. Confirm the user's intended course, dates, time window, party size, and price cap before creating, updating, or deleting an alert. Do not replay an uncertain write without first reading the current rule state.
POST /api/v1/rules— JSON body withcourse_keys,date_start,date_end,time_start,time_end, andplayers. Optionalname,holes,max_price, andalert_channels. The date window is at most three days.PATCH /api/v1/rules/{id}— update only supplied fields. Alerts with weekly schedules, groups, pursuits, or unsupported channels must be edited in PinSeek.POST /api/v1/rules/{id}/pause,POST /api/v1/rules/{id}/resume, andDELETE /api/v1/rules/{id}.
Illustrative body: replace the date placeholder with a date in the supported window.
{
"course_keys": ["teeitup:10164"],
"date_start": "YYYY-MM-DD",
"date_end": "YYYY-MM-DD",
"time_start": "12:00",
"time_end": "18:00",
"players": 4,
"max_price": 60,
"alert_channels": ["email"]
}
Response and error semantics
The API returns JSON directly. Tee-time search returns as_of, results[], providers[], and truncated. Each result includes course name/key, tee_local, tee_utc, players_available, price, holes, deeplink, source (live or cached), and observed_at.
A cached result reflects the last scan, not a current opening. The provider entry explains whether a search was live or fell back to a scan. A missing price is unknown, not free, and may pass a price filter. A deeplink opens a booking site but does not reserve anything. Availability can change after either a live check or a scan.
Errors are JSON with error and message; some include fields, action_url, or retry_after. Relevant statuses include 401 invalid_token, 403 insufficient_scope, 402 membership_required, 409 duplicate_rule, 422 invalid_request, and 429 rate_limited. Respect Retry-After where present.