API Endpoints

All API requests require authentication via OAuth 2.0 access token.

Base URL: https://parkdly.com/api/v1

Authentication

Include your access token in the Authorization header:

Authorization: Bearer {access_token}

Courses

List User's Courses

Get all courses linked to your app for the authenticated user.

GET /api/v1/courses

Response:

{
  "courses": [
    {
      "id": "course_123",
      "externalCourseId": "your-app-course-id",
      "name": "Example DGC",
      "location": {
        "lat": 60.1699,
        "lng": 24.9384
      },
      "studioUrl": "/studio/destinations/dest_456/courses/course_123",
      "fairways": [
        {
          "id": "fairway_1",
          "number": 1,
          "par": 3,
          "distance": 100,
          "teePosition": {
            "lat": 60.1699,
            "lng": 24.9384
          },
          "targetPosition": {
            "lat": 60.17,
            "lng": 24.94
          },
          "flightPath": [
            {
              "order": 0,
              "lat": 60.1699,
              "lng": 24.9384
            },
            {
              "order": 1,
              "lat": 60.16995,
              "lng": 24.9392
            },
            {
              "order": 2,
              "lat": 60.17,
              "lng": 24.94
            }
          ]
        }
      ],
      "createdAt": "2025-01-15T10:00:00Z",
      "updatedAt": "2025-01-15T12:00:00Z"
    }
  ],
  "total": 1
}

Get Course Details

Get detailed information about a specific course linked to your app.

GET /api/v1/courses/{courseId}

Response:

{
  "id": "course_123",
  "externalCourseId": "your-app-course-id",
  "name": "Example DGC",
  "location": {
    "lat": 60.1699,
    "lng": 24.9384
  },
  "studioUrl": "/studio/destinations/dest_456/courses/course_123",
  "fairways": [
    {
      "id": "fairway_1",
      "number": 1,
      "par": 3,
      "distance": 100,
      "teePosition": {
        "lat": 60.1699,
        "lng": 24.9384
      },
      "targetPosition": {
        "lat": 60.17,
        "lng": 24.94
      },
      "flightPath": [
        {
          "order": 0,
          "lat": 60.1699,
          "lng": 24.9384
        },
        {
          "order": 1,
          "lat": 60.16995,
          "lng": 24.9392
        },
        {
          "order": 2,
          "lat": 60.17,
          "lng": 24.94
        }
      ]
    },
    {
      "id": "fairway_2",
      "number": 2,
      "par": 4,
      "distance": 150,
      "teePosition": {
        "lat": 60.1701,
        "lng": 24.9386
      },
      "targetPosition": {
        "lat": 60.1702,
        "lng": 24.9402
      },
      "flightPath": null
    }
  ],
  "createdAt": "2025-01-15T10:00:00Z",
  "updatedAt": "2025-01-15T12:00:00Z"
}

Create Course

Create a new course and link it to your app. Optionally include course location and fairway data with tee and target positions.

POST /api/v1/courses

Request Body:

{
  "externalCourseId": "your-app-course-id",
  "name": "New Course Name",
  "location": {
    "lat": 60.1699,
    "lng": 24.9384
  },
  "fairways": [
    {
      "number": 1,
      "teePosition": {
        "lat": 60.1699,
        "lng": 24.9384
      },
      "targetPosition": {
        "lat": 60.1708,
        "lng": 24.9384
      },
      "flightPath": [
        { "order": 0, "lat": 60.1701, "lng": 24.9384 },
        { "order": 1, "lat": 60.1703, "lng": 24.9384 },
        { "order": 2, "lat": 60.1706, "lng": 24.9384 }
      ]
    },
    {
      "number": 2,
      "teePosition": {
        "lat": 60.1708,
        "lng": 24.9384
      },
      "targetPosition": {
        "lat": 60.1718,
        "lng": 24.9384
      }
    }
  ]
}

Minimal Request (name only):

{
  "externalCourseId": "your-app-course-id",
  "name": "New Course Name"
}

Request with location only (no fairways):

{
  "externalCourseId": "your-app-course-id",
  "name": "New Course Name",
  "location": {
    "lat": 60.1699,
    "lng": 24.9384
  }
}

Response (with fairways):

{
  "id": "course_123",
  "externalCourseId": "your-app-course-id",
  "name": "New Course Name",
  "location": {
    "lat": 60.1699,
    "lng": 24.9384
  },
  "studioUrl": "/studio/destinations/dest_456/courses/course_123",
  "fairways": [
    {
      "id": "fairway_1",
      "number": 1,
      "par": 3,
      "distance": 100,
      "teePosition": {
        "lat": 60.1699,
        "lng": 24.9384
      },
      "targetPosition": {
        "lat": 60.1708,
        "lng": 24.9384
      },
      "flightPath": null
    },
    {
      "id": "fairway_2",
      "number": 2,
      "par": 3,
      "distance": 111,
      "teePosition": {
        "lat": 60.1708,
        "lng": 24.9384
      },
      "targetPosition": {
        "lat": 60.1718,
        "lng": 24.9384
      },
      "flightPath": null
    }
  ],
  "createdAt": "2025-01-15T10:00:00Z",
  "updatedAt": "2025-01-15T10:00:00Z"
}

Response (without fairways):

{
  "id": "course_123",
  "externalCourseId": "your-app-course-id",
  "name": "New Course Name",
  "location": {
    "lat": 60.1699,
    "lng": 24.9384
  },
  "studioUrl": "/studio/destinations/dest_456/courses/course_123",
  "fairways": [],
  "createdAt": "2025-01-15T10:00:00Z",
  "updatedAt": "2025-01-15T10:00:00Z"
}

Fairway Input Fields:

FieldTypeRequiredDescription
numbernumberYesFairway number (1-27)
teePositionobjectYesTee coordinates {lat, lng}
targetPositionobjectYesTarget coordinates {lat, lng}
flightPatharrayNoFlight path control points [{order, lat, lng}]

Fairway Validation Rules:

  • fairways array is optional (can be omitted or empty)
  • Maximum 27 fairways per course
  • Fairway numbers must be unique within the course
  • Fairway numbers must be between 1 and 27
  • Coordinates must be valid: latitude (-90 to 90), longitude (-180 to 180)
  • Tee and target must not be at the same position (minimum 0.1m apart)
  • Maximum distance between tee and target: 1000 meters
  • All fairways must fit within a 5 km² area (to ensure compatibility with map area selection)
  • Calculated values (set automatically):
    • distance: Calculated using haversine formula from tee to target position (in meters)
    • par: Estimated based on distance (< 150m = 3, < 250m = 4, otherwise = 5)

Flight Path Rules:

  • flightPath is optional per fairway — when omitted, a midpoint control point is auto-generated
  • flightPath format matches the GET response format: [{order, lat, lng}]
  • Control point order must be 0-indexed, sequential, with no gaps or duplicates
  • Minimum 1 control point, maximum 50 per fairway
  • Coordinates must be valid: latitude (-90 to 90), longitude (-180 to 180)

Data Model

Course Object

FieldTypeDescription
idstringParkdly course ID
externalCourseIdstringYour app's course identifier
namestringCourse name
locationobject | nullCourse location {lat, lng}. Used as default map center in Parkdly Studio.
studioUrlstringRelative path to the course editor in Parkdly Studio. Prepend your Parkdly host (e.g. https://parkdly.com) to open in browser.
fairwaysarrayArray of fairway objects (see below)
createdAtstringISO 8601 timestamp
updatedAtstringISO 8601 timestamp

Fairway Object

FieldTypeDescription
idstringFairway ID
numbernumberFairway number (1-18+)
parnumber | nullPar for this fairway
distancenumber | nullDistance in meters
teePositionobject | nullTee coordinates {lat, lng}
targetPositionobject | nullTarget coordinates {lat, lng}
flightPatharray | nullOrdered control points for PRIMARY flight path (see below)

Flight Path Control Point

FieldTypeDescription
ordernumberControl point sequence (0-indexed, ascending from tee to target)
latnumberLatitude
lngnumberLongitude

Notes:

  • All distance values are in meters
  • flightPath returns the PRIMARY flight path only (used for recommended disc flight trajectory)
  • flightPath is null if no flight path is defined for the fairway
  • teePosition and targetPosition are null if not set
  • Control points are ordered by the order field (ascending): order 0 is closest to tee, higher numbers closer to target
  • The complete flight path is: tee → control points (ordered) → target

Response Format

Success (2xx): Returns the data object directly

Error (4xx/5xx):

{
  "error": "error_code",
  "message": "Human readable error message"
}

Rate Limiting

See Rate Limits for details on request limits.

Next Steps