Skip to main content

API Documentation

The Skateparks.world REST API provides programmatic access to skatepark data. All endpoints require authentication.

Authentication Required

All API endpoints are protected. You must include a valid API key with every request. Contact api@skateparks.world to request access.

Authentication

Include your API key in every request using one of these methods:

Header (recommended)

X-API-Key: your_api_key_here

Bearer Token

Authorization: Bearer your_api_key_here

Example request

curl -X GET "https://skateparks.world/api/v1/parks" \
  -H "X-API-Key: your_api_key_here"

Base URL

All API endpoints are relative to:

https://skateparks.world/api/v1

Error Handling

The API uses standard HTTP status codes. Errors return a JSON object with an error field.

StatusMeaning
200Success
201Resource created
400Bad request — missing or invalid parameters
401Unauthorized — invalid or missing API key
404Resource not found
500Internal server error

Error response example

{
  "error": "Unauthorized"
}

Park Object

A park has the following fields. All fields except the three marked required are optional when creating or updating.

Identity

ParameterTypeRequiredDescription
namestringRequiredName of the skatepark.
countrystringRequiredCountry name (e.g. "Denmark").
citystringRequiredCity name (e.g. "Copenhagen").
slugstringOptionalURL slug. Auto-generated from name + city if omitted.
aliasstringOptionalAlternative/local name for the park.

Location

ParameterTypeRequiredDescription
streetAddressstringOptionalStreet address.
zipcodestringOptionalPostal/zip code.
latnumberOptionalLatitude coordinate.
lngnumberOptionalLongitude coordinate.

Classification

ParameterTypeRequiredDescription
typestringOptional"indoor", "outdoor", "both", or "unknown" (default).
statusstringOptional"open", "closed", "under_construction", or "unknown" (default).
ownershipstringOptionalWho owns the park (e.g. "Municipal", "Private").
managementstringOptionalWho manages/operates the park.
builtBystringOptionalConstruction company or builder.
designedBystringOptionalPark designer.

Features (arrays of strings)

ParameterTypeRequiredDescription
obstaclesstring[]Optionale.g. ["halfpipe", "quarterpipe", "rail", "ledge", "bowl", "funbox"].
facilitiesstring[]Optionale.g. ["lights", "toilets", "parking", "water", "shop", "rental"].
sportsstring[]Optionale.g. ["skateboarding", "bmx", "scooter", "inline"].
surfacestring[]Optionale.g. ["concrete", "wood", "asphalt", "metal"].
requirementsstring[]Optionale.g. ["helmet", "pads", "membership"].

Size & Year

ParameterTypeRequiredDescription
sizeSqmnumberOptionalPark area in square meters.
yearBuiltnumberOptionalYear the park was built.

Contact & Socials

ParameterTypeRequiredDescription
websitestringOptionalPark website URL.
phonestringOptionalPhone number.
emailstringOptionalContact email address.
facebookstringOptionalFacebook page URL.
instagramstringOptionalInstagram handle or URL.
youtubestringOptionalYouTube channel URL.

Pricing

ParameterTypeRequiredDescription
priceAmountnumberOptionalEntry price (e.g. 10.00). Null if free.
priceCurrencystringOptionalISO 4217 currency code (e.g. "USD", "EUR", "DKK").
paymentMethodsstring[]Optionale.g. ["cash", "card", "mobilepay"].
membershipAvailablebooleanOptionalWhether membership/season pass is available.

Operations

ParameterTypeRequiredDescription
supervisedbooleanOptionalWhether staff supervision is available.
surveilledbooleanOptionalWhether the park has camera surveillance.
hoursobjectOptionalOpening hours. See format below.
// hours format — each day is { open, close } or null (closed)
{
  "mon": { "open": "08:00", "close": "22:00" },
  "tue": { "open": "08:00", "close": "22:00" },
  "wed": { "open": "08:00", "close": "22:00" },
  "thu": { "open": "08:00", "close": "22:00" },
  "fri": { "open": "08:00", "close": "23:00" },
  "sat": { "open": "10:00", "close": "23:00" },
  "sun": null  // closed on Sunday
}

Content

ParameterTypeRequiredDescription
shortDescriptionstringOptionalBrief summary of the park.
descriptionstringOptionalFull description. Supports newlines.

Read-only fields (returned in responses)

ParameterTypeRequiredDescription
iduuidOptionalUnique park ID. Auto-generated.
countrySlugstringOptionalAuto-generated from country.
citySlugstringOptionalAuto-generated from city.
averageRatingnumberOptionalComputed average rating (1-5).
ratingCountnumberOptionalTotal number of ratings.
viewCountnumberOptionalPage view count.
featuredbooleanOptionalWhether the park is featured.
verifiedbooleanOptionalWhether location has been verified.
createdAttimestampOptionalISO 8601 creation date.
updatedAttimestampOptionalISO 8601 last update date.
imagesarrayOptionalRelated images (included in GET by ID).
videosarrayOptionalRelated videos (included in GET by ID).

Park Endpoints

GET/api/v1/parks

List all parks with pagination, or search by query.

Query Parameters

ParameterTypeRequiredDescription
qstringOptionalSearch query. If provided, returns matching parks instead of all.
pagenumberOptionalPage number (default: 1).
limitnumberOptionalResults per page. Min 1, max 100 (default: 50).

Response

{
  "parks": [ { "id": "...", "name": "...", ... } ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 1234,
    "totalPages": 25
  }
}
POST/api/v1/parks

Create a new skatepark. Only name, country, and city are required. All other park fields (see Park Object above) can be included.

Required fields

ParameterTypeRequiredDescription
namestringRequiredName of the skatepark.
countrystringRequiredCountry name.
citystringRequiredCity name.

Plus any optional fields from the Park Object reference above (obstacles, facilities, phone, email, hours, pricing, etc.).

Example request

{
  "name": "Faelledparken Skatepark",
  "country": "Denmark",
  "city": "Copenhagen",
  "lat": 55.6945,
  "lng": 12.5728,
  "type": "outdoor",
  "status": "open",
  "obstacles": ["bowl", "quarterpipe", "rail", "ledge"],
  "facilities": ["lights", "parking"],
  "sports": ["skateboarding", "scooter"],
  "surface": ["concrete"],
  "phone": "+45 12345678",
  "email": "info@example.com",
  "instagram": "@faelledskatepark",
  "description": "A popular outdoor skatepark in the heart of Copenhagen."
}

Response (201)

{
  "park": { "id": "...", "name": "Faelledparken Skatepark", "slug": "faelledparken-skatepark-copenhagen", ... }
}
GET/api/v1/parks/:id

Get a single park by ID with all details, images, and videos.

Response

{
  "park": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Faelledparken Skatepark",
    "slug": "faelledparken-skatepark-copenhagen",
    "country": "Denmark",
    "city": "Copenhagen",
    "streetAddress": "Øster Allé 1",
    "zipcode": "2100",
    "lat": 55.6945,
    "lng": 12.5728,
    "type": "outdoor",
    "status": "open",
    "obstacles": ["bowl", "quarterpipe", "rail", "ledge"],
    "facilities": ["lights", "parking"],
    "sports": ["skateboarding", "scooter"],
    "surface": ["concrete"],
    "phone": "+45 12345678",
    "email": "info@example.com",
    "website": "https://example.com",
    "instagram": "@faelledskatepark",
    "facebook": "",
    "youtube": "",
    "priceAmount": null,
    "priceCurrency": "",
    "paymentMethods": [],
    "membershipAvailable": null,
    "supervised": false,
    "surveilled": false,
    "hours": null,
    "sizeSqm": 1200,
    "yearBuilt": 2018,
    "shortDescription": "Popular concrete park in central Copenhagen.",
    "description": "A popular outdoor skatepark in the heart of Copenhagen...",
    "averageRating": "4.20",
    "ratingCount": 15,
    "verified": true,
    "images": [
      { "id": "...", "url": "https://...", "caption": "...", "credit": "...", "order": 0 }
    ],
    "videos": [],
    "createdAt": "2024-01-15T10:30:00.000Z",
    "updatedAt": "2025-11-20T14:22:00.000Z"
  }
}
PUT/api/v1/parks/:id

Update an existing park. Send only the fields you want to change. Accepts any field from the Park Object.

Example: update contact & features

{
  "phone": "+45 87654321",
  "email": "new@example.com",
  "instagram": "@newhandle",
  "obstacles": ["bowl", "halfpipe", "rail", "ledge", "funbox"],
  "hours": {
    "mon": { "open": "10:00", "close": "20:00" },
    "tue": { "open": "10:00", "close": "20:00" },
    "wed": { "open": "10:00", "close": "20:00" },
    "thu": { "open": "10:00", "close": "20:00" },
    "fri": { "open": "10:00", "close": "22:00" },
    "sat": { "open": "10:00", "close": "22:00" },
    "sun": null
  },
  "priceAmount": 5.00,
  "priceCurrency": "EUR"
}

Response

{
  "park": { "id": "...", "name": "...", ... }
}
DELETE/api/v1/parks/:id

Permanently delete a park and all related images, videos, and ratings.

Response

{ "success": true }
GET/api/v1/search

Search parks by name, city, or country.

Query Parameters

ParameterTypeRequiredDescription
qstringRequiredSearch query (must be non-empty).
limitnumberOptionalMax results. Min 1, max 50 (default: 20).

Response

{
  "parks": [ { "id": "...", "name": "...", ... } ],
  "query": "copenhagen",
  "count": 5
}

Coordinates

GET/api/v1/parks/coordinates

Get coordinates for all parks. Useful for rendering map markers. Cached for 1 hour.

Response

{
  "parks": [
    { "id": "...", "name": "...", "lat": 55.67, "lng": 12.56, ... },
    ...
  ]
}

Ratings

POST/api/v1/parks/:id/ratings

Submit a rating for a park. Rate-limited by IP address.

Request Body

ParameterTypeRequiredDescription
ratingnumberRequiredRating value, 1 to 5.
displayNamestringRequiredName to display with the review.
commentstringOptionalOptional review text.
emailstringOptionalOptional email (not displayed publicly).
createdAtstringOptionalISO 8601 timestamp. Defaults to now. Use this to backdate ratings found online (e.g. "2023-06-15T12:00:00Z").

Response (201)

{
  "rating": {
    "id": "...",
    "parkId": "...",
    "rating": 4,
    "displayName": "Alex",
    "comment": "Great park!",
    ...
  }
}

Images

POST/api/v1/parks/:id/images

Upload a photo for a park. Uses multipart/form-data. New uploads start with pending status (moderation required).

Request Body (multipart/form-data)

ParameterTypeRequiredDescription
imageFileRequiredImage file. Must be an image/* MIME type, max 10 MB.
emailstringRequiredUploader email (for moderation contact).
captionstringOptionalOptional image caption/alt text.
creditstringOptionalPhotographer credit / copyright attribution.

Example (curl)

curl -X POST "https://skateparks.world/api/v1/parks/:id/images" \
  -H "X-API-Key: your_api_key_here" \
  -F "image=@photo.jpg" \
  -F "email=user@example.com" \
  -F "caption=Front view of the park" \
  -F "credit=John Doe"

Response (201)

{
  "image": {
    "id": "...",
    "parkId": "...",
    "url": "https://...",
    "caption": "Front view of the park",
    "credit": "John Doe",
    "status": "pending",
    ...
  }
}

Suggestions

GET/api/v1/suggestions

List all pending suggestions.

Response

{
  "suggestions": [
    {
      "id": "...",
      "parkId": "...",
      "changeType": "update",
      "changes": { ... },
      "source": "user",
      "status": "pending",
      ...
    }
  ]
}
POST/api/v1/suggestions

Submit a suggestion for a new park or changes to an existing one.

Request Body

ParameterTypeRequiredDescription
changesobjectRequiredObject describing the proposed changes.
changeTypestringRequiredType of change, e.g. "update", "new_park", "correction".
parkIdstringOptionalID of the existing park (if updating).
sourcestringOptionalSource of the suggestion (default: "user").
sourceUrlstringOptionalURL reference for the data source.
submittedBystringOptionalName of the submitter.
emailstringOptionalContact email.

Response (201)

{
  "suggestion": {
    "id": "...",
    "changeType": "update",
    "changes": { ... },
    "source": "user",
    "status": "pending",
    ...
  }
}

Cache Revalidation

POST/api/v1/revalidate

Bust the Next.js cache for specific pages or the entire site. Call this after creating or updating parks to see changes immediately.

Request Body

Provide either path or all, not both.

ParameterTypeRequiredDescription
pathstringOptionalSpecific page path to revalidate, e.g. "/denmark/copenhagen/park-name".
allbooleanOptionalSet to true to revalidate all pages.

Examples

// Revalidate a specific park page
{ "path": "/denmark/copenhagen/faelledparken" }

// Revalidate the homepage
{ "path": "/" }

// Revalidate everything
{ "all": true }

Response

// Specific path
{ "revalidated": true, "path": "/denmark/copenhagen/faelledparken" }

// All pages
{ "revalidated": true, "scope": "all" }

Notes

Rate limiting

Rating submissions are rate-limited by IP address to prevent abuse.

Image moderation

Uploaded images start with a pending status and require approval before they appear publicly.

Cache behavior

Park pages are statically generated and cached. After creating or updating data, call the revalidation endpoint to see changes reflected on the site immediately.

Content-Type

All endpoints accept and return application/json, except the image upload endpoint which uses multipart/form-data.

Need an API key or have questions? api@skateparks.world