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). Use 0 for free admission; null means unknown.
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,
      "type": "outdoor", "status": "open", "flags": 13, ... },
    ...
  ]
}

flags packs the map filters into one number: 1 free, 2 lights, 4 skateboard, 8 BMX, 16 scooter, 32 inline (13 = free, skateboard and BMX).

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. Needs your API key (or a signed-in website account). New uploads start with pending status (moderation required); photo metadata such as GPS is removed.

Request Body (multipart/form-data)

ParameterTypeRequiredDescription
imageFileRequiredJPEG, PNG or WebP image (checked from the file itself), max 10 MB.
emailstringOptionalContact email for moderation (a signed-in account's own email is used instead).
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" }

iPhone App

The Skateparks.world iPhone app uses its own limited app key in X-API-Key. It can call only the endpoints below plus POST /suggestions; every other endpoint answers 403. Signed-in calls add Authorization: Bearer <access token> from the app sign-in endpoints, and Accept-Language: en or da localizes park text and error messages. Writes are rate-limited per IP address (429 with Retry-After), and suspended accounts get 403 on writes.

EndpointSign-inDescription
GET /app/statusNoService status and the minimum supported app version.
GET /app/parks/indexNoEvery park, compact. ETag / If-None-Match → 304.
GET /app/parks/:idNoFull park, localized; approved photos only.
GET /app/parks/:id/ratings?page=1OptionalRatings, newest first, 20 per page. With a token your own rating comes first.
GET /app/parks/:id/weatherNo7-day forecast, cached for an hour per park.
POST /app/reportsOptionalReport a rating or photo for review.
POST /app/auth/signupNoCreate an account (same accounts as the website).
POST /app/auth/loginNoSign in; returns access and refresh tokens.
POST /app/auth/refreshNoExchange a refresh token for a new session.
POST /app/auth/logoutYesRevoke the session's refresh token.
GET / PUT / DELETE /app/profileYesRead, update or delete the account.
GET /app/favouritesYesSaved park ids.
PUT / DELETE /app/favourites/:parkIdYesSave or remove a park.
POST /app/favourites/mergeYesMerge the phone's saved parks into the account.
PUT / DELETE /app/parks/:id/ratingYesCreate, update or delete your rating (1–5 stars, optional comment).
POST /app/parks/:id/imagesYesUpload a photo; it appears once approved.

Park index (cached on the phone)

GET /api/v1/app/parks/index
X-API-Key: your_app_key
If-None-Match: "etag-from-last-time"

→ 304 Not Modified, or 200 (gzip) with
{ "generatedAt": "…", "parks": [{ "id", "name", "slug", "citySlug", "countrySlug",
  "city", "country", "lat", "lng", "type", "status", "isFree", "sports",
  "hasLights", "averageRating", "ratingCount", "thumbnailUrl" }] }

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