# CampWatch API and MCP guide

CampWatch monitors Australian campground availability and sends alerts. It does
not reserve a campsite or buy a camping permit. Booking links lead to providers.

Developer setup and examples: /developers. MCP endpoint: /mcp (Streamable HTTP).
Public search and availability tools require no account.
Discovery: /.well-known/agent.json (check enabled). Contract: /api/v1/openapi.json.
Base path: /api/v1. Use the canonical host returned by discovery. All operations
use HTTPS. No browser cookies, UI scraping or CAPTCHA-solving are required.

## MCP account linking and REST sign-in

For MCP/ChatGPT, use /mcp and OAuth account linking. Protected-resource metadata
is /.well-known/oauth-protected-resource. The user verifies their phone and
approves scopes in the browser. Never ask for an SMS code in ChatGPT. Private
tools request account linking automatically. Tools can use the account's existing
credits and return its balance and ledger. The plugin has no purchase tools,
prices, checkout links or subscriptions. A missing entitlement is explained
without starting a purchase. Sign in with ChatGPT is not part of this integration.

Only the official ChatGPT client identity and preregistered OAuth clients can
link accounts. Other clients: contact hello@campwatch.com.au with your client ID
and exact callback URLs. Dynamic client registration is not supported.

The direct API flow below is for an authorised client that can securely receive
the owner's verification code outside a chat transcript.

With the owner's permission and agreement to /terms and /privacy:
1. POST /session/request with {"phoneNumber":"+61412345678","consent":true}.
2. Obtain the SMS code from the owner. Never guess codes.
3. POST /session/verify with {"verificationId":"from step 1","code":"123456","agentName":"Your agent","consent":true}.
4. Store data.sessionToken securely. Send Authorization: Bearer TOKEN on private calls.
5. POST /session/logout revokes this token. POST /session/refresh returns session metadata.
Each agent should have its own session. Verification creates/reuses the same phone
account and credit balance as the website; signup cannot multiply free credits.

## Find and manage alerts

GET /campgrounds?q=beach&state=NSW&limit=25; follow data.nextCursor.
GET /providers and /campgrounds/{provider}/{slug} describe supported targets.
GET /campgrounds/{provider}/{slug}/site-types gives the exact site filter labels.
GET /campgrounds/{provider}/{slug}/availability?checkIn=YYYY-MM-DD&checkOut=YYYY-MM-DD&adults=2
returns an SSE stream. Read result or error; HTTP 200 alone is not success.

POST /watches with a unique idempotencyKey for each intended alert:
{"target":{"kind":"campground","provider":"nsw","slug":"slug-from-search"},"dates":{"checkIn":"YYYY-MM-DD","checkOut":"YYYY-MM-DD"},"partySize":{"adults":2},"idempotencyKey":"unique-request-0001"}
Use kind:park and parkSlug for park alerts. Optional siteTypeFilter and
excludedCampgroundSlugs are arrays. watchOptions exposes flexibleStartDays,
startDayFilter, campsiteFilterMode and campsiteFilter; the normal web validation
rules still apply. Never invent slugs, provider IDs or site names.

GET /watches?status=ACTIVE,STOPPED,EXPIRED lists owned alerts. GET /watches/{id}
reads one. PATCH /watches/{id} changes park exclusions. DELETE /watches/{id}
stops an alert; POST /watches/{id}/reactivate restarts it subject to current limits.
To change dates, party or target, stop the old alert and create a new alert.
GET /watches/{id}/events reads delivery history. GET/PATCH /account/preferences
reads or changes notification settings. Zero credits, opt-outs, provider outages
and quotas are enforced by the existing service.

## Existing credits and disabled API payments

Direct API payments are disabled in the current release. MCP tools never expose
purchases or subscriptions. Read /credits/balance and /credits/history for the
linked account’s existing credits.

The following describes the separately gated REST payment contract, not an active
checkout offer. Payment creation is unavailable unless the separate API payment release flag is
enabled. Read purchasesEnabled before requesting a payment; existing website
purchases do not imply API payment availability.

GET /credits/bundles returns current AUD prices, purchasesEnabled and the Stripe
publishable key. GET /credits/balance returns the owner's balance and limits.
Get explicit spending authority for the displayed pack and price. Obtain a
single-use Stripe ConfirmationToken for this merchant from the payer's authorised
payment client using Stripe's SDK. That client must supply a card payment method;
the API does not collect card numbers, accept another merchant's tokens, or supply
a wallet on the agent's behalf. First-time payment credential setup may require
the payer. Do not ask the user to paste card details into chat.

POST /payments:
{"bundleCode":"credits_5","confirmationToken":"ctoken_FROM_PAYMENT_CLIENT","expectedAmountCents":799,"currency":"aud","idempotencyKey":"unique-payment-0001"}
The amount above is illustrative; read the price first. Only status:fulfilled
means credits were granted. requires_action returns nextAction.clientSecret for
the owner's Stripe SDK to complete bank authentication; then GET /payments/{id}.
processing means poll; requires_payment_method with failureCode means declined.
A new payment attempt needs a fresh token and key after the prior outcome is known.
Never switch keys after a timeout. GET /payments/{id} also returns the paid amount,
credit quantity and reference. GET /credits/history paginates the credit ledger.
No subscription or stored-card future-charge permission is created.

## Responses and retries

JSON: {ok:true,data:...,meta:{requestId,serverTime,apiVersion}} or
{ok:false,error:{code,message,retryAfter?,details?},meta:...}.
No personal or payment response may be cached. Store bearer tokens and Stripe
client secrets as secrets; do not include them in logs, URLs, screenshots or chat.
429 includes Retry-After. Current per-IP limits: 60 reads/minute and 10 writes/minute;
payments also allow five attempts/minute per account. SMS limits are shared with web.
These application limits are process-local; production edge controls must also be verified.

Reuse the identical body and idempotencyKey after a network failure. Changed bodies
return IDEMPOTENCY_CONFLICT. Watch creation dedupe is retained for 30 days.
REQUEST_IN_PROGRESS_OR_UNCERTAIN means a prior watch action is running or may have
completed: list alerts and reconcile, or contact support. Do not use another key.
Payment keys remain bound to their purchase. PAYMENT_REVIEW_REQUIRED needs support;
never create a second payment to work around it. External campsite booking, refunds
and account deletion are not automated by this API.
