ChatGPT Actions (OpenAI Actions)
Park Graph ships a fully-conformant OpenAI Actions integration: a public OpenAPI spec and a hosted plugin manifest that a custom GPT imports to get search, booking, lookup, and close operations directly against the v1 REST API — plus an optional read-only dispatcher endpoint for discovery. This page is the end-to-end tutorial — manifest, schema, bearer-token flow, and a worked example.
OpenAPI spec v1.4.0 · Recommended model: gpt-5 · Last updated June 2026
What ChatGPT can do with Park Graph
A ChatGPT custom GPT (or Team/Enterprise action) configured against Park Graph can do four user-visible things on behalf of a driver: search lots near a destination, start a paid parking session for a license plate, look up that session, and end it. Booking, lookup, and close are the createSession, getSession, and endSession REST operations that the custom GPT imports from the OpenAPI spec.
For lightweight, read-only discovery there is also a single dispatcher endpoint that bundles three actions (search_parking, check_availability, get_rates) behind one call. The Park Graph API enforces the same validation, scope, rate-limit, and audit-log rules that the dashboard sees, so an agent cannot do something a human operator cannot do.
Manifest (.well-known/ai-plugin.json)
Park Graph hosts the canonical plugin manifest at /.well-known/ai-plugin.json. Point a custom GPT at this URL during configuration and ChatGPT will discover the OpenAPI spec, auth scheme, and human-readable description automatically.
{
"schema_version": "v1",
"name_for_human": "Park Graph",
"name_for_model": "park_graph",
"description_for_human": "Find, hold, and pay for parking at any Park Graph–connected lot. QR-code receipts, real-time availability, dynamic pricing, no driver app required.",
"description_for_model": "Park Graph is a parking infrastructure platform. Use the search action to find lots near a coordinate or address; the create_session action to start a paid session at a chosen lot for a license plate; the get_session action to look up status; and the end_session action to close a session and capture payment. Sessions are billed by the minute against the lot's hourly or daily rate. Dynamic pricing (event-based, occupancy-based, surge) may change the rate before a session starts but never after.",
"auth": {
"type": "user_http",
"authorization_type": "bearer",
"instructions": "End users issue an API key from https://parkgraph.com/dashboard/api-keys and paste it into the plugin configuration. Live keys start with pk_live_; sandbox keys start with pk_test_."
},
"api": {
"type": "openapi",
"url": "https://parkgraph.com/api/agents/openai/openapi.yaml",
"is_user_authenticated": true
},
"logo_url": "https://parkgraph.com/logo.svg",
"contact_email": "developers@parkgraph.com",
"legal_info_url": "https://parkgraph.com/legal/terms"
}Two ways ChatGPT calls Park Graph
A custom GPT configured from the OpenAPI spec gets four operations directly: searchParking (GET /api/v1/lots/search), createSession (POST /api/v1/sessions), getSession (GET /api/v1/sessions/{sessionId}), and endSession (POST /api/v1/sessions/end). These are the same REST endpoints documented in the API reference, so booking, lookup, and close all run through the standard v1 API.
Park Graph also exposes an optional single-endpoint dispatcher at POST /api/agents/openai/actions for read-only discovery. It accepts a JSON body with exactly one action from the closed set search_parking, check_availability, and get_rates, plus a parameters object whose shape depends on the action. The dispatcher requires the lots:read scope, is rate-limited to 60 requests/minute, and is gated to Pro and Enterprise plans (see below).
{
"action": "search_parking",
"parameters": {
"lat": 39.6403,
"lng": -106.3742,
"radius_km": 5,
"limit": 5
}
}The dispatcher is not part of the imported OpenAPI document — its closed action set is enforced server-side. Valid action values are exactly search_parking, check_availability, and get_rates; any other value is rejected. To create, look up, or end a session, call the REST operations above.
Bearer-token (OAuth-style) flow
ChatGPT supports two authentication patterns for custom GPT actions: end-user-supplied bearer tokens and full OAuth 2.0 with PKCE. Park Graph supports both. For most installs we recommend the bearer-token path because it lets each operator ship their own GPT to their team in minutes; reserve the full OAuth flow for ChatGPT Enterprise rollouts where individual end users authenticate with their corporate identity provider.
Step 1
Operator opens https://parkgraph.com/dashboard/api-keys, names the key (e.g. 'ChatGPT custom GPT — sales team'), and clicks Create. Park Graph returns a one-time pk_live_… string.
Step 2
Operator pastes the pk_live_ key into the plugin's auth field inside the GPT builder. ChatGPT stores the key encrypted and forwards it as Authorization: Bearer pk_live_… on every action call.
Step 3
End users in the operator's workspace can now use the GPT without touching the API key. The dispatcher logs every call against the operator's audit log with action=, agent_platform=openai.
Step 4
Rotation: operator clicks Rotate in /dashboard/api-keys, edits the GPT to use the new key, and the old key is revoked instantly.
Worked example: end-to-end conversation
The transcript below shows ChatGPT helping a driver park near Vail Village. Each → arrow is an action call generated by ChatGPT and dispatched to Park Graph.
# 1. Read-only discovery — dispatcher action (or the searchParking REST op)
→ POST /api/agents/openai/actions
{ "action": "search_parking",
"parameters": { "lat": 39.6403, "lng": -106.3742, "radius_km": 1.5, "limit": 5 } }
← { "results": [
{ "id": "…", "name": "Vail Village Lifthouse Lot", "base_rate": 8.0 },
{ "id": "…", "name": "Lionshead Public Parking", "base_rate": 6.0 } ],
"agent_platform": "openai" }
ChatGPT: 'I see two good options: Lifthouse Lot ($8/hr) and Lionshead
Public ($6/hr). Which one?'
Driver: 'Lionshead. Plate is CO-7891.'
# 2. Booking is a write — the custom GPT calls the createSession REST operation
→ POST /api/v1/sessions
{ "lot_id": "…", "plate": "CO-7891" }
← { "session": { "id": "…", "session_code": "PG-VV7891",
"status": "active", "payment_intent_id": "pi_…",
"client_secret": "pi_…_secret_…" } }
ChatGPT: 'Done — session PG-VV7891 is active. Authorise the card on the
receipt page; I will remind you before your 4 hours are up.'
# 3. Closing the session — the endSession REST operation (4 hours later)
→ POST /api/v1/sessions/end
{ "session_id": "…", "session_code": "PG-VV7891" }
← { "session": { "status": "completed",
"subtotal": 24.12, "owner_payout": 21.71 } }Try it
The call below dispatches a read-only search_parking action (no session is created). Use it to confirm your bearer token before wiring up a custom GPT. Booking, lookup, and close go through the REST operations shown above.
curl -X POST https://parkgraph.com/api/agents/openai/actions \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{
"action": "search_parking",
"parameters": { "lat": 39.6403, "lng": -106.3742, "radius_km": 5, "limit": 5 }
}'Configuring a custom GPT step-by-step
1. ChatGPT → Explore GPTs → My GPTs → Create.
2. Configure tab → Actions → Create new action.
3. Authentication → API Key → Bearer token. Paste pk_live_….
4. Schema → Import from URL → https://parkgraph.com/api/agents/openai/openapi.yaml
5. (Optional) Privacy policy URL → https://parkgraph.com/legal/privacy
6. Save → Test → ask the GPT 'find me parking near 39.6403, -106.3742'.Plan gating & scopes
AI agent access is gated by plan. The dispatcher checks the operator's plan on every call and returns HTTP 403 with AI agent integrations require Pro or Enterprise plan for accounts on the free tier — this applies to every dispatcher action, including the read-only ones. Upgrade at /dashboard/billing.
Beyond the plan check, the bearer key must carry the right scopes: the dispatcher requires lots:read, and the booking REST operations ( createSession, endSession) require the session write scopes documented in the API reference. Every call is rate-limited (60 requests/minute on the dispatcher) and written to the operator's audit log with agent_platform=openai.
Related developer pages
- ChatGPT integration overview — broader context, conversation patterns, and prompt-engineering tips.
- API reference — the underlying REST endpoints that the dispatcher delegates to.
- MCP server (Claude) — the equivalent integration for Anthropic's Model Context Protocol.
- Microsoft Copilot plugin — the M365 plugin manifest that wraps the same dispatcher.
- ChatGPT parking booking landing page — public-facing page driving SERP traffic for "ChatGPT parking".
- Changelog — every API and dispatcher change since v1.0.
Security model
The Park Graph ChatGPT Actions dispatcher sits behind the same security perimeter as the dashboard and the public website. The notes below summarise the controls that apply to every ChatGPT Actions dispatcher request; a deeper write-up lives in the developer changelog.
Authentication
Every authenticated request carries an API key in the Authorization header (`Bearer pk_live_…`). Keys are issued per project from /dashboard/api-keys. Rotation is an HTTP DELETE + re-create in the dashboard; old keys are revoked immediately. Live and sandbox keys use the `pk_live_` and `pk_test_` prefixes so they cannot be confused in code review.
Transport security
TLS 1.3 with HSTS preloaded on every host (parkgraph.com, *.parkgraph.com). Plaintext requests are rejected at the edge with HTTP 426. Certificates are issued through the public Let's Encrypt CA and pinned in the CT logs.
Payment handling
Card data is tokenised inside Stripe Elements on the driver's device — Park Graph never observes raw PANs and is therefore SAQ-A scope. Sessions reference Stripe PaymentIntents by id only; webhooks are signed by Stripe using HMAC-SHA256 and verified server-side before any state change.
Data retention
Driver email addresses and license plates are retained for the lifetime of the operator's relationship with Park Graph (or 30 days after a verified delete request, whichever comes first). Search-only requests with no booking outcome are anonymised after 24 hours. Aggregated occupancy data has no personal identifiers and is retained indefinitely.
Audit trail
Every write — session create, end, extend, refund, rate change, agent registration — appends an immutable row to the audit_log table with actor type, actor id, and the diff. The log is exposed to operators through the dashboard activity feed and via a per-lot CSV export. Deletes are tombstoned, not hard-removed.
Rate limits
Limits are enforced per API key (or per IP for unauthenticated reads) using a rolling token bucket. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers; 429 responses additionally carry Retry-After. Bursts above the per-minute budget should be backed off with jitter — the official SDK does this automatically.
| Scope | Budget | Window |
|---|---|---|
| Per-action AI-agent calls (search, availability, rates) | 240 requests | per API key per minute |
| Agent-driven session creation | 60 requests | per API key per minute |
| Per-end-user agent fan-out (operator opt-in) | 20 requests | per end-user identifier per minute |
| Sandbox AI-agent calls | 30 requests | per key per minute |
Need a higher ceiling? Email developers@parkgraph.com with your projected throughput and we will lift the burst budget on a per-key basis.
Error codes
Every error response uses the same envelope: { "error": { "code": "…", "message": "…", "request_id": "req_…" } }. The HTTP status indicates the broad class; the code field disambiguates within a class. Surface the request_id to your support team — we can look up the full server-side trace by id alone.
| Code | HTTP status | Meaning |
|---|---|---|
| BAD_REQUEST | 400 | Required parameter missing or out of range. Response body lists the offending field. |
| UNAUTHORIZED | 401 | Missing or malformed Authorization header. Send `Authorization: Bearer pk_live_…`. |
| FORBIDDEN | 403 | API key is valid but the plan does not include this scope (e.g. agent or intelligence endpoints on Starter). |
| NOT_FOUND | 404 | Lot, session, or webhook id does not exist or has been deleted. |
| CONFLICT | 409 | Idempotent retry of a non-terminal session, or attempt to end an already-completed session. |
| RATE_LIMITED | 429 | Per-key burst budget exceeded. Inspect `Retry-After` and back off. |
| PAYLOAD_TOO_LARGE | 413 | Webhook delivery body or batch upload exceeds 1 MB. |
| INTERNAL | 500 | Unexpected server error. Park Graph automatically opens an incident and retries idempotent writes. |
| BAD_GATEWAY | 502 | Upstream payment processor returned an error. Safe to retry with the same idempotency key. |
AI-agent use cases
Park Graph is built for agentic distribution: every commercial endpoint (search, availability, rates, sessions, refunds) is callable by an LLM through MCP, OpenAI Actions, Gemini function declarations, xAI function calling, Perplexity Agent API, or Microsoft Copilot plugins. The grid below maps the most common agent-driven workflows on top of the ChatGPT Actions dispatcher.
Conversational booking
An end user asks ChatGPT, Claude, Gemini, Grok, or Perplexity to find parking near a destination. The model calls /lots/search, summarises 3-5 lots with prices and walking distance, then on confirmation calls /sessions to start a session and returns the QR-coded receipt URL.
Travel-app companion
A flight-booking or hotel-booking assistant pre-fetches arrival-airport parking for the trip dates and displays inline lot suggestions inside its itinerary view. The same surface backs in-app booking and a fallback web checkout.
Voice-first ordering
Realtime voice assistants (e.g. gpt-realtime, Gemini Live, Grok Voice) confirm a lot, plate, and duration verbally, then call /sessions and read back the session code. Drivers never touch a screen until they walk away from the car.
Fleet dispatcher
An autonomous-vehicle fleet (rideshare, delivery, robotaxi) holds AV-fleet allocations with /av-fleet/dispatch, then commits the spot at handoff. See /developers/av-fleet for the AV-specific contract.
Procurement / expense agent
An accounts-payable agent reconciles staff parking against company-card statements by querying /sessions/search by plate, then files reimbursements through the agent's own ledger.
City planning analytics
A research agent queries /api/v2/intelligence/market-rate and /api/v2/intelligence/agent-demand for an address corridor, then drafts a report on rate elasticity and AI-agent traffic.
Architecture
Every Park Graph request — whether it arrives from the ChatGPT Actions dispatcher, the dashboard, the QR-driven mobile checkout, or an agent — runs through the same Next.js Edge front door, hits the same Postgres-backed core, and shares the same audit log and webhook fan-out. There is no divergent code path between "agent traffic" and "human traffic": anything an agent can do, a human can do, and vice versa.
Sandbox & getting help
Every operator gets a sandbox key prefixed with pk_test_. Sandbox traffic uses Stripe test mode, synthetic lots, and never charges a real card. Spin one up at /developers/sandbox, then point your client at https://parkgraph.com/api/v1 exactly as you would in production.
For integration help, reach the developer team at developers@parkgraph.com or open an issue on the public GitHub org. Status and incident history live at /developers/changelog; subscribe to the JSON feed for machine-readable updates.