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)
Request Body — application/x-www-form-urlencoded
| Field | Type | Description | |
|---|---|---|---|
| username | string | required | Email address |
| password | string | required | Password |
Response 200
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "bearer"
}
POST
/studio/auth/refresh
Public
Exchange a refresh token for a new access token
Request Body — JSON
{ "refresh_token": "eyJ..." }
Response 200
{ "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
Response 200
{
"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
Request Body — JSON
{ "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.
Query Parameters
| Param | Type | Default | |
|---|---|---|---|
| skip | int | optional | 0 |
| limit | int | optional | 20 (max 100) |
Response 200
{
"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
Response 200
{
"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.
Query Parameters
| Param | Type | Description | |
|---|---|---|---|
| cruise_line | string | optional | Filter by cruise line slug |
| skip | int | optional | 0 |
| limit | int | optional | 20 (max 100) |
Key Response Fields
{
"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.
Query Parameters
| Param | Type | Description | |
|---|---|---|---|
| region | string | optional | e.g. Caribbean, Alaska, Mediterranean |
| port_type | string | optional | homeport | port_of_call | private_island |
| skip / limit | int | optional | Pagination |
Key Response Fields
{
"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.
Query Parameters
| Param | Type | |
|---|---|---|
| cruise_line | string | optional |
| cruise_region | string | optional |
| nights_min / nights_max | int | optional |
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.
Query Parameters
| Param | Type | |
|---|---|---|
| ship | string | optional |
| itinerary | string | optional |
| date_from | date | optional |
| date_to | date | optional |
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.
Query Parameters
| Param | Type | |
|---|---|---|
| company | string | optional |
| state | string | optional |
| skip / limit | int | optional |
Key Response Fields
{
"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.
Query Parameters
| Param | Type | |
|---|---|---|
| theme_park | string | optional |
| thrill_level | string | optional |
| ride_type | string | optional |
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.
Query Parameters
| Param | Type | |
|---|---|---|
| theme_park | string | optional |
| date_from / date_to | date | optional |
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
Query Parameters
| Param | Type | |
|---|---|---|
| venue_type | string | optional |
| cuisine_type | string | optional |
| reservation_required | bool | optional |
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
Response 200
{
"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
Response 200
{
"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
Query Parameters
| Param | Type | |
|---|---|---|
| q | string | required |
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
Required fields
{ "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
Required fields
{ "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
Body
{ "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
Required fields
{ "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
Required fields
{ "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
Request — multipart/form-data
| Field | Type | Description | |
|---|---|---|---|
| file | file | required | JPEG, PNG, WebP or GIF. Max 5 MB. |
| content_type | string | required | e.g. ships, theme-parks |
| slug | string | required | Resource slug e.g. magic-kingdom |
| filename | string | required | Filename without extension e.g. hero |
Response 200
{
"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
Query Params
?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
Body
{ "name": "WordPress Production", "notes": "Main site", "allowed_domains": ["cruiseandthemeparkguide.com"] }
Response 201
{ "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
Body
{ "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
Body
{ "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
Response
{ "results": [{ "park_name": "Magic Kingdom Park", "destination": "Walt Disney World Resort", "entity_id": "75ea578a-..." }] }