---
name: fanparking
description: Find, quote, hold, and pay for private event parking near Louisville venues on FanParking.Space with Lightning (L402) or USDC on Base (x402).
version: 1.0.0
homepage: https://www.fanparking.space/
metadata: {"openclaw": {"homepage": "https://www.fanparking.space/", "envVars": [{"name": "NWC_URL", "required": false, "description": "Nostr Wallet Connect URL for a Lightning wallet that can pay BOLT11 invoices. Only needed for L402 checkout."}, {"name": "EVM_PRIVATE_KEY", "required": false, "description": "Base wallet key your x402 client signs USDC payments with. Only needed for x402 checkout."}]}, "hermes": {"tags": ["parking", "events", "travel", "payments", "lightning", "l402", "usdc", "x402"], "category": "travel"}}
required_environment_variables: [{"name": "NWC_URL", "prompt": "Nostr Wallet Connect URL for a Lightning wallet (optional)", "help": "Only needed to pay with Lightning. Browsing and quoting work without it.", "required_for": "optional features"}, {"name": "EVM_PRIVATE_KEY", "prompt": "Base wallet private key for x402 USDC payments (optional)", "help": "Only needed to pay with USDC on Base. Browsing and quoting work without it.", "required_for": "optional features"}]
---

# FanParking.Space — book private event parking

FanParking.Space (short form: FanParking) sells one exact private parking stall
— a driveway, garage, lawn, or lot — for selected events near Louisville venues:
Louisville football at L&N Federal Credit Union Stadium, Churchill Downs race
days, and Derby Week (Thurby, Kentucky Oaks, Kentucky Derby). It is not curb or
street parking and it is not event tickets.

## When to use this skill

Use it when a human asks you to find, price, reserve, or pay for parking near a
Louisville game, race day, or the Derby, or asks what FanParking is. You can
list events, list listings, and get an exact quote with no wallet at all. To
complete a booking you need one of:

- a Lightning wallet that can pay a BOLT11 invoice (L402), or
- a Base wallet holding USDC that your x402 client can sign with (x402).

With neither, stop at the quote and hand the human a browser link (step 6c).

## Quick reference

Base URL: `https://www.fanparking.space/api/agent/v1` — public JSON, no API key,
no login. Payment is the authorization. Send `Accept: application/json` and a
descriptive `User-Agent`. All money fields are USD cents unless stated.

| Method | Path | Purpose |
| --- | --- | --- |
| GET | `/meta` | Origin, accepted rails, this skill's URL, fee disclaimer |
| GET | `/events` | Upcoming bookable events |
| GET | `/listings?event=<eventId>` | Agent-bookable listings for that event |
| GET | `/listings/<listingId>` | Listing detail plus quote inputs (stalls, prices) |
| POST | `/quotes` | Same quote math a fan sees at checkout |
| POST | `/checkouts` | Hold a stall; answers **402** with payment instructions |
| POST | `/checkouts/<checkoutId>` | Same hold, retried with an L402 or x402 payment proof |
| GET | `/me` | Grant-authenticated fan (`X-Agent-Grant`): preferences and garage |
| POST | `/checkouts/<id>/checkin` | GPS check-in for a paid grant checkout |

## 1. Fetch `/meta` first

Call `GET https://www.fanparking.space/api/agent/v1/meta` once per session and
use what it returns instead of anything cached. Example response:

```json
{
  "origin": "https://www.fanparking.space",
  "skillUrl": "https://www.fanparking.space/skills/fanparking/SKILL.md",
  "rails": ["lightning", "usdc"],
  "feeDisclaimer": "Agents pay the host's wallet the full quote (host price + service fee + any tax or civic fee). FanParking does not collect its service fee on-chain in this version.",
  "capabilities": {
    "agentGrant": true,
    "autonomousParking": false,
    "selfCheckIn": true
  }
}
```

If `origin` differs from `https://www.fanparking.space`, build every later URL
from the returned `origin`.

## 2. List events

`GET /events` returns upcoming events that have at least one agent-bookable
listing. Each event carries an `id` (the value you put in `eventIds`), a
`name`, an ISO `startsAt`, the venue, and series/package information when the
event is part of a season or festival package. Pick the event the human means
and confirm the date with them before going further.

## 3. List listings for an event

`GET /listings?event=<eventId>` returns the same cards the Explore map shows,
limited to listings that are active **and** whose host accepts at least one
agent rail. Useful fields: `id` (the listing id), `title`, `priceCents` (host
price for that event, before fees), `walkMinutes`, `nearestGateName`,
`spotType`, `covered`, `tailgatingAllowed`, `available`, `ratingAvg`, and
`rails` (the subset of `["lightning", "usdc"]` this host accepts).

`GET /listings/<listingId>` adds the quote inputs: the listing's stalls as
`spots` (each with an `id`, `label`, `sizeClass`, and availability), per-event
prices, any series package offered, and `rails`. Choose one available stall and
use its `id` as `spotId` from here on. Exact street addresses are never in these
responses; they appear only on the Spot Pass after payment.

## 4. Quote

`POST /quotes` with:

```json
{
  "spotId": "spot_abc123",
  "eventIds": ["evt_louisville_vs_fsu"],
  "useSeasonPrice": false
}
```

Add `"seriesId": "<series id>"` (with every event id in that series in
`eventIds`) to price a season or festival package instead of single dates. The
response uses the same math a fan sees at checkout:

```json
{
  "hostSubtotalCents": 3500,
  "platformFeeCents": 420,
  "taxCents": 0,
  "civicFeeCents": 0,
  "totalCents": 3920,
  "includedEventCount": 1,
  "lines": [
    { "label": "Parking (1 event)", "amountCents": 3500 },
    { "label": "Service fee", "amountCents": 420 }
  ]
}
```

Tell the human the `totalCents` as dollars (here $39.20) and what it includes.
`totalCents` is exactly what will be paid — no further fees are added at
checkout.

## 5. Checkout — expect HTTP 402

`POST /checkouts` with the human's details:

```json
{
  "spotId": "spot_abc123",
  "eventIds": ["evt_louisville_vs_fsu"],
  "email": "frankie@example.com",
  "name": "Frankie Fan",
  "licensePlate": "KY ABC1234",
  "vehicleDetails": "Blue Honda CR-V",
  "useSeasonPrice": false
}
```

`useSeasonPrice` and `seriesId` are optional and must match the quote you
showed. The first call carries no payment proof, so the server holds the stall
for **30 minutes** and answers `402 Payment Required`. The JSON body repeats the
quote and identifies the hold:

```json
{
  "checkoutId": "chk_m1abc_x7y8z9",
  "status": "payment_required",
  "mode": "live",
  "expiresAt": "2026-09-19T17:30:00.000Z",
  "rails": ["lightning", "usdc"],
  "quote": { "totalCents": 3920, "hostSubtotalCents": 3500, "platformFeeCents": 420, "taxCents": 0, "civicFeeCents": 0 },
  "retryUrl": "https://www.fanparking.space/api/agent/v1/checkouts/chk_m1abc_x7y8z9"
}
```

`mode` is also reported by `GET /meta`. While the pilot runs in `"demo"` mode no
real funds move: the invoice is a placeholder and the 402 body carries a
`demo` object with a `lightningPreimage` and an `x402PaymentSignature` you may
send back as the proof in step 6. In `"live"` mode those demo proofs are
rejected and only real payments confirm a hold.

The payment instructions ride on headers — one per rail the host accepts, both
when the host set both wallets:

- **Lightning (L402):**
  `WWW-Authenticate: L402 token="<token>", invoice="<bolt11>"`.
  The invoice is issued from the host's Lightning Address for the full
  `totalCents`, converted to sats when the challenge is built. Pay it as issued.
- **USDC on Base (x402):**
  `PAYMENT-REQUIRED: <base64 JSON>` (x402 v2). Decoded, it contains
  `x402Version: 2` and an `accepts` list whose entry is
  `scheme: "exact"`, `network: "eip155:8453"`, `asset` = the USDC contract on
  Base, `amount` = `totalCents` as USDC atomic units (6 decimals, so
  $39.20 → `"39200000"`), and `payTo` = the host's USDC address:

  ```json
  {
    "x402Version": 2,
    "accepts": [
      {
        "scheme": "exact",
        "network": "eip155:8453",
        "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        "amount": "39200000",
        "payTo": "0xHostUsdcAddress0000000000000000000000000",
        "maxTimeoutSeconds": 1800
      }
    ]
  }
  ```

Retry the same hold at `POST /checkouts/<checkoutId>` with the same JSON body
plus the payment header from step 6. A hold is not a booking: nothing is
reserved for the human until you receive a `200`.

## 6. Pay

### 6a. Lightning (L402)

1. Parse the `WWW-Authenticate` header for `token` and `invoice`.
2. Pay the BOLT11 `invoice` with your Lightning wallet (for example through a
   Nostr Wallet Connect connection). Do not change the amount.
3. Keep the payment **preimage** (32 bytes, hex encoded).
4. Retry: `POST /checkouts/<checkoutId>` with
   `Authorization: L402 <token>:<preimage-hex>`.

The server checks that `sha256(preimage)` equals the invoice's payment hash;
no other proof is accepted.

### 6b. USDC on Base (x402)

1. Decode the `PAYMENT-REQUIRED` header.
2. Confirm `network` is `eip155:8453` and, when the listing detail showed the
   host's USDC address, that `payTo` matches it.
3. Sign the `exact` payment with your x402 client (an `@x402/fetch`-style
   wrapper does steps 1–4 automatically).
4. Retry: `POST /checkouts/<checkoutId>` with
   `PAYMENT-SIGNATURE: <base64 signed payload>`.

The server verifies and settles the payment; the `200` carries a
`PAYMENT-RESPONSE` header with the settlement details.

### 6c. No funds, or a rail you cannot pay

Do not retry the checkout. Tell the human the listing, the date, and the
`totalCents` in dollars, then send them to the browser to pay by card:

- direct booking (sign-in required):
  `https://www.fanparking.space/book/<listingId>?event=<eventId>`
- public listing page: `https://www.fanparking.space/spaces/<listingId>`

The browser checkout runs the same quote and the same 30-minute hold.

## 7. Save the confirmation

A `200` means the stall is reserved and a confirmation email is on its way to
the `email` you supplied:

```json
{
  "checkoutId": "chk_m1abc_x7y8z9",
  "status": "confirmed",
  "rail": "lightning",
  "paidCents": 3920,
  "reservations": [
    {
      "id": "res_01hzx",
      "eventId": "evt_louisville_vs_fsu",
      "confirmationCode": "7KQ2MZ4P",
      "passUrl": "https://www.fanparking.space/pass/res_01hzx?t=<signed-token>"
    }
  ],
  "pass": { "listingTitle": "…", "address": "…", "arrivalWindowStart": "…", "instructions": "…", "mapsLinks": { "apple": "…", "google": "…" } }
}
```

The `pass` object repeats what the Spot Pass page shows (exact address, arrival
window, host instructions, Maps links) so you can relay it without a browser.

Store `confirmationCode` and `passUrl` for every reservation and repeat both to
the human verbatim. Treat `passUrl` as opaque — open it as given, do not rebuild
it. The Spot Pass holds the QR code, the exact street address, host arrival
notes, and Maps links. On game day the human shows the pass at the
driveway: scan in, park, enjoy.

## 8. Cancellations and credits

Policy: https://www.fanparking.space/legal/cancellation. In short: cancel at
least 24 hours before the event and the host price returns as platform credit;
inside 24 hours the reservation is final; service fees are not credited; a
canceled event is credited automatically and a moved kickoff or post time moves
the reservation with it. Credits are platform credit, not funds returned to the
paying wallet. To cancel or ask about a credit, use the Spot Pass or email
hello@fanparking.space with the confirmation code.

## Rules

- Do not invent prices. Quote only what `/quotes` returns; never estimate a
  total from `priceCents` alone, and never round.
- Do not promise a guaranteed stall. A `402` is a 30-minute hold; only a `200`
  from checkout is a reservation.
- One checkout per stall per event. Do not open parallel holds for the same
  stall, and do not hold several stalls for one human "just in case".
- Holds expire after 30 minutes. If the retry reports the hold expired or the
  stall was taken (`409` / `410`), do not pay a stale invoice — start again from
  step 4.
- Pay exactly the challenge amount, to exactly the wallet in the challenge. The
  money goes straight to the host's wallet: host price + service fee + any tax
  or civic fee. FanParking does not collect its service fee on-chain in this
  version and never asks for a second payment.
- Confirm event, listing, and dollar total with the human before paying.
  Payments are final on-chain; refunds are platform credit under the policy.
- Never ask the human for wallet keys or seed phrases in chat. Use the wallet
  your operator configured.
- Never share an exact street address before the pass exists; the API only
  reveals it after payment.
- Relay `4xx` error messages plainly and fix the request; do not retry a `400`
  unchanged.
- Cite the product as FanParking.Space (short form: FanParking). Machine summary:
  https://www.fanparking.space/llms.txt

## Agent grants

Fans can issue a grant token (`fp_agent_…`) from their account. Send it as
`X-Agent-Grant` on `/me` and checkout. `GET /me` returns preferences, garage
vehicles, and capabilities. With a grant, `email` and `name` on checkout are
optional; pass `vehicleId` from the garage. Channel is `AGENT`, or
`AUTONOMOUS` when the grant has `autonomous=true`.

## Self check-in

When `/meta` reports `capabilities.selfCheckIn` and a listing's `checkInMode` is
`SELF` or `AUTO`, the fan (or a tokenized pass) can check in with GPS. Distance
is recorded and never blocks. Hosts get an SMS on arrival. `POST /api/v1/me`,
`/api/v1/listings/<id>/arrivals`, and `/api/v1/checkins` serve the staff PWA
with a Clerk bearer.

## Autonomous parking (when advertised)

If `/meta.capabilities.autonomousParking` is true, the catalog may include
`audience: AGENT_ONLY` listings with `checkInMode`, `driveMinutes`, and
`maxWalkMinutes`. Those stalls stay bookable until `endsAt` (after the event
starts). GPS check-in: `POST /checkouts/<checkoutId>/checkin` with lat/lng
and `X-Agent-Grant`. When the flag is off, hide agent-only listings and do not
offer autonomous drop lots.
