SendLog climbing header

Partner API

Partner API Reference

Read-only access to your climbing data for third-party apps and scripts

Base URL https://api-hoxktcdqvq-uc.a.run.app

Contents

  1. Authentication
  2. Rate Limiting
  3. Pagination
  4. Errors
  5. Data Endpoints
  6. Incremental Sync
  7. Examples

1. Authentication

Two schemes are used depending on the endpoint.

All requests must include the user's API key in the X-API-Key header:

X-API-Key: cnl_your_key_here

Users generate keys in the SendLog Web App under My Account → API Keys.

2. Rate Limiting

Each API key is limited to 1 000 requests per hour. The window resets on a rolling basis per key.

When the limit is exceeded:

HTTP 429 Too Many Requests
{ "error": "Rate limit exceeded (1000 req/hour)" }

4. Errors

All errors return a JSON body with a human-readable error field:

{ "error": "Required scopes: climbs:read" }
StatusMeaning
400Bad request — invalid query parameter (e.g. non-ISO 8601 date)
401Missing or invalid credentials
403Valid key but missing a required scope
404Resource not found or deleted
429Rate limit exceeded
500Internal server error

5. Data Endpoints

All data endpoints authenticate with X-API-Key and return data belonging to the user who generated the key.

GET

/v1/climbs

climbs:read

List climb notes (sends and projects). Ordered by date ascending by default, or by updatedAt ascending when since is used.

Query parameters

ParameterTypeDescription
limitintegerResults per page (1–100, default 50)
afterstringPagination cursor from nextCursor
fromISO 8601 dateClimbs on or after this date
toISO 8601 dateClimbs on or before this date
sinceISO 8601 datetimeOnly records updated after this timestamp. Mutually exclusive with from/to.
includestringComma-separated list of optional fields. Currently supported: gps.

Example

curl "https://api-hoxktcdqvq-uc.a.run.app/v1/climbs?from=2026-01-01&limit=20" \
  -H "X-API-Key: cnl_your_key_here"

Response 200 OK

{
  "data": [
    {
      "id": "abc123",
      "route": "Biographie",
      "climbingArea": "Céüse",
      "crag": "La Face",
      "difficulty": "9a",
      "sendType": "Redpoint",
      "routeType": "Sport",
      "noteText": "Dream route, finally!",
      "rating": 5,
      "attemptCount": 47,
      "date": "2026-08-15T00:00:00.000Z",
      "lastAttemptDate": null,
      "projectStatus": null,
      "projectNotes": null,
      "highPoint": null,
      "centralRouteID": "rTeho8rot8CTj9kEvZE4",
      "updatedAt": "2026-08-15T18:30:00.000Z"
    }
  ],
  "nextCursor": null,
  "hasMore": false
}

Climb object fields

FieldTypeDescription
idstringFirestore document ID
routestringRoute name
climbingAreastringBroader area / region
cragstring | nullSpecific crag or wall
difficultystringGrade (French, UIAA, or YDS depending on user preference)
sendTypestringRedpoint · Pinkpoint · On Sight · Flash · Top Rope · All Free · Project
routeTypestringSport · Boulder · Multi-Pitch
noteTextstring | nullFree-text notes
ratinginteger0–5 stars
attemptCountintegerNumber of attempts logged
dateISO 8601 | nullSend date (null for active projects)
lastAttemptDateISO 8601 | nullLast attempt date (projects)
projectStatusstring | nullWorking · Close · On Hold
projectNotesstring | nullProject-specific notes
highPointstring | nullHighest point reached (projects)
centralRouteIDstring | nullID of the linked entry in the SendLog central route database, or null if not linked
updatedAtISO 8601 | nullLast modification timestamp

Optional: GPS coordinates (?include=gps)

Add ?include=gps to include GPS coordinates sourced from the SendLog central route database. When requested, each climb object gains a gps field:

  • gps is a coordinates object if the linked central route has GPS stored.
  • gps is null if the climb has no centralRouteID, or if the central route does not yet have GPS coordinates.
  • Without ?include=gps the gps key is absent entirely — existing integrations are unaffected.
curl "https://api-hoxktcdqvq-uc.a.run.app/v1/climbs?include=gps" \
  -H "X-API-Key: cnl_your_key_here"
{
  "data": [
    {
      "id": "abc123",
      "route": "Biographie",
      "centralRouteID": "rTeho8rot8CTj9kEvZE4",
      "gps": { "latitude": 44.1823, "longitude": 5.9714, "country": "FR" },
      ...
    },
    {
      "id": "def456",
      "route": "My Local Project",
      "centralRouteID": null,
      "gps": null,
      ...
    }
  ]
}
GPS fieldTypeDescription
latitudenumberWGS84 latitude
longitudenumberWGS84 longitude
countrystring | nullISO 3166-1 alpha-2 country code (e.g. "AT", "FR")
GET

/v1/climbs/:id

climbs:read

Get a single climb note with its full repeat-ascent history. Supports the same ?include=gps option as the list endpoint.

Query parameters

ParameterTypeDescription
includestringOptional fields. gps fetches GPS coordinates from the central route database.

Example

curl "https://api-hoxktcdqvq-uc.a.run.app/v1/climbs/abc123?include=gps" \
  -H "X-API-Key: cnl_your_key_here"

Response 200 OK

Same fields as the list item, plus an ascents array (and gps if requested):

{
  "id": "abc123",
  "route": "Biographie",
  "centralRouteID": "rTeho8rot8CTj9kEvZE4",
  "gps": { "latitude": 44.1823, "longitude": 5.9714, "country": "FR" },
  "ascents": [
    {
      "id": "asc-uuid-1",
      "sendType": "Redpoint",
      "date": "2026-09-10T00:00:00.000Z",
      "notes": "Even better second time"
    }
  ]
}

Ascent object fields

FieldTypeDescription
idstringAscent UUID
sendTypestringSend type for this repeat ascent
dateISO 8601 | nullDate of the repeat ascent
notesstring | nullNotes specific to this ascent

Returns 404 if the climb does not exist or has been deleted.

GET

/v1/training

training:read

List training sessions, ordered by date ascending (or updatedAt when since is used).

Query parameters

ParameterTypeDescription
limitintegerResults per page (1–100, default 50)
afterstringPagination cursor
fromISO 8601 dateSessions on or after this date
toISO 8601 dateSessions on or before this date
sinceISO 8601 datetimeIncremental sync — records updated after this timestamp

Example

curl "https://api-hoxktcdqvq-uc.a.run.app/v1/training?from=2026-01-01" \
  -H "X-API-Key: cnl_your_key_here"

Response 200 OK

{
  "data": [
    {
      "id": "ts-uuid-1",
      "type": "Hangboard",
      "duration": 45,
      "intensity": 4,
      "notes": "Max hangs protocol, 20mm edge.",
      "date": "2026-03-20T00:00:00.000Z",
      "updatedAt": "2026-03-20T19:00:00.000Z"
    }
  ],
  "nextCursor": null,
  "hasMore": false
}

Training session fields

FieldTypeDescription
idstringSession UUID
typestringHangboard · Campus Board · Gym Session · Running · Yoga · or custom
durationintegerDuration in minutes
intensityinteger1–5 intensity rating
notesstring | nullFree-text notes
dateISO 8601 | nullSession date
updatedAtISO 8601 | nullLast modification timestamp
GET

/v1/goals

goals:read

List goals. Supports since for incremental sync. No pagination (goal lists are typically small).

Query parameters

ParameterTypeDescription
sinceISO 8601 datetimeOnly goals updated after this timestamp

Example

curl "https://api-hoxktcdqvq-uc.a.run.app/v1/goals" \
  -H "X-API-Key: cnl_your_key_here"

Response 200 OK

{
  "data": [
    {
      "id": "goal-uuid-1",
      "type": "gradeTarget",
      "target": 0,
      "periodIsWeekly": false,
      "gradeTarget": "8a",
      "deadline": "2026-12-31T00:00:00.000Z",
      "achievedAt": null,
      "isArchived": false,
      "completedPeriods": [],
      "updatedAt": "2026-04-01T10:00:00.000Z"
    }
  ]
}

Goal fields

FieldTypeDescription
idstringGoal UUID
typestringgradeTarget · climbingDays · trainingDays
targetintegerNumeric target (e.g. days per period)
periodIsWeeklybooleantrue = weekly, false = monthly
gradeTargetstring | nullTarget grade (grade-type goals only)
deadlineISO 8601 | nullOptional deadline
achievedAtISO 8601 | nullWhen the goal was first achieved
isArchivedbooleanWhether the goal has been archived
completedPeriodsstring[]ISO week/month strings for completed periods
updatedAtISO 8601 | nullLast modification timestamp

6. Incremental Sync

All list endpoints support a since parameter for efficient incremental sync. Pass the updatedAt of the most recently seen record to fetch only records changed after that point. When using since, results are ordered by updatedAt ascending and the parameter is mutually exclusive with from/to.

# 1. Initial full fetch — store the max updatedAt from the response
curl "https://api-hoxktcdqvq-uc.a.run.app/v1/climbs?limit=100" \
  -H "X-API-Key: cnl_your_key_here"

# 2. On subsequent runs, pass it as `since`
curl "https://api-hoxktcdqvq-uc.a.run.app/v1/climbs?since=2026-04-30T12:00:00.000Z" \
  -H "X-API-Key: cnl_your_key_here"
💡 Remember to paginate even when using since — a large batch update could produce more than 100 changed records at once.

7. Examples

Fetch all your sends from this year (curl)

API_KEY="cnl_your_key_here"
BASE="https://api-hoxktcdqvq-uc.a.run.app"

curl "$BASE/v1/climbs?from=2026-01-01&limit=100" \
  -H "X-API-Key: $API_KEY"

Fetch all climbs with pagination (Python)

import requests

API_KEY = "cnl_your_key_here"
BASE    = "https://api-hoxktcdqvq-uc.a.run.app"
HEADERS = {"X-API-Key": API_KEY}

# Fetch all climbs (handles pagination automatically)
climbs, cursor = [], None
while True:
    params = {"limit": 100}
    if cursor:
        params["after"] = cursor
    r = requests.get(f"{BASE}/v1/climbs", headers=HEADERS, params=params)
    r.raise_for_status()
    body = r.json()
    climbs.extend(body["data"])
    cursor = body.get("nextCursor")
    if not body.get("hasMore"):
        break

sends = [c for c in climbs if c["sendType"] != "Project"]
print(f"Total sends : {len(sends)}")
print(f"Hardest grade: {max((c['difficulty'] for c in sends), default='—')}")
ℹ️ Generate your API key at sendlog.at/app → My Account → API Keys. You choose which scopes to grant and can revoke the key at any time.
← Return to Home Page

Last Updated: April 2026 · API v1