CTPG Platform API

Base URL: https://api.cruiseandthemeparkguide.com
All responses are JSON. Authenticated endpoints require a Bearer token obtained from POST /studio/auth/login. Public endpoints (/api/v1/*) are rate-limited at 120 req/min per IP.

GET POST PATCH DELETE Public Studio — Bearer token Internal — API key
Authentication
POST /studio/auth/login Public Obtain access + refresh token pair (form body)
FieldTypeDescription
usernamestringrequiredEmail address
passwordstringrequiredPassword
{ "access_token": "eyJ...", "refresh_token": "eyJ...", "token_type": "bearer" }
POST /studio/auth/refresh Public Exchange a refresh token for a new access token
{ "refresh_token": "eyJ..." }
{ "access_token": "eyJ...", "token_type": "bearer" }
POST /studio/auth/logout Bearer Revoke current session tokens
No request body. Returns 204 No Content on success.
GET /studio/auth/me Bearer Return the authenticated user's profile
{ "data": { "id": "uuid", "email": "user@example.com", "full_name": "Jane Doe", "role": "admin", "is_active": true } }
POST /studio/auth/change-password Bearer Change password for the authenticated user
{ "current_password": "...", "new_password": "..." }
Returns 204 No Content on success.
Cruise Lines
GET /api/v1/cruise-lines Public List all active cruise lines. Supports pagination.
ParamTypeDefault
skipintoptional0
limitintoptional20 (max 100)
{ "data": [{ "id": "uuid", "name": "Royal Caribbean", "slug": "royal-caribbean", ... }], "total": 10, "skip": 0, "limit": 20 }
GET /api/v1/cruise-lines/{slug} Public Get a single cruise line by slug
{ "data": { "id": "uuid", "name": "Royal Caribbean International", "slug": "royal-caribbean", "description": "...", "logo_url": "https://media.cruiseandthemeparkguide.com/...", "website_url": "https://www.royalcaribbean.com", "is_active": true, "created_at": "2025-01-01T00:00:00Z" } }
Ships
GET /api/v1/ships Public List ships. Filter by cruise line slug.
ParamTypeDescription
cruise_linestringoptionalFilter by cruise line slug
skipintoptional0
limitintoptional20 (max 100)
{ "data": [{ "id": "uuid", "name": "Icon of the Seas", "slug": "icon-of-the-seas", "ship_class": "Icon Class", "year_built": 2024, "passenger_capacity": 5610, "gross_tonnage": 250800, "num_decks": 20, "ship_length_meters": "364.90", "imo_number": "9839290", "has_casino": true, "has_water_park": true, ... }] }
GET /api/v1/ships/{slug} Public Full ship detail by slug
Returns the complete ship record including all 32+ spec fields.
Ports
GET /api/v1/ports Public List ports. Filter by region or port_type.
ParamTypeDescription
regionstringoptionale.g. Caribbean, Alaska, Mediterranean
port_typestringoptionalhomeport | port_of_call | private_island
skip / limitintoptionalPagination
{ "data": [{ "name": "Port Canaveral", "slug": "port-canaveral", "city": "Cape Canaveral", "state_province": "Florida", "country": "United States", "latitude": "28.415200", "longitude": "-80.620700", "timezone": "America/New_York", "port_type": "homeport" }] }
GET /api/v1/ports/{slug} Public Port detail by slug
Includes full geo data (lat/lng, timezone) and port type.
GET /api/v1/ports/{slug}/excursions Public Shore excursions available at a specific port
Returns excursions linked to this port (stub — returns empty list until excursion data is seeded).
Itineraries
GET /api/v1/itineraries Public List itineraries. Filter by cruise line, region, or nights.
ParamType
cruise_linestringoptional
cruise_regionstringoptional
nights_min / nights_maxintoptional
GET /api/v1/itineraries/{slug} Public Single itinerary with port-of-call list
Includes embarkation_port, disembarkation_port, cruise_region, and ordered ports array.
Sailings
GET /api/v1/sailings Public Upcoming sailings. Filter by ship, itinerary, date range.
ParamType
shipstringoptional
itinerarystringoptional
date_fromdateoptional
date_todateoptional
Excursions
GET /api/excursions Public All excursions (shore + land)
Supports skip / limit pagination.
GET /api/excursions/shore Public Shore excursions only
Filters excursion_type = shore.
GET /api/excursions/land Public Land excursions only (theme park + attraction packages)
Filters excursion_type = land.
GET /api/excursions/{slug} Public Single excursion detail
Theme Parks
GET /api/v1/theme-parks Public List theme parks. Filter by parent company or state.
ParamType
companystringoptional
statestringoptional
skip / limitintoptional
{ "data": [{ "name": "Magic Kingdom Park", "slug": "magic-kingdom", "tpw_entity_id": "75ea578a-adc8-4116-a54d-dccb60765ef9", "city": "Lake Buena Vista", "state_province": "Florida", "latitude": "28.418500", "longitude": "-80.580700", "admission_price_from": "109.00", "annual_visitors": 17700000 }] }
GET /api/v1/theme-parks/{slug} Public Full theme park record including geo, pricing, and TPW entity ID
Resorts
GET /api/v1/resorts Public List hotel resorts within a theme park destination
Supports filtering by theme_park slug and standard pagination.
GET /api/v1/resorts/{slug} Public Resort detail including amenities and pricing
Attractions
GET /api/v1/attractions Public List attractions. Filter by park, thrill level, or ride type.
ParamType
theme_parkstringoptional
thrill_levelstringoptional
ride_typestringoptional
GET /api/v1/attractions/{slug} Public Attraction detail including height requirements, TPW entity ID, and accessibility info
Events
GET /api/v1/events Public Upcoming and current special events. Filter by park or date range.
ParamType
theme_parkstringoptional
date_from / date_todateoptional
GET /api/v1/events/{slug} Public Event detail including ticket price and schedule
Dining
GET /api/v1/dining Public Dining locations for parks, ships, and resorts
ParamType
venue_typestringoptional
cuisine_typestringoptional
reservation_requiredbooloptional
GET /api/v1/dining/{slug} Public Dining location detail including dress code, price range, and hours
Live Data
Live data is fetched from themeparks.wiki on demand — never cached. Requires a valid tpw_entity_id on the ThemePark record. Returns 502 if the upstream source is unavailable.
GET /api/v1/theme-parks/{slug}/live Public Real-time wait times and ride status for all attractions in the park
{ "liveData": [ { "id": "uuid", "name": "Guardians of the Galaxy", "status": "OPERATING", "queue": { "STANDBY": { "waitTime": 45 }, "SINGLE_RIDER": { "waitTime": 20 } } }, ... ] }
GET /api/v1/theme-parks/{slug}/schedule Public Park operating hours and entertainment schedule
{ "schedule": [ { "date": "2026-05-17", "type": "OPERATING", "openingTime": "2026-05-17T09:00:00-04:00", "closingTime": "2026-05-17T22:00:00-04:00" } ] }
GET /api/v1/attractions/{slug}/live Public Single attraction live status and current wait time
Content
GET/api/guidesPublicList published travel guides
GET/api/guides/{slug}PublicSingle guide by slug
GET/api/videosPublicList video content
GET/api/videos/{slug}PublicSingle video by slug
GET/api/searchPublicFull-text search across all content types
ParamType
qstringrequired
POST/api/leadsPublicSubmit a travel planning lead (name, email, trip interests)
Rate limited to 5 requests/minute per IP.
Categories
GET/api/categoriesPublicList all content categories
GET/api/categories/{slug}PublicCategory detail with nested content items
Studio — Cruises Bearer required
All /studio/* endpoints require Authorization: Bearer <access_token>. Studio users must have role admin or editor.
GET/studio/cruise-linesBearerList all cruise lines (active + inactive)
POST/studio/cruise-linesBearerCreate a cruise line — returns 201
{ "name": "string", "slug": "string" }
GET/studio/cruise-lines/{id}BearerGet cruise line by UUID
PATCH/studio/cruise-lines/{id}BearerPartial update — only include fields to change
DELETE/studio/cruise-lines/{id}BearerDelete cruise line — returns 204. Cascades to ships.
GET/studio/shipsBearerList ships (studio view, includes inactive)
POST/studio/shipsBearerCreate ship — returns 201
{ "name": "string", "slug": "string", "cruise_line_id": "uuid" }
PATCH/studio/ships/{id}BearerUpdate ship fields
DELETE/studio/ships/{id}BearerDelete ship — returns 204
POST/studio/ships/{id}/dry-docksBearerRecord a dry-dock period — returns 201
{ "start_date": "YYYY-MM-DD", "end_date": "YYYY-MM-DD", "shipyard": "string" }
DELETE/studio/ships/{id}/dry-docks/{dry_dock_id}BearerRemove dry-dock record
GET/studio/portsBearerList ports (studio)
POST/studio/portsBearerCreate port
{ "name": "string", "slug": "string", "country": "string" }
PATCH/studio/ports/{id}BearerUpdate port
DELETE/studio/ports/{id}BearerDelete port
Studio — Theme Parks Bearer required
GET/studio/theme-parksBearerList theme parks (studio)
POST/studio/theme-parksBearerCreate theme park — returns 201
{ "name": "string", "slug": "string" }
PATCH/studio/theme-parks/{id}BearerUpdate theme park — set tpw_entity_id to enable live data
DELETE/studio/theme-parks/{id}BearerDelete theme park
GET/studio/resortsBearerList resorts (studio)
POST/studio/resortsBearerCreate resort
PATCH/studio/resorts/{id}BearerUpdate resort
DELETE/studio/resorts/{id}BearerDelete resort
Studio — Media
POST /studio/media/upload Bearer Upload an image to Cloudflare R2 — returns public CDN URL
FieldTypeDescription
filefilerequiredJPEG, PNG, WebP or GIF. Max 5 MB.
content_typestringrequirede.g. ships, theme-parks
slugstringrequiredResource slug e.g. magic-kingdom
filenamestringrequiredFilename without extension e.g. hero
{ "url": "https://media.cruiseandthemeparkguide.com/images/ships/icon-of-the-seas/hero.webp", "key": "images/ships/icon-of-the-seas/hero.webp", "size": 204800 }
Studio — Analytics
GET/studio/analytics/overviewBearerHigh-level stats: total requests, error rate, avg response time
?days=30 (default 30, options: 7 | 30 | 90)
GET/studio/analytics/requests-over-timeBearerDaily request counts over the selected window
GET/studio/analytics/top-endpointsBearerMost-called endpoints ranked by request count
GET/studio/analytics/by-clientBearerRequest breakdown per API client key
GET/studio/analytics/status-codesBearerDistribution of HTTP status codes
Studio — System
GET/studio/api-clientsBearerList all API clients and their usage stats
POST/studio/api-clientsBearerCreate API client — raw key shown once in response
{ "name": "WordPress Production", "notes": "Main site", "allowed_domains": ["cruiseandthemeparkguide.com"] }
{ "id": "uuid", "raw_key": "ctpg_live_abc123...", "key_prefix": "ctpg_live_", ... }
The raw_key is only returned on creation and cannot be retrieved again.
PATCH/studio/api-clients/{id}BearerUpdate client name, notes, or domain allowlist
POST/studio/api-clients/{id}/revokeBearerImmediately revoke client API key access
POST/studio/api-clients/{id}/regenerateBearerIssue a new key — old key immediately invalid
DELETE/studio/api-clients/{id}BearerDelete client record
GET/studio/leadsBearerList travel planning leads submitted via /api/leads
GET/studio/logs/auditBearerAudit log of all studio write actions
GET/studio/logs/api-usageBearerRaw API request log with IP, client, endpoint, and response time
Credential Vault
Values are stored encrypted. The secret value is never returned after creation — only the metadata is readable.
GET/studio/vaultBearerList all vault entries (metadata only, no secret values)
POST/studio/vaultBearerStore a new secret
{ "key_name": "STRIPE_SECRET", "service_name": "Stripe", "environment": "production", "value": "sk_live_..." }
PATCH/studio/vault/{id}BearerUpdate metadata (not the secret value — use /rotate for that)
POST/studio/vault/{id}/rotateBearerReplace the secret value — sets rotated_at timestamp
{ "new_value": "sk_live_newkey..." }
DELETE/studio/vault/{id}BearerPermanently delete vault entry
TPW Sync
Utilities for syncing themeparks.wiki data into the platform database. Data is sourced live from https://api.themeparks.wiki/v1.
GET/studio/tpw/known-parksBearerReturn the hardcoded slug → entity_id map used as a fallback
GET/studio/tpw/search?q={query}BearerSearch TPW destinations for a park by name — returns entity IDs to link
{ "results": [{ "park_name": "Magic Kingdom Park", "destination": "Walt Disney World Resort", "entity_id": "75ea578a-..." }] }